UNPKG

@logtape/pretty

Version:

Beautiful text formatter for LogTape—perfect for local development

276 lines (275 loc) 8.2 kB
//#region wcwidth.ts /** * @fileoverview * wcwidth implementation for JavaScript/TypeScript * * This module provides functions to calculate the display width of Unicode * characters and strings in terminal/monospace contexts, compatible with * the Python wcwidth library and POSIX wcwidth() standard. * * Based on Unicode 15.1.0 character width tables. */ const ANSI_PATTERN = /\x1b\[[0-9;]*m/g; /** * Remove all ANSI escape sequences from a string. * * @param text The string to clean * @returns String with ANSI escape sequences removed */ function stripAnsi(text) { return text.replace(ANSI_PATTERN, ""); } /** * Calculate the display width of a string, ignoring ANSI escape codes * and accounting for Unicode character widths using wcwidth-compatible logic. * * @param text The string to measure * @returns The display width in terminal columns */ function getDisplayWidth(text) { const cleanText = stripAnsi(text); if (cleanText.length === 0) return 0; let width = 0; let i = 0; while (i < cleanText.length) { const code = cleanText.codePointAt(i); if (code === void 0) { i++; continue; } const charWidth = wcwidth(code); if (charWidth >= 0) width += charWidth; i += code > 65535 ? 2 : 1; } return width; } /** * Get the display width of a single Unicode code point. * Based on wcwidth implementation - returns: * -1: Non-printable/control character * 0: Zero-width character (combining marks, etc.) * 1: Normal width character * 2: Wide character (East Asian, emoji, etc.) * * @param code Unicode code point * @returns Display width (-1, 0, 1, or 2) */ function wcwidth(code) { if (code < 32 || code >= 127 && code < 160) return -1; if (isZeroWidth(code)) return 0; if (isWideCharacter(code)) return 2; return 1; } const ZERO_WIDTH_RANGES = [ [768, 879], [1155, 1161], [1425, 1469], [1473, 1474], [1476, 1477], [1552, 1562], [1611, 1631], [1750, 1756], [1759, 1764], [1767, 1768], [1770, 1773], [1840, 1866], [1958, 1968], [2027, 2035], [2070, 2073], [2075, 2083], [2085, 2087], [2089, 2093], [2137, 2139], [2259, 2273], [2275, 2306], [2369, 2376], [2385, 2391], [2402, 2403], [2497, 2500], [2530, 2531], [2561, 2562], [2625, 2626], [2631, 2632], [2635, 2637], [2672, 2673], [2689, 2690], [2753, 2757], [2759, 2760], [2786, 2787], [2810, 2815], [2881, 2884], [2901, 2902], [2914, 2915], [3134, 3136], [3142, 3144], [3146, 3149], [3157, 3158], [3170, 3171], [3276, 3277], [3298, 3299], [3328, 3329], [3387, 3388], [3426, 3427], [3538, 3540], [3636, 3642], [3655, 3662], [3764, 3772], [3784, 3789], [3864, 3865], [3953, 3966], [3968, 3972], [3974, 3975], [3981, 3991], [3993, 4028], [4141, 4144], [4146, 4151], [4153, 4154], [4157, 4158], [4184, 4185], [4190, 4192], [4209, 4212], [4229, 4230], [4957, 4959], [5906, 5908], [5938, 5940], [5970, 5971], [6002, 6003], [6068, 6069], [6071, 6077], [6089, 6099], [6155, 6157], [6277, 6278], [6432, 6434], [6439, 6440], [6457, 6459], [6679, 6680], [6744, 6750], [6757, 6764], [6771, 6780], [6832, 6846], [6912, 6915], [6966, 6970], [7019, 7027], [7040, 7041], [7074, 7077], [7080, 7081], [7083, 7085], [7144, 7145], [7151, 7153], [7212, 7219], [7222, 7223], [7376, 7378], [7380, 7392], [7394, 7400], [7416, 7417], [7616, 7673], [7675, 7679], [8203, 8207], [8234, 8238], [8288, 8292], [8294, 8303], [65024, 65039], [65056, 65071] ]; const ZERO_WIDTH_SINGLES = new Set([ 1471, 1479, 1648, 1809, 2045, 2362, 2364, 2381, 2433, 2492, 2509, 2558, 2620, 2641, 2677, 2748, 2765, 2817, 2876, 2879, 2893, 2946, 3008, 3021, 3072, 3076, 3201, 3260, 3263, 3270, 3393, 3396, 3405, 3457, 3530, 3542, 3633, 3761, 3893, 3895, 3897, 4038, 4226, 4237, 4253, 6086, 6109, 6313, 6450, 6683, 6742, 6752, 6754, 6783, 6964, 6972, 6978, 7142, 7149, 7405, 7412, 65279 ]); /** * Binary search to check if a value is within any range */ function isInRanges(code, ranges) { let left = 0; let right = ranges.length - 1; while (left <= right) { const mid = Math.floor((left + right) / 2); const [start, end] = ranges[mid]; if (code >= start && code <= end) return true; else if (code < start) right = mid - 1; else left = mid + 1; } return false; } /** * Check if a character is zero-width (combining marks, etc.) * Based on wcwidth's zero-width table. * * @param code Unicode code point * @returns True if the character has zero display width */ function isZeroWidth(code) { return ZERO_WIDTH_SINGLES.has(code) || isInRanges(code, ZERO_WIDTH_RANGES); } /** * Check if a character code point represents a wide character. * Based on wcwidth's wide character table (selected ranges from Unicode 15.1.0). * * @param code Unicode code point * @returns True if the character has width 2 */ function isWideCharacter(code) { return code >= 4352 && code <= 4447 || code >= 8986 && code <= 8987 || code >= 9001 && code <= 9002 || code >= 9193 && code <= 9196 || code === 9200 || code === 9203 || code >= 9725 && code <= 9726 || code >= 9748 && code <= 9749 || code >= 9800 && code <= 9811 || code === 9855 || code === 9875 || code === 9888 || code === 9889 || code === 9898 || code === 9899 || code >= 9917 && code <= 9918 || code >= 9924 && code <= 9925 || code === 9934 || code === 9940 || code >= 9962 && code <= 9962 || code >= 9970 && code <= 9971 || code === 9973 || code === 9978 || code === 9981 || code >= 9989 && code <= 9989 || code >= 9994 && code <= 9995 || code === 10024 || code === 10060 || code === 10062 || code >= 10067 && code <= 10069 || code === 10071 || code >= 10133 && code <= 10135 || code === 10160 || code === 10175 || code >= 11035 && code <= 11036 || code === 11088 || code === 11093 || code >= 11904 && code <= 11929 || code >= 11931 && code <= 12019 || code >= 12032 && code <= 12245 || code >= 12272 && code <= 12283 || code >= 12288 && code <= 12350 || code >= 12353 && code <= 12438 || code >= 12441 && code <= 12543 || code >= 12549 && code <= 12591 || code >= 12593 && code <= 12686 || code >= 12688 && code <= 12771 || code >= 12784 && code <= 12830 || code >= 12832 && code <= 12871 || code >= 12880 && code <= 19903 || code >= 19968 && code <= 40959 || code >= 43360 && code <= 43391 || code >= 44032 && code <= 55203 || code >= 55216 && code <= 55238 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65049 || code >= 65072 && code <= 65135 || code >= 65280 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 94176 && code <= 94180 || code >= 94192 && code <= 94193 || code >= 94208 && code <= 100343 || code >= 100352 && code <= 101589 || code >= 101632 && code <= 101640 || code >= 110576 && code <= 110579 || code >= 110581 && code <= 110587 || code >= 110589 && code <= 110590 || code >= 110592 && code <= 110882 || code >= 110928 && code <= 110930 || code >= 110948 && code <= 110951 || code >= 110960 && code <= 111355 || code === 126980 || code === 127183 || code >= 127374 && code <= 127374 || code >= 127377 && code <= 127386 || code >= 127462 && code <= 127487 || code >= 127488 && code <= 127490 || code >= 127504 && code <= 127547 || code >= 127552 && code <= 127560 || code >= 127568 && code <= 127569 || code >= 127584 && code <= 127589 || code >= 127744 && code <= 128727 || code >= 128736 && code <= 128748 || code >= 128752 && code <= 128764 || code >= 128768 && code <= 128883 || code >= 128896 && code <= 128984 || code >= 128992 && code <= 129003 || code >= 129008 && code <= 129008 || code >= 129024 && code <= 129035 || code >= 129040 && code <= 129095 || code >= 129104 && code <= 129113 || code >= 129120 && code <= 129159 || code >= 129168 && code <= 129197 || code >= 129200 && code <= 129201 || code >= 129280 && code <= 129619 || code >= 129632 && code <= 129645 || code >= 129648 && code <= 129660 || code >= 129664 && code <= 129672 || code >= 129680 && code <= 129725 || code >= 129727 && code <= 129733 || code >= 129742 && code <= 129755 || code >= 129760 && code <= 129768 || code >= 129776 && code <= 129784 || code >= 131072 && code <= 196605 || code >= 196608 && code <= 262141; } //#endregion export { getDisplayWidth, stripAnsi }; //# sourceMappingURL=wcwidth.js.map