From 9e6faa55542c70945c26a25fd78acc6c19f5281f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Fr=C3=B3es=20Franco?= Date: Tue, 12 May 2026 00:46:40 +0000 Subject: [PATCH] feat(mappings): vimwiki-parity default keymaps for Vim + Neovim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Phase 19 keymap layer only covered the high-value subset and used `g=`/`g-` instead of vimwiki's `=`/`-` for header levels. Users reporting "mappings don't seem to be working" were either on the Vim path (which registered zero keymaps) or hitting bindings that don't exist (e.g. ``, ``, `gll`, `gqq`). Cross-referenced upstream vimwiki/ftplugin/vimwiki.vim and rewired both `lua/nuwiki/keymaps.lua` and `ftplugin/vimwiki.vim` (Vim path) to match its default surface. Mappings backed by commands that exist on the server dispatch directly; §13.1-deferred mappings register with the same lhs but stub out to a notification — so users get parity *signalling*, not silence. New on Neovim (and matched on Vim where possible): Links group: - `` / `` / `` — follow in split / vsplit / tab - `` / `` — jump to next / prev `[[…]]` (pure Lua search) - `+` (n + x) — :VimwikiNormalizeLink stub - `wc` (n + x) — :VimwikiColorize stub Lists group: - `` (n + x) — terminal-alt for `` - `gln` / `glp` increment + decrement (deferred backward; same fn for now) - `glh` / `gll` / `gLh` / `gLl` — list level stubs - `glr` / `gLr` — list renumber stubs - `gl` / `gL` — checkbox remove stubs - `o` / `O` — open below/above and continue the bullet (pure Lua) - visual-mode variants of ``, `gln`, `glp`, `glx` Header group (key group, now buffer-local): - `=` / `-` — promote / demote (was `g=`/`g-`, matched to vimwiki) - `]]` / `[[` — next / prev header (pure Lua) - `]=` / `[=` — next / prev sibling header (pure Lua) - `]u` / `[u` — parent header (pure Lua) Table group: `gqq`, `gq1`, `gww`, `gw1`, ``, `` stubs. Wiki prefix: `wm` (vimwiki's tomorrow), `wi` (rebuild diary index). Config schema (`config.options.mappings`) updates to vimwiki's group names — `links` / `lists` / `headers` / `table_editing` / `diary` / `html_export` / `text_objects` / `wiki_prefix`. Old names (`list_editing` / `header_nav`) are retired; rename only — keeps the opt-out shape intact. Vim path: same set ported to buffer-local VimL maps. `g:nuwiki_no_default_mappings` opts out the whole layer for users who prefer their own bindings. Lua side uses the Neovim 0.11+ `vim.keymap.set` API; both call straight into the same `nuwiki#commands#…` autoload (Vim) / `nuwiki.commands` (Lua) layer that drives the LSP. Total 377 tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- ftplugin/vimwiki.vim | 68 +++++++++++ lua/nuwiki/config.lua | 18 +-- lua/nuwiki/keymaps.lua | 267 ++++++++++++++++++++++++++++++++++------- 3 files changed, 305 insertions(+), 48 deletions(-) diff --git a/ftplugin/vimwiki.vim b/ftplugin/vimwiki.vim index fd36736..737eaab 100644 --- a/ftplugin/vimwiki.vim +++ b/ftplugin/vimwiki.vim @@ -104,6 +104,74 @@ if !has('nvim') command! -buffer -nargs=? NuwikiGenerateTagLinks call nuwiki#commands#tags_generate_links() command! -buffer -nargs=1 NuwikiGoto call nuwiki#commands#wiki_goto_page() + " ===== Default key mappings (parity with upstream vimwiki) ===== + " + " Users can opt out by setting `g:nuwiki_no_default_mappings = 1` + " (whole layer) or by adding their own competing buffer-local maps. + + if !get(g:, 'nuwiki_no_default_mappings', 0) + " Wiki prefix + nnoremap ww :call nuwiki#commands#diary_today() + nnoremap wt :call nuwiki#commands#diary_today() + nnoremap wi :call nuwiki#commands#diary_index() + nnoremap ww :call nuwiki#commands#diary_today() + nnoremap wy :call nuwiki#commands#diary_yesterday() + nnoremap wt :call nuwiki#commands#diary_tomorrow() + nnoremap wm :call nuwiki#commands#diary_tomorrow() + nnoremap wi :call nuwiki#commands#diary_generate_index() + + " Links + nnoremap :LspDefinition + nnoremap :split:LspDefinition + nnoremap :vsplit:LspDefinition + nnoremap :tabnew:LspDefinition + nnoremap + nnoremap /\[\[:nohlsearch + nnoremap ?\[\[:nohlsearch + nnoremap + :echohl WarningMsgechom 'nuwiki: :VimwikiNormalizeLink not yet implemented — see SPEC §13.1'echohl None + nnoremap wn :call nuwiki#commands#wiki_goto_page('') + nnoremap wd :call nuwiki#commands#delete_file() + nnoremap wr :call nuwiki#commands#rename_file() + nnoremap wc :echohl WarningMsgechom 'nuwiki: :VimwikiColorize not yet implemented — see SPEC §13.1'echohl None + + " Diary nav + nnoremap :call nuwiki#commands#diary_next() + nnoremap :call nuwiki#commands#diary_prev() + + " Lists + nnoremap :call nuwiki#commands#toggle_list_item() + xnoremap :call nuwiki#commands#toggle_list_item() + nnoremap :call nuwiki#commands#toggle_list_item() + xnoremap :call nuwiki#commands#toggle_list_item() + nnoremap gnt :call nuwiki#commands#next_task() + nnoremap gln :call nuwiki#commands#cycle_list_item() + nnoremap glp :call nuwiki#commands#cycle_list_item() + nnoremap glx :call nuwiki#commands#reject_list_item() + nnoremap glh :echohl WarningMsgechom 'nuwiki: list level not yet implemented — see SPEC §13.1'echohl None + nnoremap gll :echohl WarningMsgechom 'nuwiki: list level not yet implemented — see SPEC §13.1'echohl None + nnoremap gLh :echohl WarningMsgechom 'nuwiki: list level subtree not yet implemented — see SPEC §13.1'echohl None + nnoremap gLl :echohl WarningMsgechom 'nuwiki: list level subtree not yet implemented — see SPEC §13.1'echohl None + nnoremap glr :echohl WarningMsgechom 'nuwiki: list renumber not yet implemented — see SPEC §13.1'echohl None + nnoremap gLr :echohl WarningMsgechom 'nuwiki: list renumber-all not yet implemented — see SPEC §13.1'echohl None + + " Headers + nnoremap = :call nuwiki#commands#heading_add() + nnoremap - :call nuwiki#commands#heading_remove() + nnoremap ]] /^\s*=\+\s:nohlsearch + nnoremap [[ ?^\s*=\+\s:nohlsearch + + " HTML export + nnoremap wh :call nuwiki#commands#export_current() + nnoremap whh :call nuwiki#commands#export_browse() + nnoremap wha :call nuwiki#commands#export_all() + + " Tables (deferred §13.1) + nnoremap gqq :echohl WarningMsgechom 'nuwiki: :VimwikiTableAlignQ not yet implemented — see SPEC §13.1'echohl None + nnoremap gww :echohl WarningMsgechom 'nuwiki: :VimwikiTableAlignW not yet implemented — see SPEC §13.1'echohl None + nnoremap :echohl WarningMsgechom 'nuwiki: :VimwikiTableMoveColumnLeft not yet implemented'echohl None + nnoremap :echohl WarningMsgechom 'nuwiki: :VimwikiTableMoveColumnRight not yet implemented'echohl None + endif + finish endif diff --git a/lua/nuwiki/config.lua b/lua/nuwiki/config.lua index adc36d9..8485648 100644 --- a/lua/nuwiki/config.lua +++ b/lua/nuwiki/config.lua @@ -11,15 +11,19 @@ M.defaults = { syntax = 'vimwiki', log_level = 'warn', - -- Phase 19: per-buffer glue. Each subgroup can be flipped off - -- independently to suppress the corresponding keymaps. + -- Phase 19: per-buffer glue. Each subgroup mirrors vimwiki's + -- `g:vimwiki_key_mappings` shape and can be flipped off + -- independently to suppress that group of keymaps. mappings = { enabled = true, - list_editing = true, - header_nav = true, - diary = true, - html_export = true, - text_objects = true, + wiki_prefix = true, -- w* + links = true, -- , , , , +, … + lists = true, -- , gln/glp/glx, gl, o/O, … + headers = true, -- =, -, ]], [[, ]=, [=, ]u, [u + table_editing = true, -- gqq, gq1, gww, gw1, , + diary = true, -- , + html_export = true, -- wh, whh + text_objects = true, -- ah, ih, … }, -- Phase 19: folding. `'lsp'` uses the server's `foldingRange` diff --git a/lua/nuwiki/keymaps.lua b/lua/nuwiki/keymaps.lua index 0c62c96..74a1007 100644 --- a/lua/nuwiki/keymaps.lua +++ b/lua/nuwiki/keymaps.lua @@ -1,11 +1,24 @@ -- lua/nuwiki/keymaps.lua — buffer-local key bindings for `.wiki` buffers. -- --- Opt-in via `mappings.enabled = true` (default). Subgroups can be --- disabled independently — see `defaults.lua` for the schema. +-- Defaults mirror upstream vimwiki's ftplugin layout (see +-- github.com/vimwiki/vimwiki ftplugin/vimwiki.vim), grouped so users +-- can flip subgroups off independently via the `mappings` config: -- --- Mappings here cover the subset of vimwiki's defaults whose server-side --- commands are implemented. Anything pointing at a SPEC §13.1 deferred --- command is intentionally omitted to avoid surprising no-ops. +-- mappings = { +-- enabled = true, +-- links = true, -- , , , , +, … +-- lists = true, -- , gln/glp, glx, gl, … +-- headers = true, -- =, -, ]], [[, ]=, [=, ]u, [u +-- table_editing = true, -- gqq, gq1, gww, gw1, , +-- html_export = true, -- wh, whh +-- diary = true, -- , , w* +-- text_objects = true, -- ah, ih, al, il +-- wiki_prefix = true, -- ww, ws, wi, … +-- } +-- +-- Mappings that point at §13.1-deferred commands stub out to a +-- "not yet implemented" notification — better than a silent no-op +-- so users get parity *signalling*, not just parity in lhs. local M = {} @@ -14,59 +27,231 @@ local function map(mode, lhs, rhs, opts, bufnr) vim.keymap.set(mode, lhs, rhs, opts) end ---- Register all keymaps for the given buffer. Caller passes the ---- effective `mappings` config (already merged with defaults). +-- ===== Header navigation (pure Lua) ===== +-- +-- Pure regex jumps, no LSP round-trip. The patterns mirror what +-- vimwiki's `vimwiki#base#goto_*_header` family does, just simpler: +-- match `^\s*=` runs as heading lines. + +local function heading_level(line) + local lead = line and line:match('^%s*(=+)%s') + if not lead then return 0 end + local trail = line:match('%s(=+)%s*$') + if not trail or #trail ~= #lead then return 0 end + return #lead +end + +local function jump_heading(direction, predicate) + local total = vim.api.nvim_buf_line_count(0) + local lnum = vim.fn.line('.') + local step = direction > 0 and 1 or -1 + local i = lnum + step + while i >= 1 and i <= total do + local lvl = heading_level(vim.fn.getline(i)) + if lvl > 0 and predicate(lvl, i) then + vim.api.nvim_win_set_cursor(0, { i, 0 }) + return + end + i = i + step + end +end + +local function current_heading_level() + local total = vim.api.nvim_buf_line_count(0) + local lnum = vim.fn.line('.') + for i = lnum, 1, -1 do + local lvl = heading_level(vim.fn.getline(i)) + if lvl > 0 then return lvl, i end + end + return 0, nil +end + +local nav = {} +function nav.next_header() jump_heading(1, function() return true end) end +function nav.prev_header() jump_heading(-1, function() return true end) end +function nav.next_sibling_header() + local lvl = current_heading_level() + jump_heading(1, function(l) return l <= lvl end) +end +function nav.prev_sibling_header() + local lvl = current_heading_level() + jump_heading(-1, function(l) return l <= lvl end) +end +function nav.parent_header() + local lvl = current_heading_level() + if lvl <= 1 then return end + jump_heading(-1, function(l) return l < lvl end) +end + +-- ===== Link navigation (pure Lua) ===== +-- +-- `` / `` jump to the next/prev `[[…]]` on or after the +-- cursor line. The server-side `goto_definition` handles the actual +-- follow; this is just movement. + +local function jump_to_pattern(pattern, direction) + -- Vim's `searchpos` handles forward/backward + wrap. + local flags = direction > 0 and 'W' or 'bW' + local pos = vim.fn.searchpos(pattern, flags) + if pos[1] > 0 then + vim.api.nvim_win_set_cursor(0, { pos[1], pos[2] - 1 }) + end +end +local link = {} +function link.next() jump_to_pattern([[\[\[]], 1) end +function link.prev() jump_to_pattern([[\[\[]], -1) end + +-- ===== List "o"/"O" continuation ===== +-- +-- Vimwiki's o/O preserves the list bullet on the new line. We +-- approximate: when the current line starts with a list marker, insert +-- the marker on the new line; otherwise fall back to plain o/O. + +local function open_below_with_bullet() + local line = vim.fn.getline('.') + local indent, marker = line:match('^(%s*)([%-%*#])%s') + if not marker then + vim.api.nvim_feedkeys('o', 'n', false) + return + end + local prefix = indent .. marker .. ' ' + -- Also preserve checkbox if present + local checkbox = line:match('^%s*[%-%*#]%s+(%[[%sxXoO%.%-]%])') + if checkbox then + prefix = prefix .. '[ ] ' + end + vim.api.nvim_feedkeys('o' .. prefix, 'n', false) +end +local function open_above_with_bullet() + local line = vim.fn.getline('.') + local indent, marker = line:match('^(%s*)([%-%*#])%s') + if not marker then + vim.api.nvim_feedkeys('O', 'n', false) + return + end + local prefix = indent .. marker .. ' ' + vim.api.nvim_feedkeys('O' .. prefix, 'n', false) +end + +-- ===== Stub for deferred §13.1 commands ===== + +local function deferred(name) + return function() + vim.notify( + 'nuwiki: ' .. name .. ' is not yet implemented — see SPEC §13.1', + vim.log.levels.WARN + ) + end +end + +-- ===== Public attach ===== + function M.attach(bufnr, mappings) local cmd = require('nuwiki.commands') - if not mappings or mappings.enabled == false then return end - -- ===== Wiki navigation ===== - map('n', 'ww', cmd.diary_today, { desc = 'nuwiki: open today\'s diary' }, bufnr) - map('n', 'wt', cmd.diary_today, { desc = 'nuwiki: open today\'s diary' }, bufnr) - map('n', 'ws', cmd.wiki_ui_select, { desc = 'nuwiki: pick wiki' }, bufnr) - map('n', 'wi', cmd.diary_index, { desc = 'nuwiki: open diary index' }, bufnr) - map('n', 'ww', cmd.diary_today, { desc = 'nuwiki: today' }, bufnr) - map('n', 'wy', cmd.diary_yesterday, { desc = 'nuwiki: yesterday' }, bufnr) - map('n', 'wt', cmd.diary_tomorrow, { desc = 'nuwiki: tomorrow' }, bufnr) - map('n', 'wd', cmd.delete_file, { desc = 'nuwiki: delete page' }, bufnr) - map('n', 'wr', cmd.rename_file, { desc = 'nuwiki: rename page' }, bufnr) - map('n', 'wn', cmd.wiki_goto_page, { desc = 'nuwiki: goto page' }, bufnr) + -- Default a missing subgroup to true so users only opt *out*. + local function on(group) + if mappings[group] == nil then return true end + return mappings[group] ~= false + end - -- Link follow / nav - map('n', '', vim.lsp.buf.definition, { desc = 'nuwiki: follow link' }, bufnr) - map('n', '', '', { desc = 'nuwiki: go back', remap = true }, bufnr) + -- ===== Wiki prefix (`w*`) ===== + if on('wiki_prefix') then + map('n', 'ww', cmd.diary_today, { desc = 'nuwiki: open default wiki / today' }, bufnr) + map('n', 'wt', cmd.diary_today, { desc = 'nuwiki: open today\'s diary' }, bufnr) + map('n', 'ws', cmd.wiki_ui_select, { desc = 'nuwiki: pick wiki' }, bufnr) + map('n', 'wi', cmd.diary_index, { desc = 'nuwiki: open diary index' }, bufnr) + map('n', 'ww', cmd.diary_today, { desc = 'nuwiki: today' }, bufnr) + map('n', 'wy', cmd.diary_yesterday, { desc = 'nuwiki: yesterday' }, bufnr) + map('n', 'wt', cmd.diary_tomorrow, { desc = 'nuwiki: tomorrow' }, bufnr) + map('n', 'wm', cmd.diary_tomorrow, { desc = 'nuwiki: tomorrow (vimwiki wm)' }, bufnr) + map('n', 'wi', cmd.diary_generate_index, { desc = 'nuwiki: rebuild diary index' }, bufnr) + end + + -- ===== Links ===== + if on('links') then + map('n', '', vim.lsp.buf.definition, { desc = 'nuwiki: follow link' }, bufnr) + map('n', '', function() vim.cmd('split'); vim.lsp.buf.definition() end, + { desc = 'nuwiki: follow link in split' }, bufnr) + map('n', '', function() vim.cmd('vsplit'); vim.lsp.buf.definition() end, + { desc = 'nuwiki: follow link in vsplit' }, bufnr) + map('n', '', function() vim.cmd('tabnew'); vim.lsp.buf.definition() end, + { desc = 'nuwiki: follow link in new tab' }, bufnr) + map('n', '', '', { desc = 'nuwiki: go back', remap = true }, bufnr) + map('n', '', link.next, { desc = 'nuwiki: next wikilink' }, bufnr) + map('n', '', link.prev, { desc = 'nuwiki: prev wikilink' }, bufnr) + map('n', '+', deferred(':VimwikiNormalizeLink'), { desc = 'nuwiki: normalize link (deferred)' }, bufnr) + map('x', '+', deferred(':VimwikiNormalizeLink'), { desc = 'nuwiki: normalize link (deferred)' }, bufnr) + map('n', 'wn', cmd.wiki_goto_page, { desc = 'nuwiki: goto page' }, bufnr) + map('n', 'wd', cmd.delete_file, { desc = 'nuwiki: delete page' }, bufnr) + map('n', 'wr', cmd.rename_file, { desc = 'nuwiki: rename page' }, bufnr) + map('n', 'wc', deferred(':VimwikiColorize'), { desc = 'nuwiki: colorize (deferred)' }, bufnr) + map('x', 'wc', deferred(':VimwikiColorize'), { desc = 'nuwiki: colorize (deferred)' }, bufnr) + end -- ===== Diary nav ===== - if mappings.diary ~= false then + if on('diary') then map('n', '', cmd.diary_next, { desc = 'nuwiki: next diary entry' }, bufnr) - map('n', '', cmd.diary_prev, { desc = 'nuwiki: previous diary entry' }, bufnr) + map('n', '', cmd.diary_prev, { desc = 'nuwiki: prev diary entry' }, bufnr) end - -- ===== List editing ===== - if mappings.list_editing ~= false then + -- ===== Lists ===== + if on('lists') then map('n', '', cmd.toggle_list_item, { desc = 'nuwiki: toggle checkbox' }, bufnr) - map('n', 'gnt', cmd.next_task, { desc = 'nuwiki: next unfinished task' }, bufnr) - map('n', 'gln', cmd.cycle_list_item, { desc = 'nuwiki: cycle checkbox' }, bufnr) - map('n', 'glp', cmd.cycle_list_item, { desc = 'nuwiki: cycle checkbox' }, bufnr) - map('n', 'glx', cmd.reject_list_item, { desc = 'nuwiki: reject checkbox' }, bufnr) + map('x', '', cmd.toggle_list_item, { desc = 'nuwiki: toggle checkbox' }, bufnr) + -- is the terminal byte for C-Space — keep parity with vimwiki. + map('n', '', cmd.toggle_list_item, { desc = 'nuwiki: toggle checkbox' }, bufnr) + map('x', '', cmd.toggle_list_item, { desc = 'nuwiki: toggle checkbox' }, bufnr) + map('n', 'gnt', cmd.next_task, { desc = 'nuwiki: next unfinished task' }, bufnr) + map('n', 'gln', cmd.cycle_list_item, { desc = 'nuwiki: increment checkbox' }, bufnr) + map('x', 'gln', cmd.cycle_list_item, { desc = 'nuwiki: increment checkbox' }, bufnr) + map('n', 'glp', cmd.cycle_list_item, { desc = 'nuwiki: decrement checkbox' }, bufnr) + map('x', 'glp', cmd.cycle_list_item, { desc = 'nuwiki: decrement checkbox' }, bufnr) + map('n', 'glx', cmd.reject_list_item, { desc = 'nuwiki: reject checkbox' }, bufnr) + map('x', 'glx', cmd.reject_list_item, { desc = 'nuwiki: reject checkbox' }, bufnr) + map('n', 'glh', deferred(':VimwikiListDecreaseLvl'), { desc = 'nuwiki: list dedent (deferred)' }, bufnr) + map('n', 'gll', deferred(':VimwikiListIncreaseLvl'), { desc = 'nuwiki: list indent (deferred)' }, bufnr) + map('n', 'gLh', deferred(':VimwikiListDecreaseLvlSubtree'), { desc = 'nuwiki: list dedent subtree (deferred)' }, bufnr) + map('n', 'gLl', deferred(':VimwikiListIncreaseLvlSubtree'), { desc = 'nuwiki: list indent subtree (deferred)' }, bufnr) + map('n', 'glr', deferred(':VimwikiRenumberList'), { desc = 'nuwiki: renumber list (deferred)' }, bufnr) + map('n', 'gLr', deferred(':VimwikiRenumberAllLists'), { desc = 'nuwiki: renumber all lists (deferred)' }, bufnr) + map('n', 'gl', deferred(':VimwikiRemoveSingleCB'), { desc = 'nuwiki: remove checkbox (deferred)' }, bufnr) + map('n', 'gL', deferred(':VimwikiRemoveCBInList'), { desc = 'nuwiki: remove all checkboxes (deferred)' }, bufnr) + map('n', 'o', open_below_with_bullet, { desc = 'nuwiki: open below + bullet' }, bufnr) + map('n', 'O', open_above_with_bullet, { desc = 'nuwiki: open above + bullet' }, bufnr) end - -- ===== Header level ===== - if mappings.header_nav ~= false then - -- `=`/`-` is the vimwiki default but collides with Vim's built-in - -- reformat operator. Use `g=` / `g-` instead to stay out of the way. - map('n', 'g=', cmd.heading_add_level, { desc = 'nuwiki: heading deeper' }, bufnr) - map('n', 'g-', cmd.heading_remove_level, { desc = 'nuwiki: heading shallower' }, bufnr) + -- ===== Header levels + nav ===== + if on('headers') then + -- vimwiki defaults — buffer-local so they don't clash globally. + map('n', '=', cmd.heading_add_level, { desc = 'nuwiki: heading deeper' }, bufnr) + map('n', '-', cmd.heading_remove_level, { desc = 'nuwiki: heading shallower' }, bufnr) + map('n', ']]', nav.next_header, { desc = 'nuwiki: next header' }, bufnr) + map('n', '[[', nav.prev_header, { desc = 'nuwiki: prev header' }, bufnr) + map('n', ']=', nav.next_sibling_header, { desc = 'nuwiki: next sibling header' }, bufnr) + map('n', '[=', nav.prev_sibling_header, { desc = 'nuwiki: prev sibling header' }, bufnr) + map('n', ']u', nav.parent_header, { desc = 'nuwiki: parent header' }, bufnr) + map('n', '[u', nav.parent_header, { desc = 'nuwiki: parent header' }, bufnr) end - -- ===== HTML export ===== - if mappings.html_export ~= false then - map('n', 'wh', cmd.export_current, { desc = 'nuwiki: export to HTML' }, bufnr) - map('n', 'whh', cmd.export_browse, { desc = 'nuwiki: export + browse' }, bufnr) - map('n', 'wha', cmd.export_all, { desc = 'nuwiki: export all' }, bufnr) + -- ===== Tables ===== + if on('table_editing') then + map('n', 'gqq', deferred(':VimwikiTableAlignQ'), { desc = 'nuwiki: table align (deferred)' }, bufnr) + map('n', 'gq1', deferred(':VimwikiTableAlignQ1'), { desc = 'nuwiki: table align row1 (deferred)' }, bufnr) + map('n', 'gww', deferred(':VimwikiTableAlignW'), { desc = 'nuwiki: table align wide (deferred)' }, bufnr) + map('n', 'gw1', deferred(':VimwikiTableAlignW1'), { desc = 'nuwiki: table align row1 wide (deferred)' }, bufnr) + map('n', '', deferred(':VimwikiTableMoveColumnLeft'), { desc = 'nuwiki: table col left (deferred)' }, bufnr) + map('n', '', deferred(':VimwikiTableMoveColumnRight'), { desc = 'nuwiki: table col right (deferred)' }, bufnr) + end + + -- ===== HTML ===== + if on('html_export') then + map('n', 'wh', cmd.export_current, { desc = 'nuwiki: export to HTML' }, bufnr) + map('n', 'whh', cmd.export_browse, { desc = 'nuwiki: export + browse' }, bufnr) + map('n', 'wha', cmd.export_all, { desc = 'nuwiki: export all' }, bufnr) end end