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; }