A new oracle, TerminalWindowRun, types into Java's own command line window: the window writes each line after its prompt, takes a blank line, cls, reset and exit itself, keeps the lines for the arrow keys, and keeps its text, history and working directory over a stop and a start. The room now holds a command line window per computer for everyone (TerminalWindow), kept over stops; the protocol carries its lines and the one way to read them (terminalScreen); the core goes on in the working directory of the run before (continueIn). The desktop opens programs in Java's frame, and the command line works with keyboard and finger: grey on black, the prompt hidden while a command runs, history, chips for the commands, Ctrl+C. The room's failure is now tested in the browser. Fixed: the room handed the engine the wall clock with fractions of a millisecond, and a ping hung after its first timeout. The engine and the room count whole milliseconds, as Java's clock does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
3.1 KiB
TypeScript
90 lines
3.1 KiB
TypeScript
// 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 {
|
|
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 });
|
|
}
|
|
}
|