diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 2f579f5a724db143bbd4b0f9853a217bd6b14655..ae7fe6abba61f094977b73155f11969c459ad28c 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -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(()) }