Skip to content

Commit 7dae73b

Browse files
[3.13] gh-97747: Improvements to WASM browser REPL. (GH-97665) (GH-119828)
(cherry picked from commit 010aaa3) Co-authored-by: Katie Bell <katie@katharos.id.au>
1 parent 8470593 commit 7dae73b

File tree

2 files changed

+69
-8
lines changed

2 files changed

+69
-8
lines changed

Tools/wasm/python.html

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@
3535
<script src="https://unpkg.com/xterm@4.18.0/lib/xterm.js" crossorigin integrity="sha384-yYdNmem1ioP5Onm7RpXutin5A8TimLheLNQ6tnMi01/ZpxXdAwIm2t4fJMx1Djs+"/></script>
3636
<script type="module">
3737
class WorkerManager {
38-
constructor(workerURL, standardIO, readyCallBack) {
38+
constructor(workerURL, standardIO, readyCallBack, finishedCallback) {
3939
this.workerURL = workerURL
4040
this.worker = null
4141
this.standardIO = standardIO
4242
this.readyCallBack = readyCallBack
43+
this.finishedCallback = finishedCallback
4344

4445
this.initialiseWorker()
4546
}
@@ -59,6 +60,15 @@
5960
})
6061
}
6162

63+
reset() {
64+
if (this.worker) {
65+
this.worker.terminate()
66+
this.worker = null
67+
}
68+
this.standardIO.message('Worker process terminated.')
69+
this.initialiseWorker()
70+
}
71+
6272
handleStdinData(inputValue) {
6373
if (this.stdinbuffer && this.stdinbufferInt) {
6474
let startingIndex = 1
@@ -92,7 +102,8 @@
92102
this.handleStdinData(inputValue)
93103
})
94104
} else if (type === 'finished') {
95-
this.standardIO.stderr(`Exited with status: ${event.data.returnCode}\r\n`)
105+
this.standardIO.message(`Exited with status: ${event.data.returnCode}`)
106+
this.finishedCallback()
96107
}
97108
}
98109
}
@@ -168,9 +179,14 @@
168179
break;
169180
case "\x7F": // BACKSPACE
170181
case "\x08": // CTRL+H
171-
case "\x04": // CTRL+D
172182
this.handleCursorErase(true);
173183
break;
184+
case "\x04": // CTRL+D
185+
// Send empty input
186+
if (this.input === '') {
187+
this.resolveInput('')
188+
this.activeInput = false;
189+
}
174190
}
175191
} else {
176192
this.handleCursorInsert(data);
@@ -265,9 +281,13 @@
265281
}
266282
}
267283

284+
const runButton = document.getElementById('run')
268285
const replButton = document.getElementById('repl')
286+
const stopButton = document.getElementById('stop')
269287
const clearButton = document.getElementById('clear')
270288

289+
const codeBox = document.getElementById('codebox')
290+
271291
window.onload = () => {
272292
const terminal = new WasmTerminal()
273293
terminal.open(document.getElementById('terminal'))
@@ -277,35 +297,72 @@
277297
stderr: (charCode) => { terminal.print(charCode) },
278298
stdin: async () => {
279299
return await terminal.prompt()
300+
},
301+
message: (text) => { terminal.writeLine(`\r\n${text}\r\n`) },
302+
}
303+
304+
const programRunning = (isRunning) => {
305+
if (isRunning) {
306+
replButton.setAttribute('disabled', true)
307+
runButton.setAttribute('disabled', true)
308+
stopButton.removeAttribute('disabled')
309+
} else {
310+
replButton.removeAttribute('disabled')
311+
runButton.removeAttribute('disabled')
312+
stopButton.setAttribute('disabled', true)
280313
}
281314
}
282315

316+
runButton.addEventListener('click', (e) => {
317+
terminal.clear()
318+
programRunning(true)
319+
const code = codeBox.value
320+
pythonWorkerManager.run({args: ['main.py'], files: {'main.py': code}})
321+
})
322+
283323
replButton.addEventListener('click', (e) => {
324+
terminal.clear()
325+
programRunning(true)
284326
// Need to use "-i -" to force interactive mode.
285327
// Looks like isatty always returns false in emscripten
286328
pythonWorkerManager.run({args: ['-i', '-'], files: {}})
287329
})
288330

331+
stopButton.addEventListener('click', (e) => {
332+
programRunning(false)
333+
pythonWorkerManager.reset()
334+
})
335+
289336
clearButton.addEventListener('click', (e) => {
290337
terminal.clear()
291338
})
292339

293340
const readyCallback = () => {
294341
replButton.removeAttribute('disabled')
342+
runButton.removeAttribute('disabled')
295343
clearButton.removeAttribute('disabled')
296344
}
297345

298-
const pythonWorkerManager = new WorkerManager('./python.worker.js', stdio, readyCallback)
346+
const finishedCallback = () => {
347+
programRunning(false)
348+
}
349+
350+
const pythonWorkerManager = new WorkerManager('./python.worker.js', stdio, readyCallback, finishedCallback)
299351
}
300352
</script>
301353
</head>
302354
<body>
303355
<h1>Simple REPL for Python WASM</h1>
304-
<div id="terminal"></div>
356+
<textarea id="codebox" cols="108" rows="16">
357+
print('Welcome to WASM!')
358+
</textarea>
305359
<div class="button-container">
360+
<button id="run" disabled>Run</button>
306361
<button id="repl" disabled>Start REPL</button>
362+
<button id="stop" disabled>Stop</button>
307363
<button id="clear" disabled>Clear</button>
308364
</div>
365+
<div id="terminal"></div>
309366
<div id="info">
310367
The simple REPL provides a limited Python experience in the browser.
311368
<a href="https://github.com/python/cpython/blob/main/Tools/wasm/README.md">

Tools/wasm/python.worker.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@ class StdinBuffer {
1919
}
2020

2121
stdin = () => {
22-
if (this.numberOfCharacters + 1 === this.readIndex) {
22+
while (this.numberOfCharacters + 1 === this.readIndex) {
2323
if (!this.sentNull) {
2424
// Must return null once to indicate we're done for now.
2525
this.sentNull = true
2626
return null
2727
}
2828
this.sentNull = false
29+
// Prompt will reset this.readIndex to 1
2930
this.prompt()
3031
}
3132
const char = this.buffer[this.readIndex]
3233
this.readIndex += 1
33-
// How do I send an EOF??
3434
return char
3535
}
3636
}
@@ -71,7 +71,11 @@ var Module = {
7171

7272
onmessage = (event) => {
7373
if (event.data.type === 'run') {
74-
// TODO: Set up files from event.data.files
74+
if (event.data.files) {
75+
for (const [filename, contents] of Object.entries(event.data.files)) {
76+
Module.FS.writeFile(filename, contents)
77+
}
78+
}
7579
const ret = callMain(event.data.args)
7680
postMessage({
7781
type: 'finished',

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy