Refactor Completions to allow non-LSP ones better (#26300)
A preparation for https://github.com/zed-industries/zed/issues/4957 that pushes all LSP-related data out from the basic completion item, so that it's possible to create completion items without any trace of LSP clearly. Release Notes: - N/A
This commit is contained in:
parent
ec5e7a2653
commit
103ad635d9
10 changed files with 326 additions and 190 deletions
|
@ -38,7 +38,7 @@ use language_model::{
|
|||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::{ActionVariant, CodeAction, ProjectTransaction};
|
||||
use project::{CodeAction, LspAction, ProjectTransaction};
|
||||
use prompt_store::PromptBuilder;
|
||||
use rope::Rope;
|
||||
use settings::{update_settings_file, Settings, SettingsStore};
|
||||
|
@ -3569,7 +3569,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||
Task::ready(Ok(vec![CodeAction {
|
||||
server_id: language::LanguageServerId(0),
|
||||
range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
|
||||
lsp_action: ActionVariant::Action(Box::new(lsp::CodeAction {
|
||||
lsp_action: LspAction::Action(Box::new(lsp::CodeAction {
|
||||
title: "Fix with Assistant".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
|
|
|
@ -27,7 +27,7 @@ use language::{Buffer, Point, Selection, TransactionId};
|
|||
use language_model::{report_assistant_event, LanguageModelRegistry};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::ActionVariant;
|
||||
use project::LspAction;
|
||||
use project::{CodeAction, ProjectTransaction};
|
||||
use prompt_store::PromptBuilder;
|
||||
use settings::{Settings, SettingsStore};
|
||||
|
@ -1728,7 +1728,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||
Task::ready(Ok(vec![CodeAction {
|
||||
server_id: language::LanguageServerId(0),
|
||||
range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
|
||||
lsp_action: ActionVariant::Action(Box::new(lsp::CodeAction {
|
||||
lsp_action: LspAction::Action(Box::new(lsp::CodeAction {
|
||||
title: "Fix with Assistant".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
|
|
|
@ -5,9 +5,9 @@ use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWor
|
|||
use editor::{CompletionProvider, Editor};
|
||||
use fuzzy::{match_strings, StringMatchCandidate};
|
||||
use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity, Window};
|
||||
use language::{Anchor, Buffer, LanguageServerId, ToPoint};
|
||||
use language::{Anchor, Buffer, ToPoint};
|
||||
use parking_lot::Mutex;
|
||||
use project::{lsp_store::CompletionDocumentation, CompletionIntent};
|
||||
use project::{lsp_store::CompletionDocumentation, CompletionIntent, CompletionSource};
|
||||
use rope::Point;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
|
@ -125,10 +125,8 @@ impl SlashCommandCompletionProvider {
|
|||
)),
|
||||
new_text,
|
||||
label: command.label(cx),
|
||||
server_id: LanguageServerId(0),
|
||||
lsp_completion: Default::default(),
|
||||
confirm,
|
||||
resolved: true,
|
||||
source: CompletionSource::Custom,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
|
@ -225,10 +223,8 @@ impl SlashCommandCompletionProvider {
|
|||
label: new_argument.label,
|
||||
new_text,
|
||||
documentation: None,
|
||||
server_id: LanguageServerId(0),
|
||||
lsp_completion: Default::default(),
|
||||
confirm,
|
||||
resolved: true,
|
||||
source: CompletionSource::Custom,
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
|
|
|
@ -10,9 +10,9 @@ use gpui::{
|
|||
};
|
||||
use language::{
|
||||
language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry,
|
||||
LanguageServerId, ToOffset,
|
||||
ToOffset,
|
||||
};
|
||||
use project::{search::SearchQuery, Completion};
|
||||
use project::{search::SearchQuery, Completion, CompletionSource};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
|
@ -309,11 +309,9 @@ impl MessageEditor {
|
|||
old_range: range.clone(),
|
||||
new_text,
|
||||
label,
|
||||
documentation: None,
|
||||
server_id: LanguageServerId(0), // TODO: Make this optional or something?
|
||||
lsp_completion: Default::default(), // TODO: Make this optional or something?
|
||||
confirm: None,
|
||||
resolved: true,
|
||||
documentation: None,
|
||||
source: CompletionSource::Custom,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
|
|
@ -6,11 +6,11 @@ use gpui::{
|
|||
};
|
||||
use language::Buffer;
|
||||
use language::CodeLabel;
|
||||
use lsp::LanguageServerId;
|
||||
use markdown::Markdown;
|
||||
use multi_buffer::{Anchor, ExcerptId};
|
||||
use ordered_float::OrderedFloat;
|
||||
use project::lsp_store::CompletionDocumentation;
|
||||
use project::CompletionSource;
|
||||
use project::{CodeAction, Completion, TaskSourceKind};
|
||||
|
||||
use std::{
|
||||
|
@ -233,11 +233,9 @@ impl CompletionsMenu {
|
|||
runs: Default::default(),
|
||||
filter_range: Default::default(),
|
||||
},
|
||||
server_id: LanguageServerId(usize::MAX),
|
||||
documentation: None,
|
||||
lsp_completion: Default::default(),
|
||||
confirm: None,
|
||||
resolved: true,
|
||||
source: CompletionSource::Custom,
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
@ -500,7 +498,12 @@ impl CompletionsMenu {
|
|||
// Ignore font weight for syntax highlighting, as we'll use it
|
||||
// for fuzzy matches.
|
||||
highlight.font_weight = None;
|
||||
if completion.lsp_completion.deprecated.unwrap_or(false) {
|
||||
if completion
|
||||
.source
|
||||
.lsp_completion()
|
||||
.and_then(|lsp_completion| lsp_completion.deprecated)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
highlight.strikethrough = Some(StrikethroughStyle {
|
||||
thickness: 1.0.into(),
|
||||
..Default::default()
|
||||
|
@ -708,7 +711,10 @@ impl CompletionsMenu {
|
|||
|
||||
let completion = &completions[mat.candidate_id];
|
||||
let sort_key = completion.sort_key();
|
||||
let sort_text = completion.lsp_completion.sort_text.as_deref();
|
||||
let sort_text = completion
|
||||
.source
|
||||
.lsp_completion()
|
||||
.and_then(|lsp_completion| lsp_completion.sort_text.as_deref());
|
||||
let score = Reverse(OrderedFloat(mat.score));
|
||||
|
||||
if mat.score >= 0.2 {
|
||||
|
|
|
@ -138,8 +138,9 @@ use multi_buffer::{
|
|||
use project::{
|
||||
lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
|
||||
project_settings::{GitGutterSetting, ProjectSettings},
|
||||
CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink,
|
||||
PrepareRenameResponse, Project, ProjectItem, ProjectTransaction, TaskSourceKind,
|
||||
CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
|
||||
Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
|
||||
TaskSourceKind,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use rpc::{proto::*, ErrorExt};
|
||||
|
@ -16897,38 +16898,40 @@ fn snippet_completions(
|
|||
Some(Completion {
|
||||
old_range: range,
|
||||
new_text: snippet.body.clone(),
|
||||
resolved: false,
|
||||
source: CompletionSource::Lsp {
|
||||
server_id: LanguageServerId(usize::MAX),
|
||||
resolved: true,
|
||||
lsp_completion: Box::new(lsp::CompletionItem {
|
||||
label: snippet.prefix.first().unwrap().clone(),
|
||||
kind: Some(CompletionItemKind::SNIPPET),
|
||||
label_details: snippet.description.as_ref().map(|description| {
|
||||
lsp::CompletionItemLabelDetails {
|
||||
detail: Some(description.clone()),
|
||||
description: None,
|
||||
}
|
||||
}),
|
||||
insert_text_format: Some(InsertTextFormat::SNIPPET),
|
||||
text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
|
||||
lsp::InsertReplaceEdit {
|
||||
new_text: snippet.body.clone(),
|
||||
insert: lsp_range,
|
||||
replace: lsp_range,
|
||||
},
|
||||
)),
|
||||
filter_text: Some(snippet.body.clone()),
|
||||
sort_text: Some(char::MAX.to_string()),
|
||||
..lsp::CompletionItem::default()
|
||||
}),
|
||||
},
|
||||
label: CodeLabel {
|
||||
text: matching_prefix.clone(),
|
||||
runs: vec![],
|
||||
runs: Vec::new(),
|
||||
filter_range: 0..matching_prefix.len(),
|
||||
},
|
||||
server_id: LanguageServerId(usize::MAX),
|
||||
documentation: snippet
|
||||
.description
|
||||
.clone()
|
||||
.map(|description| CompletionDocumentation::SingleLine(description.into())),
|
||||
lsp_completion: lsp::CompletionItem {
|
||||
label: snippet.prefix.first().unwrap().clone(),
|
||||
kind: Some(CompletionItemKind::SNIPPET),
|
||||
label_details: snippet.description.as_ref().map(|description| {
|
||||
lsp::CompletionItemLabelDetails {
|
||||
detail: Some(description.clone()),
|
||||
description: None,
|
||||
}
|
||||
}),
|
||||
insert_text_format: Some(InsertTextFormat::SNIPPET),
|
||||
text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
|
||||
lsp::InsertReplaceEdit {
|
||||
new_text: snippet.body.clone(),
|
||||
insert: lsp_range,
|
||||
replace: lsp_range,
|
||||
},
|
||||
)),
|
||||
filter_text: Some(snippet.body.clone()),
|
||||
sort_text: Some(char::MAX.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
confirm: None,
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,9 +2,9 @@ mod signature_help;
|
|||
|
||||
use crate::{
|
||||
lsp_store::{LocalLspStore, LspStore},
|
||||
ActionVariant, CodeAction, CoreCompletion, DocumentHighlight, Hover, HoverBlock,
|
||||
CodeAction, CompletionSource, CoreCompletion, DocumentHighlight, Hover, HoverBlock,
|
||||
HoverBlockKind, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip,
|
||||
InlayHintTooltip, Location, LocationLink, MarkupContent, PrepareRenameResponse,
|
||||
InlayHintTooltip, Location, LocationLink, LspAction, MarkupContent, PrepareRenameResponse,
|
||||
ProjectTransaction, ResolveState,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
|
@ -2011,9 +2011,11 @@ impl LspCommand for GetCompletions {
|
|||
CoreCompletion {
|
||||
old_range,
|
||||
new_text,
|
||||
server_id,
|
||||
lsp_completion,
|
||||
resolved: false,
|
||||
source: CompletionSource::Lsp {
|
||||
server_id,
|
||||
lsp_completion: Box::new(lsp_completion),
|
||||
resolved: false,
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
|
@ -2256,11 +2258,11 @@ impl LspCommand for GetCodeActions {
|
|||
return None;
|
||||
}
|
||||
}
|
||||
ActionVariant::Action(Box::new(lsp_action))
|
||||
LspAction::Action(Box::new(lsp_action))
|
||||
}
|
||||
lsp::CodeActionOrCommand::Command(command) => {
|
||||
if available_commands.contains(&command.command) {
|
||||
ActionVariant::Command(command)
|
||||
LspAction::Command(command)
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ use crate::{
|
|||
toolchain_store::{EmptyToolchainStore, ToolchainStoreEvent},
|
||||
worktree_store::{WorktreeStore, WorktreeStoreEvent},
|
||||
yarn::YarnPathStore,
|
||||
ActionVariant, CodeAction, Completion, CoreCompletion, Hover, InlayHint, ProjectItem as _,
|
||||
ProjectPath, ProjectTransaction, ResolveState, Symbol, ToolchainStore,
|
||||
CodeAction, Completion, CompletionSource, CoreCompletion, Hover, InlayHint, LspAction,
|
||||
ProjectItem as _, ProjectPath, ProjectTransaction, ResolveState, Symbol, ToolchainStore,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use async_trait::async_trait;
|
||||
|
@ -1629,7 +1629,7 @@ impl LocalLspStore {
|
|||
action: &mut CodeAction,
|
||||
) -> anyhow::Result<()> {
|
||||
match &mut action.lsp_action {
|
||||
ActionVariant::Action(lsp_action) => {
|
||||
LspAction::Action(lsp_action) => {
|
||||
if GetCodeActions::can_resolve_actions(&lang_server.capabilities())
|
||||
&& lsp_action.data.is_some()
|
||||
&& (lsp_action.command.is_none() || lsp_action.edit.is_none())
|
||||
|
@ -1641,7 +1641,7 @@ impl LocalLspStore {
|
|||
);
|
||||
}
|
||||
}
|
||||
ActionVariant::Command(_) => {}
|
||||
LspAction::Command(_) => {}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
@ -4401,26 +4401,33 @@ impl LspStore {
|
|||
let mut did_resolve = false;
|
||||
if let Some((client, project_id)) = client {
|
||||
for completion_index in completion_indices {
|
||||
let server_id = completions.borrow()[completion_index].server_id;
|
||||
|
||||
if Self::resolve_completion_remote(
|
||||
project_id,
|
||||
server_id,
|
||||
buffer_id,
|
||||
completions.clone(),
|
||||
completion_index,
|
||||
client.clone(),
|
||||
)
|
||||
.await
|
||||
.log_err()
|
||||
.is_some()
|
||||
{
|
||||
did_resolve = true;
|
||||
let server_id = {
|
||||
let completion = &completions.borrow()[completion_index];
|
||||
completion.source.server_id()
|
||||
};
|
||||
if let Some(server_id) = server_id {
|
||||
if Self::resolve_completion_remote(
|
||||
project_id,
|
||||
server_id,
|
||||
buffer_id,
|
||||
completions.clone(),
|
||||
completion_index,
|
||||
client.clone(),
|
||||
)
|
||||
.await
|
||||
.log_err()
|
||||
.is_some()
|
||||
{
|
||||
did_resolve = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for completion_index in completion_indices {
|
||||
let server_id = completions.borrow()[completion_index].server_id;
|
||||
let Some(server_id) = completions.borrow()[completion_index].source.server_id()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let server_and_adapter = this
|
||||
.read_with(&cx, |lsp_store, _| {
|
||||
|
@ -4480,10 +4487,19 @@ impl LspStore {
|
|||
|
||||
let request = {
|
||||
let completion = &completions.borrow()[completion_index];
|
||||
if completion.resolved {
|
||||
return Ok(());
|
||||
match &completion.source {
|
||||
CompletionSource::Lsp {
|
||||
lsp_completion,
|
||||
resolved,
|
||||
..
|
||||
} => {
|
||||
if *resolved {
|
||||
return Ok(());
|
||||
}
|
||||
server.request::<lsp::request::ResolveCompletionItem>(*lsp_completion.clone())
|
||||
}
|
||||
CompletionSource::Custom => return Ok(()),
|
||||
}
|
||||
server.request::<lsp::request::ResolveCompletionItem>(completion.lsp_completion.clone())
|
||||
};
|
||||
let completion_item = request.await?;
|
||||
|
||||
|
@ -4508,15 +4524,20 @@ impl LspStore {
|
|||
// vtsls might change the type of completion after resolution.
|
||||
let mut completions = completions.borrow_mut();
|
||||
let completion = &mut completions[completion_index];
|
||||
if completion_item.insert_text_format != completion.lsp_completion.insert_text_format {
|
||||
completion.lsp_completion.insert_text_format = completion_item.insert_text_format;
|
||||
if let Some(lsp_completion) = completion.source.lsp_completion_mut() {
|
||||
if completion_item.insert_text_format != lsp_completion.insert_text_format {
|
||||
lsp_completion.insert_text_format = completion_item.insert_text_format;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut completions = completions.borrow_mut();
|
||||
let completion = &mut completions[completion_index];
|
||||
completion.lsp_completion = completion_item;
|
||||
completion.resolved = true;
|
||||
completion.source = CompletionSource::Lsp {
|
||||
lsp_completion: Box::new(completion_item),
|
||||
resolved: true,
|
||||
server_id: server.server_id(),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -4527,9 +4548,13 @@ impl LspStore {
|
|||
completion_index: usize,
|
||||
) -> Result<()> {
|
||||
let completion_item = completions.borrow()[completion_index]
|
||||
.lsp_completion
|
||||
.clone();
|
||||
if let Some(lsp_documentation) = completion_item.documentation.clone() {
|
||||
.source
|
||||
.lsp_completion()
|
||||
.cloned();
|
||||
if let Some(lsp_documentation) = completion_item
|
||||
.as_ref()
|
||||
.and_then(|completion_item| completion_item.documentation.clone())
|
||||
{
|
||||
let mut completions = completions.borrow_mut();
|
||||
let completion = &mut completions[completion_index];
|
||||
completion.documentation = Some(lsp_documentation.into());
|
||||
|
@ -4539,25 +4564,33 @@ impl LspStore {
|
|||
completion.documentation = Some(CompletionDocumentation::Undocumented);
|
||||
}
|
||||
|
||||
// NB: Zed does not have `details` inside the completion resolve capabilities, but certain language servers violate the spec and do not return `details` immediately, e.g. https://github.com/yioneko/vtsls/issues/213
|
||||
// So we have to update the label here anyway...
|
||||
let language = snapshot.language();
|
||||
let mut new_label = match language {
|
||||
Some(language) => {
|
||||
adapter
|
||||
.labels_for_completions(&[completion_item.clone()], language)
|
||||
.await?
|
||||
let mut new_label = match completion_item {
|
||||
Some(completion_item) => {
|
||||
// NB: Zed does not have `details` inside the completion resolve capabilities, but certain language servers violate the spec and do not return `details` immediately, e.g. https://github.com/yioneko/vtsls/issues/213
|
||||
// So we have to update the label here anyway...
|
||||
let language = snapshot.language();
|
||||
match language {
|
||||
Some(language) => {
|
||||
adapter
|
||||
.labels_for_completions(&[completion_item.clone()], language)
|
||||
.await?
|
||||
}
|
||||
None => Vec::new(),
|
||||
}
|
||||
.pop()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| {
|
||||
CodeLabel::fallback_for_completion(
|
||||
&completion_item,
|
||||
language.map(|language| language.as_ref()),
|
||||
)
|
||||
})
|
||||
}
|
||||
None => Vec::new(),
|
||||
}
|
||||
.pop()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| {
|
||||
CodeLabel::fallback_for_completion(
|
||||
&completion_item,
|
||||
language.map(|language| language.as_ref()),
|
||||
)
|
||||
});
|
||||
None => CodeLabel::plain(
|
||||
completions.borrow()[completion_index].new_text.clone(),
|
||||
None,
|
||||
),
|
||||
};
|
||||
ensure_uniform_list_compatible_label(&mut new_label);
|
||||
|
||||
let mut completions = completions.borrow_mut();
|
||||
|
@ -4589,12 +4622,19 @@ impl LspStore {
|
|||
) -> Result<()> {
|
||||
let lsp_completion = {
|
||||
let completion = &completions.borrow()[completion_index];
|
||||
if completion.resolved {
|
||||
return Ok(());
|
||||
match &completion.source {
|
||||
CompletionSource::Lsp {
|
||||
lsp_completion,
|
||||
resolved,
|
||||
..
|
||||
} => {
|
||||
if *resolved {
|
||||
return Ok(());
|
||||
}
|
||||
serde_json::to_string(lsp_completion).unwrap().into_bytes()
|
||||
}
|
||||
CompletionSource::Custom => return Ok(()),
|
||||
}
|
||||
serde_json::to_string(&completion.lsp_completion)
|
||||
.unwrap()
|
||||
.into_bytes()
|
||||
};
|
||||
let request = proto::ResolveCompletionDocumentation {
|
||||
project_id,
|
||||
|
@ -4622,8 +4662,11 @@ impl LspStore {
|
|||
let mut completions = completions.borrow_mut();
|
||||
let completion = &mut completions[completion_index];
|
||||
completion.documentation = Some(documentation);
|
||||
completion.lsp_completion = lsp_completion;
|
||||
completion.resolved = true;
|
||||
completion.source = CompletionSource::Lsp {
|
||||
server_id,
|
||||
lsp_completion,
|
||||
resolved: true,
|
||||
};
|
||||
|
||||
let old_range = response
|
||||
.old_start
|
||||
|
@ -4659,17 +4702,12 @@ impl LspStore {
|
|||
completion: Some(Self::serialize_completion(&CoreCompletion {
|
||||
old_range: completion.old_range,
|
||||
new_text: completion.new_text,
|
||||
server_id: completion.server_id,
|
||||
lsp_completion: completion.lsp_completion,
|
||||
resolved: completion.resolved,
|
||||
source: completion.source,
|
||||
})),
|
||||
}
|
||||
};
|
||||
|
||||
let response = client.request(request).await?;
|
||||
completions.borrow_mut()[completion_index].resolved = true;
|
||||
|
||||
if let Some(transaction) = response.transaction {
|
||||
if let Some(transaction) = client.request(request).await?.transaction {
|
||||
let transaction = language::proto::deserialize_transaction(transaction)?;
|
||||
buffer_handle
|
||||
.update(&mut cx, |buffer, _| {
|
||||
|
@ -4687,8 +4725,9 @@ impl LspStore {
|
|||
}
|
||||
})
|
||||
} else {
|
||||
let server_id = completions.borrow()[completion_index].server_id;
|
||||
let Some(server) = buffer_handle.update(cx, |buffer, cx| {
|
||||
let completion = &completions.borrow()[completion_index];
|
||||
let server_id = completion.source.server_id()?;
|
||||
Some(
|
||||
self.language_server_for_local_buffer(buffer, server_id, cx)?
|
||||
.1
|
||||
|
@ -4709,7 +4748,11 @@ impl LspStore {
|
|||
.await
|
||||
.context("resolving completion")?;
|
||||
let completion = completions.borrow()[completion_index].clone();
|
||||
let additional_text_edits = completion.lsp_completion.additional_text_edits;
|
||||
let additional_text_edits = completion
|
||||
.source
|
||||
.lsp_completion()
|
||||
.as_ref()
|
||||
.and_then(|lsp_completion| lsp_completion.additional_text_edits.clone());
|
||||
if let Some(edits) = additional_text_edits {
|
||||
let edits = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
|
@ -7139,8 +7182,7 @@ impl LspStore {
|
|||
Rc::new(RefCell::new(Box::new([Completion {
|
||||
old_range: completion.old_range,
|
||||
new_text: completion.new_text,
|
||||
lsp_completion: completion.lsp_completion,
|
||||
server_id: completion.server_id,
|
||||
source: completion.source,
|
||||
documentation: None,
|
||||
label: CodeLabel {
|
||||
text: Default::default(),
|
||||
|
@ -7148,7 +7190,6 @@ impl LspStore {
|
|||
filter_range: Default::default(),
|
||||
},
|
||||
confirm: None,
|
||||
resolved: completion.resolved,
|
||||
}]))),
|
||||
0,
|
||||
false,
|
||||
|
@ -8112,13 +8153,33 @@ impl LspStore {
|
|||
}
|
||||
|
||||
pub(crate) fn serialize_completion(completion: &CoreCompletion) -> proto::Completion {
|
||||
let (source, server_id, lsp_completion, resolved) = match &completion.source {
|
||||
CompletionSource::Lsp {
|
||||
server_id,
|
||||
lsp_completion,
|
||||
resolved,
|
||||
} => (
|
||||
proto::completion::Source::Lsp as i32,
|
||||
server_id.0 as u64,
|
||||
serde_json::to_vec(lsp_completion).unwrap(),
|
||||
*resolved,
|
||||
),
|
||||
CompletionSource::Custom => (
|
||||
proto::completion::Source::Custom as i32,
|
||||
0,
|
||||
Vec::new(),
|
||||
true,
|
||||
),
|
||||
};
|
||||
|
||||
proto::Completion {
|
||||
old_start: Some(serialize_anchor(&completion.old_range.start)),
|
||||
old_end: Some(serialize_anchor(&completion.old_range.end)),
|
||||
new_text: completion.new_text.clone(),
|
||||
server_id: completion.server_id.0 as u64,
|
||||
lsp_completion: serde_json::to_vec(&completion.lsp_completion).unwrap(),
|
||||
resolved: completion.resolved,
|
||||
server_id,
|
||||
lsp_completion,
|
||||
resolved,
|
||||
source,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8131,24 +8192,28 @@ impl LspStore {
|
|||
.old_end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("invalid old end"))?;
|
||||
let lsp_completion = serde_json::from_slice(&completion.lsp_completion)?;
|
||||
|
||||
Ok(CoreCompletion {
|
||||
old_range: old_start..old_end,
|
||||
new_text: completion.new_text,
|
||||
server_id: LanguageServerId(completion.server_id as usize),
|
||||
lsp_completion,
|
||||
resolved: completion.resolved,
|
||||
source: match proto::completion::Source::from_i32(completion.source) {
|
||||
Some(proto::completion::Source::Custom) => CompletionSource::Custom,
|
||||
Some(proto::completion::Source::Lsp) => CompletionSource::Lsp {
|
||||
server_id: LanguageServerId::from_proto(completion.server_id),
|
||||
lsp_completion: serde_json::from_slice(&completion.lsp_completion)?,
|
||||
resolved: completion.resolved,
|
||||
},
|
||||
_ => anyhow::bail!("Unexpected completion source {}", completion.source),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
|
||||
let (kind, lsp_action) = match &action.lsp_action {
|
||||
ActionVariant::Action(code_action) => (
|
||||
LspAction::Action(code_action) => (
|
||||
proto::code_action::Kind::Action as i32,
|
||||
serde_json::to_vec(code_action).unwrap(),
|
||||
),
|
||||
ActionVariant::Command(command) => (
|
||||
LspAction::Command(command) => (
|
||||
proto::code_action::Kind::Command as i32,
|
||||
serde_json::to_vec(command).unwrap(),
|
||||
),
|
||||
|
@ -8174,10 +8239,10 @@ impl LspStore {
|
|||
.ok_or_else(|| anyhow!("invalid end"))?;
|
||||
let lsp_action = match proto::code_action::Kind::from_i32(action.kind) {
|
||||
Some(proto::code_action::Kind::Action) => {
|
||||
ActionVariant::Action(serde_json::from_slice(&action.lsp_action)?)
|
||||
LspAction::Action(serde_json::from_slice(&action.lsp_action)?)
|
||||
}
|
||||
Some(proto::code_action::Kind::Command) => {
|
||||
ActionVariant::Command(serde_json::from_slice(&action.lsp_action)?)
|
||||
LspAction::Command(serde_json::from_slice(&action.lsp_action)?)
|
||||
}
|
||||
None => anyhow::bail!("Unknown action kind {}", action.kind),
|
||||
};
|
||||
|
@ -8215,17 +8280,23 @@ fn remove_empty_hover_blocks(mut hover: Hover) -> Option<Hover> {
|
|||
}
|
||||
|
||||
async fn populate_labels_for_completions(
|
||||
mut new_completions: Vec<CoreCompletion>,
|
||||
new_completions: Vec<CoreCompletion>,
|
||||
language: Option<Arc<Language>>,
|
||||
lsp_adapter: Option<Arc<CachedLspAdapter>>,
|
||||
completions: &mut Vec<Completion>,
|
||||
) {
|
||||
let lsp_completions = new_completions
|
||||
.iter_mut()
|
||||
.map(|completion| mem::take(&mut completion.lsp_completion))
|
||||
.iter()
|
||||
.filter_map(|new_completion| {
|
||||
if let CompletionSource::Lsp { lsp_completion, .. } = &new_completion.source {
|
||||
Some(*lsp_completion.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let labels = if let Some((language, lsp_adapter)) = language.as_ref().zip(lsp_adapter) {
|
||||
let mut labels = if let Some((language, lsp_adapter)) = language.as_ref().zip(lsp_adapter) {
|
||||
lsp_adapter
|
||||
.labels_for_completions(&lsp_completions, language)
|
||||
.await
|
||||
|
@ -8233,34 +8304,45 @@ async fn populate_labels_for_completions(
|
|||
.unwrap_or_default()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
}
|
||||
.into_iter()
|
||||
.fuse();
|
||||
|
||||
for ((completion, lsp_completion), label) in new_completions
|
||||
.into_iter()
|
||||
.zip(lsp_completions)
|
||||
.zip(labels.into_iter().chain(iter::repeat(None)))
|
||||
{
|
||||
let documentation = if let Some(docs) = lsp_completion.documentation.clone() {
|
||||
Some(docs.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
for completion in new_completions {
|
||||
match &completion.source {
|
||||
CompletionSource::Lsp { lsp_completion, .. } => {
|
||||
let documentation = if let Some(docs) = lsp_completion.documentation.clone() {
|
||||
Some(docs.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut label = label.unwrap_or_else(|| {
|
||||
CodeLabel::fallback_for_completion(&lsp_completion, language.as_deref())
|
||||
});
|
||||
ensure_uniform_list_compatible_label(&mut label);
|
||||
|
||||
completions.push(Completion {
|
||||
old_range: completion.old_range,
|
||||
new_text: completion.new_text,
|
||||
label,
|
||||
server_id: completion.server_id,
|
||||
documentation,
|
||||
lsp_completion,
|
||||
confirm: None,
|
||||
resolved: false,
|
||||
})
|
||||
let mut label = labels.next().flatten().unwrap_or_else(|| {
|
||||
CodeLabel::fallback_for_completion(&lsp_completion, language.as_deref())
|
||||
});
|
||||
ensure_uniform_list_compatible_label(&mut label);
|
||||
completions.push(Completion {
|
||||
label,
|
||||
documentation,
|
||||
old_range: completion.old_range,
|
||||
new_text: completion.new_text,
|
||||
source: completion.source,
|
||||
confirm: None,
|
||||
})
|
||||
}
|
||||
CompletionSource::Custom => {
|
||||
let mut label = CodeLabel::plain(completion.new_text.clone(), None);
|
||||
ensure_uniform_list_compatible_label(&mut label);
|
||||
completions.push(Completion {
|
||||
label,
|
||||
documentation: None,
|
||||
old_range: completion.old_range,
|
||||
new_text: completion.new_text,
|
||||
source: completion.source,
|
||||
confirm: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -364,14 +364,10 @@ pub struct Completion {
|
|||
pub new_text: String,
|
||||
/// A label for this completion that is shown in the menu.
|
||||
pub label: CodeLabel,
|
||||
/// The id of the language server that produced this completion.
|
||||
pub server_id: LanguageServerId,
|
||||
/// The documentation for this completion.
|
||||
pub documentation: Option<CompletionDocumentation>,
|
||||
/// The raw completion provided by the language server.
|
||||
pub lsp_completion: lsp::CompletionItem,
|
||||
/// Whether this completion has been resolved, to ensure it happens once per completion.
|
||||
pub resolved: bool,
|
||||
/// Completion data source which it was constructed from.
|
||||
pub source: CompletionSource,
|
||||
/// An optional callback to invoke when this completion is confirmed.
|
||||
/// Returns, whether new completions should be retriggered after the current one.
|
||||
/// If `true` is returned, the editor will show a new completion menu after this completion is confirmed.
|
||||
|
@ -379,15 +375,53 @@ pub struct Completion {
|
|||
pub confirm: Option<Arc<dyn Send + Sync + Fn(CompletionIntent, &mut Window, &mut App) -> bool>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CompletionSource {
|
||||
Lsp {
|
||||
/// The id of the language server that produced this completion.
|
||||
server_id: LanguageServerId,
|
||||
/// The raw completion provided by the language server.
|
||||
lsp_completion: Box<lsp::CompletionItem>,
|
||||
/// Whether this completion has been resolved, to ensure it happens once per completion.
|
||||
resolved: bool,
|
||||
},
|
||||
Custom,
|
||||
}
|
||||
|
||||
impl CompletionSource {
|
||||
pub fn server_id(&self) -> Option<LanguageServerId> {
|
||||
if let CompletionSource::Lsp { server_id, .. } = self {
|
||||
Some(*server_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lsp_completion(&self) -> Option<&lsp::CompletionItem> {
|
||||
if let Self::Lsp { lsp_completion, .. } = self {
|
||||
Some(lsp_completion)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn lsp_completion_mut(&mut self) -> Option<&mut lsp::CompletionItem> {
|
||||
if let Self::Lsp { lsp_completion, .. } = self {
|
||||
Some(lsp_completion)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Completion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Completion")
|
||||
.field("old_range", &self.old_range)
|
||||
.field("new_text", &self.new_text)
|
||||
.field("label", &self.label)
|
||||
.field("server_id", &self.server_id)
|
||||
.field("documentation", &self.documentation)
|
||||
.field("lsp_completion", &self.lsp_completion)
|
||||
.field("source", &self.source)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -397,9 +431,7 @@ impl std::fmt::Debug for Completion {
|
|||
pub(crate) struct CoreCompletion {
|
||||
old_range: Range<Anchor>,
|
||||
new_text: String,
|
||||
server_id: LanguageServerId,
|
||||
lsp_completion: lsp::CompletionItem,
|
||||
resolved: bool,
|
||||
source: CompletionSource,
|
||||
}
|
||||
|
||||
/// A code action provided by a language server.
|
||||
|
@ -411,12 +443,12 @@ pub struct CodeAction {
|
|||
pub range: Range<Anchor>,
|
||||
/// The raw code action provided by the language server.
|
||||
/// Can be either an action or a command.
|
||||
pub lsp_action: ActionVariant,
|
||||
pub lsp_action: LspAction,
|
||||
}
|
||||
|
||||
/// An action sent back by a language server.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ActionVariant {
|
||||
pub enum LspAction {
|
||||
/// An action with the full data, may have a command or may not.
|
||||
/// May require resolving.
|
||||
Action(Box<lsp::CodeAction>),
|
||||
|
@ -424,7 +456,7 @@ pub enum ActionVariant {
|
|||
Command(lsp::Command),
|
||||
}
|
||||
|
||||
impl ActionVariant {
|
||||
impl LspAction {
|
||||
pub fn title(&self) -> &str {
|
||||
match self {
|
||||
Self::Action(action) => &action.title,
|
||||
|
@ -4605,27 +4637,38 @@ impl Completion {
|
|||
/// A key that can be used to sort completions when displaying
|
||||
/// them to the user.
|
||||
pub fn sort_key(&self) -> (usize, &str) {
|
||||
let kind_key = match self.lsp_completion.kind {
|
||||
Some(lsp::CompletionItemKind::KEYWORD) => 0,
|
||||
Some(lsp::CompletionItemKind::VARIABLE) => 1,
|
||||
_ => 2,
|
||||
};
|
||||
const DEFAULT_KIND_KEY: usize = 2;
|
||||
let kind_key = self
|
||||
.source
|
||||
.lsp_completion()
|
||||
.and_then(|lsp_completion| lsp_completion.kind)
|
||||
.and_then(|lsp_completion_kind| match lsp_completion_kind {
|
||||
lsp::CompletionItemKind::KEYWORD => Some(0),
|
||||
lsp::CompletionItemKind::VARIABLE => Some(1),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(DEFAULT_KIND_KEY);
|
||||
(kind_key, &self.label.text[self.label.filter_range.clone()])
|
||||
}
|
||||
|
||||
/// Whether this completion is a snippet.
|
||||
pub fn is_snippet(&self) -> bool {
|
||||
self.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
|
||||
self.source
|
||||
.lsp_completion()
|
||||
.map_or(false, |lsp_completion| {
|
||||
lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the corresponding color for this completion.
|
||||
///
|
||||
/// Will return `None` if this completion's kind is not [`CompletionItemKind::COLOR`].
|
||||
pub fn color(&self) -> Option<Hsla> {
|
||||
match self.lsp_completion.kind {
|
||||
Some(CompletionItemKind::COLOR) => color_extractor::extract_color(&self.lsp_completion),
|
||||
_ => None,
|
||||
let lsp_completion = self.source.lsp_completion()?;
|
||||
if lsp_completion.kind? == CompletionItemKind::COLOR {
|
||||
return color_extractor::extract_color(lsp_completion);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
syntax = "proto3";
|
||||
package zed.messages;
|
||||
import "google/protobuf/wrappers.proto";
|
||||
|
||||
// Looking for a number? Search "// current max"
|
||||
|
||||
|
@ -999,6 +999,12 @@ message Completion {
|
|||
uint64 server_id = 4;
|
||||
bytes lsp_completion = 5;
|
||||
bool resolved = 6;
|
||||
Source source = 7;
|
||||
|
||||
enum Source {
|
||||
Custom = 0;
|
||||
Lsp = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message GetCodeActions {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue