diff --git a/codex-rs/tui/src/bottom_pane/textarea.rs b/codex-rs/tui/src/bottom_pane/textarea.rs index 37aa85ff18..07b6077587 100644 --- a/codex-rs/tui/src/bottom_pane/textarea.rs +++ b/codex-rs/tui/src/bottom_pane/textarea.rs @@ -252,6 +252,11 @@ impl TextArea { modifiers: KeyModifiers::CONTROL, .. } => self.delete_backward(1), + KeyEvent { + code: KeyCode::Delete, + modifiers: KeyModifiers::ALT, + .. + } => self.delete_forward_word(), KeyEvent { code: KeyCode::Delete, .. @@ -435,6 +440,18 @@ impl TextArea { self.replace_range(start..self.cursor_pos, ""); } + /// Delete text to the right of the cursor using "word" semantics. + /// + /// Deletes from the current cursor position through the end of the next word as determined + /// by `end_of_next_word()`. Any whitespace (including newlines) between the cursor and that + /// word is included in the deletion. + pub fn delete_forward_word(&mut self) { + let end = self.end_of_next_word(); + if end > self.cursor_pos { + self.replace_range(self.cursor_pos..end, ""); + } + } + pub fn kill_to_end_of_line(&mut self) { let eol = self.end_of_current_line(); if self.cursor_pos == eol { @@ -1104,6 +1121,79 @@ mod tests { assert_eq!(t.cursor(), 3); } + #[test] + fn delete_forward_word_variants() { + let mut t = ta_with("hello world "); + t.set_cursor(0); + t.delete_forward_word(); + assert_eq!(t.text(), " world "); + assert_eq!(t.cursor(), 0); + + let mut t = ta_with("hello world "); + t.set_cursor(1); + t.delete_forward_word(); + assert_eq!(t.text(), "h world "); + assert_eq!(t.cursor(), 1); + + let mut t = ta_with("hello world"); + t.set_cursor(t.text().len()); + t.delete_forward_word(); + assert_eq!(t.text(), "hello world"); + assert_eq!(t.cursor(), t.text().len()); + + let mut t = ta_with("foo \nbar"); + t.set_cursor(3); + t.delete_forward_word(); + assert_eq!(t.text(), "foo"); + assert_eq!(t.cursor(), 3); + + let mut t = ta_with("foo\nbar"); + t.set_cursor(3); + t.delete_forward_word(); + assert_eq!(t.text(), "foo"); + assert_eq!(t.cursor(), 3); + + let mut t = ta_with("hello world "); + t.set_cursor(t.text().len() + 10); + t.delete_forward_word(); + assert_eq!(t.text(), "hello world "); + assert_eq!(t.cursor(), t.text().len()); + } + + #[test] + fn delete_forward_word_handles_atomic_elements() { + let mut t = TextArea::new(); + t.insert_element(""); + t.insert_str(" tail"); + + t.set_cursor(0); + t.delete_forward_word(); + assert_eq!(t.text(), " tail"); + assert_eq!(t.cursor(), 0); + + let mut t = TextArea::new(); + t.insert_str(" "); + t.insert_element(""); + t.insert_str(" tail"); + + t.set_cursor(0); + t.delete_forward_word(); + assert_eq!(t.text(), " tail"); + assert_eq!(t.cursor(), 0); + + let mut t = TextArea::new(); + t.insert_str("prefix "); + t.insert_element(""); + t.insert_str(" tail"); + + // cursor in the middle of the element, delete_forward_word deletes the element + let elem_range = t.elements[0].range.clone(); + t.cursor_pos = elem_range.start + (elem_range.len() / 2); + t.delete_forward_word(); + assert_eq!(t.text(), "prefix tail"); + assert_eq!(t.cursor(), elem_range.start); + } + #[test] fn cursor_left_and_right_handle_graphemes() { let mut t = ta_with("a👍b"); @@ -1174,6 +1264,21 @@ mod tests { assert_eq!(t.cursor(), 6); } + #[test] + fn delete_forward_word_with_without_alt_modifier() { + let mut t = ta_with("hello world"); + t.set_cursor(0); + t.input(KeyEvent::new(KeyCode::Delete, KeyModifiers::ALT)); + assert_eq!(t.text(), " world"); + assert_eq!(t.cursor(), 0); + + let mut t = ta_with("hello"); + t.set_cursor(0); + t.input(KeyEvent::new(KeyCode::Delete, KeyModifiers::NONE)); + assert_eq!(t.text(), "ello"); + assert_eq!(t.cursor(), 0); + } + #[test] fn control_h_backspace() { // Test Ctrl+H as backspace