From e6f400b37084b9c7125e7198c48117531961715e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Fr=C3=B3es=20Franco?= Date: Sat, 6 Jun 2026 01:15:43 +0000 Subject: [PATCH] feat(config): read g:vimwiki_list on Neovim too (lazy.nvim drop-in) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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 --- README.md | 16 +-- development/tests/test-global-shorthand.lua | 23 ++++ known-issues.md | 15 +-- lua/nuwiki/config.lua | 120 ++++++++++++++++++-- lua/nuwiki/lsp.lua | 67 +++-------- 5 files changed, 163 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 7a62bda..04b5478 100644 --- a/README.md +++ b/README.md @@ -679,15 +679,17 @@ match vimwiki. To migrate: 3. Keep your existing config, or point `wiki_root` / `wikis[i].root` at your directory. -**Existing `g:vimwiki_list` config works as-is (Vim).** When you haven't -configured nuwiki natively (`g:nuwiki_wikis` / `g:nuwiki_*`), the Vim -client reads your upstream `g:vimwiki_list` and `g:vimwiki_*` globals and -translates them: each wiki's `path`/`path_html`/`template_*`/`auto_export` +**Existing `g:vimwiki_list` config works as-is (Vim & Neovim).** When you +haven't configured nuwiki natively (`g:nuwiki_wikis` / `setup()` / `g:nuwiki_*`), +both clients read your upstream `g:vimwiki_list` and `g:vimwiki_*` globals and +translate them: each wiki's `path`/`path_html`/`template_*`/`auto_export` plus globals like `toc_header_level`, `html_header_numbering`, `html_header_numbering_sym`, `links_space_char`, and `list_margin`. -Native `g:nuwiki_*` config always takes precedence. (Settings nuwiki -implements differently — see [`known-issues.md`](./known-issues.md) — are -ignored.) +Native config always takes precedence. (Settings nuwiki implements +differently — see [`known-issues.md`](./known-issues.md) — are 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 modification. The first workspace scan after launch may show an empty diff --git a/development/tests/test-global-shorthand.lua b/development/tests/test-global-shorthand.lua index 863b433..240eb08 100644 --- a/development/tests/test-global-shorthand.lua +++ b/development/tests/test-global-shorthand.lua @@ -48,6 +48,29 @@ local io3 = lsp.init_options() check('g:nuwiki_ global folds', io3.wikis[1].toc_header_level == 5) 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 (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')) f:write(table.concat(out, '\n') .. '\n') f:close() diff --git a/known-issues.md b/known-issues.md index 4ad086d..6f4f7cc 100644 --- a/known-issues.md +++ b/known-issues.md @@ -128,15 +128,16 @@ correct as-is; the deferral is a deliberate risk/reward call. ## Notes -- **Upstream `g:vimwiki_list` config (Vim).** When you haven't configured - nuwiki natively, the Vim client reads your upstream `g:vimwiki_list` + - `g:vimwiki_*` globals and translates them into nuwiki's schema (per-wiki +- **Upstream `g:vimwiki_list` config (Vim & Neovim).** When you haven't + configured nuwiki natively, both clients read your upstream `g:vimwiki_list` + + `g:vimwiki_*` globals and translate them into nuwiki's schema (per-wiki `path`/`path_html`/`template_*`/`auto_export`, plus globals `toc_header`, `toc_header_level`, `html_header_numbering`, `html_header_numbering_sym`, - `links_space_char`, `list_margin`). `g:nuwiki_*` always wins. Globals nuwiki - doesn't model (e.g. `automatic_nested_syntaxes`) are ignored — see the - divergence list above. **Limitation:** this drop-in translation is currently - **Vim-only**; on Neovim, configure via `setup()` / `g:nuwiki_wikis`. + `links_space_char`, `list_margin`). `g:nuwiki_*` / `setup()` config always + wins. Globals nuwiki doesn't model (e.g. `automatic_nested_syntaxes`) are + ignored — see the divergence list above. On Neovim this means swapping the + `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` exposes the subset of `vimwiki#vars#get_wikilocal` that plugins like vimwiki-sync and vim-zettel call (`path`, `ext`/`extension`, `syntax`, diff --git a/lua/nuwiki/config.lua b/lua/nuwiki/config.lua index 2b9f71c..1187a80 100644 --- a/lua/nuwiki/config.lua +++ b/lua/nuwiki/config.lua @@ -114,15 +114,115 @@ function M.apply(user) M.options = vim.tbl_deep_extend('force', M.defaults, user or {}) 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_, g:nuwiki_, 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. --- Priority: setup() opts.wikis → g:nuwiki_wikis (VimL) → scalar opts/g: vars. --- Returns { name, root, ext, idx, diary_rel, diary_idx }. +-- Returns { name, root, ext, idx, diary_rel, diary_idx, space }. function M.wiki_cfg(n) - local opts = M.options - local wikis = opts.wikis or vim.g.nuwiki_wikis or nil - local w = wikis and wikis[(n or 0) + 1] or nil - 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 opts.file_extension or vim.g.nuwiki_file_extension or '.wiki' + local wikis = M.wikis() + local w = wikis[(n or 0) + 1] + local root = (w and w.root) or M.options.wiki_root or vim.g.nuwiki_wiki_root or '~/vimwiki' + local ext = (w and w.file_extension) or M.options.file_extension + or vim.g.nuwiki_file_extension or '.wiki' if not ext:match('^%.') then ext = '.' .. ext end return { 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', 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', - 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 ' ', } end @@ -139,9 +239,9 @@ end -- 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. function M.wiki_list() - local wikis = M.options.wikis or vim.g.nuwiki_wikis or nil + local wikis = M.wikis() local list = {} - if type(wikis) == 'table' and #wikis > 0 then + if #wikis > 0 then for i = 1, #wikis do list[i] = M.wiki_cfg(i - 1) end diff --git a/lua/nuwiki/lsp.lua b/lua/nuwiki/lsp.lua index 19fdbc1..4cdef38 100644 --- a/lua/nuwiki/lsp.lua +++ b/lua/nuwiki/lsp.lua @@ -14,63 +14,22 @@ local function command() return { install.expected_path() } end --- Per-wiki settings that double as wiki-wide defaults (vimwiki-style global --- shorthand): set once at the top level — via setup() or `g:nuwiki_` — --- and every wiki inherits it; a per-wiki value overrides. Mirrors the Vim --- client's `s:scalar_global_map`. -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_` 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. +-- Build the server config payload. The wiki list comes from the shared +-- resolver (config.wikis) — native setup() wikis, g:nuwiki_wikis, or an +-- upstream g:vimwiki_list, with global scalar defaults already folded — so the +-- payload and the buffer-side commands agree on which wikis exist. local function resolved_opts() local opts = config.options - if not opts.wikis and vim.g.nuwiki_wikis then - opts = vim.tbl_extend('force', opts, { wikis = vim.g.nuwiki_wikis }) - end - 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 }) + local wikis = config.wikis() + if #wikis > 0 then + opts = vim.tbl_extend('force', opts, { wikis = wikis }) else - -- Single-wiki shorthand: ensure the globals (incl. the g:nuwiki_* form) - -- sit at the top level for the server's single-wiki desugaring. - opts = vim.tbl_extend('keep', opts, globals) + -- Single-wiki shorthand: fold the global scalars into the top-level payload + -- so the server's single-wiki desugaring honours them. + local globals = config.scalar_globals() + if not vim.tbl_isempty(globals) then + opts = vim.tbl_extend('keep', opts, globals) + end end return opts end