|
1 | 1 | import './style.css';
|
2 |
| -import 'codemirror/lib/codemirror.css'; |
3 | 2 | import 'xterm/lib/xterm.css';
|
| 3 | +import CodeMirror from 'codemirror'; |
| 4 | +import 'codemirror/mode/python/python'; |
| 5 | +import 'codemirror/addon/comment/comment'; |
| 6 | +import 'codemirror/lib/codemirror.css'; |
| 7 | +import { Terminal } from 'xterm'; |
| 8 | +import LocalEchoController from 'local-echo'; |
| 9 | + |
| 10 | +let rp; |
4 | 11 |
|
5 |
| -// A dependency graph that contains any wasm must all be imported |
6 |
| -// asynchronously. This `index.js` file does the single async import, so |
7 |
| -// that no one else needs to worry about it again. |
8 |
| -import('./main.js').catch(e => { |
9 |
| - console.error('Error importing `main.js`:', e); |
10 |
| - document.getElementById('error').textContent = e; |
| 12 | +// A dependency graph that contains any wasm must be imported asynchronously. |
| 13 | +import('rustpython') |
| 14 | + .then(rustpy => { |
| 15 | + rp = rustpy; |
| 16 | + // so people can play around with it |
| 17 | + window.rp = rustpy; |
| 18 | + onReady(); |
| 19 | + }) |
| 20 | + .catch(e => { |
| 21 | + console.error('Error importing `rustpython`:', e); |
| 22 | + document.getElementById('error').textContent = e; |
| 23 | + }); |
| 24 | + |
| 25 | +const editor = CodeMirror.fromTextArea(document.getElementById('code'), { |
| 26 | + extraKeys: { |
| 27 | + 'Ctrl-Enter': runCodeFromTextarea, |
| 28 | + 'Cmd-Enter': runCodeFromTextarea, |
| 29 | + 'Shift-Tab': 'indentLess', |
| 30 | + 'Ctrl-/': 'toggleComment', |
| 31 | + 'Cmd-/': 'toggleComment', |
| 32 | + Tab: editor => { |
| 33 | + var spaces = Array(editor.getOption('indentUnit') + 1).join(' '); |
| 34 | + editor.replaceSelection(spaces); |
| 35 | + } |
| 36 | + }, |
| 37 | + lineNumbers: true, |
| 38 | + mode: 'text/x-python', |
| 39 | + indentUnit: 4, |
| 40 | + autofocus: true |
11 | 41 | });
|
| 42 | + |
| 43 | +const consoleElement = document.getElementById('console'); |
| 44 | +const errorElement = document.getElementById('error'); |
| 45 | + |
| 46 | +function runCodeFromTextarea() { |
| 47 | + // Clean the console and errors |
| 48 | + consoleElement.value = ''; |
| 49 | + errorElement.textContent = ''; |
| 50 | + |
| 51 | + const code = editor.getValue(); |
| 52 | + try { |
| 53 | + rp.pyExec(code, { |
| 54 | + stdout: output => { |
| 55 | + const shouldScroll = |
| 56 | + consoleElement.scrollHeight - consoleElement.scrollTop === |
| 57 | + consoleElement.clientHeight; |
| 58 | + consoleElement.value += output; |
| 59 | + if (shouldScroll) { |
| 60 | + consoleElement.scrollTop = consoleElement.scrollHeight; |
| 61 | + } |
| 62 | + } |
| 63 | + }); |
| 64 | + } catch (err) { |
| 65 | + if (err instanceof WebAssembly.RuntimeError) { |
| 66 | + err = window.__RUSTPYTHON_ERROR || err; |
| 67 | + } |
| 68 | + errorElement.textContent = err; |
| 69 | + console.error(err); |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +const snippets = document.getElementById('snippets'); |
| 74 | + |
| 75 | +function updateSnippet() { |
| 76 | + const selected = snippets.value; |
| 77 | + |
| 78 | + // the require here creates a webpack context; it's fine to use it |
| 79 | + // dynamically. |
| 80 | + // https://webpack.js.org/guides/dependency-management/ |
| 81 | + const snippet = require(`raw-loader!../snippets/${selected}.py`); |
| 82 | + |
| 83 | + editor.setValue(snippet); |
| 84 | + runCodeFromTextarea(); |
| 85 | +} |
| 86 | + |
| 87 | +const term = new Terminal(); |
| 88 | +term.open(document.getElementById('terminal')); |
| 89 | + |
| 90 | +const localEcho = new LocalEchoController(term); |
| 91 | + |
| 92 | +let terminalVM; |
| 93 | + |
| 94 | +function getPrompt(name) { |
| 95 | + terminalVM.exec(` |
| 96 | +try: |
| 97 | + import sys as __sys |
| 98 | + __prompt = __sys.${name} |
| 99 | +except: |
| 100 | + __prompt = '' |
| 101 | +finally: |
| 102 | + del __sys |
| 103 | +`); |
| 104 | + return String(terminalVM.eval('__prompt')); |
| 105 | +} |
| 106 | + |
| 107 | +async function readPrompts() { |
| 108 | + let continuing = false; |
| 109 | + |
| 110 | + while (true) { |
| 111 | + const ps1 = getPrompt('ps1'); |
| 112 | + const ps2 = getPrompt('ps2'); |
| 113 | + let input; |
| 114 | + if (continuing) { |
| 115 | + const prom = localEcho.read(ps2, ps2); |
| 116 | + localEcho._activePrompt.prompt = ps1; |
| 117 | + localEcho._input = localEcho.history.entries.pop() + '\n'; |
| 118 | + localEcho._cursor = localEcho._input.length; |
| 119 | + localEcho._active = true; |
| 120 | + input = await prom; |
| 121 | + if (!input.endsWith('\n')) continue; |
| 122 | + } else { |
| 123 | + input = await localEcho.read(ps1, ps2); |
| 124 | + } |
| 125 | + try { |
| 126 | + terminalVM.execSingle(input); |
| 127 | + } catch (err) { |
| 128 | + if (err instanceof SyntaxError && err.message.includes('EOF')) { |
| 129 | + continuing = true; |
| 130 | + continue; |
| 131 | + } else if (err instanceof WebAssembly.RuntimeError) { |
| 132 | + err = window.__RUSTPYTHON_ERROR || err; |
| 133 | + } |
| 134 | + localEcho.println(err); |
| 135 | + } |
| 136 | + continuing = false; |
| 137 | + } |
| 138 | +} |
| 139 | + |
| 140 | +function onReady() { |
| 141 | + snippets.addEventListener('change', updateSnippet); |
| 142 | + document |
| 143 | + .getElementById('run-btn') |
| 144 | + .addEventListener('click', runCodeFromTextarea); |
| 145 | + // Run once for demo |
| 146 | + runCodeFromTextarea(); |
| 147 | + |
| 148 | + terminalVM = rp.vmStore.init('term_vm'); |
| 149 | + terminalVM.setStdout(data => localEcho.println(data)); |
| 150 | + readPrompts().catch(err => console.error(err)); |
| 151 | +} |
0 commit comments