Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions codex-rs/features/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub enum Feature {
/// on either `unified_exec` or `shell_zsh_fork` because those features have
/// separate rollout and enterprise controls.
UnifiedExecZshFork,
/// Reflow transcript scrollback when the terminal is resized.
/// Removed compatibility flag. Transcript scrollback reflow on terminal resize is always on.
TerminalResizeReflow,
/// Add terminal-specific visualization guidance to TUI developer instructions.
TerminalVisualizationInstructions,
Expand Down Expand Up @@ -461,6 +461,9 @@ impl Features {
"skill_env_var_dependency_prompt" => {
continue;
}
"terminal_resize_reflow" => {
continue;
}
Comment thread
etraut-openai marked this conversation as resolved.
Comment thread
etraut-openai marked this conversation as resolved.
Comment thread
etraut-openai marked this conversation as resolved.
"use_legacy_landlock" => {
self.record_legacy_usage_force(
"features.use_legacy_landlock",
Expand Down Expand Up @@ -807,11 +810,7 @@ pub const FEATURES: &[FeatureSpec] = &[
FeatureSpec {
id: Feature::TerminalResizeReflow,
key: "terminal_resize_reflow",
stage: Stage::Experimental {
name: "Terminal resize reflow",
menu_description: "Rebuild Codex-owned transcript scrollback when the terminal width changes.",
announcement: "",
},
stage: Stage::Removed,
default_enabled: true,
},
FeatureSpec {
Expand Down
30 changes: 23 additions & 7 deletions codex-rs/features/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ fn default_enabled_features_are_stable() {
for spec in crate::FEATURES {
if spec.default_enabled {
assert!(
matches!(spec.stage, Stage::Stable | Stage::Removed)
|| spec.id == Feature::TerminalResizeReflow,
matches!(spec.stage, Stage::Stable | Stage::Removed),
"feature `{}` is enabled by default but is not stable/removed ({:?})",
spec.key,
spec.stage
Expand Down Expand Up @@ -151,18 +150,35 @@ fn request_permissions_tool_is_under_development() {
}

#[test]
fn terminal_resize_reflow_is_experimental_and_enabled_by_default() {
fn terminal_resize_reflow_is_removed_and_enabled_by_default() {
assert_eq!(
feature_for_key("terminal_resize_reflow"),
Some(Feature::TerminalResizeReflow)
);
assert!(matches!(
Feature::TerminalResizeReflow.stage(),
Stage::Experimental { .. }
));
assert_eq!(Feature::TerminalResizeReflow.stage(), Stage::Removed);
assert_eq!(Feature::TerminalResizeReflow.default_enabled(), true);
}

#[test]
fn from_sources_ignores_removed_terminal_resize_reflow_feature_key() {
let features_toml = FeaturesToml::from(BTreeMap::from([(
"terminal_resize_reflow".to_string(),
false,
)]));

let features = Features::from_sources(
FeatureConfigSource {
features: Some(&features_toml),
..Default::default()
},
FeatureConfigSource::default(),
FeatureOverrides::default(),
);

assert_eq!(features, Features::with_defaults());
assert_eq!(features.enabled(Feature::TerminalResizeReflow), true);
}

#[test]
fn tool_suggest_is_stable_and_enabled_by_default() {
assert_eq!(Feature::ToolSuggest.stage(), Stage::Stable);
Expand Down
56 changes: 14 additions & 42 deletions codex-rs/tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1248,16 +1248,8 @@ See the Codex keymap documentation for supported actions and examples."
app_server: &mut AppServerSession,
event: TuiEvent,
) -> Result<AppRunControl> {
let terminal_resize_reflow_enabled = self.terminal_resize_reflow_enabled();
if self.should_handle_draw_pre_render()
&& matches!(event, TuiEvent::Draw | TuiEvent::Resize)
{
if matches!(event, TuiEvent::Draw | TuiEvent::Resize) {
self.handle_draw_pre_render(tui)?;
} else if matches!(event, TuiEvent::Draw | TuiEvent::Resize) {
let size = tui.terminal.size()?;
if size != tui.terminal.last_known_screen_size {
self.refresh_status_line();
}
}

if self.overlay.is_some() {
Expand Down Expand Up @@ -1289,8 +1281,7 @@ See the Codex keymap documentation for supported actions and examples."
}
// Allow widgets to process any pending timers before rendering.
self.chat_widget.pre_draw_tick();
let rendered_area =
self.render_chat_widget_frame(tui, terminal_resize_reflow_enabled)?;
let rendered_area = self.render_chat_widget_frame(tui)?;
if self.chat_widget.ambient_pet_image_enabled() {
let terminal_size = tui.terminal.size()?;
let ambient_pet_area = Rect::new(
Expand Down Expand Up @@ -1329,43 +1320,24 @@ See the Codex keymap documentation for supported actions and examples."
pub(super) fn show_shutdown_feedback(&mut self, tui: &mut tui::Tui) -> Result<()> {
self.disable_ambient_pet_before_shutdown(tui)?;
self.chat_widget.show_shutdown_in_progress();
let terminal_resize_reflow_enabled = self.terminal_resize_reflow_enabled();
if self.should_handle_draw_pre_render() {
self.handle_draw_pre_render(tui)?;
}
self.handle_draw_pre_render(tui)?;
self.chat_widget.pre_draw_tick();
self.render_chat_widget_frame(tui, terminal_resize_reflow_enabled)?;
self.render_chat_widget_frame(tui)?;
Ok(())
}

fn render_chat_widget_frame(
&mut self,
tui: &mut tui::Tui,
terminal_resize_reflow_enabled: bool,
) -> Result<Rect> {
fn render_chat_widget_frame(&mut self, tui: &mut tui::Tui) -> Result<Rect> {
let desired_height = self.chat_widget.desired_height(tui.terminal.size()?.width);
let mut rendered_area = Rect::default();
if terminal_resize_reflow_enabled {
tui.draw_with_resize_reflow(desired_height, |frame| {
let area = frame.area();
rendered_area = area;
self.chat_widget.render(area, frame.buffer);
if let Some((x, y)) = self.chat_widget.cursor_pos(area) {
frame.set_cursor_style(self.chat_widget.cursor_style(area));
frame.set_cursor_position((x, y));
}
})?;
} else {
tui.draw(desired_height, |frame| {
let area = frame.area();
rendered_area = area;
self.chat_widget.render(area, frame.buffer);
if let Some((x, y)) = self.chat_widget.cursor_pos(area) {
frame.set_cursor_style(self.chat_widget.cursor_style(area));
frame.set_cursor_position((x, y));
}
})?;
}
tui.draw_with_resize_reflow(desired_height, |frame| {
let area = frame.area();
rendered_area = area;
self.chat_widget.render(area, frame.buffer);
if let Some((x, y)) = self.chat_widget.cursor_pos(area) {
frame.set_cursor_style(self.chat_widget.cursor_style(area));
frame.set_cursor_position((x, y));
}
})?;
Ok(rendered_area)
}
}
Expand Down
8 changes: 0 additions & 8 deletions codex-rs/tui/src/app/event_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,14 +249,6 @@ impl App {
self.insert_completed_token_activity_output_after_stream_shutdown(tui);
}
AppEvent::ConsolidateProposedPlan(source) => {
if !self.terminal_resize_reflow_enabled() {
if !self.transcript_reflow.history_cell_refresh_requested() {
self.transcript_reflow.clear();
}
self.chat_widget.note_stream_consolidation_completed();
self.insert_completed_token_activity_output_after_stream_shutdown(tui);
return Ok(AppRunControl::Continue);
}
let end = self.transcript_cells.len();
let start = trailing_run_start::<history_cell::ProposedPlanStreamCell>(
&self.transcript_cells,
Expand Down
97 changes: 15 additions & 82 deletions codex-rs/tui/src/app/resize_reflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use std::collections::VecDeque;
use std::sync::Arc;
use std::time::Instant;

use codex_features::Feature;
use color_eyre::eyre::Result;
use ratatui::text::Line;

Expand Down Expand Up @@ -109,18 +108,14 @@ impl App {
}
}

pub(super) fn terminal_resize_reflow_enabled(&self) -> bool {
self.config.features.enabled(Feature::TerminalResizeReflow)
}

/// Start retaining initial resume replay rows before they are written to scrollback.
///
/// Resume replay can insert thousands of already-finalized history cells before the first draw.
/// When resize reflow is enabled, buffering here lets the same row cap used by resize rebuilds
/// apply to the startup write. Starting this buffer while an overlay owns rendering would split
/// transcript ownership, so overlay replay continues through the normal deferred-history path.
/// Buffering here lets the same row cap used by resize rebuilds apply to the startup write.
/// Starting this buffer while an overlay owns rendering would split transcript ownership, so
/// overlay replay continues through the normal deferred-history path.
pub(super) fn begin_initial_history_replay_buffer(&mut self) {
if self.terminal_resize_reflow_enabled() && self.overlay.is_none() {
if self.overlay.is_none() {
self.initial_history_replay_buffer = Some(Default::default());
}
}
Expand All @@ -131,10 +126,7 @@ impl App {
/// defer terminal writes until the replay is complete and reuse the resize-reflow tail renderer
/// so only the rows the terminal would retain are formatted and inserted.
pub(super) fn begin_thread_switch_history_replay_buffer(&mut self) {
if self.terminal_resize_reflow_enabled()
&& self.resize_reflow_max_rows().is_some()
&& self.overlay.is_none()
{
if self.resize_reflow_max_rows().is_some() && self.overlay.is_none() {
self.initial_history_replay_buffer = Some(InitialHistoryReplayBuffer {
retained_lines: VecDeque::new(),
render_from_transcript_tail: true,
Expand Down Expand Up @@ -233,7 +225,6 @@ impl App {
}

fn schedule_resize_reflow(&mut self, target_width: Option<u16>) -> bool {
debug_assert!(self.terminal_resize_reflow_enabled());
self.transcript_reflow.schedule_debounced(target_width)
}

Expand Down Expand Up @@ -263,19 +254,6 @@ impl App {
/// source-backed reflow so terminal scrollback reflects the finalized cell instead of the
/// transient stream rows.
pub(super) fn maybe_finish_stream_reflow(&mut self, tui: &mut tui::Tui) -> Result<()> {
if !self.terminal_resize_reflow_enabled() {
if self.transcript_reflow.take_stream_finish_reflow_needed() {
self.schedule_immediate_history_cell_refresh(tui);
self.maybe_run_resize_reflow(tui)?;
return Ok(());
}
if self.transcript_reflow.history_cell_refresh_requested() {
return Ok(());
}
self.transcript_reflow.clear();
return Ok(());
}

if self.transcript_reflow.take_stream_finish_reflow_needed() {
self.schedule_immediate_resize_reflow(tui);
self.maybe_run_resize_reflow(tui)?;
Expand All @@ -286,42 +264,16 @@ impl App {
}

fn schedule_immediate_resize_reflow(&mut self, tui: &mut tui::Tui) {
if !self.terminal_resize_reflow_enabled() {
self.transcript_reflow.clear();
return;
}
self.transcript_reflow.schedule_immediate();
tui.frame_requester().schedule_frame();
}

fn schedule_immediate_history_cell_refresh(&mut self, tui: &mut tui::Tui) {
self.transcript_reflow.schedule_history_cell_refresh();
tui.frame_requester().schedule_frame();
}

pub(crate) fn retry_pending_history_cell_refresh(&self, tui: &mut tui::Tui) {
if self.transcript_reflow.history_cell_refresh_requested() {
tui.frame_requester().schedule_frame();
}
}

pub(super) fn should_handle_draw_pre_render(&self) -> bool {
self.terminal_resize_reflow_enabled()
|| self.transcript_reflow.history_cell_refresh_requested()
}

/// Force stream-finalized output through the resize reflow path.
///
/// Proposed plan consolidation uses this stricter path because a completed plan is inserted or
/// replaced as one styled source-backed cell. If this reflow is skipped after a stream-time
/// resize, the visible scrollback can keep the pre-consolidation wrapping.
pub(super) fn finish_required_stream_reflow(&mut self, tui: &mut tui::Tui) -> Result<()> {
if !self.terminal_resize_reflow_enabled() {
if !self.transcript_reflow.history_cell_refresh_requested() {
self.transcript_reflow.clear();
}
return Ok(());
}
self.schedule_immediate_resize_reflow(tui);
self.maybe_run_resize_reflow(tui)?;
if !self.transcript_reflow.has_pending_reflow() {
Expand Down Expand Up @@ -350,37 +302,24 @@ impl App {
self.chat_widget.on_terminal_resize(size.width);
}
if should_rebuild_transcript {
if self.terminal_resize_reflow_enabled() {
if reflow_needed && self.should_mark_reflow_as_stream_time() {
self.transcript_reflow.mark_resize_requested_during_stream();
}
let target_width = reflow_needed.then_some(size.width);
if self.schedule_resize_reflow(target_width) {
frame_requester.schedule_frame();
} else {
frame_requester.schedule_frame_in(TRANSCRIPT_REFLOW_DEBOUNCE);
}
} else if !self.terminal_resize_reflow_enabled()
&& width.changed
&& !self.transcript_reflow.history_cell_refresh_requested()
{
self.transcript_reflow.clear();
if reflow_needed && self.should_mark_reflow_as_stream_time() {
self.transcript_reflow.mark_resize_requested_during_stream();
}
let target_width = reflow_needed.then_some(size.width);
if self.schedule_resize_reflow(target_width) {
frame_requester.schedule_frame();
} else {
frame_requester.schedule_frame_in(TRANSCRIPT_REFLOW_DEBOUNCE);
}
}
if size != last_known_screen_size {
self.refresh_status_line();
}
if self.terminal_resize_reflow_enabled() {
self.maybe_clear_resize_reflow_without_terminal();
}
self.maybe_clear_resize_reflow_without_terminal();
should_rebuild_transcript
}

fn maybe_clear_resize_reflow_without_terminal(&mut self) {
if !self.terminal_resize_reflow_enabled() {
self.transcript_reflow.clear();
return;
}
let Some(deadline) = self.transcript_reflow.pending_until() else {
return;
};
Expand All @@ -400,7 +339,7 @@ impl App {
tui.terminal.last_known_screen_size,
&tui.frame_requester(),
);
if should_rebuild_transcript && self.terminal_resize_reflow_enabled() {
if should_rebuild_transcript {
// Resize-sensitive history inserts queued before this frame may be wrapped for the old
// viewport or targeted at rows no longer visible. Drop them and let resize reflow
// rebuild from transcript cells.
Expand All @@ -417,12 +356,6 @@ impl App {
/// reuse terminal-wrapped output here would preserve exactly the stale wrapping this feature is
/// meant to remove.
pub(super) fn maybe_run_resize_reflow(&mut self, tui: &mut tui::Tui) -> Result<()> {
if !self.terminal_resize_reflow_enabled()
&& !self.transcript_reflow.history_cell_refresh_requested()
{
self.transcript_reflow.clear();
return Ok(());
}
let Some(deadline) = self.transcript_reflow.pending_until() else {
return Ok(());
};
Expand Down
Loading
Loading