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

859 lines
58 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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); `<C-S-CR>` repointed to tab-drop.
- [x] **`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).
- [x] **`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)
- [x] **`<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).
- [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] **`<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`.
- [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 (`<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`).
- [x] **`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.
- [x] **`<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.
- [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`); `<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`).
- [x] **`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.
- [x] **`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`.
- [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.
- [x] **`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`.
- [x] **`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` +
`VimwikiTableAlignQ``gqq`; `gww`/`gw1` + `VimwikiTableAlignW``gww`;
`NuwikiTableAlign``gqq`. (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.
- [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 `<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.
- [x] `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).
- [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 `<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.
- [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
`<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`).
- [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.
- [x] **`: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 `lvimgrep`s `<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`).
- [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 `<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`).
- [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).
- [x] **`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.
- [x] **`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`.
- [x] **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`).
- [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
`<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.)
- [x] **`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`.
- [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 `<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`.)_
- [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 `<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`).
- [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 (`<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 `<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#pages``VimwikiGoto`/`NuwikiGoto` (globs every wiki
root's `*<ext>` 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.)_
- [x] **`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`.
- [x] **`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`.
- [x] **`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`).
- [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:<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`).
- [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<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.
- [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`).
- [x] **`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.
- [x] **`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.
- [x] **`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`).
- [x] **`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.
- [x] **`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_*`.
- [x] **`commentstring`** _(fixed 2026-06-03)_ — aligned to upstream's no-space
`%%%s` (`ftplugin/vimwiki.vim`); comment parsing is independent (lexer).
- [x] **`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.
- [x] **`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).
- [x] **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`.
- [x] **`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.)_
- [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`).
- [x] **`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`.
- [x] **`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).
- [x] **`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
- [x] **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.
- [x] **Insert `<S-CR>` multiline list item** _(fixed 2026-06-03)_ — upstream's
insert `<S-CR>` (`VimwikiReturn 2 2``kbd_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`).
- [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] **`<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.
- [x] **`<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.
- [x] **`<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_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, `<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 stubs**
`config.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` helpers**`registry::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 index**
`get_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.