import { BufferLocation, BufferSize, Char, Color } from "./base.ts"; import { Display } from "./display.ts"; /** * ANSI terminal display */ export class AnsiTerminalDisplay implements Display { private palette_bg: readonly Uint8Array[] = []; private palette_fg: readonly Uint8Array[] = []; private width = 1; private state = { x: -1, y: -1, f: -1, b: -1 }; // current location and color constructor( private writer: Deno.Writer = Deno.stdout, private reader: Deno.Reader = Deno.stdin, ) { } async getSize(): Promise { const size = Deno.consoleSize(Deno.stdout.rid); this.width = size.columns; return { w: size.columns, h: size.rows, }; } async setupPalette(colors: readonly Color[]): Promise { // TODO handle not fully rgb compatible terminals const cr = (x: number) => Math.round(x * 255); this.palette_bg = colors.map((col) => escape(`[48;2;${cr(col.r)};${cr(col.g)};${cr(col.b)}m`) ); this.palette_fg = colors.map((col) => escape(`[38;2;${cr(col.r)};${cr(col.g)};${cr(col.b)}m`) ); return colors; } async clear(): Promise { await this.writer.write(CLEAR); } async setChar(at: BufferLocation, char: Char): Promise { let { x, y, f, b } = this.state; if (f != char.fg) { f = char.fg; const col = this.palette_fg[f]; if (col) { await this.writer.write(col); } } if (b != char.bg) { b = char.bg; const col = this.palette_bg[b]; if (col) { await this.writer.write(col); } } if (x != at.x || y != at.y) { x = at.x; y = at.y; await this.writer.write(escape(`[${y + 1};${x + 1}H`)); } await this.writer.write(new TextEncoder().encode(char.ch)); x += 1; if (x >= this.width) { x = 0; y += 1; } this.state = { x, y, f, b }; } /** * Force the display size for subsequent prints */ forceSize(size: BufferSize) { this.width = size.w; } } function escape(sequence: string): Uint8Array { return new Uint8Array([0x1B, ...new TextEncoder().encode(sequence)]); } const CLEAR = escape("[2J");