Skip to content

fix: emit ANSI reset when truncation drops it (#778) - #956

Open
MsfPablo wants to merge 2 commits into
dalance:masterfrom
MsfPablo:fix/778-ansi-reset-on-truncate
Open

fix: emit ANSI reset when truncation drops it (#778)#956
MsfPablo wants to merge 2 commits into
dalance:masterfrom
MsfPablo:fix/778-ansi-reset-on-truncate

Conversation

@MsfPablo

Copy link
Copy Markdown

Fixes #778.

Root cause

apply_color / apply_style wrap each column in a style + trailing \x1b[0m. util::truncate() then cuts the string at the first character that would exceed the target width and returns only what it accumulated so far — everything after the cut point is discarded, including that trailing reset.

Both display paths in view.rs go through it, back to back:

row = ansi_trim_end(&row);                        // truncates to drop trailing column padding
row = truncate(&row, self.term_info.width).to_string();  // truncates to terminal width

So whenever the last visible column has padding (content shorter than the column) or the row is wider than the terminal, the reset is dropped and the row ends mid-style. That is what leaves the terminal bold/white after procs exits, as reported in the issue.

Fix

Append the reset inside truncate() when the retained output contains an escape sequence. Fixing it there covers both call sites rather than only the ansi_trim_end one, and plain output is untouched: with --color=never (and for adjust(), which truncates pre-colorization content) the buffer contains no \x1b, so nothing is appended. Un-truncated strings still take the Cow::Borrowed path unchanged.

Note this is a different, narrower fix than the earlier self-closed draft #908, which patched only ansi_trim_end — that leaves the terminal-width truncate on the very next line still able to drop the reset on narrow terminals.

Verification

Comparing a baseline build (git stash) with the fixed build, counting emitted lines that contain ANSI codes but do not end in a reset:

                 lines   unreset
base   w=40       270      268
base   w=60       271      269
base   w=120      275      273
fixed  w=40       284        0
fixed  w=60       284        0
fixed  w=120      299        0

Header and unit rows are byte-identical after stripping ANSI codes at all three widths, so no visible output changed.

Added four unit tests in src/util.rs covering truncation of styled and plain text, and ansi_trim_end with and without trailing padding.

$ cargo test
test result: ok. 16 passed; 0 failed; 0 ignored

$ cargo fmt --check
(clean)

$ cargo clippy --all-targets -- -D warnings

clippy output is byte-identical to the git stash baseline on this machine — the 12 pre-existing errors are all in macOS-specific code untouched by this change (unsafe_op_in_unsafe_fn, collapsible_if, etc.).


Written with assistance from Claude Code; reviewed and verified by me before submitting.

Pablo Garcia and others added 2 commits August 13, 2026 14:01
truncate() cuts the string at the first character exceeding the target
width and discards the remainder, including the trailing reset emitted
by apply_color/apply_style. Both display paths hit this: ansi_trim_end()
truncates to drop trailing column padding, and every row is then
truncated to the terminal width. As a result nearly every emitted row
ends with an open style, leaving the terminal bold/colored after procs
exits.

Append a reset in truncate() when the retained output contains an escape
sequence, so both call sites are covered. Plain (--color=never) output is
untouched.
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.

Font style not reset at the end of execution

1 participant