diff options
Diffstat (limited to 'src/app/runtime.rs')
| -rw-r--r-- | src/app/runtime.rs | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/src/app/runtime.rs b/src/app/runtime.rs new file mode 100644 index 00000000..d82f6440 --- /dev/null +++ b/src/app/runtime.rs @@ -0,0 +1,261 @@ +use std::time::Instant; + +use crossterm::terminal; + +use super::{ + auto_updates_enabled, repeat_key_identity, App, Mode, ANIMATION_INTERVAL, + AUTO_UPDATE_CHECK_INTERVAL, GIT_REMOTE_STATUS_REFRESH_INTERVAL, MIN_RENDER_INTERVAL, + RESIZE_POLL_INTERVAL, +}; +use crate::workspace::Workspace; + +impl App { + pub(crate) fn drain_api_requests(&mut self) -> bool { + let mut changed = false; + while let Ok(msg) = self.api_rx.try_recv() { + changed |= self.handle_api_request_message(msg); + } + changed + } + + pub(super) fn handle_api_request_message( + &mut self, + msg: crate::api::ApiRequestMessage, + ) -> bool { + let changed = crate::api::request_changes_ui(&msg.request); + let response = self.handle_api_request(msg.request); + let _ = msg.respond_to.send(response); + changed + } + + pub(super) async fn handle_raw_input_batch( + &mut self, + first: crate::raw_input::RawInputEvent, + ) -> bool { + let mut changed = self.handle_raw_input_event(first).await; + + while let Some(rx) = self.input_rx.as_mut() { + match rx.try_recv() { + Ok(event) => changed |= self.handle_raw_input_event(event).await, + Err(tokio::sync::mpsc::error::TryRecvError::Empty) => break, + Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => { + self.input_rx = None; + break; + } + } + } + + changed + } + + pub(super) async fn handle_raw_input_event( + &mut self, + event: crate::raw_input::RawInputEvent, + ) -> bool { + match event { + crate::raw_input::RawInputEvent::Key(key) => { + let key_id = repeat_key_identity(&key); + match key.kind { + crossterm::event::KeyEventKind::Press => { + if self.state.mode == Mode::Terminal { + self.suppressed_repeat_keys.remove(&key_id); + } else { + self.suppressed_repeat_keys.insert(key_id); + } + self.handle_key(key).await; + true + } + crossterm::event::KeyEventKind::Repeat => { + if self.state.mode == Mode::Terminal + && !self.suppressed_repeat_keys.contains(&key_id) + { + self.handle_key(key).await; + true + } else { + false + } + } + crossterm::event::KeyEventKind::Release => { + self.suppressed_repeat_keys.remove(&key_id); + false + } + } + } + crate::raw_input::RawInputEvent::Paste(text) => { + self.handle_paste(text).await; + true + } + crate::raw_input::RawInputEvent::Mouse(mouse) => { + self.handle_mouse(mouse); + true + } + crate::raw_input::RawInputEvent::HostDefaultColor { kind, color } => { + self.update_host_terminal_theme(kind, color) + } + crate::raw_input::RawInputEvent::Unsupported => false, + } + } + + fn handle_resize_poll(&mut self) -> bool { + let Ok(size) = terminal::size() else { + return false; + }; + if self.last_terminal_size != Some(size) { + self.last_terminal_size = Some(size); + return true; + } + false + } + + pub(crate) fn handle_scheduled_tasks(&mut self, now: Instant) -> bool { + let mut changed = false; + + self.sync_animation_timer(now); + + if now >= self.next_resize_poll { + changed |= self.handle_resize_poll(); + self.next_resize_poll = now + RESIZE_POLL_INTERVAL; + } + + if self + .config_diagnostic_deadline + .is_some_and(|deadline| now >= deadline) + { + self.config_diagnostic_deadline = None; + self.state.config_diagnostic = None; + changed = true; + } + + if self.toast_deadline.is_some_and(|deadline| now >= deadline) { + self.toast_deadline = None; + self.state.toast = None; + changed = true; + } + + if self + .next_animation_tick + .is_some_and(|deadline| now >= deadline) + { + self.state.spinner_tick = self.state.spinner_tick.wrapping_add(1); + self.next_animation_tick = Some(now + ANIMATION_INTERVAL); + changed = true; + } + + if self + .git_refresh_deadline() + .is_some_and(|deadline| now >= deadline) + { + for ws in &mut self.state.workspaces { + ws.refresh_git_ahead_behind(); + } + self.last_git_remote_status_refresh = now; + changed = true; + } + + if self + .next_auto_update_check + .is_some_and(|deadline| now >= deadline) + { + self.run_auto_update_check(); + } + + if self + .session_save_deadline + .is_some_and(|deadline| now >= deadline) + { + self.save_session_now(); + } + + self.sync_animation_timer(now); + changed + } + + pub(crate) fn sync_animation_timer(&mut self, now: Instant) { + if self.agent_panel_has_animation() { + self.next_animation_tick + .get_or_insert(now + ANIMATION_INTERVAL); + } else { + self.next_animation_tick = None; + } + } + + fn agent_panel_has_animation(&self) -> bool { + match self.state.agent_panel_scope { + crate::app::state::AgentPanelScope::CurrentWorkspace => self + .state + .active + .and_then(|idx| self.state.workspaces.get(idx)) + .is_some_and(Workspace::has_working_pane), + crate::app::state::AgentPanelScope::AllWorkspaces => self + .state + .workspaces + .iter() + .any(Workspace::has_working_pane), + } + } + + pub(crate) fn can_render_now(&self, now: Instant) -> bool { + match self.last_render_at { + Some(last_render_at) => now.duration_since(last_render_at) >= MIN_RENDER_INTERVAL, + None => true, + } + } + + pub(crate) fn run_auto_update_check(&mut self) { + if !auto_updates_enabled(self.no_session) { + self.next_auto_update_check = None; + return; + } + + self.next_auto_update_check = self + .state + .update_available + .is_none() + .then_some(Instant::now() + AUTO_UPDATE_CHECK_INTERVAL); + + if self.state.update_available.is_some() { + return; + } + + let update_tx = self.event_tx.clone(); + std::thread::spawn(move || crate::update::auto_update(update_tx)); + } + + pub(crate) fn git_refresh_deadline(&self) -> Option<Instant> { + (!self.state.workspaces.is_empty()) + .then_some(self.last_git_remote_status_refresh + GIT_REMOTE_STATUS_REFRESH_INTERVAL) + } + + pub(crate) fn next_loop_deadline(&self, now: Instant, needs_render: bool) -> Option<Instant> { + let render_deadline = if needs_render { + self.last_render_at + .map(|last_render_at| last_render_at + MIN_RENDER_INTERVAL) + .filter(|deadline| *deadline > now) + } else { + None + }; + + [ + Some(self.next_resize_poll), + self.config_diagnostic_deadline, + self.toast_deadline, + self.next_animation_tick, + self.git_refresh_deadline(), + self.next_auto_update_check, + self.session_save_deadline, + render_deadline, + ] + .into_iter() + .flatten() + .min() + } + + pub(crate) fn drain_internal_events(&mut self) -> bool { + let mut had_event = false; + while let Ok(ev) = self.event_rx.try_recv() { + had_event = true; + self.handle_internal_event(ev); + } + had_event + } +} |
