Re-enable cargo check for rust-analyzer

Antonio Scandurra created

Change summary

crates/diagnostics/src/diagnostics.rs | 80 +++++++++++++++-------------
crates/language/src/diagnostic_set.rs |  1 
crates/language/src/language.rs       |  8 ++
crates/lsp/src/lsp.rs                 | 18 +++---
crates/project/src/project.rs         |  8 ++
crates/project/src/worktree.rs        | 52 +++++++++++++++++-
crates/zed/languages/rust/config.toml |  1 
7 files changed, 118 insertions(+), 50 deletions(-)

Detailed changes

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<Project>,
     editor: ViewHandle<Editor>,
     excerpts: ModelHandle<MultiBuffer>,
     path_states: Vec<(Arc<Path>, Vec<DiagnosticGroupState>)>,
+    paths_to_update: HashMap<usize, HashSet<ProjectPath>>,
     build_settings: BuildSettings,
 }
 
@@ -95,41 +97,19 @@ impl ProjectDiagnosticsEditor {
         settings: watch::Receiver<workspace::Settings>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
-        let project_paths = project
-            .read(cx)
-            .diagnostic_summaries(cx)
-            .map(|e| e.0)
-            .collect::<Vec<_>>();
-
-        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<ProjectPath>, cx: &mut ViewContext<Self>) {
+        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<Buffer>, cx: &mut ViewContext<Self>) {
         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"))

crates/language/src/diagnostic_set.rs 🔗

@@ -19,6 +19,7 @@ pub struct DiagnosticEntry<T> {
     pub diagnostic: Diagnostic,
 }
 
+#[derive(Debug)]
 pub struct DiagnosticGroup<T> {
     pub entries: Vec<DiagnosticEntry<T>>,
     pub primary_ix: usize,

crates/language/src/language.rs 🔗

@@ -46,6 +46,7 @@ pub struct LanguageConfig {
 pub struct LanguageServerConfig {
     pub binary: String,
     pub disk_based_diagnostic_sources: HashSet<String>,
+    pub disk_based_diagnostics_progress_token: Option<String>,
     #[cfg(any(test, feature = "test-support"))]
     #[serde(skip)]
     pub fake_server: Option<(Arc<lsp::LanguageServer>, Arc<std::sync::atomic::AtomicBool>)>,
@@ -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
     }

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<dyn Send + Sync + Fn(&str)>;
+type NotificationHandler = Box<dyn Send + Sync + FnMut(&str)>;
 type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
 
 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<T, F>(&self, f: F) -> Subscription
+    pub fn on_notification<T, F>(&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,

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<ProjectEntry>),
     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);

crates/project/src/worktree.rs 🔗

@@ -66,6 +66,7 @@ pub enum Worktree {
 
 #[derive(Debug)]
 pub enum Event {
+    DiskBasedDiagnosticsUpdated,
     DiagnosticsUpdated(Arc<Path>),
 }
 
@@ -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::<lsp::notification::PublishDiagnostics, _>(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::<lsp::notification::Progress, _>(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;

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"