fix(state): cegah panic saat select mention dengan cursor stale
Cursor bisa berpindah (Left/Right) tanpa menutup dropdown mention, sehingga mention_start jadi stale relatif ke cursor saat Enter ditekan. select_autocomplete() lalu memanggil replace_range dengan start > end dan panic (crash seluruh TUI, termasuk daemon). Tambah guard: jika cursor < mention_start atau mention_start > buffer.len(), tutup dropdown dan kembalikan false alih-alih menyambung range yang tidak valid. Tambah regression test yang mereproduksi skenario ini.
This commit is contained in:
@@ -270,6 +270,17 @@ impl InputState {
|
|||||||
self.cursor = self.buffer.len();
|
self.cursor = self.buffer.len();
|
||||||
}
|
}
|
||||||
AutocompleteKind::FileMention => {
|
AutocompleteKind::FileMention => {
|
||||||
|
// Cursor movement (Left/Right) does not close the dropdown, so
|
||||||
|
// by the time Enter is pressed `mention_start` may no longer
|
||||||
|
// describe a valid range against the current cursor/buffer
|
||||||
|
// (e.g. the cursor moved left past the '@'). Splicing on a
|
||||||
|
// stale range would panic (`start > end`) or, even when it
|
||||||
|
// doesn't panic, produce a nonsensical replacement. Treat a
|
||||||
|
// stale mention context the same as "nothing selected".
|
||||||
|
if self.cursor < self.mention_start || self.mention_start > self.buffer.len() {
|
||||||
|
self.close_autocomplete();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
let replacement = format!("@{candidate} ");
|
let replacement = format!("@{candidate} ");
|
||||||
self.buffer.replace_range(self.mention_start..self.cursor, &replacement);
|
self.buffer.replace_range(self.mention_start..self.cursor, &replacement);
|
||||||
self.cursor = self.mention_start + replacement.len();
|
self.cursor = self.mention_start + replacement.len();
|
||||||
@@ -485,6 +496,21 @@ mod tests {
|
|||||||
assert_eq!(input.cursor, 8 + "@src/main.rs ".len());
|
assert_eq!(input.cursor, 8 + "@src/main.rs ".len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_file_mention_with_stale_cursor_before_mention_start_does_not_panic() {
|
||||||
|
// Simulates: user typed "foo @rea" (mention_start = 4, cursor = 8,
|
||||||
|
// dropdown open), then pressed Left 5 times without closing the
|
||||||
|
// dropdown, moving the cursor to byte 3 (before the '@'). Selecting
|
||||||
|
// now must not panic on `replace_range(4..3, ...)`.
|
||||||
|
let mut input = input_with("foo @rea", 3);
|
||||||
|
input.autocomplete_candidates = vec!["src/main.rs".to_string()];
|
||||||
|
input.autocomplete_idx = 0;
|
||||||
|
input.autocomplete_kind = AutocompleteKind::FileMention;
|
||||||
|
input.mention_start = 4;
|
||||||
|
assert!(!input.select_autocomplete());
|
||||||
|
assert!(!input.autocomplete_visible);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_command_still_replaces_whole_buffer() {
|
fn select_command_still_replaces_whole_buffer() {
|
||||||
let mut input = input_with("/mo", 3);
|
let mut input = input_with("/mo", 3);
|
||||||
|
|||||||
Reference in New Issue
Block a user