From 6e68ff5a50088e56bc05277c516dc413a29419f3 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 17 Apr 2023 16:55:10 -0400 Subject: [PATCH] =?UTF-8?q?Get=20it=20to=20build=20with=20multiple=20adapt?= =?UTF-8?q?ers=20per=20language!=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Max Brunsfeld --- crates/project/src/project.rs | 128 ++++++++++++------------- crates/project/src/project_tests.rs | 1 + crates/project/src/worktree.rs | 19 ++-- crates/zed/src/languages.rs | 23 +++-- crates/zed/src/languages/typescript.rs | 18 ++-- 5 files changed, 100 insertions(+), 89 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 36cd76fe3d..5b571c0c0c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1641,7 +1641,7 @@ impl Project { .1 .notify::( lsp::DidCloseTextDocumentParams { - text_document: lsp::TextDocumentIdentifier::new(uri), + text_document: lsp::TextDocumentIdentifier::new(uri.clone()), }, ) .log_err(); @@ -1670,17 +1670,17 @@ impl Project { let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); let initial_snapshot = buffer.text_snapshot(); + let language = buffer.language().cloned(); + let worktree_id = file.worktree_id(cx); if let Some(local_worktree) = file.worktree.read(cx).as_local() { - if let Some(diagnostics) = local_worktree.diagnostics_for_path(file.path()) { - self.update_buffer_diagnostics(buffer_handle, diagnostics, None, cx) + for (server_id, diagnostics) in local_worktree.diagnostics_for_path(file.path()) { + self.update_buffer_diagnostics(buffer_handle, diagnostics, server_id, None, cx) .log_err(); } } - if let Some(language) = buffer.language() { - let worktree_id = file.worktree_id(cx); - + if let Some(language) = language { for adapter in language.lsp_adapters() { let language_id = adapter.language_ids.get(language.name().as_ref()).cloned(); let server = self @@ -1703,7 +1703,7 @@ impl Project { .notify::( lsp::DidOpenTextDocumentParams { text_document: lsp::TextDocumentItem::new( - uri, + uri.clone(), language_id.unwrap_or_default(), 0, initial_snapshot.text(), @@ -1726,7 +1726,7 @@ impl Project { let snapshot = LspBufferSnapshot { version: 0, - snapshot: initial_snapshot, + snapshot: initial_snapshot.clone(), }; self.buffer_snapshots .entry(buffer_id) @@ -1746,13 +1746,12 @@ impl Project { buffer.update(cx, |buffer, cx| { buffer.update_diagnostics(Default::default(), cx); self.buffer_snapshots.remove(&buffer.remote_id()); + let file_url = lsp::Url::from_file_path(old_path).unwrap(); for (_, language_server) in self.language_servers_for_buffer(buffer, cx) { language_server .notify::( lsp::DidCloseTextDocumentParams { - text_document: lsp::TextDocumentIdentifier::new( - lsp::Url::from_file_path(old_path).unwrap(), - ), + text_document: lsp::TextDocumentIdentifier::new(file_url.clone()), }, ) .log_err(); @@ -1856,7 +1855,12 @@ impl Project { let uri = lsp::Url::from_file_path(abs_path).unwrap(); let next_snapshot = buffer.text_snapshot(); - for (_, language_server) in self.language_servers_for_buffer(buffer, cx) { + let language_servers: Vec<_> = self + .language_servers_iter_for_buffer(buffer, cx) + .map(|i| i.1.clone()) + .collect(); + + for language_server in language_servers { let language_server = language_server.clone(); let buffer_snapshots = self @@ -1887,14 +1891,14 @@ impl Project { buffer_snapshots.push(LspBufferSnapshot { version: next_version, - snapshot: next_snapshot, + snapshot: next_snapshot.clone(), }); language_server .notify::( lsp::DidChangeTextDocumentParams { text_document: lsp::VersionedTextDocumentIdentifier::new( - uri, + uri.clone(), next_version, ), content_changes, @@ -1925,26 +1929,24 @@ impl Project { let language_server_ids = self.language_server_ids_for_buffer(buffer.read(cx), cx); for language_server_id in language_server_ids { - let LanguageServerState::Running { + if let Some(LanguageServerState::Running { adapter, simulate_disk_based_diagnostics_completion, .. - } = match self.language_servers.get_mut(&language_server_id) { - Some(state) => state, - None => continue, - }; + }) = self.language_servers.get_mut(&language_server_id) + { + // After saving a buffer using a language server that doesn't provide + // a disk-based progress token, kick off a timer that will reset every + // time the buffer is saved. If the timer eventually fires, simulate + // disk-based diagnostics being finished so that other pieces of UI + // (e.g., project diagnostics view, diagnostic status bar) can update. + // We don't emit an event right away because the language server might take + // some time to publish diagnostics. + if adapter.disk_based_diagnostics_progress_token.is_none() { + const DISK_BASED_DIAGNOSTICS_DEBOUNCE: Duration = + Duration::from_secs(1); - // After saving a buffer using a language server that doesn't provide - // a disk-based progress token, kick off a timer that will reset every - // time the buffer is saved. If the timer eventually fires, simulate - // disk-based diagnostics being finished so that other pieces of UI - // (e.g., project diagnostics view, diagnostic status bar) can update. - // We don't emit an event right away because the language server might take - // some time to publish diagnostics. - if adapter.disk_based_diagnostics_progress_token.is_none() { - const DISK_BASED_DIAGNOSTICS_DEBOUNCE: Duration = Duration::from_secs(1); - - let task = cx.spawn_weak(|this, mut cx| async move { + let task = cx.spawn_weak(|this, mut cx| async move { cx.background().timer(DISK_BASED_DIAGNOSTICS_DEBOUNCE).await; if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx | { @@ -1958,7 +1960,8 @@ impl Project { }); } }); - *simulate_disk_based_diagnostics_completion = Some(task); + *simulate_disk_based_diagnostics_completion = Some(task); + } } } } @@ -2123,7 +2126,7 @@ impl Project { let adapters = language.lsp_adapters(); let language_servers = self.languages.start_language_servers( language.clone(), - worktree_path, + worktree_path.clone(), self.client.http_client(), cx, ); @@ -2143,19 +2146,18 @@ impl Project { _ => {} } - self.language_server_ids - .entry(key.clone()) - .or_insert_with(|| { - self.setup_language_adapter( - worktree_path, - initialization_options, - pending_server, - adapter, - &language, - key, - cx, - ) - }); + if !self.language_server_ids.contains_key(&key) { + let adapter = self.setup_language_adapter( + worktree_path.clone(), + initialization_options, + pending_server, + adapter.clone(), + language.clone(), + key.clone(), + cx, + ); + self.language_server_ids.insert(key.clone(), adapter); + } } } @@ -2164,8 +2166,8 @@ impl Project { worktree_path: Arc, initialization_options: Option, pending_server: PendingLanguageServer, - adapter: &Arc, - language: &Arc, + adapter: Arc, + language: Arc, key: (WorktreeId, LanguageServerName), cx: &mut ModelContext, ) -> usize { @@ -2409,7 +2411,7 @@ impl Project { let snapshot = versions.last().unwrap(); let version = snapshot.version; - let initial_snapshot = snapshot.snapshot; + let initial_snapshot = &snapshot.snapshot; let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); language_server .notify::( @@ -2532,6 +2534,7 @@ impl Project { None } + // TODO This will break in the case where the adapter's root paths and worktrees are not equal fn restart_language_servers( &mut self, worktree_id: WorktreeId, @@ -2568,7 +2571,7 @@ impl Project { .map(|path_buf| Arc::from(path_buf.as_path())) .unwrap_or(fallback_path); - this.start_language_servers(worktree_id, root_path, language, cx); + this.start_language_servers(worktree_id, root_path, language.clone(), cx); // Lookup new server ids and set them for each of the orphaned worktrees for adapter in language.lsp_adapters() { @@ -2577,7 +2580,7 @@ impl Project { .get(&(worktree_id, adapter.name.clone())) .cloned() { - for orphaned_worktree in orphaned_worktrees { + for &orphaned_worktree in &orphaned_worktrees { this.language_server_ids .insert((orphaned_worktree, adapter.name.clone()), new_server_id); } @@ -2948,7 +2951,7 @@ impl Project { pub fn update_diagnostic_entries( &mut self, - language_server_id: usize, + server_id: usize, abs_path: PathBuf, version: Option, diagnostics: Vec>>, @@ -2964,23 +2967,18 @@ impl Project { }; if let Some(buffer) = self.get_open_buffer(&project_path, cx) { - self.update_buffer_diagnostics(&buffer, diagnostics.clone(), version, cx)?; + self.update_buffer_diagnostics(&buffer, diagnostics.clone(), server_id, version, cx)?; } let updated = worktree.update(cx, |worktree, cx| { worktree .as_local_mut() .ok_or_else(|| anyhow!("not a local worktree"))? - .update_diagnostics( - language_server_id, - project_path.path.clone(), - diagnostics, - cx, - ) + .update_diagnostics(server_id, project_path.path.clone(), diagnostics, cx) })?; if updated { cx.emit(Event::DiagnosticsUpdated { - language_server_id, + language_server_id: server_id, path: project_path, }); } @@ -2991,6 +2989,7 @@ impl Project { &mut self, buffer: &ModelHandle, mut diagnostics: Vec>>, + server_id: usize, version: Option, cx: &mut ModelContext, ) -> Result<()> { @@ -3002,7 +3001,7 @@ impl Project { .then_with(|| a.message.cmp(&b.message)) } - let snapshot = self.buffer_snapshot_for_lsp_version(buffer, version, cx)?; + let snapshot = self.buffer_snapshot_for_lsp_version(buffer, server_id, version, cx)?; diagnostics.sort_unstable_by(|a, b| { Ordering::Equal @@ -6261,7 +6260,7 @@ impl Project { } } - fn running_language_servers_for_buffer( + pub fn language_servers_iter_for_buffer( &self, buffer: &Buffer, cx: &AppContext, @@ -6286,8 +6285,7 @@ impl Project { buffer: &Buffer, cx: &AppContext, ) -> Vec<(&Arc, &Arc)> { - self.running_language_servers_for_buffer(buffer, cx) - .collect() + self.language_servers_iter_for_buffer(buffer, cx).collect() } fn primary_language_servers_for_buffer( @@ -6295,7 +6293,7 @@ impl Project { buffer: &Buffer, cx: &AppContext, ) -> Option<(&Arc, &Arc)> { - self.running_language_servers_for_buffer(buffer, cx).next() + self.language_servers_iter_for_buffer(buffer, cx).next() } fn language_server_for_buffer( @@ -6304,7 +6302,7 @@ impl Project { server_id: usize, cx: &AppContext, ) -> Option<(&Arc, &Arc)> { - self.running_language_servers_for_buffer(buffer, cx) + self.language_servers_iter_for_buffer(buffer, cx) .find(|(_, s)| s.server_id() == server_id) } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 09c3326739..08f1768766 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1420,6 +1420,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) { }, }, ], + 0, None, cx, ) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index d0cf2faa7e..0e3c4d9ce1 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -67,7 +67,7 @@ pub struct LocalWorktree { is_scanning: (watch::Sender, watch::Receiver), _background_scanner_task: Task<()>, share: Option, - diagnostics: HashMap, Vec>>>, + diagnostics: HashMap, Vec<(usize, Vec>>)>>, diagnostic_summaries: TreeMap, client: Arc, fs: Arc, @@ -514,13 +514,13 @@ impl LocalWorktree { pub fn diagnostics_for_path( &self, path: &Path, - ) -> Option>>> { - self.diagnostics.get(path).cloned() + ) -> Vec<(usize, Vec>>)> { + self.diagnostics.get(path).cloned().unwrap_or_default() } pub fn update_diagnostics( &mut self, - language_server_id: usize, + server_id: usize, worktree_path: Arc, diagnostics: Vec>>, _: &mut ModelContext, @@ -530,11 +530,16 @@ impl LocalWorktree { .diagnostic_summaries .remove(&PathKey(worktree_path.clone())) .unwrap_or_default(); - let new_summary = DiagnosticSummary::new(language_server_id, &diagnostics); + let new_summary = DiagnosticSummary::new(server_id, &diagnostics); if !new_summary.is_empty() { self.diagnostic_summaries .insert(PathKey(worktree_path.clone()), new_summary); - self.diagnostics.insert(worktree_path.clone(), diagnostics); + let diagnostics_by_server_id = + self.diagnostics.entry(worktree_path.clone()).or_default(); + let ix = match diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) { + Ok(ix) | Err(ix) => ix, + }; + diagnostics_by_server_id[ix] = (server_id, diagnostics); } let updated = !old_summary.is_empty() || !new_summary.is_empty(); @@ -546,7 +551,7 @@ impl LocalWorktree { worktree_id: self.id().to_proto(), summary: Some(proto::DiagnosticSummary { path: worktree_path.to_string_lossy().to_string(), - language_server_id: language_server_id as u64, + language_server_id: server_id as u64, error_count: new_summary.error_count as u32, warning_count: new_summary.warning_count as u32, }), diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index db5d5913cb..4dc54f7a9b 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -89,23 +89,26 @@ pub fn init( ( "tsx", tree_sitter_typescript::language_tsx(), - vec![adapter_arc(typescript::TypeScriptLspAdapter::new( - node_runtime.clone(), - ))], + vec![ + adapter_arc(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), + adapter_arc(typescript::EsLintLspAdapter::new(node_runtime.clone())), + ], ), ( "typescript", tree_sitter_typescript::language_typescript(), - vec![adapter_arc(typescript::TypeScriptLspAdapter::new( - node_runtime.clone(), - ))], + vec![ + adapter_arc(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), + adapter_arc(typescript::EsLintLspAdapter::new(node_runtime.clone())), + ], ), ( "javascript", tree_sitter_typescript::language_tsx(), - vec![adapter_arc(typescript::TypeScriptLspAdapter::new( - node_runtime.clone(), - ))], + vec![ + adapter_arc(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), + adapter_arc(typescript::EsLintLspAdapter::new(node_runtime.clone())), + ], ), ( "html", @@ -149,7 +152,7 @@ pub async fn language( ) -> Arc { Arc::new( Language::new(load_config(name), Some(grammar)) - .with_lsp_adapters(lsp_adapter) + .with_lsp_adapters(lsp_adapter.into_iter().collect()) .await .with_queries(load_queries(name)) .unwrap(), diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 3121bfe81f..26fb3831e3 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -15,7 +15,7 @@ use std::{ use util::http::HttpClient; use util::ResultExt; -fn server_binary_arguments(server_path: &Path) -> Vec { +fn typescript_server_binary_arguments(server_path: &Path) -> Vec { vec![ server_path.into(), "--stdio".into(), @@ -24,6 +24,10 @@ fn server_binary_arguments(server_path: &Path) -> Vec { ] } +fn eslint_server_binary_arguments(server_path: &Path) -> Vec { + vec![server_path.into(), "--stdin".into()] +} + pub struct TypeScriptLspAdapter { node: Arc, } @@ -89,7 +93,7 @@ impl LspAdapter for TypeScriptLspAdapter { Ok(LanguageServerBinary { path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), + arguments: typescript_server_binary_arguments(&server_path), }) } @@ -101,12 +105,12 @@ impl LspAdapter for TypeScriptLspAdapter { if new_server_path.exists() { Ok(LanguageServerBinary { path: self.node.binary_path().await?, - arguments: server_binary_arguments(&new_server_path), + arguments: typescript_server_binary_arguments(&new_server_path), }) } else if old_server_path.exists() { Ok(LanguageServerBinary { path: self.node.binary_path().await?, - arguments: server_binary_arguments(&old_server_path), + arguments: typescript_server_binary_arguments(&old_server_path), }) } else { Err(anyhow!( @@ -169,7 +173,7 @@ pub struct EsLintLspAdapter { } impl EsLintLspAdapter { - const SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs"; + const SERVER_PATH: &'static str = "node_modules/eslint/bin/eslint.js"; pub fn new(node: Arc) -> Self { EsLintLspAdapter { node } @@ -208,7 +212,7 @@ impl LspAdapter for EsLintLspAdapter { Ok(LanguageServerBinary { path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), + arguments: eslint_server_binary_arguments(&server_path), }) } @@ -218,7 +222,7 @@ impl LspAdapter for EsLintLspAdapter { if server_path.exists() { Ok(LanguageServerBinary { path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), + arguments: eslint_server_binary_arguments(&server_path), }) } else { Err(anyhow!(