From 64098247cbf215f46e8e57c9c34ef8260eeb474a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 22 Feb 2022 10:35:20 -0800 Subject: [PATCH] Allow languages to be registered at any time Co-Authored-By: Nathan Sobo Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map.rs | 4 +- crates/language/src/language.rs | 58 ++++-- crates/language/src/tests.rs | 14 +- crates/project/src/project.rs | 13 +- crates/server/src/rpc.rs | 286 +++++++++++++++-------------- crates/workspace/src/lsp_status.rs | 2 +- crates/workspace/src/settings.rs | 12 +- crates/zed/src/test.rs | 21 +-- crates/zed/src/zed.rs | 4 +- 9 files changed, 222 insertions(+), 192 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 24fd53155886d5d980af6bec1639ed74113131e3..dd23799175d4b7b04415df07ae99a0921b678e4f 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -864,7 +864,7 @@ mod tests { let language = Arc::new( Language::new( LanguageConfig { - name: "Test".to_string(), + name: "Test".into(), path_suffixes: vec![".test".to_string()], ..Default::default() }, @@ -951,7 +951,7 @@ mod tests { let language = Arc::new( Language::new( LanguageConfig { - name: "Test".to_string(), + name: "Test".into(), path_suffixes: vec![".test".to_string()], ..Default::default() }, diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index b6bb8230fb4aa74e213fdd8bf6867e91cfbd52b8..57a4fe001a7228767e29f261b064451bcc6f8ceb 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -16,7 +16,7 @@ use futures::{ use gpui::{AppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; use serde::Deserialize; use std::{ cell::RefCell, @@ -45,7 +45,7 @@ thread_local! { lazy_static! { pub static ref PLAIN_TEXT: Arc = Arc::new(Language::new( LanguageConfig { - name: "Plain Text".to_string(), + name: "Plain Text".into(), path_suffixes: Default::default(), brackets: Default::default(), line_comment: None, @@ -92,15 +92,27 @@ pub struct CodeLabel { pub filter_range: Range, } -#[derive(Default, Deserialize)] +#[derive(Deserialize)] pub struct LanguageConfig { - pub name: String, + pub name: Arc, pub path_suffixes: Vec, pub brackets: Vec, pub line_comment: Option, pub language_server: Option, } +impl Default for LanguageConfig { + fn default() -> Self { + Self { + name: "".into(), + path_suffixes: Default::default(), + brackets: Default::default(), + line_comment: Default::default(), + language_server: Default::default(), + } + } +} + #[derive(Default, Deserialize)] pub struct LanguageServerConfig { pub disk_based_diagnostic_sources: HashSet, @@ -151,7 +163,7 @@ pub enum LanguageServerBinaryStatus { } pub struct LanguageRegistry { - languages: Vec>, + languages: RwLock>>, language_server_download_dir: Option>, lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc, LanguageServerBinaryStatus)>, @@ -168,12 +180,12 @@ impl LanguageRegistry { } } - pub fn add(&mut self, language: Arc) { - self.languages.push(language.clone()); + pub fn add(&self, language: Arc) { + self.languages.write().push(language.clone()); } pub fn set_theme(&self, theme: &SyntaxTheme) { - for language in &self.languages { + for language in self.languages.read().iter() { language.set_theme(theme); } } @@ -182,24 +194,30 @@ impl LanguageRegistry { self.language_server_download_dir = Some(path.into()); } - pub fn get_language(&self, name: &str) -> Option<&Arc> { + pub fn get_language(&self, name: &str) -> Option> { self.languages + .read() .iter() - .find(|language| language.name() == name) + .find(|language| language.name().as_ref() == name) + .cloned() } - pub fn select_language(&self, path: impl AsRef) -> Option<&Arc> { + pub fn select_language(&self, path: impl AsRef) -> Option> { 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()))) - }) + self.languages + .read() + .iter() + .find(|language| { + language + .config + .path_suffixes + .iter() + .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))) + }) + .cloned() } pub fn start_language_server( @@ -401,8 +419,8 @@ impl Language { self } - pub fn name(&self) -> &str { - self.config.name.as_str() + pub fn name(&self) -> Arc { + self.config.name.clone() } pub fn line_comment_prefix(&self) -> Option<&str> { diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 1adcca91a5c3cb6b8276d34e1e369c494359f715..9fd5d693ff65de0de342d6c2b3ac86d70d5d188c 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -24,10 +24,10 @@ fn init_logger() { #[gpui::test] fn test_select_language() { - let mut registry = LanguageRegistry::new(); + let registry = LanguageRegistry::new(); registry.add(Arc::new(Language::new( LanguageConfig { - name: "Rust".to_string(), + name: "Rust".into(), path_suffixes: vec!["rs".to_string()], ..Default::default() }, @@ -35,7 +35,7 @@ fn test_select_language() { ))); registry.add(Arc::new(Language::new( LanguageConfig { - name: "Make".to_string(), + name: "Make".into(), path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], ..Default::default() }, @@ -45,17 +45,17 @@ fn test_select_language() { // matching file extension assert_eq!( registry.select_language("zed/lib.rs").map(|l| l.name()), - Some("Rust") + Some("Rust".into()) ); assert_eq!( registry.select_language("zed/lib.mk").map(|l| l.name()), - Some("Make") + Some("Make".into()) ); // matching filename assert_eq!( registry.select_language("zed/Makefile").map(|l| l.name()), - Some("Make") + Some("Make".into()) ); // matching suffix that is not the full file extension or filename @@ -1354,7 +1354,7 @@ impl Buffer { fn rust_lang() -> Language { Language::new( LanguageConfig { - name: "Rust".to_string(), + name: "Rust".into(), path_suffixes: vec!["rs".to_string()], language_server: None, ..Default::default() diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 4069704b0c4971c939a72afdba3feb1e872a1e71..19500f322969f06bb13e60e1c04fdea1a73b772e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -387,6 +387,11 @@ impl Project { .any(|buffer| matches!(buffer, OpenBuffer::Loading(_))) } + #[cfg(any(test, feature = "test-support"))] + pub fn languages(&self) -> &Arc { + &self.languages + } + pub fn fs(&self) -> &Arc { &self.fs } @@ -817,7 +822,7 @@ impl Project { }; // If the buffer has a language, set it and start/assign the language server - if let Some(language) = self.languages.select_language(&full_path).cloned() { + if let Some(language) = self.languages.select_language(&full_path) { buffer.update(cx, |buffer, cx| { buffer.set_language(Some(language.clone()), cx); }); @@ -3386,7 +3391,7 @@ mod tests { let language = Arc::new(Language::new( LanguageConfig { - name: "Rust".to_string(), + name: "Rust".into(), path_suffixes: vec!["rs".to_string()], language_server: Some(language_server_config), ..Default::default() @@ -3532,7 +3537,7 @@ mod tests { let (language_server_config, mut fake_servers) = LanguageServerConfig::fake(); let language = Arc::new(Language::new( LanguageConfig { - name: "Rust".to_string(), + name: "Rust".into(), path_suffixes: vec!["rs".to_string()], language_server: Some(language_server_config), ..Default::default() @@ -4425,7 +4430,7 @@ mod tests { let (language_server_config, mut fake_servers) = LanguageServerConfig::fake(); let language = Arc::new(Language::new( LanguageConfig { - name: "Rust".to_string(), + name: "Rust".into(), path_suffixes: vec!["rs".to_string()], language_server: Some(language_server_config), ..Default::default() diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 8de7da219a87c79caa7957a952d85ad8770d9546..5cc21918629c4ad3b42dcc3cc13691624ddd6b58 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -2035,7 +2035,7 @@ mod tests { .unwrap() .add(Arc::new(Language::new( LanguageConfig { - name: "Rust".to_string(), + name: "Rust".into(), path_suffixes: vec!["rs".to_string()], language_server: Some(language_server_config), ..Default::default() @@ -2266,7 +2266,7 @@ mod tests { .unwrap() .add(Arc::new(Language::new( LanguageConfig { - name: "Rust".to_string(), + name: "Rust".into(), path_suffixes: vec!["rs".to_string()], language_server: Some(language_server_config), ..Default::default() @@ -2468,7 +2468,7 @@ mod tests { .unwrap() .add(Arc::new(Language::new( LanguageConfig { - name: "Rust".to_string(), + name: "Rust".into(), path_suffixes: vec!["rs".to_string()], language_server: Some(language_server_config), ..Default::default() @@ -2585,7 +2585,7 @@ mod tests { .unwrap() .add(Arc::new(Language::new( LanguageConfig { - name: "Rust".to_string(), + name: "Rust".into(), path_suffixes: vec!["rs".to_string()], language_server: Some(language_server_config), ..Default::default() @@ -2733,7 +2733,7 @@ mod tests { .unwrap() .add(Arc::new(Language::new( LanguageConfig { - name: "Rust".to_string(), + name: "Rust".into(), path_suffixes: vec!["rs".to_string()], language_server: Some(language_server_config), ..Default::default() @@ -2834,7 +2834,7 @@ mod tests { .unwrap() .add(Arc::new(Language::new( LanguageConfig { - name: "Rust".to_string(), + name: "Rust".into(), path_suffixes: vec!["rs".to_string()], language_server: Some(language_server_config), ..Default::default() @@ -3073,7 +3073,7 @@ mod tests { .unwrap() .add(Arc::new(Language::new( LanguageConfig { - name: "Rust".to_string(), + name: "Rust".into(), path_suffixes: vec!["rs".to_string()], language_server: Some(language_server_config), ..Default::default() @@ -3842,50 +3842,8 @@ mod tests { let rng = Rc::new(RefCell::new(rng)); - let mut host_lang_registry = Arc::new(LanguageRegistry::new()); let guest_lang_registry = Arc::new(LanguageRegistry::new()); - - // Set up a fake language server. - let (mut language_server_config, _fake_language_servers) = LanguageServerConfig::fake(); - language_server_config.set_fake_initializer(|fake_server| { - fake_server.handle_request::(|_| { - Some(lsp::CompletionResponse::Array(vec![lsp::CompletionItem { - text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { - range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)), - new_text: "the-new-text".to_string(), - })), - ..Default::default() - }])) - }); - - fake_server.handle_request::(|_| { - Some(vec![lsp::CodeActionOrCommand::CodeAction( - lsp::CodeAction { - title: "the-code-action".to_string(), - ..Default::default() - }, - )]) - }); - - fake_server.handle_request::(|params| { - Some(lsp::PrepareRenameResponse::Range(lsp::Range::new( - params.position, - params.position, - ))) - }); - }); - - Arc::get_mut(&mut host_lang_registry) - .unwrap() - .add(Arc::new(Language::new( - LanguageConfig { - name: "Rust".to_string(), - path_suffixes: vec!["rs".to_string()], - language_server: Some(language_server_config), - ..Default::default() - }, - None, - ))); + let (language_server_config, _fake_language_servers) = LanguageServerConfig::fake(); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -3914,7 +3872,7 @@ mod tests { Project::local( host.client.clone(), host.user_store.clone(), - host_lang_registry.clone(), + Arc::new(LanguageRegistry::new()), fs.clone(), cx, ) @@ -3939,6 +3897,7 @@ mod tests { clients.push(cx.foreground().spawn(host.simulate_host( host_project.clone(), + language_server_config, operations.clone(), max_operations, rng.clone(), @@ -4268,113 +4227,160 @@ mod tests { ) } - async fn simulate_host( + fn simulate_host( mut self, project: ModelHandle, + mut language_server_config: LanguageServerConfig, operations: Rc>, max_operations: usize, rng: Rc>, mut cx: TestAppContext, - ) -> (Self, TestAppContext) { - let fs = project.read_with(&cx, |project, _| project.fs().clone()); - let mut files: Vec = Default::default(); - while operations.get() < max_operations { - operations.set(operations.get() + 1); + ) -> impl Future { + // Set up a fake language server. + language_server_config.set_fake_initializer(|fake_server| { + fake_server.handle_request::(|_| { + Some(lsp::CompletionResponse::Array(vec![lsp::CompletionItem { + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 0), + lsp::Position::new(0, 0), + ), + new_text: "the-new-text".to_string(), + })), + ..Default::default() + }])) + }); - let distribution = rng.borrow_mut().gen_range(0..100); - match distribution { - 0..=20 if !files.is_empty() => { - let mut path = files.choose(&mut *rng.borrow_mut()).unwrap().as_path(); - while let Some(parent_path) = path.parent() { - path = parent_path; - if rng.borrow_mut().gen() { - break; + fake_server.handle_request::(|_| { + Some(vec![lsp::CodeActionOrCommand::CodeAction( + lsp::CodeAction { + title: "the-code-action".to_string(), + ..Default::default() + }, + )]) + }); + + fake_server.handle_request::(|params| { + Some(lsp::PrepareRenameResponse::Range(lsp::Range::new( + params.position, + params.position, + ))) + }); + }); + + project.update(&mut cx, |project, _| { + project.languages().add(Arc::new(Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + language_server: Some(language_server_config), + ..Default::default() + }, + None, + ))); + }); + + async move { + let fs = project.read_with(&cx, |project, _| project.fs().clone()); + let mut files: Vec = Default::default(); + while operations.get() < max_operations { + operations.set(operations.get() + 1); + + let distribution = rng.borrow_mut().gen_range(0..100); + match distribution { + 0..=20 if !files.is_empty() => { + let mut path = files.choose(&mut *rng.borrow_mut()).unwrap().as_path(); + while let Some(parent_path) = path.parent() { + path = parent_path; + if rng.borrow_mut().gen() { + break; + } } - } - log::info!("Host: find/create local worktree {:?}", path); - project - .update(&mut cx, |project, cx| { - project.find_or_create_local_worktree(path, false, cx) - }) - .await - .unwrap(); - } - 10..=80 if !files.is_empty() => { - let buffer = if self.buffers.is_empty() || rng.borrow_mut().gen() { - let file = files.choose(&mut *rng.borrow_mut()).unwrap(); - let (worktree, path) = project - .update(&mut cx, |project, cx| { - project.find_or_create_local_worktree(file, false, cx) - }) - .await - .unwrap(); - let project_path = - worktree.read_with(&cx, |worktree, _| (worktree.id(), path)); - log::info!("Host: opening path {:?}", project_path); - let buffer = project + log::info!("Host: find/create local worktree {:?}", path); + project .update(&mut cx, |project, cx| { - project.open_buffer(project_path, cx) + project.find_or_create_local_worktree(path, false, cx) }) .await .unwrap(); - self.buffers.insert(buffer.clone()); - buffer - } else { - self.buffers - .iter() - .choose(&mut *rng.borrow_mut()) - .unwrap() - .clone() - }; - - if rng.borrow_mut().gen_bool(0.1) { - cx.update(|cx| { - log::info!( - "Host: dropping buffer {:?}", - buffer.read(cx).file().unwrap().full_path(cx) - ); - self.buffers.remove(&buffer); - drop(buffer); - }); - } else { - buffer.update(&mut cx, |buffer, cx| { - log::info!( - "Host: updating buffer {:?}", - buffer.file().unwrap().full_path(cx) - ); - buffer.randomly_edit(&mut *rng.borrow_mut(), 5, cx) - }); - } - } - _ => loop { - let path_component_count = rng.borrow_mut().gen_range(1..=5); - let mut path = PathBuf::new(); - path.push("/"); - for _ in 0..path_component_count { - let letter = rng.borrow_mut().gen_range(b'a'..=b'z'); - path.push(std::str::from_utf8(&[letter]).unwrap()); } - path.set_extension("rs"); - let parent_path = path.parent().unwrap(); - - log::info!("Host: creating file {:?}", path); - if fs.create_dir(&parent_path).await.is_ok() - && fs.create_file(&path, Default::default()).await.is_ok() - { - files.push(path); - break; - } else { - log::info!("Host: cannot create file"); + 10..=80 if !files.is_empty() => { + let buffer = if self.buffers.is_empty() || rng.borrow_mut().gen() { + let file = files.choose(&mut *rng.borrow_mut()).unwrap(); + let (worktree, path) = project + .update(&mut cx, |project, cx| { + project.find_or_create_local_worktree(file, false, cx) + }) + .await + .unwrap(); + let project_path = + worktree.read_with(&cx, |worktree, _| (worktree.id(), path)); + log::info!("Host: opening path {:?}", project_path); + let buffer = project + .update(&mut cx, |project, cx| { + project.open_buffer(project_path, cx) + }) + .await + .unwrap(); + self.buffers.insert(buffer.clone()); + buffer + } else { + self.buffers + .iter() + .choose(&mut *rng.borrow_mut()) + .unwrap() + .clone() + }; + + if rng.borrow_mut().gen_bool(0.1) { + cx.update(|cx| { + log::info!( + "Host: dropping buffer {:?}", + buffer.read(cx).file().unwrap().full_path(cx) + ); + self.buffers.remove(&buffer); + drop(buffer); + }); + } else { + buffer.update(&mut cx, |buffer, cx| { + log::info!( + "Host: updating buffer {:?}", + buffer.file().unwrap().full_path(cx) + ); + buffer.randomly_edit(&mut *rng.borrow_mut(), 5, cx) + }); + } } - }, + _ => loop { + let path_component_count = rng.borrow_mut().gen_range(1..=5); + let mut path = PathBuf::new(); + path.push("/"); + for _ in 0..path_component_count { + let letter = rng.borrow_mut().gen_range(b'a'..=b'z'); + path.push(std::str::from_utf8(&[letter]).unwrap()); + } + path.set_extension("rs"); + let parent_path = path.parent().unwrap(); + + log::info!("Host: creating file {:?}", path); + if fs.create_dir(&parent_path).await.is_ok() + && fs.create_file(&path, Default::default()).await.is_ok() + { + files.push(path); + break; + } else { + log::info!("Host: cannot create file"); + } + }, + } + + cx.background().simulate_random_delay().await; } - cx.background().simulate_random_delay().await; + self.project = Some(project); + (self, cx) } - - self.project = Some(project); - (self, cx) } pub async fn simulate_guest( diff --git a/crates/workspace/src/lsp_status.rs b/crates/workspace/src/lsp_status.rs index 093f10b1435fa0aa528c4d7cf2add2c0baa34089..ee61ecf24d9fc1e509377f460689dc68d9885563 100644 --- a/crates/workspace/src/lsp_status.rs +++ b/crates/workspace/src/lsp_status.rs @@ -37,7 +37,7 @@ impl LspStatus { &mut this.downloading, &mut this.failed, ] { - vector.retain(|name| name != language.name()); + vector.retain(|name| name != language.name().as_ref()); } match event { diff --git a/crates/workspace/src/settings.rs b/crates/workspace/src/settings.rs index a1598836a48c626591ced292a3bd53622437863f..7073c1144c7f4b7bd5bf432000bff2cba188469c 100644 --- a/crates/workspace/src/settings.rs +++ b/crates/workspace/src/settings.rs @@ -11,7 +11,7 @@ pub struct Settings { pub tab_size: usize, soft_wrap: SoftWrap, preferred_line_length: u32, - overrides: HashMap, + overrides: HashMap, Override>, pub theme: Arc, } @@ -50,21 +50,25 @@ impl Settings { self } - pub fn with_overrides(mut self, language_name: impl Into, overrides: Override) -> Self { + pub fn with_overrides( + mut self, + language_name: impl Into>, + overrides: Override, + ) -> Self { self.overrides.insert(language_name.into(), overrides); self } pub fn soft_wrap(&self, language: Option<&Arc>) -> SoftWrap { language - .and_then(|language| self.overrides.get(language.name())) + .and_then(|language| self.overrides.get(language.name().as_ref())) .and_then(|settings| settings.soft_wrap) .unwrap_or(self.soft_wrap) } pub fn preferred_line_length(&self, language: Option<&Arc>) -> u32 { language - .and_then(|language| self.overrides.get(language.name())) + .and_then(|language| self.overrides.get(language.name().as_ref())) .and_then(|settings| settings.preferred_line_length) .unwrap_or(self.preferred_line_length) } diff --git a/crates/zed/src/test.rs b/crates/zed/src/test.rs index 8df47ee95e6b72cae941315ff5e00d3beef86c7c..77b092b0bb48157a61ccfce854cb33809223cda3 100644 --- a/crates/zed/src/test.rs +++ b/crates/zed/src/test.rs @@ -25,18 +25,15 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { let http = FakeHttpClient::with_404_response(); let client = Client::new(http.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx)); - let mut languages = LanguageRegistry::new(); - languages.add( - Arc::new(language::Language::new( - language::LanguageConfig { - name: "Rust".to_string(), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - Some(tree_sitter_rust::language()), - )), - - ); + let languages = LanguageRegistry::new(); + languages.add(Arc::new(language::Language::new( + language::LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ))); Arc::new(AppState { settings_tx: Arc::new(Mutex::new(settings_tx)), settings, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index cdc24e5a52e5d74381d28fd3e10e5d547ea41d12..fc1dcdff1db3d12d73dabeb62bebbcbb76e96f7e 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -534,7 +534,7 @@ mod tests { editor.read_with(&cx, |editor, cx| { assert!(!editor.is_dirty(cx)); assert_eq!(editor.title(cx), "the-new-name.rs"); - assert_eq!(editor.language(cx).unwrap().name(), "Rust"); + assert_eq!(editor.language(cx).unwrap().name().as_ref(), "Rust"); }); // Edit the file and save it again. This time, there is no filename prompt. @@ -614,7 +614,7 @@ mod tests { // The buffer is not dirty anymore and the language is assigned based on the path. editor.read_with(&cx, |editor, cx| { assert!(!editor.is_dirty(cx)); - assert_eq!(editor.language(cx).unwrap().name(), "Rust") + assert_eq!(editor.language(cx).unwrap().name().as_ref(), "Rust") }); }