Skip to content

fix(terminal,parser): honour SGR colon subparameters - #101

Merged
roramirez merged 1 commit into
mainfrom
fix/sgr-colon-subparams
Aug 9, 2026
Merged

fix(terminal,parser): honour SGR colon subparameters#101
roramirez merged 1 commit into
mainfrom
fix/sgr-colon-subparams

Conversation

@roramirez

@roramirez roramirez commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Problem

Any TUI that styles text with the extended CSI 4:<style> m form left the whole screen underlined for the rest of the session. Reproduced with Claude Code's session picker: after returning from the list, every line printed afterwards was underlined.

Root cause is in csi_dispatch, which flattened parameters with params.iter().map(|p| p[0]) and discarded everything after a colon:

  • CSI 4:3 m (curly underline) → read as bare 4 → underline on (correct by accident)
  • CSI 4:0 m (underline off) → also read as bare 4 → underline on again

There was no way to turn it back off, so the attribute leaked into every subsequent cell. The same flattening silently dropped the colon form of 38/48, so 38:2:r:g:b colours were ignored entirely.

Fix

csi_dispatch now collects Vec<&[u16]> — the full subparameter groups, with no per-parameter allocation — and passes them to handle_sgr alongside the existing flat view used by the legacy semicolon forms.

  • 4 reads its style subparameter: 4:0 disables, 4:1..4:5 enable (we render a single underline style), a bare 4 still enables.
  • The :3 in 4:3 no longer leaks through as SGR 3 (italic).
  • New parse_color_from_subparams handles 38:5:n, 38:2:r:g:b and 38:2::r:g:b (colour-space id slot present), for both fg and bg.

Tests

Nine unit tests in parser_test.rs cover each form in isolation: underline on/off by subparameter, the mixed 1;4:0;3 case, the no-leak-into-italic case, and the colon forms of truecolor and 256-colour.

The anchor is the end-to-end scenario scenario_tui_curly_underline_does_not_leak_after_exit, which replays the real shape of the bug — enter the alternate screen, draw with 4:3/4:0, exit, keep printing — and asserts on the resulting cells, not on the parser flag.

Verified it actually catches the regression: reverting only parser.rs and keeping the test makes it fail on the "must not be underlined" assert; with the fix it passes.

cargo fmt --check clean, cargo clippy --locked -- -D warnings clean, cargo test 1512 passed / 0 failed.

Manual check

Inside mmterm, printf '\033[4:3mA\033[4:0mB\n'A underlined, B not. Before this change both were underlined, along with everything after them.

csi_dispatch flattened parameters with params.iter().map(|p| p[0]),
discarding everything after a colon. `CSI 4:0 m` (underline off) was
therefore read as a bare `4` and *enabled* underline instead, so a TUI
that styled with `4:3` left every subsequent cell underlined for the
rest of the session.

SGR now receives the full subparameter groups: `4` reads its style
subparameter (0 off, 1-5 on), and the colon forms of 38/48 —
`38:5:n`, `38:2:r:g:b` and `38:2::r:g:b` with a colour-space id —
are applied instead of being ignored.
@roramirez
roramirez merged commit 5e4792a into main Aug 9, 2026
2 checks passed
@roramirez
roramirez deleted the fix/sgr-colon-subparams branch August 9, 2026 18:56
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