@travetto/terminal
Version:
General terminal support
139 lines (117 loc) • 4.7 kB
text/typescript
import type tty from 'node:tty';
import { Env, Util } from '@travetto/runtime';
import { TerminalWriter } from './writer.ts';
type TerminalStreamingConfig = { minDelay?: number, outputStreamToMain?: boolean };
type Coord = { x: number, y: number };
export const WAIT_TOKEN = '%WAIT%';
const STD_WAIT_STATES = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'.split('');
const lineStatus = (line: string): string => line.replace(WAIT_TOKEN, ' ');
const lineMain = (line: string): string => line.replace(WAIT_TOKEN, '').trim();
/** An basic tty wrapper */
export class Terminal {
async
let done = false;
signal.addEventListener('abort', () => done = true);
let i = 0;
while (!done) {
await this.
await Util.blockingTimeout(100);
}
}
constructor(output?: tty.WriteStream, config?: { width?: number, height?: number }) {
this.
this.
this.
this.
this.
}
get output(): tty.WriteStream { return this.
get width(): number { return this.
get height(): number { return this.
get writer(): TerminalWriter { return this.
get interactive(): boolean { return this.
/**
* Stream lines if interactive, with waiting indicator, otherwise print out
*/
async streamLines(source: AsyncIterable<string | undefined>): Promise<void> {
if (!this.
for await (const line of source) {
if (line !== undefined) {
await this.
}
}
} else {
await this.streamToBottom(Util.mapAsyncIterable(source, line => `%WAIT% ${line}`), { outputStreamToMain: true });
}
}
/**
* Allows for writing at bottom of screen with scrolling support for main content
*/
async streamToBottom(source: AsyncIterable<string | undefined>, config: TerminalStreamingConfig = {}): Promise<void> {
const writePosition = { x: 0, y: -1 };
const minDelay = config.minDelay ?? 0;
let previous: string | undefined;
let stop: AbortController | undefined;
let start = Date.now();
try {
await this.
.storePosition().scrollRange({ end: -2 }).restorePosition()
.changePosition({ y: -1 }).write('\n')
.commit();
for await (const line of source) {
// Previous line
if (previous && config.outputStreamToMain) {
await this.writer.writeLine(lineMain(previous)).commit();
}
if (line && (Date.now() - start) >= minDelay) {
start = Date.now();
stop?.abort();
this.writer.setPosition(writePosition).write(lineStatus(line)).clearLine(1).commit(true);
const idx = line.indexOf(WAIT_TOKEN);
if (idx >= 0) {
stop = new AbortController();
this.
}
}
previous = line;
}
stop?.abort();
if (previous !== undefined && config.outputStreamToMain) {
await this.writer.writeLine(lineMain(previous)).commit();
}
await this.
} finally {
await this.
}
}
/**
* Consumes a stream, of events, tied to specific list indices, and updates in place
*/
async streamList(source: AsyncIterable<{ idx: number, text: string, done?: boolean }>): Promise<void> {
if (!this.
const collected = [];
for await (const event of source) {
if (event.done) {
collected[event.idx] = event.text;
}
}
await this.
return;
}
let max = 0;
try {
await this.
for await (const { idx, text } of source) {
max = Math.max(idx, max);
await this.
}
} finally {
await this.
}
}
}