debugger: Fix correctly determine replace range for debug console completions (#33959)
Follow-up #33868 This PR fixes a few issues with determining the completion range for client‑ and variable‑list completions. 1. Non‑word completions We previously supported only word characters and _, using their combined length to compute the start offset. In PHP, however, an expression can contain `$`, `-`, `>`, `[`, `]`, `(`, and `)`. Because these characters weren’t treated as word characters, the start offset stopped at them, even when the preceding character was part of a word. 2. Trailing characters inside the search text When autocompletion occurred in the middle of the search text, we didn’t account for trailing characters. As a result, the start offset was off by the number of characters after the cursor. For example, replacing res with result in print(res) produced `print(rresult)` because the trailing `)` wasn’t subtracted from the start offset. The following completions are correctly covered now: - **Before** `$aut` -> `$aut$author` **After** `$aut` -> `$author` - **Before** `$author->na` -> `$author->na$author->name` **After** `$author->na` -> `$author->name` - **Before** `$author->books[` -> `$author->books[$author->books[0]` **After** `$author->books[` -> `$author->books[0]` - **Before** `print(res)` -> `print(rresult)` **After** `print(res)` -> `print(result)` **Before** https://github.com/user-attachments/assets/b530cf31-8d4d-45e6-9650-18574f14314c https://github.com/user-attachments/assets/52475b7b-2bf2-4749-98ec-0dc933fcc364 **After** https://github.com/user-attachments/assets/c065701b-31c9-4e0a-b584-d1daffe3a38c https://github.com/user-attachments/assets/455ebb3e-632e-4a57-aea8-d214d2992c06 Release Notes: - Debugger: Fixed autocompletion not always replacing the correct search text
This commit is contained in:
parent
a8cc927303
commit
833bc6979a
1 changed files with 101 additions and 37 deletions
|
@ -12,7 +12,7 @@ use gpui::{
|
|||
Action as _, AppContext, Context, Corner, Entity, FocusHandle, Focusable, HighlightStyle, Hsla,
|
||||
Render, Subscription, Task, TextStyle, WeakEntity, actions,
|
||||
};
|
||||
use language::{Buffer, CodeLabel, ToOffset};
|
||||
use language::{Anchor, Buffer, CodeLabel, TextBufferSnapshot, ToOffset};
|
||||
use menu::{Confirm, SelectNext, SelectPrevious};
|
||||
use project::{
|
||||
Completion, CompletionResponse,
|
||||
|
@ -637,27 +637,13 @@ impl ConsoleQueryBarCompletionProvider {
|
|||
});
|
||||
|
||||
let snapshot = buffer.read(cx).text_snapshot();
|
||||
let query = snapshot.text();
|
||||
let replace_range = {
|
||||
let buffer_offset = buffer_position.to_offset(&snapshot);
|
||||
let reversed_chars = snapshot.reversed_chars_for_range(0..buffer_offset);
|
||||
let mut word_len = 0;
|
||||
for ch in reversed_chars {
|
||||
if ch.is_alphanumeric() || ch == '_' {
|
||||
word_len += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let word_start_offset = buffer_offset - word_len;
|
||||
let start_anchor = snapshot.anchor_at(word_start_offset, Bias::Left);
|
||||
start_anchor..buffer_position
|
||||
};
|
||||
let buffer_text = snapshot.text();
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
const LIMIT: usize = 10;
|
||||
let matches = fuzzy::match_strings(
|
||||
&string_matches,
|
||||
&query,
|
||||
&buffer_text,
|
||||
true,
|
||||
true,
|
||||
LIMIT,
|
||||
|
@ -672,7 +658,12 @@ impl ConsoleQueryBarCompletionProvider {
|
|||
let variable_value = variables.get(&string_match.string)?;
|
||||
|
||||
Some(project::Completion {
|
||||
replace_range: replace_range.clone(),
|
||||
replace_range: Self::replace_range_for_completion(
|
||||
&buffer_text,
|
||||
buffer_position,
|
||||
string_match.string.as_bytes(),
|
||||
&snapshot,
|
||||
),
|
||||
new_text: string_match.string.clone(),
|
||||
label: CodeLabel {
|
||||
filter_range: 0..string_match.string.len(),
|
||||
|
@ -697,6 +688,28 @@ impl ConsoleQueryBarCompletionProvider {
|
|||
})
|
||||
}
|
||||
|
||||
fn replace_range_for_completion(
|
||||
buffer_text: &String,
|
||||
buffer_position: Anchor,
|
||||
new_bytes: &[u8],
|
||||
snapshot: &TextBufferSnapshot,
|
||||
) -> Range<Anchor> {
|
||||
let buffer_offset = buffer_position.to_offset(&snapshot);
|
||||
let buffer_bytes = &buffer_text.as_bytes()[0..buffer_offset];
|
||||
|
||||
let mut prefix_len = 0;
|
||||
for i in (0..new_bytes.len()).rev() {
|
||||
if buffer_bytes.ends_with(&new_bytes[0..i]) {
|
||||
prefix_len = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let start = snapshot.clip_offset(buffer_offset - prefix_len, Bias::Left);
|
||||
|
||||
snapshot.anchor_before(start)..buffer_position
|
||||
}
|
||||
|
||||
const fn completion_type_score(completion_type: CompletionItemType) -> usize {
|
||||
match completion_type {
|
||||
CompletionItemType::Field | CompletionItemType::Property => 0,
|
||||
|
@ -744,6 +757,8 @@ impl ConsoleQueryBarCompletionProvider {
|
|||
cx.background_executor().spawn(async move {
|
||||
let completions = completion_task.await?;
|
||||
|
||||
let buffer_text = snapshot.text();
|
||||
|
||||
let completions = completions
|
||||
.into_iter()
|
||||
.map(|completion| {
|
||||
|
@ -753,26 +768,14 @@ impl ConsoleQueryBarCompletionProvider {
|
|||
.as_ref()
|
||||
.unwrap_or(&completion.label)
|
||||
.to_owned();
|
||||
let buffer_text = snapshot.text();
|
||||
let buffer_bytes = buffer_text.as_bytes();
|
||||
let new_bytes = new_text.as_bytes();
|
||||
|
||||
let mut prefix_len = 0;
|
||||
for i in (0..new_bytes.len()).rev() {
|
||||
if buffer_bytes.ends_with(&new_bytes[0..i]) {
|
||||
prefix_len = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let buffer_offset = buffer_position.to_offset(&snapshot);
|
||||
let start = buffer_offset - prefix_len;
|
||||
let start = snapshot.clip_offset(start, Bias::Left);
|
||||
let start = snapshot.anchor_before(start);
|
||||
let replace_range = start..buffer_position;
|
||||
|
||||
project::Completion {
|
||||
replace_range,
|
||||
replace_range: Self::replace_range_for_completion(
|
||||
&buffer_text,
|
||||
buffer_position,
|
||||
new_text.as_bytes(),
|
||||
&snapshot,
|
||||
),
|
||||
new_text,
|
||||
label: CodeLabel {
|
||||
filter_range: 0..completion.label.len(),
|
||||
|
@ -944,3 +947,64 @@ fn color_fetcher(color: ansi::Color) -> fn(&Theme) -> Hsla {
|
|||
};
|
||||
color_fetcher
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::init_test;
|
||||
use editor::test::editor_test_context::EditorTestContext;
|
||||
use gpui::TestAppContext;
|
||||
use language::Point;
|
||||
|
||||
#[track_caller]
|
||||
fn assert_completion_range(
|
||||
input: &str,
|
||||
expect: &str,
|
||||
replacement: &str,
|
||||
cx: &mut EditorTestContext,
|
||||
) {
|
||||
cx.set_state(input);
|
||||
|
||||
let buffer_position =
|
||||
cx.editor(|editor, _, cx| editor.selections.newest::<Point>(cx).start);
|
||||
|
||||
let snapshot = &cx.buffer_snapshot();
|
||||
|
||||
let replace_range = ConsoleQueryBarCompletionProvider::replace_range_for_completion(
|
||||
&cx.buffer_text(),
|
||||
snapshot.anchor_before(buffer_position),
|
||||
replacement.as_bytes(),
|
||||
&snapshot,
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
editor.edit(
|
||||
vec![(
|
||||
snapshot.offset_for_anchor(&replace_range.start)
|
||||
..snapshot.offset_for_anchor(&replace_range.end),
|
||||
replacement,
|
||||
)],
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
pretty_assertions::assert_eq!(expect, cx.display_text());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_determine_completion_replace_range(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
assert_completion_range("resˇ", "result", "result", &mut cx);
|
||||
assert_completion_range("print(resˇ)", "print(result)", "result", &mut cx);
|
||||
assert_completion_range("$author->nˇ", "$author->name", "$author->name", &mut cx);
|
||||
assert_completion_range(
|
||||
"$author->books[ˇ",
|
||||
"$author->books[0]",
|
||||
"$author->books[0]",
|
||||
&mut cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue