mirror of
https://github.com/openai/codex.git
synced 2026-11-03 19:53:25 +00:00
docs(tui): clarify resize reflow invariants
This commit is contained in:
@@ -10759,6 +10759,11 @@ impl ChatWidget {
|
||||
self.bottom_pane.is_task_running() || self.is_review_mode
|
||||
}
|
||||
|
||||
/// Return the markdown body width available to an active stream.
|
||||
///
|
||||
/// Streaming controllers render only the message body, while history cells add bullets,
|
||||
/// gutters, or plan padding around that body. Callers pass the reserved columns for that
|
||||
/// wrapper so live output uses the same width that finalized cells will use during reflow.
|
||||
fn current_stream_width(&self, reserved_cols: usize) -> Option<usize> {
|
||||
self.last_rendered_width.get().and_then(|width| {
|
||||
if width == 0 {
|
||||
@@ -10769,6 +10774,11 @@ impl ChatWidget {
|
||||
})
|
||||
}
|
||||
|
||||
/// Update resize-sensitive chat widget state after the terminal width changes.
|
||||
///
|
||||
/// The app calls this even when terminal resize reflow is disabled so live stream wrapping
|
||||
/// remains consistent with the current viewport. Finalized transcript rebuilding stays gated at
|
||||
/// the app layer.
|
||||
pub(crate) fn on_terminal_resize(&mut self, width: u16) {
|
||||
let had_rendered_width = self.last_rendered_width.get().is_some();
|
||||
self.last_rendered_width.set(Some(width as usize));
|
||||
@@ -10790,6 +10800,7 @@ impl ChatWidget {
|
||||
self.stream_controller.is_some()
|
||||
}
|
||||
|
||||
/// Whether a proposed-plan stream is active.
|
||||
pub(crate) fn has_active_plan_stream(&self) -> bool {
|
||||
self.plan_stream_controller.is_some()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
//! Inserts finalized history rows into terminal scrollback.
|
||||
//!
|
||||
//! Codex uses the terminal scrollback itself for finalized chat history, so inserting a history
|
||||
//! cell is an escape-sequence operation rather than a normal ratatui render. The legacy insertion
|
||||
//! path may move the inline viewport when new history arrives. The resize-reflow insertion path is
|
||||
//! deliberately separate because it is replaying already-owned transcript rows after a resize and
|
||||
//! must preserve the viewport position chosen by the reflow draw path.
|
||||
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
||||
@@ -24,6 +24,13 @@ use std::time::Instant;
|
||||
|
||||
use super::StreamState;
|
||||
|
||||
/// Shared source-retaining stream state for assistant and plan output.
|
||||
///
|
||||
/// `raw_source` is the markdown source that has crossed a newline boundary and can be rendered
|
||||
/// deterministically. `rendered_lines` is the current-width render of that source. `enqueued_len`
|
||||
/// tracks how much of that render has been offered to the commit queue, while `emitted_len` tracks
|
||||
/// how much has actually reached history cells. Keeping those counters separate lets width changes
|
||||
/// rebuild pending output without duplicating lines that are already visible.
|
||||
struct StreamCore {
|
||||
state: StreamState,
|
||||
width: Option<usize>,
|
||||
@@ -166,6 +173,11 @@ impl StreamCore {
|
||||
);
|
||||
}
|
||||
|
||||
/// Append newly rendered lines to the live queue without replaying already queued rows.
|
||||
///
|
||||
/// Width changes can make the rendered line count smaller than the previous queue boundary; in
|
||||
/// that case the only safe option is rebuilding the queue from `emitted_len`, because slicing
|
||||
/// from the stale `enqueued_len` would skip pending source.
|
||||
fn sync_queue_to_render(&mut self) -> bool {
|
||||
let target_len = self.rendered_lines.len().max(self.emitted_len);
|
||||
if target_len < self.enqueued_len {
|
||||
@@ -183,6 +195,10 @@ impl StreamCore {
|
||||
true
|
||||
}
|
||||
|
||||
/// Rebuild the pending live queue from the current render and current emitted position.
|
||||
///
|
||||
/// This is used when resize invalidates queued wrapping. It must never enqueue rows before
|
||||
/// `emitted_len`, because those rows have already been inserted into terminal history.
|
||||
fn rebuild_queue_from_render(&mut self) {
|
||||
self.state.clear_queue();
|
||||
let target_len = self.rendered_lines.len().max(self.emitted_len);
|
||||
|
||||
+21
-1
@@ -444,9 +444,16 @@ fn set_panic_hook() {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TuiEvent {
|
||||
/// A terminal key event after focus, paste, and protocol bookkeeping has been handled.
|
||||
Key(KeyEvent),
|
||||
/// A bracketed paste payload normalized by the app layer before it reaches the composer.
|
||||
Paste(String),
|
||||
/// A terminal size notification that should be handled as resize-sensitive draw work.
|
||||
///
|
||||
/// Resize is separate from `Draw` so the app can run feature-gated pre-render logic without
|
||||
/// changing the default draw path for scheduled frames.
|
||||
Resize,
|
||||
/// A scheduled repaint that does not necessarily correspond to a terminal size change.
|
||||
Draw,
|
||||
}
|
||||
|
||||
@@ -456,6 +463,8 @@ pub struct Tui {
|
||||
event_broker: Arc<EventBroker>,
|
||||
pub(crate) terminal: Terminal,
|
||||
pending_history_lines: Vec<Line<'static>>,
|
||||
// Reflowed transcript rows are written through a separate queue so the feature-on draw path can
|
||||
// preserve the inline viewport while the legacy queue keeps its pre-feature insertion behavior.
|
||||
pending_reflow_history_lines: Vec<Line<'static>>,
|
||||
alt_saved_viewport: Option<ratatui::layout::Rect>,
|
||||
#[cfg(unix)]
|
||||
@@ -742,7 +751,8 @@ impl Tui {
|
||||
/// Resize the inline viewport for the resize-reflow path.
|
||||
///
|
||||
/// Unlike the legacy draw path, this path does not scroll rows above the viewport when the
|
||||
/// terminal shrinks. Resize reflow owns rebuilding those rows from transcript source.
|
||||
/// terminal shrinks. Resize reflow owns rebuilding those rows from transcript source, so
|
||||
/// scrolling here would move the viewport once and then replay history into the wrong row.
|
||||
fn update_inline_viewport_for_resize_reflow(
|
||||
terminal: &mut Terminal,
|
||||
height: u16,
|
||||
@@ -807,6 +817,11 @@ impl Tui {
|
||||
Ok(is_zellij)
|
||||
}
|
||||
|
||||
/// Write reflowed transcript rows without moving the inline viewport.
|
||||
///
|
||||
/// These rows come from a source-backed rebuild after Codex has already decided which
|
||||
/// transcript cells should be visible at the new width. Sending them through the legacy
|
||||
/// insertion helper would preserve old scroll behavior and undo the resize-reflow invariant.
|
||||
fn flush_pending_reflow_history_lines(
|
||||
terminal: &mut Terminal,
|
||||
pending_reflow_history_lines: &mut Vec<Line<'static>>,
|
||||
@@ -885,6 +900,11 @@ impl Tui {
|
||||
})?
|
||||
}
|
||||
|
||||
/// Draw a frame using the resize-reflow viewport and history insertion rules.
|
||||
///
|
||||
/// This is the feature-gated counterpart to `draw`. It intentionally skips
|
||||
/// `pending_viewport_area`, whose cursor-position heuristic is part of the legacy path, and
|
||||
/// instead lets transcript reflow rebuild scrollback before the frame is rendered.
|
||||
pub fn draw_with_resize_reflow(
|
||||
&mut self,
|
||||
height: u16,
|
||||
|
||||
Reference in New Issue
Block a user