Measuring
width() counts terminal columns by grapheme cluster and East Asian Width, with string-width's two options; lineCount, measure and widest answer the neighbouring questions.
width(text) is the number of terminal columns text occupies once drawn. It is the default
export of linegauge, and string-width's own suite grades it
(Compatibility).
How a width is counted
The text is split into grapheme clusters with the platform's Intl.Segmenter, and each
cluster is counted once:
- a cluster that draws nothing — a control character, a zero-width joiner on its own, a combining mark with no base — is 0;
- an emoji, a ZWJ sequence or a keycap is 2, however many code points it is made of;
- anything else is the East Asian Width of its first visible code point: 2 for wide and fullwidth, 1 otherwise, plus any spacing marks that follow it in the cluster;
- escape sequences are removed first and count 0.
import { lineCount, measure, width } from 'linegauge';
console.log(width('±×÷'));
console.log(width('±×÷', { ambiguousIsNarrow: false }));
console.log(width('\u001B[1mbold\u001B[22m'));
console.log(width('\u001B[1mbold\u001B[22m', { countAnsiEscapeCodes: true }));
console.log(width('é'));
console.log(width('🇯🇵'));
console.log(width(undefined));
console.log(lineCount('one\n\nthe quick brown fox jumps', 10));
console.log(measure('古池や'));3
6
4
11
1
2
0
5
6The East Asian Width tables are generated from the pinned get-east-asian-width, the one
string-width 8.3.0 uses, and are at Unicode 17: U+18D80, which Unicode 17 made wide, is two
columns (width.test.ts asserts it beside string-width). A script run with --check in the
same test fails when the committed tables drift from the package.
Options
width(text, { ambiguousIsNarrow, countAnsiEscapeCodes }) takes string-width's two options,
under string-width's names and with its defaults:
| option | default | |
|---|---|---|
ambiguousIsNarrow | true | Ambiguous-width characters (±, ×, Greek, Cyrillic, box drawing) count one column. Pass false for a terminal drawing with a CJK font, where they are two. |
countAnsiEscapeCodes | false | Count escape sequences as characters instead of removing them, less the escape byte itself. |
No terminal reports which ambiguous-width policy it uses, so that one is always the caller's decision. A terminal that disagrees with Unicode about a particular code point — a Nerd Font icon in the Private Use Area — is a width plugin, not an option.
Things that are not strings
width(undefined), width(42) and width(null) are 0, not a thrown TypeError. A width
call is usually reached with whatever a template produced, and a layout that measures one
missing cell as zero is better than a CLI that exits.
Rows: lineCount
lineCount(text, columns) is how many terminal rows the text occupies at that width: each
line of the text, wrapped. An empty line still takes a row, which is why the example above is
five — one, the empty line, and three rows of the sentence.
Plain text: measure
measure(text) is width without the escape scan, for text you already know is plain. It
takes a second argument, true, to count ambiguous characters wide.
Many lines: widest
import { widest } from 'linegauge';
function* names() {
yield 'a';
yield '古池や';
yield '\u001B[31mred\u001B[39m';
}
console.log(widest(names()));
console.log(widest(new Set(['ab', 'abcd'])));
console.log(widest([]));6
4
0widest takes any iterable — an array, a Set, a generator — and measures it in one
pass, so a long list never has to be spread into an argument list, where it would hit the
engine's argument limit (truncate.test.ts measures far more lines than a spread can pass). It
takes lines rather than one string with newlines in it, so which newline convention splits them
is your decision.
Getting started
Install linegauge, measure a string in terminal columns, and wrap, truncate and slice styled text without cutting a grapheme cluster or leaking a colour.
Wrapping
wrap() folds styled text to a column width, closing every style and hyperlink at the end of a row and reopening it on the next — wrap-ansi's API and options, graded by its suite.