Add support for insert_text_mode of a completion (#28171)

I wanted this for CONL (https://conl.dev )'s nascent langauge server,
and it seems like most of the support was already wired up on the LSP
side, so this surfaces it into the editor.

Release Notes:

- Added support for the `insert_text_mode` field of completions from the
language server protocol.
This commit is contained in:
Conrad Irwin 2025-04-07 10:35:11 -06:00 committed by GitHub
parent 5a7222edc5
commit a577a72f69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 91 additions and 4 deletions

View file

@ -112,6 +112,7 @@ impl ContextPickerCompletionProvider {
icon_path: Some(mode.icon().path().into()),
documentation: None,
source: project::CompletionSource::Custom,
insert_text_mode: None,
// This ensures that when a user accepts this completion, the
// completion menu will still be shown after "@category " is
// inserted
@ -163,6 +164,7 @@ impl ContextPickerCompletionProvider {
new_text,
label: CodeLabel::plain(thread_entry.summary.to_string(), None),
documentation: None,
insert_text_mode: None,
source: project::CompletionSource::Custom,
icon_path: Some(icon_for_completion.path().into()),
confirm: Some(confirm_completion_callback(
@ -209,6 +211,7 @@ impl ContextPickerCompletionProvider {
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(IconName::Globe.path().into()),
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
IconName::Globe.path().into(),
url_to_fetch.clone(),
@ -290,6 +293,7 @@ impl ContextPickerCompletionProvider {
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(completion_icon_path),
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
crease_icon_path,
file_name,
@ -352,6 +356,7 @@ impl ContextPickerCompletionProvider {
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(IconName::Code.path().into()),
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
IconName::Code.path().into(),
symbol.name.clone().into(),

View file

@ -127,6 +127,7 @@ impl SlashCommandCompletionProvider {
new_text,
label: command.label(cx),
icon_path: None,
insert_text_mode: None,
confirm,
source: CompletionSource::Custom,
})
@ -228,6 +229,7 @@ impl SlashCommandCompletionProvider {
new_text,
documentation: None,
confirm,
insert_text_mode: None,
source: CompletionSource::Custom,
}
})

View file

@ -315,6 +315,7 @@ impl MessageEditor {
icon_path: None,
confirm: None,
documentation: None,
insert_text_mode: None,
source: CompletionSource::Custom,
}
})

View file

@ -367,6 +367,7 @@ impl ConsoleQueryBarCompletionProvider {
documentation: None,
confirm: None,
source: project::CompletionSource::Custom,
insert_text_mode: None,
})
})
.collect(),
@ -409,6 +410,7 @@ impl ConsoleQueryBarCompletionProvider {
documentation: None,
confirm: None,
source: project::CompletionSource::Custom,
insert_text_mode: None,
})
.collect(),
))

View file

@ -240,6 +240,7 @@ impl CompletionsMenu {
icon_path: None,
documentation: None,
confirm: None,
insert_text_mode: None,
source: CompletionSource::Custom,
})
.collect();

View file

@ -136,7 +136,7 @@ use task::{ResolvedTask, TaskTemplate, TaskVariables};
pub use lsp::CompletionContext;
use lsp::{
CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
InsertTextFormat, LanguageServerId, LanguageServerName,
InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
};
use language::BufferSnapshot;
@ -4442,6 +4442,7 @@ impl Editor {
word_range,
resolved: false,
},
insert_text_mode: Some(InsertTextMode::AS_IS),
confirm: None,
}));
@ -4687,7 +4688,13 @@ impl Editor {
} else {
this.buffer.update(cx, |buffer, cx| {
let edits = ranges.iter().map(|range| (range.clone(), text));
buffer.edit(edits, this.autoindent_mode.clone(), cx);
let auto_indent = if completion.insert_text_mode == Some(InsertTextMode::AS_IS)
{
None
} else {
this.autoindent_mode.clone()
};
buffer.edit(edits, auto_indent, cx);
});
}
for (buffer, edits) in linked_edits {
@ -18637,6 +18644,7 @@ fn snippet_completions(
.description
.clone()
.map(|description| CompletionDocumentation::SingleLine(description.into())),
insert_text_mode: None,
confirm: None,
})
})

View file

@ -10235,6 +10235,62 @@ async fn test_completion_sort(cx: &mut TestAppContext) {
});
}
#[gpui::test]
async fn test_as_is_completions(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
cx.lsp
.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
Ok(Some(lsp::CompletionResponse::Array(vec![
lsp::CompletionItem {
label: "unsafe".into(),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range {
start: lsp::Position {
line: 1,
character: 2,
},
end: lsp::Position {
line: 1,
character: 3,
},
},
new_text: "unsafe".to_string(),
})),
insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
..Default::default()
},
])))
});
cx.set_state("fn a() {}\n");
cx.executor().run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.show_completions(
&ShowCompletions {
trigger: Some("\n".into()),
},
window,
cx,
);
});
cx.executor().run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.confirm_completion(&Default::default(), window, cx)
});
cx.executor().run_until_parked();
cx.assert_editor_state("fn a() {}\n unsafeˇ");
}
#[gpui::test]
async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
init_test(cx, |_| {});

View file

@ -704,8 +704,15 @@ impl LanguageServer {
}),
insert_replace_support: Some(true),
label_details_support: Some(true),
insert_text_mode_support: Some(InsertTextModeSupport {
value_set: vec![
InsertTextMode::AS_IS,
InsertTextMode::ADJUST_INDENTATION,
],
}),
..Default::default()
}),
insert_text_mode: Some(InsertTextMode::ADJUST_INDENTATION),
completion_list: Some(CompletionListCapability {
item_defaults: Some(vec![
"commitCharacters".to_owned(),

View file

@ -8053,6 +8053,7 @@ impl LspStore {
runs: Default::default(),
filter_range: Default::default(),
},
insert_text_mode: None,
icon_path: None,
confirm: None,
}]))),
@ -9342,6 +9343,7 @@ async fn populate_labels_for_completions(
documentation,
old_range: completion.old_range,
new_text: completion.new_text,
insert_text_mode: lsp_completion.insert_text_mode,
source: completion.source,
icon_path: None,
confirm: None,
@ -9356,6 +9358,7 @@ async fn populate_labels_for_completions(
old_range: completion.old_range,
new_text: completion.new_text,
source: completion.source,
insert_text_mode: None,
icon_path: None,
confirm: None,
});

View file

@ -68,8 +68,8 @@ use language::{
language_settings::InlayHintKind, proto::split_operations,
};
use lsp::{
CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServerId,
LanguageServerName, MessageActionItem,
CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, InsertTextMode,
LanguageServerId, LanguageServerName, MessageActionItem,
};
use lsp_command::*;
use lsp_store::{CompletionDocumentation, LspFormatTarget, OpenLspBufferHandle};
@ -392,6 +392,8 @@ pub struct Completion {
pub source: CompletionSource,
/// A path to an icon for this completion that is shown in the menu.
pub icon_path: Option<SharedString>,
/// Whether to adjust indentation (the default) or not.
pub insert_text_mode: Option<InsertTextMode>,
/// An optional callback to invoke when this completion is confirmed.
/// Returns, whether new completions should be retriggered after the current one.
/// If `true` is returned, the editor will show a new completion menu after this completion is confirmed.