From 014b00a6420d34b80ebf4f6ac9aebd88fe236e5d Mon Sep 17 00:00:00 2001 From: rawan10101 Date: Thu, 6 Aug 2026 02:53:00 +0300 Subject: [PATCH] fix(wasm): fix browser input and runtime exit handling Use ev.key directly to preserve Caps Lock casing, implement clipboard paste, and prevent :q/:qa from leaving the runtime in a loop. --- src/wasm/app.js | 22 ++++++++++++++++++++-- src/wasm/nvim-worker.js | 30 +++++++++++++++++++++++++++--- src/wasm/rpc.js | 8 ++++++++ 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/wasm/app.js b/src/wasm/app.js index 57d16286b5..cf6bcc2640 100644 --- a/src/wasm/app.js +++ b/src/wasm/app.js @@ -295,7 +295,7 @@ function translateKey(ev) { }; if (named[ev.key]) return named[ev.key]; if (ev.key.length === 1) { - let char = ev.shiftKey ? ev.key : ev.key.toLowerCase(); + let char = ev.key; if (char === "<") char = ""; if (!isCtrl && !isAlt) return char; let mod = ""; @@ -368,13 +368,22 @@ function main() { } } }); - + transport.onExit((code) => { + setStatus(`Neovim exited (code ${code}). Refresh the page to start a new session.`); + gridEl.style.opacity = "0.5"; + }); nvim.on("redraw", (params) => handleRedrawEvents(uiState, gridEl, modeEl, params), ); gridEl.addEventListener("click", () => gridEl.focus()); gridEl.addEventListener("keydown", (ev) => { + const isPasteShortcut = + (ev.ctrlKey && ev.shiftKey && ev.key.toLowerCase() === "v") || // Linux/Win: Ctrl+Shift+V + (ev.metaKey && ev.key.toLowerCase() === "v"); // macOS: Cmd+V + if (isPasteShortcut) { + return; + } const keys = translateKey(ev); if (!keys) return; ev.preventDefault(); @@ -382,6 +391,15 @@ function main() { .request("nvim_input", [keys]) .catch((e) => console.error("nvim_input failed", e)); }); + gridEl.addEventListener("paste", (ev) => { + ev.preventDefault(); + const text = ev.clipboardData.getData("text/plain"); + if (!text) return; + nvim + .request("nvim_paste", [text, true, -1]) + .catch((e) => console.error("paste nvim_input failed", e)); + }); + } if (!crossOriginIsolated) { diff --git a/src/wasm/nvim-worker.js b/src/wasm/nvim-worker.js index f277bf3576..3508d69ccf 100644 --- a/src/wasm/nvim-worker.js +++ b/src/wasm/nvim-worker.js @@ -511,7 +511,7 @@ self.onmessage = async (ev) => { ); }; - const { argc, argv } = makeArgv(m, ["nvim", "--embed", "--clean"]); + const { argc, argv } = makeArgv(m, ["nvim", "--embed"]); let ret; @@ -535,14 +535,38 @@ self.onmessage = async (ev) => { ret = await nvimPromise; console.log("NVIM EXITED"); + postMessage({ type: "exited", code: ret }); + try { + moduleRef.ccall("emscripten_force_exit", null, ["number"], [ret]); + } catch (e) { + safeStatus("force_exit failed: " + e); + } const unread = unreadCount(); safeStatus( `_nvim_main RETURNED ret=${ret}, unread bytes still in buffer=${unread}`, ); } catch (e) { + console.log('CAUGHT EXIT EXCEPTION', e, 'name=', e && e.name, 'status=', e && e.status, 'constructor=', e && e.constructor && e.constructor.name); + flushAll(); - safeStatus("EXCEPTION: " + e.message + "\nSTACK:\n" + e.stack); - throw e; + if (e && e.name === 'ExitStatus') { + safeStatus(`Neovim exited cleanly, code=${e.status}`); + ret = e.status; + + // Notify app.js so it can show a session ended state instead of + // leaving the UI looking frozen. + postMessage({ type: "exited", code: ret }); + try { + moduleRef.ccall("emscripten_force_exit", null, ["number"], [ret]); + } catch (fe) { + safeStatus("force_exit failed: " + fe); + } + } + else + { + safeStatus('EXCEPTION: ' + e.message + '\nSTACK:\n' + e.stack); + throw e; + } } flushAll(); diff --git a/src/wasm/rpc.js b/src/wasm/rpc.js index ecaf47942c..ae121bbd92 100644 --- a/src/wasm/rpc.js +++ b/src/wasm/rpc.js @@ -10,6 +10,7 @@ class WorkerTransport { this._bytesHandlers = []; this._statusHandlers = []; this._readyHandlers = []; + this._exitHandlers = []; this.worker = new Worker(workerPath); this.worker.onmessage = (ev) => { @@ -48,6 +49,10 @@ class WorkerTransport { console.log("[main] worker signaled ready, RPC channel should be live"); this._readyHandlers.forEach((h) => h()); } + else if (msg.type === "exited") { + console.log("worker signaled nvim exited, code=", msg.code); + this._exitHandlers.forEach((h) => h(msg.code)); + } } _emitStatus(text) { @@ -123,6 +128,9 @@ class WorkerTransport { onReady(cb) { this._readyHandlers.push(cb); } + onExit(cb) { + this._exitHandlers.push(cb); + } persist() { this.worker.postMessage({ type: "persist" }); }