4710ba03c8
Upstream is `-range -nargs=+` (change_level(line1, line2, direction, plus_children)); nuwiki was -nargs=? and range-less, so `:1,3VimwikiListChangeLvl increase 0` raised E481/E488 and the level change never spanned a selection. All four defs (Vim+Neovim × Vimwiki+Nuwiki) are now -range -nargs=+ and forward <line1>, <line2>, <f-args>. list_change_lvl(line1, line2, direction, [plus_children]) (both clients) parses the required direction + optional subtree flag and applies the change to every list item in the range via the existing over_range helper. Tests: cmd.ListChangeLvl_range_indents (test-keymaps.lua) + cmd.VimwikiListChangeLvl_accepts_range_and_args (test-keymaps-vim.vim). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
25 KiB
25 KiB
vimwiki → nuwiki Parity Gaps
Tracking document for closing the gap between nuwiki and the original vimwiki plugin across configuration, commands, and key mappings.
Source of truth for upstream: vimwiki master —
autoload/vimwiki/vars.vim (option defaults), plugin/vimwiki.vim +
ftplugin/vimwiki.vim (commands/mappings), doc/vimwiki.txt (prose).
Audited 2026-05-31. Check items off as they land. Each row cites the nuwiki fix site.
P1 — Core workflow gaps (fix first)
- Vim client split/tab link-follow commands —
VimwikiSplitLink,VimwikiVSplitLink,VimwikiGoBackLink,VimwikiTabnewLink,VimwikiTabDropLinkexist only in the Neovim branch. Vim users lose split-window link following and back-navigation. Fix:ftplugin/vimwiki.vim(Vim branch) — commands added +:Nuwiki*aliases;follow_link_drop()helper inautoload/nuwiki/commands.vim; true:tab droptab-reuse wired in both clients (lua/nuwiki/commands.luaopen_uri'tabdrop'case);<C-S-CR>repointed to tab-drop. gl<symbol>change-symbol mappings — upstream's one-key bullet/number symbol change (gl*gl#gl-gl1glagli… plusgL…) has no nuwiki keymap; only:…ChangeSymbol*commands exist. Fix: added normal-modegl{-,*,#,1,i,I,a,A}(item) +gL…(whole list) inlua/nuwiki/keymaps.lualists group andftplugin/vimwiki.vimVim-branch lists block, wired to the existinglist_change_symbol.1)stays command-only (upstream shadows it with1.); normal-mode only (server acts on cursor item / whole list, not a visual range).gl<Space>/gL<Space>semantics diverge — upstream = remove checkbox from item / list siblings; nuwiki rebound to remove done items. Same keys, different effect (parity trap). Fix: bound baregl/gLto remove-checkbox-item / -in-list for exact upstream parity (lua/nuwiki/keymaps.lualists block,ftplugin/vimwiki.vimVim-branch lists block); they share thegl…prefix so they fire aftertimeoutlen, just like upstream. Remove-done is now command-only::NuwikiRemoveDone(current list) gained a-bangso:NuwikiRemoveDone!reaches the whole-buffer sweep thatgL<Space>previously held (the only prior entry point). Normal-mode only.
Regressions (reported by users — verify, then fix)
<CR>link-follow: wrong window placement + no create-on-follow on the Vim clients — two related Vim-branch bugs (Neovim path was already correct): (a) on coc.nvim,<CR>opened the target in a new tab instead of the current window; (b) on both vim-lsp and coc,<CR>on a link to a not-yet-created page failed to open/create it. Root cause: the Vim follow path delegated to the LSP client's jump UI (:LspDefinition/CocActionAsync('jumpDefinition')). Those fetch the target's text before jumping — vim-lspreadfile()s it for the quickfix entry (autoload/lsp/utils/location.vim), which throwsE484for a missing file and aborts the jump (→ no create-on-follow); coc, lacking an explicit open command, fell back to the user'scoc.preferences.jumpCommand(→ new tab). The server itself was correct throughout — verified over JSON-RPC thattextDocument/definitionreturns a synthesised location for missing pages and resolves existing links. Fix: stop delegating.nuwiki#commands#follow_link_or_create()/follow_link_drop()now resolvetextDocument/definitiondirectly and open the URI ourselves via a shareds:open_definition(open_cmd)helper (autoload/nuwiki/commands.vim) —:editfor<CR>,tab dropfor<C-S-CR>.:editopens a buffer for a missing path (save creates it, matching vimwiki) and gives exact current-window placement regardless of the user's coc jumpCommand. Covered bycr.follow_opens_missing_page_in_current_windowindevelopment/tests/test-keymaps-vim.vim; manually reproducible viadevelopment/start-vim.sh(vim-lsp) anddevelopment/start-vim-coc.sh(coc).- coc.nvim reported valid links as broken / couldn't resolve — only via
the new
development/start-vim-coc.shlauncher: its generatedcoc-settings.jsonregistered the server withcommand/filetypesbut noinitializationOptions, sowiki_rootnever reached the server and it resolved links against its default (~/vimwiki). Fix: the launcher now emitsinitializationOptionsandsettings.nuwikimirroring the vim-lsp client (autoload/nuwiki/lsp.vims:settings()). Verified with a headless coc.nvim run:[[Notes]]resolves, only genuinely-missing links warn. (Shipped plugin only — no production code change; coc users configure their owncoc-settings.jsonper the README snippet.)
P2 — Moderate (real users hit these)
<Leader>w<Leader>tcollision — upstream = today-in-new-tab; nuwiki bound both<Leader>w<Leader>tand<Leader>w<Leader>mto tomorrow's diary. Fix:<Leader>w<Leader>tnow callsdiary_today_tab(today in a new tab),<Leader>w<Leader>mstays tomorrow — exact upstream parity (lua/nuwiki/keymaps.lua,ftplugin/vimwiki.vim; docs + README updated). Covered bydiary.leader_t_opens_today_in_tab/diary.leader_m_opens_tomorrow.- Generated-section captions hardcoded — TOC/links/tags headings and
their levels were hardcoded (
= Contents =,= Generated Links =,= Tags =, all level 1). Fix: added per-wikitoc_header/toc_header_level,links_header/links_header_level,tags_header/tags_header_level(defaultsContents/Generated Links/Generated Tags, level 1 — matching upstream; this also corrects the tags index heading fromTags→Generated Tags). Acaption_line(name, level)helper emits the markers; the text + level thread throughtoc_edit/links_edit/tag_links_edit(+ the auto_toc save hook) andfind_section_rangematches the configured text so regen stays idempotent. Tests:captions_honour_custom_header_and_level(link_health.rs) + config round-trip inindex_and_config.rs. - On-save autoregen family — added per-wiki
auto_generate_links,auto_generate_tags,auto_diary_index(all defaultfalse, like upstream), wired into thedid_savehook (crates/nuwiki-lsp/src/lib.rs) mirroringauto_toc: regen runs only when the section already exists (links_rebuild_edit/tag_links_rebuild_edit), andauto_diary_indexregenerates the diary index when a dated diary entry is saved (diary_generate_index_edit, which handles an open/closed/absent index file).auto_tagsis an intentional divergence (see below): nuwiki re-indexes on every change, so tag metadata is always fresh — there's no on-disk metadata file to update on save. Tests:links_rebuild_edit_only_acts_when_section_present(link_health.rs),tag_links_rebuild_edit_only_acts_when_index_present(commands_tags.rs), config round-trip + defaults (index_and_config.rs). listsym_rejected— the rejected glyph was a hardcodedconst '-'. Fix:ListSymsgained arejectedfield +new_with_rejected()(crates/nuwiki-core/src/listsyms.rs); per-wikilistsym_rejectedkey (default-) added toWikiConfig;WikiConfig::list_syms()builds the palette with it, used by the parse path (lib.rs) and the toggle/cycle commands — so a custom rejected glyph both lexes and round-trips. Tests:listsyms::custom_rejected_glyph_is_honoured+ config round-trip inindex_and_config.rs.custom_wiki2html(+_args) andbase_url— external HTML converter hook + export URL prefix. Fix:HtmlConfig(config.rs) gainedcustom_wiki2html,custom_wiki2html_args,base_url(all default"", round-tripped throughRawWiki/From/defaults).write_page(commands.rs) now takes aforceflag and, whencustom_wiki2htmlis set, hands the page torun_custom_wiki2html()— which shells out viash -cwith vimwiki's exact arg order (<cmd> <force> <syntax> <ext> <out_dir> <in_file> <css> <tpl_path> <tpl_default> <tpl_ext> <root_path> <args>,-for empty optionals) instead of the built-in renderer.forceis threaded throughexport_current(false),export_all(its own flag), and thedid_saveauto-export path (false).write_rssnow prefixes the channel + item links withbase_url(<base_url><diary_rel_path>/<date>.html) when set, falling back to the entry'sfile://URI otherwise. Tests:write_page_invokes_custom_wiki2html_converter,write_rss_uses_base_url_for_public_links(html_export.rs), config round-trip + defaults (index_and_config.rs).map_prefix—<Leader>wwas hardcoded across the map layer. Fix: mirror vimwiki'sg:vimwiki_map_prefixwith a configurable prefix (Neovimmap_prefixsetup option, default'<Leader>w'; Vimg:nuwiki_map_prefix). The whole wiki command family is now built from the prefix — both entry-point globals (lua/nuwiki/init.lua,plugin/nuwiki.vim, via:exe) and buffer-local maps (lua/nuwiki/keymaps.lua,ftplugin/vimwiki.vim). Setting a custom prefix relocates<prefix>{w,t,s,i,n,d,r,c,h,hh,ha}and<prefix><Leader>{w,y,t,m, i}and leaves nothing under the old<Leader>w*. Tests: newtest-keymaps-vim-prefix.vimharness (21 cases, driven bytest-keymaps-vim.sh) +map_prefix.relocates_wiki_family(test-keymaps.lua). Docs: README,doc/nuwiki.txt, lua config comment.<M-CR>badd-link — was mouse-only (<MiddleMouse>) / command-only (:VimwikiBaddLink); upstream binds<M-CR>→VimwikiBaddLink(confirmed against upstreamftplugin/vimwiki.vim). Fix: added normal-mode<M-CR>→badd_linkin the links group of both clients (lua/nuwiki/keymaps.lua,ftplugin/vimwiki.vim; docs + README updated). Covered bymap[n].<M-CR>in both keymap harnesses.diary_caption_leveldefault divergence — nuwiki defaulted to1, upstream0. Fix:wiki_defaults()incrates/nuwiki-lsp/src/config.rsnow defaultsdiary_caption_level: 0(year captions top-level, months one below), still per-wiki overridable. Testdefaults_carry_vimwiki_per_wiki_keysupdated.diary_start_week_day— removed inc63ec67(weekly diary hardwired to ISO-Monday). Restored as a configurable choice so migrators keep upstream behaviour while new wikis can use ISO weeks. New per-wiki keydiary_weekly_style:iso(default —YYYY-Www, Monday-based, nuwiki's original) ordate/vimwiki(YYYY-MM-DDof the week-start day, upstream parity, honouring the restoreddiary_start_week_day=monday..sunday). Impl:nuwiki_core::date::{WeeklyStyle, WeekStart, DiaryCalendar}own the today/next/prev logic;WikiConfig::diary_calendar()builds it from config; the diary commands (commands.rsdiary_open_relative+ next/prev pivot) use it. Defaults keep existing nuwiki weekly files working (no breaking change). Covered by calendar tests incrates/nuwiki-core/tests/diary.rsand the config round-trip incrates/nuwiki-lsp/tests/index_and_config.rs.
P3 — Niche / low impact
Commands
VimwikiVar— config-var get/set introspection. Fix:plugin/nuwiki.vim.VimwikiShowVersion— print version (trivial). Fix:plugin/nuwiki.vim.VimwikiReturn— no Ex-command (mapping-driven only). Fix:ftplugin/vimwiki.vim.VimwikiTableAlignQvsAlignWcollapsed to onetable_align—gqq(align) vsgww(align w/o resize) distinction lost.VimwikiTablewas-nargs=1vs upstream-nargs=*(couldn't pass cols+rows). Fix: all four defs (Vim+Neovim × Vimwiki+Nuwiki) are now-nargs=*and forward<f-args>totable_insert(which already parsed cols/rows). Covered bycmd.VimwikiTable_passes_cols_and_rows(test-keymaps.lua, asserts a:VimwikiTable 4 3→ 4-col, 3-row table) andcmd.VimwikiTable_accepts_cols_rows(test-keymaps-vim.vim, no E488).VimwikiListChangeLvlwas-nargs=?(range-less) vs upstream-range -nargs=+. Fix: all four defs are now-range -nargs=+passing<line1>, <line2>, <f-args>;list_change_lvl(line1, line2, direction, [plus_children])(both clients) parses the required direction + optional subtree flag and applies the level change to every list item in the range via the existingover_rangehelper. Covered bycmd.ListChangeLvl_range_indents(test-keymaps.lua) andcmd.VimwikiListChangeLvl_accepts_range_and_args(test-keymaps-vim.vim, no E481/E488).VimwikiRebuildTags-bang("rebuild all") —:…RebuildTags!now passes{ all: true }; the server (tags_rebuild) re-indexes every configured wiki (viawikis_snapshot()) instead of just the current one.-bangadded to all four command defs.:VimwikiSearch/VWSuseslvimgrep, not vimwiki's search engine.VimwikiIndexfamily is buffer-local in nuwiki (upstream defines globally inplugin/); only:…UISelectis a global entry point.VimwikiColorize/NuwikiColorizedrop their argument in the Neovim branch — both are-nargs=1, but the Neovim defs calledcolorize()with no<q-args>(ftplugin/vimwiki.vim:445,517) while the Vim branch passes it. The color name was silently ignored under Neovim (real bug, not just parity). Fix: Neovim command defs now pass<q-args>. While verifying, found a second bug:colorize()inferred visual-vs-normal fromvim.fn.visualmode(), which returns the last session visual mode with stale'</'>marks — so the normal-mode command wrapped a leftover selection instead of the cword.colorize(color, visual)now takes an explicitvisualflag; the command and normal mapping always wrap the cword. Third bug: the commands were not-range, so selecting text and running:VimwikiColorize(which Vim turns into:'<,'>VimwikiColorize) raisedE481: No range allowed. All four command defs are now-rangeand pass the range to the handler — a ranged (visual) invocation wraps the'</'>selection on both clients; thex-mode<Leader>wcmapping passes the visual flag directly. Covered bycmd.VimwikiColorize_uses_arg,cmd.Colorize_ignores_stale_visual_selection,colorize.visual_wraps_selection,cmd.Colorize_range_wraps_selectionintest-keymaps.luaandcolorize.{command_wraps_cword,visual_wraps_selection, ranged_command_no_error}intest-keymaps-vim.vim.- Colour spans aren't concealed — a colorized word shows the literal
<span style="color:…">…</span>markup instead of just the coloured text. Fix:autoload/nuwiki/colors.vimnuwiki#colors#refresh()scans the buffer and defines, per colour, a syntax region that conceals the<span …>/</span>tags (concealends) and paints the wrapped text in the span's actual colour (guifg/ctermfg). Wired from the syntax file (initial + on:colorschemereload), anftpluginTextChangedautocmd, and directly fromcolorize()for immediate effect. The LSP@vimwikiColortoken no longer links toConstantso it doesn't override the real colour on the concealed text in Neovim. Works in Vim, coc, and Neovim (pure syntax +:highlight, no LSP needed). Covered bycolorize.conceal_hides_tags_shows_text(both harnesses) andcolorize.command_conceals_immediately(test-keymaps-vim.vim). VimwikiTableMoveColumn{Left,Right}dispatch to different backing names per client — converged: the Vim-branch commands now callnuwiki#commands#table_move_column_left/right(existing aliases over thetable_move_left/rightimpls), matching the Neovim branch.NuwikiTabIndexlacks-countin the Neovim branch — added-count; also switched the NeovimIndex/TabIndexfamily (Vimwiki/NuwikiTabIndexandVimwiki/NuwikiIndex) fromvim.v.count(always 0 in command context) to<count>, so a:2NuwikiIndex/:3NuwikiTabIndexcount is now honored. (TheIndexhalf was caught by the 2026-05-31 second re-audit and fixed then.)VimwikiToggleListItem/Increment/DecrementListItem-range— all three (both branches) are now-range; the client loops the per-line op over[<line1>, <line2>](toggle_list_item_range/list_cycle_symbol_range). The server edits are version-less single-line, so each line's edit applies independently — no server protocol change needed. Covered bycmd.toggle_list_item_rangeintest-keymaps.lua.- Diary-note family lacks
-count(new, 2026-05-31 re-audit) — upstream's:Vimwiki{Make,TabMake,MakeYesterday,MakeTomorrow}DiaryNotecarry-count=0(the count selects the wiki number); nuwiki's diary-note commands declare no count in either branch. Fix:ftplugin/vimwiki.vimdiary block (both branches) + thread the count intodiary_today/yesterday/tomorrow+ server diary commands (accept an optionalwikiselector). (TheIndex/TabIndexhalf is done: all four defs now use upstream's-count=0instead of bare-count;wiki_index/wiki_tab_indexalready map the count to the wiki number.) - No
-complete=specs (new) — upstream attaches command-line completion toVimwikiGoto(links),VimwikiRenameFile(files),VimwikiColorize(colours), and the tag commands. nuwiki defines none, so<Tab>completion of page / tag / colour names at the:line is unavailable. Fix:ftplugin/vimwiki.vim(both branches) — VimL completers or LSP-backed. - Minor command-attribute gaps (new) — all now real, not cosmetic:
VimwikiNormalizeLinkis-nargs=?and with1wraps the'</'>selection (wrap_visual_as_wikilink, both clients; thex-mode+passes it too);VimwikiCheckLinksis-rangeand a ranged invocation filters the report to the current buffer's selected lines;VimwikiColorize-nargs=1→-nargs=*(bare:VimwikiColorizeprompts). Covered bynormalize.visual_wraps_selection(vim) /cmd.normalize_link_visual_wraps_selection(nvim). VimwikiSplitLink/VimwikiVSplitLinklack-nargs=*(new, 2026-05-31 third re-audit) — upstream both take an optional target link to follow in the split; nuwiki's defs (both branches) take no args, so you can't pass a link to the split-follow. Low impact (the arg path is rarely used). Fix:ftplugin/vimwiki.vimboth branches + thread a target throughfollow_link_or_create.
Config
auto_header— auto H1-from-filename on new page (server-side).create_link— toggle to suppress link-target creation.dir_link— index file to open when following a directory link.auto_chdir—:cdinto wiki root on open (client-side).bullet_types/cycle_bullets— custom bullet glyphs + wrap-around.commentstring('%%%s') — comment toggling/text-objects.color_tag_template—color_dicpresent; template regex missing.rss_max_items/rss_name—:Rssoutput hardcoded. Fix:HtmlConfig.- RSS feed structure fidelity (new, 2026-05-31 base_url audit) —
nuwiki's
write_rssis intentionally minimal (title/link/guid). Upstream emits more: channel<link>points at the diary index page (base_url + diary_rel_path + diary_index + .html, nuwiki uses barebase_url); per-item<guid isPermaLink="false">holds the bare date basename (nuwiki uses the full URL, no attribute); plus<atom:link rel= "self">, channel/item<pubDate>(from mtime), and a<![CDATA[…]]>rendered-HTML body per item.base_url's scope is faithful (RSS-only, not inter-page links) and thecustom_wiki2htmlarg contract is an exact match — only the feed document shape diverges. emoji_enable— emoji substitution.html_header_numbering(+_sym) — numbered HTML headers.valid_html_tags— allowed inline HTML tags in export.generated_links_caption,toc_link_format,markdown_link_ext.list_ignore_newline/text_ignore_newline— export newline handling.
Mappings
- Visual
<CR>normalize-link (visual+covers the same intent). - Insert
<S-CR>multiline list item. gLH/gLL/gLR— upstream binds these as case-variant aliases ofgLh/gLl/gLr(same actions: dedent / indent whole item, renumber all lists; upstreamftplugin/vimwiki.vim:553,555,561). nuwiki has the lowercase forms but not the uppercase-suffix aliases. Cosmetic — same effect, redundant keys.<D-CR>(Cmd+Return) tab-drop alias (new, 2026-05-31 re-audit) — added<D-CR>→follow_link_dropalongside<C-S-CR>in both clients (lua/nuwiki/keymaps.lua,ftplugin/vimwiki.vim); docs updated. Covered bymap[n].<D-CR>in both harnesses.<Leader>w<Leader>mmissing from the Vim global map block (new) —plugin/nuwiki.vim's global family now binds<Leader>w<Leader>m(tomorrow) and<Leader>w<Leader>topens today in a new tab (was the same collision the buffer-local maps had). Covered bymap[n].<Leader>w<Leader>min both harnesses.<Leader>w<Leader>mmissing from the Neovim global map block (new, 2026-05-31 map_prefix audit) — the same collision survived inlua/nuwiki/init.lua's_setup_global_mappings:<prefix><Leader>twas bound to tomorrow and<prefix><Leader>m(tomorrow) was absent entirely. Fixed so<prefix><Leader>topens today in a new tab and<prefix><Leader>mis tomorrow — now consistent with the Vim global side + upstream. Covered byglobal_maps.diary_tab_and_tomorrow_targets(test-keymaps.lua).
Intentional divergences (no action — architectural)
These upstream options have no nuwiki equivalent by design, because nuwiki is an LSP-backed reimplementation rather than a pure-VimL plugin. Listed so future audits don't re-flag them.
nested_syntaxes/automatic_nested_syntaxes— code-fence languages are auto-detected from the fence tag (syntax/vimwiki.vim); always on, no toggle.maxhi— existence-based link highlighting; superseded by LSP diagnostics.conceal*(conceallevel,conceal_onechar_markers, …) — semantic tokens replace conceal.ext2syntax— syntax chosen by extension /syntaxkey automatically.folding(upstream string) — nuwiki usesfolding = 'lsp' | 'expr' | 'off'backed by LSP foldingRange.key_mappings(dict) — replaced by Luamappings.<group>+ theg:nuwiki_no_<group>_mappingsglobals.use_calendar— no calendar.vim integration.auto_tags(upstream0) — upstream auto-updates an on-disk tag metadata file on save. nuwiki has no such file: the LSP re-indexes tags on everydidChange, so tag search/jump/completion are always fresh. The setting's intent is therefore always satisfied (theauto_generate_tagsregen of the in-buffer links section is the configurable on-save piece).- Mouse maps (
<2-LeftMouse>,<MiddleMouse>, …) — upstream binds them unconditionally; nuwiki ships them opt-in (mappings.mouse/g:nuwiki_mouse_mappings). Deliberate, so they're not always-on. global_ext(upstream1) — nuwiki's ftdetect always maps the configured wiki extension(s) to thevimwikifiletype regardless of location; there's no per-wiki "only inside the root" toggle. Effectively always-on, likenested_syntaxes.syntaxdefault name — nuwiki defaults the per-wikisyntaxtovimwiki(its primary syntax) where upstream defaults todefault; a naming difference, not a behavioural one.CJK_length,listing_hl*,schemes_*,w32_dir_enc,menu,rx_todo/tag_format— syntax internals, menu, or Vim/Win shims.
Notes
- Audit confidence: upstream defaults pulled from vimwiki
master; a couple of values (CJK_length,links_space_char) were summarized loosely but don't affect the gap list. - nuwiki adds some commands with no upstream equivalent (e.g.
:NuwikiFindOrphans) — additive, not divergences. - Re-audited 2026-05-31 (config / mappings / commands, three parallel agents
against vimwiki
master). All recent P1/P2/P3 fixes — split/tab link-follow,gl/gLchange-symbol + bare-gl/gLremove-checkbox,<CR>-family create-on-follow,<M-CR>badd,<Leader>w<Leader>t/m,VimwikiColorizearg+range, colour-span conceal — confirmed present and correct in both clients. New gaps from that pass (diary-note-count,-completespecs,<D-CR>, global<Leader>w<Leader>m, plusglobal_ext/syntax-name notes) were added above; no previously-closed item regressed. - Re-audited again 2026-05-31 (third pass) after the range/bang/visual and
diary-week work. All those fixes confirmed in both clients; no regressions.
Confirmed Neovim does bind the text objects (
nuwiki.textobjects.attach). Found + fixed an oversight (NeovimVimwiki/NuwikiIndexstill readvim.v.count; now<count>, completing theTabIndexfix). New gaps logged:VimwikiSplitLink/VSplitLinkmissing-nargs=*; mouse-maps-opt-in noted as an intentional divergence. Config pass found no new gaps; all shipped defaults match upstream except the documentedsyntax-name divergence.