Add dedicated actions for LSP
completions insertion mode (#28121)
Adds actions so you can have customized keybindings for `insert` and `replace` modes. And add `shift-enter` as a default for `replace`, this will override the default setting `completions.lsp_insert_mode` which is set to `replace_suffix`, which tries to "smartly" decide whether to replace or insert based on the surrounding text. For those who come from VSCode, if you want to mimic their behavior, you only have to set `completions.lsp_insert_mode` to `insert`. If you want `tab` and `enter` to do different things, you need to remap them, here is an example: ```jsonc [ // ... { "context": "Editor && showing_completions", "bindings": { "enter": "editor::ConfirmCompletionInsert", "tab": "editor::ConfirmCompletionReplace" } }, ] ``` Closes #24577 - [x] Make LSP completion insertion mode decision in guest's machine (host is currently deciding it and not allowing guests to have their own setting for it) - [x] Add shift-enter as a hotkey for `replace` by default. - [x] Test actions. - [x] Respect the setting being specified per language, instead of using the "defaults". - [x] Move `insert_range` of `Completion` to the Lsp variant of `.source`. - [x] Fix broken default, forgotten after https://github.com/zed-industries/zed/pull/27453#pullrequestreview-2736906628, should be `replace_suffix` and not `insert`. Release Notes: - LSP completions: added actions `ConfirmCompletionInsert` and `ConfirmCompletionReplace` that control how completions are inserted, these override `completions.lsp_insert_mode`, by default, `shift-enter` triggers `ConfirmCompletionReplace` which replaces the whole word.
This commit is contained in:
parent
0459b1d303
commit
b15ee1b1cc
17 changed files with 400 additions and 207 deletions
|
@ -532,6 +532,7 @@
|
||||||
"context": "Editor && showing_completions",
|
"context": "Editor && showing_completions",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::ConfirmCompletion",
|
"enter": "editor::ConfirmCompletion",
|
||||||
|
"shift-enter": "editor::ConfirmCompletionReplace",
|
||||||
"tab": "editor::ComposeCompletion"
|
"tab": "editor::ComposeCompletion"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -681,6 +681,7 @@
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::ConfirmCompletion",
|
"enter": "editor::ConfirmCompletion",
|
||||||
|
"shift-enter": "editor::ConfirmCompletionReplace",
|
||||||
"tab": "editor::ComposeCompletion"
|
"tab": "editor::ComposeCompletion"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -106,7 +106,7 @@ impl ContextPickerCompletionProvider {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|mode| {
|
.map(|mode| {
|
||||||
Completion {
|
Completion {
|
||||||
old_range: source_range.clone(),
|
replace_range: source_range.clone(),
|
||||||
new_text: format!("@{} ", mode.mention_prefix()),
|
new_text: format!("@{} ", mode.mention_prefix()),
|
||||||
label: CodeLabel::plain(mode.label().to_string(), None),
|
label: CodeLabel::plain(mode.label().to_string(), None),
|
||||||
icon_path: Some(mode.icon().path().into()),
|
icon_path: Some(mode.icon().path().into()),
|
||||||
|
@ -160,7 +160,7 @@ impl ContextPickerCompletionProvider {
|
||||||
let new_text = MentionLink::for_thread(&thread_entry);
|
let new_text = MentionLink::for_thread(&thread_entry);
|
||||||
let new_text_len = new_text.len();
|
let new_text_len = new_text.len();
|
||||||
Completion {
|
Completion {
|
||||||
old_range: source_range.clone(),
|
replace_range: source_range.clone(),
|
||||||
new_text,
|
new_text,
|
||||||
label: CodeLabel::plain(thread_entry.summary.to_string(), None),
|
label: CodeLabel::plain(thread_entry.summary.to_string(), None),
|
||||||
documentation: None,
|
documentation: None,
|
||||||
|
@ -205,7 +205,7 @@ impl ContextPickerCompletionProvider {
|
||||||
let new_text = MentionLink::for_fetch(&url_to_fetch);
|
let new_text = MentionLink::for_fetch(&url_to_fetch);
|
||||||
let new_text_len = new_text.len();
|
let new_text_len = new_text.len();
|
||||||
Completion {
|
Completion {
|
||||||
old_range: source_range.clone(),
|
replace_range: source_range.clone(),
|
||||||
new_text,
|
new_text,
|
||||||
label: CodeLabel::plain(url_to_fetch.to_string(), None),
|
label: CodeLabel::plain(url_to_fetch.to_string(), None),
|
||||||
documentation: None,
|
documentation: None,
|
||||||
|
@ -287,7 +287,7 @@ impl ContextPickerCompletionProvider {
|
||||||
let new_text = MentionLink::for_file(&file_name, &full_path);
|
let new_text = MentionLink::for_file(&file_name, &full_path);
|
||||||
let new_text_len = new_text.len();
|
let new_text_len = new_text.len();
|
||||||
Completion {
|
Completion {
|
||||||
old_range: source_range.clone(),
|
replace_range: source_range.clone(),
|
||||||
new_text,
|
new_text,
|
||||||
label,
|
label,
|
||||||
documentation: None,
|
documentation: None,
|
||||||
|
@ -350,7 +350,7 @@ impl ContextPickerCompletionProvider {
|
||||||
let new_text = MentionLink::for_symbol(&symbol.name, &full_path);
|
let new_text = MentionLink::for_symbol(&symbol.name, &full_path);
|
||||||
let new_text_len = new_text.len();
|
let new_text_len = new_text.len();
|
||||||
Some(Completion {
|
Some(Completion {
|
||||||
old_range: source_range.clone(),
|
replace_range: source_range.clone(),
|
||||||
new_text,
|
new_text,
|
||||||
label,
|
label,
|
||||||
documentation: None,
|
documentation: None,
|
||||||
|
|
|
@ -120,7 +120,7 @@ impl SlashCommandCompletionProvider {
|
||||||
) as Arc<_>
|
) as Arc<_>
|
||||||
});
|
});
|
||||||
Some(project::Completion {
|
Some(project::Completion {
|
||||||
old_range: name_range.clone(),
|
replace_range: name_range.clone(),
|
||||||
documentation: Some(CompletionDocumentation::SingleLine(
|
documentation: Some(CompletionDocumentation::SingleLine(
|
||||||
command.description().into(),
|
command.description().into(),
|
||||||
)),
|
)),
|
||||||
|
@ -219,7 +219,7 @@ impl SlashCommandCompletionProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
project::Completion {
|
project::Completion {
|
||||||
old_range: if new_argument.replace_previous_arguments {
|
replace_range: if new_argument.replace_previous_arguments {
|
||||||
argument_range.clone()
|
argument_range.clone()
|
||||||
} else {
|
} else {
|
||||||
last_argument_range.clone()
|
last_argument_range.clone()
|
||||||
|
|
|
@ -309,7 +309,7 @@ impl MessageEditor {
|
||||||
.map(|mat| {
|
.map(|mat| {
|
||||||
let (new_text, label) = completion_fn(&mat);
|
let (new_text, label) = completion_fn(&mat);
|
||||||
Completion {
|
Completion {
|
||||||
old_range: range.clone(),
|
replace_range: range.clone(),
|
||||||
new_text,
|
new_text,
|
||||||
label,
|
label,
|
||||||
icon_path: None,
|
icon_path: None,
|
||||||
|
|
|
@ -356,7 +356,7 @@ impl ConsoleQueryBarCompletionProvider {
|
||||||
let variable_value = variables.get(&string_match.string)?;
|
let variable_value = variables.get(&string_match.string)?;
|
||||||
|
|
||||||
Some(project::Completion {
|
Some(project::Completion {
|
||||||
old_range: buffer_position..buffer_position,
|
replace_range: buffer_position..buffer_position,
|
||||||
new_text: string_match.string.clone(),
|
new_text: string_match.string.clone(),
|
||||||
label: CodeLabel {
|
label: CodeLabel {
|
||||||
filter_range: 0..string_match.string.len(),
|
filter_range: 0..string_match.string.len(),
|
||||||
|
@ -428,10 +428,10 @@ impl ConsoleQueryBarCompletionProvider {
|
||||||
let buffer_offset = buffer_position.to_offset(&snapshot);
|
let buffer_offset = buffer_position.to_offset(&snapshot);
|
||||||
let start = buffer_offset - word_bytes_length;
|
let start = buffer_offset - word_bytes_length;
|
||||||
let start = snapshot.anchor_before(start);
|
let start = snapshot.anchor_before(start);
|
||||||
let old_range = start..buffer_position;
|
let replace_range = start..buffer_position;
|
||||||
|
|
||||||
project::Completion {
|
project::Completion {
|
||||||
old_range,
|
replace_range,
|
||||||
new_text,
|
new_text,
|
||||||
label: CodeLabel {
|
label: CodeLabel {
|
||||||
filter_range: 0..completion.label.len(),
|
filter_range: 0..completion.label.len(),
|
||||||
|
|
|
@ -3,6 +3,7 @@ use super::*;
|
||||||
use gpui::{action_as, action_with_deprecated_aliases, actions};
|
use gpui::{action_as, action_with_deprecated_aliases, actions};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use util::serde::default_true;
|
use util::serde::default_true;
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct SelectNext {
|
pub struct SelectNext {
|
||||||
|
@ -262,6 +263,8 @@ actions!(
|
||||||
Cancel,
|
Cancel,
|
||||||
CancelLanguageServerWork,
|
CancelLanguageServerWork,
|
||||||
ConfirmRename,
|
ConfirmRename,
|
||||||
|
ConfirmCompletionInsert,
|
||||||
|
ConfirmCompletionReplace,
|
||||||
ContextMenuFirst,
|
ContextMenuFirst,
|
||||||
ContextMenuLast,
|
ContextMenuLast,
|
||||||
ContextMenuNext,
|
ContextMenuNext,
|
||||||
|
|
|
@ -230,7 +230,7 @@ impl CompletionsMenu {
|
||||||
let completions = choices
|
let completions = choices
|
||||||
.iter()
|
.iter()
|
||||||
.map(|choice| Completion {
|
.map(|choice| Completion {
|
||||||
old_range: selection.start.text_anchor..selection.end.text_anchor,
|
replace_range: selection.start.text_anchor..selection.end.text_anchor,
|
||||||
new_text: choice.to_string(),
|
new_text: choice.to_string(),
|
||||||
label: CodeLabel {
|
label: CodeLabel {
|
||||||
text: choice.to_string(),
|
text: choice.to_string(),
|
||||||
|
|
|
@ -109,8 +109,8 @@ use language::{
|
||||||
IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
|
IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
|
||||||
TransactionId, TreeSitterOptions, WordsQuery,
|
TransactionId, TreeSitterOptions, WordsQuery,
|
||||||
language_settings::{
|
language_settings::{
|
||||||
self, InlayHintSettings, RewrapBehavior, WordsCompletionMode, all_language_settings,
|
self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
|
||||||
language_settings,
|
all_language_settings, language_settings,
|
||||||
},
|
},
|
||||||
point_from_lsp, text_diff_with_options,
|
point_from_lsp, text_diff_with_options,
|
||||||
};
|
};
|
||||||
|
@ -4462,7 +4462,7 @@ impl Editor {
|
||||||
words.remove(&lsp_completion.new_text);
|
words.remove(&lsp_completion.new_text);
|
||||||
}
|
}
|
||||||
completions.extend(words.into_iter().map(|(word, word_range)| Completion {
|
completions.extend(words.into_iter().map(|(word, word_range)| Completion {
|
||||||
old_range: old_range.clone(),
|
replace_range: old_range.clone(),
|
||||||
new_text: word.clone(),
|
new_text: word.clone(),
|
||||||
label: CodeLabel::plain(word, None),
|
label: CodeLabel::plain(word, None),
|
||||||
icon_path: None,
|
icon_path: None,
|
||||||
|
@ -4569,6 +4569,26 @@ impl Editor {
|
||||||
self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
|
self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn confirm_completion_insert(
|
||||||
|
&mut self,
|
||||||
|
_: &ConfirmCompletionInsert,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Option<Task<Result<()>>> {
|
||||||
|
self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
|
||||||
|
self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn confirm_completion_replace(
|
||||||
|
&mut self,
|
||||||
|
_: &ConfirmCompletionReplace,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Option<Task<Result<()>>> {
|
||||||
|
self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
|
||||||
|
self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn compose_completion(
|
pub fn compose_completion(
|
||||||
&mut self,
|
&mut self,
|
||||||
action: &ComposeCompletion,
|
action: &ComposeCompletion,
|
||||||
|
@ -4588,10 +4608,8 @@ impl Editor {
|
||||||
) -> Option<Task<Result<()>>> {
|
) -> Option<Task<Result<()>>> {
|
||||||
use language::ToOffset as _;
|
use language::ToOffset as _;
|
||||||
|
|
||||||
let completions_menu =
|
let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
|
||||||
if let CodeContextMenu::Completions(menu) = self.hide_context_menu(window, cx)? {
|
else {
|
||||||
menu
|
|
||||||
} else {
|
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4622,9 +4640,12 @@ impl Editor {
|
||||||
new_text = completion.new_text.clone();
|
new_text = completion.new_text.clone();
|
||||||
};
|
};
|
||||||
let selections = self.selections.all::<usize>(cx);
|
let selections = self.selections.all::<usize>(cx);
|
||||||
|
|
||||||
|
let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
|
||||||
let buffer = buffer_handle.read(cx);
|
let buffer = buffer_handle.read(cx);
|
||||||
let old_range = completion.old_range.to_offset(buffer);
|
let old_text = buffer
|
||||||
let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
|
.text_for_range(replace_range.clone())
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
let newest_selection = self.selections.newest_anchor();
|
let newest_selection = self.selections.newest_anchor();
|
||||||
if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
|
if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
|
||||||
|
@ -4635,8 +4656,8 @@ impl Editor {
|
||||||
.start
|
.start
|
||||||
.text_anchor
|
.text_anchor
|
||||||
.to_offset(buffer)
|
.to_offset(buffer)
|
||||||
.saturating_sub(old_range.start);
|
.saturating_sub(replace_range.start);
|
||||||
let lookahead = old_range
|
let lookahead = replace_range
|
||||||
.end
|
.end
|
||||||
.saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
|
.saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
|
||||||
let mut common_prefix_len = 0;
|
let mut common_prefix_len = 0;
|
||||||
|
@ -4665,8 +4686,8 @@ impl Editor {
|
||||||
ranges.clear();
|
ranges.clear();
|
||||||
ranges.extend(selections.iter().map(|s| {
|
ranges.extend(selections.iter().map(|s| {
|
||||||
if s.id == newest_selection.id {
|
if s.id == newest_selection.id {
|
||||||
range_to_replace = Some(old_range.clone());
|
range_to_replace = Some(replace_range.clone());
|
||||||
old_range.clone()
|
replace_range.clone()
|
||||||
} else {
|
} else {
|
||||||
s.start..s.end
|
s.start..s.end
|
||||||
}
|
}
|
||||||
|
@ -17980,6 +18001,81 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Consider user intent and default settings
|
||||||
|
fn choose_completion_range(
|
||||||
|
completion: &Completion,
|
||||||
|
intent: CompletionIntent,
|
||||||
|
buffer: &Entity<Buffer>,
|
||||||
|
cx: &mut Context<Editor>,
|
||||||
|
) -> Range<usize> {
|
||||||
|
fn should_replace(
|
||||||
|
completion: &Completion,
|
||||||
|
insert_range: &Range<text::Anchor>,
|
||||||
|
intent: CompletionIntent,
|
||||||
|
completion_mode_setting: LspInsertMode,
|
||||||
|
buffer: &Buffer,
|
||||||
|
) -> bool {
|
||||||
|
// specific actions take precedence over settings
|
||||||
|
match intent {
|
||||||
|
CompletionIntent::CompleteWithInsert => return false,
|
||||||
|
CompletionIntent::CompleteWithReplace => return true,
|
||||||
|
CompletionIntent::Complete | CompletionIntent::Compose => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
match completion_mode_setting {
|
||||||
|
LspInsertMode::Insert => false,
|
||||||
|
LspInsertMode::Replace => true,
|
||||||
|
LspInsertMode::ReplaceSubsequence => {
|
||||||
|
let mut text_to_replace = buffer.chars_for_range(
|
||||||
|
buffer.anchor_before(completion.replace_range.start)
|
||||||
|
..buffer.anchor_after(completion.replace_range.end),
|
||||||
|
);
|
||||||
|
let mut completion_text = completion.new_text.chars();
|
||||||
|
|
||||||
|
// is `text_to_replace` a subsequence of `completion_text`
|
||||||
|
text_to_replace
|
||||||
|
.all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
|
||||||
|
}
|
||||||
|
LspInsertMode::ReplaceSuffix => {
|
||||||
|
let range_after_cursor = insert_range.end..completion.replace_range.end;
|
||||||
|
|
||||||
|
let text_after_cursor = buffer
|
||||||
|
.text_for_range(
|
||||||
|
buffer.anchor_before(range_after_cursor.start)
|
||||||
|
..buffer.anchor_after(range_after_cursor.end),
|
||||||
|
)
|
||||||
|
.collect::<String>();
|
||||||
|
completion.new_text.ends_with(&text_after_cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
|
|
||||||
|
if let CompletionSource::Lsp {
|
||||||
|
insert_range: Some(insert_range),
|
||||||
|
..
|
||||||
|
} = &completion.source
|
||||||
|
{
|
||||||
|
let completion_mode_setting =
|
||||||
|
language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
|
||||||
|
.completions
|
||||||
|
.lsp_insert_mode;
|
||||||
|
|
||||||
|
if !should_replace(
|
||||||
|
completion,
|
||||||
|
&insert_range,
|
||||||
|
intent,
|
||||||
|
completion_mode_setting,
|
||||||
|
buffer,
|
||||||
|
) {
|
||||||
|
return insert_range.to_offset(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completion.replace_range.to_offset(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
fn insert_extra_newline_brackets(
|
fn insert_extra_newline_brackets(
|
||||||
buffer: &MultiBufferSnapshot,
|
buffer: &MultiBufferSnapshot,
|
||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
|
@ -18701,9 +18797,10 @@ fn snippet_completions(
|
||||||
end: lsp_end,
|
end: lsp_end,
|
||||||
};
|
};
|
||||||
Some(Completion {
|
Some(Completion {
|
||||||
old_range: range,
|
replace_range: range,
|
||||||
new_text: snippet.body.clone(),
|
new_text: snippet.body.clone(),
|
||||||
source: CompletionSource::Lsp {
|
source: CompletionSource::Lsp {
|
||||||
|
insert_range: None,
|
||||||
server_id: LanguageServerId(usize::MAX),
|
server_id: LanguageServerId(usize::MAX),
|
||||||
resolved: true,
|
resolved: true,
|
||||||
lsp_completion: Box::new(lsp::CompletionItem {
|
lsp_completion: Box::new(lsp::CompletionItem {
|
||||||
|
|
|
@ -9218,7 +9218,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
initial_state: String,
|
initial_state: String,
|
||||||
buffer_marked_text: String,
|
buffer_marked_text: String,
|
||||||
completion_text: &'static str,
|
completion_text: &'static str,
|
||||||
expected_with_insertion_mode: String,
|
expected_with_insert_mode: String,
|
||||||
expected_with_replace_mode: String,
|
expected_with_replace_mode: String,
|
||||||
expected_with_replace_subsequence_mode: String,
|
expected_with_replace_subsequence_mode: String,
|
||||||
expected_with_replace_suffix_mode: String,
|
expected_with_replace_suffix_mode: String,
|
||||||
|
@ -9230,7 +9230,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
initial_state: "before ediˇ after".into(),
|
initial_state: "before ediˇ after".into(),
|
||||||
buffer_marked_text: "before <edi|> after".into(),
|
buffer_marked_text: "before <edi|> after".into(),
|
||||||
completion_text: "editor",
|
completion_text: "editor",
|
||||||
expected_with_insertion_mode: "before editorˇ after".into(),
|
expected_with_insert_mode: "before editorˇ after".into(),
|
||||||
expected_with_replace_mode: "before editorˇ after".into(),
|
expected_with_replace_mode: "before editorˇ after".into(),
|
||||||
expected_with_replace_subsequence_mode: "before editorˇ after".into(),
|
expected_with_replace_subsequence_mode: "before editorˇ after".into(),
|
||||||
expected_with_replace_suffix_mode: "before editorˇ after".into(),
|
expected_with_replace_suffix_mode: "before editorˇ after".into(),
|
||||||
|
@ -9240,7 +9240,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
initial_state: "before ediˇtor after".into(),
|
initial_state: "before ediˇtor after".into(),
|
||||||
buffer_marked_text: "before <edi|tor> after".into(),
|
buffer_marked_text: "before <edi|tor> after".into(),
|
||||||
completion_text: "editor",
|
completion_text: "editor",
|
||||||
expected_with_insertion_mode: "before editorˇtor after".into(),
|
expected_with_insert_mode: "before editorˇtor after".into(),
|
||||||
expected_with_replace_mode: "before ediˇtor after".into(),
|
expected_with_replace_mode: "before ediˇtor after".into(),
|
||||||
expected_with_replace_subsequence_mode: "before ediˇtor after".into(),
|
expected_with_replace_subsequence_mode: "before ediˇtor after".into(),
|
||||||
expected_with_replace_suffix_mode: "before ediˇtor after".into(),
|
expected_with_replace_suffix_mode: "before ediˇtor after".into(),
|
||||||
|
@ -9250,7 +9250,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
initial_state: "before torˇ after".into(),
|
initial_state: "before torˇ after".into(),
|
||||||
buffer_marked_text: "before <tor|> after".into(),
|
buffer_marked_text: "before <tor|> after".into(),
|
||||||
completion_text: "editor",
|
completion_text: "editor",
|
||||||
expected_with_insertion_mode: "before editorˇ after".into(),
|
expected_with_insert_mode: "before editorˇ after".into(),
|
||||||
expected_with_replace_mode: "before editorˇ after".into(),
|
expected_with_replace_mode: "before editorˇ after".into(),
|
||||||
expected_with_replace_subsequence_mode: "before editorˇ after".into(),
|
expected_with_replace_subsequence_mode: "before editorˇ after".into(),
|
||||||
expected_with_replace_suffix_mode: "before editorˇ after".into(),
|
expected_with_replace_suffix_mode: "before editorˇ after".into(),
|
||||||
|
@ -9260,7 +9260,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
initial_state: "before ˇtor after".into(),
|
initial_state: "before ˇtor after".into(),
|
||||||
buffer_marked_text: "before <|tor> after".into(),
|
buffer_marked_text: "before <|tor> after".into(),
|
||||||
completion_text: "editor",
|
completion_text: "editor",
|
||||||
expected_with_insertion_mode: "before editorˇtor after".into(),
|
expected_with_insert_mode: "before editorˇtor after".into(),
|
||||||
expected_with_replace_mode: "before editorˇ after".into(),
|
expected_with_replace_mode: "before editorˇ after".into(),
|
||||||
expected_with_replace_subsequence_mode: "before editorˇ after".into(),
|
expected_with_replace_subsequence_mode: "before editorˇ after".into(),
|
||||||
expected_with_replace_suffix_mode: "before editorˇ after".into(),
|
expected_with_replace_suffix_mode: "before editorˇ after".into(),
|
||||||
|
@ -9270,7 +9270,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
initial_state: "pˇfield: bool".into(),
|
initial_state: "pˇfield: bool".into(),
|
||||||
buffer_marked_text: "<p|field>: bool".into(),
|
buffer_marked_text: "<p|field>: bool".into(),
|
||||||
completion_text: "pub ",
|
completion_text: "pub ",
|
||||||
expected_with_insertion_mode: "pub ˇfield: bool".into(),
|
expected_with_insert_mode: "pub ˇfield: bool".into(),
|
||||||
expected_with_replace_mode: "pub ˇ: bool".into(),
|
expected_with_replace_mode: "pub ˇ: bool".into(),
|
||||||
expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
|
expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
|
||||||
expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
|
expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
|
||||||
|
@ -9280,7 +9280,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
initial_state: "[element_ˇelement_2]".into(),
|
initial_state: "[element_ˇelement_2]".into(),
|
||||||
buffer_marked_text: "[<element_|element_2>]".into(),
|
buffer_marked_text: "[<element_|element_2>]".into(),
|
||||||
completion_text: "element_1",
|
completion_text: "element_1",
|
||||||
expected_with_insertion_mode: "[element_1ˇelement_2]".into(),
|
expected_with_insert_mode: "[element_1ˇelement_2]".into(),
|
||||||
expected_with_replace_mode: "[element_1ˇ]".into(),
|
expected_with_replace_mode: "[element_1ˇ]".into(),
|
||||||
expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
|
expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
|
||||||
expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
|
expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
|
||||||
|
@ -9290,7 +9290,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
initial_state: "[elˇelement]".into(),
|
initial_state: "[elˇelement]".into(),
|
||||||
buffer_marked_text: "[<el|element>]".into(),
|
buffer_marked_text: "[<el|element>]".into(),
|
||||||
completion_text: "element",
|
completion_text: "element",
|
||||||
expected_with_insertion_mode: "[elementˇelement]".into(),
|
expected_with_insert_mode: "[elementˇelement]".into(),
|
||||||
expected_with_replace_mode: "[elˇement]".into(),
|
expected_with_replace_mode: "[elˇement]".into(),
|
||||||
expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
|
expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
|
||||||
expected_with_replace_suffix_mode: "[elˇement]".into(),
|
expected_with_replace_suffix_mode: "[elˇement]".into(),
|
||||||
|
@ -9300,7 +9300,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
initial_state: "SubˇError".into(),
|
initial_state: "SubˇError".into(),
|
||||||
buffer_marked_text: "<Sub|Error>".into(),
|
buffer_marked_text: "<Sub|Error>".into(),
|
||||||
completion_text: "SubscriptionError",
|
completion_text: "SubscriptionError",
|
||||||
expected_with_insertion_mode: "SubscriptionErrorˇError".into(),
|
expected_with_insert_mode: "SubscriptionErrorˇError".into(),
|
||||||
expected_with_replace_mode: "SubscriptionErrorˇ".into(),
|
expected_with_replace_mode: "SubscriptionErrorˇ".into(),
|
||||||
expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
|
expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
|
||||||
expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
|
expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
|
||||||
|
@ -9310,7 +9310,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
initial_state: "SubˇErr".into(),
|
initial_state: "SubˇErr".into(),
|
||||||
buffer_marked_text: "<Sub|Err>".into(),
|
buffer_marked_text: "<Sub|Err>".into(),
|
||||||
completion_text: "SubscriptionError",
|
completion_text: "SubscriptionError",
|
||||||
expected_with_insertion_mode: "SubscriptionErrorˇErr".into(),
|
expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
|
||||||
expected_with_replace_mode: "SubscriptionErrorˇ".into(),
|
expected_with_replace_mode: "SubscriptionErrorˇ".into(),
|
||||||
expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
|
expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
|
||||||
expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
|
expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
|
||||||
|
@ -9320,7 +9320,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
initial_state: "Suˇscrirr".into(),
|
initial_state: "Suˇscrirr".into(),
|
||||||
buffer_marked_text: "<Su|scrirr>".into(),
|
buffer_marked_text: "<Su|scrirr>".into(),
|
||||||
completion_text: "SubscriptionError",
|
completion_text: "SubscriptionError",
|
||||||
expected_with_insertion_mode: "SubscriptionErrorˇscrirr".into(),
|
expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
|
||||||
expected_with_replace_mode: "SubscriptionErrorˇ".into(),
|
expected_with_replace_mode: "SubscriptionErrorˇ".into(),
|
||||||
expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
|
expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
|
||||||
expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
|
expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
|
||||||
|
@ -9330,7 +9330,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
initial_state: "foo(indˇix)".into(),
|
initial_state: "foo(indˇix)".into(),
|
||||||
buffer_marked_text: "foo(<ind|ix>)".into(),
|
buffer_marked_text: "foo(<ind|ix>)".into(),
|
||||||
completion_text: "node_index",
|
completion_text: "node_index",
|
||||||
expected_with_insertion_mode: "foo(node_indexˇix)".into(),
|
expected_with_insert_mode: "foo(node_indexˇix)".into(),
|
||||||
expected_with_replace_mode: "foo(node_indexˇ)".into(),
|
expected_with_replace_mode: "foo(node_indexˇ)".into(),
|
||||||
expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
|
expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
|
||||||
expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
|
expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
|
||||||
|
@ -9339,7 +9339,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
|
|
||||||
for run in runs {
|
for run in runs {
|
||||||
let run_variations = [
|
let run_variations = [
|
||||||
(LspInsertMode::Insert, run.expected_with_insertion_mode),
|
(LspInsertMode::Insert, run.expected_with_insert_mode),
|
||||||
(LspInsertMode::Replace, run.expected_with_replace_mode),
|
(LspInsertMode::Replace, run.expected_with_replace_mode),
|
||||||
(
|
(
|
||||||
LspInsertMode::ReplaceSubsequence,
|
LspInsertMode::ReplaceSubsequence,
|
||||||
|
@ -9395,6 +9395,98 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
let mut cx = EditorLspTestContext::new_rust(
|
||||||
|
lsp::ServerCapabilities {
|
||||||
|
completion_provider: Some(lsp::CompletionOptions {
|
||||||
|
resolve_provider: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let initial_state = "SubˇError";
|
||||||
|
let buffer_marked_text = "<Sub|Error>";
|
||||||
|
let completion_text = "SubscriptionError";
|
||||||
|
let expected_with_insert_mode = "SubscriptionErrorˇError";
|
||||||
|
let expected_with_replace_mode = "SubscriptionErrorˇ";
|
||||||
|
|
||||||
|
update_test_language_settings(&mut cx, |settings| {
|
||||||
|
settings.defaults.completions = Some(CompletionSettings {
|
||||||
|
words: WordsCompletionMode::Disabled,
|
||||||
|
// set the opposite here to ensure that the action is overriding the default behavior
|
||||||
|
lsp_insert_mode: LspInsertMode::Insert,
|
||||||
|
lsp: true,
|
||||||
|
lsp_fetch_timeout_ms: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.set_state(initial_state);
|
||||||
|
cx.update_editor(|editor, window, cx| {
|
||||||
|
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
let counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
handle_completion_request_with_insert_and_replace(
|
||||||
|
&mut cx,
|
||||||
|
&buffer_marked_text,
|
||||||
|
vec![completion_text],
|
||||||
|
counter.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
cx.condition(|editor, _| editor.context_menu_visible())
|
||||||
|
.await;
|
||||||
|
assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
|
||||||
|
|
||||||
|
let apply_additional_edits = cx.update_editor(|editor, window, cx| {
|
||||||
|
editor
|
||||||
|
.confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
cx.assert_editor_state(&expected_with_replace_mode);
|
||||||
|
handle_resolve_completion_request(&mut cx, None).await;
|
||||||
|
apply_additional_edits.await.unwrap();
|
||||||
|
|
||||||
|
update_test_language_settings(&mut cx, |settings| {
|
||||||
|
settings.defaults.completions = Some(CompletionSettings {
|
||||||
|
words: WordsCompletionMode::Disabled,
|
||||||
|
// set the opposite here to ensure that the action is overriding the default behavior
|
||||||
|
lsp_insert_mode: LspInsertMode::Replace,
|
||||||
|
lsp: true,
|
||||||
|
lsp_fetch_timeout_ms: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.set_state(initial_state);
|
||||||
|
cx.update_editor(|editor, window, cx| {
|
||||||
|
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||||
|
});
|
||||||
|
handle_completion_request_with_insert_and_replace(
|
||||||
|
&mut cx,
|
||||||
|
&buffer_marked_text,
|
||||||
|
vec![completion_text],
|
||||||
|
counter.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
cx.condition(|editor, _| editor.context_menu_visible())
|
||||||
|
.await;
|
||||||
|
assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
|
||||||
|
|
||||||
|
let apply_additional_edits = cx.update_editor(|editor, window, cx| {
|
||||||
|
editor
|
||||||
|
.confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
cx.assert_editor_state(&expected_with_insert_mode);
|
||||||
|
handle_resolve_completion_request(&mut cx, None).await;
|
||||||
|
apply_additional_edits.await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_completion(cx: &mut TestAppContext) {
|
async fn test_completion(cx: &mut TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
|
|
|
@ -461,6 +461,20 @@ impl EditorElement {
|
||||||
cx.propagate();
|
cx.propagate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
register_action(editor, window, |editor, action, window, cx| {
|
||||||
|
if let Some(task) = editor.confirm_completion_replace(action, window, cx) {
|
||||||
|
task.detach_and_notify_err(window, cx);
|
||||||
|
} else {
|
||||||
|
cx.propagate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
register_action(editor, window, |editor, action, window, cx| {
|
||||||
|
if let Some(task) = editor.confirm_completion_insert(action, window, cx) {
|
||||||
|
task.detach_and_notify_err(window, cx);
|
||||||
|
} else {
|
||||||
|
cx.propagate();
|
||||||
|
}
|
||||||
|
});
|
||||||
register_action(editor, window, |editor, action, window, cx| {
|
register_action(editor, window, |editor, action, window, cx| {
|
||||||
if let Some(task) = editor.compose_completion(action, window, cx) {
|
if let Some(task) = editor.compose_completion(action, window, cx) {
|
||||||
task.detach_and_notify_err(window, cx);
|
task.detach_and_notify_err(window, cx);
|
||||||
|
|
|
@ -370,7 +370,7 @@ fn default_words_completion_mode() -> WordsCompletionMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_lsp_insert_mode() -> LspInsertMode {
|
fn default_lsp_insert_mode() -> LspInsertMode {
|
||||||
LspInsertMode::Insert
|
LspInsertMode::ReplaceSuffix
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_lsp_fetch_timeout_ms() -> u64 {
|
fn default_lsp_fetch_timeout_ms() -> u64 {
|
||||||
|
|
|
@ -17,9 +17,7 @@ use gpui::{App, AsyncApp, Entity};
|
||||||
use language::{
|
use language::{
|
||||||
Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, OffsetRangeExt, PointUtf16,
|
Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, OffsetRangeExt, PointUtf16,
|
||||||
ToOffset, ToPointUtf16, Transaction, Unclipped,
|
ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||||
language_settings::{
|
language_settings::{InlayHintKind, LanguageSettings, language_settings},
|
||||||
AllLanguageSettings, InlayHintKind, LanguageSettings, LspInsertMode, language_settings,
|
|
||||||
},
|
|
||||||
point_from_lsp, point_to_lsp,
|
point_from_lsp, point_to_lsp,
|
||||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||||
range_from_lsp, range_to_lsp,
|
range_from_lsp, range_to_lsp,
|
||||||
|
@ -30,7 +28,6 @@ use lsp::{
|
||||||
LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities, OneOf, RenameOptions,
|
LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities, OneOf, RenameOptions,
|
||||||
ServerCapabilities,
|
ServerCapabilities,
|
||||||
};
|
};
|
||||||
use settings::Settings as _;
|
|
||||||
use signature_help::{lsp_to_proto_signature, proto_to_lsp_signature};
|
use signature_help::{lsp_to_proto_signature, proto_to_lsp_signature};
|
||||||
use std::{cmp::Reverse, mem, ops::Range, path::Path, sync::Arc};
|
use std::{cmp::Reverse, mem, ops::Range, path::Path, sync::Arc};
|
||||||
use text::{BufferId, LineEnding};
|
use text::{BufferId, LineEnding};
|
||||||
|
@ -2161,7 +2158,7 @@ impl LspCommand for GetCompletions {
|
||||||
.map(Arc::new);
|
.map(Arc::new);
|
||||||
|
|
||||||
let mut completion_edits = Vec::new();
|
let mut completion_edits = Vec::new();
|
||||||
buffer.update(&mut cx, |buffer, cx| {
|
buffer.update(&mut cx, |buffer, _cx| {
|
||||||
let snapshot = buffer.snapshot();
|
let snapshot = buffer.snapshot();
|
||||||
let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
|
let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
|
||||||
|
|
||||||
|
@ -2198,21 +2195,11 @@ impl LspCommand for GetCompletions {
|
||||||
// If the language server provides a range to overwrite, then
|
// If the language server provides a range to overwrite, then
|
||||||
// check that the range is valid.
|
// check that the range is valid.
|
||||||
Some(completion_text_edit) => {
|
Some(completion_text_edit) => {
|
||||||
let completion_mode = AllLanguageSettings::get_global(cx)
|
match parse_completion_text_edit(&completion_text_edit, &snapshot) {
|
||||||
.defaults
|
|
||||||
.completions
|
|
||||||
.lsp_insert_mode;
|
|
||||||
|
|
||||||
match parse_completion_text_edit(
|
|
||||||
&completion_text_edit,
|
|
||||||
&snapshot,
|
|
||||||
completion_mode,
|
|
||||||
) {
|
|
||||||
Some(edit) => edit,
|
Some(edit) => edit,
|
||||||
None => return false,
|
None => return false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the language server does not provide a range, then infer
|
// If the language server does not provide a range, then infer
|
||||||
// the range based on the syntax tree.
|
// the range based on the syntax tree.
|
||||||
None => {
|
None => {
|
||||||
|
@ -2264,7 +2251,12 @@ impl LspCommand for GetCompletions {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap_or(&lsp_completion.label)
|
.unwrap_or(&lsp_completion.label)
|
||||||
.clone();
|
.clone();
|
||||||
(range, text)
|
|
||||||
|
ParsedCompletionEdit {
|
||||||
|
replace_range: range,
|
||||||
|
insert_range: None,
|
||||||
|
new_text: text,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2280,8 +2272,8 @@ impl LspCommand for GetCompletions {
|
||||||
Ok(completions
|
Ok(completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(completion_edits)
|
.zip(completion_edits)
|
||||||
.map(|(mut lsp_completion, (old_range, mut new_text))| {
|
.map(|(mut lsp_completion, mut edit)| {
|
||||||
LineEnding::normalize(&mut new_text);
|
LineEnding::normalize(&mut edit.new_text);
|
||||||
if lsp_completion.data.is_none() {
|
if lsp_completion.data.is_none() {
|
||||||
if let Some(default_data) = lsp_defaults
|
if let Some(default_data) = lsp_defaults
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -2293,9 +2285,10 @@ impl LspCommand for GetCompletions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CoreCompletion {
|
CoreCompletion {
|
||||||
old_range,
|
replace_range: edit.replace_range,
|
||||||
new_text,
|
new_text: edit.new_text,
|
||||||
source: CompletionSource::Lsp {
|
source: CompletionSource::Lsp {
|
||||||
|
insert_range: edit.insert_range,
|
||||||
server_id,
|
server_id,
|
||||||
lsp_completion: Box::new(lsp_completion),
|
lsp_completion: Box::new(lsp_completion),
|
||||||
lsp_defaults: lsp_defaults.clone(),
|
lsp_defaults: lsp_defaults.clone(),
|
||||||
|
@ -2385,91 +2378,53 @@ impl LspCommand for GetCompletions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ParsedCompletionEdit {
|
||||||
|
pub replace_range: Range<Anchor>,
|
||||||
|
pub insert_range: Option<Range<Anchor>>,
|
||||||
|
pub new_text: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_completion_text_edit(
|
pub(crate) fn parse_completion_text_edit(
|
||||||
edit: &lsp::CompletionTextEdit,
|
edit: &lsp::CompletionTextEdit,
|
||||||
snapshot: &BufferSnapshot,
|
snapshot: &BufferSnapshot,
|
||||||
completion_mode: LspInsertMode,
|
) -> Option<ParsedCompletionEdit> {
|
||||||
) -> Option<(Range<Anchor>, String)> {
|
let (replace_range, insert_range, new_text) = match edit {
|
||||||
match edit {
|
lsp::CompletionTextEdit::Edit(edit) => (edit.range, None, &edit.new_text),
|
||||||
lsp::CompletionTextEdit::Edit(edit) => {
|
|
||||||
let range = range_from_lsp(edit.range);
|
|
||||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
|
||||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
|
||||||
if start != range.start.0 || end != range.end.0 {
|
|
||||||
log::info!("completion out of expected range");
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some((
|
|
||||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
|
||||||
edit.new_text.clone(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lsp::CompletionTextEdit::InsertAndReplace(edit) => {
|
lsp::CompletionTextEdit::InsertAndReplace(edit) => {
|
||||||
let replace = match completion_mode {
|
(edit.replace, Some(edit.insert), &edit.new_text)
|
||||||
LspInsertMode::Insert => false,
|
|
||||||
LspInsertMode::Replace => true,
|
|
||||||
LspInsertMode::ReplaceSubsequence => {
|
|
||||||
let range_to_replace = range_from_lsp(edit.replace);
|
|
||||||
|
|
||||||
let start = snapshot.clip_point_utf16(range_to_replace.start, Bias::Left);
|
|
||||||
let end = snapshot.clip_point_utf16(range_to_replace.end, Bias::Left);
|
|
||||||
if start != range_to_replace.start.0 || end != range_to_replace.end.0 {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
let mut completion_text = edit.new_text.chars();
|
|
||||||
|
|
||||||
let mut text_to_replace = snapshot.chars_for_range(
|
|
||||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
|
||||||
);
|
|
||||||
|
|
||||||
// is `text_to_replace` a subsequence of `completion_text`
|
|
||||||
text_to_replace.all(|needle_ch| {
|
|
||||||
completion_text.any(|haystack_ch| haystack_ch == needle_ch)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LspInsertMode::ReplaceSuffix => {
|
|
||||||
let range_after_cursor = lsp::Range {
|
|
||||||
start: edit.insert.end,
|
|
||||||
end: edit.replace.end,
|
|
||||||
};
|
|
||||||
let range_after_cursor = range_from_lsp(range_after_cursor);
|
|
||||||
|
|
||||||
let start = snapshot.clip_point_utf16(range_after_cursor.start, Bias::Left);
|
|
||||||
let end = snapshot.clip_point_utf16(range_after_cursor.end, Bias::Left);
|
|
||||||
if start != range_after_cursor.start.0 || end != range_after_cursor.end.0 {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
let text_after_cursor = snapshot
|
|
||||||
.text_for_range(
|
|
||||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
|
||||||
)
|
|
||||||
.collect::<String>();
|
|
||||||
edit.new_text.ends_with(&text_after_cursor)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let range = range_from_lsp(match replace {
|
let replace_range = {
|
||||||
true => edit.replace,
|
let range = range_from_lsp(replace_range);
|
||||||
false => edit.insert,
|
|
||||||
});
|
|
||||||
|
|
||||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||||
if start != range.start.0 || end != range.end.0 {
|
if start != range.start.0 || end != range.end.0 {
|
||||||
log::info!("completion out of expected range");
|
log::info!("completion out of expected range");
|
||||||
None
|
return None;
|
||||||
} else {
|
|
||||||
Some((
|
|
||||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
|
||||||
edit.new_text.clone(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
snapshot.anchor_before(start)..snapshot.anchor_after(end)
|
||||||
|
};
|
||||||
|
|
||||||
|
let insert_range = match insert_range {
|
||||||
|
None => None,
|
||||||
|
Some(insert_range) => {
|
||||||
|
let range = range_from_lsp(insert_range);
|
||||||
|
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||||
|
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||||
|
if start != range.start.0 || end != range.end.0 {
|
||||||
|
log::info!("completion (insert) out of expected range");
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
Some(snapshot.anchor_before(start)..snapshot.anchor_after(end))
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(ParsedCompletionEdit {
|
||||||
|
insert_range: insert_range,
|
||||||
|
replace_range: replace_range,
|
||||||
|
new_text: new_text.clone(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
|
|
|
@ -39,8 +39,7 @@ use language::{
|
||||||
LanguageToolchainStore, LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16,
|
LanguageToolchainStore, LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16,
|
||||||
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||||
language_settings::{
|
language_settings::{
|
||||||
AllLanguageSettings, FormatOnSave, Formatter, LanguageSettings, LspInsertMode,
|
FormatOnSave, Formatter, LanguageSettings, SelectedFormatter, language_settings,
|
||||||
SelectedFormatter, language_settings,
|
|
||||||
},
|
},
|
||||||
point_to_lsp,
|
point_to_lsp,
|
||||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||||
|
@ -5136,7 +5135,6 @@ impl LspStore {
|
||||||
&buffer_snapshot,
|
&buffer_snapshot,
|
||||||
completions.clone(),
|
completions.clone(),
|
||||||
completion_index,
|
completion_index,
|
||||||
cx,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.log_err()
|
.log_err()
|
||||||
|
@ -5170,7 +5168,6 @@ impl LspStore {
|
||||||
snapshot: &BufferSnapshot,
|
snapshot: &BufferSnapshot,
|
||||||
completions: Rc<RefCell<Box<[Completion]>>>,
|
completions: Rc<RefCell<Box<[Completion]>>>,
|
||||||
completion_index: usize,
|
completion_index: usize,
|
||||||
cx: &mut AsyncApp,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let server_id = server.server_id();
|
let server_id = server.server_id();
|
||||||
let can_resolve = server
|
let can_resolve = server
|
||||||
|
@ -5208,41 +5205,38 @@ impl LspStore {
|
||||||
};
|
};
|
||||||
let resolved_completion = request.await?;
|
let resolved_completion = request.await?;
|
||||||
|
|
||||||
|
let mut updated_insert_range = None;
|
||||||
if let Some(text_edit) = resolved_completion.text_edit.as_ref() {
|
if let Some(text_edit) = resolved_completion.text_edit.as_ref() {
|
||||||
// Technically we don't have to parse the whole `text_edit`, since the only
|
// Technically we don't have to parse the whole `text_edit`, since the only
|
||||||
// language server we currently use that does update `text_edit` in `completionItem/resolve`
|
// 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`.
|
// is `typescript-language-server` and they only update `text_edit.new_text`.
|
||||||
// But we should not rely on that.
|
// But we should not rely on that.
|
||||||
let completion_mode = cx
|
let edit = parse_completion_text_edit(text_edit, snapshot);
|
||||||
.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 {
|
if let Some(mut parsed_edit) = edit {
|
||||||
LineEnding::normalize(&mut new_text);
|
LineEnding::normalize(&mut parsed_edit.new_text);
|
||||||
|
|
||||||
let mut completions = completions.borrow_mut();
|
let mut completions = completions.borrow_mut();
|
||||||
let completion = &mut completions[completion_index];
|
let completion = &mut completions[completion_index];
|
||||||
|
|
||||||
completion.new_text = new_text;
|
completion.new_text = parsed_edit.new_text;
|
||||||
completion.old_range = old_range;
|
completion.replace_range = parsed_edit.replace_range;
|
||||||
|
|
||||||
|
updated_insert_range = parsed_edit.insert_range;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut completions = completions.borrow_mut();
|
let mut completions = completions.borrow_mut();
|
||||||
let completion = &mut completions[completion_index];
|
let completion = &mut completions[completion_index];
|
||||||
if let CompletionSource::Lsp {
|
if let CompletionSource::Lsp {
|
||||||
|
insert_range,
|
||||||
lsp_completion,
|
lsp_completion,
|
||||||
resolved,
|
resolved,
|
||||||
server_id: completion_server_id,
|
server_id: completion_server_id,
|
||||||
..
|
..
|
||||||
} = &mut completion.source
|
} = &mut completion.source
|
||||||
{
|
{
|
||||||
|
*insert_range = updated_insert_range;
|
||||||
if *resolved {
|
if *resolved {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -5384,12 +5378,19 @@ impl LspStore {
|
||||||
let completion = &mut completions[completion_index];
|
let completion = &mut completions[completion_index];
|
||||||
completion.documentation = Some(documentation);
|
completion.documentation = Some(documentation);
|
||||||
if let CompletionSource::Lsp {
|
if let CompletionSource::Lsp {
|
||||||
|
insert_range,
|
||||||
lsp_completion,
|
lsp_completion,
|
||||||
resolved,
|
resolved,
|
||||||
server_id: completion_server_id,
|
server_id: completion_server_id,
|
||||||
lsp_defaults: _,
|
lsp_defaults: _,
|
||||||
} = &mut completion.source
|
} = &mut completion.source
|
||||||
{
|
{
|
||||||
|
let completion_insert_range = response
|
||||||
|
.old_insert_start
|
||||||
|
.and_then(deserialize_anchor)
|
||||||
|
.zip(response.old_insert_end.and_then(deserialize_anchor));
|
||||||
|
*insert_range = completion_insert_range.map(|(start, end)| start..end);
|
||||||
|
|
||||||
if *resolved {
|
if *resolved {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -5401,14 +5402,14 @@ impl LspStore {
|
||||||
*resolved = true;
|
*resolved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let old_range = response
|
let replace_range = response
|
||||||
.old_start
|
.old_replace_start
|
||||||
.and_then(deserialize_anchor)
|
.and_then(deserialize_anchor)
|
||||||
.zip(response.old_end.and_then(deserialize_anchor));
|
.zip(response.old_replace_end.and_then(deserialize_anchor));
|
||||||
if let Some((old_start, old_end)) = old_range {
|
if let Some((old_replace_start, old_replace_end)) = replace_range {
|
||||||
if !response.new_text.is_empty() {
|
if !response.new_text.is_empty() {
|
||||||
completion.new_text = response.new_text;
|
completion.new_text = response.new_text;
|
||||||
completion.old_range = old_start..old_end;
|
completion.replace_range = old_replace_start..old_replace_end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5433,7 +5434,7 @@ impl LspStore {
|
||||||
project_id,
|
project_id,
|
||||||
buffer_id: buffer_id.into(),
|
buffer_id: buffer_id.into(),
|
||||||
completion: Some(Self::serialize_completion(&CoreCompletion {
|
completion: Some(Self::serialize_completion(&CoreCompletion {
|
||||||
old_range: completion.old_range,
|
replace_range: completion.replace_range,
|
||||||
new_text: completion.new_text,
|
new_text: completion.new_text,
|
||||||
source: completion.source,
|
source: completion.source,
|
||||||
})),
|
})),
|
||||||
|
@ -5477,7 +5478,6 @@ impl LspStore {
|
||||||
&snapshot,
|
&snapshot,
|
||||||
completions.clone(),
|
completions.clone(),
|
||||||
completion_index,
|
completion_index,
|
||||||
cx,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("resolving completion")?;
|
.context("resolving completion")?;
|
||||||
|
@ -5505,7 +5505,7 @@ impl LspStore {
|
||||||
buffer.start_transaction();
|
buffer.start_transaction();
|
||||||
|
|
||||||
for (range, text) in edits {
|
for (range, text) in edits {
|
||||||
let primary = &completion.old_range;
|
let primary = &completion.replace_range;
|
||||||
let start_within = primary.start.cmp(&range.start, buffer).is_le()
|
let start_within = primary.start.cmp(&range.start, buffer).is_le()
|
||||||
&& primary.end.cmp(&range.start, buffer).is_ge();
|
&& primary.end.cmp(&range.start, buffer).is_ge();
|
||||||
let end_within = range.start.cmp(&primary.end, buffer).is_le()
|
let end_within = range.start.cmp(&primary.end, buffer).is_le()
|
||||||
|
@ -7709,8 +7709,10 @@ impl LspStore {
|
||||||
|
|
||||||
// If we have a new buffer_id, that means we're talking to a new client
|
// If we have a new buffer_id, that means we're talking to a new client
|
||||||
// and want to check for new text_edits in the completion too.
|
// and want to check for new text_edits in the completion too.
|
||||||
let mut old_start = None;
|
let mut old_replace_start = None;
|
||||||
let mut old_end = None;
|
let mut old_replace_end = None;
|
||||||
|
let mut old_insert_start = None;
|
||||||
|
let mut old_insert_end = None;
|
||||||
let mut new_text = String::default();
|
let mut new_text = String::default();
|
||||||
if let Ok(buffer_id) = BufferId::new(envelope.payload.buffer_id) {
|
if let Ok(buffer_id) = BufferId::new(envelope.payload.buffer_id) {
|
||||||
let buffer_snapshot = this.update(&mut cx, |this, cx| {
|
let buffer_snapshot = this.update(&mut cx, |this, cx| {
|
||||||
|
@ -7719,23 +7721,18 @@ impl LspStore {
|
||||||
})??;
|
})??;
|
||||||
|
|
||||||
if let Some(text_edit) = completion.text_edit.as_ref() {
|
if let Some(text_edit) = completion.text_edit.as_ref() {
|
||||||
let completion_mode = cx
|
let edit = parse_completion_text_edit(text_edit, &buffer_snapshot);
|
||||||
.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(mut edit) = edit {
|
||||||
|
LineEnding::normalize(&mut edit.new_text);
|
||||||
|
|
||||||
if let Some((old_range, mut text_edit_new_text)) = edit {
|
new_text = edit.new_text;
|
||||||
LineEnding::normalize(&mut text_edit_new_text);
|
old_replace_start = Some(serialize_anchor(&edit.replace_range.start));
|
||||||
|
old_replace_end = Some(serialize_anchor(&edit.replace_range.end));
|
||||||
new_text = text_edit_new_text;
|
if let Some(insert_range) = edit.insert_range {
|
||||||
old_start = Some(serialize_anchor(&old_range.start));
|
old_insert_start = Some(serialize_anchor(&insert_range.start));
|
||||||
old_end = Some(serialize_anchor(&old_range.end));
|
old_insert_end = Some(serialize_anchor(&insert_range.end));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7743,10 +7740,12 @@ impl LspStore {
|
||||||
Ok(proto::ResolveCompletionDocumentationResponse {
|
Ok(proto::ResolveCompletionDocumentationResponse {
|
||||||
documentation,
|
documentation,
|
||||||
documentation_is_markdown,
|
documentation_is_markdown,
|
||||||
old_start,
|
old_replace_start,
|
||||||
old_end,
|
old_replace_end,
|
||||||
new_text,
|
new_text,
|
||||||
lsp_completion,
|
lsp_completion,
|
||||||
|
old_insert_start,
|
||||||
|
old_insert_end,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8048,7 +8047,7 @@ impl LspStore {
|
||||||
this.apply_additional_edits_for_completion(
|
this.apply_additional_edits_for_completion(
|
||||||
buffer,
|
buffer,
|
||||||
Rc::new(RefCell::new(Box::new([Completion {
|
Rc::new(RefCell::new(Box::new([Completion {
|
||||||
old_range: completion.old_range,
|
replace_range: completion.replace_range,
|
||||||
new_text: completion.new_text,
|
new_text: completion.new_text,
|
||||||
source: completion.source,
|
source: completion.source,
|
||||||
documentation: None,
|
documentation: None,
|
||||||
|
@ -9103,18 +9102,26 @@ impl LspStore {
|
||||||
|
|
||||||
pub(crate) fn serialize_completion(completion: &CoreCompletion) -> proto::Completion {
|
pub(crate) fn serialize_completion(completion: &CoreCompletion) -> proto::Completion {
|
||||||
let mut serialized_completion = proto::Completion {
|
let mut serialized_completion = proto::Completion {
|
||||||
old_start: Some(serialize_anchor(&completion.old_range.start)),
|
old_replace_start: Some(serialize_anchor(&completion.replace_range.start)),
|
||||||
old_end: Some(serialize_anchor(&completion.old_range.end)),
|
old_replace_end: Some(serialize_anchor(&completion.replace_range.end)),
|
||||||
new_text: completion.new_text.clone(),
|
new_text: completion.new_text.clone(),
|
||||||
..proto::Completion::default()
|
..proto::Completion::default()
|
||||||
};
|
};
|
||||||
match &completion.source {
|
match &completion.source {
|
||||||
CompletionSource::Lsp {
|
CompletionSource::Lsp {
|
||||||
|
insert_range,
|
||||||
server_id,
|
server_id,
|
||||||
lsp_completion,
|
lsp_completion,
|
||||||
lsp_defaults,
|
lsp_defaults,
|
||||||
resolved,
|
resolved,
|
||||||
} => {
|
} => {
|
||||||
|
let (old_insert_start, old_insert_end) = insert_range
|
||||||
|
.as_ref()
|
||||||
|
.map(|range| (serialize_anchor(&range.start), serialize_anchor(&range.end)))
|
||||||
|
.unzip();
|
||||||
|
|
||||||
|
serialized_completion.old_insert_start = old_insert_start;
|
||||||
|
serialized_completion.old_insert_end = old_insert_end;
|
||||||
serialized_completion.source = proto::completion::Source::Lsp as i32;
|
serialized_completion.source = proto::completion::Source::Lsp as i32;
|
||||||
serialized_completion.server_id = server_id.0 as u64;
|
serialized_completion.server_id = server_id.0 as u64;
|
||||||
serialized_completion.lsp_completion = serde_json::to_vec(lsp_completion).unwrap();
|
serialized_completion.lsp_completion = serde_json::to_vec(lsp_completion).unwrap();
|
||||||
|
@ -9142,20 +9149,31 @@ impl LspStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn deserialize_completion(completion: proto::Completion) -> Result<CoreCompletion> {
|
pub(crate) fn deserialize_completion(completion: proto::Completion) -> Result<CoreCompletion> {
|
||||||
let old_start = completion
|
let old_replace_start = completion
|
||||||
.old_start
|
.old_replace_start
|
||||||
.and_then(deserialize_anchor)
|
.and_then(deserialize_anchor)
|
||||||
.context("invalid old start")?;
|
.context("invalid old start")?;
|
||||||
let old_end = completion
|
let old_replace_end = completion
|
||||||
.old_end
|
.old_replace_end
|
||||||
.and_then(deserialize_anchor)
|
.and_then(deserialize_anchor)
|
||||||
.context("invalid old end")?;
|
.context("invalid old end")?;
|
||||||
|
let insert_range = {
|
||||||
|
match completion.old_insert_start.zip(completion.old_insert_end) {
|
||||||
|
Some((start, end)) => {
|
||||||
|
let start = deserialize_anchor(start).context("invalid insert old start")?;
|
||||||
|
let end = deserialize_anchor(end).context("invalid insert old end")?;
|
||||||
|
Some(start..end)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
};
|
||||||
Ok(CoreCompletion {
|
Ok(CoreCompletion {
|
||||||
old_range: old_start..old_end,
|
replace_range: old_replace_start..old_replace_end,
|
||||||
new_text: completion.new_text,
|
new_text: completion.new_text,
|
||||||
source: match proto::completion::Source::from_i32(completion.source) {
|
source: match proto::completion::Source::from_i32(completion.source) {
|
||||||
Some(proto::completion::Source::Custom) => CompletionSource::Custom,
|
Some(proto::completion::Source::Custom) => CompletionSource::Custom,
|
||||||
Some(proto::completion::Source::Lsp) => CompletionSource::Lsp {
|
Some(proto::completion::Source::Lsp) => CompletionSource::Lsp {
|
||||||
|
insert_range,
|
||||||
server_id: LanguageServerId::from_proto(completion.server_id),
|
server_id: LanguageServerId::from_proto(completion.server_id),
|
||||||
lsp_completion: serde_json::from_slice(&completion.lsp_completion)?,
|
lsp_completion: serde_json::from_slice(&completion.lsp_completion)?,
|
||||||
lsp_defaults: completion
|
lsp_defaults: completion
|
||||||
|
@ -9344,7 +9362,7 @@ async fn populate_labels_for_completions(
|
||||||
completions.push(Completion {
|
completions.push(Completion {
|
||||||
label,
|
label,
|
||||||
documentation,
|
documentation,
|
||||||
old_range: completion.old_range,
|
replace_range: completion.replace_range,
|
||||||
new_text: completion.new_text,
|
new_text: completion.new_text,
|
||||||
insert_text_mode: lsp_completion.insert_text_mode,
|
insert_text_mode: lsp_completion.insert_text_mode,
|
||||||
source: completion.source,
|
source: completion.source,
|
||||||
|
@ -9358,7 +9376,7 @@ async fn populate_labels_for_completions(
|
||||||
completions.push(Completion {
|
completions.push(Completion {
|
||||||
label,
|
label,
|
||||||
documentation: None,
|
documentation: None,
|
||||||
old_range: completion.old_range,
|
replace_range: completion.replace_range,
|
||||||
new_text: completion.new_text,
|
new_text: completion.new_text,
|
||||||
source: completion.source,
|
source: completion.source,
|
||||||
insert_text_mode: None,
|
insert_text_mode: None,
|
||||||
|
|
|
@ -359,8 +359,14 @@ pub struct InlayHint {
|
||||||
#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
|
#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
|
||||||
pub enum CompletionIntent {
|
pub enum CompletionIntent {
|
||||||
/// The user intends to 'commit' this result, if possible
|
/// The user intends to 'commit' this result, if possible
|
||||||
/// completion confirmations should run side effects
|
/// completion confirmations should run side effects.
|
||||||
|
///
|
||||||
|
/// For LSP completions, will respect the setting `completions.lsp_insert_mode`.
|
||||||
Complete,
|
Complete,
|
||||||
|
/// Similar to [Self::Complete], but behaves like `lsp_insert_mode` is set to `insert`.
|
||||||
|
CompleteWithInsert,
|
||||||
|
/// Similar to [Self::Complete], but behaves like `lsp_insert_mode` is set to `replace`.
|
||||||
|
CompleteWithReplace,
|
||||||
/// The user intends to continue 'composing' this completion
|
/// The user intends to continue 'composing' this completion
|
||||||
/// completion confirmations should not run side effects and
|
/// completion confirmations should not run side effects and
|
||||||
/// let the user continue composing their action
|
/// let the user continue composing their action
|
||||||
|
@ -377,11 +383,11 @@ impl CompletionIntent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A completion provided by a language server
|
/// Similar to `CoreCompletion`, but with extra metadata attached.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Completion {
|
pub struct Completion {
|
||||||
/// The range of the buffer that will be replaced.
|
/// The range of text that will be replaced by this completion.
|
||||||
pub old_range: Range<Anchor>,
|
pub replace_range: Range<Anchor>,
|
||||||
/// The new text that will be inserted.
|
/// The new text that will be inserted.
|
||||||
pub new_text: String,
|
pub new_text: String,
|
||||||
/// A label for this completion that is shown in the menu.
|
/// A label for this completion that is shown in the menu.
|
||||||
|
@ -404,6 +410,8 @@ pub struct Completion {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum CompletionSource {
|
pub enum CompletionSource {
|
||||||
Lsp {
|
Lsp {
|
||||||
|
/// The alternate `insert` range, if provided by the LSP server.
|
||||||
|
insert_range: Option<Range<Anchor>>,
|
||||||
/// The id of the language server that produced this completion.
|
/// The id of the language server that produced this completion.
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
/// The raw completion provided by the language server.
|
/// The raw completion provided by the language server.
|
||||||
|
@ -508,7 +516,7 @@ impl CompletionSource {
|
||||||
impl std::fmt::Debug for Completion {
|
impl std::fmt::Debug for Completion {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Completion")
|
f.debug_struct("Completion")
|
||||||
.field("old_range", &self.old_range)
|
.field("replace_range", &self.replace_range)
|
||||||
.field("new_text", &self.new_text)
|
.field("new_text", &self.new_text)
|
||||||
.field("label", &self.label)
|
.field("label", &self.label)
|
||||||
.field("documentation", &self.documentation)
|
.field("documentation", &self.documentation)
|
||||||
|
@ -517,10 +525,10 @@ impl std::fmt::Debug for Completion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A completion provided by a language server
|
/// A generic completion that can come from different sources.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct CoreCompletion {
|
pub(crate) struct CoreCompletion {
|
||||||
old_range: Range<Anchor>,
|
replace_range: Range<Anchor>,
|
||||||
new_text: String,
|
new_text: String,
|
||||||
source: CompletionSource,
|
source: CompletionSource,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3018,7 +3018,7 @@ async fn test_completions_with_text_edit(cx: &mut gpui::TestAppContext) {
|
||||||
assert_eq!(completions.len(), 1);
|
assert_eq!(completions.len(), 1);
|
||||||
assert_eq!(completions[0].new_text, "textEditText");
|
assert_eq!(completions[0].new_text, "textEditText");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
completions[0].old_range.to_offset(&snapshot),
|
completions[0].replace_range.to_offset(&snapshot),
|
||||||
text.len() - 3..text.len()
|
text.len() - 3..text.len()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3101,7 +3101,7 @@ async fn test_completions_with_edit_ranges(cx: &mut gpui::TestAppContext) {
|
||||||
assert_eq!(completions.len(), 1);
|
assert_eq!(completions.len(), 1);
|
||||||
assert_eq!(completions[0].new_text, "insertText");
|
assert_eq!(completions[0].new_text, "insertText");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
completions[0].old_range.to_offset(&snapshot),
|
completions[0].replace_range.to_offset(&snapshot),
|
||||||
text.len() - 3..text.len()
|
text.len() - 3..text.len()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3143,7 +3143,7 @@ async fn test_completions_with_edit_ranges(cx: &mut gpui::TestAppContext) {
|
||||||
assert_eq!(completions.len(), 1);
|
assert_eq!(completions.len(), 1);
|
||||||
assert_eq!(completions[0].new_text, "labelText");
|
assert_eq!(completions[0].new_text, "labelText");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
completions[0].old_range.to_offset(&snapshot),
|
completions[0].replace_range.to_offset(&snapshot),
|
||||||
text.len() - 3..text.len()
|
text.len() - 3..text.len()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3213,7 +3213,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
|
||||||
assert_eq!(completions.len(), 1);
|
assert_eq!(completions.len(), 1);
|
||||||
assert_eq!(completions[0].new_text, "fullyQualifiedName");
|
assert_eq!(completions[0].new_text, "fullyQualifiedName");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
completions[0].old_range.to_offset(&snapshot),
|
completions[0].replace_range.to_offset(&snapshot),
|
||||||
text.len() - 3..text.len()
|
text.len() - 3..text.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3240,7 +3240,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
|
||||||
assert_eq!(completions.len(), 1);
|
assert_eq!(completions.len(), 1);
|
||||||
assert_eq!(completions[0].new_text, "component");
|
assert_eq!(completions[0].new_text, "component");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
completions[0].old_range.to_offset(&snapshot),
|
completions[0].replace_range.to_offset(&snapshot),
|
||||||
text.len() - 4..text.len() - 1
|
text.len() - 4..text.len() - 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,8 +198,8 @@ message ApplyCompletionAdditionalEditsResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
message Completion {
|
message Completion {
|
||||||
Anchor old_start = 1;
|
Anchor old_replace_start = 1;
|
||||||
Anchor old_end = 2;
|
Anchor old_replace_end = 2;
|
||||||
string new_text = 3;
|
string new_text = 3;
|
||||||
uint64 server_id = 4;
|
uint64 server_id = 4;
|
||||||
bytes lsp_completion = 5;
|
bytes lsp_completion = 5;
|
||||||
|
@ -208,6 +208,8 @@ message Completion {
|
||||||
optional bytes lsp_defaults = 8;
|
optional bytes lsp_defaults = 8;
|
||||||
optional Anchor buffer_word_start = 9;
|
optional Anchor buffer_word_start = 9;
|
||||||
optional Anchor buffer_word_end = 10;
|
optional Anchor buffer_word_end = 10;
|
||||||
|
Anchor old_insert_start = 11;
|
||||||
|
Anchor old_insert_end = 12;
|
||||||
|
|
||||||
enum Source {
|
enum Source {
|
||||||
Lsp = 0;
|
Lsp = 0;
|
||||||
|
@ -428,10 +430,12 @@ message ResolveCompletionDocumentation {
|
||||||
message ResolveCompletionDocumentationResponse {
|
message ResolveCompletionDocumentationResponse {
|
||||||
string documentation = 1;
|
string documentation = 1;
|
||||||
bool documentation_is_markdown = 2;
|
bool documentation_is_markdown = 2;
|
||||||
Anchor old_start = 3;
|
Anchor old_replace_start = 3;
|
||||||
Anchor old_end = 4;
|
Anchor old_replace_end = 4;
|
||||||
string new_text = 5;
|
string new_text = 5;
|
||||||
bytes lsp_completion = 6;
|
bytes lsp_completion = 6;
|
||||||
|
Anchor old_insert_start = 7;
|
||||||
|
Anchor old_insert_end = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ResolveInlayHint {
|
message ResolveInlayHint {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue