Tidy up diagnostics more (#29629)
- Stop merging same row diagnostics - (for Rust) show code fragments surrounded by `'s in monospace Co-authored-by: Serge Radinovich <sergeradinovich@gmail.com> Closes #29362 Release Notes: - diagnostics: Diagnostics are no longer merged when they're on the same line - rust: Diagnostics now show code snippets in monospace font: <img width="551" alt="Screenshot 2025-04-29 at 16 13 45" src="https://github.com/user-attachments/assets/d289be31-717d-404f-a76a-a0cda3e96fbe" /> Co-authored-by: Serge Radinovich <sergeradinovich@gmail.com>
This commit is contained in:
parent
b4732235e3
commit
e364e48266
9 changed files with 106 additions and 99 deletions
|
@ -7,7 +7,7 @@ use editor::{
|
|||
scroll::Autoscroll,
|
||||
};
|
||||
use gpui::{AppContext, Entity, Focusable, WeakEntity};
|
||||
use language::{BufferId, DiagnosticEntry};
|
||||
use language::{BufferId, Diagnostic, DiagnosticEntry};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use markdown::{Markdown, MarkdownElement};
|
||||
use settings::Settings;
|
||||
|
@ -28,7 +28,6 @@ impl DiagnosticRenderer {
|
|||
diagnostic_group: Vec<DiagnosticEntry<Point>>,
|
||||
buffer_id: BufferId,
|
||||
diagnostics_editor: Option<WeakEntity<ProjectDiagnosticsEditor>>,
|
||||
merge_same_row: bool,
|
||||
cx: &mut App,
|
||||
) -> Vec<DiagnosticBlock> {
|
||||
let Some(primary_ix) = diagnostic_group
|
||||
|
@ -38,30 +37,12 @@ impl DiagnosticRenderer {
|
|||
return Vec::new();
|
||||
};
|
||||
let primary = diagnostic_group[primary_ix].clone();
|
||||
let mut same_row = Vec::new();
|
||||
let mut close = Vec::new();
|
||||
let mut distant = Vec::new();
|
||||
let group_id = primary.diagnostic.group_id;
|
||||
for (ix, entry) in diagnostic_group.into_iter().enumerate() {
|
||||
let mut results = vec![];
|
||||
for entry in diagnostic_group.iter() {
|
||||
if entry.diagnostic.is_primary {
|
||||
continue;
|
||||
}
|
||||
if entry.range.start.row == primary.range.start.row && merge_same_row {
|
||||
same_row.push(entry)
|
||||
} else if entry.range.start.row.abs_diff(primary.range.start.row) < 5 {
|
||||
close.push(entry)
|
||||
} else {
|
||||
distant.push((ix, entry))
|
||||
}
|
||||
}
|
||||
|
||||
let mut markdown = String::new();
|
||||
let mut markdown = Self::markdown(&entry.diagnostic);
|
||||
let diagnostic = &primary.diagnostic;
|
||||
markdown.push_str(&Markdown::escape(&diagnostic.message));
|
||||
for entry in same_row {
|
||||
markdown.push_str("\n- hint: ");
|
||||
markdown.push_str(&Markdown::escape(&entry.diagnostic.message))
|
||||
}
|
||||
if diagnostic.source.is_some() || diagnostic.code.is_some() {
|
||||
markdown.push_str(" (");
|
||||
}
|
||||
|
@ -86,58 +67,58 @@ impl DiagnosticRenderer {
|
|||
markdown.push(')');
|
||||
}
|
||||
|
||||
for (ix, entry) in &distant {
|
||||
for (ix, entry) in diagnostic_group.iter().enumerate() {
|
||||
if entry.range.start.row.abs_diff(primary.range.start.row) >= 5 {
|
||||
markdown.push_str("\n- hint: [");
|
||||
markdown.push_str(&Markdown::escape(&entry.diagnostic.message));
|
||||
markdown.push_str(&format!(
|
||||
"](file://#diagnostic-{buffer_id}-{group_id}-{ix})\n",
|
||||
))
|
||||
}
|
||||
|
||||
let mut results = vec![DiagnosticBlock {
|
||||
initial_range: primary.range,
|
||||
}
|
||||
results.push(DiagnosticBlock {
|
||||
initial_range: primary.range.clone(),
|
||||
severity: primary.diagnostic.severity,
|
||||
diagnostics_editor: diagnostics_editor.clone(),
|
||||
markdown: cx.new(|cx| Markdown::new(markdown.into(), None, None, cx)),
|
||||
}];
|
||||
|
||||
for entry in close {
|
||||
let markdown = if let Some(source) = entry.diagnostic.source.as_ref() {
|
||||
format!("{}: {}", source, entry.diagnostic.message)
|
||||
} else {
|
||||
entry.diagnostic.message
|
||||
};
|
||||
let markdown = Markdown::escape(&markdown).to_string();
|
||||
});
|
||||
} else if entry.range.start.row.abs_diff(primary.range.start.row) < 5 {
|
||||
let markdown = Self::markdown(&entry.diagnostic);
|
||||
|
||||
results.push(DiagnosticBlock {
|
||||
initial_range: entry.range,
|
||||
initial_range: entry.range.clone(),
|
||||
severity: entry.diagnostic.severity,
|
||||
diagnostics_editor: diagnostics_editor.clone(),
|
||||
markdown: cx.new(|cx| Markdown::new(markdown.into(), None, None, cx)),
|
||||
});
|
||||
}
|
||||
|
||||
for (_, entry) in distant {
|
||||
let markdown = if let Some(source) = entry.diagnostic.source.as_ref() {
|
||||
format!("{}: {}", source, entry.diagnostic.message)
|
||||
} else {
|
||||
entry.diagnostic.message
|
||||
};
|
||||
let mut markdown = Markdown::escape(&markdown).to_string();
|
||||
let mut markdown = Self::markdown(&entry.diagnostic);
|
||||
markdown.push_str(&format!(
|
||||
" ([back](file://#diagnostic-{buffer_id}-{group_id}-{primary_ix}))"
|
||||
));
|
||||
|
||||
results.push(DiagnosticBlock {
|
||||
initial_range: entry.range,
|
||||
initial_range: entry.range.clone(),
|
||||
severity: entry.diagnostic.severity,
|
||||
diagnostics_editor: diagnostics_editor.clone(),
|
||||
markdown: cx.new(|cx| Markdown::new(markdown.into(), None, None, cx)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
fn markdown(diagnostic: &Diagnostic) -> String {
|
||||
let mut markdown = String::new();
|
||||
|
||||
if let Some(md) = &diagnostic.markdown {
|
||||
markdown.push_str(md);
|
||||
} else {
|
||||
markdown.push_str(&Markdown::escape(&diagnostic.message));
|
||||
};
|
||||
markdown
|
||||
}
|
||||
}
|
||||
|
||||
impl editor::DiagnosticRenderer for DiagnosticRenderer {
|
||||
|
@ -149,7 +130,7 @@ impl editor::DiagnosticRenderer for DiagnosticRenderer {
|
|||
editor: WeakEntity<Editor>,
|
||||
cx: &mut App,
|
||||
) -> Vec<BlockProperties<Anchor>> {
|
||||
let blocks = Self::diagnostic_blocks_for_group(diagnostic_group, buffer_id, None, true, cx);
|
||||
let blocks = Self::diagnostic_blocks_for_group(diagnostic_group, buffer_id, None, cx);
|
||||
blocks
|
||||
.into_iter()
|
||||
.map(|block| {
|
||||
|
@ -176,8 +157,7 @@ impl editor::DiagnosticRenderer for DiagnosticRenderer {
|
|||
buffer_id: BufferId,
|
||||
cx: &mut App,
|
||||
) -> Option<Entity<Markdown>> {
|
||||
let blocks =
|
||||
Self::diagnostic_blocks_for_group(diagnostic_group, buffer_id, None, false, cx);
|
||||
let blocks = Self::diagnostic_blocks_for_group(diagnostic_group, buffer_id, None, cx);
|
||||
blocks.into_iter().find_map(|block| {
|
||||
if block.initial_range == range {
|
||||
Some(block.markdown)
|
||||
|
@ -211,7 +191,7 @@ impl DiagnosticBlock {
|
|||
let cx = &bcx.app;
|
||||
let status_colors = bcx.app.theme().status();
|
||||
|
||||
let max_width = bcx.em_width * 100.;
|
||||
let max_width = bcx.em_width * 120.;
|
||||
|
||||
let (background_color, border_color) = match self.severity {
|
||||
DiagnosticSeverity::ERROR => (status_colors.error_background, status_colors.error),
|
||||
|
|
|
@ -416,7 +416,6 @@ impl ProjectDiagnosticsEditor {
|
|||
group,
|
||||
buffer_snapshot.remote_id(),
|
||||
Some(this.clone()),
|
||||
true,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
|
|
@ -633,7 +633,7 @@ pub fn hover_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
|||
base_text_style,
|
||||
code_block: StyleRefinement::default().my(rems(1.)).font_buffer(cx),
|
||||
inline_code: TextStyleRefinement {
|
||||
background_color: Some(cx.theme().colors().background),
|
||||
background_color: Some(cx.theme().colors().editor_background.opacity(0.5)),
|
||||
font_family: Some(buffer_font_family),
|
||||
font_fallbacks: buffer_font_fallbacks,
|
||||
..Default::default()
|
||||
|
|
|
@ -213,6 +213,8 @@ pub struct Diagnostic {
|
|||
pub severity: DiagnosticSeverity,
|
||||
/// The human-readable message associated with this diagnostic.
|
||||
pub message: String,
|
||||
/// The human-readable message (in markdown format)
|
||||
pub markdown: Option<String>,
|
||||
/// An id that identifies the group to which this diagnostic belongs.
|
||||
///
|
||||
/// When a language server produces a diagnostic with
|
||||
|
@ -4616,6 +4618,7 @@ impl Default for Diagnostic {
|
|||
code_description: None,
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: Default::default(),
|
||||
markdown: None,
|
||||
group_id: 0,
|
||||
is_primary: false,
|
||||
is_disk_based: false,
|
||||
|
|
|
@ -239,6 +239,10 @@ impl CachedLspAdapter {
|
|||
.process_diagnostics(params, server_id, existing_diagnostics)
|
||||
}
|
||||
|
||||
pub fn diagnostic_message_to_markdown(&self, message: &str) -> Option<String> {
|
||||
self.adapter.diagnostic_message_to_markdown(message)
|
||||
}
|
||||
|
||||
pub async fn process_completions(&self, completion_items: &mut [lsp::CompletionItem]) {
|
||||
self.adapter.process_completions(completion_items).await
|
||||
}
|
||||
|
@ -460,6 +464,10 @@ pub trait LspAdapter: 'static + Send + Sync {
|
|||
/// Post-processes completions provided by the language server.
|
||||
async fn process_completions(&self, _: &mut [lsp::CompletionItem]) {}
|
||||
|
||||
fn diagnostic_message_to_markdown(&self, _message: &str) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn labels_for_completions(
|
||||
self: Arc<Self>,
|
||||
completions: &[lsp::CompletionItem],
|
||||
|
|
|
@ -203,6 +203,7 @@ pub fn serialize_diagnostics<'a>(
|
|||
start: Some(serialize_anchor(&entry.range.start)),
|
||||
end: Some(serialize_anchor(&entry.range.end)),
|
||||
message: entry.diagnostic.message.clone(),
|
||||
markdown: entry.diagnostic.markdown.clone(),
|
||||
severity: match entry.diagnostic.severity {
|
||||
DiagnosticSeverity::ERROR => proto::diagnostic::Severity::Error,
|
||||
DiagnosticSeverity::WARNING => proto::diagnostic::Severity::Warning,
|
||||
|
@ -422,6 +423,7 @@ pub fn deserialize_diagnostics(
|
|||
proto::diagnostic::Severity::None => return None,
|
||||
},
|
||||
message: diagnostic.message,
|
||||
markdown: diagnostic.markdown,
|
||||
group_id: diagnostic.group_id as usize,
|
||||
code: diagnostic.code.map(lsp::NumberOrString::from_string),
|
||||
code_description: diagnostic
|
||||
|
|
|
@ -283,6 +283,12 @@ impl LspAdapter for RustLspAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
fn diagnostic_message_to_markdown(&self, message: &str) -> Option<String> {
|
||||
static REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?m)\n *").expect("Failed to create REGEX"));
|
||||
Some(REGEX.replace_all(message, "\n\n").to_string())
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
completion: &lsp::CompletionItem,
|
||||
|
|
|
@ -8575,6 +8575,8 @@ impl LspStore {
|
|||
let mut sources_by_group_id = HashMap::default();
|
||||
let mut supporting_diagnostics = HashMap::default();
|
||||
|
||||
let adapter = self.language_server_adapter_for_id(language_server_id);
|
||||
|
||||
// Ensure that primary diagnostics are always the most severe
|
||||
params.diagnostics.sort_by_key(|item| item.severity);
|
||||
|
||||
|
@ -8622,6 +8624,9 @@ impl LspStore {
|
|||
.as_ref()
|
||||
.map(|d| d.href.clone()),
|
||||
severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
|
||||
markdown: adapter.as_ref().and_then(|adapter| {
|
||||
adapter.diagnostic_message_to_markdown(&diagnostic.message)
|
||||
}),
|
||||
message: diagnostic.message.trim().to_string(),
|
||||
group_id,
|
||||
is_primary: true,
|
||||
|
@ -8644,6 +8649,9 @@ impl LspStore {
|
|||
.as_ref()
|
||||
.map(|c| c.href.clone()),
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
markdown: adapter.as_ref().and_then(|adapter| {
|
||||
adapter.diagnostic_message_to_markdown(&info.message)
|
||||
}),
|
||||
message: info.message.trim().to_string(),
|
||||
group_id,
|
||||
is_primary: false,
|
||||
|
|
|
@ -271,6 +271,7 @@ message Diagnostic {
|
|||
}
|
||||
optional string data = 12;
|
||||
optional string code_description = 13;
|
||||
optional string markdown = 14;
|
||||
}
|
||||
|
||||
message SearchQuery {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue