mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			703 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			703 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <!doctype html>
 | |
| <html lang="en">
 | |
|     <head>
 | |
|         <meta charset="UTF-8">
 | |
|         <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | |
|         <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | |
|         <meta name="author" content="Katie Bell, Adam Hartz">
 | |
|         <meta name="description" content="Simple REPL for Python WASM">
 | |
|         <title>wasm-python terminal</title>
 | |
|         <link
 | |
|             rel="stylesheet"
 | |
|             href="https://unpkg.com/xterm@4.18.0/css/xterm.css"
 | |
|             crossorigin
 | |
|             integrity="sha384-4eEEn/eZgVHkElpKAzzPx/Kow/dTSgFk1BNe+uHdjHa+NkZJDh5Vqkq31+y7Eycd"
 | |
|         >
 | |
|         <style>
 | |
|             body {
 | |
|                 font-family: arial;
 | |
|                 max-width: 800px;
 | |
|                 margin: 0 auto;
 | |
|             }
 | |
|             #editor {
 | |
|                 padding: 5px;
 | |
|                 border: 1px solid black;
 | |
|                 width: 100%;
 | |
|                 height: 300px;
 | |
|             }
 | |
|             #info {
 | |
|                 padding-top: 20px;
 | |
|             }
 | |
|             .error {
 | |
|               border: 1px solid red;
 | |
|               background-color: #ffd9d9;
 | |
|               padding: 5px;
 | |
|               margin-top: 20px;
 | |
|             }
 | |
|             .button-container {
 | |
|                 display: flex;
 | |
|                 justify-content: end;
 | |
|                 height: 50px;
 | |
|                 align-items: center;
 | |
|                 gap: 10px;
 | |
|             }
 | |
|             button {
 | |
|                 padding: 6px 18px;
 | |
|             }
 | |
|         </style>
 | |
|         <script
 | |
|             src="https://unpkg.com/xterm@4.18.0/lib/xterm.js"
 | |
|             crossorigin
 | |
|             integrity="sha384-yYdNmem1ioP5Onm7RpXutin5A8TimLheLNQ6tnMi01/ZpxXdAwIm2t4fJMx1Djs+"
 | |
|         ></script>
 | |
|         <script
 | |
|             src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.43.1/ace.js"
 | |
|             crossorigin
 | |
|             integrity="sha512-kmA5vhcxOkZI0ReiKJMGNb8/KKbgbExIlnt6aXuPtl86AgHBEi6OHHOz2wsTazBDGZKxe7fmiE+pIuZJQks4+A=="
 | |
|         ></script>
 | |
|         <script type="module">
 | |
|             const _magic_ctrlc_string = "__WASM_REPL_CTRLC_" + (Date.now()) + "__";
 | |
|             class WorkerManager {
 | |
|                 constructor(
 | |
|                     workerURL,
 | |
|                     standardIO,
 | |
|                     readyCallBack,
 | |
|                     finishedCallback,
 | |
|                 ) {
 | |
|                     this.workerURL = workerURL;
 | |
|                     this.worker = null;
 | |
|                     this.standardIO = standardIO;
 | |
|                     this.readyCallBack = readyCallBack;
 | |
|                     this.finishedCallback = finishedCallback;
 | |
| 
 | |
|                     this.initialiseWorker();
 | |
|                 }
 | |
| 
 | |
|                 async initialiseWorker() {
 | |
|                     if (!this.worker) {
 | |
|                         this.worker = new Worker(this.workerURL, {
 | |
|                             type: "module",
 | |
|                         });
 | |
|                         this.worker.addEventListener(
 | |
|                             "message",
 | |
|                             this.handleMessageFromWorker,
 | |
|                         );
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 async run(options) {
 | |
|                     this.worker.postMessage({
 | |
|                         type: "run",
 | |
|                         args: options.args || [],
 | |
|                         files: options.files || {},
 | |
|                     });
 | |
|                 }
 | |
| 
 | |
|                 reset() {
 | |
|                     if (this.worker) {
 | |
|                         this.worker.terminate();
 | |
|                         this.worker = null;
 | |
|                     }
 | |
|                     this.standardIO.message("Worker process terminated.");
 | |
|                     this.initialiseWorker();
 | |
|                 }
 | |
| 
 | |
|                 handleStdinData(inputValue) {
 | |
|                     if (this.stdinbuffer && this.stdinbufferInt) {
 | |
|                         let startingIndex = 1;
 | |
|                         if (this.stdinbufferInt[0] > 0) {
 | |
|                             startingIndex = this.stdinbufferInt[0];
 | |
|                         }
 | |
|                         const data = new TextEncoder().encode(inputValue);
 | |
|                         data.forEach((value, index) => {
 | |
|                             this.stdinbufferInt[startingIndex + index] = value;
 | |
|                         });
 | |
| 
 | |
|                         this.stdinbufferInt[0] =
 | |
|                             startingIndex + data.length - 1;
 | |
|                         Atomics.notify(this.stdinbufferInt, 0, 1);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 handleMessageFromWorker = (event) => {
 | |
|                     const type = event.data.type;
 | |
|                     if (type === "ready") {
 | |
|                         this.readyCallBack();
 | |
|                     } else if (type === "stdout") {
 | |
|                         this.standardIO.stdout(event.data.stdout);
 | |
|                     } else if (type === "stderr") {
 | |
|                         this.standardIO.stderr(event.data.stderr);
 | |
|                     } else if (type === "stdin") {
 | |
|                         // Leave it to the terminal to decide whether to chunk it into lines
 | |
|                         // or send characters depending on the use case.
 | |
|                         this.stdinbuffer = event.data.buffer;
 | |
|                         this.stdinbufferInt = new Int32Array(this.stdinbuffer);
 | |
|                         this.standardIO.stdin().then((inputValue) => {
 | |
|                             this.handleStdinData(inputValue);
 | |
|                         });
 | |
|                     } else if (type === "finished") {
 | |
|                         this.standardIO.message(
 | |
|                             `Exited with status: ${event.data.returnCode}`,
 | |
|                         );
 | |
|                         this.finishedCallback();
 | |
|                     }
 | |
|                 };
 | |
|             }
 | |
| 
 | |
|             class WasmTerminal {
 | |
|                 constructor() {
 | |
|                     try {
 | |
|                         this.history = JSON.parse(sessionStorage.getItem('__python_wasm_repl.history'));
 | |
|                         this.historyBuffer = this.history.slice();
 | |
|                     } catch(e) {
 | |
|                         this.history = [];
 | |
|                         this.historyBuffer = [];
 | |
|                     }
 | |
|                     this.reset();
 | |
| 
 | |
|                     this.xterm = new Terminal({
 | |
|                         scrollback: 10000,
 | |
|                         fontSize: 14,
 | |
|                         theme: { background: "#1a1c1f" },
 | |
|                         cols: 100,
 | |
|                     });
 | |
| 
 | |
|                     this.xterm.onKey((keyEvent) => {
 | |
|                         // Fix for iOS Keyboard Jumping on space
 | |
|                         if (keyEvent.key === " ") {
 | |
|                             keyEvent.domEvent.preventDefault();
 | |
|                         }
 | |
|                     });
 | |
| 
 | |
|                     this.xterm.onData(this.handleTermData);
 | |
|                 }
 | |
| 
 | |
|                 reset() {
 | |
|                     this.inputBuffer = new BufferQueue();
 | |
|                     this.input = "";
 | |
|                     this.resolveInput = null;
 | |
|                     this.activeInput = false;
 | |
|                     this.inputStartCursor = null;
 | |
| 
 | |
|                     this.cursorPosition = 0;
 | |
|                     this.historyIndex = -1;
 | |
|                     this.beforeHistoryNav = "";
 | |
|                 }
 | |
| 
 | |
|                 open(container) {
 | |
|                     this.xterm.open(container);
 | |
|                 }
 | |
| 
 | |
|                 handleTermData = (data) => {
 | |
|                     const ord = data.charCodeAt(0);
 | |
|                     data = data.replace(/\r(?!\n)/g, "\n"); // Convert lone CRs to LF
 | |
| 
 | |
|                     // Handle pasted data
 | |
|                     if (data.length > 1 && data.includes("\n")) {
 | |
|                         let alreadyWrittenChars = 0;
 | |
|                         // If line already had data on it, merge pasted data with it
 | |
|                         if (this.input != "") {
 | |
|                             this.inputBuffer.addData(this.input);
 | |
|                             alreadyWrittenChars = this.input.length;
 | |
|                             this.input = "";
 | |
|                         }
 | |
|                         this.inputBuffer.addData(data);
 | |
|                         // If input is active, write the first line
 | |
|                         if (this.activeInput) {
 | |
|                             let line = this.inputBuffer.nextLine();
 | |
|                             this.writeLine(line.slice(alreadyWrittenChars));
 | |
|                             this.resolveInput(line);
 | |
|                             this.activeInput = false;
 | |
|                         }
 | |
|                         // When input isn't active, add to line buffer
 | |
|                     } else if (!this.activeInput) {
 | |
|                         // Skip non-printable characters
 | |
|                         if (!(ord === 0x1b || ord == 0x7f || ord < 32)) {
 | |
|                             this.inputBuffer.addData(data);
 | |
|                         }
 | |
|                         // TODO: Handle more escape sequences?
 | |
|                     } else if (ord === 0x1b) {
 | |
|                         // Handle special characters
 | |
|                         switch (data.slice(1)) {
 | |
|                             case "[A": // up
 | |
|                                 this.historyBack();
 | |
|                                 break;
 | |
|                             case "[B": // down
 | |
|                                 this.historyForward();
 | |
|                                 break;
 | |
|                             case "[C": // right
 | |
|                                 this.cursorRight();
 | |
|                                 break;
 | |
|                             case "[D": // left
 | |
|                                 this.cursorLeft();
 | |
|                                 break;
 | |
|                             case "[H": // home key
 | |
|                                 this.cursorHome(true);
 | |
|                                 break;
 | |
|                             case "[F": // end key
 | |
|                                 this.cursorEnd(true);
 | |
|                                 break;
 | |
|                             case "[3~": // delete key
 | |
|                                 this.deleteAtCursor();
 | |
|                                 break;
 | |
|                             default:
 | |
|                                 break;
 | |
|                         }
 | |
|                     } else if (ord < 32 || ord === 0x7f) {
 | |
|                         switch (data) {
 | |
|                             case "\x0c": // CTRL+L
 | |
|                                 this.clear();
 | |
|                                 break;
 | |
|                             case "\n": // ENTER
 | |
|                             case "\x0a": // CTRL+J
 | |
|                             case "\x0d": // CTRL+M
 | |
|                                 this.resolveInput(
 | |
|                                     this.input + this.writeLine("\n"),
 | |
|                                 );
 | |
|                                 this.input = "";
 | |
|                                 this.cursorPosition = 0;
 | |
|                                 this.activeInput = false;
 | |
|                                 break;
 | |
|                             case "\x03": // CTRL+C
 | |
|                                 this.input = "";
 | |
|                                 this.cursorPosition = 0;
 | |
|                                 this.historyIndex = -1;
 | |
|                                 this.resolveInput(_magic_ctrlc_string + "\n");
 | |
|                                 break;
 | |
|                             case "\x09": // TAB
 | |
|                                 this.handleTab();
 | |
|                                 break;
 | |
|                             case "\x7F": // BACKSPACE
 | |
|                             case "\x08": // CTRL+H
 | |
|                                 this.handleCursorErase(true);
 | |
|                                 break;
 | |
|                             case "\x04": // CTRL+D
 | |
|                                 // Send empty input
 | |
|                                 if (this.input === "") {
 | |
|                                     this.resolveInput("");
 | |
|                                     this.cursorPosition = 0;
 | |
|                                     this.activeInput = false;
 | |
|                                 }
 | |
|                         }
 | |
|                     } else {
 | |
|                         this.handleCursorInsert(data);
 | |
|                         this.updateHistory();
 | |
|                     }
 | |
|                 };
 | |
| 
 | |
|                 clearLine() {
 | |
|                     this.xterm.write("\x1b[K");
 | |
|                 }
 | |
| 
 | |
|                 writeLine(line) {
 | |
|                     this.xterm.write(line.slice(0, -1));
 | |
|                     this.xterm.write("\r\n");
 | |
|                     return line;
 | |
|                 }
 | |
| 
 | |
|                 handleCursorInsert(data) {
 | |
|                     const trailing = this.input.slice(this.cursorPosition);
 | |
|                     this.input =
 | |
|                         this.input.slice(0, this.cursorPosition) +
 | |
|                         data +
 | |
|                         trailing;
 | |
|                     this.cursorPosition += data.length;
 | |
|                     this.xterm.write(data);
 | |
|                     if (trailing.length !== 0) {
 | |
|                         this.xterm.write(trailing);
 | |
|                         this.xterm.write("\x1b[" + trailing.length + "D");
 | |
|                     }
 | |
|                     this.updateHistory();
 | |
|                 }
 | |
| 
 | |
|                 handleTab() {
 | |
|                     // handle tabs: from the current position, add spaces until
 | |
|                     // this.cursorPosition is a multiple of 4.
 | |
|                     const prefix = this.input.slice(0, this.cursorPosition);
 | |
|                     const suffix = this.input.slice(this.cursorPosition);
 | |
|                     const count = 4 - (this.cursorPosition % 4);
 | |
|                     const toAdd = " ".repeat(count);
 | |
|                     this.input = prefix + toAdd + suffix;
 | |
|                     this.cursorHome(false);
 | |
|                     this.clearLine();
 | |
|                     this.xterm.write(this.input);
 | |
|                     if (suffix) {
 | |
|                         this.xterm.write("\x1b[" + suffix.length + "D");
 | |
|                     }
 | |
|                     this.cursorPosition += count;
 | |
|                     this.updateHistory();
 | |
|                 }
 | |
| 
 | |
|                 handleCursorErase() {
 | |
|                     // Don't delete past the start of input
 | |
|                     if (
 | |
|                         this.xterm.buffer.active.cursorX <=
 | |
|                         this.inputStartCursor
 | |
|                     ) {
 | |
|                         return;
 | |
|                     }
 | |
|                     const trailing = this.input.slice(this.cursorPosition);
 | |
|                     this.input =
 | |
|                         this.input.slice(0, this.cursorPosition - 1) + trailing;
 | |
|                     this.cursorLeft();
 | |
|                     this.clearLine();
 | |
|                     if (trailing.length !== 0) {
 | |
|                         this.xterm.write(trailing);
 | |
|                         this.xterm.write("\x1b[" + trailing.length + "D");
 | |
|                     }
 | |
|                     this.updateHistory();
 | |
|                 }
 | |
| 
 | |
|                 deleteAtCursor() {
 | |
|                     if (this.cursorPosition < this.input.length) {
 | |
|                         const trailing = this.input.slice(
 | |
|                             this.cursorPosition + 1,
 | |
|                         );
 | |
|                         this.input =
 | |
|                             this.input.slice(0, this.cursorPosition) + trailing;
 | |
|                         this.clearLine();
 | |
|                         if (trailing.length !== 0) {
 | |
|                             this.xterm.write(trailing);
 | |
|                             this.xterm.write("\x1b[" + trailing.length + "D");
 | |
|                         }
 | |
|                         this.updateHistory();
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 cursorRight() {
 | |
|                     if (this.cursorPosition < this.input.length) {
 | |
|                         this.cursorPosition += 1;
 | |
|                         this.xterm.write("\x1b[C");
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 cursorLeft() {
 | |
|                     if (this.cursorPosition > 0) {
 | |
|                         this.cursorPosition -= 1;
 | |
|                         this.xterm.write("\x1b[D");
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 cursorHome(updatePosition) {
 | |
|                     if (this.cursorPosition > 0) {
 | |
|                         this.xterm.write("\x1b[" + this.cursorPosition + "D");
 | |
|                         if (updatePosition) {
 | |
|                             this.cursorPosition = 0;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 cursorEnd() {
 | |
|                     if (this.cursorPosition < this.input.length) {
 | |
|                         this.xterm.write(
 | |
|                             "\x1b[" +
 | |
|                                 (this.input.length - this.cursorPosition) +
 | |
|                                 "C",
 | |
|                         );
 | |
|                         this.cursorPosition = this.input.length;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 updateHistory() {
 | |
|                     if (this.historyIndex !== -1) {
 | |
|                         this.historyBuffer[this.historyIndex] = this.input;
 | |
|                     } else {
 | |
|                         this.beforeHistoryNav = this.input;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 historyBack() {
 | |
|                     if (this.history.length === 0) {
 | |
|                         return;
 | |
|                     } else if (this.historyIndex === -1) {
 | |
|                         // we're not currently navigating the history; store
 | |
|                         // the current command and then look at the end of our
 | |
|                         // history buffer
 | |
|                         this.beforeHistoryNav = this.input;
 | |
|                         this.historyIndex = this.history.length - 1;
 | |
|                     } else if (this.historyIndex > 0) {
 | |
|                         this.historyIndex -= 1;
 | |
|                     }
 | |
|                     this.input = this.historyBuffer[this.historyIndex];
 | |
|                     this.cursorHome(false);
 | |
|                     this.clearLine();
 | |
|                     this.xterm.write(this.input);
 | |
|                     this.cursorPosition = this.input.length;
 | |
|                 }
 | |
| 
 | |
|                 historyForward() {
 | |
|                     if (this.history.length === 0 || this.historyIndex === -1) {
 | |
|                         // we're not currently navigating the history; NOP.
 | |
|                         return;
 | |
|                     } else if (this.historyIndex < this.history.length - 1) {
 | |
|                         this.historyIndex += 1;
 | |
|                         this.input = this.historyBuffer[this.historyIndex];
 | |
|                     } else if (this.historyIndex == this.history.length - 1) {
 | |
|                         // we're coming back from the last history value; reset
 | |
|                         // the input to whatever it was when we started going
 | |
|                         // through the history
 | |
|                         this.input = this.beforeHistoryNav;
 | |
|                         this.historyIndex = -1;
 | |
|                     }
 | |
|                     this.cursorHome(false);
 | |
|                     this.clearLine();
 | |
|                     this.xterm.write(this.input);
 | |
|                     this.cursorPosition = this.input.length;
 | |
|                 }
 | |
| 
 | |
|                 prompt = async () => {
 | |
|                     this.activeInput = true;
 | |
|                     // Hack to allow stdout/stderr to finish before we figure out where input starts
 | |
|                     setTimeout(() => {
 | |
|                         this.inputStartCursor =
 | |
|                             this.xterm.buffer.active.cursorX;
 | |
|                     }, 1);
 | |
|                     // If line buffer has a line ready, send it immediately
 | |
|                     if (this.inputBuffer.hasLineReady()) {
 | |
|                         return new Promise((resolve, reject) => {
 | |
|                             resolve(
 | |
|                                 this.writeLine(this.inputBuffer.nextLine()),
 | |
|                             );
 | |
|                             this.activeInput = false;
 | |
|                         });
 | |
|                         // If line buffer has an incomplete line, use it for the active line
 | |
|                     } else if (this.inputBuffer.lastLineIsIncomplete()) {
 | |
|                         // Hack to ensure cursor input start doesn't end up after user input
 | |
|                         setTimeout(() => {
 | |
|                             this.handleCursorInsert(
 | |
|                                 this.inputBuffer.nextLine()
 | |
|                             );
 | |
|                         }, 1);
 | |
|                     }
 | |
|                     return new Promise((resolve, reject) => {
 | |
|                         this.resolveInput = (value) => {
 | |
|                             if (
 | |
|                                 value.replace(/\s/g, "").length != 0 &&
 | |
|                                 value != _magic_ctrlc_string + "\n"
 | |
|                             ) {
 | |
|                                 if (this.historyIndex !== -1) {
 | |
|                                     this.historyBuffer[this.historyIndex] =
 | |
|                                         this.history[this.historyIndex];
 | |
|                                 }
 | |
|                                 this.history.push(value.slice(0, -1));
 | |
|                                 this.historyBuffer.push(value.slice(0, -1));
 | |
|                                 this.historyIndex = -1;
 | |
|                                 this.cursorPosition = 0;
 | |
|                                 try {
 | |
|                                     sessionStorage.setItem('__python_wasm_repl.history', JSON.stringify(this.history));
 | |
|                                 } catch(e) {
 | |
|                                 }
 | |
|                             }
 | |
|                             resolve(value);
 | |
|                         };
 | |
|                     });
 | |
|                 };
 | |
| 
 | |
|                 clear() {
 | |
|                     this.xterm.clear();
 | |
|                 }
 | |
| 
 | |
|                 print(charCode) {
 | |
|                     let array = [charCode];
 | |
|                     if (charCode == 10) {
 | |
|                         array = [13, 10]; // Replace \n with \r\n
 | |
|                     }
 | |
|                     this.xterm.write(new Uint8Array(array));
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             class BufferQueue {
 | |
|                 constructor(xterm) {
 | |
|                     this.buffer = [];
 | |
|                 }
 | |
| 
 | |
|                 isEmpty() {
 | |
|                     return this.buffer.length == 0;
 | |
|                 }
 | |
| 
 | |
|                 lastLineIsIncomplete() {
 | |
|                     return (
 | |
|                         !this.isEmpty() &&
 | |
|                         !this.buffer[this.buffer.length - 1].endsWith("\n")
 | |
|                     );
 | |
|                 }
 | |
| 
 | |
|                 hasLineReady() {
 | |
|                     return !this.isEmpty() && this.buffer[0].endsWith("\n");
 | |
|                 }
 | |
| 
 | |
|                 addData(data) {
 | |
|                     let lines = data.match(/.*(\n|$)/g);
 | |
|                     if (this.lastLineIsIncomplete()) {
 | |
|                         this.buffer[this.buffer.length - 1] += lines.shift();
 | |
|                     }
 | |
|                     for (let line of lines) {
 | |
|                         this.buffer.push(line);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 nextLine() {
 | |
|                     return this.buffer.shift();
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             const runButton = document.getElementById("run");
 | |
|             const replButton = document.getElementById("repl");
 | |
|             const stopButton = document.getElementById("stop");
 | |
|             const clearButton = document.getElementById("clear");
 | |
| 
 | |
|             window.onload = () => {
 | |
|                 const terminal = new WasmTerminal();
 | |
|                 terminal.open(document.getElementById("terminal"));
 | |
| 
 | |
|                 const stdio = {
 | |
|                     stdout: (charCode) => {
 | |
|                         terminal.print(charCode);
 | |
|                     },
 | |
|                     stderr: (charCode) => {
 | |
|                         terminal.print(charCode);
 | |
|                     },
 | |
|                     stdin: async () => {
 | |
|                         return await terminal.prompt();
 | |
|                     },
 | |
|                     message: (text) => {
 | |
|                         terminal.writeLine(`\r\n${text}\r\n`);
 | |
|                     },
 | |
|                 };
 | |
| 
 | |
|                 const programRunning = (isRunning) => {
 | |
|                     if (isRunning) {
 | |
|                         replButton.setAttribute("disabled", true);
 | |
|                         runButton.setAttribute("disabled", true);
 | |
|                         stopButton.removeAttribute("disabled");
 | |
|                     } else {
 | |
|                         replButton.removeAttribute("disabled");
 | |
|                         runButton.removeAttribute("disabled");
 | |
|                         stopButton.setAttribute("disabled", true);
 | |
|                     }
 | |
|                 };
 | |
| 
 | |
|                 runButton.addEventListener("click", (e) => {
 | |
|                     terminal.clear();
 | |
|                     terminal.reset(); // reset the history
 | |
|                     programRunning(true);
 | |
|                     const code = editor.getValue();
 | |
|                     pythonWorkerManager.run({
 | |
|                         args: ["main.py"],
 | |
|                         files: { "main.py": code },
 | |
|                     });
 | |
|                 });
 | |
| 
 | |
|                 replButton.addEventListener("click", (e) => {
 | |
|                     terminal.clear();
 | |
|                     terminal.reset(); // reset the history
 | |
|                     const REPL = `
 | |
| class WASMREPLKeyboardInterrupt(KeyboardInterrupt):
 | |
|     pass
 | |
| 
 | |
| import sys
 | |
| import code
 | |
| import builtins
 | |
| 
 | |
| def _interrupt_aware_input(prompt=''):
 | |
|     line = builtins.input(prompt)
 | |
|     if line.strip() == "${_magic_ctrlc_string}":
 | |
|         raise KeyboardInterrupt()
 | |
|     return line
 | |
| 
 | |
| cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
 | |
| banner = f'Python {sys.version} on {sys.platform}\\n{cprt}'
 | |
| 
 | |
| code.interact(banner=banner, readfunc=_interrupt_aware_input, exitmsg='')
 | |
| `;
 | |
|                     programRunning(true);
 | |
|                     pythonWorkerManager.run({ args: ["-c", REPL], files: {} });
 | |
|                 });
 | |
| 
 | |
|                 stopButton.addEventListener("click", (e) => {
 | |
|                     programRunning(false);
 | |
|                     pythonWorkerManager.reset();
 | |
|                 });
 | |
| 
 | |
|                 clearButton.addEventListener("click", (e) => {
 | |
|                     terminal.clear();
 | |
|                 });
 | |
| 
 | |
|                 const readyCallback = () => {
 | |
|                     replButton.removeAttribute("disabled");
 | |
|                     runButton.removeAttribute("disabled");
 | |
|                     clearButton.removeAttribute("disabled");
 | |
|                 };
 | |
| 
 | |
|                 const finishedCallback = () => {
 | |
|                     programRunning(false);
 | |
|                     pythonWorkerManager.reset();
 | |
|                 };
 | |
| 
 | |
|                 const pythonWorkerManager = new WorkerManager(
 | |
|                     "./python.worker.mjs",
 | |
|                     stdio,
 | |
|                     readyCallback,
 | |
|                     finishedCallback,
 | |
|                 );
 | |
|             };
 | |
|             var editor;
 | |
|             document.addEventListener("DOMContentLoaded", () => {
 | |
|                 editor = ace.edit("editor");
 | |
|                 editor.session.setMode("ace/mode/python");
 | |
|             });
 | |
|         </script>
 | |
|     </head>
 | |
|     <body>
 | |
|         <div id="repldemo">
 | |
|             <h1>Simple REPL for Python WASM</h1>
 | |
|             <div id="editor">print('Welcome to WASM!')</div>
 | |
|             <div class="button-container">
 | |
|                 <button id="run" disabled>Run code</button>
 | |
|                 <button id="repl" disabled>Start REPL</button>
 | |
|                 <button id="stop" disabled>Stop</button>
 | |
|                 <button id="clear" disabled>Clear</button>
 | |
|             </div>
 | |
|             <div id="terminal"></div>
 | |
|             <div id="info">
 | |
|                 The simple REPL provides a limited Python experience in the
 | |
|                 browser.
 | |
|                 <a
 | |
|                     href="https://github.com/python/cpython/blob/main/Tools/wasm/README.md"
 | |
|                 >
 | |
|                     Tools/wasm/README.md
 | |
|                 </a>
 | |
|                 contains a list of known limitations and issues. Networking,
 | |
|                 subprocesses, and threading are not available.
 | |
|             </div>
 | |
|         </div>
 | |
|         <div id="buffererror" class="error" style="display: none">
 | |
|             <p>
 | |
|                 <code>SharedArrayBuffer</code>, which is required for this demo,
 | |
|                 is not available in your browser environment.  One common cause
 | |
|                 of this failure is loading <code>index.html</code> directly in
 | |
|                 your browser instead of using <code>server.py</code> as
 | |
|                 described in
 | |
|                 <a
 | |
|                     href="https://github.com/python/cpython/blob/main/Tools/wasm/README.md#the-web-example"
 | |
|                 >
 | |
|                     Tools/wasm/README.md
 | |
|                 </a>.
 | |
|             </p>
 | |
|             <p>
 | |
|                 For more details about security requirements for
 | |
|                 <code>SharedArrayBuffer</code>, see
 | |
|                 <a
 | |
|                     href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements"
 | |
|                   >this MDN page</a
 | |
|                 >.
 | |
|             </p>
 | |
|             <script>
 | |
|               if (typeof SharedArrayBuffer === 'undefined') {
 | |
|                   document.getElementById('repldemo').style.display = 'none';
 | |
|                   document.getElementById('buffererror').style.display = 'block';
 | |
|               }
 | |
|             </script>
 | |
|         </div>
 | |
|     </body>
 | |
| </html>
 | 
