12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182 |
- import stripAnsi from 'strip-ansi';
- import {eastAsianWidth} from 'get-east-asian-width';
- import emojiRegex from 'emoji-regex';
- const segmenter = new Intl.Segmenter();
- const defaultIgnorableCodePointRegex = /^\p{Default_Ignorable_Code_Point}$/u;
- export default function stringWidth(string, options = {}) {
- if (typeof string !== 'string' || string.length === 0) {
- return 0;
- }
- const {
- ambiguousIsNarrow = true,
- countAnsiEscapeCodes = false,
- } = options;
- if (!countAnsiEscapeCodes) {
- string = stripAnsi(string);
- }
- if (string.length === 0) {
- return 0;
- }
- let width = 0;
- const eastAsianWidthOptions = {ambiguousAsWide: !ambiguousIsNarrow};
- for (const {segment: character} of segmenter.segment(string)) {
- const codePoint = character.codePointAt(0);
- // Ignore control characters
- if (codePoint <= 0x1F || (codePoint >= 0x7F && codePoint <= 0x9F)) {
- continue;
- }
- // Ignore zero-width characters
- if (
- (codePoint >= 0x20_0B && codePoint <= 0x20_0F) // Zero-width space, non-joiner, joiner, left-to-right mark, right-to-left mark
- || codePoint === 0xFE_FF // Zero-width no-break space
- ) {
- continue;
- }
- // Ignore combining characters
- if (
- (codePoint >= 0x3_00 && codePoint <= 0x3_6F) // Combining diacritical marks
- || (codePoint >= 0x1A_B0 && codePoint <= 0x1A_FF) // Combining diacritical marks extended
- || (codePoint >= 0x1D_C0 && codePoint <= 0x1D_FF) // Combining diacritical marks supplement
- || (codePoint >= 0x20_D0 && codePoint <= 0x20_FF) // Combining diacritical marks for symbols
- || (codePoint >= 0xFE_20 && codePoint <= 0xFE_2F) // Combining half marks
- ) {
- continue;
- }
- // Ignore surrogate pairs
- if (codePoint >= 0xD8_00 && codePoint <= 0xDF_FF) {
- continue;
- }
- // Ignore variation selectors
- if (codePoint >= 0xFE_00 && codePoint <= 0xFE_0F) {
- continue;
- }
- // This covers some of the above cases, but we still keep them for performance reasons.
- if (defaultIgnorableCodePointRegex.test(character)) {
- continue;
- }
- // TODO: Use `/\p{RGI_Emoji}/v` when targeting Node.js 20.
- if (emojiRegex().test(character)) {
- width += 2;
- continue;
- }
- width += eastAsianWidth(codePoint, eastAsianWidthOptions);
- }
- return width;
- }
|