diff --git a/buffer/src/language.rs b/buffer/src/language.rs index 98f75d2831a719624940a814fdbcd608f3ae6975..365050fd3a314134eb9bc944ddf1eafbbe57cf16 100644 --- a/buffer/src/language.rs +++ b/buffer/src/language.rs @@ -1,7 +1,7 @@ use crate::{HighlightMap, SyntaxTheme}; use parking_lot::Mutex; use serde::Deserialize; -use std::str; +use std::{path::Path, str, sync::Arc}; use tree_sitter::{Language as Grammar, Query}; pub use tree_sitter::{Parser, Tree}; @@ -25,6 +25,41 @@ pub struct Language { pub highlight_map: Mutex, } +#[derive(Default)] +pub struct LanguageRegistry { + languages: Vec>, +} + +impl LanguageRegistry { + pub fn new() -> Self { + Self::default() + } + + pub fn add(&mut self, language: Arc) { + self.languages.push(language); + } + + pub fn set_theme(&self, theme: &SyntaxTheme) { + for language in &self.languages { + language.set_theme(theme); + } + } + + pub fn select_language(&self, path: impl AsRef) -> Option<&Arc> { + let path = path.as_ref(); + let filename = path.file_name().and_then(|name| name.to_str()); + let extension = path.extension().and_then(|name| name.to_str()); + let path_suffixes = [extension, filename]; + self.languages.iter().find(|language| { + language + .config + .path_suffixes + .iter() + .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))) + }) + } +} + impl Language { pub fn name(&self) -> &str { self.config.name.as_str() @@ -38,3 +73,63 @@ impl Language { *self.highlight_map.lock() = HighlightMap::new(self.highlight_query.capture_names(), theme); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_select_language() { + let grammar = tree_sitter_rust::language(); + let registry = LanguageRegistry { + languages: vec![ + Arc::new(Language { + config: LanguageConfig { + name: "Rust".to_string(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + grammar, + highlight_query: Query::new(grammar, "").unwrap(), + brackets_query: Query::new(grammar, "").unwrap(), + highlight_map: Default::default(), + }), + Arc::new(Language { + config: LanguageConfig { + name: "Make".to_string(), + path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], + ..Default::default() + }, + grammar, + highlight_query: Query::new(grammar, "").unwrap(), + brackets_query: Query::new(grammar, "").unwrap(), + highlight_map: Default::default(), + }), + ], + }; + + // matching file extension + assert_eq!( + registry.select_language("zed/lib.rs").map(|l| l.name()), + Some("Rust") + ); + assert_eq!( + registry.select_language("zed/lib.mk").map(|l| l.name()), + Some("Make") + ); + + // matching filename + assert_eq!( + registry.select_language("zed/Makefile").map(|l| l.name()), + Some("Make") + ); + + // matching suffix that is not the full file extension or filename + assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None); + assert_eq!( + registry.select_language("zed/a.cars").map(|l| l.name()), + None + ); + assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None); + } +} diff --git a/buffer/src/lib.rs b/buffer/src/lib.rs index 27b42411387d58835787e719187c4ce25b8d4dab..2c080ca57dcc7db2a800704f303db24bad9e24dd 100644 --- a/buffer/src/lib.rs +++ b/buffer/src/lib.rs @@ -15,7 +15,7 @@ use clock::ReplicaId; use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task}; pub use highlight_map::{HighlightId, HighlightMap}; use language::Tree; -pub use language::{Language, LanguageConfig}; +pub use language::{Language, LanguageConfig, LanguageRegistry}; use lazy_static::lazy_static; use operation_queue::OperationQueue; use parking_lot::Mutex; diff --git a/server/src/rpc.rs b/server/src/rpc.rs index 85b77c6155238a8389c525ef52019fa41a00aa26..dbb8b02f6338134855a6b800a60c9f76790cee4c 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -975,10 +975,10 @@ mod tests { time::Duration, }; use zed::{ + buffer::LanguageRegistry, channel::{Channel, ChannelDetails, ChannelList}, editor::{Editor, EditorStyle, Insert}, fs::{FakeFs, Fs as _}, - language::LanguageRegistry, people_panel::JoinWorktree, project::ProjectPath, rpc::{self, Client, Credentials, EstablishConnectionError}, diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 9c384d59d9e512874f6e32d19f32ec6ad880683d..172c04456f8eabcc0e2456c03f515ce8d5c3f885 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -2753,7 +2753,11 @@ impl SelectionExt for Selection { #[cfg(test)] mod tests { use super::*; - use crate::{editor::Point, language::LanguageRegistry, settings, test::sample_text}; + use crate::{ + editor::Point, + settings, + test::{self, sample_text}, + }; use buffer::History; use unindent::Unindent; @@ -4232,9 +4236,9 @@ mod tests { #[gpui::test] async fn test_select_larger_smaller_syntax_node(mut cx: gpui::TestAppContext) { - let settings = cx.read(settings::test).1; - let languages = LanguageRegistry::new(); - let lang = languages.select_language("z.rs"); + let app_state = cx.update(test::test_app_state); + + let lang = app_state.languages.select_language("z.rs"); let text = r#" use mod1::mod2::{mod3, mod4}; @@ -4247,7 +4251,7 @@ mod tests { let history = History::new(text.into()); Buffer::from_history(0, history, None, lang.cloned(), cx) }); - let (_, view) = cx.add_window(|cx| build_editor(buffer, settings.clone(), cx)); + let (_, view) = cx.add_window(|cx| build_editor(buffer, app_state.settings.clone(), cx)); view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing()) .await; diff --git a/zed/src/language.rs b/zed/src/language.rs index 5f36b29b3dccd6b09db338caa1b928634277dcbd..f86fb0d44d7757537ce608d80305fd54d8b35e20 100644 --- a/zed/src/language.rs +++ b/zed/src/language.rs @@ -1,128 +1,36 @@ -use buffer::{HighlightMap, Language, SyntaxTheme}; +use buffer::{HighlightMap, Language, LanguageRegistry}; use parking_lot::Mutex; use rust_embed::RustEmbed; -use std::{path::Path, str, sync::Arc}; +use std::{str, sync::Arc}; use tree_sitter::Query; -pub use tree_sitter::{Parser, Tree}; #[derive(RustEmbed)] #[folder = "languages"] -pub struct LanguageDir; +struct LanguageDir; -pub struct LanguageRegistry { - languages: Vec>, +pub fn build_language_registry() -> LanguageRegistry { + let mut languages = LanguageRegistry::default(); + languages.add(Arc::new(rust())); + languages } -impl LanguageRegistry { - pub fn new() -> Self { - let grammar = tree_sitter_rust::language(); - let rust_config = - toml::from_slice(&LanguageDir::get("rust/config.toml").unwrap().data).unwrap(); - let rust_language = Language { - config: rust_config, - grammar, - highlight_query: Self::load_query(grammar, "rust/highlights.scm"), - brackets_query: Self::load_query(grammar, "rust/brackets.scm"), - highlight_map: Mutex::new(HighlightMap::default()), - }; - - Self { - languages: vec![Arc::new(rust_language)], - } - } - - pub fn set_theme(&self, theme: &SyntaxTheme) { - for language in &self.languages { - language.set_theme(theme); - } - } - - pub fn select_language(&self, path: impl AsRef) -> Option<&Arc> { - let path = path.as_ref(); - let filename = path.file_name().and_then(|name| name.to_str()); - let extension = path.extension().and_then(|name| name.to_str()); - let path_suffixes = [extension, filename]; - self.languages.iter().find(|language| { - language - .config - .path_suffixes - .iter() - .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))) - }) - } - - fn load_query(grammar: tree_sitter::Language, path: &str) -> Query { - Query::new( - grammar, - str::from_utf8(&LanguageDir::get(path).unwrap().data).unwrap(), - ) - .unwrap() +pub fn rust() -> Language { + let grammar = tree_sitter_rust::language(); + let rust_config = + toml::from_slice(&LanguageDir::get("rust/config.toml").unwrap().data).unwrap(); + Language { + config: rust_config, + grammar, + highlight_query: load_query(grammar, "rust/highlights.scm"), + brackets_query: load_query(grammar, "rust/brackets.scm"), + highlight_map: Mutex::new(HighlightMap::default()), } } -impl Default for LanguageRegistry { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use buffer::LanguageConfig; - - #[test] - fn test_select_language() { - let grammar = tree_sitter_rust::language(); - let registry = LanguageRegistry { - languages: vec![ - Arc::new(Language { - config: LanguageConfig { - name: "Rust".to_string(), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - grammar, - highlight_query: Query::new(grammar, "").unwrap(), - brackets_query: Query::new(grammar, "").unwrap(), - highlight_map: Default::default(), - }), - Arc::new(Language { - config: LanguageConfig { - name: "Make".to_string(), - path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], - ..Default::default() - }, - grammar, - highlight_query: Query::new(grammar, "").unwrap(), - brackets_query: Query::new(grammar, "").unwrap(), - highlight_map: Default::default(), - }), - ], - }; - - // matching file extension - assert_eq!( - registry.select_language("zed/lib.rs").map(|l| l.name()), - Some("Rust") - ); - assert_eq!( - registry.select_language("zed/lib.mk").map(|l| l.name()), - Some("Make") - ); - - // matching filename - assert_eq!( - registry.select_language("zed/Makefile").map(|l| l.name()), - Some("Make") - ); - - // matching suffix that is not the full file extension or filename - assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None); - assert_eq!( - registry.select_language("zed/a.cars").map(|l| l.name()), - None - ); - assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None); - } +fn load_query(grammar: tree_sitter::Language, path: &str) -> Query { + Query::new( + grammar, + str::from_utf8(&LanguageDir::get(path).unwrap().data).unwrap(), + ) + .unwrap() } diff --git a/zed/src/lib.rs b/zed/src/lib.rs index 32fa9a4c363ce4b0f4c629e4df08748a2c96ed96..9ab6a3b9ae5caab38c0a1accbef15fe570a80ff0 100644 --- a/zed/src/lib.rs +++ b/zed/src/lib.rs @@ -23,6 +23,8 @@ pub mod workspace; pub mod worktree; use crate::util::TryFutureExt; +pub use buffer; +use buffer::LanguageRegistry; use channel::ChannelList; use gpui::{action, keymap::Binding, ModelHandle}; use parking_lot::Mutex; @@ -41,7 +43,7 @@ const MIN_FONT_SIZE: f32 = 6.0; pub struct AppState { pub settings_tx: Arc>>, pub settings: watch::Receiver, - pub languages: Arc, + pub languages: Arc, pub themes: Arc, pub rpc: Arc, pub user_store: ModelHandle, diff --git a/zed/src/main.rs b/zed/src/main.rs index 6070f7eab683421a850c8251afcd3756ed145670..c7370626c14b32dccb0cb561338bb672433a2286 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -32,7 +32,7 @@ fn main() { let themes = settings::ThemeRegistry::new(Assets, app.font_cache()); let (settings_tx, settings) = settings::channel(&app.font_cache(), &themes).unwrap(); - let languages = Arc::new(language::LanguageRegistry::new()); + let languages = Arc::new(language::build_language_registry()); languages.set_theme(&settings.borrow().theme.syntax); app.run(move |cx| { diff --git a/zed/src/project.rs b/zed/src/project.rs index 8c6db5e259ebf18e74f6c04d94121abc16c0648f..0a7338fc34378a5d4ceb895cbd4dbd231c077869 100644 --- a/zed/src/project.rs +++ b/zed/src/project.rs @@ -1,13 +1,13 @@ use crate::{ fs::Fs, fuzzy::{self, PathMatch}, - language::LanguageRegistry, rpc::Client, util::TryFutureExt as _, worktree::{self, Worktree}, AppState, }; use anyhow::Result; +use buffer::LanguageRegistry; use futures::Future; use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; use std::{ diff --git a/zed/src/test.rs b/zed/src/test.rs index d3044e8fc0f64fb36975f5ec6b48e2d42c88f79b..a97d44a2178a78a3036b614dc2dafe5968344927 100644 --- a/zed/src/test.rs +++ b/zed/src/test.rs @@ -3,13 +3,14 @@ use crate::{ channel::ChannelList, fs::FakeFs, http::{HttpClient, Request, Response, ServerResponse}, - language::LanguageRegistry, + language, rpc::{self, Client, Credentials, EstablishConnectionError}, settings::{self, ThemeRegistry}, user::UserStore, AppState, }; use anyhow::{anyhow, Result}; +use buffer::LanguageRegistry; use futures::{future::BoxFuture, Future}; use gpui::{AsyncAppContext, Entity, ModelHandle, MutableAppContext, TestAppContext}; use parking_lot::Mutex; @@ -83,7 +84,8 @@ fn write_tree(path: &Path, tree: serde_json::Value) { pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { let (settings_tx, settings) = settings::test(cx); - let languages = Arc::new(LanguageRegistry::new()); + let mut languages = LanguageRegistry::new(); + languages.add(Arc::new(language::rust())); let themes = ThemeRegistry::new(Assets, cx.font_cache().clone()); let rpc = rpc::Client::new(); let http = FakeHttpClient::new(|_| async move { Ok(ServerResponse::new(404)) }); @@ -92,7 +94,7 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { settings_tx: Arc::new(Mutex::new(settings_tx)), settings, themes, - languages: languages.clone(), + languages: Arc::new(languages), channel_list: cx.add_model(|cx| ChannelList::new(user_store.clone(), rpc.clone(), cx)), rpc, user_store, diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index a769443bfec8fe2a04455fa554c1b9d7dc571c10..4f2d9149edfcfc401cb622200e6ac96e45c6292c 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -4,13 +4,12 @@ use self::ignore::IgnoreStack; use crate::{ fs::{self, Fs}, fuzzy::CharBag, - language::LanguageRegistry, rpc::{self, proto, Status}, util::{Bias, TryFutureExt}, }; use ::ignore::gitignore::{Gitignore, GitignoreBuilder}; use anyhow::{anyhow, Result}; -use buffer::{self, Buffer, History, Operation, Rope}; +use buffer::{self, Buffer, History, LanguageRegistry, Operation, Rope}; use clock::ReplicaId; use futures::{Stream, StreamExt}; use gpui::{