fix(terminal,parser): honour SGR colon subparameters - #101
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Any TUI that styles text with the extended
CSI 4:<style> mform 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 withparams.iter().map(|p| p[0])and discarded everything after a colon:CSI 4:3 m(curly underline) → read as bare4→ underline on (correct by accident)CSI 4:0 m(underline off) → also read as bare4→ underline on againThere 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, so38:2:r:g:bcolours were ignored entirely.Fix
csi_dispatchnow collectsVec<&[u16]>— the full subparameter groups, with no per-parameter allocation — and passes them tohandle_sgralongside the existing flat view used by the legacy semicolon forms.4reads its style subparameter:4:0disables,4:1..4:5enable (we render a single underline style), a bare4still enables.:3in4:3no longer leaks through as SGR 3 (italic).parse_color_from_subparamshandles38:5:n,38:2:r:g:band38:2::r:g:b(colour-space id slot present), for both fg and bg.Tests
Nine unit tests in
parser_test.rscover each form in isolation: underline on/off by subparameter, the mixed1;4:0;3case, 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 with4: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.rsand keeping the test makes it fail on the "must not be underlined" assert; with the fix it passes.cargo fmt --checkclean,cargo clippy --locked -- -D warningsclean,cargo test1512 passed / 0 failed.Manual check
Inside mmterm,
printf '\033[4:3mA\033[4:0mB\n'—Aunderlined,Bnot. Before this change both were underlined, along with everything after them.