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.
This commit is contained in:
rawan10101
2026-08-06 02:53:00 +03:00
parent 5079787993
commit 014b00a642
3 changed files with 55 additions and 5 deletions

View File

@@ -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 = "<lt>";
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) {

View File

@@ -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();

View File

@@ -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" });
}