// 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] ?? ''); }