[Python-checkins] gh-84461: Improve WebAssembly in-browser demo (GH-91879)

miss-islington webhook-mailer at python.org
Fri Jul 1 06:16:40 EDT 2022


https://github.com/python/cpython/commit/ca58ca8641267c67dc6a7e67ee4009906b350bbb
commit: ca58ca8641267c67dc6a7e67ee4009906b350bbb
branch: 3.11
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: miss-islington <31488909+miss-islington at users.noreply.github.com>
date: 2022-07-01T03:16:25-07:00
summary:

gh-84461: Improve WebAssembly in-browser demo (GH-91879)


* Buffer standard input line-by-line

* Add non-root .editorconfig for JS & HTML indent

* Add support for clearing REPL with CTRL+L

* Support unicode in stdout and stderr

* Remove \r\n normalization

* Note that local .editorconfig file extends root

* Only normalize lone \r characters (convert to \n)

* Skip non-printable characters in buffered input

* Fix Safari bug (regex lookbehind not supported)

Co-authored-by: Christian Heimes <christian at python.org>
(cherry picked from commit a8e333d79aa639417e496181bcbad2cb801a7a56)

Co-authored-by: Trey Hunner <trey at treyhunner.com>

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

diff --git a/Tools/wasm/.editorconfig b/Tools/wasm/.editorconfig
new file mode 100644
index 0000000000000..da1aa6acaccc7
--- /dev/null
+++ b/Tools/wasm/.editorconfig
@@ -0,0 +1,7 @@
+root = false  # This extends the root .editorconfig
+
+[*.{html,js}]
+trim_trailing_whitespace = true
+insert_final_newline = true
+indent_style = space
+indent_size = 4
diff --git a/Tools/wasm/python.html b/Tools/wasm/python.html
index c8d17488b2e70..41cf5fcf6b6df 100644
--- a/Tools/wasm/python.html
+++ b/Tools/wasm/python.html
@@ -100,6 +100,7 @@
 class WasmTerminal {
 
     constructor() {
+        this.inputBuffer = new BufferQueue();
         this.input = ''
         this.resolveInput = null
         this.activeInput = false
@@ -123,28 +124,47 @@
         this.xterm.open(container);
     }
 
-    handleReadComplete(lastChar) {
-        this.resolveInput(this.input + lastChar)
-        this.activeInput = false
-    }
-
     handleTermData = (data) => {
-        if (!this.activeInput) {
-            return
-        }
         const ord = data.charCodeAt(0);
-        let ofs;
+        data = data.replace(/\r(?!\n)/g, "\n")  // Convert lone CRs to LF
 
+        // Handle pasted data
+        if (data.length > 1 && data.includes("\n")) {
+            let alreadyWrittenChars = 0;
+            // If line already had data on it, merge pasted data with it
+            if (this.input != '') {
+                this.inputBuffer.addData(this.input);
+                alreadyWrittenChars = this.input.length;
+                this.input = '';
+            }
+            this.inputBuffer.addData(data);
+            // If input is active, write the first line
+            if (this.activeInput) {
+                let line = this.inputBuffer.nextLine();
+                this.writeLine(line.slice(alreadyWrittenChars));
+                this.resolveInput(line);
+                this.activeInput = false;
+            }
+        // When input isn't active, add to line buffer
+        } else if (!this.activeInput) {
+            // Skip non-printable characters
+            if (!(ord === 0x1b || ord == 0x7f || ord < 32)) {
+                this.inputBuffer.addData(data);
+            }
         // TODO: Handle ANSI escape sequences
-        if (ord === 0x1b) {
+        } else if (ord === 0x1b) {
         // Handle special characters
         } else if (ord < 32 || ord === 0x7f) {
             switch (data) {
-                case "\r": // ENTER
+                case "\x0c": // CTRL+L
+                    this.clear();
+                    break;
+                case "\n": // ENTER
                 case "\x0a": // CTRL+J
                 case "\x0d": // CTRL+M
-                    this.xterm.write('\r\n');
-                    this.handleReadComplete('\n');
+                    this.resolveInput(this.input + this.writeLine('\n'));
+                    this.input = '';
+                    this.activeInput = false;
                     break;
                 case "\x7F": // BACKSPACE
                 case "\x08": // CTRL+H
@@ -157,6 +177,12 @@
         }
     }
 
+    writeLine(line) {
+        this.xterm.write(line.slice(0, -1))
+        this.xterm.write('\r\n');
+        return line;
+    }
+
     handleCursorInsert(data) {
         this.input += data;
         this.xterm.write(data)
@@ -176,9 +202,19 @@
         this.activeInput = true
         // Hack to allow stdout/stderr to finish before we figure out where input starts
         setTimeout(() => {this.inputStartCursor = this.xterm.buffer.active.cursorX}, 1)
+        // If line buffer has a line ready, send it immediately
+        if (this.inputBuffer.hasLineReady()) {
+            return new Promise((resolve, reject) => {
+                resolve(this.writeLine(this.inputBuffer.nextLine()));
+                this.activeInput = false;
+            })
+        // If line buffer has an incomplete line, use it for the active line
+        } else if (this.inputBuffer.lastLineIsIncomplete()) {
+            // Hack to ensure cursor input start doesn't end up after user input
+            setTimeout(() => {this.handleCursorInsert(this.inputBuffer.nextLine())}, 1);
+        }
         return new Promise((resolve, reject) => {
             this.resolveInput = (value) => {
-                this.input = ''
                 resolve(value)
             }
         })
@@ -188,9 +224,44 @@
         this.xterm.clear();
     }
 
-    print(message) {
-        const normInput = message.replace(/[\r\n]+/g, "\n").replace(/\n/g, "\r\n");
-        this.xterm.write(normInput);
+    print(charCode) {
+        let array = [charCode];
+        if (charCode == 10) {
+            array = [13, 10];  // Replace \n with \r\n
+        }
+        this.xterm.write(new Uint8Array(array));
+    }
+}
+
+class BufferQueue {
+    constructor(xterm) {
+        this.buffer = []
+    }
+
+    isEmpty() {
+        return this.buffer.length == 0
+    }
+
+    lastLineIsIncomplete() {
+        return !this.isEmpty() && !this.buffer[this.buffer.length-1].endsWith("\n")
+    }
+
+    hasLineReady() {
+        return !this.isEmpty() && this.buffer[0].endsWith("\n")
+    }
+
+    addData(data) {
+        let lines = data.match(/.*(\n|$)/g)
+        if (this.lastLineIsIncomplete()) {
+            this.buffer[this.buffer.length-1] += lines.shift()
+        }
+        for (let line of lines) {
+            this.buffer.push(line)
+        }
+    }
+
+    nextLine() {
+        return this.buffer.shift()
     }
 }
 
@@ -202,8 +273,8 @@
     terminal.open(document.getElementById('terminal'))
 
     const stdio = {
-        stdout: (s) => { terminal.print(s) },
-        stderr: (s) => { terminal.print(s) },
+        stdout: (charCode) => { terminal.print(charCode) },
+        stderr: (charCode) => { terminal.print(charCode) },
         stdin: async () => {
             return await terminal.prompt()
         }
diff --git a/Tools/wasm/python.worker.js b/Tools/wasm/python.worker.js
index c3a8bdf7d2fc2..1b794608fffe7 100644
--- a/Tools/wasm/python.worker.js
+++ b/Tools/wasm/python.worker.js
@@ -35,15 +35,11 @@ class StdinBuffer {
     }
 }
 
-const stdoutBufSize = 128;
-const stdoutBuf = new Int32Array()
-let index = 0;
-
 const stdout = (charCode) => {
     if (charCode) {
         postMessage({
             type: 'stdout',
-            stdout: String.fromCharCode(charCode),
+            stdout: charCode,
         })
     } else {
         console.log(typeof charCode, charCode)
@@ -54,7 +50,7 @@ const stderr = (charCode) => {
     if (charCode) {
         postMessage({
             type: 'stderr',
-            stderr: String.fromCharCode(charCode),
+            stderr: charCode,
         })
     } else {
         console.log(typeof charCode, charCode)



More information about the Python-checkins mailing list