https://github.com/python/cpython/commit/7dae73b21b500e34ebb070a4d3774e09d83d6c1d
commit: 7dae73b21b500e34ebb070a4d3774e09d83d6c1d
branch: 3.13
author: Miss Islington (bot) <31488909+miss-isling...@users.noreply.github.com>
committer: ambv <luk...@langa.pl>
date: 2024-05-31T11:25:39+02:00
summary:

[3.13] gh-97747: Improvements to WASM browser REPL. (GH-97665) (GH-119828)

(cherry picked from commit 010aaa32fb93c5033a698d7213469af02d76fef3)

Co-authored-by: Katie Bell <ka...@katharos.id.au>

files:
M Tools/wasm/python.html
M Tools/wasm/python.worker.js

diff --git a/Tools/wasm/python.html b/Tools/wasm/python.html
index 17ffa0ea8bfeff..81a035a5c4cd93 100644
--- a/Tools/wasm/python.html
+++ b/Tools/wasm/python.html
@@ -35,11 +35,12 @@
     <script src="https://unpkg.com/xterm@4.18.0/lib/xterm.js"; crossorigin 
integrity="sha384-yYdNmem1ioP5Onm7RpXutin5A8TimLheLNQ6tnMi01/ZpxXdAwIm2t4fJMx1Djs+"/></script>
     <script type="module">
 class WorkerManager {
-    constructor(workerURL, standardIO, readyCallBack) {
+    constructor(workerURL, standardIO, readyCallBack, finishedCallback) {
         this.workerURL = workerURL
         this.worker = null
         this.standardIO = standardIO
         this.readyCallBack = readyCallBack
+        this.finishedCallback = finishedCallback
 
         this.initialiseWorker()
     }
@@ -59,6 +60,15 @@
         })
     }
 
+    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
@@ -92,7 +102,8 @@
                 this.handleStdinData(inputValue)
             })
         } else if (type === 'finished') {
-            this.standardIO.stderr(`Exited with status: 
${event.data.returnCode}\r\n`)
+            this.standardIO.message(`Exited with status: 
${event.data.returnCode}`)
+            this.finishedCallback()
         }
     }
 }
@@ -168,9 +179,14 @@
                     break;
                 case "\x7F": // BACKSPACE
                 case "\x08": // CTRL+H
-                case "\x04": // CTRL+D
                     this.handleCursorErase(true);
                     break;
+                case "\x04": // CTRL+D
+                    // Send empty input
+                    if (this.input === '') {
+                        this.resolveInput('')
+                        this.activeInput = false;
+                    }
             }
         } else {
             this.handleCursorInsert(data);
@@ -265,9 +281,13 @@
     }
 }
 
+const runButton = document.getElementById('run')
 const replButton = document.getElementById('repl')
+const stopButton = document.getElementById('stop')
 const clearButton = document.getElementById('clear')
 
+const codeBox = document.getElementById('codebox')
+
 window.onload = () => {
     const terminal = new WasmTerminal()
     terminal.open(document.getElementById('terminal'))
@@ -277,35 +297,72 @@
         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()
+        programRunning(true)
+        const code = codeBox.value
+        pythonWorkerManager.run({args: ['main.py'], files: {'main.py': code}})
+    })
+
     replButton.addEventListener('click', (e) => {
+        terminal.clear()
+        programRunning(true)
         // Need to use "-i -" to force interactive mode.
         // Looks like isatty always returns false in emscripten
         pythonWorkerManager.run({args: ['-i', '-'], 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 pythonWorkerManager = new WorkerManager('./python.worker.js', stdio, 
readyCallback)
+    const finishedCallback = () => {
+        programRunning(false)
+    }
+
+    const pythonWorkerManager = new WorkerManager('./python.worker.js', stdio, 
readyCallback, finishedCallback)
 }
     </script>
 </head>
 <body>
     <h1>Simple REPL for Python WASM</h1>
-    <div id="terminal"></div>
+<textarea id="codebox" cols="108" rows="16">
+print('Welcome to WASM!')
+</textarea>
     <div class="button-container">
+      <button id="run" disabled>Run</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";>
diff --git a/Tools/wasm/python.worker.js b/Tools/wasm/python.worker.js
index 1b794608fffe7b..4ce4e16fc0fa19 100644
--- a/Tools/wasm/python.worker.js
+++ b/Tools/wasm/python.worker.js
@@ -19,18 +19,18 @@ class StdinBuffer {
     }
 
     stdin = () => {
-        if (this.numberOfCharacters + 1 === this.readIndex) {
+        while (this.numberOfCharacters + 1 === this.readIndex) {
             if (!this.sentNull) {
                 // Must return null once to indicate we're done for now.
                 this.sentNull = true
                 return null
             }
             this.sentNull = false
+            // Prompt will reset this.readIndex to 1
             this.prompt()
         }
         const char = this.buffer[this.readIndex]
         this.readIndex += 1
-        // How do I send an EOF??
         return char
     }
 }
@@ -71,7 +71,11 @@ var Module = {
 
 onmessage = (event) => {
     if (event.data.type === 'run') {
-        // TODO: Set up files from event.data.files
+        if (event.data.files) {
+            for (const [filename, contents] of 
Object.entries(event.data.files)) {
+                Module.FS.writeFile(filename, contents)
+            }
+        }
         const ret = callMain(event.data.args)
         postMessage({
             type: 'finished',

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to