fix: keymap conflicts + generated Vim keymap cheatsheet #10

Merged
rootiest merged 4 commits from fix/keymap-conflicts-cheatsheet into main 2026-10-04 06:42:33 +00:00
2 changed files with 145 additions and 49 deletions
Showing only changes of commit 8280886a2a - Show all commits

No files matched your search

+73 -25
View File
@@ -101,6 +101,7 @@ nav a { color: var(--accent); text-decoration: none; font-weight: 600; }
nav a:hover { text-decoration: underline; } nav a:hover { text-decoration: underline; }
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 9px; overflow: hidden; background: var(--panel); flex: none; } .seg { display: inline-flex; border: 1px solid var(--line); border-radius: 9px; overflow: hidden; background: var(--panel); flex: none; }
.seg button { border: 0; background: transparent; padding: 6px 12px; cursor: pointer; font-size: 0.85rem; font-weight: 600; } .seg button { border: 0; background: transparent; padding: 6px 12px; cursor: pointer; font-size: 0.85rem; font-weight: 600; }
#kblayout { justify-self: start; }
.seg button + button { border-left: 1px solid var(--line); } .seg button + button { border-left: 1px solid var(--line); }
.seg button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); } .seg button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; list-style: none; margin: 0; padding: 0; } .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; list-style: none; margin: 0; padding: 0; }
@@ -395,6 +396,7 @@ footer code { font-size: 0.8rem; }
<button type="button" data-ai="a" aria-pressed="false">a · around</button> <button type="button" data-ai="a" aria-pressed="false">a · around</button>
</div> </div>
</div> </div>
<div class="seg" id="kblayout" role="group" aria-label="Keyboard layout"></div>
<div class="legend" id="kblegend"></div> <div class="legend" id="kblegend"></div>
<div class="board"><div class="case"><div class="kb" id="kb" role="group" aria-label="Keyboard"></div></div></div> <div class="board"><div class="case"><div class="kb" id="kb" role="group" aria-label="Keyboard"></div></div></div>
<div class="keyinfo" id="keyinfo" aria-live="polite"></div> <div class="keyinfo" id="keyinfo" aria-live="polite"></div>
@@ -479,7 +481,7 @@ footer code { font-size: 0.8rem; }
<script> <script>
"use strict"; "use strict";
const DATA = {"meta":{"version":"0.12.5+v0.12.5","leader":" ","localleader":"\\","date":"2026-10-03","plugins":["blink-copilot","blink.cmp","blink.lib","catppuccin","comment-box","conform","flash","focusline.nvim","friendly-snippets","gitsigns","grug-far","gx","haunt","inc-rename","kitty-scrollback","lazydev","leap","lualine","mason","mason-lspconfig","mini.ai","mini.pairs","mini.surround","noice","nui","nvim-lspconfig","nvim-treesitter-context","nvim-web-devicons","obsidian.nvim","persistence","qalc","sidekick","snacks","snacks-tea","undotree","vim-wakatime","which-key"],"options":{"ignorecase":false,"shiftwidth":2,"relativenumber":true,"wrap":true,"undofile":true,"clipboard":"unnamedplus","timeoutlen":1000,"autowrite":true,"smartcase":false},"timeoutlen":1000},"builtins":[{"k":"<C-@>","m":["i"],"d":"Insert the text from the last Insert session and leave Insert","s":"builtin","t":"i_CTRL-@","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-A>","m":["i"],"d":"Insert the text from the last Insert session","s":"builtin","t":"i_CTRL-A","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-C>","m":["i"],"d":"Leave Insert mode without abbreviations or InsertLeave","s":"builtin","t":"i_CTRL-C","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null,"n":"Skips InsertLeave autocmds, which plugins may rely on; prefer <kbd>&lt;Esc&gt;<\/kbd>"},{"k":"<C-D>","m":["i"],"d":"Dedent the current line one 'shiftwidth'","s":"builtin","t":"i_CTRL-D","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-E>","m":["i"],"d":"Insert the character below the cursor","s":"builtin","t":"i_CTRL-E","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G><C-J>","m":["i"],"d":"Down a line, to the column where Insert started","s":"builtin","t":"i_CTRL-G_j","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G>j","m":["i"],"d":"Down a line, to the column where Insert started","s":"builtin","t":"i_CTRL-G_j","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G><Down>","m":["i"],"d":"Down a line, to the column where Insert started","s":"builtin","t":"i_CTRL-G_j","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G><C-K>","m":["i"],"d":"Up a line, to the column where Insert started","s":"builtin","t":"i_CTRL-G_k","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G>k","m":["i"],"d":"Up a line, to the column where Insert started","s":"builtin","t":"i_CTRL-G_k","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G><Up>","m":["i"],"d":"Up a line, to the column where Insert started","s":"builtin","t":"i_CTRL-G_k","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G>u","m":["i"],"d":"Start a new undoable change (undo break)","s":"builtin","t":"i_CTRL-G_u","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G>U","m":["i"],"d":"Don't start a new undo step for the next cursor movement","s":"builtin","t":"i_CTRL-G_U","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<BS>","m":["i"],"d":"Delete the character before the cursor","s":"builtin","t":"i_<BS>","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null,"ov":[{"m":"i","by":224},{"m":"i","by":245}]},{"k":"{char1}<BS>{char2}","m":["i"],"d":"enter digraph (only when 'digraph' option set)","s":"builtin","t":"i_digraph","h":"digraph.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-H>","m":["i"],"d":"Delete the character before the cursor (same as <BS>)","s":"builtin","t":"i_CTRL-H","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<Tab>","m":["i"],"d":"Insert a tab (or spaces with 'expandtab')","s":"builtin","t":"i_<Tab>","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null,"n":"In buffers where blink.cmp is attached, <kbd>&lt;Tab&gt;<\/kbd> tries in order: next snippet placeholder, Sidekick next-edit suggestion, accept Copilot ghost text, else a normal Tab (<code>lua/plugins.lua:331-340<\/code>)","ov":[{"m":"i","by":229}]},{"k":"<C-I>","m":["i"],"d":"Insert a tab (same as <Tab>)","s":"builtin","t":"i_CTRL-I","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<NL>","m":["i"],"d":"Start a new line (same as <CR>)","s":"builtin","t":"i_<NL>","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-J>","m":["i"],"d":"Start a new line (same as <CR>)","s":"builtin","t":"i_CTRL-J","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-K>{char1}{char2}","m":["i"],"dLine truncated const DATA = {"meta":{"version":"0.12.5+v0.12.5","leader":" ","localleader":"\\","date":"2026-10-04","plugins":["blink-copilot","blink.cmp","blink.lib","catppuccin","comment-box","conform","flash","focusline.nvim","friendly-snippets","gitsigns","grug-far","gx","haunt","inc-rename","kitty-scrollback","lazydev","leap","lualine","mason","mason-lspconfig","mini.ai","mini.pairs","mini.surround","noice","nui","nvim-lspconfig","nvim-treesitter-context","nvim-web-devicons","obsidian.nvim","persistence","qalc","sidekick","snacks","snacks-tea","undotree","vim-wakatime","which-key"],"options":{"clipboard":"unnamedplus","timeoutlen":1000,"autowrite":true,"undofile":true,"shiftwidth":2,"smartcase":false,"ignorecase":false,"wrap":true,"relativenumber":true},"timeoutlen":1000},"builtins":[{"k":"<C-@>","m":["i"],"d":"Insert the text from the last Insert session and leave Insert","s":"builtin","t":"i_CTRL-@","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-A>","m":["i"],"d":"Insert the text from the last Insert session","s":"builtin","t":"i_CTRL-A","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-C>","m":["i"],"d":"Leave Insert mode without abbreviations or InsertLeave","s":"builtin","t":"i_CTRL-C","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null,"n":"Skips InsertLeave autocmds, which plugins may rely on; prefer <kbd>&lt;Esc&gt;<\/kbd>"},{"k":"<C-D>","m":["i"],"d":"Dedent the current line one 'shiftwidth'","s":"builtin","t":"i_CTRL-D","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-E>","m":["i"],"d":"Insert the character below the cursor","s":"builtin","t":"i_CTRL-E","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G><C-J>","m":["i"],"d":"Down a line, to the column where Insert started","s":"builtin","t":"i_CTRL-G_j","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G>j","m":["i"],"d":"Down a line, to the column where Insert started","s":"builtin","t":"i_CTRL-G_j","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G><Down>","m":["i"],"d":"Down a line, to the column where Insert started","s":"builtin","t":"i_CTRL-G_j","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G><C-K>","m":["i"],"d":"Up a line, to the column where Insert started","s":"builtin","t":"i_CTRL-G_k","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G>k","m":["i"],"d":"Up a line, to the column where Insert started","s":"builtin","t":"i_CTRL-G_k","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G><Up>","m":["i"],"d":"Up a line, to the column where Insert started","s":"builtin","t":"i_CTRL-G_k","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G>u","m":["i"],"d":"Start a new undoable change (undo break)","s":"builtin","t":"i_CTRL-G_u","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-G>U","m":["i"],"d":"Don't start a new undo step for the next cursor movement","s":"builtin","t":"i_CTRL-G_U","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<BS>","m":["i"],"d":"Delete the character before the cursor","s":"builtin","t":"i_<BS>","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null,"ov":[{"m":"i","by":224},{"m":"i","by":245}]},{"k":"{char1}<BS>{char2}","m":["i"],"d":"enter digraph (only when 'digraph' option set)","s":"builtin","t":"i_digraph","h":"digraph.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-H>","m":["i"],"d":"Delete the character before the cursor (same as <BS>)","s":"builtin","t":"i_CTRL-H","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<Tab>","m":["i"],"d":"Insert a tab (or spaces with 'expandtab')","s":"builtin","t":"i_<Tab>","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null,"n":"In buffers where blink.cmp is attached, <kbd>&lt;Tab&gt;<\/kbd> tries in order: next snippet placeholder, Sidekick next-edit suggestion, accept Copilot ghost text, else a normal Tab (<code>lua/plugins.lua:331-340<\/code>)","ov":[{"m":"i","by":229}]},{"k":"<C-I>","m":["i"],"d":"Insert a tab (same as <Tab>)","s":"builtin","t":"i_CTRL-I","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<NL>","m":["i"],"d":"Start a new line (same as <CR>)","s":"builtin","t":"i_<NL>","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-J>","m":["i"],"d":"Start a new line (same as <CR>)","s":"builtin","t":"i_CTRL-J","h":"insert.txt","sec":"Insert mode","cat":"Insert","mo":null,"ch":null,"ctx":null},{"k":"<C-K>{char1}{char2}","m":["i"],"dLine truncated
// ---- Basics ------------------------------------------------------------------- // ---- Basics -------------------------------------------------------------------
// Every DATA section is optional: missing pieces render as empty, never crash. // Every DATA section is optional: missing pieces render as empty, never crash.
@@ -1147,32 +1149,65 @@ function groupName(seq, kids) {
} }
// ---- Keyboard --------------------------------------------------------------------------------- // ---- Keyboard ---------------------------------------------------------------------------------
// US ANSI board in key units (1u = one letter key). Printable keys carry base/shifted characters, // Two physical boards in key units (1u = one letter key): a standard US ANSI tenkeyless board and
// named keys a Vim key name, and modifier keys are toggles. // the full-size QMK board (Keychron Q5 Max) from the QMK cheatsheet. Letters are QWERTY on both, so a
const KB_W = 18.25, KB_H = 6.25; // key does the same Vim thing on either; only where it sits (and which keys exist) changes.
const KEYS = []; // Printable keys carry base/shifted characters, named keys a Vim key name, modifier keys are toggles.
function addRow(y, x, items) {
for (const it of items) {
const w = it.w || 1;
if (!it.gap) KEYS.push({ ...it, x, y, w });
x += w;
}
}
const pair = (b, s, w) => ({ b, s, w }); const pair = (b, s, w) => ({ b, s, w });
const letters = (s) => s.split("").map((c) => pair(c, c.toUpperCase())); const letters = (s) => s.split("").map((c) => pair(c, c.toUpperCase()));
const fk = (a, b) => Array.from({ length: b - a + 1 }, (_, i) => ({ n: "F" + (a + i) })); const fk = (a, b) => Array.from({ length: b - a + 1 }, (_, i) => ({ n: "F" + (a + i) }));
addRow(0, 0, [{ n: "Esc" }, { gap: 1, w: 1 }, ...fk(1, 4), { gap: 1, w: 0.5 }, ...fk(5, 8), { gap: 1, w: 0.5 }, ...fk(9, 12)]); const numrow = [pair("`", "~"), pair("1", "!"), pair("2", "@"), pair("3", "#"), pair("4", "$"), pair("5", "%"), pair("6", "^"),
addRow(1.25, 0, [pair("`", "~"), pair("1", "!"), pair("2", "@"), pair("3", "#"), pair("4", "$"), pair("5", "%"), pair("6", "^"), pair("7", "&"), pair("8", "*"), pair("9", "("), pair("0", ")"), pair("-", "_"), pair("=", "+")];
pair("7", "&"), pair("8", "*"), pair("9", "("), pair("0", ")"), pair("-", "_"), pair("=", "+"), { n: "BS", w: 2, label: "Backspace" }]); const kp = (n, label, h) => ({ n, label, h });
addRow(1.25, 15.25, [{ n: "Insert", label: "Ins" }, { n: "Home" }, { n: "PageUp", label: "PgUp" }]); function buildBoard(w, h, rows) {
addRow(2.25, 0, [{ n: "Tab", w: 1.5 }, ...letters("qwertyuiop"), pair("[", "{"), pair("]", "}"), pair("\\", "|", 1.5)]); const keys = [];
addRow(2.25, 15.25, [{ n: "Del" }, { n: "End" }, { n: "PageDown", label: "PgDn" }]); for (const [y, x0, items] of rows) {
addRow(3.25, 0, [{ mod: "Caps", w: 1.75 }, ...letters("asdfghjkl"), pair(";", ":"), pair("'", '"'), { n: "CR", w: 2.25, label: "Enter" }]); let x = x0;
addRow(4.25, 0, [{ mod: "Shift", w: 2.25 }, ...letters("zxcvbnm"), pair(",", "<"), pair(".", ">"), pair("/", "?"), { mod: "Shift", w: 2.75 }]); for (const it of items) {
addRow(4.25, 16.25, [{ n: "Up", label: "↑" }]); const kw = it.w || 1;
addRow(5.25, 0, [{ mod: "Ctrl", w: 1.25 }, { mod: "Super", w: 1.25 }, { mod: "Alt", w: 1.25 }, { n: "Space", w: 6.25 }, if (!it.gap) keys.push({ ...it, x, y, w: kw, h: it.h || 1 });
{ mod: "Alt", w: 1.25 }, { mod: "Super", w: 1.25 }, { mod: "Menu", w: 1.25 }, { mod: "Ctrl", w: 1.25 }]); x += kw;
addRow(5.25, 15.25, [{ n: "Left", label: "←" }, { n: "Down", label: "↓" }, { n: "Right", label: "→" }]); }
}
return { w, h, keys };
}
const BOARDS = {
standard: { name: "Standard", ...buildBoard(18.25, 6.25, [
[0, 0, [{ n: "Esc" }, { gap: 1 }, ...fk(1, 4), { gap: 1, w: 0.5 }, ...fk(5, 8), { gap: 1, w: 0.5 }, ...fk(9, 12)]],
[1.25, 0, [...numrow, { n: "BS", w: 2, label: "Backspace" }]],
[1.25, 15.25, [{ n: "Insert", label: "Ins" }, { n: "Home" }, { n: "PageUp", label: "PgUp" }]],
[2.25, 0, [{ n: "Tab", w: 1.5 }, ...letters("qwertyuiop"), pair("[", "{"), pair("]", "}"), pair("\\", "|", 1.5)]],
[2.25, 15.25, [{ n: "Del" }, { n: "End" }, { n: "PageDown", label: "PgDn" }]],
[3.25, 0, [{ mod: "Caps", w: 1.75 }, ...letters("asdfghjkl"), pair(";", ":"), pair("'", '"'), { n: "CR", w: 2.25, label: "Enter" }]],
[4.25, 0, [{ mod: "Shift", w: 2.25 }, ...letters("zxcvbnm"), pair(",", "<"), pair(".", ">"), pair("/", "?"), { mod: "Shift", w: 2.75 }]],
[4.25, 16.25, [{ n: "Up", label: "↑" }]],
[5.25, 0, [{ mod: "Ctrl", w: 1.25 }, { mod: "Super", w: 1.25 }, { mod: "Alt", w: 1.25 }, { n: "Space", w: 6.25 },
{ mod: "Alt", w: 1.25 }, { mod: "Super", w: 1.25 }, { mod: "Menu", w: 1.25 }, { mod: "Ctrl", w: 1.25 }]],
[5.25, 15.25, [{ n: "Left", label: "←" }, { n: "Down", label: "↓" }, { n: "Right", label: "→" }]],
]) },
// Positions from the Q5 Max info.json (LAYOUT_ansi_101). It has no Insert key, the nav column is
// PgUp / PgDn / Home-End (a tap dance: tap Home, double-tap End), the right-hand Alt/Menu are the
// FN1/FN2 layer keys, and the top-right keys (Print Screen, Calc, Find, knob) have no Vim meaning.
q5: { name: "Q5 Max (QMK)", ...buildBoard(20.5, 6.5, [
[0, 0, [{ n: "Esc" }, { gap: 1 }, ...fk(1, 4), { gap: 1, w: 0.5 }, ...fk(5, 8), { gap: 1, w: 0.5 }, ...fk(9, 12), { gap: 1, w: 0.25 },
{ n: "Del" }, { n: "PrtSc" }, { n: "Calc" }, { n: "Find" }, { n: "Knob", label: "⏯" }]],
[1.25, 0, [...numrow, { n: "BS", w: 2, label: "Backspace" }, { gap: 1, w: 0.25 }, { n: "PageUp", label: "PgUp" },
{ n: "NumLock", label: "Num" }, kp("kDivide", "/"), kp("kMultiply", "*"), kp("kMinus", "-")]],
[2.25, 0, [{ n: "Tab", w: 1.5 }, ...letters("qwertyuiop"), pair("[", "{"), pair("]", "}"), pair("\\", "|", 1.5), { gap: 1, w: 0.25 },
{ n: "PageDown", label: "PgDn" }, kp("k7", "7"), kp("k8", "8"), kp("k9", "9"), kp("kPlus", "+", 2)]],
[3.25, 0, [{ mod: "Caps", w: 1.75 }, ...letters("asdfghjkl"), pair(";", ":"), pair("'", '"'), { n: "CR", w: 2.25, label: "Enter" }, { gap: 1, w: 0.25 },
{ n: "Home", label: "Home" }, kp("k4", "4"), kp("k5", "5"), kp("k6", "6")]],
[4.25, 0, [{ mod: "Shift", w: 2.25 }, ...letters("zxcvbnm"), pair(",", "<"), pair(".", ">"), pair("/", "?"), { mod: "Shift", w: 1.75 }, { gap: 1, w: 2.5 },
kp("k1", "1"), kp("k2", "2"), kp("k3", "3"), kp("kEnter", "Ent", 2)]],
[4.5, 14.25, [{ n: "Up", label: "↑" }]],
[5.25, 0, [{ mod: "Ctrl", w: 1.25 }, { mod: "Super", w: 1.25 }, { mod: "Alt", w: 1.25 }, { n: "Space", w: 6.25 },
{ mod: "Fn2" }, { mod: "Fn1" }, { mod: "Ctrl" }, { gap: 1, w: 3.5 }, { n: "k0", label: "0", w: 2 }, kp("kPoint", ".")]],
[5.5, 13.25, [{ n: "Left", label: "←" }, { n: "Down", label: "↓" }, { n: "Right", label: "→" }]],
]) },
};
let KB_BOARD = "standard";
try { const b = localStorage.getItem("nvim-cheatsheet-board"); if (BOARDS[b]) KB_BOARD = b; } catch (e) { /* storage blocked: default board */ }
let KEYS = BOARDS[KB_BOARD].keys;
// Layers: a mode, a prefix typed first, and which modifiers start held. // Layers: a mode, a prefix typed first, and which modifiers start held.
const LAYERS = [ const LAYERS = [
@@ -1250,12 +1285,15 @@ function drawKB() {
$("aiswitch").hidden = !layer.ai; $("aiswitch").hidden = !layer.ai;
for (const b of $("aiswitch").children) b.setAttribute("aria-pressed", String(b.dataset.ai === KB.ai)); for (const b of $("aiswitch").children) b.setAttribute("aria-pressed", String(b.dataset.ai === KB.ai));
KB.infos = KEYS.map((k) => infoFor(k, layer)); KB.infos = KEYS.map((k) => infoFor(k, layer));
const B = BOARDS[KB_BOARD];
$("kb").style.aspectRatio = `${B.w} / ${B.h}`;
for (const b of $("kblayout").children) b.setAttribute("aria-pressed", String(b.dataset.board === KB_BOARD));
const cats = new Map(); const cats = new Map();
$("kb").innerHTML = KEYS.map((k, i) => { $("kb").innerHTML = KEYS.map((k, i) => {
const info = KB.infos[i]; const info = KB.infos[i];
const c = capContent(k, info || {}); const c = capContent(k, info || {});
if (c.cat) cats.set(c.cat, (cats.get(c.cat) || 0) + 1); if (c.cat) cats.set(c.cat, (cats.get(c.cat) || 0) + 1);
const style = `left:${(k.x / KB_W) * 100}%;top:${(k.y / KB_H) * 100}%;width:${(k.w / KB_W) * 100}%;height:${(1 / KB_H) * 100}%`; const style = `left:${(k.x / B.w) * 100}%;top:${(k.y / B.h) * 100}%;width:${(k.w / B.w) * 100}%;height:${(k.h / B.h) * 100}%`;
const pressed = k.mod === "Shift" ? KB.shift : k.mod === "Ctrl" ? KB.ctrl : null; const pressed = k.mod === "Shift" ? KB.shift : k.mod === "Ctrl" ? KB.ctrl : null;
const aria = k.mod ? `${k.mod}${pressed === null ? "" : pressed ? " (held)" : " (not held)"}` const aria = k.mod ? `${k.mod}${pressed === null ? "" : pressed ? " (held)" : " (not held)"}`
: `${info ? info.seq.join("") : ""}${c.lb ? ": " + c.lb : ""}`; : `${info ? info.seq.join("") : ""}${c.lb ? ": " + c.lb : ""}`;
@@ -1298,6 +1336,16 @@ function tabArrows(listEl, onPick) {
}); });
} }
tabArrows($("kbtabs"), (t) => selectLayer(+t.dataset.i)); tabArrows($("kbtabs"), (t) => selectLayer(+t.dataset.i));
$("kblayout").innerHTML = Object.entries(BOARDS).map(([id, b]) => `<button type="button" data-board="${id}" aria-pressed="${id === KB_BOARD}">${esc(b.name)}</button>`).join("");
$("kblayout").addEventListener("click", (e) => {
const b = e.target.closest("[data-board]");
if (!b || b.dataset.board === KB_BOARD) return;
KB_BOARD = b.dataset.board;
KEYS = BOARDS[KB_BOARD].keys;
KB.cur = -1;
try { localStorage.setItem("nvim-cheatsheet-board", KB_BOARD); } catch (err) { /* storage blocked: choice isn't remembered */ }
drawKB();
});
$("aiswitch").addEventListener("click", (e) => { const b = e.target.closest("[data-ai]"); if (b) { KB.ai = b.dataset.ai; drawKB(); } }); $("aiswitch").addEventListener("click", (e) => { const b = e.target.closest("[data-ai]"); if (b) { KB.ai = b.dataset.ai; drawKB(); } });
/** Detail panel: every entry for the key in this layer, then the longer keys it starts. */ /** Detail panel: every entry for the key in this layer, then the longer keys it starts. */
+72 -24
View File
@@ -102,6 +102,7 @@ nav a { color: var(--accent); text-decoration: none; font-weight: 600; }
nav a:hover { text-decoration: underline; } nav a:hover { text-decoration: underline; }
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 9px; overflow: hidden; background: var(--panel); flex: none; } .seg { display: inline-flex; border: 1px solid var(--line); border-radius: 9px; overflow: hidden; background: var(--panel); flex: none; }
.seg button { border: 0; background: transparent; padding: 6px 12px; cursor: pointer; font-size: 0.85rem; font-weight: 600; } .seg button { border: 0; background: transparent; padding: 6px 12px; cursor: pointer; font-size: 0.85rem; font-weight: 600; }
#kblayout { justify-self: start; }
.seg button + button { border-left: 1px solid var(--line); } .seg button + button { border-left: 1px solid var(--line); }
.seg button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); } .seg button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; list-style: none; margin: 0; padding: 0; } .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; list-style: none; margin: 0; padding: 0; }
@@ -396,6 +397,7 @@ footer code { font-size: 0.8rem; }
<button type="button" data-ai="a" aria-pressed="false">a · around</button> <button type="button" data-ai="a" aria-pressed="false">a · around</button>
</div> </div>
</div> </div>
<div class="seg" id="kblayout" role="group" aria-label="Keyboard layout"></div>
<div class="legend" id="kblegend"></div> <div class="legend" id="kblegend"></div>
<div class="board"><div class="case"><div class="kb" id="kb" role="group" aria-label="Keyboard"></div></div></div> <div class="board"><div class="case"><div class="kb" id="kb" role="group" aria-label="Keyboard"></div></div></div>
<div class="keyinfo" id="keyinfo" aria-live="polite"></div> <div class="keyinfo" id="keyinfo" aria-live="polite"></div>
@@ -1148,32 +1150,65 @@ function groupName(seq, kids) {
} }
// ---- Keyboard --------------------------------------------------------------------------------- // ---- Keyboard ---------------------------------------------------------------------------------
// US ANSI board in key units (1u = one letter key). Printable keys carry base/shifted characters, // Two physical boards in key units (1u = one letter key): a standard US ANSI tenkeyless board and
// named keys a Vim key name, and modifier keys are toggles. // the full-size QMK board (Keychron Q5 Max) from the QMK cheatsheet. Letters are QWERTY on both, so a
const KB_W = 18.25, KB_H = 6.25; // key does the same Vim thing on either; only where it sits (and which keys exist) changes.
const KEYS = []; // Printable keys carry base/shifted characters, named keys a Vim key name, modifier keys are toggles.
function addRow(y, x, items) {
for (const it of items) {
const w = it.w || 1;
if (!it.gap) KEYS.push({ ...it, x, y, w });
x += w;
}
}
const pair = (b, s, w) => ({ b, s, w }); const pair = (b, s, w) => ({ b, s, w });
const letters = (s) => s.split("").map((c) => pair(c, c.toUpperCase())); const letters = (s) => s.split("").map((c) => pair(c, c.toUpperCase()));
const fk = (a, b) => Array.from({ length: b - a + 1 }, (_, i) => ({ n: "F" + (a + i) })); const fk = (a, b) => Array.from({ length: b - a + 1 }, (_, i) => ({ n: "F" + (a + i) }));
addRow(0, 0, [{ n: "Esc" }, { gap: 1, w: 1 }, ...fk(1, 4), { gap: 1, w: 0.5 }, ...fk(5, 8), { gap: 1, w: 0.5 }, ...fk(9, 12)]); const numrow = [pair("`", "~"), pair("1", "!"), pair("2", "@"), pair("3", "#"), pair("4", "$"), pair("5", "%"), pair("6", "^"),
addRow(1.25, 0, [pair("`", "~"), pair("1", "!"), pair("2", "@"), pair("3", "#"), pair("4", "$"), pair("5", "%"), pair("6", "^"), pair("7", "&"), pair("8", "*"), pair("9", "("), pair("0", ")"), pair("-", "_"), pair("=", "+")];
pair("7", "&"), pair("8", "*"), pair("9", "("), pair("0", ")"), pair("-", "_"), pair("=", "+"), { n: "BS", w: 2, label: "Backspace" }]); const kp = (n, label, h) => ({ n, label, h });
addRow(1.25, 15.25, [{ n: "Insert", label: "Ins" }, { n: "Home" }, { n: "PageUp", label: "PgUp" }]); function buildBoard(w, h, rows) {
addRow(2.25, 0, [{ n: "Tab", w: 1.5 }, ...letters("qwertyuiop"), pair("[", "{"), pair("]", "}"), pair("\\", "|", 1.5)]); const keys = [];
addRow(2.25, 15.25, [{ n: "Del" }, { n: "End" }, { n: "PageDown", label: "PgDn" }]); for (const [y, x0, items] of rows) {
addRow(3.25, 0, [{ mod: "Caps", w: 1.75 }, ...letters("asdfghjkl"), pair(";", ":"), pair("'", '"'), { n: "CR", w: 2.25, label: "Enter" }]); let x = x0;
addRow(4.25, 0, [{ mod: "Shift", w: 2.25 }, ...letters("zxcvbnm"), pair(",", "<"), pair(".", ">"), pair("/", "?"), { mod: "Shift", w: 2.75 }]); for (const it of items) {
addRow(4.25, 16.25, [{ n: "Up", label: "↑" }]); const kw = it.w || 1;
addRow(5.25, 0, [{ mod: "Ctrl", w: 1.25 }, { mod: "Super", w: 1.25 }, { mod: "Alt", w: 1.25 }, { n: "Space", w: 6.25 }, if (!it.gap) keys.push({ ...it, x, y, w: kw, h: it.h || 1 });
{ mod: "Alt", w: 1.25 }, { mod: "Super", w: 1.25 }, { mod: "Menu", w: 1.25 }, { mod: "Ctrl", w: 1.25 }]); x += kw;
addRow(5.25, 15.25, [{ n: "Left", label: "←" }, { n: "Down", label: "↓" }, { n: "Right", label: "→" }]); }
}
return { w, h, keys };
}
const BOARDS = {
standard: { name: "Standard", ...buildBoard(18.25, 6.25, [
[0, 0, [{ n: "Esc" }, { gap: 1 }, ...fk(1, 4), { gap: 1, w: 0.5 }, ...fk(5, 8), { gap: 1, w: 0.5 }, ...fk(9, 12)]],
[1.25, 0, [...numrow, { n: "BS", w: 2, label: "Backspace" }]],
[1.25, 15.25, [{ n: "Insert", label: "Ins" }, { n: "Home" }, { n: "PageUp", label: "PgUp" }]],
[2.25, 0, [{ n: "Tab", w: 1.5 }, ...letters("qwertyuiop"), pair("[", "{"), pair("]", "}"), pair("\\", "|", 1.5)]],
[2.25, 15.25, [{ n: "Del" }, { n: "End" }, { n: "PageDown", label: "PgDn" }]],
[3.25, 0, [{ mod: "Caps", w: 1.75 }, ...letters("asdfghjkl"), pair(";", ":"), pair("'", '"'), { n: "CR", w: 2.25, label: "Enter" }]],
[4.25, 0, [{ mod: "Shift", w: 2.25 }, ...letters("zxcvbnm"), pair(",", "<"), pair(".", ">"), pair("/", "?"), { mod: "Shift", w: 2.75 }]],
[4.25, 16.25, [{ n: "Up", label: "↑" }]],
[5.25, 0, [{ mod: "Ctrl", w: 1.25 }, { mod: "Super", w: 1.25 }, { mod: "Alt", w: 1.25 }, { n: "Space", w: 6.25 },
{ mod: "Alt", w: 1.25 }, { mod: "Super", w: 1.25 }, { mod: "Menu", w: 1.25 }, { mod: "Ctrl", w: 1.25 }]],
[5.25, 15.25, [{ n: "Left", label: "←" }, { n: "Down", label: "↓" }, { n: "Right", label: "→" }]],
]) },
// Positions from the Q5 Max info.json (LAYOUT_ansi_101). It has no Insert key, the nav column is
// PgUp / PgDn / Home-End (a tap dance: tap Home, double-tap End), the right-hand Alt/Menu are the
// FN1/FN2 layer keys, and the top-right keys (Print Screen, Calc, Find, knob) have no Vim meaning.
q5: { name: "Q5 Max (QMK)", ...buildBoard(20.5, 6.5, [
[0, 0, [{ n: "Esc" }, { gap: 1 }, ...fk(1, 4), { gap: 1, w: 0.5 }, ...fk(5, 8), { gap: 1, w: 0.5 }, ...fk(9, 12), { gap: 1, w: 0.25 },
{ n: "Del" }, { n: "PrtSc" }, { n: "Calc" }, { n: "Find" }, { n: "Knob", label: "⏯" }]],
[1.25, 0, [...numrow, { n: "BS", w: 2, label: "Backspace" }, { gap: 1, w: 0.25 }, { n: "PageUp", label: "PgUp" },
{ n: "NumLock", label: "Num" }, kp("kDivide", "/"), kp("kMultiply", "*"), kp("kMinus", "-")]],
[2.25, 0, [{ n: "Tab", w: 1.5 }, ...letters("qwertyuiop"), pair("[", "{"), pair("]", "}"), pair("\\", "|", 1.5), { gap: 1, w: 0.25 },
{ n: "PageDown", label: "PgDn" }, kp("k7", "7"), kp("k8", "8"), kp("k9", "9"), kp("kPlus", "+", 2)]],
[3.25, 0, [{ mod: "Caps", w: 1.75 }, ...letters("asdfghjkl"), pair(";", ":"), pair("'", '"'), { n: "CR", w: 2.25, label: "Enter" }, { gap: 1, w: 0.25 },
{ n: "Home", label: "Home" }, kp("k4", "4"), kp("k5", "5"), kp("k6", "6")]],
[4.25, 0, [{ mod: "Shift", w: 2.25 }, ...letters("zxcvbnm"), pair(",", "<"), pair(".", ">"), pair("/", "?"), { mod: "Shift", w: 1.75 }, { gap: 1, w: 2.5 },
kp("k1", "1"), kp("k2", "2"), kp("k3", "3"), kp("kEnter", "Ent", 2)]],
[4.5, 14.25, [{ n: "Up", label: "↑" }]],
[5.25, 0, [{ mod: "Ctrl", w: 1.25 }, { mod: "Super", w: 1.25 }, { mod: "Alt", w: 1.25 }, { n: "Space", w: 6.25 },
{ mod: "Fn2" }, { mod: "Fn1" }, { mod: "Ctrl" }, { gap: 1, w: 3.5 }, { n: "k0", label: "0", w: 2 }, kp("kPoint", ".")]],
[5.5, 13.25, [{ n: "Left", label: "←" }, { n: "Down", label: "↓" }, { n: "Right", label: "→" }]],
]) },
};
let KB_BOARD = "standard";
try { const b = localStorage.getItem("nvim-cheatsheet-board"); if (BOARDS[b]) KB_BOARD = b; } catch (e) { /* storage blocked: default board */ }
let KEYS = BOARDS[KB_BOARD].keys;
// Layers: a mode, a prefix typed first, and which modifiers start held. // Layers: a mode, a prefix typed first, and which modifiers start held.
const LAYERS = [ const LAYERS = [
@@ -1251,12 +1286,15 @@ function drawKB() {
$("aiswitch").hidden = !layer.ai; $("aiswitch").hidden = !layer.ai;
for (const b of $("aiswitch").children) b.setAttribute("aria-pressed", String(b.dataset.ai === KB.ai)); for (const b of $("aiswitch").children) b.setAttribute("aria-pressed", String(b.dataset.ai === KB.ai));
KB.infos = KEYS.map((k) => infoFor(k, layer)); KB.infos = KEYS.map((k) => infoFor(k, layer));
const B = BOARDS[KB_BOARD];
$("kb").style.aspectRatio = `${B.w} / ${B.h}`;
for (const b of $("kblayout").children) b.setAttribute("aria-pressed", String(b.dataset.board === KB_BOARD));
const cats = new Map(); const cats = new Map();
$("kb").innerHTML = KEYS.map((k, i) => { $("kb").innerHTML = KEYS.map((k, i) => {
const info = KB.infos[i]; const info = KB.infos[i];
const c = capContent(k, info || {}); const c = capContent(k, info || {});
if (c.cat) cats.set(c.cat, (cats.get(c.cat) || 0) + 1); if (c.cat) cats.set(c.cat, (cats.get(c.cat) || 0) + 1);
const style = `left:${(k.x / KB_W) * 100}%;top:${(k.y / KB_H) * 100}%;width:${(k.w / KB_W) * 100}%;height:${(1 / KB_H) * 100}%`; const style = `left:${(k.x / B.w) * 100}%;top:${(k.y / B.h) * 100}%;width:${(k.w / B.w) * 100}%;height:${(k.h / B.h) * 100}%`;
const pressed = k.mod === "Shift" ? KB.shift : k.mod === "Ctrl" ? KB.ctrl : null; const pressed = k.mod === "Shift" ? KB.shift : k.mod === "Ctrl" ? KB.ctrl : null;
const aria = k.mod ? `${k.mod}${pressed === null ? "" : pressed ? " (held)" : " (not held)"}` const aria = k.mod ? `${k.mod}${pressed === null ? "" : pressed ? " (held)" : " (not held)"}`
: `${info ? info.seq.join("") : ""}${c.lb ? ": " + c.lb : ""}`; : `${info ? info.seq.join("") : ""}${c.lb ? ": " + c.lb : ""}`;
@@ -1299,6 +1337,16 @@ function tabArrows(listEl, onPick) {
}); });
} }
tabArrows($("kbtabs"), (t) => selectLayer(+t.dataset.i)); tabArrows($("kbtabs"), (t) => selectLayer(+t.dataset.i));
$("kblayout").innerHTML = Object.entries(BOARDS).map(([id, b]) => `<button type="button" data-board="${id}" aria-pressed="${id === KB_BOARD}">${esc(b.name)}</button>`).join("");
$("kblayout").addEventListener("click", (e) => {
const b = e.target.closest("[data-board]");
if (!b || b.dataset.board === KB_BOARD) return;
KB_BOARD = b.dataset.board;
KEYS = BOARDS[KB_BOARD].keys;
KB.cur = -1;
try { localStorage.setItem("nvim-cheatsheet-board", KB_BOARD); } catch (err) { /* storage blocked: choice isn't remembered */ }
drawKB();
});
$("aiswitch").addEventListener("click", (e) => { const b = e.target.closest("[data-ai]"); if (b) { KB.ai = b.dataset.ai; drawKB(); } }); $("aiswitch").addEventListener("click", (e) => { const b = e.target.closest("[data-ai]"); if (b) { KB.ai = b.dataset.ai; drawKB(); } });
/** Detail panel: every entry for the key in this layer, then the longer keys it starts. */ /** Detail panel: every entry for the key in this layer, then the longer keys it starts. */