feat(commands): command-line completion for Goto/Colorize/tag commands (P3)
CI / cargo fmt --check (push) Successful in 37s
CI / cargo clippy (push) Successful in 39s
CI / cargo test (push) Successful in 37s
CI / editor keymaps (push) Successful in 1m29s

Upstream attaches -complete= to several commands; nuwiki had none, so <Tab>
at the : line never completed page/tag/colour names. New
autoload/nuwiki/complete.vim adds pure client-side, synchronous completers
(config + filesystem, never the LSP — so they work under vim-lsp, coc and
Neovim), wired via -complete=customlist into all command contexts:

- nuwiki#complete#pages → VimwikiGoto/NuwikiGoto (glob wiki roots for page names)
- nuwiki#complete#colors → VimwikiColorize/NuwikiColorize (color_dic keys +
  colours already used in buffer color:… spans)
- nuwiki#complete#tags → VimwikiSearchTags/GenerateTagLinks/GenerateTags (+Nuwiki)
  (scan wiki files for :tag: runs — the tag index is server-side and a
  synchronous completer can't query it, so we read the same source it indexes)

Documented divergence: no file completer for VimwikiRenameFile — nuwiki
delegates renaming to the LSP rename request (no filename arg to complete),
unlike upstream which renames itself.

Tests: complete.{pages_globs_and_filters,tags_scans_wiki_files,
colors_from_buffer_spans} (test-keymaps-vim.vim). Both harnesses green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 20:40:22 +00:00
parent efea225240
commit 3b3d8ca782
4 changed files with 162 additions and 23 deletions
+86
View File
@@ -0,0 +1,86 @@
" autoload/nuwiki/complete.vim — command-line completion for the
" :Vimwiki*/:Nuwiki* commands (mirrors upstream vimwiki's -complete= specs).
"
" Pure client-side and synchronous: candidates come from config and the
" filesystem, never the LSP, so a `-complete=customlist,{fn}` attribute can
" call these directly under vim-lsp, coc, and Neovim alike.
"
" NOTE on files/RenameFile: upstream's :VimwikiRenameFile takes a new-name arg
" with file completion because upstream performs the rename itself. nuwiki
" delegates renaming to the LSP rename request (`:LspRename` / coc `rename`),
" which drives its own interactive prompt — there is no filename argument to
" complete, so no file completer is provided (documented divergence).
" Page-name completion (VimwikiGoto). Globs every configured wiki root for its
" `*<ext>` files and returns their root-relative names with the extension
" stripped, filtered by {arglead}. Mirrors upstream complete_links_raw.
function! nuwiki#complete#pages(arglead, cmdline, cursorpos) abort
let l:names = {}
for l:w in nuwiki#commands#wiki_list()
let l:root = substitute(get(l:w, 'root', ''), '/\+$', '', '')
if l:root ==# '' | continue | endif
let l:ext = get(l:w, 'ext', '.wiki')
for l:f in glob(l:root . '/**/*' . l:ext, 0, 1)
let l:rel = l:f[len(l:root) + 1 :]
if l:rel ==# '' | continue | endif
let l:rel = l:rel[: -len(l:ext) - 1]
let l:names[l:rel] = 1
endfor
endfor
return filter(sort(keys(l:names)), 'stridx(v:val, a:arglead) == 0')
endfunction
" Colour completion (VimwikiColorize) — keys from any configured `color_dic`
" (per-wiki `g:nuwiki_wikis[].color_dic` or the scalar `g:nuwiki_color_dic`)
" plus colours already used in `color:NAME` spans in the current buffer.
function! nuwiki#complete#colors(arglead, cmdline, cursorpos) abort
let l:set = {}
for l:w in get(g:, 'nuwiki_wikis', [])
for l:k in keys(get(l:w, 'color_dic', {}))
let l:set[l:k] = 1
endfor
endfor
for l:k in keys(get(g:, 'nuwiki_color_dic', {}))
let l:set[l:k] = 1
endfor
for l:line in getline(1, '$')
let l:start = 0
while 1
let l:m = matchstrpos(l:line, 'color:\s*\zs[^"'';)]\+', l:start)
if l:m[1] < 0 | break | endif
let l:set[trim(l:m[0])] = 1
let l:start = l:m[2]
endwhile
endfor
return filter(sort(keys(l:set)), 'stridx(v:val, a:arglead) == 0')
endfunction
" Tag completion (VimwikiSearchTags / VimwikiGenerateTagLinks). The tag index
" lives in the LSP server, which a synchronous completer can't query, so we
" harvest `:tag:` tokens by scanning the configured wikis' files directly —
" the same source the server indexes. Runs only on <Tab> at the `:` line.
function! nuwiki#complete#tags(arglead, cmdline, cursorpos) abort
let l:set = {}
for l:w in nuwiki#commands#wiki_list()
let l:root = substitute(get(l:w, 'root', ''), '/\+$', '', '')
if l:root ==# '' | continue | endif
let l:ext = get(l:w, 'ext', '.wiki')
for l:f in glob(l:root . '/**/*' . l:ext, 0, 1)
for l:line in readfile(l:f)
" Tags come in colon-delimited runs like `:foo:bar:` (no spaces).
" Match a whole run, then split out each tag (adjacent tags share a
" colon, so a per-tag scan would miss every other one).
let l:start = 0
while 1
let l:m = matchstrpos(l:line, ':\%([^:[:space:]]\+:\)\+', l:start)
if l:m[1] < 0 | break | endif
for l:t in split(l:m[0], ':')
if l:t !=# '' | let l:set[l:t] = 1 | endif
endfor
let l:start = l:m[2]
endwhile
endfor
endfor
endfor
return filter(sort(keys(l:set)), 'stridx(v:val, a:arglead) == 0')
endfunction
+38
View File
@@ -689,6 +689,44 @@ call s:record(
\ 'cmd.VimwikiSplitLink_accepts_args',
\ 'err=' . s:sl_err)
" ===== Command-line completion (nuwiki#complete#*) =====
" Pure client-side completers backing the -complete= specs on VimwikiGoto
" (pages), VimwikiColorize (colours), and the tag commands. Driven directly
" (no LSP) against a throwaway wiki dir.
let s:cw = tempname()
call mkdir(s:cw, 'p')
call writefile(['= alpha =', 'tagged :foo:bar:'], s:cw . '/alpha.wiki')
call writefile(['= beta ='], s:cw . '/beta.wiki')
let s:save_root = get(g:, 'nuwiki_wiki_root', v:null)
let g:nuwiki_wiki_root = s:cw
let s:pg = nuwiki#complete#pages('a', '', 0)
call s:record(
\ (index(s:pg, 'alpha') >= 0 && index(s:pg, 'beta') < 0) ? 1 : 0,
\ 'complete.pages_globs_and_filters',
\ 'got ' . string(s:pg))
let s:tg = nuwiki#complete#tags('', '', 0)
call s:record(
\ (index(s:tg, 'foo') >= 0 && index(s:tg, 'bar') >= 0) ? 1 : 0,
\ 'complete.tags_scans_wiki_files',
\ 'got ' . string(s:tg))
if s:save_root is v:null
unlet g:nuwiki_wiki_root
else
let g:nuwiki_wiki_root = s:save_root
endif
call delete(s:cw, 'rf')
" Colours come from `color:NAME` spans already in the buffer.
call s:set_buf(['<span style="color:tomato">x</span> plain'])
let s:cl = nuwiki#complete#colors('t', '', 0)
call s:record(
\ index(s:cl, 'tomato') >= 0 ? 1 : 0,
\ 'complete.colors_from_buffer_spans',
\ 'got ' . string(s:cl))
" ===== Wrap up =====
call add(s:results, '')
+20 -5
View File
@@ -270,11 +270,26 @@ fix site.
half is done: all four defs now use upstream's `-count=0` instead of bare
`-count`; `wiki_index`/`wiki_tab_index` already map the count to the wiki
number.)_
- [ ] **No `-complete=` specs** _(new)_ — upstream attaches command-line completion
to `VimwikiGoto` (links), `VimwikiRenameFile` (files), `VimwikiColorize`
(colours), and the tag commands. nuwiki defines none, so `<Tab>` completion of
page / tag / colour names at the `:` line is unavailable. _Fix:_
`ftplugin/vimwiki.vim` (both branches) — VimL completers or LSP-backed.
- [x] **`-complete=` specs** _(new; done 2026-05-31 command-attribute pass)_
upstream attaches command-line completion to several commands; nuwiki had
none, so `<Tab>` at the `:` line never completed page / tag / colour names.
_Fix:_ new `autoload/nuwiki/complete.vim` with pure client-side, synchronous
completers (config + filesystem, never the LSP, so they work under vim-lsp,
coc and Neovim alike), wired via `-complete=customlist,…` into all branches:
- `nuwiki#complete#pages``VimwikiGoto`/`NuwikiGoto` (globs every wiki
root's `*<ext>` files → root-relative page names).
- `nuwiki#complete#colors``VimwikiColorize`/`NuwikiColorize` (keys from a
configured `color_dic` + colours already used in buffer `color:…` spans).
- `nuwiki#complete#tags``VimwikiSearchTags`/`GenerateTagLinks`/
`GenerateTags` (+ Nuwiki forms) — scans the wikis' files for `:tag:` runs
(the tag index lives in the server, which a synchronous completer can't
query; the file scan reads the same source the server indexes).
**Divergence (documented):** no file completer for `VimwikiRenameFile`
upstream completes a new-name arg because it renames itself, but nuwiki
delegates renaming to the LSP rename request (`:LspRename`/coc), which drives
its own prompt and takes no filename argument. Covered by
`complete.pages_globs_and_filters`, `complete.tags_scans_wiki_files`,
`complete.colors_from_buffer_spans` (`test-keymaps-vim.vim`).
- [x] **Minor command-attribute gaps** _(new)_ — all now real, not cosmetic:
`VimwikiNormalizeLink` is `-nargs=?` and with `1` wraps the `'<`/`'>` selection
(`wrap_visual_as_wikilink`, both clients; the `x`-mode `+` passes it too);
+18 -18
View File
@@ -69,7 +69,7 @@ if !has('nvim')
command! -buffer VimwikiBaddLink call nuwiki#commands#badd_link()
command! -buffer -nargs=1 VimwikiSearch lvimgrep /<args>/ **
command! -buffer -nargs=1 VWS lvimgrep /<args>/ **
command! -buffer -nargs=1 VimwikiGoto call nuwiki#commands#wiki_goto_page(<q-args>)
command! -buffer -nargs=1 -complete=customlist,nuwiki#complete#pages VimwikiGoto call nuwiki#commands#wiki_goto_page(<q-args>)
command! -buffer VimwikiDeleteFile call nuwiki#commands#delete_file()
command! -buffer VimwikiDeleteLink call nuwiki#commands#delete_file()
@@ -105,15 +105,15 @@ if !has('nvim')
command! -buffer VimwikiFindOrphans call nuwiki#commands#find_orphans()
command! -buffer -bang VimwikiRebuildTags call nuwiki#commands#tags_rebuild(<bang>0)
command! -buffer -nargs=? VimwikiSearchTags call nuwiki#commands#tags_search(<q-args>)
command! -buffer -nargs=? VimwikiGenerateTagLinks call nuwiki#commands#tags_generate_links(<q-args>)
command! -buffer -nargs=? VimwikiGenerateTags call nuwiki#commands#tags_generate_links(<q-args>)
command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags VimwikiSearchTags call nuwiki#commands#tags_search(<q-args>)
command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags VimwikiGenerateTagLinks call nuwiki#commands#tags_generate_links(<q-args>)
command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags VimwikiGenerateTags call nuwiki#commands#tags_generate_links(<q-args>)
" Tables, colorize, and clipboard paste.
command! -buffer -nargs=* VimwikiTable call nuwiki#commands#table_insert(<f-args>)
command! -buffer VimwikiTableMoveColumnLeft call nuwiki#commands#table_move_column_left()
command! -buffer VimwikiTableMoveColumnRight call nuwiki#commands#table_move_column_right()
command! -buffer -nargs=* -range VimwikiColorize call nuwiki#commands#colorize(<q-args>, <range>)
command! -buffer -nargs=* -range -complete=customlist,nuwiki#complete#colors VimwikiColorize call nuwiki#commands#colorize(<q-args>, <range>)
command! -buffer VimwikiPasteLink call nuwiki#commands#paste_link()
command! -buffer VimwikiPasteUrl call nuwiki#commands#paste_url()
command! -buffer VimwikiCatUrl call nuwiki#commands#cat_url()
@@ -149,9 +149,9 @@ if !has('nvim')
command! -buffer -range NuwikiCheckLinks call nuwiki#commands#check_links(<range>, <line1>, <line2>)
command! -buffer NuwikiFindOrphans call nuwiki#commands#find_orphans()
command! -buffer -bang NuwikiRebuildTags call nuwiki#commands#tags_rebuild(<bang>0)
command! -buffer -nargs=? NuwikiSearchTags call nuwiki#commands#tags_search(<q-args>)
command! -buffer -nargs=? NuwikiGenerateTagLinks call nuwiki#commands#tags_generate_links(<q-args>)
command! -buffer -nargs=1 NuwikiGoto call nuwiki#commands#wiki_goto_page(<q-args>)
command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags NuwikiSearchTags call nuwiki#commands#tags_search(<q-args>)
command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags NuwikiGenerateTagLinks call nuwiki#commands#tags_generate_links(<q-args>)
command! -buffer -nargs=1 -complete=customlist,nuwiki#complete#pages NuwikiGoto call nuwiki#commands#wiki_goto_page(<q-args>)
" Canonical :Nuwiki* names that mirror the documented surface.
" The older :Nuwiki* spellings above stay defined for back-compat.
@@ -185,7 +185,7 @@ if !has('nvim')
command! -buffer -nargs=* NuwikiTable call nuwiki#commands#table_insert(<f-args>)
command! -buffer NuwikiTableMoveColumnLeft call nuwiki#commands#table_move_column_left()
command! -buffer NuwikiTableMoveColumnRight call nuwiki#commands#table_move_column_right()
command! -buffer -nargs=* -range NuwikiColorize call nuwiki#commands#colorize(<q-args>, <range>)
command! -buffer -nargs=* -range -complete=customlist,nuwiki#complete#colors NuwikiColorize call nuwiki#commands#colorize(<q-args>, <range>)
command! -buffer NuwikiPasteLink call nuwiki#commands#paste_link()
command! -buffer NuwikiPasteUrl call nuwiki#commands#paste_url()
command! -buffer NuwikiCatUrl call nuwiki#commands#cat_url()
@@ -412,7 +412,7 @@ command! -buffer VimwikiTabDropLink lua require('nuwiki.commands').follow
command! -buffer VimwikiNextLink lua require('nuwiki.commands').link_next()
command! -buffer VimwikiPrevLink lua require('nuwiki.commands').link_prev()
command! -buffer VimwikiBaddLink lua require('nuwiki.commands').badd_link()
command! -buffer -nargs=1 VimwikiGoto lua require('nuwiki.commands').wiki_goto_page(<q-args>)
command! -buffer -nargs=1 -complete=customlist,nuwiki#complete#pages VimwikiGoto lua require('nuwiki.commands').wiki_goto_page(<q-args>)
command! -buffer VimwikiBacklinks lua vim.lsp.buf.references()
command! -buffer VWB lua vim.lsp.buf.references()
command! -buffer -nargs=1 VimwikiSearch lvimgrep /<args>/ **
@@ -453,15 +453,15 @@ command! -buffer -range VimwikiCheckLinks lua require('nuwiki.commands').
command! -buffer VimwikiFindOrphans lua require('nuwiki.commands').find_orphans()
command! -buffer -bang VimwikiRebuildTags lua require('nuwiki.commands').tags_rebuild('<bang>' == '!')
command! -buffer -nargs=? VimwikiSearchTags lua require('nuwiki.commands').tags_search(<q-args>)
command! -buffer -nargs=? VimwikiGenerateTagLinks lua require('nuwiki.commands').tags_generate_links(<q-args>)
command! -buffer -nargs=? VimwikiGenerateTags lua require('nuwiki.commands').tags_generate_links(<q-args>)
command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags VimwikiSearchTags lua require('nuwiki.commands').tags_search(<q-args>)
command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags VimwikiGenerateTagLinks lua require('nuwiki.commands').tags_generate_links(<q-args>)
command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags VimwikiGenerateTags lua require('nuwiki.commands').tags_generate_links(<q-args>)
" Tables, colorize, and clipboard paste.
command! -buffer -nargs=* VimwikiTable lua require('nuwiki.commands').table_insert(<f-args>)
command! -buffer VimwikiTableMoveColumnLeft lua require('nuwiki.commands').table_move_column_left()
command! -buffer VimwikiTableMoveColumnRight lua require('nuwiki.commands').table_move_column_right()
command! -buffer -nargs=* -range VimwikiColorize lua require('nuwiki.commands').colorize(<q-args>, <range> > 0)
command! -buffer -nargs=* -range -complete=customlist,nuwiki#complete#colors VimwikiColorize lua require('nuwiki.commands').colorize(<q-args>, <range> > 0)
command! -buffer VimwikiPasteLink lua require('nuwiki.commands').paste_link()
command! -buffer VimwikiPasteUrl lua require('nuwiki.commands').paste_url()
command! -buffer VimwikiCatUrl lua require('nuwiki.commands').cat_url()
@@ -493,9 +493,9 @@ command! -buffer NuwikiGenerateLinks lua require('nuwiki.commands').
command! -buffer -range NuwikiCheckLinks lua require('nuwiki.commands').check_links(<range>, <line1>, <line2>)
command! -buffer NuwikiFindOrphans lua require('nuwiki.commands').find_orphans()
command! -buffer -bang NuwikiRebuildTags lua require('nuwiki.commands').tags_rebuild('<bang>' == '!')
command! -buffer -nargs=? NuwikiSearchTags lua require('nuwiki.commands').tags_search(<q-args>)
command! -buffer -nargs=? NuwikiGenerateTagLinks lua require('nuwiki.commands').tags_generate_links(<q-args>)
command! -buffer -nargs=1 NuwikiGoto lua require('nuwiki.commands').wiki_goto_page(<q-args>)
command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags NuwikiSearchTags lua require('nuwiki.commands').tags_search(<q-args>)
command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags NuwikiGenerateTagLinks lua require('nuwiki.commands').tags_generate_links(<q-args>)
command! -buffer -nargs=1 -complete=customlist,nuwiki#complete#pages NuwikiGoto lua require('nuwiki.commands').wiki_goto_page(<q-args>)
" Canonical :Nuwiki* names that mirror the documented surface.
" The older :Nuwiki* spellings above stay defined for back-compat.
@@ -533,7 +533,7 @@ command! -buffer NuwikiTableAlign lua require('nuwiki.command
command! -buffer -nargs=* NuwikiTable lua require('nuwiki.commands').table_insert(<f-args>)
command! -buffer NuwikiTableMoveColumnLeft lua require('nuwiki.commands').table_move_column_left()
command! -buffer NuwikiTableMoveColumnRight lua require('nuwiki.commands').table_move_column_right()
command! -buffer -nargs=* -range NuwikiColorize lua require('nuwiki.commands').colorize(<q-args>, <range> > 0)
command! -buffer -nargs=* -range -complete=customlist,nuwiki#complete#colors NuwikiColorize lua require('nuwiki.commands').colorize(<q-args>, <range> > 0)
command! -buffer NuwikiPasteLink lua require('nuwiki.commands').paste_link()
command! -buffer NuwikiPasteUrl lua require('nuwiki.commands').paste_url()
command! -buffer NuwikiCatUrl lua require('nuwiki.commands').cat_url()