Skip to content

Commit

Permalink
Use Intl.Segmenter, require Node.js v16 (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker authored May 5, 2023
1 parent 9f90691 commit f85812f
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 23 deletions.
9 changes: 5 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ jobs:
fail-fast: false
matrix:
node-version:
- 14
- 12
- 20
- 18
- 16
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install
Expand Down
4 changes: 2 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export interface Options {
export type Options = {
/**
Count [ambiguous width characters](https://www.unicode.org/reports/tr11/#Ambiguous) as having narrow width (count of 1) instead of wide width (count of 2).
@default true
*/
readonly ambiguousIsNarrow: boolean;
}
};

/**
Get the visual width of a string - the number of columns required to display it.
Expand Down
28 changes: 21 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@ import stripAnsi from 'strip-ansi';
import eastAsianWidth from 'eastasianwidth';
import emojiRegex from 'emoji-regex';

export default function stringWidth(string, options = {}) {
let segmenter;
function * splitString(string) {
segmenter ??= new Intl.Segmenter();

for (const {segment: character} of segmenter.segment(string)) {
yield character;
}
}

export default function stringWidth(string, options) {
if (typeof string !== 'string' || string.length === 0) {
return 0;
}

options = {
ambiguousIsNarrow: true,
...options
...options,
};

string = stripAnsi(string);
Expand All @@ -23,7 +32,7 @@ export default function stringWidth(string, options = {}) {
const ambiguousCharacterWidth = options.ambiguousIsNarrow ? 1 : 2;
let width = 0;

for (const character of string) {
for (const character of splitString(string)) {
const codePoint = character.codePointAt(0);

// Ignore control characters
Expand All @@ -32,21 +41,26 @@ export default function stringWidth(string, options = {}) {
}

// Ignore combining characters
if (codePoint >= 0x300 && codePoint <= 0x36F) {
if (codePoint >= 0x3_00 && codePoint <= 0x3_6F) {
continue;
}

const code = eastAsianWidth.eastAsianWidth(character);
switch (code) {
case 'F':
case 'W':
case 'W': {
width += 2;
break;
case 'A':
}

case 'A': {
width += ambiguousCharacterWidth;
break;
default:
}

default: {
width += 1;
}
}
}

Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"type": "module",
"exports": "./index.js",
"engines": {
"node": ">=12"
"node": ">=16"
},
"scripts": {
"test": "xo && ava && tsd"
Expand Down Expand Up @@ -48,12 +48,12 @@
],
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"emoji-regex": "^10.2.1",
"strip-ansi": "^7.0.1"
},
"devDependencies": {
"ava": "^3.15.0",
"tsd": "^0.14.0",
"xo": "^0.38.2"
"ava": "^5.2.0",
"tsd": "^0.28.1",
"xo": "^0.54.2"
}
}
13 changes: 8 additions & 5 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@ test('main', t => {
t.is(stringWidth('\u{2194}\u{FE0F}'), 2, '↔️ default text presentation character rendered as emoji');
t.is(stringWidth('\u{1F469}'), 2, '👩 emoji modifier base (Emoji_Modifier_Base)');
t.is(stringWidth('\u{1F469}\u{1F3FF}'), 2, '👩🏿 emoji modifier base followed by a modifier');
t.is(stringWidth('\u{845B}\u{E0100}'), 2, 'Variation Selectors');
t.is(stringWidth('ปฏัก'), 3, 'Thai script');
t.is(stringWidth('_\u0E34'), 1, 'Thai script');
});

test('ignores control characters', t => {
t.is(stringWidth(String.fromCharCode(0)), 0);
t.is(stringWidth(String.fromCharCode(31)), 0);
t.is(stringWidth(String.fromCharCode(127)), 0);
t.is(stringWidth(String.fromCharCode(134)), 0);
t.is(stringWidth(String.fromCharCode(159)), 0);
t.is(stringWidth(String.fromCodePoint(0)), 0);
t.is(stringWidth(String.fromCodePoint(31)), 0);
t.is(stringWidth(String.fromCodePoint(127)), 0);
t.is(stringWidth(String.fromCodePoint(134)), 0);
t.is(stringWidth(String.fromCodePoint(159)), 0);
t.is(stringWidth('\u001B'), 0);
});

Expand Down

0 comments on commit f85812f

Please sign in to comment.