Files
nuwiki/development/vimwiki-gap.md
T
2026-06-03 13:40:31 +00:00

58 KiB
Raw Blame History

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 masterautoload/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 commandsVimwikiSplitLink, VimwikiVSplitLink, VimwikiGoBackLink, VimwikiTabnewLink, VimwikiTabDropLink exist 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 in autoload/nuwiki/commands.vim; true :tab drop tab-reuse wired in both clients (lua/nuwiki/commands.lua open_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- gl1 gla gli … plus gL…) has no nuwiki keymap; only :…ChangeSymbol* commands exist. Fix: added normal-mode gl{-,*,#,1,i,I,a,A} (item) + gL… (whole list) in lua/nuwiki/keymaps.lua lists group and ftplugin/vimwiki.vim Vim-branch lists block, wired to the existing list_change_symbol. 1) stays command-only (upstream shadows it with 1.); 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 bare gl/gL to remove-checkbox-item / -in-list for exact upstream parity (lua/nuwiki/keymaps.lua lists block, ftplugin/vimwiki.vim Vim-branch lists block); they share the gl… prefix so they fire after timeoutlen, just like upstream. Remove-done is now command-only: :NuwikiRemoveDone (current list) gained a -bang so :NuwikiRemoveDone! reaches the whole-buffer sweep that gL<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-lsp readfile()s it for the quickfix entry (autoload/lsp/utils/location.vim), which throws E484 for a missing file and aborts the jump (→ no create-on-follow); coc, lacking an explicit open command, fell back to the user's coc.preferences.jumpCommand (→ new tab). The server itself was correct throughout — verified over JSON-RPC that textDocument/definition returns a synthesised location for missing pages and resolves existing links. Fix: stop delegating. nuwiki#commands#follow_link_or_create() / follow_link_drop() now resolve textDocument/definition directly and open the URI ourselves via a shared s:open_definition(open_cmd) helper (autoload/nuwiki/commands.vim) — :edit for <CR>, tab drop for <C-S-CR>. :edit opens 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 by cr.follow_opens_missing_page_in_current_window in development/tests/test-keymaps-vim.vim; manually reproducible via development/start-vim.sh (vim-lsp) and development/start-vim-coc.sh (coc).
  • coc.nvim reported valid links as broken / couldn't resolve — only via the new development/start-vim-coc.sh launcher: its generated coc-settings.json registered the server with command/filetypes but no initializationOptions, so wiki_root never reached the server and it resolved links against its default (~/vimwiki). Fix: the launcher now emits initializationOptions and settings.nuwiki mirroring the vim-lsp client (autoload/nuwiki/lsp.vim s: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 own coc-settings.json per the README snippet.)

P2 — Moderate (real users hit these)

  • <Leader>w<Leader>t collision — upstream = today-in-new-tab; nuwiki bound both <Leader>w<Leader>t and <Leader>w<Leader>m to tomorrow's diary. Fix: <Leader>w<Leader>t now calls diary_today_tab (today in a new tab), <Leader>w<Leader>m stays tomorrow — exact upstream parity (lua/nuwiki/keymaps.lua, ftplugin/vimwiki.vim; docs + README updated). Covered by diary.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-wiki toc_header/toc_header_level, links_header/links_header_level, tags_header/tags_header_level (defaults Contents/Generated Links/Generated Tags, level 1 — matching upstream; this also corrects the tags index heading from TagsGenerated Tags). A caption_line(name, level) helper emits the markers; the text + level thread through toc_edit/links_edit/tag_links_edit (+ the auto_toc save hook) and find_section_range matches the configured text so regen stays idempotent. Tests: captions_honour_custom_header_and_level (link_health.rs) + config round-trip in index_and_config.rs.
  • On-save autoregen family — added per-wiki auto_generate_links, auto_generate_tags, auto_diary_index (all default false, like upstream), wired into the did_save hook (crates/nuwiki-lsp/src/lib.rs) mirroring auto_toc: regen runs only when the section already exists (links_rebuild_edit / tag_links_rebuild_edit), and auto_diary_index regenerates the diary index when a dated diary entry is saved (diary_generate_index_edit, which handles an open/closed/absent index file). auto_tags is 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 hardcoded const '-'. Fix: ListSyms gained a rejected field + new_with_rejected() (crates/nuwiki-core/src/listsyms.rs); per-wiki listsym_rejected key (default -) added to WikiConfig; 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 in index_and_config.rs.
  • custom_wiki2html (+_args) and base_url — external HTML converter hook + export URL prefix. Fix: HtmlConfig (config.rs) gained custom_wiki2html, custom_wiki2html_args, base_url (all default "", round-tripped through RawWiki/From/defaults). write_page (commands.rs) now takes a force flag and, when custom_wiki2html is set, hands the page to run_custom_wiki2html() — which shells out via sh -c with 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. force is threaded through export_current (false), export_all (its own flag), and the did_save auto-export path (false). write_rss now prefixes the channel + item links with base_url (<base_url><diary_rel_path>/<date>.html) when set, falling back to the entry's file:// 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>w was hardcoded across the map layer. Fix: mirror vimwiki's g:vimwiki_map_prefix with a configurable prefix (Neovim map_prefix setup option, default '<Leader>w'; Vim g: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: new test-keymaps-vim-prefix.vim harness (21 cases, driven by test-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 upstream ftplugin/vimwiki.vim). Fix: added normal-mode <M-CR>badd_link in the links group of both clients (lua/nuwiki/keymaps.lua, ftplugin/vimwiki.vim; docs + README updated). Covered by map[n].<M-CR> in both keymap harnesses.
  • diary_caption_level default divergence — nuwiki defaulted to 1, upstream 0. Fix: wiki_defaults() in crates/nuwiki-lsp/src/config.rs now defaults diary_caption_level: 0 (year captions top-level, months one below), still per-wiki overridable. Test defaults_carry_vimwiki_per_wiki_keys updated.
  • diary_start_week_day — removed in c63ec67 (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 key diary_weekly_style: iso (default — YYYY-Www, Monday-based, nuwiki's original) or date/vimwiki (YYYY-MM-DD of the week-start day, upstream parity, honouring the restored diary_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.rs diary_open_relative + next/prev pivot) use it. Defaults keep existing nuwiki weekly files working (no breaking change). Covered by calendar tests in crates/nuwiki-core/tests/diary.rs and the config round-trip in crates/nuwiki-lsp/tests/index_and_config.rs.

P3 — Niche / low impact

Commands

  • VimwikiGoto/NuwikiGoto were -nargs=1 (new, 2026-06-03 re-audit; fixed same day) — upstream is -nargs=* (ftplugin/vimwiki.vim:337); nuwiki's four defs were -nargs=1, so :VimwikiGoto (no arg) raised E471 and :VimwikiGoto My Page (spaced name) raised E488 — and the handler's empty-arg input('Goto page: ') prompt fallback was unreachable. Fix: all four defs (Vim+Neovim × Vimwiki*/Nuwiki*) are now -nargs=* (keeping -complete=…pages); <q-args> joins a spaced name and an empty arg reaches the prompt. Both handlers already prompt on empty. Covered by cmd.VimwikiGoto_accepts_multiword (test-keymaps-vim.vim).
  • VimwikiRenameFile -nargs=? (new, 2026-06-03 re-audit; fixed 2026-06-03) — upstream is -nargs=? -complete=…file; nuwiki's defs were bare, so :VimwikiRenameFile foo raised E488. Fix: all four defs are now -nargs=? (arg accepted-and-ignored — rename still delegates to the LSP rename request, which drives its own prompt). No more E488.
  • VimwikiVar/NuwikiVar (fixed 2026-06-03) — config-var get/set introspection (upstream vimwiki#vars#cmd). Fix: global command in both clients — no arg prints the client config (Vim: every g:nuwiki_*; Neovim: the resolved setup() options), one arg gets a key, two+ sets it (server-backed settings need a restart, which the echo notes). nuwiki#commands#var / commands.var. Covered by cmd.VimwikiVar_sets_global (vim) / cmd.VimwikiVar_sets_option (nvim) + surface.{Vimwiki,Nuwiki}Var.
  • VimwikiShowVersion (fixed 2026-06-03) — added global VimwikiShowVersion/NuwikiShowVersion (both clients) echoing g:nuwiki_version (0.1.0) + the host editor, via nuwiki#commands#show_version / commands.show_version. Covered by surface.{Vimwiki,Nuwiki}ShowVersion in both keymap harnesses.
  • VimwikiReturn/NuwikiReturn (fixed 2026-06-03) — upstream exposes the smart-<CR> continuation as a command; nuwiki had it only as the insert-mode <expr> mapping. Fix: buffer-local -nargs=* command in both clients that enters insert at EOL and triggers the same <CR> mapping (return_cmd / vimwiki_return), continuing the list item / table row. Upstream's mode-flag args are accepted but unused (continuation is inferred from the line). Covered by surface.{Vimwiki,Nuwiki}Return.
  • VimwikiTableAlignQ vs AlignW (fixed 2026-06-03; earlier framing corrected) — the earlier note ("gqq align vs gww align-without-resize") mis-read upstream: vimwiki#tbl#align_or_cmd('gqq'|'gww') calls the same vimwiki#tbl#format when on a table — the only difference is the non-table fallback (normal! gqq vs normal! gww, native paragraph-format; gw keeps the cursor put). nuwiki had both unconditionally call table_align, so off a table they did nothing. Fix: a shared table_align_or_cmd(cmd) (both clients) aligns when on a table row, else runs normal! <cmd>gqq/gq1 + VimwikiTableAlignQgqq; gww/gw1 + VimwikiTableAlignWgww; NuwikiTableAligngqq. (The -nargs=? attribute + the partial-column 2 arg, accepted-and-ignored, are unchanged; true partial-column align stays a separate deeper table feature.) Covered by cmd.search_and_tablealign_nargs (test-keymaps-vim.vim) + the table alignment cases that still route through it.
  • VimwikiTable was -nargs=1 vs upstream -nargs=* (couldn't pass cols+rows). Fix: all four defs (Vim+Neovim × Vimwiki+Nuwiki) are now -nargs=* and forward <f-args> to table_insert (which already parsed cols/rows). Covered by cmd.VimwikiTable_passes_cols_and_rows (test-keymaps.lua, asserts a :VimwikiTable 4 3 → 4-col, 3-row table) and cmd.VimwikiTable_accepts_cols_rows (test-keymaps-vim.vim, no E488). Follow-up (same pass): the no-arg default cols diverged (nuwiki 3 vs upstream 5; rows 2 already matched — confirmed against upstream vimwiki#tbl#create). Aligned both backing fns to default 5 cols.
  • VimwikiListChangeLvl was -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 existing over_range helper. Covered by cmd.ListChangeLvl_range_indents (test-keymaps.lua) and cmd.VimwikiListChangeLvl_accepts_range_and_args (test-keymaps-vim.vim, no E481/E488).
  • Neovim follow_link_or_create "botched" (new, 2026-05-31 command-attribute audit; FALSE ALARM — verified + tidied) — the audit mis-read the function. lua/nuwiki/commands.lua had a single, correct definition that already did the bare-word → [[link]] wrap (via wrap_cword_as_wikilink) and was dispatched by the <CR> keymap — there was no dead pos_args() code and no s:wrap_word_under_cursor (that name doesn't exist). The only real artifact was two separate vim.lsp.buf.definition() call sites for the "inside a wikilink" and "nothing to wrap" branches. Done: collapsed to one tail call (if not cursor_inside_wikilink() and wrap_cword_as_wikilink() then return end; vim.lsp.buf.definition()) — behaviour identical (wrap still skipped when already inside a link, via short-circuit), just tidier. No <CR> behaviour change.
  • Neovim *Link Ex-commands skip the bare-word wrap (new, same audit; broader than first noted) — not just Split/VSplit: all eight Neovim Ex-command link defs (Vimwiki/Nuwiki × FollowLink/SplitLink/ VSplitLink/TabnewLink) dispatched to raw lua vim.lsp.buf.definition(), bypassing the wrap + create-on-follow that the Vim commands and the Neovim <CR> family provide. Fix: all eight now call lua require('nuwiki.commands').follow_link_or_create() (keeping their split/vsplit/tabnew prefixes); TabDropLink already used follow_link_drop and is unchanged. Covered by cmd.VimwikiFollowLink_wraps_bare_word + cmd.VimwikiSplitLink_wraps_bare_word (test-keymaps.lua).
  • VimwikiRebuildTags -bang ("rebuild all") — :…RebuildTags! now passes { all: true }; the server (tags_rebuild) re-indexes every configured wiki (via wikis_snapshot()) instead of just the current one. -bang added to all four command defs.
  • :VimwikiSearch / VWS now scope to the wiki (fixed 2026-06-03) — upstream's search runs over the wiki's files, not the editor's CWD; nuwiki's lvimgrep /<args>/ ** searched CWD-relative **. Fix: both commands route through nuwiki#commands#search / commands.search (both clients), which resolves the wiki whose root contains the current file (else the first configured wiki, else CWD) and lvimgreps <root>/**/*<ext> into the location list; an empty pattern reuses the last search (@/); a no-match is caught and reported (no raw E480). Added a NuwikiSearch alias alongside VimwikiSearch/VWS. The -nargs=* attr fix (2026-06-03) is folded in. nuwiki uses Vim's lvimgrep — the same mechanism vimwiki uses. Minor presentation divergence: nuwiki populates + opens the location list (/j, then :lopen) showing all matches, whereas upstream jumps to the first match and leaves the list closed. Covered by cmd.search_and_tablealign_nargs (test-keymaps-vim.vim).
  • VimwikiChangeSymbolTo / VimwikiListChangeSymbolI lack -range (new, 2026-05-31 re-audit; fixed same day) — upstream both carry -range -nargs=1; nuwiki had -nargs=1 only, so a visual-range invocation raised E481. Fix: all four defs (Vim+Neovim × the Vimwiki*/Nuwiki* current-item forms — VimwikiChangeSymbolTo/ListChangeSymbolI and NuwikiChangeSymbol) are now -range -nargs=1 passing <line1>,<line2>, <q-args> to a new list_change_symbol_range(symbol, l1, l2) helper that loops over_range (both clients). ChangeSymbolInListTo/InList stay range-less (and correctly keep whole_list=true). Covered by cmd.ChangeSymbolTo_range_sets_marker (test-keymaps.lua) + cmd.VimwikiChangeSymbolTo_accepts_range (test-keymaps-vim.vim).
  • VimwikiGenerateLinks lacks -nargs=? (new, 2026-05-31 re-audit; fixed same day) — upstream takes an optional rel-path arg; nuwiki was bare, so :VimwikiGenerateLinks foo raised E488. Fix: all four defs are now -nargs=?; links_generate([path]) (both clients) dispatches the existing server command nuwiki.links.generateForPath (with {path}) when an arg is given, else nuwiki.links.generate — a real path filter, not accepted-and-ignored (the server's links_generate_for_path already implements subtree scoping). Covered by cmd.GenerateLinks_accepts_optional_path (test-keymaps.lua) + cmd.VimwikiGenerateLinks_accepts_path (test-keymaps-vim.vim).
  • NuwikiGenerateTags missing (new, 2026-05-31 re-audit; fixed same day) — added NuwikiGenerateTags (Vim + Neovim) mirroring VimwikiGenerateTagstags_generate_links, with -nargs=? + -complete=…tags. Covered by cmd.NuwikiGenerateTags_exists (both harnesses).
  • VimwikiIndex family now has global entry points (fixed 2026-06-03) — upstream defines the Index family in plugin/; nuwiki had Index/TabIndex only buffer-local (so a wiki couldn't be opened from a non-wiki buffer; only :…UISelect/:…ShowVersion were global). Fix: global Vimwiki/NuwikiIndex + Vimwiki/NuwikiTabIndex (with -count for the wiki number) added in plugin/nuwiki.vim (Vim) and init.lua _setup_global_commands (Neovim), dispatching to the existing wiki_index/wiki_tab_index. The buffer-local copies still shadow them inside wiki buffers. Covered by cmd.global_entry_points in both harnesses.
  • VimwikiColorize / NuwikiColorize drop their argument in the Neovim branch — both are -nargs=1, but the Neovim defs called colorize() 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 from vim.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 explicit visual flag; 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) raised E481: No range allowed. All four command defs are now -range and pass the range to the handler — a ranged (visual) invocation wraps the '</'> selection on both clients; the x-mode <Leader>wc mapping passes the visual flag directly. Covered by cmd.VimwikiColorize_uses_arg, cmd.Colorize_ignores_stale_visual_selection, colorize.visual_wraps_selection, cmd.Colorize_range_wraps_selection in test-keymaps.lua and colorize.{command_wraps_cword,visual_wraps_selection, ranged_command_no_error} in test-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.vim nuwiki#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 :colorscheme reload), an ftplugin TextChanged autocmd, and directly from colorize() for immediate effect. The LSP @vimwikiColor token no longer links to Constant so 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 by colorize.conceal_hides_tags_shows_text (both harnesses) and colorize.command_conceals_immediately (test-keymaps-vim.vim).
  • VimwikiTableMoveColumn{Left,Right} dispatch to different backing names per client — converged: the Vim-branch commands now call nuwiki#commands#table_move_column_left/right (existing aliases over the table_move_left/right impls), matching the Neovim branch.
  • NuwikiTabIndex lacks -count in the Neovim branch — added -count; also switched the Neovim Index/TabIndex family (Vimwiki/NuwikiTabIndex and Vimwiki/NuwikiIndex) from vim.v.count (always 0 in command context) to <count>, so a :2NuwikiIndex / :3NuwikiTabIndex count is now honored. (The Index half 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 by cmd.toggle_list_item_range in test-keymaps.lua.
  • Diary-note family lacks -count (new, 2026-05-31 re-audit; fixed same day) — upstream's :Vimwiki{Make,TabMake,MakeYesterday,MakeTomorrow}DiaryNote
    • VimwikiDiaryIndex (and the Nuwiki* forms) carry -count=0, where the count selects the wiki number. Fix: a real wiki selector end-to-end —
    • Server (commands.rs): OptionalUriArg gained an optional wiki selector and resolve_diary_wiki(backend, uri, wiki) now prefers it (via the existing resolve_wiki_selector, reused — accepts a 0-indexed number) over the buffer URI; used by diary_open_relative + diary_open_index.
    • Clients: s:diary_open/diary_open (both clients) and diary_today/today_tab/yesterday/tomorrow/index take a count and send {wiki: count-1} when > 0. All 22 diary-note + DiaryIndex command defs across the 4 contexts are now -count=0 passing <count>. diary_next/ diary_prev correctly ignore the count. Tests: cmd.VimwikiMakeDiaryNote_has_count (test-keymaps.lua), cmd.Vimwiki{MakeDiaryNote,DiaryIndex}_accepts_count (test-keymaps-vim.vim). (The Index/TabIndex family was already -count=0, mapping the count to the wiki number via wiki_index/wiki_tab_index.)
  • VimwikiToggleRejectedListItem / NuwikiToggleRejected lack -range (new, 2026-06-02 re-audit; fixed same day) — upstream is -range (ftplugin/vimwiki.vim:351) and binds it in visual mode (glx, line 543); nuwiki's four defs were bare, so :'<,'>VimwikiToggleRejectedListItem raised E481 and a ranged invocation acted cursor-only. This is the same -range class fixed above for ToggleListItem/Increment/DecrementToggleRejected was simply missed in that pass. Fix: all four defs (Vim+Neovim × Vimwiki*/ Nuwiki*) are now -range, routed through a new reject_list_item_range(l1, l2) looping over_range (both clients), mirroring toggle_list_item_range. The visual glx keymap stays cursor-only, consistent with its <C-Space>/ gln/glp siblings (the keymap-range question is a separate, family-wide item). Covered by cmd.reject_list_item_range (test-keymaps.lua) + cmd.VimwikiToggleRejectedListItem_accepts_range (test-keymaps-vim.vim).
  • VimwikiRemoveDone regained upstream's -range (new, 2026-06-02 re-audit; fixed 2026-06-02) — upstream is -range (ftplugin/vimwiki.vim:362, "remove done items in the selection"); nuwiki had repurposed it to -bang only, so :'<,'>VimwikiRemoveDone raised E481. Fix: all four defs are now -bang -range, dispatched by nuwiki#commands#remove_done / commands.remove_done(bang, range, l1, l2) (both clients): ! → whole-buffer sweep (unchanged); an explicit range (<range> > 0, e.g. :'<,'>) → a new list_remove_done_range sending {range:[l1-1,l2-1]}; otherwise the cursor's current list. Server remove_done_edit gained an Option<(u32,u32)> range that filters whole-doc victims by start line (commands.rs). Tests: remove_done_ranged_restricts_to_selected_lines + remove_done_ranged_returns_none_when_range_has_no_done (commands_lists.rs), cmd.remove_done_range (test-keymaps.lua), cmd.VimwikiRemoveDone_accepts_range_and_bang (test-keymaps-vim.vim).
  • -complete= specs (new; done 2026-05-31 command-attribute pass) — upstream attaches command-line completion to several commands; nuwiki had none, so <Tab> at the : line never completed page / tag / colour names. Fix: new autoload/nuwiki/complete.vim with pure client-side, synchronous completers (config + filesystem, never the LSP, so they work under vim-lsp, coc and Neovim alike), wired via -complete=customlist,… into all branches:
    • nuwiki#complete#pagesVimwikiGoto/NuwikiGoto (globs every wiki root's *<ext> files → root-relative page names).
    • nuwiki#complete#colorsVimwikiColorize/NuwikiColorize (keys from a configured color_dic + colours already used in buffer color:… spans).
    • nuwiki#complete#tagsVimwikiSearchTags/GenerateTagLinks/ GenerateTags (+ Nuwiki forms) — scans the wikis' files for :tag: runs (the tag index lives in the server, which a synchronous completer can't query; the file scan reads the same source the server indexes). Divergence (documented): no file completer for VimwikiRenameFile — upstream completes a new-name arg because it renames itself, but nuwiki delegates renaming to the LSP rename request (:LspRename/coc), which drives its own prompt and takes no filename argument. Covered by complete.pages_globs_and_filters, complete.tags_scans_wiki_files, complete.colors_from_buffer_spans (test-keymaps-vim.vim).
  • Minor command-attribute gaps (new) — all now real, not cosmetic: VimwikiNormalizeLink is -nargs=? and with 1 wraps the '</'> selection (wrap_visual_as_wikilink, both clients; the x-mode + passes it too); VimwikiCheckLinks is -range and a ranged invocation filters the report to the current buffer's selected lines; VimwikiColorize -nargs=1-nargs=* (bare :VimwikiColorize prompts). Covered by normalize.visual_wraps_selection (vim) / cmd.normalize_link_visual_wraps_selection (nvim).
  • VimwikiSplitLink / VimwikiVSplitLink lack -nargs=* (new, 2026-05-31 third re-audit; corrected during the 2026-05-31 command-attribute pass) — the earlier note was wrong: upstream's optional args are NOT a target link but reuse_other_split_window (a:1) and move_cursor (a:2) — the link is always the one under the cursor (vimwiki#base#follow_link). nuwiki's defs took no args, so :VimwikiSplitLink 0 1 (upstream muscle memory / migrated scripts) raised E488. Fix: all eight defs (both clients × Split/VSplit × Vimwiki/Nuwiki) are now -nargs=*, restoring signature parity. Divergence (documented): the two flags are accepted but not acted on — nuwiki always opens a fresh split and follows in it, because the follow is an async LSP jump and honoring move_cursor/reuse would require threading a completion callback through that path (disproportionate for this rarely-used arg form; the no-arg case — the 99% path — is fully equivalent). Covered by cmd.VimwikiSplitLink_accepts_args (test-keymaps-vim.vim, no E488).

Config

  • autowriteall (new, 2026-06-03 re-audit; fixed same day) — upstream global (vars.vim:136, default 1/ON): while a wiki buffer is current, mirror Vim's global 'autowriteall' (auto-save a modified buffer on page switch / :make), restoring the prior value on leave (upstream plugin/vimwiki.vim:165-166,39). nuwiki had no equivalent → out-of-box behavioral divergence. Fix: client-side, both clients — Neovim lua/nuwiki/ftplugin.lua (setup_autowriteall, BufEnter/BufLeave save+set/ restore) gated by the autowriteall setup option (default true); Vim ftplugin/vimwiki.vim (NuwikiAutoWriteAll augroup) gated by g:nuwiki_autowriteall (default 1). Covered by config.autowriteall_applied in both harnesses.
  • table_auto_fmt (new, 2026-06-03 re-audit; fixed same day) — upstream global (vars.vim:183, default 1/ON): re-format the table under the cursor on InsertLeave (upstream plugin/vimwiki.vim:330). nuwiki had no equivalent (tables formatted only via gqq/gww). Fix: client-side InsertLeave autocmd, gated by config — Neovim table_auto_fmt setup option (default true, ftplugin.lua setup_table_auto_fmt); Vim g:nuwiki_table_auto_fmt (default 1, NuwikiTableAutoFmt augroup). Both guard on the cursor line containing | before the (async LSP) table_align, so non-table edits don't round-trip. Covered by config.table_auto_fmt_autocmd in both harnesses. (Divergence: nuwiki's table_align is an async server edit, vs upstream's synchronous format — the result lands a beat after Esc.)
  • table_reduce_last_col (fixed 2026-06-03) — per-wiki flag (default off): column_widths clamps the last table column to width 1 so it isn't padded to fill; threaded through table_align_edit/table_move_column_edit and both table commands. Test table_reduce_last_col_keeps_last_column_minimal.
  • user_htmls (fixed 2026-06-03)allToHtml now prunes orphan HTML (no wiki source) under html_path (export_ops::prune_orphan_html), skipping the CSS file and any basename in user_htmls; gated on !html_filename_parameterization (mirrors upstream). Test prune_orphan_html_deletes_sourceless_keeps_protected.
  • hl_headers / hl_cb_checked (resolved as divergence 2026-06-03) — upstream opt-in toggles (default off) that turn ON per-level header / checked- checkbox highlighting. nuwiki already highlights headers per-level (@vimwikiHeading.level1..6) and checkboxes always-on via LSP semantic tokens — richer than the upstream toggles and on by default. Implementing the toggle faithfully (default off) would remove nuwiki's coloring, a regression. Folded into the LSP-highlighting divergence family (with maxhi).
  • list_margin — buffer-side parity + markdown default (new, 2026-06-02 re-audit; fixed 2026-06-02) — upstream (vars.vim:516, doc vimwiki.txt:2667): left-margin width in spaces for buffer-side generated lists — generated links (:VimwikiGenerateLinks), the TOC, the tags index, and the diary index — default -1 but 0 for markdown wikis (vars.vim:651). nuwiki had instead applied it only as an HTML CSS margin-left:<n>em. Fix (true-parity, chosen over keeping the HTML meaning): dropped the HTML em-margin entirely (renderer list_margin field/method + the render_page_html param removed) and now prepend max(0, list_margin) leading spaces to every generated bullet in build_toc_text / build_links_text / build_tag_links_text / diary::build_index_body (the TOC keeps its per-depth nesting after the base margin); headings stay at column 0. From<RawWiki> derives 0 for markdown wikis when the key is unset. Divergence (documented in the field doc): upstream resolves a negative value to the buffer's 'shiftwidth', which the server can't observe, so negatives collapse to zero indent (matches nuwiki's prior default output). Tests: list_margin_indents_generated_bullets_not_headings (link_health.rs), markdown_wiki_derives_list_margin_zero_when_unset (index_and_config.rs).
  • diary_months (new, 2026-06-02 re-audit; fixed 2026-06-02) — upstream global (vars.vim:141) maps month numbers → names for the generated diary-index tree; nuwiki hardcoded English. Fix: per-wiki diary_months: Vec<String> (default the 12 English names) on WikiConfig/RawWiki, threaded into build_index_body; a missing/empty slot falls back to the English month_name. Tests: build_index_body_uses_custom_diary_months + …_falls_back_per_missing_month_slot (diary.rs), config round-trip (index_and_config.rs).
  • markdown_header_style (new, 2026-06-02 re-audit; deferred) — upstream global (vars.vim:174, default 1): blank lines after a generated header in markdown syntax. Intentionally deferred: nuwiki's generators emit vimwiki syntax only (caption_line always writes = H =, never # H), so there's no markdown header to attach the style to yet. This is part of a broader "markdown generated-content" gap — markdown-style heading generation comes first, then this blank-line control on top. See the divergence note below.
  • diary_caption_level = -1 now representable (new, 2026-06-02 re-audit; fixed 2026-06-02) — upstream allows -1 (vars.vim:507 min: -1); nuwiki had typed the field as u8, so a -1 config failed deserialization and silently fell back to 0. Fix: widened to i8 across WikiConfig/RawWiki/WikiDefaults; build_index_body clamps < 0 to a base tree level of 0. (nuwiki builds the index tree from dates, so upstream's -1 = "no per-page captions" semantic doesn't apply — it just clamps; noted in the field doc.) Tests: diary_caption_level_accepts_negative (index_and_config.rs), build_index_body_clamps_negative_caption_level_to_zero (diary.rs).
  • auto_header (fixed 2026-06-03) — new wiki pages get a = Stem = H1 from the filename, skipping the wiki/diary index. Client BufNewFile autocmd, both clients (init.lua + plugin/nuwiki.vim; auto_header handlers in both command layers). Default off.
  • create_link (fixed 2026-06-03) — server resolve_target_uri only synthesises a missing-page URI when create_link is set; off → following a missing link is a no-op. Default true.
  • dir_link (fixed 2026-06-03) — a [[dir/]] link resolves to <dir>/<dir_link><ext> when set, else opens the directory (resolve_target_uri, using LinkTarget.is_directory).
  • auto_chdir (fixed 2026-06-03):lcd into the owning wiki root on buffer enter; both clients (ftplugin.lua setup_auto_chdir / NuwikiAutoChdir augroup), via wiki_root_for resolvers. Default off.
  • bullet_types / cycle_bullets (fixed 2026-06-03) — per-wiki bullet_types (default -,*,#) + cycle_bullets (default off): on indent/dedent of an unordered item, change_level_edit rotates the glyph through bullet_types (depth-keyed ring). Tests cycle_bullets_*.
  • commentstring (fixed 2026-06-03) — aligned to upstream's no-space %%%s (ftplugin/vimwiki.vim); comment parsing is independent (lexer).
  • color_tag_template (fixed 2026-06-03):VimwikiColorize now wraps via a configurable template (color_tag_template / g:nuwiki_color_tag_template, split on __CONTENT__ with __COLORFG__ → colour), both clients; colour spans render in HTML export via the valid_html_tags span passthrough. color_dic now ships a populated default palette (red/green/blue/…) instead of empty.
  • rss_max_items / rss_name (fixed 2026-06-03)HtmlConfig fields (default 10 / rss.xml); rss_path honours rss_name, write_rss caps at rss_max_items (0 = unlimited).
  • RSS feed structure fidelity (fixed 2026-06-03)write_rss now emits upstream's shape: xmlns:atom; channel <link> → the diary index page (base_url + diary_rel + diary_index + .html); <atom:link rel="self">; channel + item <pubDate> (RFC-822, derived from the diary date via to_days_epoch weekday math); per-item <guid isPermaLink="false"> with the bare date; and a <![CDATA[…]]> rendered-HTML body per entry (render_entry_body reads+parses+renders with the wiki's colour/valid-tag/ emoji opts; cdata_safe splits ]]>). Tests: write_rss_uses_base_url_for_public_links, write_rss_honours_rss_name_and_max_items.
  • emoji_enable (fixed 2026-06-03) — render-time :alias: → glyph substitution (curated common set, substitute_emoji/emoji_for), default on, wired via with_emoji from HtmlConfig. (Divergence: upstream's emoji_enable is buffer conceal + completion; nuwiki implements it as export substitution — the user-facing emoji output — which is the practical intent.)
  • html_header_numbering (+_sym) — numbered HTML headers. Fix: HtmlConfig (config.rs) gained html_header_numbering: u8 (start level, 0 = off) + html_header_numbering_sym: String (trailing symbol), both round-tripped through RawWiki/defaults. A HeadingNumberer in nuwiki-core/src/render/html.rs walks top-level headings in document order, bumping per-depth counters and emitting a dotted prefix (1, 1.1, …); render_body numbers only document-level headers (nested ones in lists/ quotes stay unnumbered, matching upstream's line scan). Wired via with_header_numbering() from export.rs. Tests: render_page_html_numbers_headers_when_enabled, …_numbering_skips_headers_above_start_level, …_no_header_numbering_by_default (html_export.rs) + config round-trip + defaults (index_and_config.rs).
  • valid_html_tags (fixed 2026-06-03) — HTML export passes the allowlisted inline tags through verbatim and escapes the rest, via a render-time tag-aware escaper (write_escaped_allowing/match_allowed_tag), mirroring upstream's s:safe_html_line regex (no parser change). Default b,i,s,u,sub,sup,kbd,br,hr,div,center,strong,em; span always allowed. Tests in html_export.rs.
  • generated_links_caption + toc_link_format (fixed 2026-06-03)generated_links_caption emits [[page|FirstHeading]] from a page→heading map (ops::page_captions); toc_link_format 1[[#anchor]] (no description) vs 0[[#anchor|title]]. markdown_link_ext stays deferred — part of the markdown generated-content cluster (see the divergence note below).
  • list_ignore_newline / text_ignore_newline (fixed 2026-06-03) — the parser now emits a dedicated InlineNode::SoftBreak for soft line breaks (instead of collapsing to Text(" ")); every inline consumer (visitor, renderer, semantic tokens, nav, text extraction) treats it as whitespace. The HTML renderer renders it as a space (= true, default) or <br /> (= false), distinguishing paragraph (text_ignore_newline) from list-item (list_ignore_newline) context via render_inlines_break + with_newline_handling. Test render_page_html_ignore_newline_controls_soft_breaks (html_export.rs).

Mappings

  • Visual <CR> normalize-link (fixed 2026-06-03) — upstream binds visual <CR> to <Plug>VimwikiNormalizeLinkVisualCR:VimwikiNormalizeLink 1, identical to visual +. nuwiki had only visual +. Fix: added x <CR> → the same wrap-selection handler (normalize_link(true)) in both clients (lua/nuwiki/keymaps.lua, ftplugin/vimwiki.vim). Covered by map.visual_cr_wraps_selection in both harnesses.
  • Insert <S-CR> multiline list item (fixed 2026-06-03) — upstream's insert <S-CR> (VimwikiReturn 2 2kbd_cr with no new marker) continues the current list item on a new line indented to align under the item's text. Fix: new smart_shift_return (both clients, <expr> insert mapping): on a list item returns <CR> + (indent +) marker-width + 1 (+4 for a checkbox) spaces — no marker; off a list item, a plain <CR>. Mirrors smart_return's auto-indent handling (column math verified against upstream's s:text_begin). Both smart_return and smart_shift_return also gained upstream's pumvisible() guard (return a plain <CR> to accept a completion). Minor divergence: <S-CR> on an empty bullet emits the aligned no-marker continuation rather than upstream's "blank line above, keep the marker" — a niche case; the common non-empty continuation is exact. Covered by map.insert_shift_cr_multiline (test-keymaps.lua) + cr.shift_cr_multiline{,_checkbox} (test-keymaps-vim.vim).
  • gLH / gLL / gLR (fixed 2026-06-03) — upstream binds these as case-variant aliases of gLh / gLl / gLr (dedent / indent whole item, renumber all lists; upstream ftplugin/vimwiki.vim:553,555,561). nuwiki had only the lowercase forms. Fix: added the uppercase-suffix aliases (same actions) in both clients (lua/nuwiki/keymaps.lua, ftplugin/vimwiki.vim). Covered by map[n].gL{H,L,R} in both harnesses.
  • <D-CR> (Cmd+Return) tab-drop alias (new, 2026-05-31 re-audit) — added <D-CR>follow_link_drop alongside <C-S-CR> in both clients (lua/nuwiki/keymaps.lua, ftplugin/vimwiki.vim); docs updated. Covered by map[n].<D-CR> in both harnesses.
  • <Leader>w<Leader>m missing from the Vim global map block (new)plugin/nuwiki.vim's global family now binds <Leader>w<Leader>m (tomorrow) and <Leader>w<Leader>t opens today in a new tab (was the same collision the buffer-local maps had). Covered by map[n].<Leader>w<Leader>m in both harnesses.
  • <Leader>w<Leader>m missing from the Neovim global map block (new, 2026-05-31 map_prefix audit) — the same collision survived in lua/nuwiki/init.lua's _setup_global_mappings: <prefix><Leader>t was bound to tomorrow and <prefix><Leader>m (tomorrow) was absent entirely. Fixed so <prefix><Leader>t opens today in a new tab and <prefix><Leader>m is tomorrow — now consistent with the Vim global side + upstream. Covered by global_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 / syntax key automatically.
  • folding (upstream string) — nuwiki uses folding = 'lsp' | 'expr' | 'off' backed by LSP foldingRange.
  • key_mappings (dict) — replaced by Lua mappings.<group> + the g:nuwiki_no_<group>_mappings globals.
  • auto_tags (upstream 0) — upstream auto-updates an on-disk tag metadata file on save. nuwiki has no such file: the LSP re-indexes tags on every didChange, so tag search/jump/completion are always fresh. The setting's intent is therefore always satisfied (the auto_generate_tags regen 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 (upstream 1) — nuwiki's ftdetect always maps the configured wiki extension(s) to the vimwiki filetype regardless of location; there's no per-wiki "only inside the root" toggle. Effectively always-on, like nested_syntaxes.
  • syntax default name — nuwiki defaults the per-wiki syntax to vimwiki (its primary syntax) where upstream defaults to default; 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.
  • Default output-location derivations (noted 2026-06-02 re-audit) — where a key is unset, nuwiki derives a different default location than upstream, by design (root-relative, self-contained per wiki): path_html<root>/_html (upstream: sibling <wikidir>_html/); template_path<root>/_templates (upstream: global ~/vimwiki/templates/); diary_rel_pathdiary vs upstream diary/ (trailing slash only — paths are joined identically, so behaviour matches). Listed so future audits stop re-flagging them; users can still set any of these explicitly.
  • Generated-content syntax is vimwiki-only (for now) (noted 2026-06-02 re-audit) — the buffer-side generators (TOC, generated links, tags index, diary index) always emit vimwiki markup (= H = headings via caption_line), regardless of the wiki's syntax. Consequently the markdown-specific markdown_header_style (blank lines after a generated markdown header) has nothing to attach to and is deferred. This is a deliberate scoping choice, not an oversight: markdown generated-content (markdown-style headings, then markdown_header_style, markdown_link_ext in generated links) is planned as a single later effort. Until then, markdown wikis get vimwiki-syntax generated sections.

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/gL change-symbol + bare-gl/gL remove-checkbox, <CR>-family create-on-follow, <M-CR> badd, <Leader>w<Leader>t/m, VimwikiColorize arg+range, colour-span conceal — confirmed present and correct in both clients. New gaps from that pass (diary-note -count, -complete specs, <D-CR>, global <Leader>w<Leader>m, plus global_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 (Neovim Vimwiki/NuwikiIndex still read vim.v.count; now <count>, completing the TabIndex fix). New gaps logged: VimwikiSplitLink/VSplitLink missing -nargs=*; mouse-maps-opt-in noted as an intentional divergence. Config pass found no new gaps; all shipped defaults match upstream except the documented syntax-name divergence.
  • Re-audited 2026-06-02 (fourth pass — commands / config / mappings, three parallel agents) against vimwiki a54a300 (cloned locally; upstream had no commits since the 2026-05-31 pass, so this was miss-catching, not new upstream surface). Mappings: clean (~55 keys + the global family verified identical in both clients; no new gaps, no regressions). Commands: one real bug — VimwikiToggleRejectedListItem missing -range (fixed same day, see P3); plus VimwikiRemoveDone's lost -range logged as low-priority. Config: four new items logged (list_margin semantics/markdown-default, diary_months, markdown_header_style, diary_caption_level = -1); the default output-location derivations (path_html/template_path/ diary_rel_path) recorded as intentional divergences. No previously-closed item regressed.
  • Fixed the fourth-pass findings (2026-06-02), in order: VimwikiRemoveDone -range; list_margin reworked to upstream's buffer-side meaning (HTML em-margin dropped) + markdown-0 default; diary_months; diary_caption_level widened to i8. markdown_header_style deferred as part of the documented "generated-content is vimwiki-only" divergence. All five P1-blocking sections remain clear; only niche P3 items (and the deferred markdown work) are left.
  • Re-audited 2026-06-03 (fifth pass — commands / config / mappings, three parallel agents) against vimwiki a54a300 (still no upstream commits since 2026-04-30). Mappings: clean again (confirmed gLH/gLL/gLR correct; no new gaps, no regressions). Commands: one real bug — VimwikiGoto/NuwikiGoto were -nargs=1 (E471/E488 + unreachable prompt), fixed same day; plus VimwikiRenameFile's missing -nargs=? logged (subsumed by the LSP-rename divergence). Config: five new items — autowriteall and table_auto_fmt (both ON upstream → out-of-box divergences, implemented same day), table_reduce_last_col, user_htmls, hl_headers/hl_cb_checked logged; and a commentstring value-mismatch note. No previously-closed item regressed.
  • Cleared the entire P3 Commands section (2026-06-03): VimwikiRenameFile -nargs=?; VimwikiVar/NuwikiVar (get/set client config); VimwikiReturn/ NuwikiReturn (command form of smart-<CR>); the TableAlignQ vs AlignW split (corrected — the real difference is the non-table normal! gqq/gww fallback, now via table_align_or_cmd); :VimwikiSearch/VWS scoped to the wiki root; and global Index/TabIndex entry points. All client-side (no server change). Both harnesses green (Neovim 299, Vim 291/18/21).
  • Merged the calendar-support branch (2026-06-03): calendar-vim integration now wires g:calendar_action/g:calendar_sign to nuwiki's diary paths via a FileType vimwiki autocmd (plugin/nuwiki.vim, both clients), with a use_calendar opt-out (g:nuwiki_no_calendar / setup use_calendar = false). So use_calendar is no longer an intentional divergence — removed from that list above. See development/calendar-vim-integration.md.
  • Cleared the entire P3 Mappings section (2026-06-03): visual <CR> → normalize-link (same handler as visual +), and insert <S-CR> → multiline list item (smart_shift_return, continuation with no new marker aligned under the text). Both client-side, both clients, tested in both harnesses (Neovim 305, Vim 299/18/21). Only P3 Config items now remain open.
  • Closed the P3 Config section (2026-06-03), in groups: rss_name/rss_max_items
    • RSS structure fidelity, toc_link_format, generated_links_caption, table_reduce_last_col, color_dic palette, commentstring, auto_chdir; auto_header, create_link, dir_link; cycle_bullets/bullet_types; valid_html_tags (render-time allowlist passthrough), emoji_enable (export substitution), color_tag_template (configurable colorize wrap); user_htmls (orphan-HTML pruning). Intentional non-implementations: hl_headers/hl_cb_checked are superseded by nuwiki's always-on LSP per-level header + checkbox highlighting (like maxhi). Deferred: the markdown cluster (markdown_header_style, markdown_link_ext, markdown-style generated headers) remains one future effort; list_ignore_newline / text_ignore_newline = false (→ <br>) needs a parser-level soft-break node (the = true default, which is upstream's default, already works).

Codebase review findings (2026-06-03)

Full read-only review (4 parallel agents: dead code, optimization, docs, client) + verification. Clippy/compiler clean overall. Tracked here so we don't lose them; checked off as fixed.

P1 — user-facing / real bugs

  • R1 Docs: the 2026-06-03 config batch is undocumented in README.md
    • doc/nuwiki.txt (~22 options: per-wiki create_link/dir_link/ bullet_types/cycle_bullets/generated_links_caption/toc_link_format/ table_reduce_last_col/diary_months; HtmlConfig rss_name/rss_max_items/ valid_html_tags/list_ignore_newline/text_ignore_newline/emoji_enable/ user_htmls/color_tag_template; client auto_header/auto_chdir/ autowriteall/table_auto_fmt/use_calendar).
  • R2 wiki_root_for falls back to the first wiki when the file is under no wiki (autoload/nuwiki/commands.vim, lua/nuwiki/config.lua, inlined in search). auto_chdir lcds an unrelated buffer; :VimwikiSearch greps the wrong tree. Fix: return empty/nil on no match; callers no-op / fall back to CWD.

P2 — dead code, correctness, perf

  • R3 Color render chain unreached — the vimwiki parser never builds InlineNode::Color, so render_color/colors/with_colors/default_color_dic are dead on the parse path (verified). Fix: make render_color honour color_tag_template (so the field is consumed) + document ColorNode as a wired extension point (colour currently flows via :Colorize span + valid_html_tags).
  • R4 color_tag_template (Rust HtmlConfig) stored but never read — resolved by R3 (render_color consumes it).
  • R5 Wiki.config cloned wholesale on every wiki_for_uri — make it Arc<WikiConfig> (clone → refcount bump). Hot path.
  • R6 WorkspaceIndex::remove is O(n²) — scans every backlink/tag value vec per upsert. Track per-source contributed keys.
  • R7 auto_header ignores links_space_char — H1 uses the raw stem; a links_space_char='_' wiki gets = My_Page = not = My Page =.
  • R8 cursor_inside_wikilink off-by-one (commands.vim, commands.lua) — < len misses a ]]/[[ ending at EOL.
  • R9 Doc contradictions: doc/nuwiki.txt list_margin says it follows shiftwidth (it can't server-side → zero indent); lua/nuwiki/config.lua per-wiki key comment stale; gap-doc trailing "Deferred" note still lists list/text_ignore_newline though done.

P3 — cleanups, dedup, cold-path opts

  • R10 Dead #[allow(dead_code)] import-retention stubsconfig.rs _ensure_url_is_used, commands.rs _retain_tag_occurrence_import
    • export_ops::_silence_unused, lib.rs DocumentState.version. Remove stubs + narrow imports.
  • R11 Uncalled pub helpersregistry::register_arc, WorkspaceEditBuilder::edits, export::html_output_dir, index::diary_date_for_uri. Remove (verify zero callers).
  • R12 Duplicated span_of_inline ×3 (parser/nav/semantic_tokens) → one InlineNode::span() method. Also s:wiki_cfg duplicated (commands.vim+diary.vim) and wiki-root resolution inlined 3 ways.
  • R13 Cold-path opts — RSS render_entry_body re-reads/parses open docs + rebuilds renderer per entry; page_captions double-lookup + wasted sort; push_level_edit_for_line walks from offset 0 per line; diary next/prev rebuild+sort for one neighbor; diary_step double lock.
  • R14 auto_header Neovim loop fragile (for i=0,64, dead break) → use wiki_list(); add auto_header = false to lua config defaults.
  • R15 autoload/vimwiki/vars.vim shim ignores the wiki indexget_wikilocal(key, N) always returns wiki 0 (latent multi-wiki bug for third-party plugins).

P4 — trivial

  • R16 ftplugin/vimwiki.vim header comment says ftplugin/nuwiki.vim.
  • R17 :VimwikiNormalizeLink uses <args> not <q-args> → E121 on a non-numeric arg.
  • R18 lua/nuwiki/commands.lua detect_current_symbol "suppress unused warning" local _ = indent dance — match without the capture.