feat(client): add per-subgroup Vim mapping opt-out globals
CI / cargo fmt --check (push) Successful in 40s
CI / cargo clippy (push) Failing after 38s
CI / cargo test (push) Successful in 38s
CI / editor keymaps (push) Successful in 1m30s

The plain-Vim ftplugin only exposed a whole-layer
g:nuwiki_no_default_mappings gate, so Vim users could not drop a single
keymap group the way Neovim users can via mappings.<group> = false. Add
g:nuwiki_no_{wiki_prefix,links,lists,headers,table_editing,diary,
html_export,text_objects}_mappings, each wrapping its group block, to
reach parity with lua/nuwiki/keymaps.lua. Cover them with a dedicated
opt-out harness and document them in the README and help file.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-30 23:40:29 -03:00
parent 3a33d53c22
commit e9243f743e
5 changed files with 269 additions and 103 deletions
+15 -3
View File
@@ -257,6 +257,8 @@ let g:nuwiki_link_severity = 'warn' " 'off'|'hint'|'warn'|'error'
let g:nuwiki_no_default_mappings = 0 " set to 1 to skip the keymap layer
let g:nuwiki_no_folding = 0 " set to 1 to skip foldexpr setup
let g:nuwiki_mouse_mappings = 0 " set to 1 to enable mouse maps
" Drop individual keymap subgroups (mirror the Lua `mappings.<group>` toggles):
" g:nuwiki_no_{wiki_prefix,links,lists,headers,table_editing,diary,html_export,text_objects}_mappings
```
### Reference
@@ -337,7 +339,15 @@ For users configuring without Lua.
| `g:nuwiki_file_extension` | `'.wiki'` | wiki file extension |
| `g:nuwiki_log_level` | `'warn'` | `error` \| `warn` \| `info` \| `debug` |
| `g:nuwiki_link_severity` | `'warn'` | broken-link severity: `off` \| `hint` \| `warn` \| `error` |
| `g:nuwiki_no_default_mappings` | `0` | `1` skips the keymap layer |
| `g:nuwiki_no_default_mappings` | `0` | `1` skips the whole keymap layer |
| `g:nuwiki_no_wiki_prefix_mappings` | `0` | `1` skips the `<Leader>w*` group |
| `g:nuwiki_no_links_mappings` | `0` | `1` skips the link group (`<CR>`, `+`, …) |
| `g:nuwiki_no_lists_mappings` | `0` | `1` skips the list group (`<C-Space>`, `gl*`, `o`/`O`, …) |
| `g:nuwiki_no_headers_mappings` | `0` | `1` skips the header group (`=`, `-`, `]]`, …) |
| `g:nuwiki_no_table_editing_mappings` | `0` | `1` skips the table group (`gqq`, `<A-Left>`, insert `<Tab>`) |
| `g:nuwiki_no_diary_mappings` | `0` | `1` skips the diary nav group (`<C-Down>`, `<C-Up>`) |
| `g:nuwiki_no_html_export_mappings` | `0` | `1` skips the HTML export group (`<Leader>wh*`) |
| `g:nuwiki_no_text_objects_mappings` | `0` | `1` skips the text-object group (`ah`, `il`, …) |
| `g:nuwiki_no_folding` | `0` | `1` skips foldexpr setup |
| `g:nuwiki_mouse_mappings` | `0` | `1` enables mouse maps |
@@ -443,8 +453,10 @@ instead.
### Default keymaps
All buffer-local. Disable any group via `mappings.<group> = false` in
Neovim, or `let g:nuwiki_no_default_mappings = 1` in Vim.
All buffer-local. Disable the whole layer via `mappings.enabled = false` in
Neovim, or `let g:nuwiki_no_default_mappings = 1` in Vim. Drop a single group
via `mappings.<group> = false` in Neovim, or
`let g:nuwiki_no_<group>_mappings = 1` in Vim.
**Links** (normal mode)
@@ -0,0 +1,73 @@
" development/tests/test-keymaps-vim-optout.vim — driven by
" development/tests/test-keymaps-vim.sh.
"
" Per-subgroup mapping opt-out regression. The shell driver sets a subset
" of `g:nuwiki_no_<group>_mappings = 1` globals *before* the ftplugin loads,
" so this harness asserts that the disabled groups leave no buffer-local
" mappings while the still-enabled groups keep theirs. Mirrors the Lua-side
" subgroup toggles in lua/nuwiki/keymaps.lua.
let s:results = []
let s:pass = 0
let s:fail = 0
let s:out = $NUWIKI_KEYMAP_RESULTS
function! s:record(ok, name, detail) abort
let l:line = (a:ok ? 'PASS' : 'FAIL') . ' ' . a:name
if a:detail !=# ''
let l:line .= ' — ' . a:detail
endif
call add(s:results, l:line)
if a:ok
let s:pass += 1
else
let s:fail += 1
endif
endfunction
function! s:has_map(lhs, mode) abort
let l:d = maparg(a:lhs, a:mode, 0, 1)
return !empty(l:d) && get(l:d, 'buffer', 0)
endfunction
" Groups the driver disabled (g:nuwiki_no_<group>_mappings = 1): every
" representative LHS below must be ABSENT.
let s:disabled = [
\ ['wiki_prefix', '<Leader>ww', 'n'],
\ ['wiki_prefix', '<Leader>w<Leader>w', 'n'],
\ ['links', '<CR>', 'n'],
\ ['links', '+', 'n'],
\ ['links', '<Leader>wr', 'n'],
\ ['headers', '=', 'n'],
\ ['headers', ']]', 'n'],
\ ['headers', ']u', 'n'],
\ ['text_objects', 'ah', 'x'],
\ ['text_objects', 'il', 'o'],
\ ]
for s:e in s:disabled
let s:ok = !s:has_map(s:e[1], s:e[2])
call s:record(s:ok ? 1 : 0, 'optout.' . s:e[0] . '.' . s:e[2] . '.' . s:e[1],
\ s:ok ? '' : s:e[1] . ' (' . s:e[2] . ') still mapped despite opt-out')
endfor
" Groups left enabled: every representative LHS must still be PRESENT.
let s:enabled = [
\ ['lists', 'gln', 'n'],
\ ['lists', 'gnt', 'n'],
\ ['lists', '<C-Space>', 'n'],
\ ['diary', '<C-Down>', 'n'],
\ ['table_editing', 'gqq', 'n'],
\ ['table_editing', '<A-Left>', 'n'],
\ ['html_export', '<Leader>wh', 'n'],
\ ['html_export', '<Leader>wha', 'n'],
\ ]
for s:e in s:enabled
let s:ok = s:has_map(s:e[1], s:e[2])
call s:record(s:ok ? 1 : 0, 'kept.' . s:e[0] . '.' . s:e[2] . '.' . s:e[1],
\ s:ok ? '' : s:e[1] . ' (' . s:e[2] . ') unexpectedly absent')
endfor
call add(s:results, '')
call add(s:results, printf('SUMMARY: %d passed, %d failed', s:pass, s:fail))
call writefile(s:results, s:out)
execute (s:fail == 0 ? 'qall!' : 'cquit!')
+38 -1
View File
@@ -59,7 +59,44 @@ fi
cat "$RESULTS"
if grep -qE '^SUMMARY: [0-9]+ passed, 0 failed' "$RESULTS"; then
if ! grep -qE '^SUMMARY: [0-9]+ passed, 0 failed' "$RESULTS"; then
exit 1
fi
# ===== Per-subgroup opt-out run =====
# Fresh Vim instance with a subset of g:nuwiki_no_<group>_mappings set
# *before* the ftplugin loads, so we can assert the disabled groups drop
# their bindings while the rest survive.
VIMRC_OPTOUT="$TMP/vimrc-optout"
cat > "$VIMRC_OPTOUT" <<EOF
set nocompatible
let &runtimepath = '$REPO_ROOT' . ',' . &runtimepath
let g:nuwiki_no_wiki_prefix_mappings = 1
let g:nuwiki_no_links_mappings = 1
let g:nuwiki_no_headers_mappings = 1
let g:nuwiki_no_text_objects_mappings = 1
filetype plugin indent on
syntax enable
EOF
RESULTS_OPTOUT="$TMP/results-optout.txt"
log "running opt-out harness…"
NUWIKI_KEYMAP_RESULTS="$RESULTS_OPTOUT" \
timeout 30 vim -e -s -u "$VIMRC_OPTOUT" -c "edit $SEED" -c "source $REPO_ROOT/development/tests/test-keymaps-vim-optout.vim" \
</dev/null >"$TMP/vim-optout.log" 2>&1 || true
if [[ ! -f "$RESULTS_OPTOUT" ]]; then
echo 'keymap opt-out harness produced no output' >&2
echo '--- vim log ---' >&2
cat "$TMP/vim-optout.log" >&2 || true
exit 1
fi
cat "$RESULTS_OPTOUT"
if grep -qE '^SUMMARY: [0-9]+ passed, 0 failed' "$RESULTS_OPTOUT"; then
exit 0
fi
exit 1
+27 -2
View File
@@ -207,6 +207,28 @@ Vim-specific globals ~
When 1, the Vim path skips every buffer-local keymap. Equivalent to
`mappings.enabled = false` in Neovim.
*g:nuwiki_no_wiki_prefix_mappings*
*g:nuwiki_no_links_mappings*
*g:nuwiki_no_lists_mappings*
*g:nuwiki_no_headers_mappings*
*g:nuwiki_no_table_editing_mappings*
*g:nuwiki_no_diary_mappings*
*g:nuwiki_no_html_export_mappings*
*g:nuwiki_no_text_objects_mappings*
`g:nuwiki_no_<group>_mappings` 0
Per-subgroup opt-outs, each defaulting to 0. Setting one to 1 drops just
that group's buffer-local keymaps while leaving the rest in place; the
whole-layer |g:nuwiki_no_default_mappings| still wins over all of them.
These mirror the Lua-side `mappings.<group>` booleans. Valid groups:
`wiki_prefix` `<Leader>w*`
`links` `<CR>`, `<S-CR>`, `<Tab>`, `<BS>`, `+`, …
`lists` `<C-Space>`, `gl*`, `o`/`O`, insert-mode list edits
`headers` `=`, `-`, `]]`, `[[`, `]=`, `[=`, `]u`, `[u`
`table_editing` `gqq`, `<A-Left>`, `<A-Right>`, insert `<Tab>`
`diary` `<C-Down>`, `<C-Up>`
`html_export` `<Leader>wh`, `<Leader>whh`, `<Leader>wha`
`text_objects` `ah`, `ih`, `al`, `il`, `a\`, `i\`, `ac`, `ic`
*g:nuwiki_no_folding*
`g:nuwiki_no_folding` 0
When 1, the Vim path skips `foldexpr` / `foldmethod` setup.
@@ -378,8 +400,11 @@ Other ~
==============================================================================
6. KEYMAPS *nuwiki-keymaps*
Every keymap is buffer-local. Disable a group via `mappings.<group> =
false` in Neovim, or `let g:nuwiki_no_default_mappings = 1` in Vim.
Every keymap is buffer-local. Disable the whole layer via
`mappings.enabled = false` in Neovim, or `let g:nuwiki_no_default_mappings
= 1` in Vim. Drop a single group via `mappings.<group> = false` in Neovim,
or `let g:nuwiki_no_<group>_mappings = 1` in Vim (see
|g:nuwiki_no_links_mappings| for the group list).
Links (normal mode) ~
+24 -5
View File
@@ -171,11 +171,15 @@ if !has('nvim')
" ===== 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.
" Users can opt out of the whole layer with `g:nuwiki_no_default_mappings = 1`,
" or flip individual subgroups off via `g:nuwiki_no_<group>_mappings = 1`
" (group ∈ wiki_prefix, links, lists, headers, table_editing, diary,
" html_export, text_objects). These mirror the Lua-side `mappings.<group>`
" toggles in lua/nuwiki/keymaps.lua.
if !get(g:, 'nuwiki_no_default_mappings', 0)
" Wiki prefix
if !get(g:, 'nuwiki_no_wiki_prefix_mappings', 0)
nnoremap <silent><buffer> <Leader>ww :call nuwiki#commands#wiki_index(0)<CR>
nnoremap <silent><buffer> <Leader>wt :call nuwiki#commands#wiki_tab_index(0)<CR>
nnoremap <silent><buffer> <Leader>ws :call nuwiki#commands#wiki_ui_select()<CR>
@@ -185,8 +189,10 @@ if !has('nvim')
nnoremap <silent><buffer> <Leader>w<Leader>t :call nuwiki#commands#diary_tomorrow()<CR>
nnoremap <silent><buffer> <Leader>w<Leader>m :call nuwiki#commands#diary_tomorrow()<CR>
nnoremap <silent><buffer> <Leader>w<Leader>i :call nuwiki#commands#diary_generate_index()<CR>
endif
" Links
if !get(g:, 'nuwiki_no_links_mappings', 0)
nnoremap <silent><buffer> <CR> :call nuwiki#commands#follow_link_or_create()<CR>
nnoremap <silent><buffer> <S-CR> :split<CR>:call nuwiki#commands#follow_link_or_create()<CR>
nnoremap <silent><buffer> <C-CR> :vsplit<CR>:call nuwiki#commands#follow_link_or_create()<CR>
@@ -201,12 +207,16 @@ if !has('nvim')
nnoremap <silent><buffer> <Leader>wr :call nuwiki#commands#rename_file()<CR>
nnoremap <silent><buffer> <Leader>wc :call nuwiki#commands#colorize('')<CR>
xnoremap <silent><buffer> <Leader>wc :<C-u>call nuwiki#commands#colorize('')<CR>
endif
" Diary nav
if !get(g:, 'nuwiki_no_diary_mappings', 0)
nnoremap <silent><buffer> <C-Down> :call nuwiki#commands#diary_next()<CR>
nnoremap <silent><buffer> <C-Up> :call nuwiki#commands#diary_prev()<CR>
endif
" Lists
if !get(g:, 'nuwiki_no_lists_mappings', 0)
nnoremap <silent><buffer> <C-Space> :call nuwiki#commands#toggle_list_item()<CR>
xnoremap <silent><buffer> <C-Space> :<C-u>call nuwiki#commands#toggle_list_item()<CR>
nnoremap <silent><buffer> <C-@> :call nuwiki#commands#toggle_list_item()<CR>
@@ -238,10 +248,10 @@ if !has('nvim')
inoremap <silent><buffer> <C-L><C-K> <C-o>:call nuwiki#commands#list_cycle_symbol(-1)<CR>
inoremap <silent><buffer> <C-L><C-M> <C-o>:call nuwiki#commands#list_toggle_or_add_checkbox()<CR>
inoremap <silent><buffer><expr> <CR> nuwiki#commands#smart_return()
inoremap <silent><buffer><expr> <Tab> nuwiki#commands#smart_tab()
inoremap <silent><buffer><expr> <S-Tab> nuwiki#commands#smart_shift_tab()
endif
" Headers
if !get(g:, 'nuwiki_no_headers_mappings', 0)
nnoremap <silent><buffer> = :call nuwiki#commands#heading_add()<CR>
nnoremap <silent><buffer> - :call nuwiki#commands#heading_remove()<CR>
nnoremap <silent><buffer> ]] :call nuwiki#commands#next_header()<CR>
@@ -250,11 +260,14 @@ if !has('nvim')
nnoremap <silent><buffer> [= :call nuwiki#commands#prev_sibling_header()<CR>
nnoremap <silent><buffer> ]u :call nuwiki#commands#parent_header()<CR>
nnoremap <silent><buffer> [u :call nuwiki#commands#parent_header()<CR>
endif
" HTML export
if !get(g:, 'nuwiki_no_html_export_mappings', 0)
nnoremap <silent><buffer> <Leader>wh :call nuwiki#commands#export_current()<CR>
nnoremap <silent><buffer> <Leader>whh :call nuwiki#commands#export_browse()<CR>
nnoremap <silent><buffer> <Leader>wha :call nuwiki#commands#export_all()<CR>
endif
" Mouse (opt-in via g:nuwiki_mouse_mappings = 1).
if get(g:, 'nuwiki_mouse_mappings', 0)
@@ -265,13 +278,17 @@ if !has('nvim')
nnoremap <silent><buffer> <RightMouse> <C-o>
endif
" Tables
" Tables (and insert-mode table-cell navigation, same surface).
if !get(g:, 'nuwiki_no_table_editing_mappings', 0)
nnoremap <silent><buffer> gqq :call nuwiki#commands#table_align()<CR>
nnoremap <silent><buffer> gq1 :call nuwiki#commands#table_align()<CR>
nnoremap <silent><buffer> gww :call nuwiki#commands#table_align()<CR>
nnoremap <silent><buffer> gw1 :call nuwiki#commands#table_align()<CR>
nnoremap <silent><buffer> <A-Left> :call nuwiki#commands#table_move_left()<CR>
nnoremap <silent><buffer> <A-Right> :call nuwiki#commands#table_move_right()<CR>
inoremap <silent><buffer><expr> <Tab> nuwiki#commands#smart_tab()
inoremap <silent><buffer><expr> <S-Tab> nuwiki#commands#smart_shift_tab()
endif
" Folding — heading-block fold structure (matches lua/nuwiki/folding.lua).
" Opt-out via `let g:nuwiki_no_folding = 1` (mirrors the Lua-side
@@ -290,6 +307,7 @@ if !has('nvim')
" Text objects — vimwiki parity (matches lua/nuwiki/textobjects.lua).
" The `:<C-u>` clears any prefix count and the visual mode; the
" autoload function re-establishes the selection at the right range.
if !get(g:, 'nuwiki_no_text_objects_mappings', 0)
xnoremap <silent><buffer> ah :<C-u>call nuwiki#textobjects#heading(0, 0)<CR>
onoremap <silent><buffer> ah :<C-u>call nuwiki#textobjects#heading(0, 0)<CR>
xnoremap <silent><buffer> ih :<C-u>call nuwiki#textobjects#heading(1, 0)<CR>
@@ -311,6 +329,7 @@ if !has('nvim')
xnoremap <silent><buffer> ic :<C-u>call nuwiki#textobjects#table_column(1)<CR>
onoremap <silent><buffer> ic :<C-u>call nuwiki#textobjects#table_column(1)<CR>
endif
endif
finish
endif