fix(vim): buffer commands honour g:vimwiki_list (<Leader>ww opened ~/vimwiki)
CI / cargo fmt --check (push) Successful in 33s
CI / cargo clippy (push) Successful in 33s
CI / cargo test (push) Successful in 41s
CI / editor keymaps (push) Successful in 1m23s

The g:vimwiki_* drop-in only fed the LSP payload (lsp.vim s:settings).
The buffer-side commands — <Leader>ww (wiki_index), :VimwikiSearch,
auto_chdir, diary, completion — resolve the wiki list independently via
s:wiki_cfg / nuwiki#commands#wiki_list, which read only g:nuwiki_wikis /
g:nuwiki_wiki_root. So with a g:vimwiki_list config the server knew the
wikis but <Leader>ww fell back to the default ~/vimwiki and opened an
empty index.

Extract the resolution into a single source of truth,
autoload/nuwiki/config.vim (nuwiki#config#wikis / #scalar_globals), which
resolves g:nuwiki_wikis or an upstream g:vimwiki_list and folds the global
scalar defaults. lsp.vim (payload), commands.vim, and diary.vim all route
through it — also dedups the s:wiki_cfg copy that lived in both
commands.vim and diary.vim.

Regression guard in test-vimwiki-compat-vim: nuwiki#commands#wiki_list()
must resolve the g:vimwiki_list roots (21 checks). All Vim harnesses +
config-parity goldens green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 00:40:48 +00:00
parent 346e8b0de6
commit 9d6d28a1b6
5 changed files with 137 additions and 131 deletions
+12 -12
View File
@@ -588,14 +588,13 @@ endfunction
" open their wiki from any buffer. Files are opened directly from config;
" the LSP auto-starts via the FileType autocmd once the buffer loads.
" Return a dict with {root, ext, idx, diary_rel, diary_idx} for wiki #n
" (0-based). Prefers g:nuwiki_wikis[n]; falls back to scalar g:nuwiki_*
" vars and finally to built-in defaults.
" Return a dict with {root, ext, idx, diary_rel, diary_idx, space} for wiki #n
" (0-based). Resolves through nuwiki#config#wikis() — so g:nuwiki_wikis AND an
" upstream g:vimwiki_list both work — and falls back to the scalar g:nuwiki_*
" vars (single-wiki shorthand) and finally built-in defaults.
function! s:wiki_cfg(n) abort
let l:w = {}
if exists('g:nuwiki_wikis') && len(g:nuwiki_wikis) > a:n
let l:w = g:nuwiki_wikis[a:n]
endif
let l:wikis = nuwiki#config#wikis()
let l:w = (a:n >= 0 && a:n < len(l:wikis)) ? l:wikis[a:n] : {}
let l:root = expand(get(l:w, 'root',
\ get(g:, 'nuwiki_wiki_root', '~/vimwiki')))
let l:ext = get(l:w, 'file_extension',
@@ -612,14 +611,15 @@ function! s:wiki_cfg(n) abort
endfunction
" Build the list of configured wikis as {name, root, ext, idx, diary_rel,
" diary_idx} dicts, straight from config — no LSP round-trip. Drives the wiki
" picker so it works before any wiki buffer (and therefore the server) exists.
" Falls back to a single entry from the scalar `g:nuwiki_wiki_root` config.
" diary_idx, space} dicts, straight from config — no LSP round-trip. Drives the
" wiki picker so it works before any wiki buffer (and therefore the server)
" exists. Falls back to a single entry from the scalar `g:nuwiki_wiki_root`.
function! nuwiki#commands#wiki_list() abort
let l:wikis = nuwiki#config#wikis()
let l:list = []
if exists('g:nuwiki_wikis') && !empty(g:nuwiki_wikis)
if !empty(l:wikis)
let l:n = 0
for l:w in g:nuwiki_wikis
for l:w in l:wikis
let l:c = s:wiki_cfg(l:n)
let l:c['name'] = get(l:w, 'name', fnamemodify(l:c.root, ':t'))
call add(l:list, l:c)
+103
View File
@@ -0,0 +1,103 @@
" autoload/nuwiki/config.vim — single source of truth for the configured wiki
" list, shared by the LSP payload (lsp.vim) and the buffer-side commands
" (commands.vim, diary.vim, complete.vim).
"
" Resolves wikis from g:nuwiki_wikis (native) or, when that's unset, an upstream
" g:vimwiki_list (drop-in compatibility), translating upstream key names and
" folding the global scalar defaults (g:vimwiki_* / g:nuwiki_*) into each entry.
" Everything downstream — `<Leader>ww`, link resolution, the server payload —
" then sees the same wikis.
" Per-wiki dict keys: upstream `g:vimwiki_list[i]` key -> nuwiki/server key.
let s:vimwiki_wiki_map = {
\ 'path': 'root',
\ 'path_html': 'html_path',
\ 'template_path': 'template_path',
\ 'template_default': 'template_default',
\ 'template_ext': 'template_ext',
\ 'css_name': 'css_name',
\ 'auto_export': 'auto_export',
\ 'auto_toc': 'auto_toc',
\ 'syntax': 'syntax',
\ 'ext': 'file_extension',
\ 'index': 'index',
\ 'diary_rel_path': 'diary_rel_path',
\ 'diary_index': 'diary_index',
\ 'name': 'name',
\ }
" Global scalar settings vimwiki applies to every wiki (via wikilocal
" defaults): upstream suffix (`g:vimwiki_<suffix>`) -> nuwiki/server key.
let s:scalar_global_map = {
\ 'toc_header': 'toc_header',
\ 'toc_header_level': 'toc_header_level',
\ 'toc_link_format': 'toc_link_format',
\ 'links_header': 'links_header',
\ 'links_header_level': 'links_header_level',
\ 'tags_header': 'tags_header',
\ 'tags_header_level': 'tags_header_level',
\ 'html_header_numbering': 'html_header_numbering',
\ 'html_header_numbering_sym': 'html_header_numbering_sym',
\ 'links_space_char': 'links_space_char',
\ 'list_margin': 'list_margin',
\ 'listsyms': 'listsyms',
\ 'listsym_rejected': 'listsym_rejected',
\ 'auto_export': 'auto_export',
\ 'auto_toc': 'auto_toc',
\ 'auto_generate_links': 'auto_generate_links',
\ 'auto_generate_tags': 'auto_generate_tags',
\ 'auto_diary_index': 'auto_diary_index',
\ }
" Resolve the per-wiki scalar globals: vimwiki values first (compat), then
" nuwiki-native `g:nuwiki_<key>` overrides on top.
function! nuwiki#config#scalar_globals() abort
let l:g = {}
for [l:src, l:dst] in items(s:scalar_global_map)
if exists('g:vimwiki_' . l:src)
let l:g[l:dst] = get(g:, 'vimwiki_' . l:src)
endif
endfor
for l:dst in values(s:scalar_global_map)
if exists('g:nuwiki_' . l:dst)
let l:g[l:dst] = get(g:, 'nuwiki_' . l:dst)
endif
endfor
return l:g
endfunction
" Translate an upstream g:vimwiki_list into nuwiki's wiki list, folding the
" given global scalars into each entry. Returns [] when unset/malformed.
function! s:wikis_from_vimwiki(globals) abort
if !exists('g:vimwiki_list') || type(g:vimwiki_list) != type([]) || empty(g:vimwiki_list)
return []
endif
let l:wikis = []
for l:vw in g:vimwiki_list
if type(l:vw) != type({}) | continue | endif
let l:w = copy(a:globals)
for [l:src, l:dst] in items(s:vimwiki_wiki_map)
if has_key(l:vw, l:src)
let l:w[l:dst] = l:vw[l:src]
endif
endfor
if has_key(l:w, 'root')
call add(l:wikis, l:w)
endif
endfor
return l:wikis
endfunction
" The configured wikis as normalized dicts (nuwiki/server key names), with the
" global scalar defaults folded in (per-wiki value wins). Priority:
" 1. g:nuwiki_wikis (native)
" 2. g:vimwiki_list (upstream drop-in)
" Returns [] when neither is set — callers fall back to the single-wiki
" shorthand (g:nuwiki_wiki_root).
function! nuwiki#config#wikis() abort
let l:globals = nuwiki#config#scalar_globals()
if exists('g:nuwiki_wikis') && !empty(g:nuwiki_wikis)
return map(copy(g:nuwiki_wikis), {_, w -> extend(copy(l:globals), w, 'force')})
endif
return s:wikis_from_vimwiki(l:globals)
endfunction
+5 -6
View File
@@ -75,13 +75,12 @@ function! nuwiki#diary#calendar_sign(day, month, year) abort
return ''
endfunction
" Helper: get wiki configuration for wiki n (0-based).
" Mirrors the same logic used by nuwiki#commands#open_diary_path etc.
" Helper: get wiki configuration for wiki n (0-based). Resolves through
" nuwiki#config#wikis() so g:nuwiki_wikis AND an upstream g:vimwiki_list both
" work, falling back to the scalar g:nuwiki_* single-wiki shorthand.
function! s:wiki_cfg(n) abort
let l:w = {}
if exists('g:nuwiki_wikis') && len(g:nuwiki_wikis) > a:n
let l:w = g:nuwiki_wikis[a:n]
endif
let l:wikis = nuwiki#config#wikis()
let l:w = (a:n >= 0 && a:n < len(l:wikis)) ? l:wikis[a:n] : {}
let l:root = expand(get(l:w, 'root',
\ get(g:, 'nuwiki_wiki_root', '~/vimwiki')))
let l:ext = get(l:w, 'file_extension',
+9 -113
View File
@@ -12,97 +12,6 @@ let s:plugin_root = fnamemodify(resolve(expand('<sfile>:p')), ':h:h:h')
let s:started = 0
" ===== Upstream vimwiki config compatibility =====
"
" nuwiki is a drop-in vimwiki replacement, so a user who keeps their original
" `g:vimwiki_list` + `g:vimwiki_*` globals shouldn't have to rewrite anything.
" These tables translate the upstream names into the server's config schema.
" nuwiki-native config (`g:nuwiki_wikis` / `g:nuwiki_*`) always wins.
" Per-wiki dict keys: upstream `g:vimwiki_list[i]` key -> server key.
let s:vimwiki_wiki_map = {
\ 'path': 'root',
\ 'path_html': 'html_path',
\ 'template_path': 'template_path',
\ 'template_default': 'template_default',
\ 'template_ext': 'template_ext',
\ 'css_name': 'css_name',
\ 'auto_export': 'auto_export',
\ 'auto_toc': 'auto_toc',
\ 'syntax': 'syntax',
\ 'ext': 'file_extension',
\ 'index': 'index',
\ 'diary_rel_path': 'diary_rel_path',
\ 'diary_index': 'diary_index',
\ 'name': 'name',
\ }
" Global scalar settings vimwiki applies to every wiki (via wikilocal
" defaults): upstream suffix (`g:vimwiki_<suffix>`) -> server key. The server
" reads these per-wiki, so we fold them into each wiki entry (and into the
" single-wiki shorthand payload).
let s:scalar_global_map = {
\ 'toc_header': 'toc_header',
\ 'toc_header_level': 'toc_header_level',
\ 'toc_link_format': 'toc_link_format',
\ 'links_header': 'links_header',
\ 'links_header_level': 'links_header_level',
\ 'tags_header': 'tags_header',
\ 'tags_header_level': 'tags_header_level',
\ 'html_header_numbering': 'html_header_numbering',
\ 'html_header_numbering_sym': 'html_header_numbering_sym',
\ 'links_space_char': 'links_space_char',
\ 'list_margin': 'list_margin',
\ 'listsyms': 'listsyms',
\ 'listsym_rejected': 'listsym_rejected',
\ 'auto_export': 'auto_export',
\ 'auto_toc': 'auto_toc',
\ 'auto_generate_links': 'auto_generate_links',
\ 'auto_generate_tags': 'auto_generate_tags',
\ 'auto_diary_index': 'auto_diary_index',
\ }
" Resolve the per-wiki scalar globals: vimwiki values first (compat), then
" nuwiki-native `g:nuwiki_<key>` overrides on top.
function! s:scalar_globals() abort
let l:g = {}
for [l:src, l:dst] in items(s:scalar_global_map)
if exists('g:vimwiki_' . l:src)
let l:g[l:dst] = get(g:, 'vimwiki_' . l:src)
endif
endfor
for l:dst in values(s:scalar_global_map)
if exists('g:nuwiki_' . l:dst)
let l:g[l:dst] = get(g:, 'nuwiki_' . l:dst)
endif
endfor
return l:g
endfunction
" Build nuwiki's `wikis` list from an upstream `g:vimwiki_list`, folding the
" global scalar settings into each entry. Returns [] when unset/malformed.
function! s:wikis_from_vimwiki() abort
if !exists('g:vimwiki_list') || type(g:vimwiki_list) != type([]) || empty(g:vimwiki_list)
return []
endif
let l:globals = s:scalar_globals()
let l:wikis = []
for l:vw in g:vimwiki_list
if type(l:vw) != type({}) | continue | endif
let l:w = copy(l:globals)
for [l:src, l:dst] in items(s:vimwiki_wiki_map)
if has_key(l:vw, l:src)
let l:w[l:dst] = l:vw[l:src]
endif
endfor
" A wiki needs a root; skip entries without one.
if has_key(l:w, 'root')
call add(l:wikis, l:w)
endif
endfor
return l:wikis
endfunction
function! nuwiki#lsp#start() abort
if s:started
return
@@ -225,29 +134,16 @@ function! s:settings() abort
\ 'link_severity': get(g:, 'nuwiki_link_severity', 'warn'),
\ },
\ }
" Global scalar settings (g:vimwiki_* then g:nuwiki_* overrides) act as
" defaults applied to every wiki, vimwiki-style. A per-wiki value always
" wins. Resolved once and folded into whichever wiki shape we send.
let l:globals = s:scalar_globals()
" Wiki list resolution, in priority order:
" 1. g:nuwiki_wikis (native multi-wiki config)
" 2. g:vimwiki_list (upstream vimwiki config — drop-in compat)
" 3. wiki_root shorthand (single wiki)
" Without a list the server only sees wiki_root and resolves links relative
" to that directory instead of each individual wiki's root.
if exists('g:nuwiki_wikis') && !empty(g:nuwiki_wikis)
let l:cfg['wikis'] = map(copy(g:nuwiki_wikis),
\ {_, w -> extend(copy(l:globals), w, 'force')})
" Wiki list comes from the shared resolver (nuwiki#config#wikis), so the
" server payload and the buffer-side commands agree on which wikis exist —
" native g:nuwiki_wikis or an upstream g:vimwiki_list, globals already folded.
let l:wikis = nuwiki#config#wikis()
if !empty(l:wikis)
let l:cfg['wikis'] = l:wikis
else
let l:vw_wikis = s:wikis_from_vimwiki()
if !empty(l:vw_wikis)
let l:cfg['wikis'] = l:vw_wikis
else
" Single-wiki shorthand: fold the global scalars into the top-level
" payload so the server's single-wiki desugaring honours them.
call extend(l:cfg, l:globals, 'force')
endif
" Single-wiki shorthand: fold the global scalars into the top-level payload
" so the server's single-wiki desugaring honours them.
call extend(l:cfg, nuwiki#config#scalar_globals(), 'force')
endif
return l:cfg
endfunction