Skip to content

util: add getStringWidth() - #66642

Open
marcopiraccini wants to merge 4 commits into
nodejs:mainfrom
marcopiraccini:util-get-string-width
Open

marcopiraccini wants to merge 4 commits into
nodejs:mainfrom
marcopiraccini:util-get-string-width

Conversation

@marcopiraccini

@marcopiraccini marcopiraccini commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Expose getStringWidth() as the experimental util.getStringWidth(str) API.

The function estimates terminal width by grapheme cluster. Full-width characters and emoji sequences count as two columns; invisible clusters count as zero. ANSI escape sequences are ignored. Its results matched the string-width package on 200,000 random strings.

The existing width helper is also used by readline. It counts each emoji in a ZWJ sequence separately, a behavior introduced in #31112 for cursor movement. This PR leaves that helper unchanged and uses a new grapheme-aware ICU function for the public API. Builds without ICU use an approximation and leave the shared width table unchanged.

Bun provides Bun.stringWidth(), and Deno's @std/cli provides unicodeWidth(). PR #40214 is a precedent for exposing an existing utility as a public API.

Refs: #31112
Refs: #40214

Expose the string width helper that readline, the REPL and the test
runner already use. It returns the number of terminal columns a string
occupies: full-width characters count as two, zero-width characters as
zero, and ANSI escape sequences are ignored. The API is experimental.

Refs: nodejs#40214
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. util Issues and PRs related to the built-in util module. labels Oct 10, 2026
The internal helper counts code points and, since nodejs#31112, counts each
emoji of a ZWJ sequence separately, because terminals of the time did
not render such sequences as one glyph. That is still right for the
cursor movement in readline, where overcounting is the safer error,
but for a public API it disagrees with what terminals render today and
with the string-width package that most CLI tools use.

Add a grapheme cluster based width to the ICU binding and use it for
util.getStringWidth(): an emoji sequence, flag, keycap or skin tone
sequence takes two columns, Hangul jamo are grouped into syllable
blocks, and spacing marks and halfwidth forms add their own columns.
The build without ICU approximates this on top of its code point
tables. readline and the REPL keep the code point based function.

Refs: nodejs#31112
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
@marcopiraccini
marcopiraccini marked this pull request as ready for review October 11, 2026 08:42
@codecov

codecov Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.32636% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.49%. Comparing base (29dd655) to head (5c0b2f0).
⚠️ Report is 35 commits behind head on main.

Files with missing lines Patch % Lines
src/node_i18n.cc 95.78% 0 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #66642      +/-   ##
==========================================
+ Coverage   90.43%   90.49%   +0.06%     
==========================================
  Files         791      791              
  Lines      276609   277284     +675     
  Branches    53118    53359     +241     
==========================================
+ Hits       250154   250940     +786     
+ Misses      16867    16754     -113     
- Partials     9588     9590       +2     
Files with missing lines Coverage Δ
lib/internal/util/inspect.js 98.83% <100.00%> (+1.78%) ⬆️
lib/util.js 100.00% <100.00%> (ø)
src/node_i18n.cc 81.93% <95.78%> (+3.66%) ⬆️

... and 45 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Move the width fallback used without ICU, and the table it shares with
the code point based helper, out of the hasIntl branch so that a test
can call it on builds with ICU. Cover the remaining branches of the
grapheme cluster width in the ICU binding: unqualified ZWJ sequences,
keycap bases, a jamo cluster with a spacing mark and an invalid flag.

Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Assign the fallback when it is defined and let the build with ICU
replace it, instead of assigning it in the branch that only runs
without ICU. Also cover the last keycap base branch.

Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-ci PRs that need a full CI run. util Issues and PRs related to the built-in util module.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants