Files
codex/codex-rs/ansi-escape/src/lib.rs
T
Dmitriy Kovalenko ab84d71f57 Omit payloads from multiline ANSI warnings (#49416)
## Why

`ansi_escape_line` logged the rendered contents of every line when given multiline input, allowing large payloads to inflate warning logs.

## What changed

Replace line contents in the warning with structured `input_bytes` and `line_count` fields. Preserve the existing behavior of returning the styled first line.

## Testing

Enable unit tests for `codex-ansi-escape` and add coverage with small and 128 KiB tails, asserting the exact warning fields and preservation of first-line styling.

GitOrigin-RevId: 4be985b4442eaa12f7fdee8a583d6e9714c618d8
2026-09-30 00:51:39 +00:00

67 lines
2.3 KiB
Rust

use ansi_to_tui::Error;
use ansi_to_tui::IntoText;
use ratatui::text::Line;
use ratatui::text::Text;
// Expand tabs in a best-effort way for transcript rendering.
// Tabs can interact poorly with left-gutter prefixes in our TUI and CLI
// transcript views (e.g., `nl` separates line numbers from content with a tab).
// Replacing tabs with spaces avoids odd visual artifacts without changing
// semantics for our use cases.
fn expand_tabs(s: &str) -> std::borrow::Cow<'_, str> {
if s.contains('\t') {
// Keep it simple: replace each tab with 4 spaces.
// We do not try to align to tab stops since most usages (like `nl`)
// look acceptable with a fixed substitution and this avoids stateful math
// across spans.
std::borrow::Cow::Owned(s.replace('\t', " "))
} else {
std::borrow::Cow::Borrowed(s)
}
}
/// This function should be used when the contents of `s` are expected to match
/// a single line. If multiple lines are found, a warning is logged and only the
/// first line is returned.
pub fn ansi_escape_line(s: &str) -> Line<'static> {
// Normalize tabs to spaces to avoid odd gutter collisions in transcript mode.
let s = expand_tabs(s);
let text = ansi_escape(&s);
match text.lines.as_slice() {
[] => "".into(),
[only] => only.clone(),
[first, rest @ ..] => {
tracing::warn!(
input_bytes = s.len(),
line_count = rest.len() + 1,
"ansi_escape_line: expected a single line"
);
first.clone()
}
}
}
pub fn ansi_escape(s: &str) -> Text<'static> {
// to_text() claims to be faster, but introduces complex lifetime issues
// such that it's not worth it.
match s.into_text() {
Ok(text) => text,
Err(err) => match err {
Error::NomError(message) => {
tracing::error!(
"ansi_to_tui NomError docs claim should never happen when parsing `{s}`: {message}"
);
panic!();
}
Error::Utf8Error(utf8error) => {
tracing::error!("Utf8Error: {utf8error}");
panic!();
}
},
}
}
#[cfg(test)]
#[path = "ansi_escape_tests.rs"]
mod tests;