diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index f523b4ca7f..aa917461c1 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -11,7 +11,7 @@ use gpui::{ }; use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point}; use postage::watch; -use project::Project; +use project::{Project, ProjectPath}; use std::{cmp::Ordering, ops::Range, path::Path, sync::Arc}; use util::TryFutureExt; use workspace::Workspace; @@ -41,9 +41,11 @@ struct ProjectDiagnostics { } struct ProjectDiagnosticsEditor { + project: ModelHandle, editor: ViewHandle, excerpts: ModelHandle, path_states: Vec<(Arc, Vec)>, + paths_to_update: HashMap>, build_settings: BuildSettings, } @@ -95,41 +97,19 @@ impl ProjectDiagnosticsEditor { settings: watch::Receiver, cx: &mut ViewContext, ) -> Self { - let project_paths = project - .read(cx) - .diagnostic_summaries(cx) - .map(|e| e.0) - .collect::>(); - - cx.spawn(|this, mut cx| { - let project = project.clone(); - async move { - for project_path in project_paths { - let buffer = project - .update(&mut cx, |project, cx| project.open_buffer(project_path, cx)) - .await?; - this.update(&mut cx, |view, cx| view.populate_excerpts(buffer, cx)) + cx.subscribe(&project, |this, _, event, cx| match event { + project::Event::DiskBasedDiagnosticsUpdated { worktree_id } => { + if let Some(paths) = this.paths_to_update.remove(&worktree_id) { + this.update_excerpts(paths, cx); } - Result::<_, anyhow::Error>::Ok(()) } - }) - .detach(); - - cx.subscribe(&project, |_, project, event, cx| { - if let project::Event::DiagnosticsUpdated(project_path) = event { - let project_path = project_path.clone(); - cx.spawn(|this, mut cx| { - async move { - let buffer = project - .update(&mut cx, |project, cx| project.open_buffer(project_path, cx)) - .await?; - this.update(&mut cx, |view, cx| view.populate_excerpts(buffer, cx)); - Ok(()) - } - .log_err() - }) - .detach(); + project::Event::DiagnosticsUpdated(path) => { + this.paths_to_update + .entry(path.worktree_id) + .or_default() + .insert(path.clone()); } + _ => {} }) .detach(); @@ -139,12 +119,22 @@ impl ProjectDiagnosticsEditor { cx.add_view(|cx| Editor::for_buffer(excerpts.clone(), build_settings.clone(), cx)); cx.subscribe(&editor, |_, _, event, cx| cx.emit(*event)) .detach(); - Self { + + let paths_to_update = project + .read(cx) + .diagnostic_summaries(cx) + .map(|e| e.0) + .collect(); + let this = Self { + project, excerpts, editor, build_settings, path_states: Default::default(), - } + paths_to_update: Default::default(), + }; + this.update_excerpts(paths_to_update, cx); + this } #[cfg(test)] @@ -189,6 +179,23 @@ impl ProjectDiagnosticsEditor { .update(cx, |editor, cx| editor.remove_blocks(blocks_to_delete, cx)); } + fn update_excerpts(&self, paths: HashSet, cx: &mut ViewContext) { + let project = self.project.clone(); + cx.spawn(|this, mut cx| { + async move { + for path in paths { + let buffer = project + .update(&mut cx, |project, cx| project.open_buffer(path, cx)) + .await?; + this.update(&mut cx, |view, cx| view.populate_excerpts(buffer, cx)) + } + Result::<_, anyhow::Error>::Ok(()) + } + .log_err() + }) + .detach(); + } + fn populate_excerpts(&mut self, buffer: ModelHandle, cx: &mut ViewContext) { let snapshot; let path; @@ -572,7 +579,7 @@ mod tests { use client::{http::ServerResponse, test::FakeHttpClient, Client, UserStore}; use gpui::TestAppContext; use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, LanguageRegistry, PointUtf16}; - use project::FakeFs; + use project::{worktree, FakeFs}; use serde_json::json; use std::sync::Arc; use unindent::Unindent as _; @@ -813,6 +820,7 @@ mod tests { cx, ) .unwrap(); + cx.emit(worktree::Event::DiskBasedDiagnosticsUpdated); }); view.condition(&mut cx, |view, cx| view.text(cx).contains("const a")) diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index 047513fce7..9c2091739f 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -19,6 +19,7 @@ pub struct DiagnosticEntry { pub diagnostic: Diagnostic, } +#[derive(Debug)] pub struct DiagnosticGroup { pub entries: Vec>, pub primary_ix: usize, diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index fe832929a9..bd5f91b792 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -46,6 +46,7 @@ pub struct LanguageConfig { pub struct LanguageServerConfig { pub binary: String, pub disk_based_diagnostic_sources: HashSet, + pub disk_based_diagnostics_progress_token: Option, #[cfg(any(test, feature = "test-support"))] #[serde(skip)] pub fake_server: Option<(Arc, Arc)>, @@ -199,6 +200,13 @@ impl Language { .map(|config| &config.disk_based_diagnostic_sources) } + pub fn disk_based_diagnostics_progress_token(&self) -> Option<&String> { + self.config + .language_server + .as_ref() + .and_then(|config| config.disk_based_diagnostics_progress_token.as_ref()) + } + pub fn brackets(&self) -> &[BracketPair] { &self.config.brackets } diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 769922523c..d0ce93b973 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -28,7 +28,7 @@ pub use lsp_types::*; const JSON_RPC_VERSION: &'static str = "2.0"; const CONTENT_LEN_HEADER: &'static str = "Content-Length: "; -type NotificationHandler = Box; +type NotificationHandler = Box; type ResponseHandler = Box)>; pub struct LanguageServer { @@ -139,7 +139,7 @@ impl LanguageServer { if let Ok(AnyNotification { method, params }) = serde_json::from_slice(&buffer) { - if let Some(handler) = notification_handlers.read().get(method) { + if let Some(handler) = notification_handlers.write().get_mut(method) { handler(params.get()); } else { log::info!( @@ -226,15 +226,15 @@ impl LanguageServer { process_id: Default::default(), root_path: Default::default(), root_uri: Some(root_uri), - initialization_options: Some(json!({ - "checkOnSave": { - "enable": false - }, - })), + initialization_options: Default::default(), capabilities: lsp_types::ClientCapabilities { experimental: Some(json!({ "serverStatusNotification": true, })), + window: Some(lsp_types::WindowClientCapabilities { + work_done_progress: Some(true), + ..Default::default() + }), ..Default::default() }, trace: Default::default(), @@ -283,10 +283,10 @@ impl LanguageServer { } } - pub fn on_notification(&self, f: F) -> Subscription + pub fn on_notification(&self, mut f: F) -> Subscription where T: lsp_types::notification::Notification, - F: 'static + Send + Sync + Fn(T::Params), + F: 'static + Send + Sync + FnMut(T::Params), { let prev_handler = self.notification_handlers.write().insert( T::METHOD, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 499b3d4a52..acabca0033 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1,6 +1,6 @@ pub mod fs; mod ignore; -mod worktree; +pub mod worktree; use anyhow::{anyhow, Result}; use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore}; @@ -60,6 +60,7 @@ pub struct Collaborator { pub enum Event { ActiveEntryChanged(Option), WorktreeRemoved(usize), + DiskBasedDiagnosticsUpdated { worktree_id: usize }, DiagnosticsUpdated(ProjectPath), } @@ -482,6 +483,11 @@ impl Project { path: path.clone(), })); } + worktree::Event::DiskBasedDiagnosticsUpdated => { + cx.emit(Event::DiskBasedDiagnosticsUpdated { + worktree_id: worktree.id(), + }); + } }) .detach(); self.worktrees.push(worktree); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 76f0e2b270..c74a2dc62f 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -66,6 +66,7 @@ pub enum Worktree { #[derive(Debug)] pub enum Event { + DiskBasedDiagnosticsUpdated, DiagnosticsUpdated(Arc), } @@ -1037,18 +1038,61 @@ impl LocalWorktree { .disk_based_diagnostic_sources() .cloned() .unwrap_or_default(); + let disk_based_diagnostics_progress_token = + language.disk_based_diagnostics_progress_token().cloned(); let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); language_server .on_notification::(move |params| { smol::block_on(diagnostics_tx.send(params)).ok(); }) .detach(); + cx.spawn_weak(|this, mut cx| { + let has_disk_based_diagnostic_progress_token = + disk_based_diagnostics_progress_token.is_some(); + async move { + while let Ok(diagnostics) = diagnostics_rx.recv().await { + if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { + handle.update(&mut cx, |this, cx| { + this.update_diagnostics(diagnostics, &disk_based_sources, cx) + .log_err(); + if !has_disk_based_diagnostic_progress_token { + cx.emit(Event::DiskBasedDiagnosticsUpdated); + } + }); + } else { + break; + } + } + } + }) + .detach(); + + let (mut disk_based_diagnostics_done_tx, mut disk_based_diagnostics_done_rx) = + watch::channel_with(()); + language_server + .on_notification::(move |params| { + let token = match params.token { + lsp::NumberOrString::Number(_) => None, + lsp::NumberOrString::String(token) => Some(token), + }; + + if token == disk_based_diagnostics_progress_token { + match params.value { + lsp::ProgressParamsValue::WorkDone(progress) => match progress { + lsp::WorkDoneProgress::End(_) => { + smol::block_on(disk_based_diagnostics_done_tx.send(())).ok(); + } + _ => {} + }, + } + } + }) + .detach(); cx.spawn_weak(|this, mut cx| async move { - while let Ok(diagnostics) = diagnostics_rx.recv().await { + while let Some(()) = disk_based_diagnostics_done_rx.recv().await { if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { - handle.update(&mut cx, |this, cx| { - this.update_diagnostics(diagnostics, &disk_based_sources, cx) - .log_err(); + handle.update(&mut cx, |_, cx| { + cx.emit(Event::DiskBasedDiagnosticsUpdated); }); } else { break; diff --git a/crates/zed/languages/rust/config.toml b/crates/zed/languages/rust/config.toml index 655a264e6c..426dcc2b48 100644 --- a/crates/zed/languages/rust/config.toml +++ b/crates/zed/languages/rust/config.toml @@ -13,3 +13,4 @@ brackets = [ [language_server] binary = "rust-analyzer" disk_based_diagnostic_sources = ["rustc"] +disk_based_diagnostics_progress_token = "rustAnalyzer/cargo check"