diff --git a/CHANGELOG.md b/CHANGELOG.md index 99862f0..0027d94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,8 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ### Added -- Vim bindings in the input via CodeMirror 6 + `@replit/codemirror-vim` (loaded from esm.sh, no build step). Supports `i`/`Esc`, `hjkl`, word/line motions, `dd`/`yy`/`p`, undo/redo, `/` search, `:` command line. Auto-disabled on touch devices (detected via `(pointer: coarse)`) where modal editing fights the virtual keyboard. +- Vim bindings in the input via CodeMirror 6 + `@replit/codemirror-vim` (loaded from esm.sh, no build step). Supports `i`/`Esc`, `hjkl`, word/line motions, `dd`/`yy`/`p` (vim's internal register), undo/redo, `/` search, `:` command line. Auto-disabled on touch devices (detected via `(pointer: coarse)`) where modal editing fights the virtual keyboard. - `Shift-H` / `Shift-L` cycle focus across the page controls (source → target → model → editor → button) like `Shift-Tab` / `Tab`. Also overridden in vim normal mode (replacing vim's viewport-top/bottom default) so the same shortcut works to leave the editor. -- `yy` / `Y` and visual-mode `y` in the editor copy to the system clipboard via custom `navigator.clipboard.writeText()` actions (the library's `+` register is broken on Firefox). Bare `y` (e.g. `yw`, `y$`) still uses vim's internal register — that path would need a deeper hook into the library's register system. -- `p` / `P` in vim normal mode paste from the system clipboard at / before the cursor via `navigator.clipboard.readText()`. Browsers may show a one-time permission prompt or chip; granting clipboard-read for the origin makes future calls silent. On Firefox, ensure `dom.events.asyncClipboard.readText` is `true` (default in recent versions) or the read will be blocked. -- Vim-style modal navigation on the From / To / Model dropdowns (default NORMAL): - - `j` / `k` move selectedIndex down / up (works whether the dropdown is open or collapsed; uses document-level capture-phase listener so Firefox plays along) - - `/` or `i` enters search mode — letter keys then do native typeahead; accent-coloured border indicates it - - `Esc` returns to normal mode; blur also resets so the next focus starts in normal - On page load, the model dropdown defaults to whichever model Ollama already has hot — skips the 5-15 s disk-reload pain on the first translation. After load it follows the user's manual selection. ### Changed diff --git a/static/index.html b/static/index.html index 2259bdc..53bbfd1 100644 --- a/static/index.html +++ b/static/index.html @@ -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); - } @@ -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` (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