Add completions.lsp_insert_mode setting to control what ranges are replaced when a completion is inserted (#27453)

This PR adds `completions.lsp_insert_mode` and effectively changes the
default from `"replace"` to `"replace_suffix"`, which automatically
detects whether to use the LSP `replace` range instead of `insert`
range.

`"replace_suffix"` was chosen as a default because it's more
conservative than `"replace_subsequence"`, considering that deleting
text is usually faster and less disruptive than having to rewrite a long
replaced word.

Fixes #27197
Fixes #23395 (again)
Fixes #4816 (again)

Release Notes:

- Added new setting `completions.lsp_insert_mode` that changes what will
be replaced when an LSP completion is accepted. The default is
`"replace_suffix"`, but it accepts 4 values: `"insert"` for replacing
only the text before the cursor, `"replace"` for replacing the whole
text, `"replace_suffix"` that acts like `"replace"` when the text after
the cursor is a suffix of the completion, and `"replace_subsequence"`
that acts like `"replace"` when the text around your cursor is a
subsequence of the completion (similiar to a fuzzy match). Check [the
documentation](https://zed.dev/docs/configuring-zed#LSP-Insert-Mode) for
more information.

---------

Co-authored-by: João Marcos <marcospb19@hotmail.com>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
frederik-uni 2025-04-02 21:55:03 +02:00 committed by GitHub
parent 108ae0b5b0
commit 07a77792c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 467 additions and 13 deletions

View file

@ -39,7 +39,8 @@ use language::{
LanguageToolchainStore, LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16,
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
language_settings::{
FormatOnSave, Formatter, LanguageSettings, SelectedFormatter, language_settings,
AllLanguageSettings, FormatOnSave, Formatter, LanguageSettings, LspInsertMode,
SelectedFormatter, language_settings,
},
point_to_lsp,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
@ -5151,6 +5152,7 @@ impl LspStore {
&buffer_snapshot,
completions.clone(),
completion_index,
cx,
)
.await
.log_err()
@ -5184,6 +5186,7 @@ impl LspStore {
snapshot: &BufferSnapshot,
completions: Rc<RefCell<Box<[Completion]>>>,
completion_index: usize,
cx: &mut AsyncApp,
) -> Result<()> {
let server_id = server.server_id();
let can_resolve = server
@ -5226,7 +5229,15 @@ impl LspStore {
// language server we currently use that does update `text_edit` in `completionItem/resolve`
// is `typescript-language-server` and they only update `text_edit.new_text`.
// But we should not rely on that.
let edit = parse_completion_text_edit(text_edit, snapshot);
let completion_mode = cx
.read_global(|_: &SettingsStore, cx| {
AllLanguageSettings::get_global(cx)
.defaults
.completions
.lsp_insert_mode
})
.unwrap_or(LspInsertMode::Insert);
let edit = parse_completion_text_edit(text_edit, snapshot, completion_mode);
if let Some((old_range, mut new_text)) = edit {
LineEnding::normalize(&mut new_text);
@ -5482,6 +5493,7 @@ impl LspStore {
&snapshot,
completions.clone(),
completion_index,
cx,
)
.await
.context("resolving completion")?;
@ -7723,7 +7735,16 @@ impl LspStore {
})??;
if let Some(text_edit) = completion.text_edit.as_ref() {
let edit = parse_completion_text_edit(text_edit, &buffer_snapshot);
let completion_mode = cx
.read_global(|_: &SettingsStore, cx| {
AllLanguageSettings::get_global(cx)
.defaults
.completions
.lsp_insert_mode
})
.unwrap_or(LspInsertMode::Insert);
let edit = parse_completion_text_edit(text_edit, &buffer_snapshot, completion_mode);
if let Some((old_range, mut text_edit_new_text)) = edit {
LineEnding::normalize(&mut text_edit_new_text);