@teaui/core
Version:
A high-level terminal UI library for Node
100 lines • 3.45 kB
JavaScript
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