Add LspAdapter hook for processing completions, fix completion sorting from Pyright
This commit is contained in:
parent
5020c70a04
commit
bca635e5d3
4 changed files with 116 additions and 81 deletions
|
@ -135,6 +135,10 @@ impl CachedLspAdapter {
|
||||||
self.adapter.process_diagnostics(params).await
|
self.adapter.process_diagnostics(params).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn process_completion(&self, completion_item: &mut lsp::CompletionItem) {
|
||||||
|
self.adapter.process_completion(completion_item).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn label_for_completion(
|
pub async fn label_for_completion(
|
||||||
&self,
|
&self,
|
||||||
completion_item: &lsp::CompletionItem,
|
completion_item: &lsp::CompletionItem,
|
||||||
|
@ -175,6 +179,8 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||||
|
|
||||||
async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
|
async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
|
||||||
|
|
||||||
|
async fn process_completion(&self, _: &mut lsp::CompletionItem) {}
|
||||||
|
|
||||||
async fn label_for_completion(
|
async fn label_for_completion(
|
||||||
&self,
|
&self,
|
||||||
_: &lsp::CompletionItem,
|
_: &lsp::CompletionItem,
|
||||||
|
@ -826,6 +832,12 @@ impl Language {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn process_completion(self: &Arc<Self>, completion: &mut lsp::CompletionItem) {
|
||||||
|
if let Some(adapter) = self.adapter.as_ref() {
|
||||||
|
adapter.process_completion(completion).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn label_for_completion(
|
pub async fn label_for_completion(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
completion: &lsp::CompletionItem,
|
completion: &lsp::CompletionItem,
|
||||||
|
|
|
@ -426,10 +426,11 @@ pub async fn deserialize_completion(
|
||||||
.and_then(deserialize_anchor)
|
.and_then(deserialize_anchor)
|
||||||
.ok_or_else(|| anyhow!("invalid old end"))?;
|
.ok_or_else(|| anyhow!("invalid old end"))?;
|
||||||
let lsp_completion = serde_json::from_slice(&completion.lsp_completion)?;
|
let lsp_completion = serde_json::from_slice(&completion.lsp_completion)?;
|
||||||
let label = match language {
|
|
||||||
Some(l) => l.label_for_completion(&lsp_completion).await,
|
let mut label = None;
|
||||||
None => None,
|
if let Some(language) = language {
|
||||||
};
|
label = language.label_for_completion(&lsp_completion).await;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Completion {
|
Ok(Completion {
|
||||||
old_range: old_start..old_end,
|
old_range: old_start..old_end,
|
||||||
|
|
|
@ -3329,88 +3329,91 @@ impl Project {
|
||||||
let snapshot = this.snapshot();
|
let snapshot = this.snapshot();
|
||||||
let clipped_position = this.clip_point_utf16(position, Bias::Left);
|
let clipped_position = this.clip_point_utf16(position, Bias::Left);
|
||||||
let mut range_for_token = None;
|
let mut range_for_token = None;
|
||||||
completions.into_iter().filter_map(move |lsp_completion| {
|
completions
|
||||||
// For now, we can only handle additional edits if they are returned
|
.into_iter()
|
||||||
// when resolving the completion, not if they are present initially.
|
.filter_map(move |mut lsp_completion| {
|
||||||
if lsp_completion
|
// For now, we can only handle additional edits if they are returned
|
||||||
.additional_text_edits
|
// when resolving the completion, not if they are present initially.
|
||||||
.as_ref()
|
if lsp_completion
|
||||||
.map_or(false, |edits| !edits.is_empty())
|
.additional_text_edits
|
||||||
{
|
.as_ref()
|
||||||
return None;
|
.map_or(false, |edits| !edits.is_empty())
|
||||||
}
|
{
|
||||||
|
|
||||||
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);
|
|
||||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
|
||||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
|
||||||
if start != range.start || end != range.end {
|
|
||||||
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 position != clipped_position {
|
|
||||||
log::info!("completion out of expected range");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let Range { start, end } = range_for_token
|
|
||||||
.get_or_insert_with(|| {
|
|
||||||
let offset = position.to_offset(&snapshot);
|
|
||||||
let (range, kind) = snapshot.surrounding_word(offset);
|
|
||||||
if kind == Some(CharKind::Word) {
|
|
||||||
range
|
|
||||||
} else {
|
|
||||||
offset..offset
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.clone();
|
|
||||||
let text = lsp_completion
|
|
||||||
.insert_text
|
|
||||||
.as_ref()
|
|
||||||
.unwrap_or(&lsp_completion.label)
|
|
||||||
.clone();
|
|
||||||
(
|
|
||||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
|
||||||
text,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => {
|
|
||||||
log::info!("unsupported insert/replace completion");
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
LineEnding::normalize(&mut new_text);
|
let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref()
|
||||||
let language = language.clone();
|
{
|
||||||
Some(async move {
|
// If the language server provides a range to overwrite, then
|
||||||
let label = if let Some(language) = language {
|
// check that the range is valid.
|
||||||
language.label_for_completion(&lsp_completion).await
|
Some(lsp::CompletionTextEdit::Edit(edit)) => {
|
||||||
} else {
|
let range = range_from_lsp(edit.range);
|
||||||
None
|
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||||
};
|
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||||
Completion {
|
if start != range.start || end != range.end {
|
||||||
old_range,
|
log::info!("completion out of expected range");
|
||||||
new_text,
|
return None;
|
||||||
label: label.unwrap_or_else(|| {
|
}
|
||||||
CodeLabel::plain(
|
(
|
||||||
lsp_completion.label.clone(),
|
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||||
lsp_completion.filter_text.as_deref(),
|
edit.new_text.clone(),
|
||||||
)
|
)
|
||||||
}),
|
}
|
||||||
lsp_completion,
|
// If the language server does not provide a range, then infer
|
||||||
}
|
// the range based on the syntax tree.
|
||||||
|
None => {
|
||||||
|
if position != clipped_position {
|
||||||
|
log::info!("completion out of expected range");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let Range { start, end } = range_for_token
|
||||||
|
.get_or_insert_with(|| {
|
||||||
|
let offset = position.to_offset(&snapshot);
|
||||||
|
let (range, kind) = snapshot.surrounding_word(offset);
|
||||||
|
if kind == Some(CharKind::Word) {
|
||||||
|
range
|
||||||
|
} else {
|
||||||
|
offset..offset
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.clone();
|
||||||
|
let text = lsp_completion
|
||||||
|
.insert_text
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&lsp_completion.label)
|
||||||
|
.clone();
|
||||||
|
(
|
||||||
|
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||||
|
text,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => {
|
||||||
|
log::info!("unsupported insert/replace completion");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LineEnding::normalize(&mut new_text);
|
||||||
|
let language = language.clone();
|
||||||
|
Some(async move {
|
||||||
|
let mut label = None;
|
||||||
|
if let Some(language) = language {
|
||||||
|
language.process_completion(&mut lsp_completion).await;
|
||||||
|
label = language.label_for_completion(&lsp_completion).await;
|
||||||
|
}
|
||||||
|
Completion {
|
||||||
|
old_range,
|
||||||
|
new_text,
|
||||||
|
label: label.unwrap_or_else(|| {
|
||||||
|
CodeLabel::plain(
|
||||||
|
lsp_completion.label.clone(),
|
||||||
|
lsp_completion.filter_text.as_deref(),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
lsp_completion,
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(futures::future::join_all(completions).await)
|
Ok(futures::future::join_all(completions).await)
|
||||||
|
|
|
@ -87,6 +87,25 @@ impl LspAdapter for PythonLspAdapter {
|
||||||
.log_err()
|
.log_err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn process_completion(&self, item: &mut lsp::CompletionItem) {
|
||||||
|
// Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`.
|
||||||
|
// Where `XX` is the sorting category, `YYYY` is based on most recent usage,
|
||||||
|
// and `name` is the symbol name itself.
|
||||||
|
//
|
||||||
|
// Because the the symbol name is included, there generally are not ties when
|
||||||
|
// sorting by the `sortText`, so the symbol's fuzzy match score is not taken
|
||||||
|
// into account. Here, we remove the symbol name from the sortText in order
|
||||||
|
// to allow our own fuzzy score to be used to break ties.
|
||||||
|
//
|
||||||
|
// see https://github.com/microsoft/pyright/blob/95ef4e103b9b2f129c9320427e51b73ea7cf78bd/packages/pyright-internal/src/languageService/completionProvider.ts#LL2873
|
||||||
|
let Some(sort_text) = &mut item.sort_text else { return };
|
||||||
|
let mut parts = sort_text.split('.');
|
||||||
|
let Some(first) = parts.next() else { return };
|
||||||
|
let Some(second) = parts.next() else { return };
|
||||||
|
let Some(_) = parts.next() else { return };
|
||||||
|
sort_text.replace_range(first.len() + second.len() + 1.., "");
|
||||||
|
}
|
||||||
|
|
||||||
async fn label_for_completion(
|
async fn label_for_completion(
|
||||||
&self,
|
&self,
|
||||||
item: &lsp::CompletionItem,
|
item: &lsp::CompletionItem,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue