takt/src/shared/utils/text.ts
2026-02-02 17:11:42 +09:00

57 lines
1.8 KiB
TypeScript

/**
* Text display width utilities
*
* Pure functions for calculating and truncating text based on
* terminal display width, with full-width (CJK) character support.
*/
/**
* Check if a Unicode code point is full-width (occupies 2 columns).
* Covers CJK unified ideographs, Hangul, fullwidth forms, etc.
*/
export function isFullWidth(code: number): boolean {
return (
(code >= 0x1100 && code <= 0x115F) || // Hangul Jamo
(code >= 0x2E80 && code <= 0x9FFF) || // CJK radicals, symbols, ideographs
(code >= 0xAC00 && code <= 0xD7AF) || // Hangul syllables
(code >= 0xF900 && code <= 0xFAFF) || // CJK compatibility ideographs
(code >= 0xFE10 && code <= 0xFE6F) || // CJK compatibility forms
(code >= 0xFF01 && code <= 0xFF60) || // Fullwidth ASCII variants
(code >= 0xFFE0 && code <= 0xFFE6) || // Fullwidth symbols
(code >= 0x20000 && code <= 0x2FA1F) // CJK extension B+
);
}
/**
* Calculate the display width of a plain text string.
* Full-width characters (CJK etc.) count as 2, others as 1.
*/
export function getDisplayWidth(text: string): number {
let width = 0;
for (const char of text) {
const code = char.codePointAt(0) ?? 0;
width += isFullWidth(code) ? 2 : 1;
}
return width;
}
/**
* Truncate plain text to fit within maxWidth display columns.
* Appends '…' if truncated. The ellipsis itself counts as 1 column.
*/
export function truncateText(text: string, maxWidth: number): string {
if (maxWidth <= 0) return '';
let width = 0;
let i = 0;
for (const char of text) {
const charWidth = isFullWidth(char.codePointAt(0) ?? 0) ? 2 : 1;
if (width + charWidth > maxWidth - 1) {
// Not enough room; truncate and add ellipsis
return text.slice(0, i) + '…';
}
width += charWidth;
i += char.length;
}
return text;
}