feat(tui/input): support macOS Cmd shortcuts in composer

- Cmd+Left/Right jump to beginning/end of line
- Cmd+Backspace deletes the entire current line contents (preserves newline)
- Treat Shift+Left/Right as normal cursor moves (no selection mode)
- Keep Option+Delete (Alt+Backspace) and Option+Arrow word‑wise behavior
  as before for parity with common macOS editors

This aligns the TUI composer with expected macOS editing shortcuts and
mirrors Codex behavior. Closes #54.
This commit is contained in:
James Peter
2025-09-08 10:39:05 +10:00
parent a78a225644
commit 7f021e37e4
+44 -10
View File
@@ -285,6 +285,33 @@ impl TextArea {
} => {
self.delete_backward_word();
}
// macOS-like shortcuts (when terminals report the Command key as SUPER):
// Cmd+Left -> move to beginning of line
// Cmd+Right -> move to end of line
// Cmd+Backspace -> delete entire current line contents
#[allow(unreachable_patterns)]
KeyEvent { code: KeyCode::Left, modifiers, .. }
if modifiers.bits() & (KeyModifiers::ALT | KeyModifiers::CONTROL).bits() == 0
&& { #[allow(deprecated)] { modifiers.bits() & (crossterm::event::KeyModifiers::from_bits_truncate(0b0000_1000).bits()) } } != 0 =>
{
// Treat unknown extra modifier bit (commonly SUPER/Cmd) as line home.
self.move_cursor_to_beginning_of_line(false);
}
#[allow(unreachable_patterns)]
KeyEvent { code: KeyCode::Right, modifiers, .. }
if modifiers.bits() & (KeyModifiers::ALT | KeyModifiers::CONTROL).bits() == 0
&& { #[allow(deprecated)] { modifiers.bits() & (crossterm::event::KeyModifiers::from_bits_truncate(0b0000_1000).bits()) } } != 0 =>
{
// Treat unknown extra modifier bit (commonly SUPER/Cmd) as line end.
self.move_cursor_to_end_of_line(false);
}
#[allow(unreachable_patterns)]
KeyEvent { code: KeyCode::Backspace, modifiers, .. }
if { #[allow(deprecated)] { modifiers.bits() & (crossterm::event::KeyModifiers::from_bits_truncate(0b0000_1000).bits()) } } != 0 =>
{
// Treat Cmd+Backspace as delete entire line contents.
self.delete_entire_line();
}
// Meta-b -> move to beginning of previous word
// Meta-f -> move to end of next word
// Many terminals map Option (macOS) to Alt. Some send Alt|Shift, so match contains(ALT).
@@ -318,18 +345,14 @@ impl TextArea {
}
// Cursor movement
KeyEvent {
code: KeyCode::Left,
modifiers: KeyModifiers::NONE,
..
} => {
KeyEvent { code: KeyCode::Left, modifiers, .. }
if modifiers == KeyModifiers::NONE || modifiers == KeyModifiers::SHIFT =>
{
self.move_cursor_left();
}
KeyEvent {
code: KeyCode::Right,
modifiers: KeyModifiers::NONE,
..
} => {
KeyEvent { code: KeyCode::Right, modifiers, .. }
if modifiers == KeyModifiers::NONE || modifiers == KeyModifiers::SHIFT =>
{
self.move_cursor_right();
}
KeyEvent {
@@ -473,6 +496,17 @@ impl TextArea {
}
}
/// Delete the entire contents of the current logical line, preserving the newline.
/// Leaves the cursor at the beginning of the line.
pub fn delete_entire_line(&mut self) {
let bol = self.beginning_of_current_line();
let eol = self.end_of_current_line();
if bol < eol {
self.replace_range(bol..eol, "");
}
self.set_cursor(bol);
}
/// Move the cursor left by a single grapheme cluster.
pub fn move_cursor_left(&mut self) {
self.cursor_pos = self.prev_atomic_boundary(self.cursor_pos);