// A computer's command line window in the room (plan ยง7.3): one for everyone // who opens it, as Java has one per computer. It writes the program's lines // into the window's lines after the window's own (the start screen, each // entered line after its prompt, a blank line, cls, exit), in the order they // happened. Like Java's window it outlives a stop and a start: its text, its // history and the working directory stay (TerminalWindowRun); each start // brings a new program, which goes on where the last one was. import type { Terminal } from '@mdg-filius/core'; import type { TerminalLine } from '@mdg-filius/protocol'; /** StringUtils.isBlank: nothing but whitespace. */ const blank = (line: string) => line.trim() === ''; export class TerminalWindow { private readonly linesValue: TerminalLine[] = []; private terminal!: Terminal; /** How many of the program's lines are in the window. */ private seen = 0; private busy = false; private prompt: string | undefined; private directory = '/'; /** A window on the program of the first start. */ constructor(terminal: Terminal) { this.attach(terminal); } get lines(): readonly TerminalLine[] { return this.linesValue; } /** The program of a start: the first time the window shows its start screen, later it goes on in the working directory it had. */ attach(terminal: Terminal): void { if (this.terminal === terminal) return; this.terminal = terminal; this.seen = 0; terminal.continueIn(this.directory); if (this.linesValue.length === 0) this.linesValue.push({ kind: 'text', text: terminal.startScreen }); if (this.busy) { // A command cut off by the stop has ended. this.busy = false; this.linesValue.push({ kind: 'busy', busy: false }); } this.syncPrompt(); } /** Takes the program's new lines into the window. */ pull(): void { const terminal = this.terminal; for (const entry of terminal.log.all.slice(this.seen)) { if (entry.kind === 'text') this.linesValue.push({ kind: 'text', text: entry.text }); else { this.busy = entry.busy; this.linesValue.push({ kind: 'busy', busy: entry.busy }); } } this.seen = terminal.log.all.length; this.directory = terminal.directory; if (!this.busy) this.syncPrompt(); } /** A line entered in the window. */ enter(line: string): void { const terminal = this.terminal; this.pull(); if (blank(line)) { this.linesValue.push({ kind: 'text', text: ' \n' }); return; } const prompt = this.prompt ?? terminal.prompt; const action = terminal.enter(line); this.linesValue.push({ kind: 'input', prompt, line, command: action === undefined }); if (action === 'exit') this.linesValue.push({ kind: 'exit' }); if (action === 'clear') this.linesValue.push({ kind: 'clear' }, { kind: 'text', text: terminal.startScreen }); this.pull(); } /** Ctrl+C in the window. */ interrupt(): void { this.terminal.interrupt(); } private syncPrompt(): void { const prompt = this.terminal.prompt; if (prompt === this.prompt) return; this.prompt = prompt; this.linesValue.push({ kind: 'prompt', prompt }); } }