Always group diagnostics the way they're grouped in the LSP message

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2022-01-06 14:22:28 -08:00
parent 943571af2a
commit 10548c2038

View file

@ -34,7 +34,6 @@ use std::{
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
fmt, fmt,
future::Future, future::Future,
mem,
ops::{Deref, Range}, ops::{Deref, Range},
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{ sync::{
@ -691,7 +690,7 @@ impl Worktree {
pub fn update_diagnostics( pub fn update_diagnostics(
&mut self, &mut self,
mut params: lsp::PublishDiagnosticsParams, params: lsp::PublishDiagnosticsParams,
disk_based_sources: &HashSet<String>, disk_based_sources: &HashSet<String>,
cx: &mut ModelContext<Worktree>, cx: &mut ModelContext<Worktree>,
) -> Result<()> { ) -> Result<()> {
@ -706,58 +705,92 @@ impl Worktree {
.context("path is not within worktree")?, .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; let mut next_group_id = 0;
for diagnostic in &mut params.diagnostics { let mut diagnostics = Vec::default();
let mut primary_diagnostic_group_ids = HashMap::default();
let mut sources_by_group_id = HashMap::default();
let mut supporting_diagnostic_severities = HashMap::default();
for diagnostic in &params.diagnostics {
let source = diagnostic.source.as_ref(); let source = diagnostic.source.as_ref();
let code = diagnostic.code.as_ref(); let code = diagnostic.code.as_ref().map(|code| match code {
let group_id = diagnostic_ranges(&diagnostic, &abs_path) lsp::NumberOrString::Number(code) => code.to_string(),
.find_map(|range| group_ids_by_diagnostic_range.get(&(source, code, range))) lsp::NumberOrString::String(code) => code.clone(),
.copied() });
.unwrap_or_else(|| { let range = range_from_lsp(diagnostic.range);
let group_id = post_inc(&mut next_group_id); let is_supporting = diagnostic
for range in diagnostic_ranges(&diagnostic, &abs_path) { .related_information
group_ids_by_diagnostic_range.insert((source, code, range), group_id); .as_ref()
} .map_or(false, |infos| {
group_id infos.iter().any(|info| {
primary_diagnostic_group_ids.contains_key(&(
source,
code.clone(),
range_from_lsp(info.location.range),
))
})
}); });
diagnostics_by_group_id if is_supporting {
.entry(group_id) if let Some(severity) = diagnostic.severity {
.or_insert(Vec::new()) supporting_diagnostic_severities
.push(DiagnosticEntry { .insert((source, code.clone(), range), severity);
range: diagnostic.range.start.to_point_utf16() }
..diagnostic.range.end.to_point_utf16(), } else {
let group_id = post_inc(&mut next_group_id);
let is_disk_based =
source.map_or(false, |source| disk_based_sources.contains(source));
sources_by_group_id.insert(group_id, source);
primary_diagnostic_group_ids
.insert((source, code.clone(), range.clone()), group_id);
diagnostics.push(DiagnosticEntry {
range,
diagnostic: Diagnostic { diagnostic: Diagnostic {
code: diagnostic.code.clone().map(|code| match code { code: code.clone(),
lsp::NumberOrString::Number(code) => code.to_string(),
lsp::NumberOrString::String(code) => code,
}),
severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR), severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
message: mem::take(&mut diagnostic.message), message: diagnostic.message.clone(),
group_id, group_id,
is_primary: false, is_primary: true,
is_valid: true, is_valid: true,
is_disk_based: diagnostic is_disk_based,
.source
.as_ref()
.map_or(false, |source| disk_based_sources.contains(source)),
}, },
}); });
if let Some(infos) = &diagnostic.related_information {
for info in infos {
if info.location.uri == params.uri {
let range = range_from_lsp(info.location.range);
diagnostics.push(DiagnosticEntry {
range,
diagnostic: Diagnostic {
code: code.clone(),
severity: DiagnosticSeverity::INFORMATION,
message: info.message.clone(),
group_id,
is_primary: false,
is_valid: true,
is_disk_based,
},
});
}
}
}
}
} }
let diagnostics = diagnostics_by_group_id for entry in &mut diagnostics {
.into_values() let diagnostic = &mut entry.diagnostic;
.flat_map(|mut diagnostics| { if !diagnostic.is_primary {
let primary = diagnostics let source = *sources_by_group_id.get(&diagnostic.group_id).unwrap();
.iter_mut() if let Some(&severity) = supporting_diagnostic_severities.get(&(
.min_by_key(|entry| entry.diagnostic.severity) source,
.unwrap(); diagnostic.code.clone(),
primary.diagnostic.is_primary = true; entry.range.clone(),
diagnostics )) {
}) diagnostic.severity = severity;
.collect::<Vec<_>>(); }
}
}
self.update_diagnostic_entries(worktree_path, params.version, diagnostics, cx)?; self.update_diagnostic_entries(worktree_path, params.version, diagnostics, cx)?;
Ok(()) Ok(())
@ -3103,32 +3136,10 @@ impl ToPointUtf16 for lsp::Position {
} }
} }
fn diagnostic_ranges<'a>( fn range_from_lsp(range: lsp::Range) -> Range<PointUtf16> {
diagnostic: &'a lsp::Diagnostic, let start = PointUtf16::new(range.start.line, range.start.character);
abs_path: &'a Path, let end = PointUtf16::new(range.end.line, range.end.character);
) -> impl 'a + Iterator<Item = Range<PointUtf16>> { start..end
diagnostic
.related_information
.iter()
.flatten()
.filter_map(move |info| {
if info.location.uri.to_file_path().ok()? == abs_path {
let info_start = PointUtf16::new(
info.location.range.start.line,
info.location.range.start.character,
);
let info_end = PointUtf16::new(
info.location.range.end.line,
info.location.range.end.character,
);
Some(info_start..info_end)
} else {
None
}
})
.chain(Some(
diagnostic.range.start.to_point_utf16()..diagnostic.range.end.to_point_utf16(),
))
} }
#[cfg(test)] #[cfg(test)]