Compare commits
2 Commits
settings-u
...
pretty-typ
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53cf8a4e0e | ||
|
|
6a9ec10dac |
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -9134,6 +9134,7 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"indoc",
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
|
||||
@@ -6,7 +6,7 @@ use editor::{
|
||||
hover_popover::diagnostics_markdown_style,
|
||||
};
|
||||
use gpui::{AppContext, Entity, Focusable, WeakEntity};
|
||||
use language::{BufferId, Diagnostic, DiagnosticEntry};
|
||||
use language::{BufferId, Diagnostic, DiagnosticEntry, LanguageRegistry};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use markdown::{Markdown, MarkdownElement};
|
||||
use settings::Settings;
|
||||
@@ -27,6 +27,7 @@ impl DiagnosticRenderer {
|
||||
diagnostic_group: Vec<DiagnosticEntry<Point>>,
|
||||
buffer_id: BufferId,
|
||||
diagnostics_editor: Option<WeakEntity<ProjectDiagnosticsEditor>>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
cx: &mut App,
|
||||
) -> Vec<DiagnosticBlock> {
|
||||
let Some(primary_ix) = diagnostic_group
|
||||
@@ -79,7 +80,9 @@ impl DiagnosticRenderer {
|
||||
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(), Some(languages.clone()), None, cx)
|
||||
}),
|
||||
});
|
||||
} else if entry.range.start.row.abs_diff(primary.range.start.row) < 5 {
|
||||
let markdown = Self::markdown(&entry.diagnostic);
|
||||
@@ -88,7 +91,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(), Some(languages.clone()), None, cx)
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
let mut markdown = Self::markdown(&entry.diagnostic);
|
||||
@@ -100,7 +105,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(), Some(languages.clone()), None, cx)
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -127,9 +134,11 @@ impl editor::DiagnosticRenderer for DiagnosticRenderer {
|
||||
buffer_id: BufferId,
|
||||
snapshot: EditorSnapshot,
|
||||
editor: WeakEntity<Editor>,
|
||||
languages: 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, languages, cx);
|
||||
blocks
|
||||
.into_iter()
|
||||
.map(|block| {
|
||||
@@ -155,9 +164,11 @@ impl editor::DiagnosticRenderer for DiagnosticRenderer {
|
||||
diagnostic_group: Vec<DiagnosticEntry<Point>>,
|
||||
range: Range<Point>,
|
||||
buffer_id: BufferId,
|
||||
languages: 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, languages, cx);
|
||||
blocks.into_iter().find_map(|block| {
|
||||
if block.initial_range == range {
|
||||
Some(block.markdown)
|
||||
|
||||
@@ -508,6 +508,15 @@ impl ProjectDiagnosticsEditor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let languages = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.project
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.languages()
|
||||
.clone();
|
||||
let was_empty = self.multibuffer.read(cx).is_empty();
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let buffer_id = buffer_snapshot.remote_id();
|
||||
@@ -559,6 +568,7 @@ impl ProjectDiagnosticsEditor {
|
||||
group,
|
||||
buffer_snapshot.remote_id(),
|
||||
Some(this.clone()),
|
||||
languages.clone(),
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -111,8 +111,9 @@ use itertools::Itertools;
|
||||
use language::{
|
||||
AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
|
||||
CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
|
||||
EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
|
||||
Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
|
||||
EditPreview, HighlightedText, IndentKind, IndentSize, Language, LanguageRegistry,
|
||||
OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
|
||||
WordsQuery,
|
||||
language_settings::{
|
||||
self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
|
||||
all_language_settings, language_settings,
|
||||
@@ -402,6 +403,7 @@ pub trait DiagnosticRenderer {
|
||||
buffer_id: BufferId,
|
||||
snapshot: EditorSnapshot,
|
||||
editor: WeakEntity<Editor>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
cx: &mut App,
|
||||
) -> Vec<BlockProperties<Anchor>>;
|
||||
|
||||
@@ -410,6 +412,7 @@ pub trait DiagnosticRenderer {
|
||||
diagnostic_group: Vec<DiagnosticEntry<Point>>,
|
||||
range: Range<Point>,
|
||||
buffer_id: BufferId,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
cx: &mut App,
|
||||
) -> Option<Entity<markdown::Markdown>>;
|
||||
|
||||
@@ -16574,13 +16577,20 @@ impl Editor {
|
||||
let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
|
||||
return;
|
||||
};
|
||||
let languages = self.project.as_ref().unwrap().read(cx).languages().clone();
|
||||
|
||||
let diagnostic_group = buffer
|
||||
.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 blocks = renderer.render_group(
|
||||
diagnostic_group,
|
||||
buffer_id,
|
||||
snapshot,
|
||||
cx.weak_entity(),
|
||||
languages,
|
||||
cx,
|
||||
);
|
||||
|
||||
let blocks = self.display_map.update(cx, |display_map, cx| {
|
||||
display_map.insert_blocks(blocks, cx).into_iter().collect()
|
||||
|
||||
@@ -275,6 +275,13 @@ fn show_hover(
|
||||
return None;
|
||||
}
|
||||
}
|
||||
let languages = editor
|
||||
.project
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.languages()
|
||||
.clone();
|
||||
|
||||
let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
|
||||
let all_diagnostics_active = editor.active_diagnostics == ActiveDiagnostic::All;
|
||||
@@ -340,7 +347,7 @@ 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, languages, cx)
|
||||
})
|
||||
.context("no rendered diagnostic")
|
||||
})??;
|
||||
|
||||
@@ -44,6 +44,7 @@ dap.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
indoc.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
|
||||
@@ -262,6 +262,7 @@ impl LspAdapter for RustLspAdapter {
|
||||
_: LanguageServerId,
|
||||
_: Option<&'_ Buffer>,
|
||||
) {
|
||||
// https://zed.dev/cla
|
||||
static REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?m)`([^`]+)\n`$").expect("Failed to create REGEX"));
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@ use futures::future::join_all;
|
||||
use gpui::{App, AppContext, AsyncApp, Task};
|
||||
use http_client::github::{AssetKind, GitHubLspBinaryVersion, build_asset_url};
|
||||
use language::{
|
||||
ContextLocation, ContextProvider, File, LanguageToolchainStore, LspAdapter, LspAdapterDelegate,
|
||||
Buffer, ContextLocation, ContextProvider, File, LanguageToolchainStore, LspAdapter,
|
||||
LspAdapterDelegate,
|
||||
};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerId, LanguageServerName};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::{Fs, lsp_store::language_server_settings};
|
||||
use serde_json::{Value, json};
|
||||
@@ -605,6 +606,7 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// >>> https://zed.dev/cla <<<
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
latest_version: Box<dyn 'static + Send + Any>,
|
||||
@@ -748,6 +750,15 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||
("TSX".into(), "typescriptreact".into()),
|
||||
])
|
||||
}
|
||||
|
||||
fn process_diagnostics(
|
||||
&self,
|
||||
d: &mut lsp::PublishDiagnosticsParams,
|
||||
_: LanguageServerId,
|
||||
_: Option<&'_ Buffer>,
|
||||
) {
|
||||
dbg!("called with ", d);
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_ts_server_binary(
|
||||
|
||||
@@ -280,6 +280,185 @@ impl LspAdapter for VtslsLspAdapter {
|
||||
("TSX".into(), "typescriptreact".into()),
|
||||
])
|
||||
}
|
||||
|
||||
fn diagnostic_message_to_markdown(&self, message: &str) -> Option<String> {
|
||||
use regex::{Captures, Regex};
|
||||
dbg!(&message);
|
||||
|
||||
// Helper functions for formatting
|
||||
let format_type_block = |prefix: &str, content: &str| -> String {
|
||||
if prefix.is_empty() {
|
||||
if content.len() > 50 || content.contains('\n') || content.contains('`') {
|
||||
format!("\n```typescript\ntype a ={}\n```\n", dbg!(content))
|
||||
} else {
|
||||
format!("`{}`", dbg!(content))
|
||||
}
|
||||
} else {
|
||||
if content.len() > 50 || content.contains('\n') || content.contains('`') {
|
||||
format!(
|
||||
"{}\n```typescript\ntype a ={}\n```\n",
|
||||
prefix,
|
||||
dbg!(content)
|
||||
)
|
||||
} else {
|
||||
format!("{} `{}`", prefix, dbg!(content))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let format_typescript_block =
|
||||
|content: &str| -> String { format!("\n\n```typescript\n{}\n```\n", dbg!(content)) };
|
||||
|
||||
let format_simple_type_block = |content: &str| -> String { format!("`{}`", dbg!(content)) };
|
||||
|
||||
let unstyle_code_block = |content: &str| -> String { format!("`{}`", dbg!(content)) };
|
||||
|
||||
let mut result = message.to_string();
|
||||
|
||||
// Format 'key' with "value"
|
||||
let re = Regex::new(r#"(\w+)(\s+)'(.+?)'(\s+)with(\s+)"(.+?)""#).unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
format!(
|
||||
"{}{}`{}`{} with `\"{}\"`",
|
||||
&caps[1], &caps[2], &caps[3], &caps[4], &caps[6]
|
||||
)
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format "key"
|
||||
let re = Regex::new(r#"(\s)'"(.*?)"'(\s|:|.|$)"#).unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
format!("{}`\"{}\"`{}", &caps[1], &caps[2], &caps[3])
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format declare module snippet
|
||||
let re = Regex::new(r#"['"](declare module )['"](.*)['""];['"']"#).unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
format_typescript_block(&format!("{} \"{}\"", &caps[1], &caps[2]))
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format missing props error
|
||||
let re = Regex::new(r#"(is missing the following properties from type\s?)'(.*)': ([^:]+)"#)
|
||||
.unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
let props: Vec<&str> = caps[3].split(", ").filter(|s| !s.is_empty()).collect();
|
||||
let props_html = props
|
||||
.iter()
|
||||
.map(|prop| format!("<li>{}</li>", prop))
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
format!("{}`{}`: <ul>{}</ul>", &caps[1], &caps[2], props_html)
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format type pairs
|
||||
let re = Regex::new(r#"(?i)(types) ['"](.*?)['"] and ['"](.*?)['"][.]?"#).unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
format!("{} `{}` and `{}`", &caps[1], &caps[2], &caps[3])
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format type annotation options
|
||||
let re = Regex::new(r#"(?i)type annotation must be ['"](.*?)['"] or ['"](.*?)['"][.]?"#)
|
||||
.unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
format!("type annotation must be `{}` or `{}`", &caps[1], &caps[2])
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format overload
|
||||
let re = Regex::new(r#"(?i)(Overload \d of \d), ['"](.*?)['"], "#).unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
format!("{}, `{}`, ", &caps[1], &caps[2])
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format simple strings
|
||||
let re = Regex::new(r#"^['"]"[^"]*"['"]$"#).unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| format_typescript_block(&caps[0]))
|
||||
.to_string();
|
||||
|
||||
// Replace module 'x' by module "x" for ts error #2307
|
||||
let re = Regex::new(r#"(?i)(module )'([^"]*?)'"#).unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
format!("{}\"{}\"", &caps[1], &caps[2])
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format string types
|
||||
let re = Regex::new(r#"(?i)(module|file|file name|imported via) ['""](.*?)['""]"#).unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
format_type_block(&caps[1], &format!("\"{}\"", &caps[2]))
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format types
|
||||
dbg!(&result);
|
||||
let re = Regex::new(r#"(?i)(type|type alias|interface|module|file|file name|class|method's|subtype of constraint) ['"](.*?)['"]"#).unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
dbg!(&caps);
|
||||
format_type_block(&caps[1], &caps[2])
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format reversed types
|
||||
let re = Regex::new(r#"(?i)(.*)['"]([^>]*)['"] (type|interface|return type|file|module|is (not )?assignable)"#).unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
format!("{}`{}` {}", &caps[1], &caps[2], &caps[3])
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format simple types that didn't captured before
|
||||
let re = Regex::new(
|
||||
r#"['"]((void|null|undefined|any|boolean|string|number|bigint|symbol)(\[\])?)['"']"#,
|
||||
)
|
||||
.unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
format_simple_type_block(&caps[1])
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format some typescript keywords
|
||||
let re = Regex::new(r#"['"](import|export|require|in|continue|break|let|false|true|const|new|throw|await|for await|[0-9]+)( ?.*?)['"]"#).unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
format_typescript_block(&format!("{}{}", &caps[1], &caps[2]))
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format return values
|
||||
let re = Regex::new(r#"(?i)(return|operator) ['"](.*?)['"']"#).unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
format!("{} {}", &caps[1], format_typescript_block(&caps[2]))
|
||||
})
|
||||
.to_string();
|
||||
|
||||
// Format regular code blocks
|
||||
let re = Regex::new(r#"(\W|^)'([^'"]*?)'(\W|$)"#).unwrap();
|
||||
result = re
|
||||
.replace_all(&result, |caps: &Captures| {
|
||||
format!("{}{}{}", &caps[1], unstyle_code_block(&caps[2]), &caps[3])
|
||||
})
|
||||
.to_string();
|
||||
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_ts_server_binary(
|
||||
@@ -301,3 +480,25 @@ async fn get_cached_ts_server_binary(
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn test_diagnostic_message_to_markdown() {
|
||||
let message = "Property 'user' is missing in type '{ person: { username: string; email: string; }; }' but required in type '{ user: { name: string; email: `${string}@${string}.${string}`; age: number; }; }'.";
|
||||
let expected = indoc! { "
|
||||
Property `user` is missing in type `{ person: { username: string; email: string; }; }` but required in type
|
||||
|
||||
```typescript
|
||||
{ user: { name: string; email: `${string}@${string}.${string}`; age: number; }; }
|
||||
```
|
||||
"};
|
||||
let result = VtslsLspAdapter::new(NodeRuntime::unavailable())
|
||||
.diagnostic_message_to_markdown(message)
|
||||
.unwrap();
|
||||
pretty_assertions::assert_eq!(result, expected.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1534,12 +1534,26 @@ impl MarkdownElementBuilder {
|
||||
rendered_index: self.pending_line.text.len(),
|
||||
source_index: source_range.start,
|
||||
});
|
||||
self.pending_line.text.push_str(text);
|
||||
if text.starts_with("type a =") {
|
||||
self.pending_line.text.push_str(&text["type a =".len()..]);
|
||||
} else {
|
||||
self.pending_line.text.push_str(text);
|
||||
}
|
||||
self.current_source_index = source_range.end;
|
||||
|
||||
if let Some(Some(language)) = self.code_block_stack.last() {
|
||||
dbg!(&language);
|
||||
let mut offset = 0;
|
||||
for (range, highlight_id) in language.highlight_text(&Rope::from(text), 0..text.len()) {
|
||||
for (mut range, highlight_id) in
|
||||
language.highlight_text(&Rope::from(text), 0..text.len())
|
||||
{
|
||||
if text.starts_with("type a =") {
|
||||
if range.start < "type a =".len() || range.end < "type a =".len() {
|
||||
continue;
|
||||
}
|
||||
range.start -= "type a =".len();
|
||||
range.end -= "type a =".len();
|
||||
};
|
||||
if range.start > offset {
|
||||
self.pending_line
|
||||
.runs
|
||||
|
||||
@@ -54,6 +54,7 @@ impl sqlez::bindable::Bind for SerializedAxis {
|
||||
}
|
||||
}
|
||||
|
||||
// > https://zed.dev/cla
|
||||
impl sqlez::bindable::Column for SerializedAxis {
|
||||
fn column(
|
||||
statement: &mut sqlez::statement::Statement,
|
||||
|
||||
Reference in New Issue
Block a user