diff --git a/website/src/lib/tutorial-shell.test.ts b/website/src/lib/tutorial-shell.test.ts index 3b22ed88..0e81c050 100644 --- a/website/src/lib/tutorial-shell.test.ts +++ b/website/src/lib/tutorial-shell.test.ts @@ -45,6 +45,22 @@ describe("TutorialShell program dispatch", () => { expect(output.join("")).toContain("$ "); }); + it("forwards trailing bytes to a program launched within the same chunk", () => { + const { program, shell, startProgram } = createHarness(); + + // A single chunk (e.g. a paste) both launches the program and carries the + // first keystroke for it. The trailing `q` must reach the program, not the + // shell line editor. + shell.handleInput("ascii-splash --no-mouse\rq"); + + expect(startProgram).toHaveBeenCalledWith( + "ascii-splash", + ["--no-mouse"], + expect.any(Function), + ); + expect(program.handleInput).toHaveBeenCalledWith("q"); + }); + it("disposes the active program with the shell", () => { const { program, shell } = createHarness(); diff --git a/website/src/lib/tutorial-shell.ts b/website/src/lib/tutorial-shell.ts index 8caa1fec..cb1efa97 100644 --- a/website/src/lib/tutorial-shell.ts +++ b/website/src/lib/tutorial-shell.ts @@ -101,6 +101,14 @@ export class TutorialShell { this.lineBuffer = ''; this.historyIndex = null; this.historyDraft = ''; + // `processCommand` may have launched an interactive program. Any bytes + // left in this chunk (e.g. a paste of `cmd\rinput`) belong to that + // program, not the shell line editor — forward them and stop parsing. + if (this.activeProgram) { + const rest = data.slice(index + 1); + if (rest) this.activeProgram.handleInput(rest); + return; + } } else if (ch === '\x7f' || ch === '\b') { if (this.lineBuffer.length > 0) { this.lineBuffer = this.lineBuffer.slice(0, -1);