|
35 | 35 | <script src=" https://unpkg.com/[email protected]/lib/xterm.js" crossorigin integrity=" sha384-yYdNmem1ioP5Onm7RpXutin5A8TimLheLNQ6tnMi01/ZpxXdAwIm2t4fJMx1Djs+" /></script > |
36 | 36 | <script type="module"> |
37 | 37 | class WorkerManager { |
38 | | - constructor(workerURL, standardIO, readyCallBack) { |
| 38 | + constructor(workerURL, standardIO, readyCallBack, finishedCallback) { |
39 | 39 | this.workerURL = workerURL |
40 | 40 | this.worker = null |
41 | 41 | this.standardIO = standardIO |
42 | 42 | this.readyCallBack = readyCallBack |
| 43 | + this.finishedCallback = finishedCallback |
43 | 44 |
|
44 | 45 | this.initialiseWorker() |
45 | 46 | } |
|
59 | 60 | }) |
60 | 61 | } |
61 | 62 |
|
| 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 | + |
62 | 72 | handleStdinData(inputValue) { |
63 | 73 | if (this.stdinbuffer && this.stdinbufferInt) { |
64 | 74 | let startingIndex = 1 |
|
92 | 102 | this.handleStdinData(inputValue) |
93 | 103 | }) |
94 | 104 | } 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() |
96 | 107 | } |
97 | 108 | } |
98 | 109 | } |
|
168 | 179 | break; |
169 | 180 | case "\x7F": // BACKSPACE |
170 | 181 | case "\x08": // CTRL+H |
171 | | - case "\x04": // CTRL+D |
172 | 182 | this.handleCursorErase(true); |
173 | 183 | break; |
| 184 | + case "\x04": // CTRL+D |
| 185 | + // Send empty input |
| 186 | + if (this.input === '') { |
| 187 | + this.resolveInput('') |
| 188 | + this.activeInput = false; |
| 189 | + } |
174 | 190 | } |
175 | 191 | } else { |
176 | 192 | this.handleCursorInsert(data); |
|
265 | 281 | } |
266 | 282 | } |
267 | 283 |
|
| 284 | +const runButton = document.getElementById('run') |
268 | 285 | const replButton = document.getElementById('repl') |
| 286 | +const stopButton = document.getElementById('stop') |
269 | 287 | const clearButton = document.getElementById('clear') |
270 | 288 |
|
| 289 | +const codeBox = document.getElementById('codebox') |
| 290 | + |
271 | 291 | window.onload = () => { |
272 | 292 | const terminal = new WasmTerminal() |
273 | 293 | terminal.open(document.getElementById('terminal')) |
|
277 | 297 | stderr: (charCode) => { terminal.print(charCode) }, |
278 | 298 | stdin: async () => { |
279 | 299 | 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) |
280 | 313 | } |
281 | 314 | } |
282 | 315 |
|
| 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 | + |
283 | 323 | replButton.addEventListener('click', (e) => { |
| 324 | + terminal.clear() |
| 325 | + programRunning(true) |
284 | 326 | // Need to use "-i -" to force interactive mode. |
285 | 327 | // Looks like isatty always returns false in emscripten |
286 | 328 | pythonWorkerManager.run({args: ['-i', '-'], files: {}}) |
287 | 329 | }) |
288 | 330 |
|
| 331 | + stopButton.addEventListener('click', (e) => { |
| 332 | + programRunning(false) |
| 333 | + pythonWorkerManager.reset() |
| 334 | + }) |
| 335 | + |
289 | 336 | clearButton.addEventListener('click', (e) => { |
290 | 337 | terminal.clear() |
291 | 338 | }) |
292 | 339 |
|
293 | 340 | const readyCallback = () => { |
294 | 341 | replButton.removeAttribute('disabled') |
| 342 | + runButton.removeAttribute('disabled') |
295 | 343 | clearButton.removeAttribute('disabled') |
296 | 344 | } |
297 | 345 |
|
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) |
299 | 351 | } |
300 | 352 | </script> |
301 | 353 | </head> |
302 | 354 | <body> |
303 | 355 | <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> |
305 | 359 | <div class="button-container"> |
| 360 | + <button id="run" disabled>Run</button> |
306 | 361 | <button id="repl" disabled>Start REPL</button> |
| 362 | + <button id="stop" disabled>Stop</button> |
307 | 363 | <button id="clear" disabled>Clear</button> |
308 | 364 | </div> |
| 365 | + <div id="terminal"></div> |
309 | 366 | <div id="info"> |
310 | 367 | The simple REPL provides a limited Python experience in the browser. |
311 | 368 | <a href="https://github.com/python/cpython/blob/main/Tools/wasm/README.md"> |
|
0 commit comments