Get things compiling with diagnostics on worktree

Max Brunsfeld , Antonio Scandurra , and Nathan Sobo created

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/diagnostics/src/diagnostics.rs |   4 
crates/language/src/tests.rs          |  84 -----
crates/project/src/project.rs         |  14 
crates/project/src/worktree.rs        | 411 ++++++++++++++++------------
crates/rpc/proto/zed.proto            |   8 
crates/server/src/rpc.rs              |   6 
6 files changed, 264 insertions(+), 263 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics.rs 🔗

@@ -15,7 +15,9 @@ impl ProjectDiagnostics {
         cx: &mut ViewContext<Self>,
     ) -> Self {
         let mut buffer = cx.add_model(|cx| MultiBuffer::new(project.read(cx).replica_id(cx)));
-        for (path, diagnostics) in project.read(cx).diagnostics(cx) {}
+        for diagnostic_summary in project.read(cx).diagnostic_summaries(cx) {
+            //
+        }
 
         Self {
             editor: cx.add_view(|cx| {

crates/language/src/tests.rs 🔗

@@ -1,14 +1,11 @@
 use super::*;
-use gpui::{ModelHandle, MutableAppContext, Task};
+use gpui::{ModelHandle, MutableAppContext};
 use std::{
-    any::Any,
     cell::RefCell,
-    ffi::OsString,
     iter::FromIterator,
     ops::Range,
-    path::PathBuf,
     rc::Rc,
-    time::{Duration, Instant, SystemTime},
+    time::{Duration, Instant},
 };
 use unindent::Unindent as _;
 
@@ -871,80 +868,3 @@ fn rust_lang() -> Language {
 fn empty(point: Point) -> Range<Point> {
     point..point
 }
-
-#[derive(Clone)]
-struct FakeFile {
-    abs_path: PathBuf,
-}
-
-impl FakeFile {
-    fn new(abs_path: impl Into<PathBuf>) -> Self {
-        Self {
-            abs_path: abs_path.into(),
-        }
-    }
-}
-
-impl File for FakeFile {
-    fn worktree_id(&self) -> usize {
-        todo!()
-    }
-
-    fn entry_id(&self) -> Option<usize> {
-        todo!()
-    }
-
-    fn mtime(&self) -> SystemTime {
-        SystemTime::now()
-    }
-
-    fn path(&self) -> &Arc<Path> {
-        todo!()
-    }
-
-    fn abs_path(&self) -> Option<PathBuf> {
-        Some(self.abs_path.clone())
-    }
-
-    fn full_path(&self) -> PathBuf {
-        todo!()
-    }
-
-    fn file_name(&self) -> Option<OsString> {
-        todo!()
-    }
-
-    fn is_deleted(&self) -> bool {
-        todo!()
-    }
-
-    fn save(
-        &self,
-        _: u64,
-        _: Rope,
-        _: clock::Global,
-        _: &mut MutableAppContext,
-    ) -> Task<Result<(clock::Global, SystemTime)>> {
-        todo!()
-    }
-
-    fn load_local(&self, _: &AppContext) -> Option<Task<Result<String>>> {
-        todo!()
-    }
-
-    fn buffer_updated(&self, _: u64, _: super::Operation, _: &mut MutableAppContext) {
-        todo!()
-    }
-
-    fn buffer_removed(&self, _: u64, _: &mut MutableAppContext) {
-        todo!()
-    }
-
-    fn boxed_clone(&self) -> Box<dyn File> {
-        todo!()
-    }
-
-    fn as_any(&self) -> &dyn Any {
-        todo!()
-    }
-}

crates/project/src/project.rs 🔗

@@ -8,7 +8,7 @@ use clock::ReplicaId;
 use futures::Future;
 use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
 use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
-use language::{DiagnosticEntry, LanguageRegistry, PointUtf16};
+use language::LanguageRegistry;
 use std::{
     path::Path,
     sync::{atomic::AtomicBool, Arc},
@@ -39,6 +39,14 @@ pub struct ProjectPath {
     pub path: Arc<Path>,
 }
 
+pub struct DiagnosticSummary {
+    pub project_path: ProjectPath,
+    pub error_count: usize,
+    pub warning_count: usize,
+    pub info_count: usize,
+    pub hint_count: usize,
+}
+
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 pub struct ProjectEntry {
     pub worktree_id: usize,
@@ -165,10 +173,10 @@ impl Project {
         }
     }
 
-    pub fn diagnostics<'a>(
+    pub fn diagnostic_summaries<'a>(
         &'a self,
         cx: &'a AppContext,
-    ) -> impl Iterator<Item = (&'a Path, &'a [DiagnosticEntry<PointUtf16>])> {
+    ) -> impl Iterator<Item = DiagnosticSummary> {
         std::iter::empty()
     }
 

crates/project/src/worktree.rs 🔗

@@ -1,6 +1,7 @@
 use super::{
     fs::{self, Fs},
     ignore::IgnoreStack,
+    DiagnosticSummary,
 };
 use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
 use anyhow::{anyhow, Context, Result};
@@ -306,6 +307,7 @@ impl Worktree {
                     updates_tx,
                     client: client.clone(),
                     open_buffers: Default::default(),
+                    diagnostics: Vec::new(),
                     collaborators,
                     queued_operations: Default::default(),
                     languages,
@@ -467,6 +469,13 @@ impl Worktree {
         }
     }
 
+    pub fn diagnostic_summaries<'a>(
+        &'a self,
+        cx: &'a AppContext,
+    ) -> impl Iterator<Item = DiagnosticSummary> {
+        std::iter::empty()
+    }
+
     pub fn open_buffer(
         &mut self,
         path: impl AsRef<Path>,
@@ -754,10 +763,11 @@ impl Worktree {
             .uri
             .to_file_path()
             .map_err(|_| anyhow!("URI is not a file"))?;
-        let worktree_path = abs_path
-            .strip_prefix(&this.abs_path)
-            .context("path is not within worktree")?
-            .to_owned();
+        let worktree_path = Arc::from(
+            abs_path
+                .strip_prefix(&this.abs_path)
+                .context("path is not within worktree")?,
+        );
 
         let mut group_ids_by_diagnostic_range = HashMap::new();
         let mut diagnostics_by_group_id = HashMap::new();
@@ -784,7 +794,10 @@ impl Worktree {
                         ..diagnostic.range.end.to_point_utf16(),
                     diagnostic: Diagnostic {
                         source: diagnostic.source.clone(),
-                        code: diagnostic.code.clone(),
+                        code: diagnostic.code.clone().map(|code| match code {
+                            lsp::NumberOrString::Number(code) => code.to_string(),
+                            lsp::NumberOrString::String(code) => code,
+                        }),
                         severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
                         message: diagnostic.message.clone(),
                         group_id,
@@ -810,12 +823,12 @@ impl Worktree {
                 if buffer
                     .read(cx)
                     .file()
-                    .map_or(false, |file| file.path().as_ref() == worktree_path)
+                    .map_or(false, |file| *file.path() == worktree_path)
                 {
                     let (remote_id, operation) = buffer.update(cx, |buffer, cx| {
                         (
                             buffer.remote_id(),
-                            buffer.update_diagnostics(params.version, params.diagnostics, cx),
+                            buffer.update_diagnostics(params.version, diagnostics, cx),
                         )
                     });
                     self.send_buffer_update(remote_id, operation?, cx);
@@ -888,7 +901,7 @@ pub struct LocalWorktree {
     share: Option<ShareState>,
     open_buffers: HashMap<usize, WeakModelHandle<Buffer>>,
     shared_buffers: HashMap<PeerId, HashMap<u64, ModelHandle<Buffer>>>,
-    diagnostics: HashMap<PathBuf, Vec<DiagnosticEntry<PointUtf16>>>,
+    diagnostics: HashMap<Arc<Path>, Vec<DiagnosticEntry<PointUtf16>>>,
     collaborators: HashMap<PeerId, Collaborator>,
     queued_operations: Vec<(u64, Operation)>,
     languages: Arc<LanguageRegistry>,
@@ -1488,6 +1501,7 @@ pub struct RemoteWorktree {
     replica_id: ReplicaId,
     open_buffers: HashMap<usize, RemoteBuffer>,
     collaborators: HashMap<PeerId, Collaborator>,
+    diagnostics: Vec<DiagnosticSummary>,
     languages: Arc<LanguageRegistry>,
     user_store: ModelHandle<UserStore>,
     queued_operations: Vec<(u64, Operation)>,
@@ -3105,6 +3119,7 @@ mod tests {
         time::{SystemTime, UNIX_EPOCH},
     };
     use text::Point;
+    use unindent::Unindent as _;
     use util::test::temp_tree;
 
     #[gpui::test]
@@ -3821,7 +3836,8 @@ mod tests {
                         severity: lsp::DiagnosticSeverity::ERROR,
                         message: "undefined variable 'A'".to_string(),
                         group_id: 0,
-                        is_primary: true
+                        is_primary: true,
+                        ..Default::default()
                     }
                 }]
             )
@@ -3830,220 +3846,265 @@ mod tests {
 
     #[gpui::test]
     async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) {
-        cx.add_model(|cx| {
-            let text = "
-            fn foo(mut v: Vec<usize>) {
-                for x in &v {
-                    v.push(1);
-                }
-            }
-        "
-            .unindent();
+        let fs = Arc::new(FakeFs::new());
+        let client = Client::new();
+        let http_client = FakeHttpClient::with_404_response();
+        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 
-            let file = FakeFile::new("/example.rs");
-            let mut buffer = Buffer::from_file(0, text, Box::new(file.clone()), cx);
-            buffer.set_language(Some(Arc::new(rust_lang())), None, cx);
-            let diagnostics = vec![
-                DiagnosticEntry {
-                    range: PointUtf16::new(1, 8)..PointUtf16::new(1, 9),
-                    diagnostic: Diagnostic {
-                        severity: Some(DiagnosticSeverity::WARNING),
-                        message: "error 1".to_string(),
-                        related_information: Some(vec![lsp::DiagnosticRelatedInformation {
-                            location: lsp::Location {
-                                uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
-                                range: PointUtf16::new(1, 8)..PointUtf16::new(1, 9),
-                            },
-                            message: "error 1 hint 1".to_string(),
-                        }]),
-                        ..Default::default()
-                    },
+        fs.insert_tree(
+            "/the-dir",
+            json!({
+                "a.rs": "
+                    fn foo(mut v: Vec<usize>) {
+                        for x in &v {
+                            v.push(1);
+                        }
+                    }
+                "
+                .unindent(),
+            }),
+        )
+        .await;
+
+        let worktree = Worktree::open_local(
+            client.clone(),
+            user_store,
+            "/the-dir".as_ref(),
+            fs,
+            Default::default(),
+            &mut cx.to_async(),
+        )
+        .await
+        .unwrap();
+
+        let buffer = worktree
+            .update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx))
+            .await
+            .unwrap();
+
+        let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap();
+        let message = lsp::PublishDiagnosticsParams {
+            uri: buffer_uri.clone(),
+            diagnostics: vec![
+                lsp::Diagnostic {
+                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
+                    severity: Some(DiagnosticSeverity::WARNING),
+                    message: "error 1".to_string(),
+                    related_information: Some(vec![lsp::DiagnosticRelatedInformation {
+                        location: lsp::Location {
+                            uri: buffer_uri.clone(),
+                            range: lsp::Range::new(
+                                lsp::Position::new(1, 8),
+                                lsp::Position::new(1, 9),
+                            ),
+                        },
+                        message: "error 1 hint 1".to_string(),
+                    }]),
+                    ..Default::default()
                 },
-                DiagnosticEntry {
-                    range: PointUtf16::new(1, 8)..PointUtf16::new(1, 9),
-                    diagnostic: Diagnostic {},
+                lsp::Diagnostic {
+                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
                     severity: Some(DiagnosticSeverity::HINT),
                     message: "error 1 hint 1".to_string(),
                     related_information: Some(vec![lsp::DiagnosticRelatedInformation {
                         location: lsp::Location {
-                            uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
-                            range: PointUtf16::new(1, 8)..PointUtf16::new(1, 9),
+                            uri: buffer_uri.clone(),
+                            range: lsp::Range::new(
+                                lsp::Position::new(1, 8),
+                                lsp::Position::new(1, 9),
+                            ),
                         },
                         message: "original diagnostic".to_string(),
                     }]),
                     ..Default::default()
                 },
-                DiagnosticEntry {
-                    range: PointUtf16::new(2, 8)..PointUtf16::new(2, 17),
-                    diagnostic: Diagnostic {},
+                lsp::Diagnostic {
+                    range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
                     severity: Some(DiagnosticSeverity::ERROR),
                     message: "error 2".to_string(),
                     related_information: Some(vec![
                         lsp::DiagnosticRelatedInformation {
                             location: lsp::Location {
-                                uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
-                                range: PointUtf16::new(1, 13)..PointUtf16::new(1, 15),
+                                uri: buffer_uri.clone(),
+                                range: lsp::Range::new(
+                                    lsp::Position::new(1, 13),
+                                    lsp::Position::new(1, 15),
+                                ),
                             },
                             message: "error 2 hint 1".to_string(),
                         },
                         lsp::DiagnosticRelatedInformation {
                             location: lsp::Location {
-                                uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
-                                range: PointUtf16::new(1, 13)..PointUtf16::new(1, 15),
+                                uri: buffer_uri.clone(),
+                                range: lsp::Range::new(
+                                    lsp::Position::new(1, 13),
+                                    lsp::Position::new(1, 15),
+                                ),
                             },
                             message: "error 2 hint 2".to_string(),
                         },
                     ]),
                     ..Default::default()
                 },
-                DiagnosticEntry {
-                    range: PointUtf16::new(1, 13)..PointUtf16::new(1, 15),
-                    diagnostic: Diagnostic {},
+                lsp::Diagnostic {
+                    range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
                     severity: Some(DiagnosticSeverity::HINT),
                     message: "error 2 hint 1".to_string(),
                     related_information: Some(vec![lsp::DiagnosticRelatedInformation {
                         location: lsp::Location {
-                            uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
-                            range: PointUtf16::new(2, 8)..PointUtf16::new(2, 17),
+                            uri: buffer_uri.clone(),
+                            range: lsp::Range::new(
+                                lsp::Position::new(2, 8),
+                                lsp::Position::new(2, 17),
+                            ),
                         },
                         message: "original diagnostic".to_string(),
                     }]),
                     ..Default::default()
                 },
-                DiagnosticEntry {
-                    range: PointUtf16::new(1, 13)..PointUtf16::new(1, 15),
-                    diagnostic: Diagnostic {},
+                lsp::Diagnostic {
+                    range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
                     severity: Some(DiagnosticSeverity::HINT),
                     message: "error 2 hint 2".to_string(),
                     related_information: Some(vec![lsp::DiagnosticRelatedInformation {
                         location: lsp::Location {
-                            uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
-                            range: PointUtf16::new(2, 8)..PointUtf16::new(2, 17),
+                            uri: buffer_uri.clone(),
+                            range: lsp::Range::new(
+                                lsp::Position::new(2, 8),
+                                lsp::Position::new(2, 17),
+                            ),
                         },
                         message: "original diagnostic".to_string(),
                     }]),
                     ..Default::default()
                 },
-            ];
-            buffer.update_diagnostics(None, diagnostics, cx).unwrap();
-            assert_eq!(
-                buffer
-                    .snapshot()
-                    .diagnostics_in_range::<_, Point>(0..buffer.len())
-                    .collect::<Vec<_>>(),
-                &[
-                    DiagnosticEntry {
-                        range: Point::new(1, 8)..Point::new(1, 9),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::WARNING,
-                            message: "error 1".to_string(),
-                            group_id: 0,
-                            is_primary: true,
-                        }
-                    },
-                    DiagnosticEntry {
-                        range: Point::new(1, 8)..Point::new(1, 9),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::HINT,
-                            message: "error 1 hint 1".to_string(),
-                            group_id: 0,
-                            is_primary: false,
-                        }
-                    },
-                    DiagnosticEntry {
-                        range: Point::new(1, 13)..Point::new(1, 15),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::HINT,
-                            message: "error 2 hint 1".to_string(),
-                            group_id: 1,
-                            is_primary: false,
-                        }
-                    },
-                    DiagnosticEntry {
-                        range: Point::new(1, 13)..Point::new(1, 15),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::HINT,
-                            message: "error 2 hint 2".to_string(),
-                            group_id: 1,
-                            is_primary: false,
-                        }
-                    },
-                    DiagnosticEntry {
-                        range: Point::new(2, 8)..Point::new(2, 17),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::ERROR,
-                            message: "error 2".to_string(),
-                            group_id: 1,
-                            is_primary: true,
-                        }
-                    }
-                ]
-            );
+            ],
+            version: None,
+        };
 
-            assert_eq!(
-                buffer
-                    .snapshot()
-                    .diagnostic_group::<Point>(0)
-                    .collect::<Vec<_>>(),
-                &[
-                    DiagnosticEntry {
-                        range: Point::new(1, 8)..Point::new(1, 9),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::WARNING,
-                            message: "error 1".to_string(),
-                            group_id: 0,
-                            is_primary: true,
-                        }
-                    },
-                    DiagnosticEntry {
-                        range: Point::new(1, 8)..Point::new(1, 9),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::HINT,
-                            message: "error 1 hint 1".to_string(),
-                            group_id: 0,
-                            is_primary: false,
-                        }
-                    },
-                ]
-            );
-            assert_eq!(
-                buffer
-                    .snapshot()
-                    .diagnostic_group::<Point>(1)
-                    .collect::<Vec<_>>(),
-                &[
-                    DiagnosticEntry {
-                        range: Point::new(1, 13)..Point::new(1, 15),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::HINT,
-                            message: "error 2 hint 1".to_string(),
-                            group_id: 1,
-                            is_primary: false,
-                        }
-                    },
-                    DiagnosticEntry {
-                        range: Point::new(1, 13)..Point::new(1, 15),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::HINT,
-                            message: "error 2 hint 2".to_string(),
-                            group_id: 1,
-                            is_primary: false,
-                        }
-                    },
-                    DiagnosticEntry {
-                        range: Point::new(2, 8)..Point::new(2, 17),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::ERROR,
-                            message: "error 2".to_string(),
-                            group_id: 1,
-                            is_primary: true,
-                        }
-                    }
-                ]
-            );
+        worktree
+            .update(&mut cx, |tree, cx| tree.update_diagnostics(message, cx))
+            .unwrap();
+        let buffer = buffer.read_with(&cx, |buffer, cx| buffer.snapshot());
 
+        assert_eq!(
             buffer
-        });
+                .diagnostics_in_range::<_, Point>(0..buffer.len())
+                .collect::<Vec<_>>(),
+            &[
+                DiagnosticEntry {
+                    range: Point::new(1, 8)..Point::new(1, 9),
+                    diagnostic: Diagnostic {
+                        severity: DiagnosticSeverity::WARNING,
+                        message: "error 1".to_string(),
+                        group_id: 0,
+                        is_primary: true,
+                        ..Default::default()
+                    }
+                },
+                DiagnosticEntry {
+                    range: Point::new(1, 8)..Point::new(1, 9),
+                    diagnostic: Diagnostic {
+                        severity: DiagnosticSeverity::HINT,
+                        message: "error 1 hint 1".to_string(),
+                        group_id: 0,
+                        is_primary: false,
+                        ..Default::default()
+                    }
+                },
+                DiagnosticEntry {
+                    range: Point::new(1, 13)..Point::new(1, 15),
+                    diagnostic: Diagnostic {
+                        severity: DiagnosticSeverity::HINT,
+                        message: "error 2 hint 1".to_string(),
+                        group_id: 1,
+                        is_primary: false,
+                        ..Default::default()
+                    }
+                },
+                DiagnosticEntry {
+                    range: Point::new(1, 13)..Point::new(1, 15),
+                    diagnostic: Diagnostic {
+                        severity: DiagnosticSeverity::HINT,
+                        message: "error 2 hint 2".to_string(),
+                        group_id: 1,
+                        is_primary: false,
+                        ..Default::default()
+                    }
+                },
+                DiagnosticEntry {
+                    range: Point::new(2, 8)..Point::new(2, 17),
+                    diagnostic: Diagnostic {
+                        severity: DiagnosticSeverity::ERROR,
+                        message: "error 2".to_string(),
+                        group_id: 1,
+                        is_primary: true,
+                        ..Default::default()
+                    }
+                }
+            ]
+        );
+
+        assert_eq!(
+            buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
+            &[
+                DiagnosticEntry {
+                    range: Point::new(1, 8)..Point::new(1, 9),
+                    diagnostic: Diagnostic {
+                        severity: DiagnosticSeverity::WARNING,
+                        message: "error 1".to_string(),
+                        group_id: 0,
+                        is_primary: true,
+                        ..Default::default()
+                    }
+                },
+                DiagnosticEntry {
+                    range: Point::new(1, 8)..Point::new(1, 9),
+                    diagnostic: Diagnostic {
+                        severity: DiagnosticSeverity::HINT,
+                        message: "error 1 hint 1".to_string(),
+                        group_id: 0,
+                        is_primary: false,
+                        ..Default::default()
+                    }
+                },
+            ]
+        );
+        assert_eq!(
+            buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
+            &[
+                DiagnosticEntry {
+                    range: Point::new(1, 13)..Point::new(1, 15),
+                    diagnostic: Diagnostic {
+                        severity: DiagnosticSeverity::HINT,
+                        message: "error 2 hint 1".to_string(),
+                        group_id: 1,
+                        is_primary: false,
+                        ..Default::default()
+                    }
+                },
+                DiagnosticEntry {
+                    range: Point::new(1, 13)..Point::new(1, 15),
+                    diagnostic: Diagnostic {
+                        severity: DiagnosticSeverity::HINT,
+                        message: "error 2 hint 2".to_string(),
+                        group_id: 1,
+                        is_primary: false,
+                        ..Default::default()
+                    }
+                },
+                DiagnosticEntry {
+                    range: Point::new(2, 8)..Point::new(2, 17),
+                    diagnostic: Diagnostic {
+                        severity: DiagnosticSeverity::ERROR,
+                        message: "error 2".to_string(),
+                        group_id: 1,
+                        is_primary: true,
+                        ..Default::default()
+                    }
+                }
+            ]
+        );
     }
 
     #[gpui::test(iterations = 100)]

crates/rpc/proto/zed.proto 🔗

@@ -40,6 +40,7 @@ message Envelope {
         UnshareWorktree unshare_worktree = 35;
         UpdateContacts update_contacts = 36;
         LeaveWorktree leave_worktree = 37;
+        UpdateDiagnosticSummary update_diagnostic_summary = 38;
     }
 }
 
@@ -138,6 +139,13 @@ message BufferSaved {
     Timestamp mtime = 4;
 }
 
+message UpdateDiagnosticSummary {
+    uint64 worktree_id = 1;
+    string path = 2;
+    uint32 error_count = 3;
+    uint32 warning_count = 4;
+}
+
 message GetChannels {}
 
 message GetChannelsResponse {

crates/server/src/rpc.rs 🔗

@@ -1719,7 +1719,8 @@ mod tests {
                             group_id: 0,
                             message: "message 1".to_string(),
                             severity: lsp::DiagnosticSeverity::ERROR,
-                            is_primary: true
+                            is_primary: true,
+                            ..Default::default()
                         }
                     },
                     DiagnosticEntry {
@@ -1728,7 +1729,8 @@ mod tests {
                             group_id: 1,
                             severity: lsp::DiagnosticSeverity::WARNING,
                             message: "message 2".to_string(),
-                            is_primary: true
+                            is_primary: true,
+                            ..Default::default()
                         }
                     }
                 ]