From 9d6d28a1b68fe05c6ee38420f31b6069f04dc482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Fr=C3=B3es=20Franco?= Date: Fri, 5 Jun 2026 00:40:48 +0000 Subject: [PATCH] fix(vim): buffer commands honour g:vimwiki_list (ww opened ~/vimwiki) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The g:vimwiki_* drop-in only fed the LSP payload (lsp.vim s:settings). The buffer-side commands — 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 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 --- autoload/nuwiki/commands.vim | 24 ++-- autoload/nuwiki/config.vim | 103 +++++++++++++++ autoload/nuwiki/diary.vim | 11 +- autoload/nuwiki/lsp.vim | 122 ++---------------- development/tests/test-vimwiki-compat-vim.vim | 8 ++ 5 files changed, 137 insertions(+), 131 deletions(-) create mode 100644 autoload/nuwiki/config.vim diff --git a/autoload/nuwiki/commands.vim b/autoload/nuwiki/commands.vim index acfa0c6..d0cade0 100644 --- a/autoload/nuwiki/commands.vim +++ b/autoload/nuwiki/commands.vim @@ -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) diff --git a/autoload/nuwiki/config.vim b/autoload/nuwiki/config.vim new file mode 100644 index 0000000..e129ff7 --- /dev/null +++ b/autoload/nuwiki/config.vim @@ -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 — `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_`) -> 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_` 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 diff --git a/autoload/nuwiki/diary.vim b/autoload/nuwiki/diary.vim index f0c2cbd..acfebea 100644 --- a/autoload/nuwiki/diary.vim +++ b/autoload/nuwiki/diary.vim @@ -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', diff --git a/autoload/nuwiki/lsp.vim b/autoload/nuwiki/lsp.vim index d673905..5996191 100644 --- a/autoload/nuwiki/lsp.vim +++ b/autoload/nuwiki/lsp.vim @@ -12,97 +12,6 @@ let s:plugin_root = fnamemodify(resolve(expand(':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_`) -> 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_` 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 diff --git a/development/tests/test-vimwiki-compat-vim.vim b/development/tests/test-vimwiki-compat-vim.vim index 18364c7..1749c38 100644 --- a/development/tests/test-vimwiki-compat-vim.vim +++ b/development/tests/test-vimwiki-compat-vim.vim @@ -41,6 +41,14 @@ call s:check('w0 numbering_sym', get(s:w0, 'html_header_numbering_sym', '') call s:check('w1 root', get(s:w1, 'root', '') ==# '~/.vimwiki/ifood_wiki') call s:check('w1 globals applied', get(s:w1, 'toc_header_level', 0) == 2) +" The buffer-side commands (e.g. ww) must resolve the SAME wikis from +" g:vimwiki_list — not just the LSP payload. Regression guard for "ww +" opens an empty ~/vimwiki". +let s:wl = nuwiki#commands#wiki_list() +call s:check('wiki_list sees vimwiki config', len(s:wl) == 2) +call s:check('wiki_list w0 root', len(s:wl) >= 1 && s:wl[0].root ==# expand('~/.vimwiki/personal_wiki')) +call s:check('wiki_list w1 root', len(s:wl) >= 2 && s:wl[1].root ==# expand('~/.vimwiki/ifood_wiki')) + " ----- nuwiki-native config wins over vimwiki ----- let g:nuwiki_wikis = [{'root': '~/native', 'name': 'native'}] let s:cfg2 = nuwiki#lsp#settings()