Implement redraw debounce (#1599)
## Summary - debouce redraw events so repeated requests don't overwhelm the terminal - add `RequestRedraw` event and schedule redraws after 100ms ## Testing - `cargo clippy --tests` - `cargo test` *(fails: Sandbox Denied errors in landlock tests)* ------ https://chatgpt.com/codex/tasks/task_i_68792a65b8b483218ec90a8f68746cd8 --------- Co-authored-by: Michael Bolin <mbolin@openai.com>
This commit is contained in:
@@ -18,8 +18,15 @@ use crossterm::event::KeyEvent;
|
|||||||
use crossterm::event::MouseEvent;
|
use crossterm::event::MouseEvent;
|
||||||
use crossterm::event::MouseEventKind;
|
use crossterm::event::MouseEventKind;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::Mutex;
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// Time window for debouncing redraw requests.
|
||||||
|
const REDRAW_DEBOUNCE: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
/// Top-level application state: which full-screen view is currently active.
|
/// Top-level application state: which full-screen view is currently active.
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
@@ -46,6 +53,9 @@ pub(crate) struct App<'a> {
|
|||||||
|
|
||||||
file_search: FileSearchManager,
|
file_search: FileSearchManager,
|
||||||
|
|
||||||
|
/// True when a redraw has been scheduled but not yet executed.
|
||||||
|
pending_redraw: Arc<Mutex<bool>>,
|
||||||
|
|
||||||
/// Stored parameters needed to instantiate the ChatWidget later, e.g.,
|
/// Stored parameters needed to instantiate the ChatWidget later, e.g.,
|
||||||
/// after dismissing the Git-repo warning.
|
/// after dismissing the Git-repo warning.
|
||||||
chat_args: Option<ChatWidgetArgs>,
|
chat_args: Option<ChatWidgetArgs>,
|
||||||
@@ -70,6 +80,7 @@ impl App<'_> {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let (app_event_tx, app_event_rx) = channel();
|
let (app_event_tx, app_event_rx) = channel();
|
||||||
let app_event_tx = AppEventSender::new(app_event_tx);
|
let app_event_tx = AppEventSender::new(app_event_tx);
|
||||||
|
let pending_redraw = Arc::new(Mutex::new(false));
|
||||||
let scroll_event_helper = ScrollEventHelper::new(app_event_tx.clone());
|
let scroll_event_helper = ScrollEventHelper::new(app_event_tx.clone());
|
||||||
|
|
||||||
// Spawn a dedicated thread for reading the crossterm event loop and
|
// Spawn a dedicated thread for reading the crossterm event loop and
|
||||||
@@ -83,7 +94,7 @@ impl App<'_> {
|
|||||||
app_event_tx.send(AppEvent::KeyEvent(key_event));
|
app_event_tx.send(AppEvent::KeyEvent(key_event));
|
||||||
}
|
}
|
||||||
crossterm::event::Event::Resize(_, _) => {
|
crossterm::event::Event::Resize(_, _) => {
|
||||||
app_event_tx.send(AppEvent::Redraw);
|
app_event_tx.send(AppEvent::RequestRedraw);
|
||||||
}
|
}
|
||||||
crossterm::event::Event::Mouse(MouseEvent {
|
crossterm::event::Event::Mouse(MouseEvent {
|
||||||
kind: MouseEventKind::ScrollUp,
|
kind: MouseEventKind::ScrollUp,
|
||||||
@@ -152,6 +163,7 @@ impl App<'_> {
|
|||||||
app_state,
|
app_state,
|
||||||
config,
|
config,
|
||||||
file_search,
|
file_search,
|
||||||
|
pending_redraw,
|
||||||
chat_args,
|
chat_args,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,6 +174,29 @@ impl App<'_> {
|
|||||||
self.app_event_tx.clone()
|
self.app_event_tx.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Schedule a redraw if one is not already pending.
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
fn schedule_redraw(&self) {
|
||||||
|
{
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let mut flag = self.pending_redraw.lock().unwrap();
|
||||||
|
if *flag {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*flag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx = self.app_event_tx.clone();
|
||||||
|
let pending_redraw = self.pending_redraw.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
thread::sleep(REDRAW_DEBOUNCE);
|
||||||
|
tx.send(AppEvent::Redraw);
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let mut f = pending_redraw.lock().unwrap();
|
||||||
|
*f = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn run(
|
pub(crate) fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
terminal: &mut tui::Tui,
|
terminal: &mut tui::Tui,
|
||||||
@@ -169,10 +204,13 @@ impl App<'_> {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Insert an event to trigger the first render.
|
// Insert an event to trigger the first render.
|
||||||
let app_event_tx = self.app_event_tx.clone();
|
let app_event_tx = self.app_event_tx.clone();
|
||||||
app_event_tx.send(AppEvent::Redraw);
|
app_event_tx.send(AppEvent::RequestRedraw);
|
||||||
|
|
||||||
while let Ok(event) = self.app_event_rx.recv() {
|
while let Ok(event) = self.app_event_rx.recv() {
|
||||||
match event {
|
match event {
|
||||||
|
AppEvent::RequestRedraw => {
|
||||||
|
self.schedule_redraw();
|
||||||
|
}
|
||||||
AppEvent::Redraw => {
|
AppEvent::Redraw => {
|
||||||
self.draw_next_frame(terminal)?;
|
self.draw_next_frame(terminal)?;
|
||||||
}
|
}
|
||||||
@@ -249,7 +287,7 @@ impl App<'_> {
|
|||||||
Vec::new(),
|
Vec::new(),
|
||||||
));
|
));
|
||||||
self.app_state = AppState::Chat { widget: new_widget };
|
self.app_state = AppState::Chat { widget: new_widget };
|
||||||
self.app_event_tx.send(AppEvent::Redraw);
|
self.app_event_tx.send(AppEvent::RequestRedraw);
|
||||||
}
|
}
|
||||||
SlashCommand::ToggleMouseMode => {
|
SlashCommand::ToggleMouseMode => {
|
||||||
if let Err(e) = mouse_capture.toggle() {
|
if let Err(e) = mouse_capture.toggle() {
|
||||||
@@ -336,7 +374,7 @@ impl App<'_> {
|
|||||||
args.initial_images,
|
args.initial_images,
|
||||||
));
|
));
|
||||||
self.app_state = AppState::Chat { widget };
|
self.app_state = AppState::Chat { widget };
|
||||||
self.app_event_tx.send(AppEvent::Redraw);
|
self.app_event_tx.send(AppEvent::RequestRedraw);
|
||||||
}
|
}
|
||||||
GitWarningOutcome::Quit => {
|
GitWarningOutcome::Quit => {
|
||||||
self.app_event_tx.send(AppEvent::ExitRequest);
|
self.app_event_tx.send(AppEvent::ExitRequest);
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ use crate::slash_command::SlashCommand;
|
|||||||
pub(crate) enum AppEvent {
|
pub(crate) enum AppEvent {
|
||||||
CodexEvent(Event),
|
CodexEvent(Event),
|
||||||
|
|
||||||
|
/// Request a redraw which will be debounced by the [`App`].
|
||||||
|
RequestRedraw,
|
||||||
|
|
||||||
|
/// Actually draw the next frame.
|
||||||
Redraw,
|
Redraw,
|
||||||
|
|
||||||
KeyEvent(KeyEvent),
|
KeyEvent(KeyEvent),
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ impl BottomPane<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn request_redraw(&self) {
|
pub(crate) fn request_redraw(&self) {
|
||||||
self.app_event_tx.send(AppEvent::Redraw)
|
self.app_event_tx.send(AppEvent::RequestRedraw)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true when a popup inside the composer is visible.
|
/// Returns true when a popup inside the composer is visible.
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ impl ChatWidget<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn request_redraw(&mut self) {
|
fn request_redraw(&mut self) {
|
||||||
self.app_event_tx.send(AppEvent::Redraw);
|
self.app_event_tx.send(AppEvent::RequestRedraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_diff_output(&mut self, diff_output: String) {
|
pub(crate) fn add_diff_output(&mut self, diff_output: String) {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ impl StatusIndicatorWidget {
|
|||||||
std::thread::sleep(Duration::from_millis(200));
|
std::thread::sleep(Duration::from_millis(200));
|
||||||
counter = counter.wrapping_add(1);
|
counter = counter.wrapping_add(1);
|
||||||
frame_idx_clone.store(counter, Ordering::Relaxed);
|
frame_idx_clone.store(counter, Ordering::Relaxed);
|
||||||
app_event_tx_clone.send(AppEvent::Redraw);
|
app_event_tx_clone.send(AppEvent::RequestRedraw);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user