Refactor LSP adapter methods to compute labels in batches (#10097)
Once we enable extensions to customize the labels of completions and symbols, this new structure will allow this to be done with a single WASM call, instead of one WASM call per completion / symbol. Release Notes: - N/A --------- Co-authored-by: Marshall Bowers <elliott.codes@gmail.com> Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
parent
ef3d04efe6
commit
256b446bdf
9 changed files with 589 additions and 448 deletions
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel,
|
||||
InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink,
|
||||
MarkupContent, Project, ProjectTransaction, ResolveState,
|
||||
CodeAction, CoreCompletion, DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint,
|
||||
InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location,
|
||||
LocationLink, MarkupContent, Project, ProjectTransaction, ResolveState,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
|
@ -10,11 +10,10 @@ use futures::future;
|
|||
use gpui::{AppContext, AsyncAppContext, Model};
|
||||
use language::{
|
||||
language_settings::{language_settings, InlayHintKind},
|
||||
point_from_lsp, point_to_lsp, prepare_completion_documentation,
|
||||
point_from_lsp, point_to_lsp,
|
||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
|
||||
CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
|
||||
Unclipped,
|
||||
OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
};
|
||||
use lsp::{
|
||||
CompletionListItemDefaultsEditRange, DocumentHighlightKind, LanguageServer, LanguageServerId,
|
||||
|
@ -1429,7 +1428,7 @@ impl LspCommand for GetHover {
|
|||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for GetCompletions {
|
||||
type Response = Vec<Completion>;
|
||||
type Response = Vec<CoreCompletion>;
|
||||
type LspRequest = lsp::request::Completion;
|
||||
type ProtoRequest = proto::GetCompletions;
|
||||
|
||||
|
@ -1458,9 +1457,9 @@ impl LspCommand for GetCompletions {
|
|||
buffer: Model<Buffer>,
|
||||
server_id: LanguageServerId,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Vec<Completion>> {
|
||||
) -> Result<Self::Response> {
|
||||
let mut response_list = None;
|
||||
let completions = if let Some(completions) = completions {
|
||||
let mut completions = if let Some(completions) = completions {
|
||||
match completions {
|
||||
lsp::CompletionResponse::Array(completions) => completions,
|
||||
|
||||
|
@ -1480,147 +1479,120 @@ impl LspCommand for GetCompletions {
|
|||
})?
|
||||
.ok_or_else(|| anyhow!("no such language server"))?;
|
||||
|
||||
let completions = buffer.update(&mut cx, |buffer, cx| {
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
let language = buffer.language().cloned();
|
||||
let mut completion_edits = Vec::new();
|
||||
buffer.update(&mut cx, |buffer, _cx| {
|
||||
let snapshot = buffer.snapshot();
|
||||
let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
|
||||
|
||||
let mut range_for_token = None;
|
||||
completions
|
||||
.into_iter()
|
||||
.filter_map(move |mut lsp_completion| {
|
||||
let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() {
|
||||
// If the language server provides a range to overwrite, then
|
||||
// check that the range is valid.
|
||||
Some(lsp::CompletionTextEdit::Edit(edit)) => {
|
||||
let range = range_from_lsp(edit.range);
|
||||
completions.retain_mut(|lsp_completion| {
|
||||
let edit = match lsp_completion.text_edit.as_ref() {
|
||||
// If the language server provides a range to overwrite, then
|
||||
// check that the range is valid.
|
||||
Some(lsp::CompletionTextEdit::Edit(edit)) => {
|
||||
let range = range_from_lsp(edit.range);
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion out of expected range");
|
||||
return false;
|
||||
}
|
||||
(
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||
edit.new_text.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
// If the language server does not provide a range, then infer
|
||||
// the range based on the syntax tree.
|
||||
None => {
|
||||
if self.position != clipped_position {
|
||||
log::info!("completion out of expected range");
|
||||
return false;
|
||||
}
|
||||
|
||||
let default_edit_range = response_list
|
||||
.as_ref()
|
||||
.and_then(|list| list.item_defaults.as_ref())
|
||||
.and_then(|defaults| defaults.edit_range.as_ref())
|
||||
.and_then(|range| match range {
|
||||
CompletionListItemDefaultsEditRange::Range(r) => Some(r),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let range = if let Some(range) = default_edit_range {
|
||||
let range = range_from_lsp(*range);
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion out of expected range");
|
||||
return None;
|
||||
}
|
||||
(
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||
edit.new_text.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
// If the language server does not provide a range, then infer
|
||||
// the range based on the syntax tree.
|
||||
None => {
|
||||
if self.position != clipped_position {
|
||||
log::info!("completion out of expected range");
|
||||
return None;
|
||||
return false;
|
||||
}
|
||||
|
||||
let default_edit_range = response_list
|
||||
.as_ref()
|
||||
.and_then(|list| list.item_defaults.as_ref())
|
||||
.and_then(|defaults| defaults.edit_range.as_ref())
|
||||
.and_then(|range| match range {
|
||||
CompletionListItemDefaultsEditRange::Range(r) => Some(r),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let range = if let Some(range) = default_edit_range {
|
||||
let range = range_from_lsp(*range);
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion out of expected range");
|
||||
return None;
|
||||
}
|
||||
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end)
|
||||
} else {
|
||||
range_for_token
|
||||
.get_or_insert_with(|| {
|
||||
let offset = self.position.to_offset(&snapshot);
|
||||
let (range, kind) = snapshot.surrounding_word(offset);
|
||||
let range = if kind == Some(CharKind::Word) {
|
||||
range
|
||||
} else {
|
||||
offset..offset
|
||||
};
|
||||
|
||||
snapshot.anchor_before(range.start)
|
||||
..snapshot.anchor_after(range.end)
|
||||
})
|
||||
.clone()
|
||||
};
|
||||
|
||||
let text = lsp_completion
|
||||
.insert_text
|
||||
.as_ref()
|
||||
.unwrap_or(&lsp_completion.label)
|
||||
.clone();
|
||||
(range, text)
|
||||
}
|
||||
|
||||
Some(lsp::CompletionTextEdit::InsertAndReplace(edit)) => {
|
||||
let range = range_from_lsp(edit.insert);
|
||||
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion out of expected range");
|
||||
return None;
|
||||
}
|
||||
(
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||
edit.new_text.clone(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let language_registry = language_registry.clone();
|
||||
let language = language.clone();
|
||||
let language_server_adapter = language_server_adapter.clone();
|
||||
LineEnding::normalize(&mut new_text);
|
||||
Some(async move {
|
||||
let mut label = None;
|
||||
if let Some(language) = &language {
|
||||
language_server_adapter
|
||||
.process_completion(&mut lsp_completion)
|
||||
.await;
|
||||
label = language_server_adapter
|
||||
.label_for_completion(&lsp_completion, language)
|
||||
.await;
|
||||
}
|
||||
|
||||
let documentation = if let Some(lsp_docs) = &lsp_completion.documentation {
|
||||
Some(
|
||||
prepare_completion_documentation(
|
||||
lsp_docs,
|
||||
&language_registry,
|
||||
language.clone(),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end)
|
||||
} else {
|
||||
None
|
||||
range_for_token
|
||||
.get_or_insert_with(|| {
|
||||
let offset = self.position.to_offset(&snapshot);
|
||||
let (range, kind) = snapshot.surrounding_word(offset);
|
||||
let range = if kind == Some(CharKind::Word) {
|
||||
range
|
||||
} else {
|
||||
offset..offset
|
||||
};
|
||||
|
||||
snapshot.anchor_before(range.start)
|
||||
..snapshot.anchor_after(range.end)
|
||||
})
|
||||
.clone()
|
||||
};
|
||||
|
||||
Completion {
|
||||
old_range,
|
||||
new_text,
|
||||
label: label.unwrap_or_else(|| {
|
||||
language::CodeLabel::plain(
|
||||
lsp_completion.label.clone(),
|
||||
lsp_completion.filter_text.as_deref(),
|
||||
)
|
||||
}),
|
||||
documentation,
|
||||
server_id,
|
||||
lsp_completion,
|
||||
let text = lsp_completion
|
||||
.insert_text
|
||||
.as_ref()
|
||||
.unwrap_or(&lsp_completion.label)
|
||||
.clone();
|
||||
(range, text)
|
||||
}
|
||||
|
||||
Some(lsp::CompletionTextEdit::InsertAndReplace(edit)) => {
|
||||
let range = range_from_lsp(edit.insert);
|
||||
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion out of expected range");
|
||||
return false;
|
||||
}
|
||||
})
|
||||
})
|
||||
(
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||
edit.new_text.clone(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
completion_edits.push(edit);
|
||||
true
|
||||
});
|
||||
})?;
|
||||
|
||||
Ok(future::join_all(completions).await)
|
||||
language_server_adapter
|
||||
.process_completions(&mut completions)
|
||||
.await;
|
||||
|
||||
Ok(completions
|
||||
.into_iter()
|
||||
.zip(completion_edits)
|
||||
.map(|(lsp_completion, (old_range, mut new_text))| {
|
||||
LineEnding::normalize(&mut new_text);
|
||||
CoreCompletion {
|
||||
old_range,
|
||||
new_text,
|
||||
server_id,
|
||||
lsp_completion,
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetCompletions {
|
||||
|
@ -1656,7 +1628,7 @@ impl LspCommand for GetCompletions {
|
|||
}
|
||||
|
||||
fn response_to_proto(
|
||||
completions: Vec<Completion>,
|
||||
completions: Vec<CoreCompletion>,
|
||||
_: &mut Project,
|
||||
_: PeerId,
|
||||
buffer_version: &clock::Global,
|
||||
|
@ -1665,7 +1637,7 @@ impl LspCommand for GetCompletions {
|
|||
proto::GetCompletionsResponse {
|
||||
completions: completions
|
||||
.iter()
|
||||
.map(language::proto::serialize_completion)
|
||||
.map(Project::serialize_completion)
|
||||
.collect(),
|
||||
version: serialize_version(buffer_version),
|
||||
}
|
||||
|
@ -1674,26 +1646,21 @@ impl LspCommand for GetCompletions {
|
|||
async fn response_from_proto(
|
||||
self,
|
||||
message: proto::GetCompletionsResponse,
|
||||
project: Model<Project>,
|
||||
_project: Model<Project>,
|
||||
buffer: Model<Buffer>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Vec<Completion>> {
|
||||
) -> Result<Self::Response> {
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| {
|
||||
buffer.wait_for_version(deserialize_version(&message.version))
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
|
||||
let language_registry = project.update(&mut cx, |project, _| project.languages.clone())?;
|
||||
let completions = message.completions.into_iter().map(|completion| {
|
||||
language::proto::deserialize_completion(
|
||||
completion,
|
||||
language.clone(),
|
||||
&language_registry,
|
||||
)
|
||||
});
|
||||
future::try_join_all(completions).await
|
||||
message
|
||||
.completions
|
||||
.into_iter()
|
||||
.map(Project::deserialize_completion)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &proto::GetCompletions) -> Result<BufferId> {
|
||||
|
@ -1816,7 +1783,7 @@ impl LspCommand for GetCodeActions {
|
|||
proto::GetCodeActionsResponse {
|
||||
actions: code_actions
|
||||
.iter()
|
||||
.map(language::proto::serialize_code_action)
|
||||
.map(Project::serialize_code_action)
|
||||
.collect(),
|
||||
version: serialize_version(buffer_version),
|
||||
}
|
||||
|
@ -1837,7 +1804,7 @@ impl LspCommand for GetCodeActions {
|
|||
message
|
||||
.actions
|
||||
.into_iter()
|
||||
.map(language::proto::deserialize_code_action)
|
||||
.map(Project::deserialize_code_action)
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue