diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-08-04 05:19:40 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-04 05:19:40 +0300 |
| commit | adb50cba9b15583db019bb655119915869e8c44e (patch) | |
| tree | 910180a20322a581436b8d8e88b1279f40e1500e /src | |
| parent | cffc6c4b50178d404fbb939346dab7a33e5ed8d4 (diff) | |
fix(input): preserve shift-tab in pane automation (#2259)
refs #1561
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/api/panes.rs | 18 | ||||
| -rw-r--r-- | src/pane/input.rs | 7 | ||||
| -rw-r--r-- | src/pane/terminal.rs | 39 |
3 files changed, 63 insertions, 1 deletions
diff --git a/src/app/api/panes.rs b/src/app/api/panes.rs index 72798a1b..a5f1cbcb 100644 --- a/src/app/api/panes.rs +++ b/src/app/api/panes.rs @@ -1979,6 +1979,24 @@ mod tests { } #[tokio::test] + async fn api_pane_send_keys_encodes_shift_tab_as_backtab() { + let (mut app, pane_id, mut rx) = app_with_send_key_runtime(1); + + let response = app.handle_api_request(crate::api::schema::Request { + id: "req".into(), + method: crate::api::schema::Method::PaneSendKeys(PaneSendKeysParams { + pane_id, + keys: vec!["shift+tab".into()], + }), + }); + + let success: SuccessResponse = serde_json::from_str(&response).unwrap(); + assert_eq!(success.result, ResponseResult::Ok {}); + assert_eq!(rx.try_recv().unwrap(), bytes::Bytes::from_static(b"\x1b[Z")); + assert!(rx.try_recv().is_err()); + } + + #[tokio::test] async fn api_pane_get_exposes_scroll_metrics() { let (mut app, public_pane_id, pane_id) = app_with_scrollback_runtime(); let runtime = app diff --git a/src/pane/input.rs b/src/pane/input.rs index 38e8138b..2e60bfc8 100644 --- a/src/pane/input.rs +++ b/src/pane/input.rs @@ -13,7 +13,12 @@ pub(super) fn ghostty_key_event_from_terminal_key( crate::ghostty::ffi::GhosttyKeyAction_GHOSTTY_KEY_ACTION_REPEAT } }); - event.set_mods(ghostty_mods_from_key_modifiers(key.modifiers)); + let mut mods = ghostty_mods_from_key_modifiers(key.modifiers); + if matches!(key.code, crossterm::event::KeyCode::BackTab) { + // Ghostty represents backtab as Tab with Shift rather than a distinct key. + mods |= crate::ghostty::MOD_SHIFT; + } + event.set_mods(mods); event.set_key(ghostty_key_from_crossterm_key_code( key.code, key.shifted_codepoint, diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index b90c63f1..d4f167a9 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -3857,6 +3857,45 @@ mod tests { } #[test] + fn ghostty_backtab_preserves_shift_across_keyboard_protocols() { + for (kitty_flags, expected) in [ + (None, b"\x1b[Z".as_slice()), + (Some(1), b"\x1b[9;2u".as_slice()), + ] { + let (tx, _rx) = mpsc::channel(4); + let mut terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); + if let Some(flags) = kitty_flags { + terminal.write(format!("\x1b[>{flags}u").as_bytes()); + } + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + let protocol = pane.keyboard_protocol().unwrap(); + + for modifiers in [ + crossterm::event::KeyModifiers::empty(), + crossterm::event::KeyModifiers::SHIFT, + ] { + let encoded = pane.encode_terminal_key( + crate::input::TerminalKey::new(crossterm::event::KeyCode::BackTab, modifiers), + protocol, + ); + assert_eq!(encoded, expected, "backtab with modifiers {modifiers:?}"); + } + } + + let (tx, _rx) = mpsc::channel(4); + let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + let encoded = pane.encode_terminal_key( + crate::input::TerminalKey::new( + crossterm::event::KeyCode::Tab, + crossterm::event::KeyModifiers::empty(), + ), + crate::input::KeyboardProtocol::Legacy, + ); + assert_eq!(encoded, b"\t"); + } + + #[test] fn ghostty_enter_backspace_release_in_legacy_pane_emits_nothing() { let (tx, _rx) = mpsc::channel(4); let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); |
