lsp: Update completion new_text from resolved completion item
Kunall Banerjee
created
Some language servers (notably vtsls with completeFunctionCalls) update
insertText/textEdit during completionItem/resolve to add snippet content
like function call parentheses. Previously, Completion.new_text was only
set from the initial completion response and never re-derived after
resolve, so the snippet text was silently discarded.
Re-derive new_text from the resolved lsp_completion. Only the text
content is updated—replace/insert ranges are left as anchors from the
original response since the LSP ranges in the resolved text_edit are
stale when completions are cached across keystrokes (#34094).
Closes #53275
@@ -6568,9 +6568,6 @@ impl LspStore {
.into_response()
.context("resolve completion")?;
- // We must not use any data such as sortText, filterText, insertText and textEdit to edit `Completion` since they are not suppose change during resolve.- // Refer: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion-
let mut completions = completions.borrow_mut();
let completion = &mut completions[completion_index];
if let CompletionSource::Lsp {
@@ -6589,6 +6586,33 @@ impl LspStore {
);
**lsp_completion = resolved_completion;
*resolved = true;
+
+ // Re-derive new_text from the resolved completion. Some servers
+ // (e.g. vtsls with completeFunctionCalls) update insertText/textEdit
+ // during resolve to add snippet content like function call parentheses.
+ //
+ // vtsls resolve flow:
+ // https://github.com/yioneko/vtsls/blob/fecf52324a30e72dfab1537047556076720c1a5f/packages/service/src/service/completion.ts#L228-L244
+ // vtsls converter (isSnippet / insertTextFormat):
+ // https://github.com/yioneko/vtsls/blob/28e075105d7711d635ebf8aefc971bb8e1d2fe65/packages/service/src/utils/converter.ts#L149-L200
+ //
+ // NB: We only update the text content here, NOT the replace/insert
+ // ranges on `Completion`. Those ranges were converted to anchors from
+ // the original response and stay valid across buffer edits. The LSP
+ // ranges in the resolved text_edit are stale when completions are
+ // cached across keystrokes (see #34094).
+ let resolved_new_text = lsp_completion
+ .text_edit
+ .as_ref()
+ .map(|edit| match edit {
+ lsp::CompletionTextEdit::Edit(e) => e.new_text.clone(),
+ lsp::CompletionTextEdit::InsertAndReplace(e) => e.new_text.clone(),
+ })
+ .or_else(|| lsp_completion.insert_text.clone());
+ if let Some(mut resolved_new_text) = resolved_new_text {
+ LineEnding::normalize(&mut resolved_new_text);
+ completion.new_text = resolved_new_text;
+ }
}
Ok(())
}