Detailed changes
@@ -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,
)
})?;
@@ -117,8 +117,9 @@ use language::{
AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
- IndentSize, Language, OffsetRangeExt, OutlineItem, Point, Runnable, RunnableRange, Selection,
- SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
+ IndentSize, Language, LanguageRegistry, OffsetRangeExt, OutlineItem, Point, Runnable,
+ RunnableRange, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
+ WordsQuery,
language_settings::{
self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
language_settings,
@@ -371,6 +372,7 @@ pub trait DiagnosticRenderer {
buffer_id: BufferId,
snapshot: EditorSnapshot,
editor: WeakEntity<Editor>,
+ language_registry: Option<Arc<LanguageRegistry>>,
cx: &mut App,
) -> Vec<BlockProperties<Anchor>>;
@@ -379,6 +381,7 @@ pub trait DiagnosticRenderer {
diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
range: Range<Point>,
buffer_id: BufferId,
+ language_registry: Option<Arc<LanguageRegistry>>,
cx: &mut App,
) -> Option<Entity<markdown::Markdown>>;
@@ -17947,8 +17950,18 @@ impl Editor {
.diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
.collect::<Vec<_>>();
- let blocks =
- renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
+ let language_registry = self
+ .project()
+ .map(|project| project.read(cx).languages().clone());
+
+ let blocks = renderer.render_group(
+ diagnostic_group,
+ buffer_id,
+ snapshot,
+ cx.weak_entity(),
+ language_registry,
+ cx,
+ );
let blocks = self.display_map.update(cx, |display_map, cx| {
display_map.insert_blocks(blocks, cx).into_iter().collect()
@@ -341,7 +341,13 @@ fn show_hover(
renderer
.as_ref()
.and_then(|renderer| {
- renderer.render_hover(group, point_range, buffer_id, cx)
+ renderer.render_hover(
+ group,
+ point_range,
+ buffer_id,
+ language_registry.clone(),
+ cx,
+ )
})
.context("no rendered diagnostic")
})??;
@@ -986,6 +992,11 @@ impl DiagnosticPopover {
self.markdown.clone(),
diagnostics_markdown_style(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| {
if let Some(renderer) = GlobalDiagnosticRenderer::global(cx)
@@ -9,6 +9,36 @@
(type_identifier) @type
(predefined_type) @type.builtin
+;; Highlights object literals by hijacking the statement_block pattern, but only if
+;; the statement block follows an object literal pattern
+((statement_block
+ (labeled_statement
+ ;; highlight the label like a property name
+ label: (statement_identifier) @property.name
+ body: [
+ ;; match a terminating expression statement
+ (expression_statement
+ ;; single identifier - treat as a type name
+ [(identifier) @type.name
+ ;; object - treat as a property - type pair
+ (object
+ (pair
+ key: (_) @property.name
+ value: (_) @type.name))
+ ;; subscript_expression - treat as an array declaration
+ (subscript_expression
+ object: (_) @type.name
+ index: (_)
+ )
+ ;; templated string - treat each identifier contained as a type name
+ (template_string
+ (template_substitution
+ (identifier) @type.name))
+ ])
+ ;; match a nested statement block
+ (statement_block) @nested
+ ])))
+
(import_specifier
"type"
name: (identifier) @type
@@ -79,6 +109,8 @@
left: (identifier) @function
right: [(function_expression) (arrow_function)])
+(arrow_function) @function
+
; Literals
(this) @variable.special
@@ -6,11 +6,12 @@ use language::{LanguageName, LspAdapter, LspAdapterDelegate, LspInstaller, Toolc
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
use project::{Fs, lsp_store::language_server_settings};
+use regex::Regex;
use serde_json::Value;
use std::{
ffi::OsString,
path::{Path, PathBuf},
- sync::Arc,
+ sync::{Arc, LazyLock},
};
use util::{ResultExt, maybe, merge_json_value_into};
@@ -56,6 +57,20 @@ impl VtslsLspAdapter {
None
}
}
+
+ pub fn enhance_diagnostic_message(message: &str) -> Option<String> {
+ static SINGLE_WORD_REGEX: LazyLock<Regex> =
+ LazyLock::new(|| Regex::new(r"'([^\s']*)'").expect("Failed to create REGEX"));
+
+ static MULTI_WORD_REGEX: LazyLock<Regex> =
+ LazyLock::new(|| Regex::new(r"'([^']+\s+[^']*)'").expect("Failed to create REGEX"));
+
+ let first = SINGLE_WORD_REGEX.replace_all(message, "`$1`").to_string();
+ let second = MULTI_WORD_REGEX
+ .replace_all(&first, "\n```typescript\n$1\n```\n")
+ .to_string();
+ Some(second)
+ }
}
pub struct TypeScriptVersions {
@@ -274,6 +289,10 @@ impl LspAdapter for VtslsLspAdapter {
Ok(default_workspace_configuration)
}
+ fn diagnostic_message_to_markdown(&self, message: &str) -> Option<String> {
+ VtslsLspAdapter::enhance_diagnostic_message(message)
+ }
+
fn language_ids(&self) -> HashMap<LanguageName, String> {
HashMap::from_iter([
(LanguageName::new("TypeScript"), "typescript".into()),
@@ -302,3 +321,41 @@ async fn get_cached_ts_server_binary(
.await
.log_err()
}
+
+#[cfg(test)]
+mod tests {
+ use crate::vtsls::VtslsLspAdapter;
+
+ #[test]
+ fn test_diagnostic_message_to_markdown() {
+ // Leaves simple messages unchanged
+ let message = "The expected type comes from the return type of this signature.";
+
+ let expected = "The expected type comes from the return type of this signature.";
+
+ assert_eq!(
+ VtslsLspAdapter::enhance_diagnostic_message(message).expect("Should be some"),
+ expected
+ );
+
+ // Parses both multi-word and single-word correctly
+ let message = "Property 'baz' is missing in type '{ foo: string; bar: string; }' but required in type 'User'.";
+
+ let expected = "Property `baz` is missing in type \n```typescript\n{ foo: string; bar: string; }\n```\n but required in type `User`.";
+
+ assert_eq!(
+ VtslsLspAdapter::enhance_diagnostic_message(message).expect("Should be some"),
+ expected
+ );
+
+ // Parses multi-and-single word in any order, and ignores existing newlines
+ let message = "Type '() => { foo: string; bar: string; }' is not assignable to type 'GetUserFunction'.\n Property 'baz' is missing in type '{ foo: string; bar: string; }' but required in type 'User'.";
+
+ let expected = "Type \n```typescript\n() => { foo: string; bar: string; }\n```\n is not assignable to type `GetUserFunction`.\n Property `baz` is missing in type \n```typescript\n{ foo: string; bar: string; }\n```\n but required in type `User`.";
+
+ assert_eq!(
+ VtslsLspAdapter::enhance_diagnostic_message(message).expect("Should be some"),
+ expected
+ );
+ }
+}