Fix multiline completions when surroundings don't match completion text (#28995)
Follow up to the scenarios I overlooked in https://github.com/zed-industries/zed/pull/28586. Release Notes: - N/A
This commit is contained in:
parent
58d8b91131
commit
1aa1b2bede
2 changed files with 116 additions and 23 deletions
|
@ -4816,19 +4816,18 @@ impl Editor {
|
||||||
let suffix = &old_text[lookbehind.min(old_text.len())..];
|
let suffix = &old_text[lookbehind.min(old_text.len())..];
|
||||||
|
|
||||||
let selections = self.selections.all::<usize>(cx);
|
let selections = self.selections.all::<usize>(cx);
|
||||||
let mut edits = Vec::new();
|
let mut ranges = Vec::new();
|
||||||
let mut linked_edits = HashMap::<_, Vec<_>>::default();
|
let mut linked_edits = HashMap::<_, Vec<_>>::default();
|
||||||
|
|
||||||
for selection in &selections {
|
for selection in &selections {
|
||||||
let edit = if selection.id == newest_anchor.id {
|
let range = if selection.id == newest_anchor.id {
|
||||||
(replace_range_multibuffer.clone(), new_text.as_str())
|
replace_range_multibuffer.clone()
|
||||||
} else {
|
} else {
|
||||||
let mut range = selection.range();
|
let mut range = selection.range();
|
||||||
let mut text = new_text.as_str();
|
|
||||||
|
|
||||||
// if prefix is present, don't duplicate it
|
// if prefix is present, don't duplicate it
|
||||||
if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
|
if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
|
||||||
text = &new_text[lookbehind.min(new_text.len())..];
|
range.start = range.start.saturating_sub(lookbehind);
|
||||||
|
|
||||||
// if suffix is also present, mimic the newest cursor and replace it
|
// if suffix is also present, mimic the newest cursor and replace it
|
||||||
if selection.id != newest_anchor.id
|
if selection.id != newest_anchor.id
|
||||||
|
@ -4837,10 +4836,10 @@ impl Editor {
|
||||||
range.end += lookahead;
|
range.end += lookahead;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(range, text)
|
range
|
||||||
};
|
};
|
||||||
|
|
||||||
edits.push(edit);
|
ranges.push(range);
|
||||||
|
|
||||||
if !self.linked_edit_ranges.is_empty() {
|
if !self.linked_edit_ranges.is_empty() {
|
||||||
let start_anchor = snapshot.anchor_before(selection.head());
|
let start_anchor = snapshot.anchor_before(selection.head());
|
||||||
|
@ -4866,19 +4865,14 @@ impl Editor {
|
||||||
self.transact(window, cx, |this, window, cx| {
|
self.transact(window, cx, |this, window, cx| {
|
||||||
if let Some(mut snippet) = snippet {
|
if let Some(mut snippet) = snippet {
|
||||||
snippet.text = new_text.to_string();
|
snippet.text = new_text.to_string();
|
||||||
let ranges = edits
|
|
||||||
.iter()
|
|
||||||
.map(|(range, _)| range.clone())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
this.insert_snippet(&ranges, snippet, window, cx).log_err();
|
this.insert_snippet(&ranges, snippet, window, cx).log_err();
|
||||||
} else {
|
} else {
|
||||||
this.buffer.update(cx, |buffer, cx| {
|
this.buffer.update(cx, |buffer, cx| {
|
||||||
let auto_indent = if completion.insert_text_mode == Some(InsertTextMode::AS_IS)
|
let auto_indent = match completion.insert_text_mode {
|
||||||
{
|
Some(InsertTextMode::AS_IS) => None,
|
||||||
None
|
_ => this.autoindent_mode.clone(),
|
||||||
} else {
|
|
||||||
this.autoindent_mode.clone()
|
|
||||||
};
|
};
|
||||||
|
let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
|
||||||
buffer.edit(edits, auto_indent, cx);
|
buffer.edit(edits, auto_indent, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -10104,7 +10104,7 @@ async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_completion_replacing_suffix_in_multicursors(cx: &mut TestAppContext) {
|
async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
let mut cx = EditorLspTestContext::new_rust(
|
let mut cx = EditorLspTestContext::new_rust(
|
||||||
lsp::ServerCapabilities {
|
lsp::ServerCapabilities {
|
||||||
|
@ -10118,6 +10118,8 @@ async fn test_completion_replacing_suffix_in_multicursors(cx: &mut TestAppContex
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
// scenario: surrounding text matches completion text
|
||||||
|
let completion_text = "to_offset";
|
||||||
let initial_state = indoc! {"
|
let initial_state = indoc! {"
|
||||||
1. buf.to_offˇsuffix
|
1. buf.to_offˇsuffix
|
||||||
2. buf.to_offˇsuf
|
2. buf.to_offˇsuf
|
||||||
|
@ -10148,7 +10150,6 @@ async fn test_completion_replacing_suffix_in_multicursors(cx: &mut TestAppContex
|
||||||
|
|
||||||
buf.<to_off|suffix> // newest cursor
|
buf.<to_off|suffix> // newest cursor
|
||||||
"};
|
"};
|
||||||
let completion_text = "to_offset";
|
|
||||||
let expected = indoc! {"
|
let expected = indoc! {"
|
||||||
1. buf.to_offsetˇ
|
1. buf.to_offsetˇ
|
||||||
2. buf.to_offsetˇsuf
|
2. buf.to_offsetˇsuf
|
||||||
|
@ -10164,24 +10165,122 @@ async fn test_completion_replacing_suffix_in_multicursors(cx: &mut TestAppContex
|
||||||
|
|
||||||
buf.to_offsetˇ // newest cursor
|
buf.to_offsetˇ // newest cursor
|
||||||
"};
|
"};
|
||||||
|
|
||||||
cx.set_state(initial_state);
|
cx.set_state(initial_state);
|
||||||
cx.update_editor(|editor, window, cx| {
|
cx.update_editor(|editor, window, cx| {
|
||||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
let counter = Arc::new(AtomicUsize::new(0));
|
|
||||||
handle_completion_request_with_insert_and_replace(
|
handle_completion_request_with_insert_and_replace(
|
||||||
&mut cx,
|
&mut cx,
|
||||||
completion_marked_buffer,
|
completion_marked_buffer,
|
||||||
vec![completion_text],
|
vec![completion_text],
|
||||||
counter.clone(),
|
Arc::new(AtomicUsize::new(0)),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
cx.condition(|editor, _| editor.context_menu_visible())
|
cx.condition(|editor, _| editor.context_menu_visible())
|
||||||
.await;
|
.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);
|
||||||
|
handle_resolve_completion_request(&mut cx, None).await;
|
||||||
|
apply_additional_edits.await.unwrap();
|
||||||
|
|
||||||
|
// scenario: surrounding text matches surroundings of newest cursor, inserting at the end
|
||||||
|
let completion_text = "foo_and_bar";
|
||||||
|
let initial_state = indoc! {"
|
||||||
|
1. ooanbˇ
|
||||||
|
2. zooanbˇ
|
||||||
|
3. ooanbˇz
|
||||||
|
4. zooanbˇz
|
||||||
|
5. ooanˇ
|
||||||
|
6. oanbˇ
|
||||||
|
|
||||||
|
ooanbˇ
|
||||||
|
"};
|
||||||
|
let completion_marked_buffer = indoc! {"
|
||||||
|
1. ooanb
|
||||||
|
2. zooanb
|
||||||
|
3. ooanbz
|
||||||
|
4. zooanbz
|
||||||
|
5. ooan
|
||||||
|
6. oanb
|
||||||
|
|
||||||
|
<ooanb|>
|
||||||
|
"};
|
||||||
|
let expected = indoc! {"
|
||||||
|
1. foo_and_barˇ
|
||||||
|
2. zfoo_and_barˇ
|
||||||
|
3. foo_and_barˇz
|
||||||
|
4. zfoo_and_barˇz
|
||||||
|
5. ooanfoo_and_barˇ
|
||||||
|
6. oanbfoo_and_barˇ
|
||||||
|
|
||||||
|
foo_and_barˇ
|
||||||
|
"};
|
||||||
|
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,
|
||||||
|
completion_marked_buffer,
|
||||||
|
vec![completion_text],
|
||||||
|
Arc::new(AtomicUsize::new(0)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
cx.condition(|editor, _| editor.context_menu_visible())
|
||||||
|
.await;
|
||||||
|
let apply_additional_edits = cx.update_editor(|editor, window, cx| {
|
||||||
|
editor
|
||||||
|
.confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
cx.assert_editor_state(expected);
|
||||||
|
handle_resolve_completion_request(&mut cx, None).await;
|
||||||
|
apply_additional_edits.await.unwrap();
|
||||||
|
|
||||||
|
// scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
|
||||||
|
// (expects the same as if it was inserted at the end)
|
||||||
|
let completion_text = "foo_and_bar";
|
||||||
|
let initial_state = indoc! {"
|
||||||
|
1. ooˇanb
|
||||||
|
2. zooˇanb
|
||||||
|
3. ooˇanbz
|
||||||
|
4. zooˇanbz
|
||||||
|
|
||||||
|
ooˇanb
|
||||||
|
"};
|
||||||
|
let completion_marked_buffer = indoc! {"
|
||||||
|
1. ooanb
|
||||||
|
2. zooanb
|
||||||
|
3. ooanbz
|
||||||
|
4. zooanbz
|
||||||
|
|
||||||
|
<oo|anb>
|
||||||
|
"};
|
||||||
|
let expected = indoc! {"
|
||||||
|
1. foo_and_barˇ
|
||||||
|
2. zfoo_and_barˇ
|
||||||
|
3. foo_and_barˇz
|
||||||
|
4. zfoo_and_barˇz
|
||||||
|
|
||||||
|
foo_and_barˇ
|
||||||
|
"};
|
||||||
|
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,
|
||||||
|
completion_marked_buffer,
|
||||||
|
vec![completion_text],
|
||||||
|
Arc::new(AtomicUsize::new(0)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
cx.condition(|editor, _| editor.context_menu_visible())
|
||||||
|
.await;
|
||||||
let apply_additional_edits = cx.update_editor(|editor, window, cx| {
|
let apply_additional_edits = cx.update_editor(|editor, window, cx| {
|
||||||
editor
|
editor
|
||||||
.confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
|
.confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue