diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 4d67ce0add..34959f83ba 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -3385,12 +3385,34 @@ impl App { frame_requester: &tui::FrameRequester, resize_reflow_mode: tui::ResizeReflowMode, ) -> bool { + let pending_reflow_before = self.transcript_reflow.has_pending_reflow(); let width = self.transcript_reflow.note_width(size.width); + tracing::info!( + event = "resize_width_observed", + cols = size.width, + rows = size.height, + last_known_cols = last_known_screen_size.width, + last_known_rows = last_known_screen_size.height, + width_changed = width.changed, + width_initialized = width.initialized, + pending_reflow_before, + resize_reflow_mode = ?resize_reflow_mode, + "observed terminal width during draw pre-render" + ); if width.changed { self.chat_widget.on_terminal_resize(size.width); match resize_reflow_mode { tui::ResizeReflowMode::Debounced => { - if self.schedule_resize_reflow() { + let due_now = self.schedule_resize_reflow(); + tracing::info!( + event = "resize_reflow_scheduled_debounced", + cols = size.width, + rows = size.height, + due_now, + debounce_ms = TRANSCRIPT_REFLOW_DEBOUNCE.as_millis(), + "scheduled debounced resize reflow" + ); + if due_now { frame_requester.schedule_frame(); } else { frame_requester.schedule_frame_in(TRANSCRIPT_REFLOW_DEBOUNCE); @@ -3451,7 +3473,16 @@ impl App { let Some(deadline) = self.transcript_reflow.pending_until() else { return Ok(()); }; - if Instant::now() < deadline || self.overlay.is_some() { + let now = Instant::now(); + let due = now >= deadline; + let overlay_active = self.overlay.is_some(); + if !due || overlay_active { + tracing::info!( + event = "resize_reflow_deferred", + due, + overlay_active, + "resize reflow remains pending" + ); return Ok(()); } @@ -3463,7 +3494,18 @@ impl App { let reflow_ran_during_stream = !self.transcript_cells.is_empty() && self.should_mark_reflow_as_stream_time(); + tracing::info!( + event = "resize_reflow_started", + transcript_cells = self.transcript_cells.len(), + reflow_ran_during_stream, + "running resize reflow" + ); self.reflow_transcript_now(tui)?; + tracing::info!( + event = "resize_reflow_completed", + reflow_ran_during_stream, + "completed resize reflow" + ); if reflow_ran_during_stream { self.transcript_reflow.mark_ran_during_stream(); @@ -4236,19 +4278,38 @@ impl App { event: TuiEvent, ) -> Result { let is_resize = matches!(event, TuiEvent::Resize); + let render_trigger = match &event { + TuiEvent::Draw => Some("draw"), + TuiEvent::Resize => Some("resize"), + TuiEvent::Key(_) | TuiEvent::Paste(_) => None, + }; if is_resize { let terminal_size = tui.terminal.size().ok(); tracing::info!( event = "tui_resize_event_handled", cols = terminal_size.map(|size| size.width), rows = terminal_size.map(|size| size.height), + terminal_name = ?tui.terminal_name(), resize_reflow_mode = ?tui.resize_reflow_mode(), + target_os = std::env::consts::OS, + is_windows = cfg!(windows), + is_macos = cfg!(target_os = "macos"), "handling TUI resize event with forced full repaint" ); tui.force_full_repaint(); } - if matches!(event, TuiEvent::Draw | TuiEvent::Resize) { + if let Some(render_trigger) = render_trigger { + tracing::info!( + event = "tui_render_pre_render_started", + render_trigger, + "starting draw pre-render handling" + ); self.handle_draw_pre_render(tui)?; + tracing::info!( + event = "tui_render_pre_render_completed", + render_trigger, + "completed draw pre-render handling" + ); } if self.overlay.is_some() { @@ -4267,6 +4328,7 @@ impl App { self.chat_widget.handle_paste(pasted); } TuiEvent::Draw | TuiEvent::Resize => { + let render_trigger = render_trigger.unwrap_or("unknown"); if self.backtrack_render_pending { self.backtrack_render_pending = false; self.render_transcript_once(tui); @@ -4280,15 +4342,30 @@ impl App { } // Allow widgets to process any pending timers before rendering. self.chat_widget.pre_draw_tick(); - tui.draw( - self.chat_widget.desired_height(tui.terminal.size()?.width), - |frame| { - self.chat_widget.render(frame.area(), frame.buffer); - if let Some((x, y)) = self.chat_widget.cursor_pos(frame.area()) { - frame.set_cursor_position((x, y)); - } - }, - )?; + let terminal_size = tui.terminal.size()?; + let desired_height = self.chat_widget.desired_height(terminal_size.width); + tracing::info!( + event = "tui_render_started", + render_trigger, + cols = terminal_size.width, + rows = terminal_size.height, + desired_height, + "starting TUI render" + ); + tui.draw(desired_height, |frame| { + self.chat_widget.render(frame.area(), frame.buffer); + if let Some((x, y)) = self.chat_widget.cursor_pos(frame.area()) { + frame.set_cursor_position((x, y)); + } + })?; + tracing::info!( + event = "tui_render_completed", + render_trigger, + cols = terminal_size.width, + rows = terminal_size.height, + desired_height, + "completed TUI render" + ); if self.chat_widget.external_editor_state() == ExternalEditorState::Requested { self.chat_widget .set_external_editor_state(ExternalEditorState::Active); diff --git a/codex-rs/tui/src/tui.rs b/codex-rs/tui/src/tui.rs index 3b29e1ce67..1dc65c81b7 100644 --- a/codex-rs/tui/src/tui.rs +++ b/codex-rs/tui/src/tui.rs @@ -302,6 +302,7 @@ pub struct Tui { notification_backend: Option, notification_condition: NotificationCondition, is_zellij: bool, + terminal_name: TerminalName, resize_reflow_mode: ResizeReflowMode, force_full_repaint: bool, // When false, enter_alt_screen() becomes a no-op (for Zellij scrollback support) @@ -334,6 +335,19 @@ impl Tui { mode = ?resize_reflow_mode, terminal_name = ?terminal_info.name, term_program = ?terminal_info.term_program, + term_program_version = ?terminal_info.version, + term = ?terminal_info.term, + colorterm = ?std::env::var("COLORTERM").ok(), + target_os = std::env::consts::OS, + target_arch = std::env::consts::ARCH, + is_windows = cfg!(windows), + is_macos = cfg!(target_os = "macos"), + vscode_ipc_hook_cli_present = std::env::var_os("VSCODE_IPC_HOOK_CLI").is_some(), + vscode_injection_present = std::env::var_os("VSCODE_INJECTION").is_some(), + vscode_shell_login_present = std::env::var_os("VSCODE_SHELL_LOGIN").is_some(), + vscode_cwd_present = std::env::var_os("VSCODE_CWD").is_some(), + term_session_id_present = std::env::var_os("TERM_SESSION_ID").is_some(), + lc_terminal_present = std::env::var_os("LC_TERMINAL").is_some(), "configured terminal resize reflow mode" ); @@ -352,6 +366,7 @@ impl Tui { notification_backend: Some(detect_backend(NotificationMethod::default())), notification_condition: NotificationCondition::default(), is_zellij, + terminal_name: terminal_info.name, resize_reflow_mode, force_full_repaint: false, alt_screen_enabled: true, @@ -384,6 +399,10 @@ impl Tui { self.resize_reflow_mode } + pub(crate) fn terminal_name(&self) -> TerminalName { + self.terminal_name + } + pub fn enhanced_keys_supported(&self) -> bool { self.enhanced_keys_supported } @@ -637,7 +656,26 @@ impl Tui { self.pending_viewport_area()? }; - stdout().sync_update(|_| { + let terminal_size = self.terminal.size().ok(); + let viewport_area = self.terminal.viewport_area; + tracing::info!( + event = "terminal_draw_started", + requested_height = height, + force_full_repaint, + pending_viewport_area = pending_viewport_area.is_some(), + pending_history_lines = self.pending_history_lines.len(), + terminal_cols = terminal_size.map(|size| size.width), + terminal_rows = terminal_size.map(|size| size.height), + viewport_x = viewport_area.x, + viewport_y = viewport_area.y, + viewport_width = viewport_area.width, + viewport_height = viewport_area.height, + terminal_name = ?self.terminal_name, + resize_reflow_mode = ?self.resize_reflow_mode, + "starting terminal draw" + ); + + let draw_result = stdout().sync_update(|_| { #[cfg(unix)] if let Some(prepared) = prepared_resume.take() { prepared.apply(&mut self.terminal)?; @@ -683,7 +721,25 @@ impl Tui { terminal.draw(|frame| { draw_fn(frame); }) - })? + })?; + + match &draw_result { + Ok(()) => tracing::info!( + event = "terminal_draw_completed", + requested_height = height, + force_full_repaint, + "completed terminal draw" + ), + Err(err) => tracing::warn!( + event = "terminal_draw_failed", + requested_height = height, + force_full_repaint, + error = %err, + "terminal draw failed" + ), + } + + draw_result } fn pending_viewport_area(&mut self) -> Result> { diff --git a/codex-rs/tui/src/tui/event_stream.rs b/codex-rs/tui/src/tui/event_stream.rs index 4977ad84f0..5eb315f0cd 100644 --- a/codex-rs/tui/src/tui/event_stream.rs +++ b/codex-rs/tui/src/tui/event_stream.rs @@ -242,6 +242,7 @@ pub struct TuiEventStream, resume_stream: WatchStream<()>, resize_watchdog: Option, + terminal_name: TerminalName, terminal_focused: Arc, poll_draw_first: bool, #[cfg(unix)] @@ -263,6 +264,26 @@ impl TuiEventStream { let has_superset_env_markers = has_superset_env_markers(); let enable_resize_watchdog = should_enable_superset_resize_watchdog(&terminal_info, has_superset_env_markers); + tracing::info!( + event = "terminal_resize_event_stream_configured", + terminal_name = ?terminal_info.name, + term_program = ?terminal_info.term_program, + term_program_version = ?terminal_info.version, + term = ?terminal_info.term, + colorterm = ?std::env::var("COLORTERM").ok(), + target_os = std::env::consts::OS, + target_arch = std::env::consts::ARCH, + is_windows = cfg!(windows), + is_macos = cfg!(target_os = "macos"), + vscode_ipc_hook_cli_present = std::env::var_os("VSCODE_IPC_HOOK_CLI").is_some(), + vscode_injection_present = std::env::var_os("VSCODE_INJECTION").is_some(), + vscode_shell_login_present = std::env::var_os("VSCODE_SHELL_LOGIN").is_some(), + vscode_cwd_present = std::env::var_os("VSCODE_CWD").is_some(), + term_session_id_present = std::env::var_os("TERM_SESSION_ID").is_some(), + lc_terminal_present = std::env::var_os("LC_TERMINAL").is_some(), + resize_watchdog_enabled = enable_resize_watchdog, + "configured terminal resize event stream" + ); tracing::info!( event = "superset_resize_watchdog_configured", enabled = enable_resize_watchdog, @@ -279,6 +300,7 @@ impl TuiEventStream { draw_stream: BroadcastStream::new(draw_rx), resume_stream, resize_watchdog, + terminal_name: terminal_info.name, terminal_focused, poll_draw_first: false, #[cfg(unix)] @@ -342,8 +364,22 @@ impl TuiEventStream { /// Poll the draw broadcast stream for the next draw event. Draw events are used to trigger a redraw of the TUI. pub fn poll_draw_event(&mut self, cx: &mut Context<'_>) -> Poll> { match Pin::new(&mut self.draw_stream).poll_next(cx) { - Poll::Ready(Some(Ok(()))) => Poll::Ready(Some(TuiEvent::Draw)), + Poll::Ready(Some(Ok(()))) => { + tracing::info!( + event = "tui_draw_event_received", + terminal_name = ?self.terminal_name, + target_os = std::env::consts::OS, + "received scheduled TUI draw event" + ); + Poll::Ready(Some(TuiEvent::Draw)) + } Poll::Ready(Some(Err(BroadcastStreamRecvError::Lagged(_)))) => { + tracing::warn!( + event = "tui_draw_event_lagged", + terminal_name = ?self.terminal_name, + target_os = std::env::consts::OS, + "scheduled TUI draw event lagged" + ); Poll::Ready(Some(TuiEvent::Draw)) } Poll::Ready(None) => Poll::Ready(None), @@ -388,6 +424,10 @@ impl TuiEventStream { event = "crossterm_resize_event_received", cols, rows, + terminal_name = ?self.terminal_name, + target_os = std::env::consts::OS, + is_windows = cfg!(windows), + is_macos = cfg!(target_os = "macos"), watchdog_enabled = self.resize_watchdog.is_some(), "received crossterm terminal resize event" );