Skip to content

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

Open
sylvestre wants to merge 3 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 3 commits into
uutils:mainfrom
sylvestre:expand-unexpand-mb-width

Conversation

@sylvestre

Copy link
Copy Markdown
Contributor

No description provided.

sylvestre added 3 commits July 9, 2026 22:00
…lper

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 length from its
bit pattern. That is only correct for 2-byte characters; 3- and 4-byte ones were
split into single bytes and each counted as one column, so a following tab
expanded to the wrong column. Derive the length from the leading byte and
measure the display width with unicode-width.

expand and unexpand needed identical logic, so it lives in a new
uucore::char_width helper (char_width_at) instead of being duplicated.

Should make test tests/expand/mb.sh pass
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.
GNU treats a wide blank such as U+3000 (any character for which iswblank is
true) as a convertible blank: a run of them that reaches a tab stop is absorbed
into a tab, but a run that does not is left as-is. Recognize the Unicode blank
set in next_char_info, advance the column by the blank's display width, and
remember buffered wide blanks so write_tabs can emit them verbatim when their
run is not converted (and never let a tab straddle one).

Expose the decoded char from uucore::char_width via char_info_at so unexpand
reuses the shared UTF-8 decoding instead of duplicating it.

Should make test tests/unexpand/mb.sh pass.
@codspeed-hq

codspeed-hq Bot commented Jul 11, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 65.27%

⚠️ 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
❌ 1 regressed benchmark
✅ 328 untouched benchmarks
⏩ 46 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation du_all_wide_tree[(5000, 500)] 16.3 ms 17.1 ms -4.14%
Simulation unexpand_large_file[10] 282.3 ms 130 ms ×2.2
Simulation unexpand_many_lines[100000] 134.7 ms 62.1 ms ×2.2

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing sylvestre:expand-unexpand-mb-width (b21d0d6) with main (3ceaab5)

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 2 times, most recently 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/cut/bounded-memory (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/tail/retry (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/expand/mb is no longer failing!
Congrats! The gnu test tests/unexpand/mb is no longer failing!
Note: The gnu test tests/printf/printf-surprise is now being skipped but was previously passing.
Congrats! The gnu test tests/pr/bounded-memory is now passing!

@sylvestre sylvestre force-pushed the expand-unexpand-mb-width branch from e607a91 to b21d0d6 Compare July 11, 2026 21:18
@sylvestre sylvestre requested a review from cakebaker July 12, 2026 13:15
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