function codePointWidth(codePoint: number): number { if (codePoint === 0) return 0; if (codePoint < 32 || (codePoint >= 0x7f && codePoint < 0xa0)) return 0; if ( (codePoint >= 0x1100 && codePoint <= 0x115f) || (codePoint >= 0x2329 && codePoint <= 0x232a) || (codePoint >= 0x2e80 && codePoint <= 0xa4cf) || (codePoint >= 0xac00 && codePoint <= 0xd7a3) || (codePoint >= 0xf900 && codePoint <= 0xfaff) || (codePoint >= 0xfe10 && codePoint <= 0xfe19) || (codePoint >= 0xfe30 && codePoint <= 0xfe6f) || (codePoint >= 0xff00 && codePoint <= 0xff60) || (codePoint >= 0xffe0 && codePoint <= 0xffe6) || (codePoint >= 0x1f300 && codePoint <= 0x1faff) || (codePoint >= 0x20000 && codePoint <= 0x3fffd) ) { return 2; } return 1; } export function visibleColumns(value: string): number { let width = 0; for (const character of value) width += codePointWidth(character.codePointAt(0) ?? 0); return width; } export function sliceColumns(value: string, start: number, width: number): string { if (width <= 0) return ""; let column = 0; let output = ""; for (const character of value) { const characterWidth = codePointWidth(character.codePointAt(0) ?? 0); const next = column + characterWidth; if (next <= start) { column = next; continue; } if (column >= start + width || next > start + width) break; output += character; column = next; } const outputWidth = visibleColumns(output); return output + " ".repeat(Math.max(0, width - outputWidth)); } export function markerColumn(lines: string[], marker: string): { row: number; column: number } | undefined { for (const [row, line] of lines.entries()) { const index = line.indexOf(marker); if (index >= 0) return { row, column: visibleColumns(line.slice(0, index)) }; } return undefined; } export function cropViewport( lines: string[], width: number, height: number, offsetX: number, offsetY: number, ): string[] { const selected = lines.slice(Math.max(0, offsetY), Math.max(0, offsetY) + Math.max(0, height)); return selected.map((line) => sliceColumns(line, Math.max(0, offsetX), Math.max(0, width))); }