Skip to content

fix: preserve ANSI styles across wrapped lines - #132

Open
tecfu wants to merge 59 commits into
masterfrom
fix/preserve-ansi-wrap-state
Open

tecfu wants to merge 59 commits into
masterfrom
fix/preserve-ansi-wrap-state

Conversation

@tecfu

@tecfu tecfu commented Oct 6, 2026

Copy link
Copy Markdown
Owner

The renderer currently strips only leading/trailing SGR sequences before wrapping and reattaches them afterward. If a wrap boundary lands inside a styled span, the continuation line loses the active style. Let smartwrap handle ANSI sequences directly and add a regression test for a style crossing a wrap boundary.

@tecfu

tecfu commented Oct 6, 2026

Copy link
Copy Markdown
Owner Author

Review: do not merge

18 of 21 goldens fail. The premise — "let smartwrap handle ANSI sequences directly" — does not hold with smartwrap@4 as configured here: it is not preserving (or is actively dropping/mis-widening) the SGR runs, so styled cells come out with the escape codes gone and alignment shifted. Diff for examples/wide-characters.js:

- │<33m          ⚡️ <39m│          …   (master: style opens/closes around padded cell)
+ │           ⚡️           │          …   (branch: bare text, no SGR at all)

and every colored example golden fails the same way. Style is silently lost — a much worse regression than the mid-span-continuation bug being fixed.

Also: typecheck fails (test/format.test.ts references wrapCellText without importing it — TS2304), and the branch's own new test fails. It looks like the suite wasn't run before pushing.

The underlying bug is real and narrow: when a wrap boundary lands inside a styled span (not at a span edge), the continuation line loses the style, because today only start-of-string and end-of-string runs are hoisted and re-attached. But the fix must preserve the current byte-exact output for the common leading/trailing cases (all those goldens) — e.g., split the cell into style runs, carry the active run's opens onto the next line's head (plus a close on the previous line's tail), and re-attach as today for pure leading/trailing spans. Verify with:

  • all 21 goldens unchanged (they are the regression suite for styling),
  • 71-case matrix 0 on everything except a new mid-span probe case you add,
  • the new test with the import fixed.

Sincerely qwen3.8-flash-next

tecfu commented Oct 6, 2026

Copy link
Copy Markdown
Owner Author

Review: request changes

The underlying bug is real, but the current fix is still too dependent on the distinction between boundary-only and inline ANSI.

hasInlineAnsi is true only when there is neither a leading nor trailing ANSI sequence. A string containing both a boundary style and an additional inline style remains on the old path, which can still expose an internal style transition to wrapping.

More importantly, the regression test only checks that some opening SGR occurs somewhere before the final line and that some closing SGR occurs somewhere after the first line. It does not establish that the continuation line actually carries the active style. A buggy implementation could satisfy those assertions while still rendering the continuation unstyled.

Please add a test that explicitly inspects individual wrapped lines: the continuation of the styled span should carry the active SGR, and the previous line should be properly closed. Also cover a mixed leading-style plus inline-style case.

I would not merge until the test proves the actual invariant rather than merely the presence of the two escape sequences.

— GPT-5.6 Luna

tecfu commented Oct 7, 2026

Copy link
Copy Markdown
Owner Author

Re-review: changes addressed

Interior ANSI sequences are preserved even with boundary styles, and the regression verifies the active style on wrapped continuation lines, including mixed boundary/inline ANSI.

CI is green. No further changes requested.

— GPT-5.6 Luna

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