diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 83bc074f18..d04c69611b 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -15,7 +15,9 @@ impl ProjectDiagnostics { cx: &mut ViewContext, ) -> 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| { diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 07f21dabf8..93c2f99c98 100644 --- a/crates/language/src/tests.rs +++ b/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 } - -#[derive(Clone)] -struct FakeFile { - abs_path: PathBuf, -} - -impl FakeFile { - fn new(abs_path: impl Into) -> Self { - Self { - abs_path: abs_path.into(), - } - } -} - -impl File for FakeFile { - fn worktree_id(&self) -> usize { - todo!() - } - - fn entry_id(&self) -> Option { - todo!() - } - - fn mtime(&self) -> SystemTime { - SystemTime::now() - } - - fn path(&self) -> &Arc { - todo!() - } - - fn abs_path(&self) -> Option { - Some(self.abs_path.clone()) - } - - fn full_path(&self) -> PathBuf { - todo!() - } - - fn file_name(&self) -> Option { - todo!() - } - - fn is_deleted(&self) -> bool { - todo!() - } - - fn save( - &self, - _: u64, - _: Rope, - _: clock::Global, - _: &mut MutableAppContext, - ) -> Task> { - todo!() - } - - fn load_local(&self, _: &AppContext) -> Option>> { - todo!() - } - - fn buffer_updated(&self, _: u64, _: super::Operation, _: &mut MutableAppContext) { - todo!() - } - - fn buffer_removed(&self, _: u64, _: &mut MutableAppContext) { - todo!() - } - - fn boxed_clone(&self) -> Box { - todo!() - } - - fn as_any(&self) -> &dyn Any { - todo!() - } -} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 10e92ae56a..ca15f67377 100644 --- a/crates/project/src/project.rs +++ b/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, } +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])> { + ) -> impl Iterator { std::iter::empty() } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 166de727a7..fa6f6a0f39 100644 --- a/crates/project/src/worktree.rs +++ b/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 { + std::iter::empty() + } + pub fn open_buffer( &mut self, path: impl AsRef, @@ -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, open_buffers: HashMap>, shared_buffers: HashMap>>, - diagnostics: HashMap>>, + diagnostics: HashMap, Vec>>, collaborators: HashMap, queued_operations: Vec<(u64, Operation)>, languages: Arc, @@ -1488,6 +1501,7 @@ pub struct RemoteWorktree { replica_id: ReplicaId, open_buffers: HashMap, collaborators: HashMap, + diagnostics: Vec, languages: Arc, user_store: ModelHandle, 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) { - 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) { + 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::>(), - &[ - 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::(0) - .collect::>(), - &[ - 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::(1) - .collect::>(), - &[ - 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::>(), + &[ + 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::(0).collect::>(), + &[ + 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::(1).collect::>(), + &[ + 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)] diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index fa4efe695b..34c8043795 100644 --- a/crates/rpc/proto/zed.proto +++ b/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 { diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 7220740af4..e06db2a273 100644 --- a/crates/server/src/rpc.rs +++ b/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() } } ]