Fix clicking on file links in editor (#25117)

Closes #18641
Contributes: #13194

Release Notes:

- Open LSP documentation file links in Zed not the system opener
- Render completion documentation markdown consistently with
documentation markdown
This commit is contained in:
Conrad Irwin 2025-02-18 22:54:35 -07:00 committed by GitHub
parent ebbc6a9752
commit 1678e3cbf1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 353 additions and 352 deletions

View file

@ -26,7 +26,8 @@ use futures::{
};
use globset::{Glob, GlobBuilder, GlobMatcher, GlobSet, GlobSetBuilder};
use gpui::{
App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, PromptLevel, Task, WeakEntity,
App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, PromptLevel, SharedString, Task,
WeakEntity,
};
use http_client::HttpClient;
use itertools::Itertools as _;
@ -34,13 +35,12 @@ use language::{
language_settings::{
language_settings, FormatOnSave, Formatter, LanguageSettings, SelectedFormatter,
},
markdown, point_to_lsp, prepare_completion_documentation,
point_to_lsp,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel,
CompletionDocumentation, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, File as _, Language,
LanguageRegistry, LanguageServerBinaryStatus, LanguageToolchainStore, LocalFile, LspAdapter,
LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
Unclipped,
Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, File as _, Language, LanguageRegistry,
LanguageServerBinaryStatus, LanguageToolchainStore, LocalFile, LspAdapter, LspAdapterDelegate,
Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
};
use lsp::{
notification::DidRenameFiles, CodeActionKind, CompletionContext, DiagnosticSeverity,
@ -4204,14 +4204,8 @@ impl LspStore {
cx.foreground_executor().spawn(async move {
let completions = task.await?;
let mut result = Vec::new();
populate_labels_for_completions(
completions,
&language_registry,
language,
lsp_adapter,
&mut result,
)
.await;
populate_labels_for_completions(completions, language, lsp_adapter, &mut result)
.await;
Ok(result)
})
} else if let Some(local) = self.as_local() {
@ -4260,7 +4254,6 @@ impl LspStore {
if let Ok(new_completions) = task.await {
populate_labels_for_completions(
new_completions,
&language_registry,
language.clone(),
lsp_adapter,
&mut completions,
@ -4284,7 +4277,6 @@ impl LspStore {
cx: &mut Context<Self>,
) -> Task<Result<bool>> {
let client = self.upstream_client();
let language_registry = self.languages.clone();
let buffer_id = buffer.read(cx).remote_id();
let buffer_snapshot = buffer.read(cx).snapshot();
@ -4302,7 +4294,6 @@ impl LspStore {
completions.clone(),
completion_index,
client.clone(),
language_registry.clone(),
)
.await
.log_err()
@ -4343,7 +4334,6 @@ impl LspStore {
&buffer_snapshot,
completions.clone(),
completion_index,
language_registry.clone(),
)
.await
.log_err();
@ -4419,22 +4409,14 @@ impl LspStore {
snapshot: &BufferSnapshot,
completions: Rc<RefCell<Box<[Completion]>>>,
completion_index: usize,
language_registry: Arc<LanguageRegistry>,
) -> Result<()> {
let completion_item = completions.borrow()[completion_index]
.lsp_completion
.clone();
if let Some(lsp_documentation) = completion_item.documentation.as_ref() {
let documentation = language::prepare_completion_documentation(
lsp_documentation,
&language_registry,
snapshot.language().cloned(),
)
.await;
if let Some(lsp_documentation) = completion_item.documentation.clone() {
let mut completions = completions.borrow_mut();
let completion = &mut completions[completion_index];
completion.documentation = Some(documentation);
completion.documentation = Some(lsp_documentation.into());
} else {
let mut completions = completions.borrow_mut();
let completion = &mut completions[completion_index];
@ -4487,7 +4469,6 @@ impl LspStore {
completions: Rc<RefCell<Box<[Completion]>>>,
completion_index: usize,
client: AnyProtoClient,
language_registry: Arc<LanguageRegistry>,
) -> Result<()> {
let lsp_completion = {
let completion = &completions.borrow()[completion_index];
@ -4514,14 +4495,11 @@ impl LspStore {
let documentation = if response.documentation.is_empty() {
CompletionDocumentation::Undocumented
} else if response.documentation_is_markdown {
CompletionDocumentation::MultiLineMarkdown(
markdown::parse_markdown(&response.documentation, Some(&language_registry), None)
.await,
)
CompletionDocumentation::MultiLineMarkdown(response.documentation.into())
} else if response.documentation.lines().count() <= 1 {
CompletionDocumentation::SingleLine(response.documentation)
CompletionDocumentation::SingleLine(response.documentation.into())
} else {
CompletionDocumentation::MultiLinePlainText(response.documentation)
CompletionDocumentation::MultiLinePlainText(response.documentation.into())
};
let mut completions = completions.borrow_mut();
@ -8060,7 +8038,6 @@ fn remove_empty_hover_blocks(mut hover: Hover) -> Option<Hover> {
async fn populate_labels_for_completions(
mut new_completions: Vec<CoreCompletion>,
language_registry: &Arc<LanguageRegistry>,
language: Option<Arc<Language>>,
lsp_adapter: Option<Arc<CachedLspAdapter>>,
completions: &mut Vec<Completion>,
@ -8085,8 +8062,8 @@ async fn populate_labels_for_completions(
.zip(lsp_completions)
.zip(labels.into_iter().chain(iter::repeat(None)))
{
let documentation = if let Some(docs) = &lsp_completion.documentation {
Some(prepare_completion_documentation(docs, language_registry, language.clone()).await)
let documentation = if let Some(docs) = lsp_completion.documentation.clone() {
Some(docs.into())
} else {
None
};
@ -8477,6 +8454,46 @@ impl DiagnosticSummary {
}
}
#[derive(Clone, Debug)]
pub enum CompletionDocumentation {
/// There is no documentation for this completion.
Undocumented,
/// A single line of documentation.
SingleLine(SharedString),
/// Multiple lines of plain text documentation.
MultiLinePlainText(SharedString),
/// Markdown documentation.
MultiLineMarkdown(SharedString),
}
impl From<lsp::Documentation> for CompletionDocumentation {
fn from(docs: lsp::Documentation) -> Self {
match docs {
lsp::Documentation::String(text) => {
if text.lines().count() <= 1 {
CompletionDocumentation::SingleLine(text.into())
} else {
CompletionDocumentation::MultiLinePlainText(text.into())
}
}
lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind {
lsp::MarkupKind::PlainText => {
if value.lines().count() <= 1 {
CompletionDocumentation::SingleLine(value.into())
} else {
CompletionDocumentation::MultiLinePlainText(value.into())
}
}
lsp::MarkupKind::Markdown => {
CompletionDocumentation::MultiLineMarkdown(value.into())
}
},
}
}
}
fn glob_literal_prefix(glob: &Path) -> PathBuf {
glob.components()
.take_while(|component| match component {