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:
Max Brunsfeld 2024-04-03 09:22:56 -07:00 committed by GitHub
parent ef3d04efe6
commit 256b446bdf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 589 additions and 448 deletions

View file

@ -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()
}