diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-14 02:17:56 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-14 02:17:56 +0300 |
| commit | 8cac2c0df47af927b1c467fb8449d8f84e210407 (patch) | |
| tree | 8682565afbf07b3ad5d4c6811f30c93cbc6e9cd1 /src/app/input.rs | |
| parent | edaa3a1a092763cb25fd9e21772c95ee56a75a15 (diff) | |
feat support double prefix passthrough
Diffstat (limited to 'src/app/input.rs')
| -rw-r--r-- | src/app/input.rs | 115 |
1 files changed, 109 insertions, 6 deletions
diff --git a/src/app/input.rs b/src/app/input.rs index c3717439..347d8e41 100644 --- a/src/app/input.rs +++ b/src/app/input.rs @@ -164,12 +164,12 @@ impl App { pub(super) async fn handle_key(&mut self, key: TerminalKey) { match self.state.mode { Mode::Terminal => self.handle_terminal_key(key).await, + Mode::Navigate => self.handle_navigate_key(key), _ => { let key = AppKey::from(key); match self.state.mode { Mode::Onboarding => self.handle_onboarding_key(key), Mode::ReleaseNotes => self.handle_release_notes_key(key), - Mode::Navigate => self.handle_navigate_key(key), Mode::RenameWorkspace | Mode::RenameTab => { handle_rename_key(&mut self.state, key) } @@ -179,7 +179,7 @@ impl App { Mode::Settings => self.handle_settings_key(key), Mode::GlobalMenu => handle_global_menu_key(&mut self.state, key), Mode::KeybindHelp => handle_keybind_help_key(&mut self.state, key), - Mode::Terminal => unreachable!(), + Mode::Terminal | Mode::Navigate => unreachable!(), } } } @@ -261,12 +261,18 @@ impl App { } } - fn handle_navigate_key(&mut self, key: AppKey) { + fn handle_navigate_key(&mut self, raw_key: TerminalKey) { + let key = AppKey::from(raw_key); self.state.update_dismissed = true; - if key.matches_binding(self.state.prefix_code, self.state.prefix_mods) - || key.code() == KeyCode::Esc - { + if key.matches_binding(self.state.prefix_code, self.state.prefix_mods) { + if !self.pass_through_key_to_focused_pane(raw_key) { + leave_navigate_mode(&mut self.state); + } + return; + } + + if key.code() == KeyCode::Esc { leave_navigate_mode(&mut self.state); return; } @@ -424,6 +430,26 @@ impl App { } } + fn pass_through_key_to_focused_pane(&mut self, key: TerminalKey) -> bool { + let Some(ws) = self.state.active.and_then(|i| self.state.workspaces.get(i)) else { + return false; + }; + let Some(rt) = ws.focused_runtime() else { + return false; + }; + + let bytes = rt.encode_terminal_key(key); + if bytes.is_empty() { + return false; + } + if rt.try_send_bytes(Bytes::from(bytes)).is_err() { + return false; + } + + self.state.mode = Mode::Terminal; + true + } + async fn handle_terminal_key(&mut self, key: TerminalKey) { self.state.clear_selection(); self.state.update_dismissed = true; @@ -3609,6 +3635,83 @@ mod tests { } #[tokio::test] + async fn double_prefix_passes_prefix_through_to_focused_pane() { + let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel(); + let mut app = App::new( + &Config::default(), + true, + None, + None, + api_rx, + crate::api::EventHub::default(), + ); + let mut workspace = Workspace::test_new("test"); + let pane_id = workspace.tabs[0].root_pane; + let (runtime, mut rx) = + crate::pane::PaneRuntime::test_with_screen_bytes_and_receiver(20, 5, b""); + let expected = runtime + .encode_terminal_key(TerminalKey::new(KeyCode::Char('a'), KeyModifiers::CONTROL)); + workspace.tabs[0].runtimes.insert(pane_id, runtime); + + app.state.workspaces = vec![workspace]; + app.state.active = Some(0); + app.state.selected = 0; + app.state.mode = Mode::Terminal; + app.state.prefix_code = KeyCode::Char('a'); + app.state.prefix_mods = KeyModifiers::CONTROL; + + app.handle_key(TerminalKey::new(KeyCode::Char('a'), KeyModifiers::CONTROL)) + .await; + assert_eq!(app.state.mode, Mode::Navigate); + + app.handle_key(TerminalKey::new(KeyCode::Char('a'), KeyModifiers::CONTROL)) + .await; + assert_eq!(app.state.mode, Mode::Terminal); + + let forwarded = rx.try_recv().expect("prefix bytes should be forwarded"); + assert_eq!(forwarded.as_ref(), expected.as_slice()); + } + + #[tokio::test] + async fn double_shifted_prefix_preserves_shifted_codepoint_when_forwarded() { + let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel(); + let mut app = App::new( + &Config::default(), + true, + None, + None, + api_rx, + crate::api::EventHub::default(), + ); + let mut workspace = Workspace::test_new("test"); + let pane_id = workspace.tabs[0].root_pane; + let (runtime, mut rx) = + crate::pane::PaneRuntime::test_with_screen_bytes_and_receiver(20, 5, b""); + let prefix = TerminalKey::new(KeyCode::Char('/'), KeyModifiers::SHIFT) + .with_shifted_codepoint('?' as u32); + let expected = runtime.encode_terminal_key(prefix); + workspace.tabs[0].runtimes.insert(pane_id, runtime); + + app.state.workspaces = vec![workspace]; + app.state.active = Some(0); + app.state.selected = 0; + app.state.mode = Mode::Terminal; + app.state.prefix_code = KeyCode::Char('?'); + app.state.prefix_mods = KeyModifiers::empty(); + + app.handle_key(prefix).await; + assert_eq!(app.state.mode, Mode::Navigate); + + app.handle_key(prefix).await; + assert_eq!(app.state.mode, Mode::Terminal); + + let forwarded = rx + .try_recv() + .expect("shifted prefix bytes should be forwarded"); + assert_eq!(forwarded.as_ref(), expected.as_slice()); + } + + #[tokio::test] async fn custom_command_runs_from_prefix_key_in_navigate_mode() { let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel(); let mut app = App::new( |
