# vimwiki → nuwiki Parity Gaps Tracking document for closing the gap between nuwiki and the original [vimwiki](https://github.com/vimwiki/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) - [x] **Vim client split/tab link-follow commands** — `VimwikiSplitLink`, `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); `` repointed to tab-drop. - [x] **`gl` 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). - [x] **`gl` / `gL` 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` previously held (the only prior entry point). Normal-mode only. ## Regressions (reported by users — verify, then fix) - [x] **`` 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, `` opened the target in a **new tab** instead of the current window; (b) on **both** vim-lsp and coc, `` 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 ``, `tab drop` for ``. `: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). - [x] **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) - [x] **`wt` collision** — upstream = today-in-new-tab; nuwiki bound both `wt` and `wm` to tomorrow's diary. _Fix:_ `wt` now calls `diary_today_tab` (today in a new tab), `wm` 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`. - [x] **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 `Tags` → `Generated 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`. - [x] **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`). - [x] **`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`. - [x] **`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 (` `, `-` 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` (`/.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`). - [x] **`map_prefix`** — `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 `'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 `{w,t,s,i,n,d,r,c,h,hh,ha}` and `{w,y,t,m, i}` and leaves nothing under the old `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. - [x] **`` badd-link** — was mouse-only (``) / command-only (`:VimwikiBaddLink`); upstream binds `` → `VimwikiBaddLink` (confirmed against upstream `ftplugin/vimwiki.vim`). _Fix:_ added normal-mode `` → `badd_link` in the links group of both clients (`lua/nuwiki/keymaps.lua`, `ftplugin/vimwiki.vim`; docs + README updated). Covered by `map[n].` in both keymap harnesses. - [x] **`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. - [x] **`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 - [x] **`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`); `` 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` lacks `-nargs=?`** _(new, 2026-06-03 re-audit)_ — upstream is `-nargs=? -complete=…file`; nuwiki's defs are bare, so `:VimwikiRenameFile foo` raises E488. Subsumed by the existing "rename delegates to the LSP rename request, takes no filename arg" divergence (the `-complete=` item below) — adding `-nargs=?` (accepted-ignored) would restore signature parity if desired. Low priority. - [ ] `VimwikiVar` — config-var get/set introspection. _Fix:_ `plugin/nuwiki.vim`. - [x] **`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` — no Ex-command (mapping-driven only). _Fix:_ `ftplugin/vimwiki.vim`. - [ ] `VimwikiTableAlignQ` vs `AlignW` collapsed to one `table_align` — `gqq` (align) vs `gww` (align w/o resize) distinction still lost (the remaining open part). _(Re-audit 2026-05-31: also the attr differed — bare vs upstream's `-nargs=?`, so `:VimwikiTableAlignQ 2` raised E488.)_ _Attr part fixed 2026-06-03:_ `VimwikiTableAlignQ`/`AlignW` + `NuwikiTableAlign` are now `-nargs=?` in all contexts (the optional column arg is accepted-and-ignored pending the align-vs-resize split), so the E488 is gone. Covered by `cmd.search_and_tablealign_nargs` (`test-keymaps-vim.vim`). - [x] `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 `` 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. - [x] `VimwikiListChangeLvl` was `-nargs=?` (range-less) vs upstream `-range -nargs=+`. _Fix:_ all four defs are now `-range -nargs=+` passing `, , `; `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). - [x] **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 `` 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 `` behaviour change. - [x] **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 `` 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`). - [x] `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` uses `lvimgrep`, not vimwiki's search engine (the remaining open part — a deliberate, simpler implementation). _(Re-audit 2026-05-31: also `-nargs=1` vs upstream `-nargs=*`.)_ _Attr part fixed 2026-06-03:_ both are now `-nargs=*` (Vim + Neovim), so a multi-word `:VimwikiSearch foo bar` no longer raises E488 and an empty `:VimwikiSearch` reuses the last search pattern (`lvimgrep //`). Covered by `cmd.search_and_tablealign_nargs` (`test-keymaps-vim.vim`). - [x] **`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 `,, ` 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`). - [x] **`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`). - [x] **`NuwikiGenerateTags` missing** _(new, 2026-05-31 re-audit; fixed same day)_ — added `NuwikiGenerateTags` (Vim + Neovim) mirroring `VimwikiGenerateTags` → `tags_generate_links`, with `-nargs=?` + `-complete=…tags`. Covered by `cmd.NuwikiGenerateTags_exists` (both harnesses). - [ ] `VimwikiIndex` family is buffer-local in nuwiki (upstream defines globally in `plugin/`); only `:…UISelect` is a global entry point. - [x] **`VimwikiColorize` / `NuwikiColorize` drop their argument in the Neovim branch** — both are `-nargs=1`, but the Neovim defs called `colorize()` with no `` (`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 ``. 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 `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`. - [x] **Colour spans aren't concealed** — a colorized word shows the literal `` 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 ``/`` 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`). - [x] **`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. - [x] **`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 ``, so a `:2NuwikiIndex` / `:3NuwikiTabIndex` count is now honored. (The `Index` half was caught by the 2026-05-31 second re-audit and fixed then.) - [x] **`VimwikiToggleListItem` / `Increment` / `DecrementListItem` `-range`** — all three (both branches) are now `-range`; the client loops the per-line op over `[, ]` (`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`. - [x] **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 ``. `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`.)_ - [x] **`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`/`Decrement` — `ToggleRejected` 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 ``/ `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`). - [x] **`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 (` > 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`). - [x] **`-complete=` specs** _(new; done 2026-05-31 command-attribute pass)_ — upstream attaches command-line completion to several commands; nuwiki had none, so `` 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#pages` → `VimwikiGoto`/`NuwikiGoto` (globs every wiki root's `*` files → root-relative page names). - `nuwiki#complete#colors` → `VimwikiColorize`/`NuwikiColorize` (keys from a configured `color_dic` + colours already used in buffer `color:…` spans). - `nuwiki#complete#tags` → `VimwikiSearchTags`/`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`). - [x] **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). - [x] **`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 - [x] **`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. - [x] **`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`** _(new, 2026-06-03 re-audit)_ — upstream global (`vars.vim:184`, default `0`): don't expand the last column separator to fill the cell. nuwiki absent. Low impact (off by default). _Fix:_ thread a flag into the table-format routine. - [ ] **`user_htmls`** _(new, 2026-06-03 re-audit)_ — upstream global (`vars.vim:191`, default `''`): comma-separated HTML files with no wiki source that `:VimwikiAll2HTML` must not delete. nuwiki absent — only relevant if nuwiki's all-to-HTML prunes orphan HTML (it may not, in which case this is moot and worth a one-line divergence note rather than code). - [ ] **`hl_headers` / `hl_cb_checked`** _(new, 2026-06-03 re-audit)_ — upstream globals (`vars.vim:158`/`157`, both default `0`): per-level header highlight / checked-checkbox line highlight. nuwiki absent. Borderline — either implement via highlight groups/semantic tokens, or fold into the `conceal*`/`maxhi` highlighting-divergence family so future audits stop flagging them. - [x] **`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: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` 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`). - [x] **`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` (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. - [x] **`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` — 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` — `:cd` into wiki root on open (client-side). - [ ] `bullet_types` / `cycle_bullets` — custom bullet glyphs + wrap-around. - [ ] `commentstring` (`'%%%s'`) — comment toggling/text-objects. _(2026-06-03 re-audit: also a value mismatch — nuwiki hardcodes `%% %s` (with a space) in `ftplugin/vimwiki.vim:18` vs upstream's no-space `%%%s`; align when picked up.)_ - [ ] `color_tag_template` — `color_dic` present; template regex missing. Related divergence _(2026-05-31 config re-audit)_: nuwiki's `color_dic` defaults to **empty** while upstream ships a populated palette (`default`/`red`/…). Documented accurately on the nuwiki side (so it's not a doc↔code mismatch); the renderer falls back to `class="color-"`. Decide whether to seed a default palette or keep empty-by-design. - [ ] `rss_max_items` / `rss_name` — `:Rss` output hardcoded. _Fix:_ `HtmlConfig`. - [ ] **RSS feed structure fidelity** _(new, 2026-05-31 base_url audit)_ — nuwiki's `write_rss` is intentionally minimal (title/link/guid). Upstream emits more: channel `` points at the diary index page (`base_url + diary_rel_path + diary_index + .html`, nuwiki uses bare `base_url`); per-item `` holds the bare date basename (nuwiki uses the full URL, no attribute); plus ``, channel/item `` (from mtime), and a `` rendered-HTML body per item. `base_url`'s *scope* is faithful (RSS-only, not inter-page links) and the `custom_wiki2html` arg contract is an exact match — only the feed document shape diverges. - [ ] `emoji_enable` — emoji substitution. - [x] **`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` — 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 `` normalize-link (visual `+` covers the same intent). - [ ] Insert `` multiline list item. - [x] **`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. - [x] **`` (Cmd+Return) tab-drop alias** _(new, 2026-05-31 re-audit)_ — added `` → `follow_link_drop` alongside `` in both clients (`lua/nuwiki/keymaps.lua`, `ftplugin/vimwiki.vim`); docs updated. Covered by `map[n].` in both harnesses. - [x] **`wm` missing from the Vim *global* map block** _(new)_ — `plugin/nuwiki.vim`'s global family now binds `wm` (tomorrow) and `wt` opens today in a new tab (was the same collision the buffer-local maps had). Covered by `map[n].wm` in both harnesses. - [x] **`wm` 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`: `t` was bound to tomorrow and `m` (tomorrow) was absent entirely. Fixed so `t` opens today in a new tab and `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.` + the `g:nuwiki_no__mappings` globals. - `use_calendar` — no calendar.vim integration. - `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>`, ``, …) — 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` → `/_html` (upstream: sibling `_html/`); `template_path` → `/_templates` (upstream: global `~/vimwiki/templates/`); `diary_rel_path` → `diary` 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, ``-family create-on-follow, `` badd, `wt`/`m`, `VimwikiColorize` arg+range, colour-span conceal — confirmed present and correct in **both** clients. New gaps from that pass (diary-note `-count`, `-complete` specs, ``, global `wm`, 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 ``, 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.