Files
nuwiki/development/vimwiki-gap.md
T
gffranco c161f9d21a
CI / cargo fmt --check (push) Successful in 21s
CI / cargo clippy (push) Successful in 26s
CI / cargo test (push) Successful in 36s
CI / editor keymaps (push) Successful in 1m47s
feat(parity): P3 quick-wins — ShowVersion, gLH/gLL/gLR, Search/Align nargs
Closes four low-risk P3 parity items (gap doc updated):

- VimwikiShowVersion / NuwikiShowVersion: new global command (both clients)
  echoing g:nuwiki_version (0.1.0) + the host editor, via
  nuwiki#commands#show_version / commands.show_version.

- gLH / gLL / gLR: upstream's case-variant aliases of gLh/gLl/gLr (dedent /
  indent whole item, renumber all lists) added in both clients.

- VimwikiSearch / VWS: -nargs=1 -> -nargs=* (Vim + Neovim), so a multi-word
  search no longer raises E488 and an empty invocation reuses the last search
  pattern. (The lvimgrep-vs-vimwiki-engine difference stays open.)

- VimwikiTableAlignQ / AlignW / NuwikiTableAlign: bare -> -nargs=?, so
  `:VimwikiTableAlignQ 2` no longer raises E488 (optional column arg is
  accepted-and-ignored; the gqq-align vs gww-align-without-resize split
  stays open).

Docs (doc/nuwiki.txt + README) updated for the command and the aliases.
Tests: surface.{Vimwiki,Nuwiki}ShowVersion, map[n].gL{H,L,R} (both
harnesses), cmd.search_and_tablealign_nargs (vim). Neovim 291, Vim
282/18/21 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 00:28:51 +00:00

611 lines
41 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
- [ ] `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 `<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.
- [ ] `: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 `<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).
- [ ] `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
`<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] **`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`).
- [ ] `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.
- [ ] `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-<name>"`. 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 `<link>` points at the diary index page
(`base_url + diary_rel_path + diary_index + .html`, nuwiki uses bare
`base_url`); per-item `<guid isPermaLink="false">` holds the bare date
basename (nuwiki uses the full URL, no attribute); plus `<atom:link rel=
"self">`, channel/item `<pubDate>` (from mtime), and a `<![CDATA[…]]>`
rendered-HTML body per item. `base_url`'s *scope* is faithful (RSS-only,
not inter-page links) and 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 `<CR>` normalize-link (visual `+` covers the same intent).
- [ ] Insert `<S-CR>` 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] **`<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.
- `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>`, `<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.