From 89b62a56e178cdc152a40603e8bbca147a20d58a Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Wed, 26 Mar 2025 19:20:00 +0100 Subject: [PATCH] editable_line: guard against empty text there's got to be a nicer way to do this Fixes #11324 --- src/editable_line.rs | 28 +++++++++++++++++++++------- tests/pexpects/abbrs.py | 5 +++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/editable_line.rs b/src/editable_line.rs index 433ccf0b1..1c69374a7 100644 --- a/src/editable_line.rs +++ b/src/editable_line.rs @@ -41,7 +41,11 @@ pub fn new(range: std::ops::Range, replacement: WString) -> Self { /// Currently exposed for testing only. pub fn apply_edit(target: &mut WString, colors: &mut Vec, edit: &Edit) { let range = &edit.range; - target.replace_range(range.clone(), &edit.replacement); + if target.is_empty() { + *target = edit.replacement.clone(); + } else { + target.replace_range(range.clone(), &edit.replacement); + } // Now do the same to highlighting. let last_color = edit @@ -50,10 +54,16 @@ pub fn apply_edit(target: &mut WString, colors: &mut Vec, edit: & .checked_sub(1) .map(|i| colors[i]) .unwrap_or_default(); - colors.splice( - range.clone(), - std::iter::repeat(last_color).take(edit.replacement.len()), - ); + if colors.is_empty() { + *colors = std::iter::repeat(last_color) + .take(edit.replacement.len()) + .collect(); + } else { + colors.splice( + range.clone(), + std::iter::repeat(last_color).take(edit.replacement.len()), + ); + } } /// The history of all edits to some command line. @@ -185,7 +195,11 @@ pub fn push_edit(&mut self, mut edit: Edit, allow_coalesce: bool) { .truncate(self.undo_history.edits_applied); } edit.cursor_position_before_edit = self.pending_position.take().unwrap_or(self.position()); - edit.old = self.text[range.clone()].to_owned(); + edit.old = if self.text.is_empty() { + L!("").to_owned() + } else { + self.text[range.clone()].to_owned() + }; apply_edit(&mut self.text, &mut self.colors, &edit); self.set_position(cursor_position_after_edit(&edit)); assert_eq!( @@ -204,7 +218,7 @@ pub fn undo(&mut self) -> bool { let mut last_group_id = None; let position_before_undo = self.position(); let end = self.undo_history.edits_applied; - while self.undo_history.edits_applied != 0 { + while !self.undo_history.edits.is_empty() && self.undo_history.edits_applied != 0 { let edit = &self.undo_history.edits[self.undo_history.edits_applied - 1]; if did_undo && edit diff --git a/tests/pexpects/abbrs.py b/tests/pexpects/abbrs.py index fdd91f232..96c78bcd0 100644 --- a/tests/pexpects/abbrs.py +++ b/tests/pexpects/abbrs.py @@ -184,3 +184,8 @@ sendline(r"""abbr fruit --command={git,hg,svn,} banana""") expect_prompt() sendline(r"""fruit foo""") expect_prompt("I am a banana") + +# (don't add the literal string "bar" here or the expect_prompt will match it - so we add some no-op quotes) +sendline(r"""function replace; commandline -r ""; echo echo b''ar; end; abbr foo --function replace""") +sendline("foo") +expect_prompt("bar")