mirror of
https://github.com/openai/codex.git
synced 2026-11-03 19:53:25 +00:00
wip(tui): auto-cap resize reflow by terminal
This commit is contained in:
@@ -532,14 +532,15 @@ pub struct ModelAvailabilityNuxConfig {
|
||||
pub shown_count: HashMap<String, u32>,
|
||||
}
|
||||
|
||||
pub const DEFAULT_TERMINAL_RESIZE_REFLOW_MAX_ROWS: usize = 12_000;
|
||||
pub const DEFAULT_TERMINAL_RESIZE_REFLOW_FALLBACK_MAX_ROWS: usize = 1_000;
|
||||
|
||||
/// Experimental terminal resize-reflow tuning knobs.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TerminalResizeReflowToml {
|
||||
/// Trim resize-reflow replay to the most recent rendered terminal rows when the transcript
|
||||
/// exceeds this cap. Omit or set to `0` to keep all rendered rows.
|
||||
/// exceeds this cap. Omit to use Codex's terminal-specific default. Set to `0` to keep all
|
||||
/// rendered rows.
|
||||
#[serde(default)]
|
||||
#[schemars(range(min = 0))]
|
||||
pub max_rows: Option<usize>,
|
||||
|
||||
@@ -1939,7 +1939,7 @@
|
||||
"properties": {
|
||||
"max_rows": {
|
||||
"default": null,
|
||||
"description": "Trim resize-reflow replay to the most recent rendered terminal rows when the transcript exceeds this cap. Omit or set to `0` to keep all rendered rows.",
|
||||
"description": "Trim resize-reflow replay to the most recent rendered terminal rows when the transcript exceeds this cap. Omit to use Codex's terminal-specific default. Set to `0` to keep all rendered rows.",
|
||||
"format": "uint",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
|
||||
@@ -1335,6 +1335,10 @@ async fn runtime_config_resolves_terminal_resize_reflow_defaults_and_overrides()
|
||||
cfg.terminal_resize_reflow,
|
||||
TerminalResizeReflowConfig::default()
|
||||
);
|
||||
assert_eq!(
|
||||
cfg.terminal_resize_reflow.max_rows,
|
||||
TerminalResizeReflowMaxRows::Auto
|
||||
);
|
||||
|
||||
let cfg = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
@@ -1352,7 +1356,10 @@ async fn runtime_config_resolves_terminal_resize_reflow_defaults_and_overrides()
|
||||
.await
|
||||
.expect("load overridden config");
|
||||
|
||||
assert_eq!(cfg.terminal_resize_reflow.max_rows, Some(9000));
|
||||
assert_eq!(
|
||||
cfg.terminal_resize_reflow.max_rows,
|
||||
TerminalResizeReflowMaxRows::Limit(9000)
|
||||
);
|
||||
|
||||
let cfg = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
@@ -1368,7 +1375,10 @@ async fn runtime_config_resolves_terminal_resize_reflow_defaults_and_overrides()
|
||||
.await
|
||||
.expect("load config with disabled resize reflow limits");
|
||||
|
||||
assert_eq!(cfg.terminal_resize_reflow.max_rows, None);
|
||||
assert_eq!(
|
||||
cfg.terminal_resize_reflow.max_rows,
|
||||
TerminalResizeReflowMaxRows::Disabled
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -625,9 +625,20 @@ impl Default for MultiAgentV2Config {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum TerminalResizeReflowMaxRows {
|
||||
/// Use the runtime terminal detector to choose a scrollback-sized cap.
|
||||
#[default]
|
||||
Auto,
|
||||
/// Keep all rendered transcript rows during resize reflow.
|
||||
Disabled,
|
||||
/// Keep at most this many rendered transcript rows during resize reflow.
|
||||
Limit(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct TerminalResizeReflowConfig {
|
||||
pub max_rows: Option<usize>,
|
||||
pub max_rows: TerminalResizeReflowMaxRows,
|
||||
}
|
||||
|
||||
impl AuthManagerConfig for Config {
|
||||
@@ -1488,17 +1499,16 @@ fn resolve_multi_agent_v2_config(
|
||||
}
|
||||
|
||||
fn resolve_terminal_resize_reflow_config(config_toml: &ConfigToml) -> TerminalResizeReflowConfig {
|
||||
let defaults = TerminalResizeReflowConfig::default();
|
||||
let Some(tui) = config_toml.tui.as_ref() else {
|
||||
return defaults;
|
||||
return TerminalResizeReflowConfig::default();
|
||||
};
|
||||
|
||||
TerminalResizeReflowConfig {
|
||||
max_rows: tui
|
||||
.terminal_resize_reflow
|
||||
.max_rows
|
||||
.and_then(|rows| (rows > 0).then_some(rows))
|
||||
.or(defaults.max_rows),
|
||||
max_rows: match tui.terminal_resize_reflow.max_rows {
|
||||
Some(0) => TerminalResizeReflowMaxRows::Disabled,
|
||||
Some(rows) => TerminalResizeReflowMaxRows::Limit(rows),
|
||||
None => TerminalResizeReflowMaxRows::Auto,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -676,7 +676,10 @@ max_rows = 9000
|
||||
|
||||
app.refresh_in_memory_config_from_disk().await?;
|
||||
|
||||
assert_eq!(app.config.terminal_resize_reflow.max_rows, Some(9000));
|
||||
assert_eq!(
|
||||
app.config.terminal_resize_reflow.max_rows,
|
||||
crate::legacy_core::config::TerminalResizeReflowMaxRows::Limit(9000)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ impl App {
|
||||
}
|
||||
|
||||
fn resize_reflow_max_rows(&self) -> Option<usize> {
|
||||
self.config.terminal_resize_reflow.max_rows
|
||||
crate::resize_reflow_cap::resize_reflow_max_rows(self.config.terminal_resize_reflow)
|
||||
}
|
||||
|
||||
/// Finish stream consolidation by repairing any resize work that happened during streaming.
|
||||
|
||||
@@ -23,6 +23,7 @@ use assert_matches::assert_matches;
|
||||
|
||||
use crate::legacy_core::config::ConfigBuilder;
|
||||
use crate::legacy_core::config::ConfigOverrides;
|
||||
use crate::legacy_core::config::TerminalResizeReflowMaxRows;
|
||||
use codex_app_server_protocol::AdditionalFileSystemPermissions;
|
||||
use codex_app_server_protocol::AdditionalNetworkPermissions;
|
||||
use codex_app_server_protocol::AdditionalPermissionProfile;
|
||||
@@ -3705,7 +3706,7 @@ fn rendered_line_text(line: &Line<'static>) -> String {
|
||||
#[tokio::test]
|
||||
async fn capped_resize_reflow_renders_recent_suffix_only() {
|
||||
let (mut app, _rx, _op_rx) = make_test_app_with_channels().await;
|
||||
app.config.terminal_resize_reflow.max_rows = Some(5);
|
||||
app.config.terminal_resize_reflow.max_rows = TerminalResizeReflowMaxRows::Limit(5);
|
||||
app.transcript_cells = (0..20)
|
||||
.map(|i| plain_line_cell(format!("cell {i}")))
|
||||
.collect();
|
||||
@@ -3732,7 +3733,7 @@ async fn capped_resize_reflow_renders_recent_suffix_only() {
|
||||
#[tokio::test]
|
||||
async fn uncapped_resize_reflow_renders_all_cells_when_row_cap_absent() {
|
||||
let (mut app, _rx, _op_rx) = make_test_app_with_channels().await;
|
||||
app.config.terminal_resize_reflow.max_rows = None;
|
||||
app.config.terminal_resize_reflow.max_rows = TerminalResizeReflowMaxRows::Disabled;
|
||||
app.transcript_cells = (0..20)
|
||||
.map(|i| plain_line_cell(format!("cell {i}")))
|
||||
.collect();
|
||||
@@ -3747,7 +3748,7 @@ async fn uncapped_resize_reflow_renders_all_cells_when_row_cap_absent() {
|
||||
#[tokio::test]
|
||||
async fn uncapped_resize_reflow_renders_all_cells_under_row_limit() {
|
||||
let (mut app, _rx, _op_rx) = make_test_app_with_channels().await;
|
||||
app.config.terminal_resize_reflow.max_rows = Some(100);
|
||||
app.config.terminal_resize_reflow.max_rows = TerminalResizeReflowMaxRows::Limit(100);
|
||||
app.transcript_cells = (0..3)
|
||||
.map(|i| plain_line_cell(format!("cell {i}")))
|
||||
.collect();
|
||||
@@ -3774,7 +3775,7 @@ async fn uncapped_resize_reflow_renders_all_cells_under_row_limit() {
|
||||
async fn initial_replay_buffer_keeps_recent_rows_when_row_cap_present() {
|
||||
let (mut app, _rx, _op_rx) = make_test_app_with_channels().await;
|
||||
enable_terminal_resize_reflow(&mut app);
|
||||
app.config.terminal_resize_reflow.max_rows = Some(3);
|
||||
app.config.terminal_resize_reflow.max_rows = TerminalResizeReflowMaxRows::Limit(3);
|
||||
|
||||
app.begin_initial_history_replay_buffer();
|
||||
for index in 0..5 {
|
||||
|
||||
@@ -144,6 +144,7 @@ mod oss_selection;
|
||||
mod pager_overlay;
|
||||
pub(crate) mod public_widgets;
|
||||
mod render;
|
||||
mod resize_reflow_cap;
|
||||
mod resume_picker;
|
||||
mod selection_list;
|
||||
mod session_log;
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
//! Terminal-specific row caps for resize reflow.
|
||||
//!
|
||||
//! The auto cap mirrors documented scrollback defaults for terminals we can identify. Console Host
|
||||
//! does not expose its configured screen buffer through terminal metadata, so it usually lands in
|
||||
//! the fallback bucket.
|
||||
|
||||
use codex_config::types::DEFAULT_TERMINAL_RESIZE_REFLOW_FALLBACK_MAX_ROWS;
|
||||
use codex_terminal_detection::TerminalInfo;
|
||||
use codex_terminal_detection::TerminalName;
|
||||
use codex_terminal_detection::terminal_info;
|
||||
|
||||
use crate::legacy_core::config::TerminalResizeReflowConfig;
|
||||
use crate::legacy_core::config::TerminalResizeReflowMaxRows;
|
||||
|
||||
const VSCODE_RESIZE_REFLOW_MAX_ROWS: usize = 1_000;
|
||||
const WINDOWS_TERMINAL_RESIZE_REFLOW_MAX_ROWS: usize = 9_001;
|
||||
const WEZTERM_RESIZE_REFLOW_MAX_ROWS: usize = 3_500;
|
||||
const ALACRITTY_RESIZE_REFLOW_MAX_ROWS: usize = 10_000;
|
||||
|
||||
pub(crate) fn resize_reflow_max_rows(config: TerminalResizeReflowConfig) -> Option<usize> {
|
||||
resize_reflow_max_rows_for(
|
||||
config,
|
||||
&terminal_info(),
|
||||
crate::tui::running_in_vscode_terminal(),
|
||||
)
|
||||
}
|
||||
|
||||
fn resize_reflow_max_rows_for(
|
||||
config: TerminalResizeReflowConfig,
|
||||
terminal: &TerminalInfo,
|
||||
running_in_vscode_terminal: bool,
|
||||
) -> Option<usize> {
|
||||
match config.max_rows {
|
||||
TerminalResizeReflowMaxRows::Auto => Some(auto_resize_reflow_max_rows(
|
||||
terminal.name,
|
||||
running_in_vscode_terminal,
|
||||
)),
|
||||
TerminalResizeReflowMaxRows::Disabled => None,
|
||||
TerminalResizeReflowMaxRows::Limit(max_rows) => Some(max_rows),
|
||||
}
|
||||
}
|
||||
|
||||
fn auto_resize_reflow_max_rows(
|
||||
terminal_name: TerminalName,
|
||||
running_in_vscode_terminal: bool,
|
||||
) -> usize {
|
||||
if running_in_vscode_terminal {
|
||||
return VSCODE_RESIZE_REFLOW_MAX_ROWS;
|
||||
}
|
||||
|
||||
match terminal_name {
|
||||
TerminalName::VsCode => VSCODE_RESIZE_REFLOW_MAX_ROWS,
|
||||
TerminalName::WindowsTerminal => WINDOWS_TERMINAL_RESIZE_REFLOW_MAX_ROWS,
|
||||
TerminalName::WezTerm => WEZTERM_RESIZE_REFLOW_MAX_ROWS,
|
||||
TerminalName::Alacritty => ALACRITTY_RESIZE_REFLOW_MAX_ROWS,
|
||||
TerminalName::AppleTerminal
|
||||
| TerminalName::Ghostty
|
||||
| TerminalName::Iterm2
|
||||
| TerminalName::WarpTerminal
|
||||
| TerminalName::Kitty
|
||||
| TerminalName::Konsole
|
||||
| TerminalName::GnomeTerminal
|
||||
| TerminalName::Vte
|
||||
| TerminalName::Dumb
|
||||
| TerminalName::Unknown => DEFAULT_TERMINAL_RESIZE_REFLOW_FALLBACK_MAX_ROWS,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_terminal_detection::Multiplexer;
|
||||
|
||||
fn test_terminal(name: TerminalName) -> TerminalInfo {
|
||||
TerminalInfo {
|
||||
name,
|
||||
term_program: None,
|
||||
version: None,
|
||||
term: None,
|
||||
multiplexer: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_resize_reflow_max_rows_uses_terminal_defaults() {
|
||||
let cases = [
|
||||
(TerminalName::VsCode, VSCODE_RESIZE_REFLOW_MAX_ROWS),
|
||||
(
|
||||
TerminalName::WindowsTerminal,
|
||||
WINDOWS_TERMINAL_RESIZE_REFLOW_MAX_ROWS,
|
||||
),
|
||||
(TerminalName::WezTerm, WEZTERM_RESIZE_REFLOW_MAX_ROWS),
|
||||
(TerminalName::Alacritty, ALACRITTY_RESIZE_REFLOW_MAX_ROWS),
|
||||
(
|
||||
TerminalName::Ghostty,
|
||||
DEFAULT_TERMINAL_RESIZE_REFLOW_FALLBACK_MAX_ROWS,
|
||||
),
|
||||
(
|
||||
TerminalName::Unknown,
|
||||
DEFAULT_TERMINAL_RESIZE_REFLOW_FALLBACK_MAX_ROWS,
|
||||
),
|
||||
];
|
||||
|
||||
for (terminal_name, expected_max_rows) in cases {
|
||||
assert_eq!(
|
||||
auto_resize_reflow_max_rows(
|
||||
terminal_name,
|
||||
/*running_in_vscode_terminal*/ false
|
||||
),
|
||||
expected_max_rows
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_resize_reflow_max_rows_prefers_vscode_probe() {
|
||||
assert_eq!(
|
||||
auto_resize_reflow_max_rows(
|
||||
TerminalName::WindowsTerminal,
|
||||
/*running_in_vscode_terminal*/ true
|
||||
),
|
||||
VSCODE_RESIZE_REFLOW_MAX_ROWS
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configured_resize_reflow_max_rows_overrides_auto_detection() {
|
||||
let terminal = test_terminal(TerminalName::VsCode);
|
||||
let config = TerminalResizeReflowConfig {
|
||||
max_rows: TerminalResizeReflowMaxRows::Limit(42),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
resize_reflow_max_rows_for(
|
||||
config, &terminal, /*running_in_vscode_terminal*/ false
|
||||
),
|
||||
Some(42)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_resize_reflow_max_rows_keeps_all_rows() {
|
||||
let terminal = test_terminal(TerminalName::VsCode);
|
||||
let config = TerminalResizeReflowConfig {
|
||||
max_rows: TerminalResizeReflowMaxRows::Disabled,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
resize_reflow_max_rows_for(
|
||||
config, &terminal, /*running_in_vscode_terminal*/ false
|
||||
),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_terminal_uses_fallback_even_under_multiplexer() {
|
||||
let terminal = TerminalInfo {
|
||||
name: TerminalName::Unknown,
|
||||
term_program: None,
|
||||
version: None,
|
||||
term: Some("xterm-256color".to_string()),
|
||||
multiplexer: Some(Multiplexer::Tmux { version: None }),
|
||||
};
|
||||
let config = TerminalResizeReflowConfig::default();
|
||||
|
||||
assert_eq!(
|
||||
resize_reflow_max_rows_for(
|
||||
config, &terminal, /*running_in_vscode_terminal*/ false
|
||||
),
|
||||
Some(DEFAULT_TERMINAL_RESIZE_REFLOW_FALLBACK_MAX_ROWS)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,7 @@ fn running_in_wsl() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn running_in_vscode_terminal() -> bool {
|
||||
pub(crate) fn running_in_vscode_terminal() -> bool {
|
||||
vscode_terminal_detected(
|
||||
std::env::var("TERM_PROGRAM").ok().as_deref(),
|
||||
windows_term_program().as_deref(),
|
||||
|
||||
Reference in New Issue
Block a user