Skip to content

expand/unexpand: fix display width of 3- and 4-byte UTF-8 characters #13352

Open
sylvestre wants to merge 4 commits into
uutils:mainfrom
sylvestre:expand-unexpand-mb-width
Open

expand/unexpand: fix display width of 3- and 4-byte UTF-8 characters #13352
sylvestre wants to merge 4 commits into
uutils:mainfrom
sylvestre:expand-unexpand-mb-width

Conversation

@sylvestre

Copy link
Copy Markdown
Contributor

No description provided.

The UTF-8 sequence length was derived from char::from(b).len_utf8(),
which uses the leading byte's value as a codepoint rather than decoding
the sequence length from its bit pattern. This only happened to be
correct for 2-byte characters; 3- and 4-byte characters were split into
individual bytes and counted as one column each, so a following tab
expanded to the wrong column.

Should make test tests/expand/mb.sh pass
next_char_info derived the UTF-8 sequence length from char::from(b).len_utf8()
(the leading byte's codepoint value, correct only for 2-byte sequences) and
then used that byte count as the display width. So 3- and 4-byte characters
were mis-measured, and every multibyte character advanced the column by its
byte length instead of its display width, mis-aligning tab conversion.

Derive the length from the leading byte and use UnicodeWidthChar for the width,
matching GNU 9.11. Update the multibyte column test, which asserted the old
byte-count behavior.
@codspeed-hq

codspeed-hq Bot commented Jul 11, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by ×2.6

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
✅ 329 untouched benchmarks
⏩ 46 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation unexpand_large_file[10] 282.3 ms 107 ms ×2.6
Simulation unexpand_many_lines[100000] 134.7 ms 51.2 ms ×2.6

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing sylvestre:expand-unexpand-mb-width (e607a91) with main (e646122)

Open in CodSpeed

Footnotes

  1. 46 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@sylvestre sylvestre force-pushed the expand-unexpand-mb-width branch from 82a054d to f0138a1 Compare July 11, 2026 01:34
Both utilities derived the UTF-8 sequence length from the leading byte and
computed a character's display width identically. Extract that into a
uucore::char_width helper and use it from both, removing the duplicated (and
subtle, w.r.t. invalid UTF-8) logic.

unicode-width is a direct, non-optional uucore dependency: os_display already
pulls it into every uucore build, so gating the helper behind an optional-dep
feature added no leanness and failed to activate on wasm.
The fast path that batches leading spaces/tabs was gated on --no-utf8, so
the default (UTF-8) mode fell back to the per-byte loop, which the display-width
fix made measurably slower on ASCII input. Leading whitespace is ASCII and
independent of UTF-8 mode, so run the fast path there too, and add the missing
lastcol == 0 guard so it does not apply when multiple tab stops cap conversion.
@sylvestre sylvestre force-pushed the expand-unexpand-mb-width branch from f0138a1 to 3c971f8 Compare July 11, 2026 01:41
@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown

GNU testsuite comparison:

Skip an intermittent issue tests/rm/isatty (fails in this run but passes in the 'main' branch)
Congrats! The gnu test tests/expand/mb is no longer failing!
Note: The gnu test tests/cut/cut-huge-range is now being skipped but was previously passing.
Note: The gnu test tests/env/env-signal-handler was skipped on 'main' but is now failing.

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