diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 475fc6eaab..764b5e1278 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -77,7 +77,7 @@ pub struct Buffer { pub struct BufferSnapshot { text: text::BufferSnapshot, tree: Option, - diagnostics: DiagnosticSet, + diagnostics: HashMap<&'static str, DiagnosticSet>, remote_selections: TreeMap]>>, diagnostics_update_count: usize, is_parsing: bool, @@ -115,7 +115,7 @@ struct LanguageServerSnapshot { pub enum Operation { Buffer(text::Operation), UpdateDiagnostics { - diagnostics: Arc<[DiagnosticEntry]>, + diagnostic_set: Arc, lamport_timestamp: clock::Lamport, }, UpdateSelections { @@ -298,10 +298,12 @@ impl Buffer { proto::deserialize_selections(selection_set.selections), ); } - this.apply_diagnostic_update( - Arc::from(proto::deserialize_diagnostics(message.diagnostics)), - cx, - ); + for diagnostic_set in message.diagnostic_sets { + this.apply_diagnostic_update( + Arc::from(proto::deserialize_diagnostics(diagnostic_set)), + cx, + ); + } Ok(this) } @@ -323,7 +325,7 @@ impl Buffer { selections: proto::serialize_selections(selections), }) .collect(), - diagnostics: proto::serialize_diagnostics(self.diagnostics.iter()), + diagnostics: proto::serialize_diagnostic_set(self.diagnostics.iter()), } } diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index a246a104aa..2f27593575 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -8,8 +8,9 @@ use std::{ use sum_tree::{self, Bias, SumTree}; use text::{Anchor, FromAnchor, PointUtf16, ToOffset}; -#[derive(Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct DiagnosticSet { + provider_name: String, diagnostics: SumTree>, } @@ -34,22 +35,32 @@ pub struct Summary { } impl DiagnosticSet { - pub fn from_sorted_entries(iter: I, buffer: &text::BufferSnapshot) -> Self + pub fn provider_name(&self) -> &str { + &self.provider_name + } + + pub fn from_sorted_entries( + provider_name: String, + iter: I, + buffer: &text::BufferSnapshot, + ) -> Self where I: IntoIterator>, { Self { + provider_name, diagnostics: SumTree::from_iter(iter, buffer), } } - pub fn new(iter: I, buffer: &text::BufferSnapshot) -> Self + pub fn new(provider_name: &'static str, iter: I, buffer: &text::BufferSnapshot) -> Self where I: IntoIterator>, { let mut entries = iter.into_iter().collect::>(); entries.sort_unstable_by_key(|entry| (entry.range.start, Reverse(entry.range.end))); Self { + provider_name, diagnostics: SumTree::from_iter( entries.into_iter().map(|entry| DiagnosticEntry { range: buffer.anchor_before(entry.range.start) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 1c369e738b..e752364835 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -66,6 +66,8 @@ pub struct BracketPair { #[async_trait] pub trait DiagnosticSource: 'static + Send + Sync { + fn name(&self) -> &'static str; + async fn diagnose( &self, path: Arc, diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index bf33673fc9..8dcc741e1d 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -1,4 +1,4 @@ -use crate::{diagnostic_set::DiagnosticEntry, Diagnostic, Operation}; +use crate::{diagnostic_set::DiagnosticEntry, Diagnostic, DiagnosticSet, Operation}; use anyhow::{anyhow, Result}; use clock::ReplicaId; use lsp::DiagnosticSeverity; @@ -57,12 +57,12 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation { lamport_timestamp: lamport_timestamp.value, }), Operation::UpdateDiagnostics { - diagnostics, + diagnostic_set, lamport_timestamp, - } => proto::operation::Variant::UpdateDiagnostics(proto::UpdateDiagnostics { + } => proto::operation::Variant::UpdateDiagnosticSet(proto::UpdateDiagnosticSet { replica_id: lamport_timestamp.replica_id as u32, lamport_timestamp: lamport_timestamp.value, - diagnostics: serialize_diagnostics(diagnostics.iter()), + diagnostic_set: Some(serialize_diagnostic_set(&diagnostic_set)), }), }), } @@ -99,29 +99,30 @@ pub fn serialize_selections(selections: &Arc<[Selection]>) -> Vec( - diagnostics: impl IntoIterator>, -) -> Vec { - diagnostics - .into_iter() - .map(|entry| proto::Diagnostic { - start: Some(serialize_anchor(&entry.range.start)), - end: Some(serialize_anchor(&entry.range.end)), - message: entry.diagnostic.message.clone(), - severity: match entry.diagnostic.severity { - DiagnosticSeverity::ERROR => proto::diagnostic::Severity::Error, - DiagnosticSeverity::WARNING => proto::diagnostic::Severity::Warning, - DiagnosticSeverity::INFORMATION => proto::diagnostic::Severity::Information, - DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint, - _ => proto::diagnostic::Severity::None, - } as i32, - group_id: entry.diagnostic.group_id as u64, - is_primary: entry.diagnostic.is_primary, - is_valid: entry.diagnostic.is_valid, - code: entry.diagnostic.code.clone(), - is_disk_based: entry.diagnostic.is_disk_based, - }) - .collect() +pub fn serialize_diagnostic_set(set: &DiagnosticSet) -> proto::DiagnosticSet { + proto::DiagnosticSet { + provider_name: set.provider_name().to_string(), + diagnostics: set + .iter() + .map(|entry| proto::Diagnostic { + start: Some(serialize_anchor(&entry.range.start)), + end: Some(serialize_anchor(&entry.range.end)), + message: entry.diagnostic.message.clone(), + severity: match entry.diagnostic.severity { + DiagnosticSeverity::ERROR => proto::diagnostic::Severity::Error, + DiagnosticSeverity::WARNING => proto::diagnostic::Severity::Warning, + DiagnosticSeverity::INFORMATION => proto::diagnostic::Severity::Information, + DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint, + _ => proto::diagnostic::Severity::None, + } as i32, + group_id: entry.diagnostic.group_id as u64, + is_primary: entry.diagnostic.is_primary, + is_valid: entry.diagnostic.is_valid, + code: entry.diagnostic.code.clone(), + is_disk_based: entry.diagnostic.is_disk_based, + }) + .collect(), + } } fn serialize_anchor(anchor: &Anchor) -> proto::Anchor { @@ -207,13 +208,15 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { value: message.lamport_timestamp, }, }, - proto::operation::Variant::UpdateDiagnostics(message) => Operation::UpdateDiagnostics { - diagnostics: Arc::from(deserialize_diagnostics(message.diagnostics)), - lamport_timestamp: clock::Lamport { - replica_id: message.replica_id as ReplicaId, - value: message.lamport_timestamp, - }, - }, + proto::operation::Variant::UpdateDiagnosticSet(message) => { + Operation::UpdateDiagnostics { + diagnostics: Arc::from(deserialize_diagnostic_set(message.diagnostic_set?)), + lamport_timestamp: clock::Lamport { + replica_id: message.replica_id as ReplicaId, + value: message.lamport_timestamp, + }, + } + } }, ) } @@ -253,12 +256,13 @@ pub fn deserialize_selections(selections: Vec) -> Arc<[Selecti ) } -pub fn deserialize_diagnostics( - diagnostics: Vec, -) -> Vec> { - diagnostics - .into_iter() - .filter_map(|diagnostic| { +pub fn deserialize_diagnostic_set( + message: proto::DiagnosticSet, + buffer: &BufferSnapshot, +) -> DiagnosticSet { + DiagnosticSet::from_sorted_entries( + message.provider_name, + message.diagnostics.into_iter().filter_map(|diagnostic| { Some(DiagnosticEntry { range: deserialize_anchor(diagnostic.start?)?..deserialize_anchor(diagnostic.end?)?, diagnostic: Diagnostic { @@ -277,8 +281,9 @@ pub fn deserialize_diagnostics( is_disk_based: diagnostic.is_disk_based, }, }) - }) - .collect() + }), + buffer, + ) } fn deserialize_anchor(anchor: proto::Anchor) -> Option { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index cef2c35e35..8349a2aa27 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -672,6 +672,79 @@ impl Worktree { } } + pub fn update_lsp_diagnostics( + &mut self, + mut params: lsp::PublishDiagnosticsParams, + disk_based_sources: &HashSet, + cx: &mut ModelContext, + ) -> Result<()> { + let this = self.as_local_mut().ok_or_else(|| anyhow!("not local"))?; + let abs_path = params + .uri + .to_file_path() + .map_err(|_| anyhow!("URI is not a file"))?; + 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::default(); + let mut diagnostics_by_group_id = HashMap::default(); + let mut next_group_id = 0; + for diagnostic in &mut params.diagnostics { + let source = diagnostic.source.as_ref(); + let code = diagnostic.code.as_ref(); + let group_id = diagnostic_ranges(&diagnostic, &abs_path) + .find_map(|range| group_ids_by_diagnostic_range.get(&(source, code, range))) + .copied() + .unwrap_or_else(|| { + let group_id = post_inc(&mut next_group_id); + for range in diagnostic_ranges(&diagnostic, &abs_path) { + group_ids_by_diagnostic_range.insert((source, code, range), group_id); + } + group_id + }); + + diagnostics_by_group_id + .entry(group_id) + .or_insert(Vec::new()) + .push(DiagnosticEntry { + range: diagnostic.range.start.to_point_utf16() + ..diagnostic.range.end.to_point_utf16(), + diagnostic: Diagnostic { + 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: mem::take(&mut diagnostic.message), + group_id, + is_primary: false, + is_valid: true, + is_disk_based: diagnostic + .source + .as_ref() + .map_or(false, |source| disk_based_sources.contains(source)), + }, + }); + } + + let diagnostics = diagnostics_by_group_id + .into_values() + .flat_map(|mut diagnostics| { + let primary = diagnostics + .iter_mut() + .min_by_key(|entry| entry.diagnostic.severity) + .unwrap(); + primary.diagnostic.is_primary = true; + diagnostics + }) + .collect::>(); + + self.update_diagnostic_entries(worktree_path, params.version, diagnostics, cx) + } + pub fn update_diagnostics( &mut self, mut params: lsp::PublishDiagnosticsParams, @@ -1046,7 +1119,7 @@ impl LocalWorktree { 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) + this.update_lsp_diagnostics(diagnostics, &disk_based_sources, cx) .log_err(); }); } else { @@ -3835,7 +3908,7 @@ mod tests { worktree .update(&mut cx, |tree, cx| { - tree.update_diagnostics(message, &Default::default(), cx) + tree.update_lsp_diagnostics(message, &Default::default(), cx) }) .unwrap(); let buffer = buffer.read_with(&cx, |buffer, _| buffer.snapshot()); diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 5ef34960e7..0bcd992788 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -265,7 +265,7 @@ message Buffer { string content = 2; repeated Operation.Edit history = 3; repeated SelectionSet selections = 4; - repeated Diagnostic diagnostics = 5; + repeated DiagnosticSet diagnostic_sets = 5; } message SelectionSet { @@ -292,10 +292,15 @@ enum Bias { Right = 1; } -message UpdateDiagnostics { +message UpdateDiagnosticSet { uint32 replica_id = 1; uint32 lamport_timestamp = 2; - repeated Diagnostic diagnostics = 3; + DiagnosticSet diagnostic_set = 3; +} + +message DiagnosticSet { + string provider_name = 1; + repeated Diagnostic diagnostics = 2; } message Diagnostic { @@ -324,7 +329,7 @@ message Operation { Undo undo = 2; UpdateSelections update_selections = 3; RemoveSelections remove_selections = 4; - UpdateDiagnostics update_diagnostics = 5; + UpdateDiagnosticSet update_diagnostic_set = 5; } message Edit {