feat(config): read g:vimwiki_list on Neovim too (lazy.nvim drop-in)
CI / cargo fmt --check (push) Successful in 2m12s
CI / cargo clippy (push) Successful in 2m18s
CI / cargo test (push) Successful in 2m24s
CI / editor keymaps (push) Successful in 1m27s

The g:vimwiki_* drop-in was Vim-only: the Lua client read only setup()
opts and g:nuwiki_wikis, so swapping the vimwiki plugin for nuwiki under
lazy.nvim left the existing vim.g.vimwiki_list ignored and nuwiki pointed
at the default ~/vimwiki.

Mirror autoload/nuwiki/config.vim on the Lua side: config.lua now owns a
single resolver (M.wikis / M.scalar_globals) that resolves setup() wikis →
g:nuwiki_wikis → g:vimwiki_list (translating path->root, path_html->html_path,
template_*, ext->file_extension, …) and folds the global scalar defaults
(g:vimwiki_* then g:nuwiki_* then explicit setup() values; built-in defaults
excluded). lsp.lua (payload) and wiki_cfg/wiki_list (buffer commands) both
route through it, so the server and <Leader>ww agree on the wikis.

Extends test-global-shorthand (15 checks) with the vimwiki_list drop-in +
buffer-command resolution + native-wins cases. Config-parity goldens and
keymap harnesses green on both clients. README + known-issues updated
(drop-in is now Vim & Neovim).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 01:15:43 +00:00
parent 96d53dddf3
commit e6f400b370
5 changed files with 163 additions and 78 deletions
+9 -7
View File
@@ -679,15 +679,17 @@ match vimwiki. To migrate:
3. Keep your existing config, or point `wiki_root` / `wikis[i].root` at 3. Keep your existing config, or point `wiki_root` / `wikis[i].root` at
your directory. your directory.
**Existing `g:vimwiki_list` config works as-is (Vim).** When you haven't **Existing `g:vimwiki_list` config works as-is (Vim & Neovim).** When you
configured nuwiki natively (`g:nuwiki_wikis` / `g:nuwiki_*`), the Vim haven't configured nuwiki natively (`g:nuwiki_wikis` / `setup()` / `g:nuwiki_*`),
client reads your upstream `g:vimwiki_list` and `g:vimwiki_*` globals and both clients read your upstream `g:vimwiki_list` and `g:vimwiki_*` globals and
translates them: each wiki's `path`/`path_html`/`template_*`/`auto_export` translate them: each wiki's `path`/`path_html`/`template_*`/`auto_export`
plus globals like `toc_header_level`, `html_header_numbering`, plus globals like `toc_header_level`, `html_header_numbering`,
`html_header_numbering_sym`, `links_space_char`, and `list_margin`. `html_header_numbering_sym`, `links_space_char`, and `list_margin`.
Native `g:nuwiki_*` config always takes precedence. (Settings nuwiki Native config always takes precedence. (Settings nuwiki implements
implements differently — see [`known-issues.md`](./known-issues.md) — are differently — see [`known-issues.md`](./known-issues.md) — are ignored.)
ignored.) On Neovim with lazy.nvim, swap the `vimwiki` plugin spec for nuwiki and
call `require('nuwiki').setup()` — your `vim.g.vimwiki_list` is picked up
unchanged.
Existing pages, diary entries, tags, and templates work without Existing pages, diary entries, tags, and templates work without
modification. The first workspace scan after launch may show an empty modification. The first workspace scan after launch may show an empty
@@ -48,6 +48,29 @@ local io3 = lsp.init_options()
check('g:nuwiki_ global folds', io3.wikis[1].toc_header_level == 5) check('g:nuwiki_ global folds', io3.wikis[1].toc_header_level == 5)
vim.g.nuwiki_toc_header_level = nil vim.g.nuwiki_toc_header_level = nil
-- ----- upstream vim.g.vimwiki_list drop-in (the lazy.nvim vimwiki-swap case) --
config.apply({}) -- setup() with no native config
vim.g.vimwiki_list = {
{ path = '~/.vimwiki/personal', path_html = '~/public_html/personal', auto_export = 1 },
{ path = '~/.vimwiki/work' },
}
vim.g.vimwiki_toc_header_level = 2
local io4 = lsp.init_options()
check('vimwiki_list -> 2 wikis', io4.wikis ~= nil and #io4.wikis == 2)
check('path -> root', io4.wikis[1].root == '~/.vimwiki/personal')
check('path_html -> html_path', io4.wikis[1].html_path == '~/public_html/personal')
check('vimwiki global folds', io4.wikis[1].toc_header_level == 2)
check('w2 root', io4.wikis[2].root == '~/.vimwiki/work')
-- Buffer-side commands (<Leader>ww) resolve the same wikis.
check('wiki_list reads vimwiki_list', config.wiki_list()[1].root == '~/.vimwiki/personal')
-- Native g:nuwiki_wikis wins over g:vimwiki_list.
vim.g.nuwiki_wikis = { { root = '~/native', name = 'native' } }
local io5 = lsp.init_options()
check('native g:nuwiki_wikis wins', #io5.wikis == 1 and io5.wikis[1].root == '~/native')
vim.g.nuwiki_wikis = nil
vim.g.vimwiki_list = nil
vim.g.vimwiki_toc_header_level = nil
local f = assert(io.open(os.getenv('NUWIKI_GS_OUT'), 'w')) local f = assert(io.open(os.getenv('NUWIKI_GS_OUT'), 'w'))
f:write(table.concat(out, '\n') .. '\n') f:write(table.concat(out, '\n') .. '\n')
f:close() f:close()
+8 -7
View File
@@ -128,15 +128,16 @@ correct as-is; the deferral is a deliberate risk/reward call.
## Notes ## Notes
- **Upstream `g:vimwiki_list` config (Vim).** When you haven't configured - **Upstream `g:vimwiki_list` config (Vim & Neovim).** When you haven't
nuwiki natively, the Vim client reads your upstream `g:vimwiki_list` + configured nuwiki natively, both clients read your upstream `g:vimwiki_list` +
`g:vimwiki_*` globals and translates them into nuwiki's schema (per-wiki `g:vimwiki_*` globals and translate them into nuwiki's schema (per-wiki
`path`/`path_html`/`template_*`/`auto_export`, plus globals `toc_header`, `path`/`path_html`/`template_*`/`auto_export`, plus globals `toc_header`,
`toc_header_level`, `html_header_numbering`, `html_header_numbering_sym`, `toc_header_level`, `html_header_numbering`, `html_header_numbering_sym`,
`links_space_char`, `list_margin`). `g:nuwiki_*` always wins. Globals nuwiki `links_space_char`, `list_margin`). `g:nuwiki_*` / `setup()` config always
doesn't model (e.g. `automatic_nested_syntaxes`) are ignored — see the wins. Globals nuwiki doesn't model (e.g. `automatic_nested_syntaxes`) are
divergence list above. **Limitation:** this drop-in translation is currently ignored — see the divergence list above. On Neovim this means swapping the
**Vim-only**; on Neovim, configure via `setup()` / `g:nuwiki_wikis`. `vimwiki` plugin for nuwiki under lazy.nvim works with your existing
`vim.g.vimwiki_list` untouched (call `require('nuwiki').setup()` with no args).
- **Third-party plugin compatibility.** A shim at `autoload/vimwiki/vars.vim` - **Third-party plugin compatibility.** A shim at `autoload/vimwiki/vars.vim`
exposes the subset of `vimwiki#vars#get_wikilocal` that plugins like exposes the subset of `vimwiki#vars#get_wikilocal` that plugins like
vimwiki-sync and vim-zettel call (`path`, `ext`/`extension`, `syntax`, vimwiki-sync and vim-zettel call (`path`, `ext`/`extension`, `syntax`,
+110 -10
View File
@@ -114,15 +114,115 @@ function M.apply(user)
M.options = vim.tbl_deep_extend('force', M.defaults, user or {}) M.options = vim.tbl_deep_extend('force', M.defaults, user or {})
end end
-- ===== Upstream vimwiki config compatibility (drop-in) =====
--
-- Single source of truth for the configured wikis, shared by the LSP payload
-- (lsp.lua) and the buffer-side commands. Mirrors autoload/nuwiki/config.vim so
-- a user who keeps their original vim.g.vimwiki_list + g:vimwiki_* config (e.g.
-- swapping the vimwiki plugin for nuwiki under lazy.nvim) needs no rewrite.
-- Native config (setup() wikis / g:nuwiki_*) always wins.
-- Upstream vimwiki per-wiki dict key -> nuwiki/server key.
local 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',
}
-- Per-wiki settings that double as wiki-wide global defaults.
local SCALAR_GLOBAL_KEYS = {
'toc_header', 'toc_header_level', 'toc_link_format',
'links_header', 'links_header_level',
'tags_header', 'tags_header_level',
'html_header_numbering', 'html_header_numbering_sym',
'links_space_char', 'list_margin',
'listsyms', 'listsym_rejected',
'auto_export', 'auto_toc',
'auto_generate_links', 'auto_generate_tags', 'auto_diary_index',
}
-- Resolve the global scalar defaults applied to every wiki. Priority (low to
-- high): g:vimwiki_<key>, g:nuwiki_<key>, explicit setup() value. Built-in
-- defaults are excluded (reads M.user, not M.options) so they don't get folded
-- onto every wiki and override per-wiki values.
function M.scalar_globals()
local g = {}
for _, key in ipairs(SCALAR_GLOBAL_KEYS) do
if vim.g['vimwiki_' .. key] ~= nil then
g[key] = vim.g['vimwiki_' .. key]
end
if vim.g['nuwiki_' .. key] ~= nil then
g[key] = vim.g['nuwiki_' .. key]
end
if M.user[key] ~= nil then
g[key] = M.user[key]
end
end
return g
end
-- Translate vim.g.vimwiki_list into nuwiki's wiki list (server key names),
-- folding the global scalars into each entry. Returns {} when unset/malformed.
local function wikis_from_vimwiki(globals)
local list = vim.g.vimwiki_list
if type(list) ~= 'table' or vim.tbl_isempty(list) then
return {}
end
local out = {}
for _, vw in ipairs(list) do
if type(vw) == 'table' then
local w = vim.tbl_extend('force', {}, globals)
for src, dst in pairs(VIMWIKI_WIKI_MAP) do
if vw[src] ~= nil then
w[dst] = vw[src]
end
end
if w.root ~= nil then
table.insert(out, w)
end
end
end
return out
end
-- The configured wikis as normalized dicts (nuwiki/server key names), with the
-- global scalars folded in (per-wiki value wins). Priority:
-- 1. setup() opts.wikis
-- 2. g:nuwiki_wikis
-- 3. g:vimwiki_list (upstream drop-in)
-- Returns {} when none is set — callers fall back to the single-wiki shorthand.
function M.wikis()
local globals = M.scalar_globals()
local native = M.options.wikis or vim.g.nuwiki_wikis
if type(native) == 'table' and #native > 0 then
local out = {}
for i, w in ipairs(native) do
out[i] = vim.tbl_extend('force', globals, w)
end
return out
end
return wikis_from_vimwiki(globals)
end
-- Resolve the config for wiki #n (0-based) without touching the LSP. -- Resolve the config for wiki #n (0-based) without touching the LSP.
-- Priority: setup() opts.wikis → g:nuwiki_wikis (VimL) → scalar opts/g: vars. -- Returns { name, root, ext, idx, diary_rel, diary_idx, space }.
-- Returns { name, root, ext, idx, diary_rel, diary_idx }.
function M.wiki_cfg(n) function M.wiki_cfg(n)
local opts = M.options local wikis = M.wikis()
local wikis = opts.wikis or vim.g.nuwiki_wikis or nil local w = wikis[(n or 0) + 1]
local w = wikis and wikis[(n or 0) + 1] or nil local root = (w and w.root) or M.options.wiki_root or vim.g.nuwiki_wiki_root or '~/vimwiki'
local root = (w and w.root) or opts.wiki_root or vim.g.nuwiki_wiki_root or '~/vimwiki' local ext = (w and w.file_extension) or M.options.file_extension
local ext = (w and w.file_extension) or opts.file_extension or vim.g.nuwiki_file_extension or '.wiki' or vim.g.nuwiki_file_extension or '.wiki'
if not ext:match('^%.') then ext = '.' .. ext end if not ext:match('^%.') then ext = '.' .. ext end
return { return {
name = (w and w.name) or vim.fn.fnamemodify(vim.fn.expand(root), ':t'), name = (w and w.name) or vim.fn.fnamemodify(vim.fn.expand(root), ':t'),
@@ -131,7 +231,7 @@ function M.wiki_cfg(n)
idx = (w and w.index) or 'index', idx = (w and w.index) or 'index',
diary_rel = (w and w.diary_rel_path) or vim.g.nuwiki_diary_rel_path or 'diary', diary_rel = (w and w.diary_rel_path) or vim.g.nuwiki_diary_rel_path or 'diary',
diary_idx = (w and w.diary_index) or 'diary', diary_idx = (w and w.diary_index) or 'diary',
space = (w and w.links_space_char) or opts.links_space_char space = (w and w.links_space_char) or M.options.links_space_char
or vim.g.nuwiki_links_space_char or ' ', or vim.g.nuwiki_links_space_char or ' ',
} }
end end
@@ -139,9 +239,9 @@ end
-- The full list of configured wikis as wiki_cfg dicts. Falls back to a single -- The full list of configured wikis as wiki_cfg dicts. Falls back to a single
-- entry built from the scalar wiki_root config when no list is configured. -- entry built from the scalar wiki_root config when no list is configured.
function M.wiki_list() function M.wiki_list()
local wikis = M.options.wikis or vim.g.nuwiki_wikis or nil local wikis = M.wikis()
local list = {} local list = {}
if type(wikis) == 'table' and #wikis > 0 then if #wikis > 0 then
for i = 1, #wikis do for i = 1, #wikis do
list[i] = M.wiki_cfg(i - 1) list[i] = M.wiki_cfg(i - 1)
end end
+13 -54
View File
@@ -14,63 +14,22 @@ local function command()
return { install.expected_path() } return { install.expected_path() }
end end
-- Per-wiki settings that double as wiki-wide defaults (vimwiki-style global -- Build the server config payload. The wiki list comes from the shared
-- shorthand): set once at the top level — via setup() or `g:nuwiki_<key>` — -- resolver (config.wikis) — native setup() wikis, g:nuwiki_wikis, or an
-- and every wiki inherits it; a per-wiki value overrides. Mirrors the Vim -- upstream g:vimwiki_list, with global scalar defaults already folded — so the
-- client's `s:scalar_global_map`. -- payload and the buffer-side commands agree on which wikis exist.
local SCALAR_GLOBALS = {
'toc_header', 'toc_header_level', 'toc_link_format',
'links_header', 'links_header_level',
'tags_header', 'tags_header_level',
'html_header_numbering', 'html_header_numbering_sym',
'links_space_char', 'list_margin',
'listsyms', 'listsym_rejected',
'auto_export', 'auto_toc',
'auto_generate_links', 'auto_generate_tags', 'auto_diary_index',
}
-- Resolve the global scalar defaults from config the user *explicitly* set:
-- the raw setup() table first, then the `g:nuwiki_<key>` global. Built-in
-- defaults are deliberately excluded so they don't get folded into every wiki
-- (that would diverge from the Vim client and override per-wiki values with a
-- default).
local function scalar_globals()
local user = config.user or {}
local g = {}
for _, key in ipairs(SCALAR_GLOBALS) do
if user[key] ~= nil then
g[key] = user[key]
elseif vim.g['nuwiki_' .. key] ~= nil then
g[key] = vim.g['nuwiki_' .. key]
end
end
return g
end
-- Merge g:nuwiki_wikis (VimL config) into opts.wikis when the user has not
-- passed wikis through setup(). This lets users who configure via a .vim
-- file avoid duplicating the list in their Lua setup() call.
local function resolved_opts() local function resolved_opts()
local opts = config.options local opts = config.options
if not opts.wikis and vim.g.nuwiki_wikis then local wikis = config.wikis()
opts = vim.tbl_extend('force', opts, { wikis = vim.g.nuwiki_wikis }) if #wikis > 0 then
end opts = vim.tbl_extend('force', opts, { wikis = wikis })
local globals = scalar_globals()
if vim.tbl_isempty(globals) then
return opts
end
if opts.wikis then
-- Fold the globals into each wiki as defaults (per-wiki value wins),
-- without mutating the user's setup table.
local folded = {}
for i, w in ipairs(opts.wikis) do
folded[i] = vim.tbl_extend('force', globals, w)
end
opts = vim.tbl_extend('force', opts, { wikis = folded })
else else
-- Single-wiki shorthand: ensure the globals (incl. the g:nuwiki_* form) -- Single-wiki shorthand: fold the global scalars into the top-level payload
-- sit at the top level for the server's single-wiki desugaring. -- so the server's single-wiki desugaring honours them.
opts = vim.tbl_extend('keep', opts, globals) local globals = config.scalar_globals()
if not vim.tbl_isempty(globals) then
opts = vim.tbl_extend('keep', opts, globals)
end
end end
return opts return opts
end end