From 3b3d8ca782cadcc69147483fc4b51dd8867eae86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Fr=C3=B3es=20Franco?= Date: Sun, 31 May 2026 20:40:22 +0000 Subject: [PATCH] feat(commands): command-line completion for Goto/Colorize/tag commands (P3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream attaches -complete= to several commands; nuwiki had none, so 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) --- autoload/nuwiki/complete.vim | 86 ++++++++++++++++++++++++++ development/tests/test-keymaps-vim.vim | 38 ++++++++++++ development/vimwiki-gap.md | 25 ++++++-- ftplugin/vimwiki.vim | 36 +++++------ 4 files changed, 162 insertions(+), 23 deletions(-) create mode 100644 autoload/nuwiki/complete.vim diff --git a/autoload/nuwiki/complete.vim b/autoload/nuwiki/complete.vim new file mode 100644 index 0000000..dd28b01 --- /dev/null +++ b/autoload/nuwiki/complete.vim @@ -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 +" `*` 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 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 diff --git a/development/tests/test-keymaps-vim.vim b/development/tests/test-keymaps-vim.vim index 2d5bcdc..771f37d 100644 --- a/development/tests/test-keymaps-vim.vim +++ b/development/tests/test-keymaps-vim.vim @@ -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(['x 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, '') diff --git a/development/vimwiki-gap.md b/development/vimwiki-gap.md index c5422c9..124108b 100644 --- a/development/vimwiki-gap.md +++ b/development/vimwiki-gap.md @@ -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 `` 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 `` 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 `*` 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); diff --git a/ftplugin/vimwiki.vim b/ftplugin/vimwiki.vim index 7e01c0e..348bf0d 100644 --- a/ftplugin/vimwiki.vim +++ b/ftplugin/vimwiki.vim @@ -69,7 +69,7 @@ if !has('nvim') command! -buffer VimwikiBaddLink call nuwiki#commands#badd_link() command! -buffer -nargs=1 VimwikiSearch lvimgrep // ** command! -buffer -nargs=1 VWS lvimgrep // ** - command! -buffer -nargs=1 VimwikiGoto call nuwiki#commands#wiki_goto_page() + command! -buffer -nargs=1 -complete=customlist,nuwiki#complete#pages VimwikiGoto call nuwiki#commands#wiki_goto_page() 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(0) - command! -buffer -nargs=? VimwikiSearchTags call nuwiki#commands#tags_search() - command! -buffer -nargs=? VimwikiGenerateTagLinks call nuwiki#commands#tags_generate_links() - command! -buffer -nargs=? VimwikiGenerateTags call nuwiki#commands#tags_generate_links() + command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags VimwikiSearchTags call nuwiki#commands#tags_search() + command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags VimwikiGenerateTagLinks call nuwiki#commands#tags_generate_links() + command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags VimwikiGenerateTags call nuwiki#commands#tags_generate_links() " Tables, colorize, and clipboard paste. command! -buffer -nargs=* VimwikiTable call nuwiki#commands#table_insert() 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(, ) + command! -buffer -nargs=* -range -complete=customlist,nuwiki#complete#colors VimwikiColorize call nuwiki#commands#colorize(, ) 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(, , ) command! -buffer NuwikiFindOrphans call nuwiki#commands#find_orphans() command! -buffer -bang NuwikiRebuildTags call nuwiki#commands#tags_rebuild(0) - command! -buffer -nargs=? NuwikiSearchTags call nuwiki#commands#tags_search() - command! -buffer -nargs=? NuwikiGenerateTagLinks call nuwiki#commands#tags_generate_links() - command! -buffer -nargs=1 NuwikiGoto call nuwiki#commands#wiki_goto_page() + command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags NuwikiSearchTags call nuwiki#commands#tags_search() + command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags NuwikiGenerateTagLinks call nuwiki#commands#tags_generate_links() + command! -buffer -nargs=1 -complete=customlist,nuwiki#complete#pages NuwikiGoto call nuwiki#commands#wiki_goto_page() " 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() 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(, ) + command! -buffer -nargs=* -range -complete=customlist,nuwiki#complete#colors NuwikiColorize call nuwiki#commands#colorize(, ) 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() +command! -buffer -nargs=1 -complete=customlist,nuwiki#complete#pages VimwikiGoto lua require('nuwiki.commands').wiki_goto_page() command! -buffer VimwikiBacklinks lua vim.lsp.buf.references() command! -buffer VWB lua vim.lsp.buf.references() command! -buffer -nargs=1 VimwikiSearch lvimgrep // ** @@ -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('' == '!') -command! -buffer -nargs=? VimwikiSearchTags lua require('nuwiki.commands').tags_search() -command! -buffer -nargs=? VimwikiGenerateTagLinks lua require('nuwiki.commands').tags_generate_links() -command! -buffer -nargs=? VimwikiGenerateTags lua require('nuwiki.commands').tags_generate_links() +command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags VimwikiSearchTags lua require('nuwiki.commands').tags_search() +command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags VimwikiGenerateTagLinks lua require('nuwiki.commands').tags_generate_links() +command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags VimwikiGenerateTags lua require('nuwiki.commands').tags_generate_links() " Tables, colorize, and clipboard paste. command! -buffer -nargs=* VimwikiTable lua require('nuwiki.commands').table_insert() 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(, > 0) +command! -buffer -nargs=* -range -complete=customlist,nuwiki#complete#colors VimwikiColorize lua require('nuwiki.commands').colorize(, > 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(, , ) command! -buffer NuwikiFindOrphans lua require('nuwiki.commands').find_orphans() command! -buffer -bang NuwikiRebuildTags lua require('nuwiki.commands').tags_rebuild('' == '!') -command! -buffer -nargs=? NuwikiSearchTags lua require('nuwiki.commands').tags_search() -command! -buffer -nargs=? NuwikiGenerateTagLinks lua require('nuwiki.commands').tags_generate_links() -command! -buffer -nargs=1 NuwikiGoto lua require('nuwiki.commands').wiki_goto_page() +command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags NuwikiSearchTags lua require('nuwiki.commands').tags_search() +command! -buffer -nargs=? -complete=customlist,nuwiki#complete#tags NuwikiGenerateTagLinks lua require('nuwiki.commands').tags_generate_links() +command! -buffer -nargs=1 -complete=customlist,nuwiki#complete#pages NuwikiGoto lua require('nuwiki.commands').wiki_goto_page() " 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() 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(, > 0) +command! -buffer -nargs=* -range -complete=customlist,nuwiki#complete#colors NuwikiColorize lua require('nuwiki.commands').colorize(, > 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()