UNPKG

@dodona/papyros

Version:

Scratchpad for multiple programming languages in the browser.

239 lines 9.67 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { State, stateProperty } from "@dodona/lit-state"; import { BackendManager } from "../../communication/BackendManager"; import { BackendEventType } from "../../communication/BackendEvent"; import { isValidFileName, parseData } from "../../util/Util"; import { RunState } from "./Runner"; import { ServiceWorkerInputError } from "./PapyrosErrors"; export var OutputType; (function (OutputType) { OutputType["stdout"] = "stdout"; OutputType["stderr"] = "stderr"; OutputType["img"] = "img"; OutputType["turtle"] = "turtle"; })(OutputType || (OutputType = {})); export var InputMode; (function (InputMode) { InputMode["batch"] = "batch"; InputMode["interactive"] = "interactive"; })(InputMode || (InputMode = {})); export const CODE_TAB = "code"; export const OUTPUT_TAB = "output"; export const TURTLE_TAB = "turtle"; export function parseFileEntries(data, contentType) { const parsed = parseData(data, contentType); return Object.entries(parsed).map(([name, { content, binary }]) => ({ name, content, binary })); } export class InputOutput extends State { get inputBuffer() { return this._inputBuffer; } set inputBuffer(value) { this._inputBuffer = value; if (this.nextBufferedLine !== undefined && this.inputMode === InputMode.batch && this.awaitingInput) { this.provideInput(this.nextBufferedLine); } if (!this.papyros.debugger.active && this.papyros.runner.state === RunState.Ready) { this.clearInputs(); } } get nextBufferedLine() { const bufferedLines = this.inputBuffer.split("\n").slice(0, -1); if (bufferedLines.length > this.inputs.length) { return bufferedLines[this.inputs.length]; } return undefined; } constructor(papyros) { super(); this.inputs = []; this.output = []; this.hasTurtleOutput = false; this.files = []; this.activeEditorTab = CODE_TAB; this.activeOutputTab = OUTPUT_TAB; this.outputTabManuallySet = false; this.prompt = ""; this.awaitingInput = false; this.inputMode = InputMode.interactive; this._inputBuffer = ""; this.papyros = papyros; this.reset(); BackendManager.subscribe(BackendEventType.Start, () => this.reset()); BackendManager.subscribe(BackendEventType.Output, (e) => { const data = parseData(e.data, e.contentType); if (e.contentType && e.contentType.startsWith("image/")) { this.logImage(data, e.contentType); } else { this.logOutput(data); } }); BackendManager.subscribe(BackendEventType.Turtle, (e) => { const data = parseData(e.data, e.contentType); this.logTurtle(data, e.contentType); }); BackendManager.subscribe(BackendEventType.Error, (e) => { const data = parseData(e.data, e.contentType); this.logError(data); }); BackendManager.subscribe(BackendEventType.Input, (e) => { if (this.nextBufferedLine !== undefined && this.inputMode === InputMode.batch) { this.provideInput(this.nextBufferedLine); return; } this.prompt = e.data || ""; this.awaitingInput = true; }); BackendManager.subscribe(BackendEventType.End, () => { this.awaitingInput = false; // If the finished run produced no turtle output, drop the (stale) Turtle tab // selection so the tab bar hides. A manual selection is preserved. if (this.activeOutputTab === TURTLE_TAB && !this.hasTurtleOutput && !this.outputTabManuallySet) { this.activeOutputTab = OUTPUT_TAB; } }); BackendManager.subscribe(BackendEventType.Files, (e) => { this.files = parseFileEntries(e.data, e.contentType); }); } logError(error) { var _a; if (typeof error === "string") { if (error.includes("service worker for reading input")) { this.papyros.errorHandler(new ServiceWorkerInputError("Service worker for reading input was not available", { cause: error })); } } else if ((_a = error.traceback) === null || _a === void 0 ? void 0 : _a.includes("service worker for reading input")) { this.papyros.errorHandler(new ServiceWorkerInputError("Service worker for reading input was not available", { cause: error })); } this.output = [...this.output, { type: OutputType.stderr, content: error }]; } logImage(imageData, contentType = "image/png") { this.output = [...this.output, { type: OutputType.img, content: imageData, contentType }]; } logTurtle(imageData, contentType = "image/svg+xml;base64") { const isFirstSnapshot = !this.hasTurtleOutput; this.output = [...this.output, { type: OutputType.turtle, content: imageData, contentType }]; this.hasTurtleOutput = true; if (isFirstSnapshot && this.activeOutputTab === OUTPUT_TAB && !this.outputTabManuallySet) { this.activeOutputTab = TURTLE_TAB; } } selectOutputTab(tab) { this.activeOutputTab = tab; this.outputTabManuallySet = true; } logOutput(output) { // lines have been merged to limit the number of events // we split them again here, to simplify overflow detection const lines = output.split("\n"); if (lines.length > 1) { this.output = [ ...this.output, ...lines.slice(0, -1).map((line) => ({ type: OutputType.stdout, content: line + "\n" })), { type: OutputType.stdout, content: lines[lines.length - 1] }, ]; } else { this.output = [...this.output, { type: OutputType.stdout, content: output }]; } } provideInput(input) { this.inputs = [...this.inputs, input]; this.papyros.runner.provideInput(input); this.prompt = ""; this.awaitingInput = false; } removeFile(name) { if (this.activeEditorTab === name) { this.activeEditorTab = CODE_TAB; } this.files = this.files.filter((f) => f.name !== name); } addFile(name, content = "", binary = false) { if (!isValidFileName(name) || this.files.some((f) => f.name === name)) { return false; } this.files = [...this.files, { name, content, binary }]; this.activeEditorTab = name; return true; } updateFileContent(name, content, binary) { this.files = this.files.map((f) => (f.name === name ? Object.assign(Object.assign({}, f), { content, binary }) : f)); } renameFile(oldName, newName) { if (!isValidFileName(newName) || newName === oldName || !this.files.some((f) => f.name === oldName) || this.files.some((f) => f.name === newName)) { return false; } this.files = this.files.map((f) => (f.name === oldName ? Object.assign(Object.assign({}, f), { name: newName }) : f)); if (this.activeEditorTab === oldName) { this.activeEditorTab = newName; } return true; } upsertFile(name, content, binary) { if (!this.addFile(name, content, binary)) { this.updateFileContent(name, content, binary); } } clearInputs() { this.inputs = []; } reset() { this.inputs = []; this.output = []; this.hasTurtleOutput = false; this.prompt = ""; this.awaitingInput = false; this.activeEditorTab = CODE_TAB; // activeOutputTab is intentionally preserved across reruns: resetting it would make // the tab bar flicker (Turtle → Output → Turtle) as a turtle rerun progresses. It is // pruned back to OUTPUT_TAB on End when no turtle output was produced. } } __decorate([ stateProperty ], InputOutput.prototype, "inputs", void 0); __decorate([ stateProperty ], InputOutput.prototype, "output", void 0); __decorate([ stateProperty ], InputOutput.prototype, "hasTurtleOutput", void 0); __decorate([ stateProperty ], InputOutput.prototype, "files", void 0); __decorate([ stateProperty ], InputOutput.prototype, "activeEditorTab", void 0); __decorate([ stateProperty ], InputOutput.prototype, "activeOutputTab", void 0); __decorate([ stateProperty ], InputOutput.prototype, "outputTabManuallySet", void 0); __decorate([ stateProperty ], InputOutput.prototype, "prompt", void 0); __decorate([ stateProperty ], InputOutput.prototype, "awaitingInput", void 0); __decorate([ stateProperty ], InputOutput.prototype, "inputMode", void 0); __decorate([ stateProperty ], InputOutput.prototype, "_inputBuffer", void 0); __decorate([ stateProperty ], InputOutput.prototype, "inputBuffer", null); //# sourceMappingURL=InputOutput.js.map