import { Vim } from "@replit/codemirror-vim"; import { type EditorView, ViewPlugin } from "@codemirror/view"; import type { Extension } from "@codemirror/state"; export type VimHandlers = { save: () => void; close: () => void }; const handlers = new WeakMap(); /** A CodeMirror extension that binds :w / :q handlers to this view. */ export function vimHandlersExtension(getHandlers: () => VimHandlers): Extension { return ViewPlugin.define((view) => { handlers.set(view, getHandlers()); return { update() { // Keep handlers fresh in case the closure captured stale refs. handlers.set(view, getHandlers()); }, destroy() { handlers.delete(view); }, }; }); } let initialized = false; export function initVimGlobals(): void { if (initialized) return; initialized = true; type CmAdapter = { cm6?: EditorView }; const getView = (cm: CmAdapter) => cm.cm6; Vim.defineEx("write", "w", (cm: CmAdapter) => { const view = getView(cm); if (view) handlers.get(view)?.save(); }); Vim.defineEx("quit", "q", (cm: CmAdapter) => { const view = getView(cm); if (view) handlers.get(view)?.close(); }); Vim.defineEx("wq", "wq", (cm: CmAdapter) => { const view = getView(cm); if (!view) return; const h = handlers.get(view); h?.save(); h?.close(); }); Vim.defineEx("xit", "x", (cm: CmAdapter) => { const view = getView(cm); if (!view) return; const h = handlers.get(view); h?.save(); h?.close(); }); // Arrow keys are forwarded by the plugin to the editor scope handlers, // which breaks operator-pending (d) and counts (15). Remap to // hjkl so they stay inside the vim state machine. Vim.map("", "k", "normal"); Vim.map("", "j", "normal"); Vim.map("", "h", "normal"); Vim.map("", "l", "normal"); Vim.map("", "k", "visual"); Vim.map("", "j", "visual"); Vim.map("", "h", "visual"); Vim.map("", "l", "visual"); }