const COMBINING_MARK_RANGES: ReadonlyArray = [ [0x0300, 0x036f], [0x1ab0, 0x1aff], [0x1dc0, 0x1dff], [0x20d0, 0x20ff], [0xfe20, 0xfe2f], ]; const WIDE_CHARACTER_RANGES: ReadonlyArray = [ [0x1100, 0x115f], [0x231a, 0x231b], [0x2329, 0x232a], [0x23e9, 0x23ec], [0x23f0, 0x23f0], [0x23f3, 0x23f3], [0x25fd, 0x25fe], [0x2614, 0x2615], [0x2648, 0x2653], [0x267f, 0x267f], [0x2693, 0x2693], [0x26a1, 0x26a1], [0x26aa, 0x26ab], [0x26bd, 0x26be], [0x26c4, 0x26c5], [0x26ce, 0x26ce], [0x26d4, 0x26d4], [0x26ea, 0x26ea], [0x26f2, 0x26f3], [0x26f5, 0x26f5], [0x26fa, 0x26fa], [0x26fd, 0x26fd], [0x2705, 0x2705], [0x270a, 0x270b], [0x2728, 0x2728], [0x274c, 0x274c], [0x274e, 0x274e], [0x2753, 0x2755], [0x2757, 0x2757], [0x2795, 0x2797], [0x27b0, 0x27b0], [0x27bf, 0x27bf], [0x2b1b, 0x2b1c], [0x2b50, 0x2b50], [0x2b55, 0x2b55], [0x2e80, 0xa4cf], [0xac00, 0xd7a3], [0xf900, 0xfaff], [0xfe10, 0xfe19], [0xfe30, 0xfe6f], [0xff00, 0xff60], [0xffe0, 0xffe6], [0x1f300, 0x1f64f], [0x1f680, 0x1f6ff], [0x1f900, 0x1f9ff], [0x20000, 0x3fffd], ]; export function visibleCellWidth(text: string): number { let width = 0; for (const char of text) { width += codePointCellWidth(char.codePointAt(0) ?? 0); } return width; } export function isSingleCellGlyph(text: string): boolean { return Array.from(text).length === 1 && visibleCellWidth(text) === 1; } function codePointCellWidth(codePoint: number): number { if (codePoint === 0) return 0; if (codePoint < 0x20 || (codePoint >= 0x7f && codePoint < 0xa0)) return 0; if (isInRanges(codePoint, COMBINING_MARK_RANGES)) return 0; if (isInRanges(codePoint, WIDE_CHARACTER_RANGES)) return 2; return 1; } function isInRanges( codePoint: number, ranges: ReadonlyArray, ): boolean { return ranges.some(([start, end]) => codePoint >= start && codePoint <= end); }