From 5381edb133075143839efb73de4f8695cf6332d0 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Wed, 22 Jul 2026 17:28:18 +0000 Subject: [PATCH] Skip syntax highlighting for lines over 4 KiB (#34796) ## Why Very long individual lines can make syntax highlighting consume excessive CPU or memory even when the overall input remains below the existing size and line-count limits. ## What changed - Fall back to plain, unstyled text when any input line exceeds 4 KiB. - Keep the existing 512 KiB total-size and 10,000-line guardrails. ## Testing - Cover highlighting fallback and text preservation for a long single-line Bash command. - Snapshot rendering of a command that exceeds the per-line limit. GitOrigin-RevId: ba02997331a294d8a32ca89ee06acb12dd3bbd83 --- ...hlight_limit_uses_plain_text_fallback.snap | 9 ++++++ codex-rs/tui/src/history_cell/tests.rs | 25 ++++++++++++++++ codex-rs/tui/src/render/highlight.rs | 29 ++++++++++++++++--- 3 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 codex-rs/tui/src/history_cell/snapshots/codex_tui__history_cell__tests__single_line_command_over_highlight_limit_uses_plain_text_fallback.snap diff --git a/codex-rs/tui/src/history_cell/snapshots/codex_tui__history_cell__tests__single_line_command_over_highlight_limit_uses_plain_text_fallback.snap b/codex-rs/tui/src/history_cell/snapshots/codex_tui__history_cell__tests__single_line_command_over_highlight_limit_uses_plain_text_fallback.snap new file mode 100644 index 0000000000..5c1d0f5bc7 --- /dev/null +++ b/codex-rs/tui/src/history_cell/snapshots/codex_tui__history_cell__tests__single_line_command_over_highlight_limit_uses_plain_text_fallback.snap @@ -0,0 +1,9 @@ +--- +source: tui/src/history_cell/tests.rs +expression: rendered +--- +• Ran AAAAAAAAAAAAAAAAAA + │ AAAAAAAAAAAAAAAAAA + │ AAAAAAAAAAAAAAAAAA + │ … +225 lines + └ (no output) diff --git a/codex-rs/tui/src/history_cell/tests.rs b/codex-rs/tui/src/history_cell/tests.rs index 565055e35d..5535b2e70a 100644 --- a/codex-rs/tui/src/history_cell/tests.rs +++ b/codex-rs/tui/src/history_cell/tests.rs @@ -7,6 +7,7 @@ use crate::exec_cell::ExecCell; use crate::legacy_core::config::Config; use crate::legacy_core::config::ConfigBuilder; use crate::line_truncation::line_width; +use crate::render::highlight::MAX_HIGHLIGHT_LINE_BYTES; use crate::session_state::ThreadSessionState; use crate::wrapping::word_wrap_lines; use codex_app_server_protocol::AskForApproval; @@ -1861,6 +1862,30 @@ fn single_line_command_wraps_with_four_space_continuation() { insta::assert_snapshot!(rendered); } +#[test] +fn single_line_command_over_highlight_limit_uses_plain_text_fallback() { + let call_id = "c1".to_string(); + let base64_like = "A".repeat(MAX_HIGHLIGHT_LINE_BYTES + 1); + let mut cell = ExecCell::new( + ExecCall { + call_id: call_id.clone(), + command: vec!["bash".into(), "-lc".into(), base64_like], + parsed: Vec::new(), + output: None, + source: ExecCommandSource::Agent, + start_time: Some(Instant::now()), + duration: None, + interaction_input: None, + }, + /*animations_enabled*/ true, + ); + cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1)); + + let rendered = render_lines(&cell.display_lines(/*width*/ 24)).join("\n"); + + insta::assert_snapshot!(rendered); +} + #[test] fn multiline_command_without_wrap_uses_branch_then_eight_spaces() { let call_id = "c1".to_string(); diff --git a/codex-rs/tui/src/render/highlight.rs b/codex-rs/tui/src/render/highlight.rs index 058dbc8a45..e04fcc542d 100644 --- a/codex-rs/tui/src/render/highlight.rs +++ b/codex-rs/tui/src/render/highlight.rs @@ -18,9 +18,10 @@ //! swap/snapshot the theme for live preview. All highlighting functions read //! the theme via `theme_lock()`. //! -//! **Guardrails:** inputs exceeding 512 KB or 10 000 lines are rejected early -//! (returns `None`) to prevent pathological CPU/memory usage. Callers must -//! fall back to plain unstyled text. +//! **Guardrails:** inputs exceeding 512 KB or 10 000 lines, or containing an +//! individual line longer than 4 KiB, are rejected early (returns `None`) to +//! prevent pathological CPU/memory usage. Callers must fall back to plain +//! unstyled text. use ratatui::style::Color as RtColor; use ratatui::style::Modifier; @@ -581,6 +582,9 @@ const MAX_HIGHLIGHT_BYTES: usize = 512 * 1024; /// Skip highlighting for inputs with more than 10,000 lines. const MAX_HIGHLIGHT_LINES: usize = 10_000; +/// Skip highlighting when an individual line is longer than 4 KiB. +pub(crate) const MAX_HIGHLIGHT_LINE_BYTES: usize = 4 * 1024; + /// Check whether an input exceeds the safe highlighting limits. /// /// Callers that highlight content in a loop (e.g. per diff-line) should @@ -611,7 +615,12 @@ fn highlight_to_line_spans_with_theme( // Bail out early for oversized inputs to avoid excessive resource usage. // Count actual lines (not newline bytes) to avoid an off-by-one when // the input does not end with a newline. - if code.len() > MAX_HIGHLIGHT_BYTES || code.lines().count() > MAX_HIGHLIGHT_LINES { + if code.len() > MAX_HIGHLIGHT_BYTES + || code.lines().count() > MAX_HIGHLIGHT_LINES + || code + .lines() + .any(|line| line.len() > MAX_HIGHLIGHT_LINE_BYTES) + { return None; } @@ -1139,6 +1148,18 @@ mod tests { assert!(result.is_none(), "oversized input should fall back to None"); } + #[test] + fn long_single_line_bash_skips_highlighting_and_preserves_text() { + let token = "eHh4".repeat(MAX_HIGHLIGHT_LINE_BYTES / 4 + 1); + let code = format!("printf %s {token} | base64 -d >/dev/null"); + + assert!(highlight_code_to_styled_spans(&code, "bash").is_none()); + assert_eq!( + highlight_code_to_lines(&code, "bash"), + vec![Line::from(code)] + ); + } + #[test] fn highlight_many_lines_falls_back() { // Input exceeding MAX_HIGHLIGHT_LINES should return None.