feat(editor): F4.6b the command line on a computer's desktop

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>
This commit is contained in:
2026-10-03 14:22:49 +00:00
co-authored by Claude Opus 5.5
parent dfd5ec03ce
commit ac48e969f4
23 changed files with 1255 additions and 47 deletions
+82
View File
@@ -0,0 +1,82 @@
// A command line's window as the room keeps it and every client shows it
// (plan §7.3): its lines, in order, and how a client reads them, after
// GUIApplicationTerminalWindow (measured with tools/java-oracle's
// TerminalWindowRun). The window, not the program, puts the start screen
// up, writes each entered line after its prompt, takes a blank line, cls,
// reset and exit itself, hides the prompt while a command runs and keeps the
// lines entered for the arrow keys.
/** A line of a command line's window. */
export type TerminalLine =
/** What the program or the window wrote: the start screen, a command's output, a blank line's " \n". */
| { readonly kind: 'text'; readonly text: string }
/** A line entered after its prompt; `command` when the program runs it (not cls, reset or exit). */
| { readonly kind: 'input'; readonly prompt: string; readonly line: string; readonly command: boolean }
/** cls or reset: the window starts again (the start screen follows). */
| { readonly kind: 'clear' }
/** A command that runs on (ping …) began or ended (Java's Boolean to the window). */
| { readonly kind: 'busy'; readonly busy: boolean }
/** The prompt after a command, when it changed (the working directory). */
| { readonly kind: 'prompt'; readonly prompt: string }
/** exit: the window closes; its text stays. */
| { readonly kind: 'exit' };
export interface TerminalScreen {
/** What the window shows, from its last cls on. */
readonly text: string;
readonly prompt: string;
/** Java hides the prompt from a command's start until its end. */
readonly promptShown: boolean;
/** Every line entered, the oldest first (blank lines not), for the arrow keys. */
readonly history: readonly string[];
}
/** The window as Java shows it after these lines. */
export function terminalScreen(lines: readonly TerminalLine[]): TerminalScreen {
let text = '';
let prompt = '';
let promptShown = true;
let busy = false;
const history: string[] = [];
for (const line of lines) {
switch (line.kind) {
case 'text':
text += line.text;
if (!busy) promptShown = true;
break;
case 'input':
text += `\n${line.prompt}${line.line}\n`;
history.push(line.line);
if (line.command) promptShown = false;
break;
case 'clear':
text = '';
break;
case 'busy':
busy = line.busy;
promptShown = !busy;
break;
case 'prompt':
prompt = line.prompt;
break;
case 'exit':
break;
}
}
return { text, prompt, promptShown, history };
}
/**
* The arrow keys in the history (Java's commandHistoryPointer): -1 is the
* empty line below the newest, 0 the newest, and so on; up goes back, down
* forward, neither beyond the ends.
*/
export function historyStep(pointer: number, step: 'up' | 'down', length: number): number {
const next = pointer + (step === 'up' ? 1 : -1);
return Math.max(-1, Math.min(length - 1, next));
}
/** The line the history shows at a pointer: empty below the newest. */
export function historyLine(history: readonly string[], pointer: number): string {
return pointer < 0 ? '' : (history[history.length - 1 - pointer] ?? '');
}