aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-03-30 04:12:56 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-03-30 04:12:56 +0300
commitca14500717864b1c9b1885cbbc6a451a19b9caf8 (patch)
treec45883c9633e2275136283b75ae3e41b353073da /src/app
parentaee654578408c7fbdb1640d2dca8eaea4ec1754e (diff)
refactor keyboard protocol parsing and forwarding
Diffstat (limited to 'src/app')
-rw-r--r--src/app/input.rs41
-rw-r--r--src/app/mod.rs60
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,