Co-Authored-By: Antonio Scandurra <me@as-cii.com>
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Nathan Sobo 2021-12-23 11:24:35 -07:00
parent 7b453beebc
commit e3ecd87081
6 changed files with 156 additions and 58 deletions

View file

@ -77,7 +77,7 @@ pub struct Buffer {
pub struct BufferSnapshot { pub struct BufferSnapshot {
text: text::BufferSnapshot, text: text::BufferSnapshot,
tree: Option<Tree>, tree: Option<Tree>,
diagnostics: DiagnosticSet, diagnostics: HashMap<&'static str, DiagnosticSet>,
remote_selections: TreeMap<ReplicaId, Arc<[Selection<Anchor>]>>, remote_selections: TreeMap<ReplicaId, Arc<[Selection<Anchor>]>>,
diagnostics_update_count: usize, diagnostics_update_count: usize,
is_parsing: bool, is_parsing: bool,
@ -115,7 +115,7 @@ struct LanguageServerSnapshot {
pub enum Operation { pub enum Operation {
Buffer(text::Operation), Buffer(text::Operation),
UpdateDiagnostics { UpdateDiagnostics {
diagnostics: Arc<[DiagnosticEntry<Anchor>]>, diagnostic_set: Arc<DiagnosticSet>,
lamport_timestamp: clock::Lamport, lamport_timestamp: clock::Lamport,
}, },
UpdateSelections { UpdateSelections {
@ -298,10 +298,12 @@ impl Buffer {
proto::deserialize_selections(selection_set.selections), proto::deserialize_selections(selection_set.selections),
); );
} }
this.apply_diagnostic_update( for diagnostic_set in message.diagnostic_sets {
Arc::from(proto::deserialize_diagnostics(message.diagnostics)), this.apply_diagnostic_update(
cx, Arc::from(proto::deserialize_diagnostics(diagnostic_set)),
); cx,
);
}
Ok(this) Ok(this)
} }
@ -323,7 +325,7 @@ impl Buffer {
selections: proto::serialize_selections(selections), selections: proto::serialize_selections(selections),
}) })
.collect(), .collect(),
diagnostics: proto::serialize_diagnostics(self.diagnostics.iter()), diagnostics: proto::serialize_diagnostic_set(self.diagnostics.iter()),
} }
} }

View file

