Skip to content

Preserve trailing ANSI reset codes in ansi_trim_end - #957

Open
MsfPablo wants to merge 2 commits into
dalance:masterfrom
MsfPablo:fix-ansi-trim-end-preserve-reset
Open

Preserve trailing ANSI reset codes in ansi_trim_end#957
MsfPablo wants to merge 2 commits into
dalance:masterfrom
MsfPablo:fix-ansi-trim-end-preserve-reset

Conversation

@MsfPablo

Copy link
Copy Markdown

Background

While looking into #769 (extraneous trailing spaces with --color=always) I found that the reported symptom is already fixed on master by bb7d47c (Fix double-spaced TTY output caused by ANSI-unaware trim_end, for #848) — colored output no longer has trailing spaces. #769 can probably be closed.

That fix, however, left a related defect behind.

Problem

ansi_trim_end() computes the visible width of the trimmed row and then calls truncate() with that width. truncate() stops as soon as the width budget is exhausted, so any escape sequence that sits after the trimmed padding is discarded along with the spaces.

Rows are assembled by wrapping each padded column in apply_color(), which puts the reset code at the very end of the styled span. Trimming therefore removes the reset that closed the last column, leaving an unterminated SGR sequence on every line — the final row's color bleeds into whatever is printed next (typically the shell prompt).

On master:

$ procs --color=always zsh | tail -1 | cat -v | tail -c 20
/bin/zsh -l          # <- no ESC[0m; the terminal stays colored afterwards

Reduced:

ansi_trim_end("\x1b[31mabc   \x1b[0m")
// master:   "\x1b[31mabc"        <- reset lost
// expected: "\x1b[31mabc\x1b[0m"

Fix

Trim the trailing whitespace directly rather than routing through truncate(), keeping every escape sequence that follows the trimmed run. Behavior for the whitespace itself is unchanged, so the #848/#769 fix is preserved.

Tests

Adds src/util.rs unit tests for ansi_trim_end: plain strings, padding inside a styled span, reset-code preservation, multiple styled columns, whitespace-only input, and wide/box-drawing characters.

5 of the 6 fail on master and pass with this change:

test util::tests::test_ansi_trim_end_keeps_reset_code ... FAILED
test util::tests::test_ansi_trim_end_removes_padding_inside_style ... FAILED
test util::tests::test_ansi_trim_end_multiple_columns ... FAILED
test util::tests::test_ansi_trim_end_only_whitespace ... FAILED
test util::tests::test_ansi_trim_end_wide_chars ... FAILED

Verification

$ cargo test
test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

$ cargo fmt --check
(clean)

$ cargo clippy --all-targets -- -D warnings
14 findings, byte-for-byte the same set as the git-stash baseline
(only line numbers in util.rs shift). No new lint introduced.

End-to-end assertion over procs --color=always output — 12 lines, 0 with trailing whitespace, 0 with an unterminated SGR sequence.

Tested on macOS (aarch64).


Disclosure: this patch was written with AI assistance (Claude). I reviewed the diff, ran the test/fmt/clippy commands above myself, and isolated the pre-existing clippy findings against a clean baseline.

Pablo Garcia and others added 2 commits August 13, 2026 14:02
ansi_trim_end() measured the visible width of the trimmed row and then
called truncate() with that width. truncate() stops as soon as the width
budget is exhausted, so any escape sequence sitting after the trimmed
padding was discarded along with the spaces.

Rows are built by wrapping each padded column in apply_color(), which
places the reset code at the very end of the styled span. Trimming
therefore removed the reset that closed the last column, leaving an
unterminated SGR sequence on every line: the final row's color bleeds
into whatever is printed next (e.g. the shell prompt).

Trim the trailing whitespace directly instead of going through
truncate(), keeping every escape sequence that follows it.

Verified with 'procs --color=always': no trailing whitespace, and no
line ends with an unterminated SGR sequence.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant