revert: drop clipboard yank/paste and modal dropdowns
The vim clipboard integration was unreliable across browsers
(library "+" register broken on Firefox; custom navigator.clipboard
actions worked on Chrome but hit chip prompts and Firefox config
edge cases). The modal dropdown layer added more state than it
saved keystrokes. Pulling both back out.
What remains of the vim work:
- CodeMirror + @replit/codemirror-vim in the input buffer
(i/Esc, hjkl, word/line motions, yy/p via vim's internal
register, undo, /, :)
- Shift-H / Shift-L focus navigation across controls, including
the H/L override inside the editor so it works from any focus
This commit is contained in:
@@ -179,12 +179,6 @@
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
}
|
||||
|
||||
/* Vim search-mode indicator on the language/model dropdowns
|
||||
(default is normal mode — j/k navigate; / or i enters search). */
|
||||
select.vim-search {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 1px var(--accent);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -518,111 +512,14 @@
|
||||
moveFocus(k === "l" ? 1 : -1);
|
||||
});
|
||||
|
||||
// Vim customisations: focus navigation + system-clipboard yank/paste.
|
||||
// Override H/L (normally "viewport top/bottom") to exit the editor across
|
||||
// controls, matching the Shift-H/L contract the document handler uses.
|
||||
// Custom yank/paste via navigator.clipboard — the library's "+" register
|
||||
// path is unreliable on Firefox, so we own the clipboard I/O ourselves.
|
||||
// Limitation: bare `y<motion>` (yw, y$, etc.) still goes to vim's internal
|
||||
// register; only yy / Y / visual `y` reach the system clipboard.
|
||||
// Override vim's H/L (normally "viewport top/bottom") so they exit the
|
||||
// editor to the previous/next control, matching the Shift-H/L contract
|
||||
// the document handler uses elsewhere.
|
||||
if (!isCoarsePointer) {
|
||||
Vim.defineAction("finrodFocusPrev", () => moveFocus(-1));
|
||||
Vim.defineAction("finrodFocusNext", () => moveFocus(1));
|
||||
Vim.mapCommand("H", "action", "finrodFocusPrev", {}, { context: "normal" });
|
||||
Vim.mapCommand("L", "action", "finrodFocusNext", {}, { context: "normal" });
|
||||
|
||||
Vim.defineAction("finrodYankLine", () => {
|
||||
const line = editor.state.doc.lineAt(editor.state.selection.main.head);
|
||||
const text = editor.state.doc.sliceString(line.from, line.to);
|
||||
navigator.clipboard.writeText(text)
|
||||
.catch(e => console.warn("Clipboard write failed:", e));
|
||||
});
|
||||
Vim.defineAction("finrodYankSelection", () => {
|
||||
const { from, to } = editor.state.selection.main;
|
||||
if (from === to) return;
|
||||
const text = editor.state.sliceDoc(from, to);
|
||||
navigator.clipboard.writeText(text)
|
||||
.catch(e => console.warn("Clipboard write failed:", e));
|
||||
});
|
||||
async function pasteAt(after) {
|
||||
let text;
|
||||
try {
|
||||
text = await navigator.clipboard.readText();
|
||||
} catch (e) {
|
||||
console.warn("Clipboard read failed:", e);
|
||||
return;
|
||||
}
|
||||
if (!text) return;
|
||||
const sel = editor.state.selection.main;
|
||||
const pos = after ? sel.to : sel.from;
|
||||
editor.dispatch({
|
||||
changes: { from: pos, insert: text },
|
||||
selection: { anchor: pos + text.length },
|
||||
});
|
||||
}
|
||||
Vim.defineAction("finrodPasteAfter", () => pasteAt(true));
|
||||
Vim.defineAction("finrodPasteBefore", () => pasteAt(false));
|
||||
|
||||
Vim.mapCommand("yy", "action", "finrodYankLine", {}, { context: "normal" });
|
||||
Vim.mapCommand("Y", "action", "finrodYankLine", {}, { context: "normal" });
|
||||
Vim.mapCommand("y", "action", "finrodYankSelection", {}, { context: "visual" });
|
||||
Vim.mapCommand("p", "action", "finrodPasteAfter", {}, { context: "normal" });
|
||||
Vim.mapCommand("P", "action", "finrodPasteBefore", {}, { context: "normal" });
|
||||
}
|
||||
|
||||
// Modal vim-style behavior on the language/model dropdowns.
|
||||
// default: NORMAL mode — j/k move selectedIndex (vim everywhere)
|
||||
// `/` or `i`: enter search mode — letter keys do native typeahead
|
||||
// Esc: back to normal mode
|
||||
// blur: reset to normal mode on next focus
|
||||
// Uses a document-level CAPTURE listener so events fire whether the
|
||||
// dropdown is open or collapsed (Firefox doesn't bubble keydown
|
||||
// reliably from a collapsed <select>).
|
||||
if (!isCoarsePointer) {
|
||||
const vimSelects = [sourceSel, targetSel, modelSel];
|
||||
for (const sel of vimSelects) {
|
||||
sel.dataset.mode = "normal";
|
||||
sel.addEventListener("blur", () => {
|
||||
sel.dataset.mode = "normal";
|
||||
sel.classList.remove("vim-search");
|
||||
});
|
||||
}
|
||||
const step = (sel, delta) => {
|
||||
const next = sel.selectedIndex + delta;
|
||||
if (next < 0 || next >= sel.options.length) return;
|
||||
sel.selectedIndex = next;
|
||||
sel.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
};
|
||||
document.addEventListener("keydown", (e) => {
|
||||
const sel = document.activeElement;
|
||||
if (!vimSelects.includes(sel)) return;
|
||||
// Shift-H/L: let the focus-nav handler take it; don't preventDefault.
|
||||
if (e.shiftKey && (e.key === "H" || e.key === "L")) return;
|
||||
|
||||
if (e.key === "Escape") {
|
||||
sel.dataset.mode = "normal";
|
||||
sel.classList.remove("vim-search");
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
if (sel.dataset.mode === "search") return; // let native typeahead happen
|
||||
// normal mode
|
||||
if (e.key === "j") { e.preventDefault(); e.stopPropagation(); step(sel, +1); return; }
|
||||
if (e.key === "k") { e.preventDefault(); e.stopPropagation(); step(sel, -1); return; }
|
||||
if (e.key === "/" || e.key === "i") {
|
||||
sel.dataset.mode = "search";
|
||||
sel.classList.add("vim-search");
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
// Suppress any other letter so typeahead doesn't fire silently in normal mode.
|
||||
if (e.key.length === 1) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}, true); // CAPTURE phase — needed for closed-dropdown keydowns on Firefox
|
||||
}
|
||||
|
||||
fillLangs();
|
||||
|
||||
Reference in New Issue
Block a user