UNPKG

@teaui/core

Version:

A high-level terminal UI library for Node

100 lines 3.45 kB
import * as unicode from '@teaui/term'; /** * A headless SGRTerminal that captures all output as an ANSI string. * Uses a 2D grid internally so cursor positioning is handled correctly — * the output contains only SGR escape codes and visible characters * (no cursor positioning sequences), making it compatible with ansi-to-html. */ export class StringTerminal { cols; rows; #grid; #cursorX = 0; #cursorY = 0; #pendingSgr = ''; constructor({ cols, rows }) { this.cols = cols; this.rows = rows; this.#grid = this.#createGrid(); } #createGrid() { const grid = []; for (let y = 0; y < this.rows; y++) { const row = []; for (let x = 0; x < this.cols; x++) { row.push({ char: ' ', sgr: '' }); } grid.push(row); } return grid; } move(x, y) { this.#cursorX = x; this.#cursorY = y; } write(str) { // Parse the string: separate ANSI escape sequences from visible characters let i = 0; while (i < str.length) { if (str[i] === '\x1b' && i + 1 < str.length && str[i + 1] === '[') { // ANSI escape sequence — find the end (letter character) let j = i + 2; while (j < str.length && !isAnsiTerminator(str[j])) { j++; } if (j < str.length) { j++; // include the terminator } this.#pendingSgr += str.slice(i, j); i = j; } else { // Visible character — may be multi-code-unit (emoji, CJK) const codePoint = str.codePointAt(i); const char = String.fromCodePoint(codePoint); const width = unicode.charWidth(char); if (this.#cursorY >= 0 && this.#cursorY < this.rows && this.#cursorX >= 0 && this.#cursorX < this.cols) { this.#grid[this.#cursorY][this.#cursorX] = { char, sgr: this.#pendingSgr, }; this.#pendingSgr = ''; // Wide characters occupy 2 cells — blank out the second cell if (width === 2 && this.#cursorX + 1 < this.cols) { this.#grid[this.#cursorY][this.#cursorX + 1] = { char: '', sgr: '' }; } } this.#cursorX += Math.max(width, 1); i += char.length; } } } flush() { } get output() { const lines = []; for (let y = 0; y < this.rows; y++) { let line = ''; for (let x = 0; x < this.cols; x++) { const cell = this.#grid[y][x]; line += cell.sgr + cell.char; } lines.push(line); } return lines.join('\n') + '\x1b[0m'; } reset() { this.#grid = this.#createGrid(); this.#cursorX = 0; this.#cursorY = 0; this.#pendingSgr = ''; } } function isAnsiTerminator(ch) { const code = ch.charCodeAt(0); // ANSI sequence terminators are letters (A-Z, a-z) return (code >= 65 && code <= 90) || (code >= 97 && code <= 122); } //# sourceMappingURL=StringTerminal.js.map