|
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