Skip to content

bugfix: restore ITU T.416 colon-format colors with colon separators - #239

Closed
dylanpulver wants to merge 1 commit into
jquast:masterfrom
dylanpulver:fix-sgr-colon-color-restore
Closed

dylanpulver wants to merge 1 commit into
jquast:masterfrom
dylanpulver:fix-sgr-colon-color-restore

Conversation

@dylanpulver

@dylanpulver dylanpulver commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

_sgr_state_to_sequence() joins the parsed color parameters with ;. A colon-format color carries the ITU-T T.416 (ISO/IEC 8613-6) §13.1.8 colour space id element — 38:2:<cs>:R:G:B — which the legacy 38;2;R;G;B form has no slot for, so the restored sequence shifts R, G and B one position and leaves a trailing parameter.

before this change:

>>> wcwidth.wrap('\x1b[38:2::255:0:0mred text', width=4)
['\x1b[38:2::255:0:0mred\x1b[0m', '\x1b[38;2;0;255;0;0mtext\x1b[0m']
>>> # feeding that restored prefix back to the library's own parser:
>>> _sgr_state_update(_SGR_STATE_DEFAULT, '\x1b[38;2;0;255;0;0m').foreground
None

after:

>>> wcwidth.wrap('\x1b[38:2::255:0:0mred text', width=4)
['\x1b[38:2::255:0:0mred\x1b[0m', '\x1b[38:2:0:255:0:0mtext\x1b[0m']

The None is the second half of it: under ECMA-48 §8.3.117 the parameters are applied in order, so the extra 0 at the end is a RESET — the restoration sequence also clears any bold/underline it was carrying for that line. clip() shares the same call site.

38:5:N is unaffected (; and : are interchangeable there), so the change is scoped to the RGB tuple that actually has the extra element.

_parse_sgr_params() knows about the colon form and documents the 6-tuple it returns; _sgr_state_to_sequence() does not. The existing coverage matches that split: test_sgr_state_parse_colors_colon_format asserts the parsed tuple and never re-serializes it, and test_clip_sgr_result_is_self_contained — exactly the invariant that would catch this — is parametrized only over basic 3039 colors. Not from a real workload; found reading escape-sequence handling for values parsed by one path and re-emitted by another.

Alternative I rejected: normalizing the colour space element away at parse time (so 38:2::R:G:B and 38;2;R;G;B produce the same 5-tuple) also fixes it and needs no serializer change — but it changes the documented return of _parse_sgr_params() and fails test_sgr_state_parse_colors_colon_format. Happy to switch if you'd rather have the canonical form.

Verified: pytest tests/ --ignore=tests/test_benchmarks.py — 1262 passed / 4 skipped before, 1263 passed / 4 skipped after (the one new test). Reverting only wcwidth/sgr_state.py and keeping the new test: 1 failed, 24 passed in test_sgr_state.py, foreground: None != (38, 2, 0, 255, 0, 0). Full tox -e lint chain run locally: flake8, pylint 10.00/10, mypy --strict, pydocstyle, codespell, isort all clean.

Not tested: no real terminal was driven. The claim above is about what the emitted sequence denotes under T.416/ECMA-48 and under this library's own parser; how a given emulator resolves a 6-parameter 38;2;… is not something I measured.

AI assistance: this change was written with Claude Code (model Claude Opus 5). I reviewed the diff and ran everything reported above.

_sgr_state_to_sequence() joined the parsed color parameters with ';'.
A colon-format color carries the T.416 colour space element
(38:2:<cs>:R:G:B) that the legacy 38;2;R;G;B form has no slot for, so
the restored sequence shifted R, G and B one position and left a
trailing parameter, which reads as SGR 0.

    >>> wcwidth.wrap('\x1b[38:2::255:0:0mred text', width=4)[1]
    '\x1b[38;2;0;255;0;0mtext\x1b[0m'

Feeding that prefix back to _sgr_state_update() yields foreground=None:
the color is lost, and the trailing 0 also clears any bold/underline in
the same restoration sequence.

Co-authored-by: Claude <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (17986f5) to head (357f1ef).

Additional details and impacted files
@@            Coverage Diff            @@
##            master      #239   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           27        27           
  Lines         1976      1980    +4     
  Branches       463       464    +1     
=========================================
+ Hits          1976      1980    +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jquast

jquast commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Colon-delimited SGR parameters are rare, but I'm happy to add support for edge cases of any standard, thank you for identifying and creating a pull request.

@codspeed-hq

codspeed-hq Bot commented Sep 14, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 108 untouched benchmarks


Comparing dylanpulver:fix-sgr-colon-color-restore (357f1ef) with master (17986f5)

Open in CodSpeed

@jquast jquast closed this in #243 Sep 14, 2026
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.

2 participants