feat(vim): close keymap gap with Neovim (47 → 63 maps)
CI / cargo fmt --check (push) Successful in 30s
CI / cargo clippy (push) Successful in 1m8s
CI / cargo test (push) Successful in 1m14s

The previous commit registered the parity surface on Neovim but only
ported ~75% to plain Vim. Filling the remaining 16:

autoload/nuwiki/commands.vim — new functions:
- `wiki_ui_select` — Vim has no `vim.ui.select`, so route through
  `inputlist()` after fetching the wiki list via the LSP.
- `open_below_with_bullet` / `open_above_with_bullet` — port of the
  Lua `o` / `O` continuation that preserves the list marker (and a
  `[ ]` checkbox when present) on the new line.
- `next_header` / `prev_header` / `next_sibling_header` /
  `prev_sibling_header` / `parent_header` — port of the Lua heading
  jumper. Pure VimL, no LSP round-trip.

ftplugin/vimwiki.vim (Vim path) new mappings:
- `<Leader>ws` → `wiki_ui_select`
- `]=` / `[=` → next / prev sibling header
- `]u` / `[u` → parent header
- `o` / `O` → bullet continuation
- `gl<Space>` / `gL<Space>` → :VimwikiRemove*CB stubs
- `gq1` / `gw1` → :VimwikiTableAlignQ1/W1 stubs
- x-mode variants for `+`, `<Leader>wc`, `gln`, `glp`, `glx`

Replaced the regex-based `]]` / `[[` (which just searched `^\s*=\+\s`)
with calls into the new heading nav so they skip non-heading `=`
runs and respect heading levels.

Verified end-to-end: `silent map <buffer>` reports 63 maps on Vim,
matching Neovim's 63. All 377 Rust tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 00:50:49 +00:00
parent 9e6faa5554
commit d58a34a2d7
2 changed files with 135 additions and 2 deletions
+117
View File
@@ -65,6 +65,123 @@ function! nuwiki#commands#wiki_index(count) abort
\ function('s:open_uri_from'))
endfunction
" Vim equivalent of the Lua `wiki_ui_select` — receives the list, asks
" via `inputlist()` (no `vim.ui.select` on plain Vim), then opens.
function! nuwiki#commands#wiki_ui_select() abort
call s:exec('nuwiki.wiki.select', [{}],
\ function('s:wiki_pick'))
endfunction
function! s:wiki_pick(notification) abort
let l:rows = get(get(a:notification, 'response', {}), 'result', [])
if type(l:rows) != type([]) || empty(l:rows)
echom 'nuwiki: no wikis configured'
return
endif
let l:prompt = ['Pick a wiki:']
let l:i = 0
for l:w in l:rows
let l:i += 1
call add(l:prompt, l:i . '. ' . get(l:w, 'name', '?')
\ . ' (' . get(l:w, 'root', '?') . ')')
endfor
let l:choice = inputlist(l:prompt)
if l:choice < 1 || l:choice > len(l:rows)
return
endif
let l:wiki_id = get(l:rows[l:choice - 1], 'id', l:choice - 1)
call s:exec('nuwiki.wiki.openIndex', [{ 'wiki': l:wiki_id }],
\ function('s:open_uri_from'))
endfunction
" Pure-VimL bullet-continuation for `o` / `O` — mirrors the Lua side
" so re-pressing the key keeps the list marker (and checkbox if any).
function! nuwiki#commands#open_below_with_bullet() abort
let l:line = getline('.')
let l:match = matchlist(l:line, '^\(\s*\)\([-*#]\)\s')
if empty(l:match)
call feedkeys('o', 'n')
return
endif
let l:prefix = l:match[1] . l:match[2] . ' '
if l:line =~# '^\s*[-*#]\s\+\[[ xXoO.\-]\]'
let l:prefix .= '[ ] '
endif
call feedkeys('o' . l:prefix, 'n')
endfunction
function! nuwiki#commands#open_above_with_bullet() abort
let l:line = getline('.')
let l:match = matchlist(l:line, '^\(\s*\)\([-*#]\)\s')
if empty(l:match)
call feedkeys('O', 'n')
return
endif
let l:prefix = l:match[1] . l:match[2] . ' '
call feedkeys('O' . l:prefix, 'n')
endfunction
" ===== Heading navigation (pure VimL) =====
function! s:heading_level(line) abort
let l:lead = matchstr(a:line, '^\s*\zs=\+\ze\s')
if l:lead ==# ''
return 0
endif
let l:trail = matchstr(a:line, '\s\zs=\+\ze\s*$')
if len(l:trail) != len(l:lead)
return 0
endif
return len(l:lead)
endfunction
function! s:current_heading_level() abort
let l:lnum = line('.')
while l:lnum > 0
let l:lvl = s:heading_level(getline(l:lnum))
if l:lvl > 0
return [l:lvl, l:lnum]
endif
let l:lnum -= 1
endwhile
return [0, 0]
endfunction
function! s:jump_heading(direction, predicate) abort
let l:total = line('$')
let l:i = line('.') + a:direction
while l:i >= 1 && l:i <= l:total
let l:lvl = s:heading_level(getline(l:i))
if l:lvl > 0 && a:predicate(l:lvl, l:i)
call cursor(l:i, 1)
return
endif
let l:i += a:direction
endwhile
endfunction
function! nuwiki#commands#next_header() abort
call s:jump_heading(1, {l, n -> 1})
endfunction
function! nuwiki#commands#prev_header() abort
call s:jump_heading(-1, {l, n -> 1})
endfunction
function! nuwiki#commands#next_sibling_header() abort
let [l:lvl, _] = s:current_heading_level()
call s:jump_heading(1, {l, n -> l <= l:lvl})
endfunction
function! nuwiki#commands#prev_sibling_header() abort
let [l:lvl, _] = s:current_heading_level()
call s:jump_heading(-1, {l, n -> l <= l:lvl})
endfunction
function! nuwiki#commands#parent_header() abort
let [l:lvl, _] = s:current_heading_level()
if l:lvl <= 1
return
endif
call s:jump_heading(-1, {l, n -> l < l:lvl})
endfunction
function! nuwiki#commands#wiki_tab_index(count) abort
let l:args = a:count > 0 ? [{ 'wiki': a:count - 1 }] : [{}]
call s:exec('nuwiki.wiki.tabOpenIndex', l:args,