@ -8,8 +8,9 @@ use std::{
use sum_tree::{self, Bias, SumTree}; use sum_tree::{self, Bias, SumTree};
use text::{Anchor, FromAnchor, PointUtf16, ToOffset}; use text::{Anchor, FromAnchor, PointUtf16, ToOffset};
#[derive(Clone, Default)] #[derive(Clone, Debug, Default)]
pub struct DiagnosticSet { pub struct DiagnosticSet {
provider_name: String,
diagnostics: SumTree<DiagnosticEntry<Anchor>>, diagnostics: SumTree<DiagnosticEntry<Anchor>>,
} }
@ -34,22 +35,32 @@ pub struct Summary {
} }
impl DiagnosticSet { impl DiagnosticSet {
pub fn from_sorted_entries<I>(iter: I, buffer: &text::BufferSnapshot) -> Self pub fn provider_name(&self) -> &str {
&self.provider_name
}
pub fn from_sorted_entries<I>(
provider_name: String,
iter: I,
buffer: &text::BufferSnapshot,
) -> Self
where where
I: IntoIterator<Item = DiagnosticEntry<Anchor>>, I: IntoIterator<Item = DiagnosticEntry<Anchor>>,
{ {
Self { Self {
provider_name,
diagnostics: SumTree::from_iter(iter, buffer), diagnostics: SumTree::from_iter(iter, buffer),
} }
} }
pub fn new<I>(iter: I, buffer: &text::BufferSnapshot) -> Self pub fn new<I>(provider_name: &'static str, iter: I, buffer: &text::BufferSnapshot) -> Self
where where
I: IntoIterator<Item = DiagnosticEntry<PointUtf16>>, I: IntoIterator<Item = DiagnosticEntry<PointUtf16>>,
{ {
let mut entries = iter.into_iter().collect::<Vec<_>>(); let mut entries = iter.into_iter().collect::<Vec<_>>();
entries.sort_unstable_by_key(|entry| (entry.range.start, Reverse(entry.range.end))); entries.sort_unstable_by_key(|entry| (entry.range.start, Reverse(entry.range.end)));
Self { Self {
provider_name,
diagnostics: SumTree::from_iter( diagnostics: SumTree::from_iter(
entries.into_iter().map(|entry| DiagnosticEntry { entries.into_iter().map(|entry| DiagnosticEntry {
range: buffer.anchor_before(entry.range.start) range: buffer.anchor_before(entry.range.start)

View file

@ -66,6 +66,8 @@ pub struct BracketPair {
#[async_trait] #[async_trait]
pub trait DiagnosticSource: 'static + Send + Sync { pub trait DiagnosticSource: 'static + Send + Sync {
fn name(&self) -> &'static str;
async fn diagnose( async fn diagnose(
&self, &self,
path: Arc<Path>, path: Arc<Path>,

View file

@ -1,4 +1,4 @@
use crate::{diagnostic_set::DiagnosticEntry, Diagnostic, Operation}; use crate::{diagnostic_set::DiagnosticEntry, Diagnostic, DiagnosticSet, Operation};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use clock::ReplicaId; use clock::ReplicaId;
use lsp::DiagnosticSeverity; use lsp::DiagnosticSeverity;
@ -57,12 +57,12 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation {
lamport_timestamp: lamport_timestamp.value, lamport_timestamp: lamport_timestamp.value,
}), }),
Operation::UpdateDiagnostics { Operation::UpdateDiagnostics {
diagnostics, diagnostic_set,
lamport_timestamp, lamport_timestamp,
} => proto::operation::Variant::UpdateDiagnostics(proto::UpdateDiagnostics { } => proto::operation::Variant::UpdateDiagnosticSet(proto::UpdateDiagnosticSet {
replica_id: lamport_timestamp.replica_id as u32, replica_id: lamport_timestamp.replica_id as u32,
lamport_timestamp: lamport_timestamp.value, 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<Anchor>]>) -> Vec<proto:
.collect() .collect()
} }
pub fn serialize_diagnostics<'a>( pub fn serialize_diagnostic_set(set: &DiagnosticSet) -> proto::DiagnosticSet {
diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<Anchor>>, proto::DiagnosticSet {
) -> Vec<proto::Diagnostic> { provider_name: set.provider_name().to_string(),
diagnostics diagnostics: set
.into_iter() .iter()
.map(|entry| proto::Diagnostic { .map(|entry| proto::Diagnostic {
start: Some(serialize_anchor(&entry.range.start)), start: Some(serialize_anchor(&entry.range.start)),
end: Some(serialize_anchor(&entry.range.end)), end: Some(serialize_anchor(&entry.range.end)),
message: entry.diagnostic.message.clone(), message: entry.diagnostic.message.clone(),
severity: match entry.diagnostic.severity { severity: match entry.diagnostic.severity {
DiagnosticSeverity::ERROR => proto::diagnostic::Severity::Error, DiagnosticSeverity::ERROR => proto::diagnostic::Severity::Error,
DiagnosticSeverity::WARNING => proto::diagnostic::Severity::Warning, DiagnosticSeverity::WARNING => proto::diagnostic::Severity::Warning,
DiagnosticSeverity::INFORMATION => proto::diagnostic::Severity::Information, DiagnosticSeverity::INFORMATION => proto::diagnostic::Severity::Information,
DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint, DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint,
_ => proto::diagnostic::Severity::None, _ => proto::diagnostic::Severity::None,
} as i32, } as i32,
group_id: entry.diagnostic.group_id as u64, group_id: entry.diagnostic.group_id as u64,
is_primary: entry.diagnostic.is_primary, is_primary: entry.diagnostic.is_primary,
is_valid: entry.diagnostic.is_valid, is_valid: entry.diagnostic.is_valid,
code: entry.diagnostic.code.clone(), code: entry.diagnostic.code.clone(),
is_disk_based: entry.diagnostic.is_disk_based, is_disk_based: entry.diagnostic.is_disk_based,
}) })
.collect() .collect(),
}
} }
fn serialize_anchor(anchor: &Anchor) -> proto::Anchor { fn serialize_anchor(anchor: &Anchor) -> proto::Anchor {
@ -207,13 +208,15 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
value: message.lamport_timestamp, value: message.lamport_timestamp,
}, },
}, },
proto::operation::Variant::UpdateDiagnostics(message) => Operation::UpdateDiagnostics { proto::operation::Variant::UpdateDiagnosticSet(message) => {
diagnostics: Arc::from(deserialize_diagnostics(message.diagnostics)), Operation::UpdateDiagnostics {
lamport_timestamp: clock::Lamport { diagnostics: Arc::from(deserialize_diagnostic_set(message.diagnostic_set?)),
replica_id: message.replica_id as ReplicaId, lamport_timestamp: clock::Lamport {
value: message.lamport_timestamp, replica_id: message.replica_id as ReplicaId,
}, value: message.lamport_timestamp,
}, },
}
}
}, },
) )
} }
@ -253,12 +256,13 @@ pub fn deserialize_selections(selections: Vec<proto::Selection>) -> Arc<[Selecti
) )
} }
pub fn deserialize_diagnostics( pub fn deserialize_diagnostic_set(
diagnostics: Vec<proto::Diagnostic>, message: proto::DiagnosticSet,
) -> Vec<DiagnosticEntry<Anchor>> { buffer: &BufferSnapshot,
diagnostics ) -> DiagnosticSet {
.into_iter() DiagnosticSet::from_sorted_entries(
.filter_map(|diagnostic| { message.provider_name,
message.diagnostics.into_iter().filter_map(|diagnostic| {
Some(DiagnosticEntry { Some(DiagnosticEntry {
range: deserialize_anchor(diagnostic.start?)?..deserialize_anchor(diagnostic.end?)?, range: deserialize_anchor(diagnostic.start?)?..deserialize_anchor(diagnostic.end?)?,
diagnostic: Diagnostic { diagnostic: Diagnostic {
@ -277,8 +281,9 @@ pub fn deserialize_diagnostics(
is_disk_based: diagnostic.is_disk_based, is_disk_based: diagnostic.is_disk_based,
}, },
}) })
}) }),
.collect() buffer,
)
} }
fn deserialize_anchor(anchor: proto::Anchor) -> Option<Anchor> { fn deserialize_anchor(anchor: proto::Anchor) -> Option<Anchor> {

View file

@ -672,6 +672,79 @@ impl Worktree {
} }
} }
pub fn update_lsp_diagnostics(
&mut self,
mut params: lsp::PublishDiagnosticsParams,
disk_based_sources: &HashSet<String>,
cx: &mut ModelContext<Worktree>,
) -> 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::<Vec<_>>();
self.update_diagnostic_entries(worktree_path, params.version, diagnostics, cx)
}
pub fn update_diagnostics( pub fn update_diagnostics(
&mut self, &mut self,
mut params: lsp::PublishDiagnosticsParams, mut params: lsp::PublishDiagnosticsParams,
@ -1046,7 +1119,7 @@ impl LocalWorktree {
while let Ok(diagnostics) = diagnostics_rx.recv().await { while let Ok(diagnostics) = diagnostics_rx.recv().await {
if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { if let Some(handle) = cx.read(|cx| this.upgrade(cx)) {
handle.update(&mut cx, |this, 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(); .log_err();
}); });
} else { } else {
@ -3835,7 +3908,7 @@ mod tests {
worktree worktree
.update(&mut cx, |tree, cx| { .update(&mut cx, |tree, cx| {
tree.update_diagnostics(message, &Default::default(), cx) tree.update_lsp_diagnostics(message, &Default::default(), cx)
}) })
.unwrap(); .unwrap();
let buffer = buffer.read_with(&cx, |buffer, _| buffer.snapshot()); let buffer = buffer.read_with(&cx, |buffer, _| buffer.snapshot());

View file

@ -265,7 +265,7 @@ message Buffer {
string content = 2; string content = 2;
repeated Operation.Edit history = 3; repeated Operation.Edit history = 3;
repeated SelectionSet selections = 4; repeated SelectionSet selections = 4;
repeated Diagnostic diagnostics = 5; repeated DiagnosticSet diagnostic_sets = 5;
} }
message SelectionSet { message SelectionSet {
@ -292,10 +292,15 @@ enum Bias {
Right = 1; Right = 1;
} }
message UpdateDiagnostics { message UpdateDiagnosticSet {
uint32 replica_id = 1; uint32 replica_id = 1;
uint32 lamport_timestamp = 2; uint32 lamport_timestamp = 2;
repeated Diagnostic diagnostics = 3; DiagnosticSet diagnostic_set = 3;
}
message DiagnosticSet {
string provider_name = 1;
repeated Diagnostic diagnostics = 2;
} }
message Diagnostic { message Diagnostic {
@ -324,7 +329,7 @@ message Operation {
Undo undo = 2; Undo undo = 2;
UpdateSelections update_selections = 3; UpdateSelections update_selections = 3;
RemoveSelections remove_selections = 4; RemoveSelections remove_selections = 4;
UpdateDiagnostics update_diagnostics = 5; UpdateDiagnosticSet update_diagnostic_set = 5;
} }
message Edit { message Edit {