From 5fa7d46ddf4a035288cc5afadfe883a597ac4287 Mon Sep 17 00:00:00 2001 From: Jeremy Rose <172423086+nornagon-openai@users.noreply.github.com> Date: Fri, 22 Aug 2025 16:33:18 -0700 Subject: [PATCH] tui: fix resize on wezterm (#2600) WezTerm doesn't respond to cursor queries during a synchronized update, so resizing was broken there. --- codex-rs/tui/src/tui.rs | 68 +++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/codex-rs/tui/src/tui.rs b/codex-rs/tui/src/tui.rs index 4498f46e..d30e9a6e 100644 --- a/codex-rs/tui/src/tui.rs +++ b/codex-rs/tui/src/tui.rs @@ -138,6 +138,12 @@ enum ResumeAction { RestoreAlt = 2, } +#[cfg(unix)] +enum PreparedResumeAction { + RestoreAltScreen, + RealignViewport(ratatui::layout::Rect), +} + #[cfg(unix)] fn take_resume_action(pending: &AtomicU8) -> ResumeAction { match pending.swap(ResumeAction::None as u8, Ordering::Relaxed) { @@ -333,23 +339,37 @@ impl Tui { } #[cfg(unix)] - fn apply_resume_action(&mut self, action: ResumeAction) -> Result<()> { + fn prepare_resume_action( + &mut self, + action: ResumeAction, + ) -> Result> { match action { ResumeAction::RealignInline => { let cursor_pos = self.terminal.get_cursor_position()?; - self.terminal - .set_viewport_area(ratatui::layout::Rect::new(0, cursor_pos.y, 0, 0)); + Ok(Some(PreparedResumeAction::RealignViewport( + ratatui::layout::Rect::new(0, cursor_pos.y, 0, 0), + ))) } ResumeAction::RestoreAlt => { - // When we're resuming from alt screen, we need to save what the cursor position - // _was_ when we resumed. That way, when we leave the alt screen, we can restore - // the cursor to the new position. if let Ok((_x, y)) = crossterm::cursor::position() && let Some(saved) = self.alt_saved_viewport.as_mut() { saved.y = y; } - let _ = execute!(self.terminal.backend_mut(), EnterAlternateScreen); + Ok(Some(PreparedResumeAction::RestoreAltScreen)) + } + ResumeAction::None => Ok(None), + } + } + + #[cfg(unix)] + fn apply_prepared_resume_action(&mut self, prepared: PreparedResumeAction) -> Result<()> { + match prepared { + PreparedResumeAction::RealignViewport(area) => { + self.terminal.set_viewport_area(area); + } + PreparedResumeAction::RestoreAltScreen => { + execute!(self.terminal.backend_mut(), EnterAlternateScreen)?; if let Ok(size) = self.terminal.size() { self.terminal.set_viewport_area(ratatui::layout::Rect::new( 0, @@ -360,13 +380,10 @@ impl Tui { self.terminal.clear()?; } } - ResumeAction::None => {} } Ok(()) } - // Public suspend() removed; Ctrl+Z is handled internally via event_stream + draw. - /// Enter alternate screen and expand the viewport to full terminal size, saving the current /// inline viewport for restoration when leaving. pub fn enter_alt_screen(&mut self) -> Result<()> { @@ -405,12 +422,13 @@ impl Tui { height: u16, draw_fn: impl FnOnce(&mut custom_terminal::Frame), ) -> Result<()> { - std::io::stdout().sync_update(|_| { - #[cfg(unix)] - { - // Apply any post-resume action before layout/clear/draw. - self.apply_resume_action(take_resume_action(&self.resume_pending))?; - } + // Precompute any viewport updates that need a cursor-position query before entering + // the synchronized update, to avoid racing with the event reader. + let mut pending_viewport_area: Option = None; + #[cfg(unix)] + let mut prepared_resume = + self.prepare_resume_action(take_resume_action(&self.resume_pending))?; + { let terminal = &mut self.terminal; let screen_size = terminal.size()?; let last_known_screen_size = terminal.last_known_screen_size; @@ -419,15 +437,27 @@ impl Tui { let last_known_cursor_pos = terminal.last_known_cursor_pos; if cursor_pos.y != last_known_cursor_pos.y { let cursor_delta = cursor_pos.y as i32 - last_known_cursor_pos.y as i32; - let new_viewport_area = terminal.viewport_area.offset(Offset { x: 0, y: cursor_delta, }); - terminal.set_viewport_area(new_viewport_area); - terminal.clear()?; + pending_viewport_area = Some(new_viewport_area); } } + } + + std::io::stdout().sync_update(|_| { + #[cfg(unix)] + { + if let Some(prepared) = prepared_resume.take() { + self.apply_prepared_resume_action(prepared)?; + } + } + let terminal = &mut self.terminal; + if let Some(new_area) = pending_viewport_area.take() { + terminal.set_viewport_area(new_area); + terminal.clear()?; + } let size = terminal.size()?;