diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-03-30 04:12:56 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-03-30 04:12:56 +0300 |
| commit | ca14500717864b1c9b1885cbbc6a451a19b9caf8 (patch) | |
| tree | c45883c9633e2275136283b75ae3e41b353073da /src/app | |
| parent | aee654578408c7fbdb1640d2dca8eaea4ec1754e (diff) | |
refactor keyboard protocol parsing and forwarding
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/input.rs | 41 | ||||
| -rw-r--r-- | src/app/mod.rs | 60 |
2 files changed, 73 insertions, 28 deletions
diff --git a/src/app/input.rs b/src/app/input.rs index 0c901be5..68fbe260 100644 --- a/src/app/input.rs +++ b/src/app/input.rs @@ -2,6 +2,8 @@ use bytes::Bytes; use crossterm::event::{KeyCode, KeyEvent, MouseButton, MouseEvent, MouseEventKind}; + +use crate::input::TerminalKey; use ratatui::layout::{Direction, Rect}; use tracing::warn; @@ -18,16 +20,22 @@ use super::App; // --------------------------------------------------------------------------- impl App { - pub(super) async fn handle_key(&mut self, key: KeyEvent) { + pub(super) async fn handle_key(&mut self, key: TerminalKey) { match self.state.mode { - Mode::Onboarding => self.handle_onboarding_key(key), - Mode::Navigate => handle_navigate_key(&mut self.state, key), Mode::Terminal => self.handle_terminal_key(key).await, - Mode::RenameSession => handle_rename_key(&mut self.state, key), - Mode::Resize => handle_resize_key(&mut self.state, key), - Mode::ConfirmClose => handle_confirm_close_key(&mut self.state, key), - Mode::ContextMenu => handle_context_menu_key(&mut self.state, key), - Mode::Settings => self.handle_settings_key(key), + _ => { + let key = key.as_key_event(); + match self.state.mode { + Mode::Onboarding => self.handle_onboarding_key(key), + Mode::Navigate => handle_navigate_key(&mut self.state, key), + Mode::RenameSession => handle_rename_key(&mut self.state, key), + Mode::Resize => handle_resize_key(&mut self.state, key), + Mode::ConfirmClose => handle_confirm_close_key(&mut self.state, key), + Mode::ContextMenu => handle_context_menu_key(&mut self.state, key), + Mode::Settings => self.handle_settings_key(key), + Mode::Terminal => unreachable!(), + } + } } } @@ -122,11 +130,13 @@ impl App { } } - async fn handle_terminal_key(&mut self, key: KeyEvent) { + async fn handle_terminal_key(&mut self, key: TerminalKey) { self.state.clear_selection(); self.state.update_dismissed = true; - if self.state.is_prefix(&key) { + let key_event = key.as_key_event(); + + if self.state.is_prefix(&key_event) { self.state.mode = Mode::Navigate; return; } @@ -134,10 +144,15 @@ impl App { if let Some(ws) = self.state.active.and_then(|i| self.state.workspaces.get(i)) { if let Some(rt) = ws.focused_runtime() { rt.scroll_reset(); - let kitty = rt.kitty_keyboard.load(std::sync::atomic::Ordering::Relaxed); - let bytes = crate::input::encode_key(key, kitty); + let flags = rt + .kitty_keyboard_flags + .load(std::sync::atomic::Ordering::Relaxed); + let bytes = crate::input::encode_terminal_key( + key, + crate::input::KeyboardProtocol::from_kitty_flags(flags), + ); if bytes.is_empty() { - warn!(code = ?key.code, mods = ?key.modifiers, state = ?key.state, "key produced empty encoding"); + warn!(code = ?key_event.code, mods = ?key_event.modifiers, state = ?key_event.state, "key produced empty encoding"); } else { let _ = rt.sender.send(Bytes::from(bytes)).await; } diff --git a/src/app/mod.rs b/src/app/mod.rs index 15ae2b9b..5c5467c5 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -14,7 +14,7 @@ use std::time::{Duration, Instant}; const GIT_REMOTE_STATUS_REFRESH_INTERVAL: Duration = Duration::from_millis(1500); const SIDEBAR_DOUBLE_CLICK_WINDOW: Duration = Duration::from_millis(350); -use crossterm::event::{self, Event, KeyEventKind}; +use crossterm::terminal; use ratatui::layout::Rect; use ratatui::DefaultTerminal; use tokio::sync::mpsc; @@ -35,6 +35,8 @@ pub struct App { event_hub: crate::api::EventHub, last_focus: Option<(usize, crate::layout::PaneId)>, no_session: bool, + input_rx: Option<mpsc::Receiver<crate::raw_input::RawInputEvent>>, + last_terminal_size: Option<(u16, u16)>, config_diagnostic_deadline: Option<Instant>, toast_deadline: Option<Instant>, last_git_remote_status_refresh: Instant, @@ -192,10 +194,16 @@ impl App { event_hub, last_focus, no_session, + input_rx: None, + last_terminal_size: terminal::size().ok(), } } pub async fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { + if self.input_rx.is_none() { + self.input_rx = Some(crate::raw_input::spawn_input_reader()); + } + while !self.state.should_quit { if self .config_diagnostic_deadline @@ -236,18 +244,10 @@ impl App { } self.sync_focus_events(); + self.handle_resize_poll(); + self.drain_raw_input().await; - if event::poll(Duration::from_millis(16))? { - match event::read()? { - Event::Key(key) if key.kind == KeyEventKind::Press => { - self.handle_key(key).await; - } - Event::Paste(text) => self.handle_paste(text).await, - Event::Mouse(mouse) => self.handle_mouse(mouse), - Event::Resize(_, _) => {} - _ => {} - } - } + tokio::time::sleep(Duration::from_millis(16)).await; if self.state.request_complete_onboarding { self.state.request_complete_onboarding = false; @@ -273,6 +273,33 @@ impl App { Ok(()) } + async fn drain_raw_input(&mut self) { + while let Some(rx) = self.input_rx.as_mut() { + match rx.try_recv() { + Ok(crate::raw_input::RawInputEvent::Key(key)) => { + if key.kind == crossterm::event::KeyEventKind::Press { + self.handle_key(key).await; + } + } + Ok(crate::raw_input::RawInputEvent::Paste(text)) => self.handle_paste(text).await, + Ok(crate::raw_input::RawInputEvent::Mouse(mouse)) => self.handle_mouse(mouse), + Ok(crate::raw_input::RawInputEvent::Unsupported) => {} + Err(tokio::sync::mpsc::error::TryRecvError::Empty) => break, + Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => { + self.input_rx = None; + break; + } + } + } + } + + fn handle_resize_poll(&mut self) { + let Ok(size) = terminal::size() else { return }; + if self.last_terminal_size != Some(size) { + self.last_terminal_size = Some(size); + } + } + fn drain_internal_events(&mut self) { while let Ok(ev) = self.event_rx.try_recv() { match &ev { @@ -871,11 +898,14 @@ impl App { }) .unwrap(); }; - let kitty = runtime + let flags = runtime .1 - .kitty_keyboard + .kitty_keyboard_flags .load(std::sync::atomic::Ordering::Relaxed); - let bytes = crate::input::encode_key(key_event, kitty); + let bytes = crate::input::encode_key( + key_event, + crate::input::KeyboardProtocol::from_kitty_flags(flags), + ); if let Err(err) = runtime.0.try_send(Bytes::from(bytes)) { return serde_json::to_string(&ErrorResponse { id: request.id, |
