fix: Shift-H/L also exit the editor in vim normal mode
The previous version only handled Shift-H/L when focus was outside the editor; inside vim normal mode they were viewport top/bottom and required Esc+Tab to escape. Now Vim.mapCommand overrides H/L in normal-mode context to call the same moveFocus(±1) helper, so the shortcut works uniformly regardless of focus position.
This commit is contained in:
@@ -224,7 +224,7 @@
|
||||
import { EditorView, keymap, placeholder } from "https://esm.sh/@codemirror/view@6";
|
||||
import { EditorState } from "https://esm.sh/@codemirror/state@6";
|
||||
import { history, defaultKeymap, historyKeymap } from "https://esm.sh/@codemirror/commands@6";
|
||||
import { vim } from "https://esm.sh/@replit/codemirror-vim@6";
|
||||
import { vim, Vim } from "https://esm.sh/@replit/codemirror-vim@6";
|
||||
|
||||
const LANGUAGES = [
|
||||
"English", "Portuguese (Brazil)", "Portuguese (Portugal)", "Spanish",
|
||||
@@ -478,9 +478,11 @@
|
||||
|
||||
goBtn.addEventListener("click", translate);
|
||||
|
||||
// Shift-H / Shift-L navigate focus across controls (vim-style Shift-Tab /
|
||||
// Tab). Skipped when focus is inside the CodeMirror editor — there H/L
|
||||
// are vim's viewport motions and must reach the editor untouched.
|
||||
// Shift-H / Shift-L cycle focus across controls (vim-style Shift-Tab /
|
||||
// Tab). Two paths kept in sync:
|
||||
// 1. Document keydown listener — fires when focus is on a select / button
|
||||
// 2. Vim normal-mode mapping (below) — fires when focus is in the editor
|
||||
// Together they make Shift-H/L work everywhere, including escaping the editor.
|
||||
const focusables = [
|
||||
{ el: sourceSel, focus: () => sourceSel.focus() },
|
||||
{ el: targetSel, focus: () => targetSel.focus() },
|
||||
@@ -488,23 +490,37 @@
|
||||
{ el: editor.contentDOM, focus: () => editor.focus() },
|
||||
{ el: goBtn, focus: () => goBtn.focus() },
|
||||
];
|
||||
function moveFocus(delta) {
|
||||
const ae = document.activeElement;
|
||||
let idx = focusables.findIndex(f => f.el === ae || f.el.contains?.(ae));
|
||||
if (idx === -1) {
|
||||
focusables[delta > 0 ? 0 : focusables.length - 1].focus();
|
||||
return;
|
||||
}
|
||||
const next = focusables[(idx + delta + focusables.length) % focusables.length];
|
||||
next.focus();
|
||||
}
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (!e.shiftKey) return;
|
||||
const k = e.key.toLowerCase();
|
||||
if (k !== "h" && k !== "l") return;
|
||||
if (editor.dom.contains(document.activeElement)) return; // vim owns H/L in the editor
|
||||
// Inside the editor, vim's mapCommand handles H/L (see below). Letting
|
||||
// the event bubble here would double-jump.
|
||||
if (editor.dom.contains(document.activeElement)) return;
|
||||
e.preventDefault();
|
||||
const ae = document.activeElement;
|
||||
let idx = focusables.findIndex(f => f.el === ae);
|
||||
if (idx === -1) {
|
||||
focusables[k === "l" ? 0 : focusables.length - 1].focus();
|
||||
return;
|
||||
}
|
||||
const delta = k === "l" ? 1 : -1;
|
||||
const next = focusables[(idx + delta + focusables.length) % focusables.length];
|
||||
next.focus();
|
||||
moveFocus(k === "l" ? 1 : -1);
|
||||
});
|
||||
|
||||
// Override vim's H/L (normally "viewport top/bottom") so they exit the
|
||||
// editor to the previous/next control, matching the same 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" });
|
||||
}
|
||||
|
||||
fillLangs();
|
||||
loadModels().then(() => refreshLoaded(true));
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user