diff --git a/crates/agent/src/context_picker/completion_provider.rs b/crates/agent/src/context_picker/completion_provider.rs index a62872e62d..cda7abbe5d 100644 --- a/crates/agent/src/context_picker/completion_provider.rs +++ b/crates/agent/src/context_picker/completion_provider.rs @@ -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(), diff --git a/crates/assistant_context_editor/src/slash_command.rs b/crates/assistant_context_editor/src/slash_command.rs index 481a408f4c..da3e53435b 100644 --- a/crates/assistant_context_editor/src/slash_command.rs +++ b/crates/assistant_context_editor/src/slash_command.rs @@ -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, } }) diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index d5dafd8193..31d25f0054 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -315,6 +315,7 @@ impl MessageEditor { icon_path: None, confirm: None, documentation: None, + insert_text_mode: None, source: CompletionSource::Custom, } }) diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index cccfe68786..5f248ec3c7 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -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(), )) diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 85d3acbc98..fbbfc75af7 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -240,6 +240,7 @@ impl CompletionsMenu { icon_path: None, documentation: None, confirm: None, + insert_text_mode: None, source: CompletionSource::Custom, }) .collect(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1d64ea5a44..5c4b6b90b9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -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, }) }) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 8d986db7f8..77a25d472d 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -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::(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 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, |_| {}); diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index b0abd1067c..22de58f205 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -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(), diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 7f825ec873..7d46754a30 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -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, }); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f378dfd7c9..9e68bcf397 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -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, + /// Whether to adjust indentation (the default) or not. + pub insert_text_mode: Option, /// 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.