diff options
| author | kangal-bot <kangal@herdr.dev> | 2026-05-18 20:13:19 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-18 20:13:19 +0300 |
| commit | fa0e380e627644f269cfbd0ed4cf79f15ff74176 (patch) | |
| tree | c231d9801bef7830ec5600a4a538771d34960562 | |
| parent | f33e1a0faae5e0ecbfd9653f9635d83793c98b95 (diff) | |
fix: preserve alt backspace in panes (#165)
refs #155
Co-authored-by: kangal-bot <kangal-bot@users.noreply.github.com>
| -rw-r--r-- | src/app/input/terminal.rs | 27 | ||||
| -rw-r--r-- | src/input/encode.rs | 15 | ||||
| -rw-r--r-- | src/input/parse.rs | 17 | ||||
| -rw-r--r-- | src/pane/terminal.rs | 14 | ||||
| -rw-r--r-- | src/raw_input.rs | 22 | ||||
| -rw-r--r-- | tests/fixtures/keyboard_protocol_corpus.tsv | 2 | ||||
| -rw-r--r-- | tests/fixtures/macos_terminal_variants.tsv | 3 |
7 files changed, 100 insertions, 0 deletions
diff --git a/src/app/input/terminal.rs b/src/app/input/terminal.rs index a9af902a..b5ceda12 100644 --- a/src/app/input/terminal.rs +++ b/src/app/input/terminal.rs @@ -487,6 +487,33 @@ mod tests { } #[tokio::test] + async fn alt_backspace_is_forwarded_to_focused_pane() { + let mut app = app_for_mouse_test(); + let mut ws = Workspace::test_new("test"); + let pane_id = ws.tabs[0].root_pane; + let pane_infos = ws.tabs[0].layout.panes(Rect::new(0, 0, 80, 24)); + let info = pane_infos[0].clone(); + let (runtime, mut rx) = crate::pane::PaneRuntime::test_with_channel( + info.inner_rect.width, + info.inner_rect.height, + ); + ws.tabs[0].runtimes.insert(pane_id, runtime); + + app.state.workspaces = vec![ws]; + app.state.active = Some(0); + app.state.selected = 0; + app.state.mode = Mode::Terminal; + app.state.view.pane_infos = pane_infos; + + let key = crate::input::parse_terminal_key_sequence("\x1b\x7f").unwrap(); + app.handle_terminal_key_headless(key); + + let bytes = rx.try_recv().unwrap(); + assert_eq!(bytes.as_ref(), b"\x1b\x7f"); + assert!(rx.try_recv().is_err()); + } + + #[tokio::test] async fn page_up_scrolls_plain_shell_pane() { let mut app = app_for_mouse_test(); let mut ws = Workspace::test_new("test"); diff --git a/src/input/encode.rs b/src/input/encode.rs index 81454ddc..ea7949aa 100644 --- a/src/input/encode.rs +++ b/src/input/encode.rs @@ -524,6 +524,12 @@ mod tests { } #[test] + fn legacy_alt_backspace_sends_escape_delete() { + let key = KeyEvent::new(KeyCode::Backspace, KeyModifiers::ALT); + assert_eq!(encode_key(key, KeyboardProtocol::Legacy), b"\x1b\x7f"); + } + + #[test] fn application_cursor_keys_use_ss3_sequences() { assert_eq!(encode_cursor_key(KeyCode::Up, true), b"\x1bOA"); assert_eq!(encode_cursor_key(KeyCode::Down, true), b"\x1bOB"); @@ -639,6 +645,15 @@ mod tests { } #[test] + fn kitty_alt_backspace_uses_csi_u() { + let key = KeyEvent::new(KeyCode::Backspace, KeyModifiers::ALT); + assert_eq!( + encode_key(key, KeyboardProtocol::Kitty { flags: 1 }), + b"\x1b[127;3u" + ); + } + + #[test] fn kitty_plain_ctrl_c_uses_csi_u() { let key = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL); assert_eq!( diff --git a/src/input/parse.rs b/src/input/parse.rs index a7d10ab2..367c0f69 100644 --- a/src/input/parse.rs +++ b/src/input/parse.rs @@ -67,6 +67,7 @@ fn parse_legacy_key_sequence(data: &str) -> Option<TerminalKey> { "\r" => Some(TerminalKey::new(KeyCode::Enter, KeyModifiers::empty())), "\t" => Some(TerminalKey::new(KeyCode::Tab, KeyModifiers::empty())), "\x1b" => Some(TerminalKey::new(KeyCode::Esc, KeyModifiers::empty())), + "\x1b\x7f" => Some(TerminalKey::new(KeyCode::Backspace, KeyModifiers::ALT)), "\x7f" => Some(TerminalKey::new(KeyCode::Backspace, KeyModifiers::empty())), _ if data.starts_with('\x1b') => { let rest = data.strip_prefix('\x1b')?; @@ -442,6 +443,15 @@ mod tests { } #[test] + fn parse_kitty_alt_backspace_sequence() { + let key = parse_terminal_key_sequence("\x1b[127;3u").unwrap(); + assert_eq!(key.code, KeyCode::Backspace); + assert_eq!(key.modifiers, KeyModifiers::ALT); + assert_eq!(key.kind, crossterm::event::KeyEventKind::Press); + assert_eq!(key.shifted_codepoint, None); + } + + #[test] fn parse_modify_other_keys_sequence() { let key = parse_terminal_key_sequence("\x1b[27;6;108~").unwrap(); assert_eq!(key.code, KeyCode::Char('l')); @@ -465,6 +475,13 @@ mod tests { } #[test] + fn parse_legacy_alt_backspace_sequence() { + let key = parse_terminal_key_sequence("\x1b\x7f").unwrap(); + assert_eq!(key.code, KeyCode::Backspace); + assert_eq!(key.modifiers, KeyModifiers::ALT); + } + + #[test] fn parse_kitty_modifier_sequence() { let key = parse_terminal_key_sequence("\x1b[57441;2:1u").unwrap(); assert_eq!(key.code, KeyCode::Modifier(ModifierKeyCode::LeftShift)); diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index efe9c7ce..fcbedeff 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -1503,6 +1503,20 @@ mod tests { } #[test] + fn ghostty_kitty_pane_encodes_parsed_legacy_alt_backspace_as_csi_u() { + let (tx, _rx) = mpsc::channel(4); + let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); + let pane = GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap(); + let pane_id = PaneId::from_raw(1); + pane.process_pty_bytes(pane_id, 0, b"\x1b[>1u", &tx); + + let key = crate::input::parse_terminal_key_sequence("\x1b\x7f").unwrap(); + let encoded = pane.encode_terminal_key(key, crate::input::KeyboardProtocol::Legacy); + + assert_eq!(encoded, b"\x1b[127;3u"); + } + + #[test] fn ghostty_key_encoders_are_isolated_per_pane() { let (tx, _rx) = mpsc::channel(4); let first = GhosttyPaneTerminal::new( diff --git a/src/raw_input.rs b/src/raw_input.rs index 28a2e561..fa496b95 100644 --- a/src/raw_input.rs +++ b/src/raw_input.rs @@ -674,6 +674,26 @@ mod tests { } #[test] + fn parses_legacy_alt_backspace() { + let (RawInputEvent::Key(key), consumed) = extract_one_event(b"\x1b\x7f").unwrap() else { + panic!("expected key"); + }; + assert_eq!(consumed, 2); + assert_eq!(key.code, KeyCode::Backspace); + assert_eq!(key.modifiers, KeyModifiers::ALT); + } + + #[test] + fn parses_kitty_alt_backspace() { + let (RawInputEvent::Key(key), consumed) = extract_one_event(b"\x1b[127;3u").unwrap() else { + panic!("expected key"); + }; + assert_eq!(consumed, 8); + assert_eq!(key.code, KeyCode::Backspace); + assert_eq!(key.modifiers, KeyModifiers::ALT); + } + + #[test] fn parses_enhanced_pageup_press() { let (RawInputEvent::Key(key), consumed) = extract_one_event(b"\x1b[5;1:1~").unwrap() else { panic!("expected key"); @@ -704,6 +724,8 @@ mod tests { (b"\x7f", KeyCode::Backspace, KeyModifiers::empty()), (b"\x1b[A", KeyCode::Up, KeyModifiers::empty()), (b"\x1b[1;3A", KeyCode::Up, KeyModifiers::ALT), + (b"\x1b\x7f", KeyCode::Backspace, KeyModifiers::ALT), + (b"\x1b[127;3u", KeyCode::Backspace, KeyModifiers::ALT), (b"\x1b[57420;1u", KeyCode::Down, KeyModifiers::empty()), (b"\x1b[57423;1u", KeyCode::Home, KeyModifiers::empty()), (b"\x1b[49:33;2:1u", KeyCode::Char('1'), KeyModifiers::SHIFT), diff --git a/tests/fixtures/keyboard_protocol_corpus.tsv b/tests/fixtures/keyboard_protocol_corpus.tsv index f9b1d734..a709d4d9 100644 --- a/tests/fixtures/keyboard_protocol_corpus.tsv +++ b/tests/fixtures/keyboard_protocol_corpus.tsv @@ -4,6 +4,7 @@ legacy_ctrl_z 1a char:z control press legacy_enter 0d enter - press legacy_tab 09 tab - press legacy_backspace 7f backspace - press +legacy_alt_backspace 1b7f backspace alt press legacy_up 1b5b41 up - press legacy_home 1b5b48 home - press xterm_alt_up 1b5b313b3341 up alt press @@ -12,6 +13,7 @@ xterm_shift_pageup 1b5b353b327e pageup shift press ghostty_enhanced_up_press 1b5b313b313a3141 up - press ghostty_enhanced_up_release 1b5b313b313a3341 up - release modify_other_keys_ctrl_shift_l 1b5b32373b363b3130387e char:l control+shift press +kitty_alt_backspace 1b5b3132373b3375 backspace alt press kitty_shift_letter 1b5b3130383a37363b323a3175 char:l shift press 76 kitty_shift_symbol 1b5b34393a33333b323a3175 char:1 shift press 33 kitty_release_letter 1b5b3130383a37363b323a3375 char:l shift release 76 diff --git a/tests/fixtures/macos_terminal_variants.tsv b/tests/fixtures/macos_terminal_variants.tsv index 24d3840e..c2fd8b53 100644 --- a/tests/fixtures/macos_terminal_variants.tsv +++ b/tests/fixtures/macos_terminal_variants.tsv @@ -1,13 +1,16 @@ # source key bytes_hex code modifiers kind shifted_codepoint +ghostty-macos alt+backspace 1b7f backspace alt press iterm2-macos alt+up 1b5b313b3341 up alt press iterm2-macos alt+down 1b5b313b3342 down alt press iterm2-macos alt+left 1b62 char:b alt press iterm2-macos alt+right 1b66 char:f alt press +iterm2-macos alt+backspace 1b7f backspace alt press iterm2-macos shift+enter 0d enter - press terminal-app-macos alt+up 1b1b5b41 up alt press terminal-app-macos alt+down 1b1b5b42 down alt press terminal-app-macos alt+left 1b62 char:b alt press terminal-app-macos alt+right 1b66 char:f alt press +terminal-app-macos alt+backspace 1b7f backspace alt press terminal-app-macos shift+enter 0d enter - press terminal-app-tmux alt+up 1b1b5b41 up alt press terminal-app-tmux alt+down 1b1b5b42 down alt press |
