diff --git a/codex-rs/tui/src/bottom_pane/textarea.rs b/codex-rs/tui/src/bottom_pane/textarea.rs index c6c756e3e..063841211 100644 --- a/codex-rs/tui/src/bottom_pane/textarea.rs +++ b/codex-rs/tui/src/bottom_pane/textarea.rs @@ -2779,6 +2779,53 @@ mod tests { assert_eq!(t.cursor(), 6); } + #[test] + fn shift_backspace_and_shift_delete_keep_grapheme_delete_behavior() { + let mut t = ta_with("abc"); + t.set_cursor(/*pos*/ 2); + + t.input(KeyEvent::new(KeyCode::Backspace, KeyModifiers::SHIFT)); + assert_eq!(t.text(), "ac"); + assert_eq!(t.cursor(), 1); + + let mut t = ta_with("abc"); + t.set_cursor(/*pos*/ 1); + + t.input(KeyEvent::new(KeyCode::Delete, KeyModifiers::SHIFT)); + assert_eq!(t.text(), "ac"); + assert_eq!(t.cursor(), 1); + } + + #[test] + fn control_backspace_variants_delete_backward_word() { + for modifiers in [ + KeyModifiers::CONTROL, + KeyModifiers::CONTROL | KeyModifiers::SHIFT, + ] { + let mut t = ta_with("hello world"); + t.set_cursor(t.text().len()); + + t.input(KeyEvent::new(KeyCode::Backspace, modifiers)); + assert_eq!(t.text(), "hello "); + assert_eq!(t.cursor(), 6); + } + } + + #[test] + fn control_delete_variants_delete_forward_word() { + for modifiers in [ + KeyModifiers::CONTROL, + KeyModifiers::CONTROL | KeyModifiers::SHIFT, + ] { + let mut t = ta_with("hello world"); + t.set_cursor(/*pos*/ 0); + + t.input(KeyEvent::new(KeyCode::Delete, modifiers)); + assert_eq!(t.text(), " world"); + assert_eq!(t.cursor(), 0); + } + } + #[test] fn delete_backward_word_handles_narrow_no_break_space() { let mut t = ta_with("32\u{202F}AM"); diff --git a/codex-rs/tui/src/keymap.rs b/codex-rs/tui/src/keymap.rs index 419a5ae8c..5e88423b2 100644 --- a/codex-rs/tui/src/keymap.rs +++ b/codex-rs/tui/src/keymap.rs @@ -588,11 +588,21 @@ impl RuntimeKeymap { move_line_end: default_bindings![plain(KeyCode::End), ctrl(KeyCode::Char('e'))], delete_backward: default_bindings![ plain(KeyCode::Backspace), + shift(KeyCode::Backspace), ctrl(KeyCode::Char('h')) ], - delete_forward: default_bindings![plain(KeyCode::Delete), ctrl(KeyCode::Char('d'))], + delete_forward: default_bindings![ + plain(KeyCode::Delete), + shift(KeyCode::Delete), + ctrl(KeyCode::Char('d')) + ], delete_backward_word: default_bindings![ alt(KeyCode::Backspace), + ctrl(KeyCode::Backspace), + raw(KeyBinding::new( + KeyCode::Backspace, + KeyModifiers::CONTROL | KeyModifiers::SHIFT, + )), ctrl(KeyCode::Char('w')), raw(KeyBinding::new( KeyCode::Char('h'), @@ -601,6 +611,11 @@ impl RuntimeKeymap { ], delete_forward_word: default_bindings![ alt(KeyCode::Delete), + ctrl(KeyCode::Delete), + raw(KeyBinding::new( + KeyCode::Delete, + KeyModifiers::CONTROL | KeyModifiers::SHIFT, + )), alt(KeyCode::Char('d')) ], kill_line_start: default_bindings![ctrl(KeyCode::Char('u'))], @@ -1919,6 +1934,54 @@ mod tests { ); } + #[test] + fn default_editor_deletion_includes_modified_backspace_delete_aliases() { + let runtime = RuntimeKeymap::defaults(); + + assert!( + runtime + .editor + .delete_backward + .contains(&key_hint::shift(KeyCode::Backspace)) + ); + assert!( + runtime + .editor + .delete_forward + .contains(&key_hint::shift(KeyCode::Delete)) + ); + assert!( + runtime + .editor + .delete_backward_word + .contains(&key_hint::ctrl(KeyCode::Backspace)) + ); + assert!( + runtime + .editor + .delete_backward_word + .contains(&KeyBinding::new( + KeyCode::Backspace, + KeyModifiers::CONTROL | KeyModifiers::SHIFT + )) + ); + assert!( + runtime + .editor + .delete_forward_word + .contains(&key_hint::ctrl(KeyCode::Delete)) + ); + assert!( + runtime + .editor + .delete_forward_word + .contains(&KeyBinding::new( + KeyCode::Delete, + KeyModifiers::CONTROL | KeyModifiers::SHIFT + )) + ); + } + #[test] fn default_composer_toggle_shortcuts_includes_shift_question_mark() { let runtime = RuntimeKeymap::defaults();