Implement pretty TypeScript errors (#42494)
Closes #7844 This change uses tree-sitter highlights as a method of showing typescript errors prettily, keeping regex as simple as possible: <img width="832" height="446" alt="Screenshot 2025-11-11 at 3 40 24 PM" src="https://github.com/user-attachments/assets/0b3b6cf1-4d4d-4398-b89b-ef5ec0df87ec" /> It covers three main areas: 1. Diagnostics Diagnostics are now rendered with language-aware typescript, by providing the project's language registry. 2. Vtsls The LSP provider for typescript now implements the `diagnostic_message_to_markdown` function in the `LspAdapter` trait, so as to provide Diagnostics with \`\`\`typescript...\`\`\`-style code blocks for any selection of typescript longer than one word. In the single-word case, it simply wraps with \`\` 3. Typescript's `highlights.scm` `vtsls` doesn't provide strictly valid typescript in much of its messaging. Rather, it returns a message with snippets of typescript values which are invalid. Tree-sitter was not properly highlighting these snippets because it was expecting key-value formats. For instance: ``` type foo = { foo: string; bar: string; baz: number[] } ``` is valid, whereas simply ``` { foo: string; bar: string; baz: number[] } ``` is not. Therefore, highlights.scm needed to be adjusted in order to pattern-match on literal values that might be returned from the vtsls diagnostics messages. This was done by a) identifying arrow functions on their own, and b) augmenting the `statment_block` pattern matching in order to match on values which were clearly object literals. This approach may not be exhaustive - I'm happy to work on any additional cases we might identify from `vtsls` here - but hopefully demonstrates an extensible approach to making these messages look nice, without taking on the technical burden of extensive regex. Release Notes: - Show pretty TypeScript errors with language-aware Markdown.
This commit is contained in:
@@ -370,11 +370,16 @@ impl BufferDiagnosticsEditor {
|
||||
continue;
|
||||
}
|
||||
|
||||
let languages = buffer_diagnostics_editor
|
||||
.read_with(cx, |b, cx| b.project.read(cx).languages().clone())
|
||||
.ok();
|
||||
|
||||
let diagnostic_blocks = cx.update(|_window, cx| {
|
||||
DiagnosticRenderer::diagnostic_blocks_for_group(
|
||||
group,
|
||||
buffer_snapshot.remote_id(),
|
||||
Some(Arc::new(buffer_diagnostics_editor.clone())),
|
||||
languages,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -6,7 +6,7 @@ use editor::{
|
||||
hover_popover::diagnostics_markdown_style,
|
||||
};
|
||||
use gpui::{AppContext, Entity, Focusable, WeakEntity};
|
||||
use language::{BufferId, Diagnostic, DiagnosticEntryRef};
|
||||
use language::{BufferId, Diagnostic, DiagnosticEntryRef, LanguageRegistry};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use markdown::{Markdown, MarkdownElement};
|
||||
use settings::Settings;
|
||||
@@ -27,6 +27,7 @@ impl DiagnosticRenderer {
|
||||
diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
|
||||
buffer_id: BufferId,
|
||||
diagnostics_editor: Option<Arc<dyn DiagnosticsToolbarEditor>>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
cx: &mut App,
|
||||
) -> Vec<DiagnosticBlock> {
|
||||
let Some(primary_ix) = diagnostic_group
|
||||
@@ -75,11 +76,14 @@ impl DiagnosticRenderer {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
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)),
|
||||
markdown: cx.new(|cx| {
|
||||
Markdown::new(markdown.into(), language_registry.clone(), None, cx)
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
if entry.range.start.row.abs_diff(primary.range.start.row) >= 5 {
|
||||
@@ -91,7 +95,9 @@ impl DiagnosticRenderer {
|
||||
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)),
|
||||
markdown: cx.new(|cx| {
|
||||
Markdown::new(markdown.into(), language_registry.clone(), None, cx)
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -118,9 +124,16 @@ impl editor::DiagnosticRenderer for DiagnosticRenderer {
|
||||
buffer_id: BufferId,
|
||||
snapshot: EditorSnapshot,
|
||||
editor: WeakEntity<Editor>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
cx: &mut App,
|
||||
) -> Vec<BlockProperties<Anchor>> {
|
||||
let blocks = Self::diagnostic_blocks_for_group(diagnostic_group, buffer_id, None, cx);
|
||||
let blocks = Self::diagnostic_blocks_for_group(
|
||||
diagnostic_group,
|
||||
buffer_id,
|
||||
None,
|
||||
language_registry,
|
||||
cx,
|
||||
);
|
||||
|
||||
blocks
|
||||
.into_iter()
|
||||
@@ -146,9 +159,16 @@ impl editor::DiagnosticRenderer for DiagnosticRenderer {
|
||||
diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
|
||||
range: Range<Point>,
|
||||
buffer_id: BufferId,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
cx: &mut App,
|
||||
) -> Option<Entity<Markdown>> {
|
||||
let blocks = Self::diagnostic_blocks_for_group(diagnostic_group, buffer_id, None, cx);
|
||||
let blocks = Self::diagnostic_blocks_for_group(
|
||||
diagnostic_group,
|
||||
buffer_id,
|
||||
None,
|
||||
language_registry,
|
||||
cx,
|
||||
);
|
||||
blocks
|
||||
.into_iter()
|
||||
.find_map(|block| (block.initial_range == range).then(|| block.markdown))
|
||||
@@ -206,6 +226,11 @@ impl DiagnosticBlock {
|
||||
self.markdown.clone(),
|
||||
diagnostics_markdown_style(bcx.window, cx),
|
||||
)
|
||||
.code_block_renderer(markdown::CodeBlockRenderer::Default {
|
||||
copy_button: false,
|
||||
copy_button_on_hover: false,
|
||||
border: false,
|
||||
})
|
||||
.on_url_click({
|
||||
move |link, window, cx| {
|
||||
editor
|
||||
|
||||
@@ -73,7 +73,7 @@ pub fn init(cx: &mut App) {
|
||||
}
|
||||
|
||||
pub(crate) struct ProjectDiagnosticsEditor {
|
||||
project: Entity<Project>,
|
||||
pub project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
editor: Entity<Editor>,
|
||||
@@ -545,11 +545,15 @@ impl ProjectDiagnosticsEditor {
|
||||
if group_severity.is_none_or(|s| s > max_severity) {
|
||||
continue;
|
||||
}
|
||||
let languages = this
|
||||
.read_with(cx, |t, cx| t.project.read(cx).languages().clone())
|
||||
.ok();
|
||||
let more = cx.update(|_, cx| {
|
||||
crate::diagnostic_renderer::DiagnosticRenderer::diagnostic_blocks_for_group(
|
||||
group,
|
||||
buffer_snapshot.remote_id(),
|
||||
Some(diagnostics_toolbar_editor.clone()),
|
||||
languages,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
||||
Reference in New Issue
Block a user