Upstream's :VimwikiToggleRejectedListItem is -range and binds glx in
visual mode; nuwiki's four defs were bare, so a ranged invocation raised
E481 and acted cursor-only. Same -range class already fixed for
ToggleListItem/Increment/Decrement -- ToggleRejected was missed.
All four defs (Vim+Neovim x Vimwiki*/Nuwiki*) are now -range, routed
through a new reject_list_item_range(l1, l2) looping over_range in both
clients, mirroring toggle_list_item_range. Visual glx stays cursor-only,
consistent with its <C-Space>/gln/glp siblings.
Also logs the 2026-06-02 re-audit findings in development/vimwiki-gap.md:
RemoveDone lost -range (low pri); config gaps list_margin
semantics/markdown-default, diary_months, markdown_header_style,
diary_caption_level=-1; and the path_html/template_path default-location
divergences. Mappings audit came back clean.
Tests: cmd.reject_list_item_range (test-keymaps.lua),
cmd.VimwikiToggleRejectedListItem_accepts_range (test-keymaps-vim.vim).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
874bdd0 committed the diary -count feature with several Edits that had
silently no-matched, leaving the tree non-compiling and the clients
inconsistent (CI run 236 failed). This completes it:
- commands.rs: add the `wiki` field to OptionalUriArg (the previous edit
targeted a wrong struct name) and pass None to the six resolve_diary_wiki
callers that take no selector (date/list/step paths). Server builds clean.
- autoload/nuwiki/commands.vim + lua/nuwiki/commands.lua: actually thread the
count through s:diary_open / _diary_open and the diary_today/today_tab/
yesterday/tomorrow/index handlers (these edits had failed before, so the
ftplugin defs were calling handlers that ignored/rejected the arg).
- Add the test cases the prior commit referenced but never landed:
cmd.VimwikiMakeDiaryNote_has_count (test-keymaps.lua) +
cmd.Vimwiki{MakeDiaryNote,DiaryIndex}_accepts_count (test-keymaps-vim.vim).
- Fix the OptUriArg→OptionalUriArg name in the gap-doc note.
Verified with CI flags: workspace test/clippy/fmt clean; lua 284, vim 272/18/21,
all 0 failed; the three new diary cases pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the three new command gaps from the 2026-05-31 re-audit (all real,
client-side, both clients × Vimwiki/Nuwiki):
- VimwikiChangeSymbolTo / ListChangeSymbolI / NuwikiChangeSymbol are now
-range -nargs=1 via a new list_change_symbol_range(symbol,l1,l2) helper that
loops over_range; was -nargs=1 only → E481 on a visual selection.
ChangeSymbolInListTo/InList stay range-less.
Bonus: the Neovim VimwikiChangeSymbolInListTo + NuwikiChangeSymbolInList defs
passed whole_list=false, changing only the current item — corrected to true.
- VimwikiGenerateLinks / NuwikiGenerateLinks are now -nargs=?; with an arg the
client dispatches the existing server command nuwiki.links.generateForPath
({path}) for real subtree-scoped link generation, else nuwiki.links.generate.
Was bare → E488 on an arg.
- Added NuwikiGenerateTags (Vim + Neovim) mirroring VimwikiGenerateTags, with
-nargs=? -complete=…tags — closes the :Vimwiki*↔:Nuwiki* alias asymmetry.
Tests: cmd.ChangeSymbolTo_range_sets_marker, cmd.GenerateLinks_accepts_optional_path,
cmd.NuwikiGenerateTags_exists (test-keymaps.lua, 283 pass) +
cmd.VimwikiChangeSymbolTo_accepts_range, cmd.VimwikiGenerateLinks_accepts_path,
cmd.NuwikiGenerateTags_exists (test-keymaps-vim.vim, 272/18/21). fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audit follow-up on the two link-path items flagged during the command-attribute
pass:
- REAL (bug #2): all eight Neovim Ex-command link defs (Vimwiki/Nuwiki ×
FollowLink/SplitLink/VSplitLink/TabnewLink) dispatched to raw
`lua vim.lsp.buf.definition()`, bypassing the bare-word → [[link]] wrap +
create-on-follow that the Vim commands and the Neovim <CR> family already do.
All eight now call follow_link_or_create() (keeping their split/vsplit/tabnew
prefixes). TabDropLink already used follow_link_drop and is unchanged.
- FALSE ALARM (bug #1): the audit's claim that lua follow_link_or_create was
"botched" (double definition() with dead pos_args code, no wrap) was a
mis-read — it was a single correct definition that already wrapped bare words
and is dispatched by <CR>. Tidied anyway: collapsed its two definition() call
sites into one tail call; behaviour is identical (wrap still skipped when
already inside a link, via short-circuit).
Tests: cmd.VimwikiFollowLink_wraps_bare_word + cmd.VimwikiSplitLink_wraps_bare_word
(test-keymaps.lua). Both harnesses green (lua 280, vim 269/18/21, 0 failed);
gap doc corrected for both entries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verification audit of the command-attribute pass confirmed all four changes
(Table, Index/TabIndex, ListChangeLvl, SplitLink) faithful with no regressions,
and surfaced three pre-existing items:
- VimwikiTable no-arg default cols was 3 vs upstream's 5 (rows 2 already
matched, confirmed against vimwiki#tbl#create). Aligned both backing fns
(autoload + lua) to default 5 cols.
- Mapped (not fixed here — core <CR> behavior, out of attribute scope):
the Neovim lua follow_link_or_create double-calls definition() with dead
code and never wraps a bare word into [[link]] (the Vim side does); and the
Neovim Split/VSplitLink dispatch to raw definition() instead of
follow_link_or_create. Both logged in development/vimwiki-gap.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Upstream is `-range -nargs=+` (change_level(line1, line2, direction,
plus_children)); nuwiki was -nargs=? and range-less, so `:1,3VimwikiListChangeLvl
increase 0` raised E481/E488 and the level change never spanned a selection.
All four defs (Vim+Neovim × Vimwiki+Nuwiki) are now -range -nargs=+ and forward
<line1>, <line2>, <f-args>. list_change_lvl(line1, line2, direction,
[plus_children]) (both clients) parses the required direction + optional subtree
flag and applies the change to every list item in the range via the existing
over_range helper.
Tests: cmd.ListChangeLvl_range_indents (test-keymaps.lua) +
cmd.VimwikiListChangeLvl_accepts_range_and_args (test-keymaps-vim.vim).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ftplugin/vimwiki.vim: add buffer-local :NuwikiUISelect to the Vim path
(it previously existed only on the Neovim path, contradicting the docs'
"buffer-local on every .wiki buffer" claim — a global fallback masked it).
Now symmetric with :VimwikiUISelect.
- Make every keymap subgroup description list all of its bindings instead
of trailing with "…": the README mappings-subgroups table, the README
config-example comments, the README g:nuwiki_no_<group>_mappings rows,
the doc/nuwiki.txt group→keys list, and the lua/nuwiki/config.lua comment.
Also fixes the doc list that had dropped aH/iH from text_objects and adds
the mouse group. Each notes that the <Leader>w prefix follows map_prefix.
helptags validates (no dup tags); all keymap suites pass (266+18+21 Vim,
275 Lua); :NuwikiUISelect confirmed defined on a Vim .wiki buffer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A map_prefix parity audit against upstream vimwiki found the Neovim global
entry-point block in lua/nuwiki/init.lua carried the same collision the Vim
global + buffer-local maps were already fixed for: <prefix><Leader>t was
bound to tomorrow and <prefix><Leader>m (tomorrow) was missing entirely.
Now <prefix><Leader>t opens today in a new tab and <prefix><Leader>m is
tomorrow — consistent with plugin/nuwiki.vim, the buffer-local maps, and
upstream (VimwikiTabMakeDiaryNote / VimwikiMakeTomorrowDiaryNote).
Adds global_maps.diary_tab_and_tomorrow_targets to test-keymaps.lua (queries
in a scratch buffer so buffer-local maps don't shadow the globals). Logs the
fix plus a new "RSS feed structure fidelity" gap (the audit confirmed the
custom_wiki2html arg contract and base_url scope are faithful; only the RSS
document shape diverges) in development/vimwiki-gap.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror vimwiki's g:vimwiki_map_prefix. The whole <Leader>w* family was
hardcoded across the map layer; it is now built from a configurable prefix:
- Neovim: `map_prefix` setup() option (default '<Leader>w'), read by
lua/nuwiki/keymaps.lua (buffer-local) and lua/nuwiki/init.lua (global
entry points).
- Vim: g:nuwiki_map_prefix, applied via :exe in plugin/nuwiki.vim (global)
and ftplugin/vimwiki.vim (buffer-local).
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}, leaving nothing under the old <Leader>w*.
Non-prefix maps (<CR>, gl*, headers, …) are untouched.
Tests: new test-keymaps-vim-prefix.vim harness (21 cases, wired into
test-keymaps-vim.sh) + map_prefix.relocates_wiki_family in test-keymaps.lua.
Docs: README, doc/nuwiki.txt, lua config comment, vimwiki-gap.md ticked.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add three per-wiki HtmlConfig keys mirroring vimwiki globals:
- custom_wiki2html / custom_wiki2html_args: when set, export shells out
to the external converter 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.
- base_url: when set, diary RSS item + channel links become
<base_url><diary_rel_path>/<date>.html instead of file:// URIs.
write_page() gains a `force` flag threaded through export_current (false),
export_all (its own flag) and the did_save auto-export path (false).
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). Docs: README, doc/nuwiki.txt,
lua config comment, vimwiki-gap.md item ticked.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Added per-wiki auto_generate_links, auto_generate_tags, auto_diary_index
(default false, like upstream), wired into the did_save hook mirroring
auto_toc:
- auto_generate_links / auto_generate_tags rebuild the Generated Links /
Generated Tags section ONLY when it already exists (new
links_rebuild_edit / tag_links_rebuild_edit, like toc_rebuild_edit) — never
inserts one into a page that lacks it.
- auto_diary_index regenerates the diary index page when a dated diary entry
(not the index itself) is saved, via diary_generate_index_edit (handles the
index file open / on-disk / absent).
auto_tags is recorded as an intentional divergence: nuwiki re-indexes tags on
every change, so the tag metadata is always fresh (no on-disk file to update).
Tests: links_rebuild_edit_only_acts_when_section_present,
tag_links_rebuild_edit_only_acts_when_index_present, config round-trip +
defaults. README/doc/lua comment updated. fmt/clippy clean; 0 Rust failures.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TOC / links / tags section headings (and their levels) were hardcoded.
Added per-wiki toc_header/toc_header_level, links_header/links_header_level,
tags_header/tags_header_level — defaults Contents / Generated Links /
Generated Tags at level 1, matching upstream (also fixes the tags index
heading: Tags -> Generated Tags).
A caption_line(name, level) helper emits the `=`-markers; the configured
text + level thread through toc_edit / links_edit / tag_links_edit and the
auto_toc save hook. find_section_range matches the configured text, so
regeneration stays idempotent at any level.
Tests: captions_honour_custom_header_and_level (link_health) + config
round-trip + default assertions (index_and_config); existing tag/toc/links
tests updated for the new arity and the Generated Tags default. fmt/clippy
clean (8-arg ops get allow(too_many_arguments), matching the codebase
precedent); 0 Rust test failures; config-parity green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The rejected/cancelled checkbox glyph was a hardcoded `const REJECTED = '-'`.
ListSyms now carries a `rejected` field (new_with_rejected); a per-wiki
`listsym_rejected` key (default `-`) is added to WikiConfig, and
WikiConfig::list_syms() builds the palette with it. Routed the three
config-driven ListSyms construction sites (parse path in lib.rs + the
toggle/cycle commands) through list_syms(), so a custom rejected glyph both
lexes and round-trips.
Tests: listsyms::custom_rejected_glyph_is_honoured + config round-trip.
README/doc/lua config comment updated. fmt clean; 0 Rust test failures.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements the four items previously left as "needs handler work" — all
fully wired, not cosmetic.
- ToggleListItem / IncrementListItem / DecrementListItem are now -range
(both clients). The client loops the per-line op over [<line1>,<line2>]
(toggle_list_item_range / list_cycle_symbol_range). The server's edits are
version-less single-line WorkspaceEdits, so each line applies independently
— no server protocol change. `:'<,'>VimwikiToggleListItem` toggles the whole
selection.
- VimwikiRebuildTags gains -bang: `:…RebuildTags!` passes { all: true } and
the server (tags_rebuild) re-indexes every configured wiki via
wikis_snapshot(), not just the current one.
- VimwikiNormalizeLink is -nargs=?: with `1` (upstream's visual flag; the
x-mode `+` mapping now passes it) it wraps the '< / '> selection as
[[selection]] (new wrap_visual_as_wikilink in both clients).
- VimwikiCheckLinks is -range: a ranged invocation filters the broken-link
report to the current buffer's selected lines (client-side filter in
results_to_qf / check_links).
Tests: cmd.toggle_list_item_range + cmd.normalize_link_visual_wraps_selection
(test-keymaps.lua), normalize.visual_wraps_selection (test-keymaps-vim.vim).
README + doc/nuwiki.txt + gap doc updated. fmt/clippy clean; nvim 274,
vim 266+18, all Rust tests + config-parity green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clean, fully-wired parity fixes (skipping the accept-but-ignore ones):
- <D-CR> (macOS Cmd+Return) → follow_link_drop, alongside <C-S-CR>, in both
clients. Upstream binds both to VimwikiTabDropLink.
- plugin/nuwiki.vim global map block: fix the same diary collision the
buffer-local maps had — <Leader>w<Leader>t now opens today in a new tab and
<Leader>w<Leader>m (tomorrow) is added.
- Neovim TabIndex: add -count to NuwikiTabIndex and switch both
Vimwiki/NuwikiTabIndex from vim.v.count (always 0 in command context) to
<count>, so a count is actually honored.
- Converge VimwikiTableMoveColumn{Left,Right}: the Vim-branch commands now call
the table_move_column_left/right aliases, matching the Neovim branch.
- VimwikiColorize/NuwikiColorize: -nargs=1 → -nargs=* (all four defs) for
upstream parity; a bare :VimwikiColorize now prompts for the colour.
Docs (README + doc/nuwiki.txt) updated; mapping surfaces gain <D-CR> and
<Leader>w<Leader>m in both harnesses. vim 265+18, nvim 272, all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
c63ec67 dropped diary_start_week_day, hardwiring the weekly diary to ISO
(Monday) weeks. Upstream vimwiki instead names a weekly note by the
week-start day's date (YYYY-MM-DD) and honours diary_start_week_day. Rather
than force one scheme, make it a per-wiki choice so migrators keep upstream
behaviour while existing nuwiki weekly files keep working:
- 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).
- Restored per-wiki key `diary_start_week_day` (`monday`..`sunday`, default
monday); applies only in `date` mode.
Implementation:
- nuwiki-core::date gains WeeklyStyle, WeekStart, and DiaryCalendar (owns
today/next/prev — date-mode snaps to the week-start and steps ±7 days;
iso/daily/monthly/yearly defer to the existing DiaryPeriod logic).
- WikiConfig gains the two fields (+ defaults, RawWiki, From) and a
diary_calendar() builder; commands.rs diary_open_relative and the
next/prev pivot use it.
Defaults preserve current behaviour (iso/monday), so no breaking change.
Tests: DiaryCalendar cases in nuwiki-core/tests/diary.rs (snap, ±7, sunday
start, iso delegation, daily) + config round-trip in
nuwiki-lsp/tests/index_and_config.rs. README + doc/nuwiki.txt + lua config
comment updated. fmt + clippy clean; all crate tests + config-parity green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two upstream-parity keymap fixes (confirmed against upstream
ftplugin/vimwiki.vim):
1. <Leader>w<Leader>t collision — nuwiki bound both <Leader>w<Leader>t and
<Leader>w<Leader>m to tomorrow's diary. Upstream: <Leader>w<Leader>t opens
today's diary in a NEW TAB, <Leader>w<Leader>m opens tomorrow. <Leader>w
<Leader>t now calls diary_today_tab; <Leader>w<Leader>m stays tomorrow.
2. <M-CR> badd-link — adding a link target to the buffer list was mouse-only
(<MiddleMouse>) / command-only (:VimwikiBaddLink). Upstream binds
<M-CR> -> VimwikiBaddLink. Added normal-mode <M-CR> -> badd_link to the
links group of both clients.
Both clients (lua/nuwiki/keymaps.lua, ftplugin/vimwiki.vim); README + doc/
nuwiki.txt updated. Tests: map[n].<M-CR> in both harnesses;
diary.leader_t_opens_today_in_tab / diary.leader_m_opens_tomorrow (vim).
nvim 270, vim 263+18, all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A :VimwikiColorize'd word showed the literal
`<span style="color:NAME">TEXT</span>` markup. Now the tags are concealed
and TEXT is painted in NAME, so the buffer shows just the coloured word —
matching the HTML export.
New autoload/nuwiki/colors.vim: nuwiki#colors#refresh() scans the buffer
and, per distinct colour, defines a syntax region that conceals the
`<span …>` / `</span>` tags (concealends) and highlights the body with the
span's actual colour (guifg always; ctermfg for named colours). Idempotent
(clears the group before redefining) and tracked in b:nuwiki_color_seen.
Refresh is driven from three places so it's both correct and immediate:
- syntax/vimwiki.vim: initial pass + re-establish after :colorscheme reload
(resets the per-buffer cache since :syntax clear drops the regions);
- ftplugin TextChanged/InsertLeave autocmd for spans that arrive via paste/
undo/external edits;
- colorize() itself calls refresh() right after wrapping, so a freshly
colorized word conceals instantly (TextChanged doesn't fire reliably mid
command / in headless ex-mode).
The LSP @vimwikiColor token no longer links to Constant, so in Neovim it
doesn't override the real per-span colour on the concealed text. Pure syntax
+ :highlight — works identically in Vim, coc.nvim, and Neovim, no LSP needed.
Tests: colorize.conceal_hides_tags_shows_text (both harnesses) and
colorize.command_conceals_immediately (vim). Sample wiki's colorize section
notes the conceal. nvim 269, vim 259+18, config-parity green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Selecting text and running :VimwikiColorize {color} raised
"E481: No range allowed" — Vim prepends '<,'> to the command when invoked
from a visual selection, but the command was defined without -range.
All four colorize command defs (Vim + Neovim, Vimwiki* + Nuwiki*) are now
-range and forward the range count to the handler. A ranged (visual)
invocation wraps the '< / '> selection; a bare invocation wraps the cword.
The x-mode <Leader>wc mapping passes the visual flag explicitly.
The Vim-branch colorize() gained selection support (it was cword-only),
matching the Neovim branch, so both clients wrap a visual selection
identically. visual_selection_range() (Lua) no longer gates on
vim.fn.visualmode() — the explicit visual flag now carries that intent, and
the marks check guards unset selections.
Tests: cmd.Colorize_range_wraps_selection (nvim) and
colorize.{command_wraps_cword,visual_wraps_selection,ranged_command_no_error}
(vim). nvim 268, vim 257+18, all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Neovim command defs for :VimwikiColorize / :NuwikiColorize were
-nargs=1 but called colorize() without <q-args>, so the colour name was
silently dropped and the user was prompted instead (the Vim branch passed
it correctly). Pass <q-args>.
While verifying, found a second bug in the Lua colorize(): it inferred
visual-vs-normal from vim.fn.visualmode(), which returns the *last* visual
mode used in the session along with stale '< / '> marks. So running
:VimwikiColorize in normal mode after any earlier visual selection wrapped
that leftover selection instead of the word under the cursor (produced an
empty, misplaced span). colorize(color, visual) now takes an explicit
visual flag, passed only by the x-mode <Leader>wc mapping; the command and
the normal-mode mapping always wrap the cword.
Tests: cmd.VimwikiColorize_uses_arg, cmd.NuwikiColorize_uses_arg,
cmd.Colorize_ignores_stale_visual_selection, colorize.visual_wraps_selection
in test-keymaps.lua. nvim suite 267, vim suite 254+18, all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The lists mapping comment still listed gl<Space>; bare gl/gL are now the
remove-checkbox maps after the P1 #3 parity change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Upstream vimwiki binds bare gl/gL to remove-checkbox (item / whole list);
nuwiki had rebound the conceptual keys to gl<Space>/gL<Space> as remove-done,
a same-keys-different-effect parity trap. Bind bare gl/gL to remove-checkbox
instead — they share the gl… prefix so they fire after 'timeoutlen', exactly
as upstream. Remove-done becomes command-only; add -bang to *RemoveDone so
:NuwikiRemoveDone! reaches the whole-buffer sweep that gL<Space> previously
held (its only entry point).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Upstream's one-key marker-change keys had no nuwiki binding — the
functionality was reachable only via :NuwikiChangeSymbol. Add normal-mode
gl{-,*,#,1,i,I,a,A} (current item) and gL{...} (whole list) to both
clients, wired to the existing list_change_symbol command. NumericParen
1) stays command-only, matching upstream's default number_types shadowing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The plain-Vim client only defined VimwikiFollowLink; the split/vsplit/
tabnew/tabdrop/back command surface existed only in the Neovim branch.
Add :Vimwiki{Split,VSplit,Tabnew,TabDrop,GoBack}Link (+ :Nuwiki* aliases)
to the Vim branch, backed by a new nuwiki#commands#follow_link_drop()
helper that runs `:tab drop` to reuse an already-open tab.
Both clients now implement real tab-drop: open_uri gains a 'tabdrop'
case and M.follow_link_drop(); the Neovim TabDropLink commands and the
<C-S-CR> mappings (Vim + Lua) are repointed off the old tabnew cheat.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove nested_syntaxes, maxhi and diary_start_week_day from the config
reference (block, tables and key lists) since the server no longer
consumes them, and note that language-tagged code fences are highlighted
automatically with no nested_syntaxes key.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Neovim adds a `diagnostic = { link_severity = 'warn' }` default so the value
is actually included in the payload. Vim reads the flat g:nuwiki_link_severity
global and nests it into the same `diagnostic` dict, keeping the two clients'
server-bound payloads identical.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Expose the init_options payload builders publicly in both clients
(M.init_options/M.server_settings in Lua, nuwiki#lsp#settings() in Vim) so
they can be inspected. Add editor harnesses that dump each client's
server-bound config as deterministic key=value lines and diff against a
shared golden file: nvim == golden and vim == golden together prove the two
clients send identical config. Add a server-side test asserting the Vim flat
shape and the Neovim {nuwiki:{...}} wrapper desugar to the same WikiConfig.
Wire both harnesses into CI.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Register the named :Vimwiki*/:Nuwiki* entry points that previously
existed only as mappings or were missing entirely — including
RemoveSingleCB/RemoveCBInList, CatUrl, TabMakeDiaryNote,
NormalizeLink, Renumber{List,AllLists}, TableAlign, ChangeSymbol(InList),
ListToggle, Increment/DecrementListItem, and the DeleteLink/RenameLink/
GenerateTags compat aliases. Both the Vim (vim-lsp/coc) and Neovim
client functions are added to back them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Every :Vimwiki* command now has a canonical :Nuwiki* counterpart
(tables, colorize, clipboard paste, list-level/remove-done, and the
Neovim-only split/tab link-follow variants). Remove the now-unused
"not yet implemented" stub helpers (_not_yet, deferred,
s:notify_deferred) and the stale "deferred" comments, since every
command is implemented and server-backed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The picker fetched the wiki list via workspace/executeCommand, but the LSP
only starts once a vimwiki buffer exists — so a wiki could not be selected
until one was already open (chicken-and-egg). Read the list straight from
config instead (g:nuwiki_wikis / scalar fallback, the same source as
open_wiki_path) and open the chosen index directly; that auto-starts the
server via the FileType autocmd.
- Vim: new public nuwiki#commands#wiki_list(); wiki_ui_select() uses it +
inputlist() + :edit. Global :VimwikiUISelect / :NuwikiUISelect commands.
- Neovim: config.wiki_cfg(n) + config.wiki_list() (init.lua delegates);
commands.wiki_ui_select() uses them + vim.ui.select + :edit; global
VimwikiUISelect / NuwikiUISelect user commands registered in setup().
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Heading-block folds were computed but all collapsed on file open,
so users had to `zR` after every `:edit`. Set `foldlevel=99` once at
ftplugin attach so the fold structure is still there (closeable with
`zc`/`zM`) but the buffer renders fully expanded.
`foldlevel` is window-local; we set it on initial attach only — not
in the Lua BufWinEnter re-apply — so closing folds with `zc` in a
window survives navigating away and back to that buffer. Split
windows still get the same value because Vim copies window-local
options to the new window when splitting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pressing <Tab> past the last cell unconditionally appended a new row
below, even when there was already a next row to jump into. Tabbing
through a header → separator → body table from the header row
inserted a blank row between header and separator instead of landing
in the first body cell.
After the final cell, walk forward from the current row to the end
of the table, skip any separator row, and jump to the first cell of
the next data row. Only insert a fresh row when the cursor is on the
table's last row (or only separator rows follow). Applied to both
the Vim (autoload/nuwiki/commands.vim) and Neovim (lua/nuwiki/
commands.lua) sides so the two harnesses stay in sync; covered by
new keymap tests in scripts/test-keymaps-vim.vim and test-keymaps.lua.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The server received only wiki_root (the parent directory) and had no
knowledge of the per-wiki sub-roots configured in g:nuwiki_wikis / the
setup() wikis list. It therefore resolved all links relative to the
parent root, causing every link in a sub-wiki to appear broken.
Vim path (autoload/nuwiki/lsp.vim):
s:settings() now includes a 'wikis' key when g:nuwiki_wikis is set,
so the initialization_options and settings payloads carry the full
per-wiki config (root, diary_rel_path, file_extension, …).
Lua path (lua/nuwiki/lsp.lua):
Add resolved_opts() which merges vim.g.nuwiki_wikis into
config.options.wikis when the user configures via VimL rather than
setup({wikis=…}). init_options(), server_settings(), and the
pre-0.11 root_dir_for() fallback all use resolved_opts() so the
server receives the correct per-wiki roots in every code path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The global <Leader>ww / diary helpers read g:nuwiki_wiki_root directly,
so they always opened the root-level index.wiki even when the user had
configured per-wiki roots via g:nuwiki_wikis (e.g. personal_wiki.root =
'~/.vimwiki/personal_wiki').
Vim path (autoload): replace the three open_*_path functions with a
shared s:wiki_cfg(n) helper that checks g:nuwiki_wikis[n] first, then
falls back to the scalar g:nuwiki_* vars and built-in defaults.
Lua path (init.lua): replace the separate _wiki_index_path /_diary_path
locals with a unified _wiki_cfg() that checks setup() opts.wikis, then
vim.g.nuwiki_wikis (for users who configure via VimL rather than
setup()), then scalar opts / g: vars.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs prevented keymaps from working after a Dein install:
1. <Leader>ww and <Leader>wt both called diary_today() instead of
wiki_index() / wiki_tab_index(). Fixed in keymaps.lua (Neovim)
and ftplugin/vimwiki.vim (Vim).
2. All keymaps were buffer-local (<buffer> / buffer=bufnr), so they
only activated inside an already-open .wiki file. Users had no way
to reach the wiki from any other buffer, making the plugin appear
broken on a fresh Vim start.
Fix: register the eight <Leader>w* entry-point mappings globally —
in setup() for Neovim (lua/nuwiki/init.lua) and in plugin/nuwiki.vim
for plain Vim. To avoid a chicken-and-egg LSP dependency, the global
mappings open files directly from config (wiki_root, diary_rel_path,
file_extension); the LSP auto-starts via the FileType autocmd once
the vimwiki buffer loads. Three new autoload helpers
(open_wiki_path, open_diary_path, open_diary_index_path) provide the
LSP-free path for the Vim side.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nvim defaults autoindent=on, so the indent inserted by Vim/Nvim after
the <CR> we return was being doubled by our own indent prefix —
pressing Enter on a sub-item produced a deeper-nested item instead of
a same-level one. Detect any auto-indent mechanism (autoindent,
smartindent, cindent, indentexpr) and let it own the indentation;
otherwise add it ourselves so vim's noautoindent default still works.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The keymap harness caught a regression in 5b6f789: returning '' from
the <expr> mapping and deferring cursor placement to vim.schedule
breaks any keypress immediately following Tab/<CR>. nvim_feedkeys with
the 'x' flag drains all queued keys synchronously, so the user's next
key lands at the pre-jump cursor position — and the scheduled
callback's startinsert leaks insert mode into the next test.
Mirror what the VimL side already does: have <expr> return
"<Cmd>lua require('nuwiki.commands')._helper(args)<CR>" so the
align + cursor-jump runs synchronously inside the same keypress. No
schedule, no mode juggling.
cr.adds_new_table_row ✓
tab.next_cell_in_table ✓
tab.from_first_cell_moves_to_second ✓
shift_tab.prev_cell_in_table ✓
39/39 in scripts/test-keymaps.sh (was 35/4-fail).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs in the insert-mode table editing path:
1. <Tab> past the last cell (and <CR> on a table row) used <CR>+typed
keystrokes to create a new row, which split the current line at the
cursor — pulling the trailing | down onto the next line and mangling
the formatting. Both paths now go through helpers that append() a
fresh row beside the current one without touching it.
2. The table never realigned to new content. Ported the LSP-side
render_aligned_table algorithm to Lua + VimL so smart_tab,
smart_shift_tab, and smart_return tighten column widths locally on
every navigation. No vim-lsp / nuwiki server roundtrip required.
Neovim side schedules the work via vim.schedule (textlock-safe);
plain Vim hands off via <Cmd>:call …<CR> to keep insert mode and
avoid the cmdline-mode flash.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`<Tab>` / `<S-Tab>` in insert mode navigate between table cells when
the cursor is on a `|…|` row, otherwise they pass through to their
default insert-mode behaviour (literal tab / shift-tab). Same
`<expr>`-mapping idiom as Cluster 5's smart_return.
<Tab> next cell; creates a fresh row below if past the last cell
<S-Tab> previous cell; no-op past the first
Cursor lands immediately after the destination cell's leading `|`,
matching upstream vimwiki's `vimwiki#tbl#go_*_cell` behaviour.
Both Lua and VimL counterparts wired into ftplugin's existing
table_editing block (gated alongside `gqq`/`<A-Left>`).
Tests: 4 new in scripts/test-keymaps.lua — next cell, new-row
overflow, previous cell, and pass-through outside a table.
Gates: 421 Rust / 39 Neovim / 12 Vim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bind `<CR>` in insert mode as an `<expr>` map to a `smart_return`
helper. Behaviour matches upstream vimwiki's `:VimwikiReturn`:
- On a list line with content → continue the list with the same
marker on a new line (preserving leading checkbox `[ ]` if any).
- On an empty list line (marker only) → clear the marker and break
out of the list with a plain newline.
- Inside a `|…|` table row → insert a fresh empty row below with
the same column count, cursor lands inside the first cell.
- Otherwise → plain `<CR>`.
Implementation note: the helper is `<expr>`-bound, which means
textlock is active and the buffer can't be mutated from inside the
callback. The function returns key sequences only — including
`<Esc>0DA<CR>` for the empty-list break and `<Esc>0li` for the
table-row cursor jump — so every effect flows through Vim's normal
keystroke pipeline and stays undo-coherent.
Both Lua and VimL counterparts shipped.
Tests: 4 new in scripts/test-keymaps.lua covering list continuation,
checkbox preservation, empty-marker break-out, and new table row.
Gates: 421 Rust / 35 Neovim / 12 Vim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five operator-pending + visual text-object pairs, matching upstream
vimwiki:
ah / ih heading section only (stops at any next heading)
aH / iH heading section + sub-tree (stops at same-or-shallower)
al / il list item (line, with / without marker prefix + checkbox)
a\ / i\ table cell (with / without surrounding `|` separators)
ac / ic table column (with / without the header separator row)
Pre-existing `ah` used aH semantics — fixed to match vimwiki: `ah`
now stops at the next heading regardless of level. `aH` is the new
descendants-inclusive variant.
Implementation notes:
- Pure regex; no LSP round-trip. Each helper computes a `(line, col)`
rectangle and drives the selection with feedkeys.
- Operator-pending and visual need different feed sequences. In visual
the previous anchor is sticky, so bounce through `<Esc>` first. In
operator-pending an `<Esc>` would CANCEL the pending operator —
feed the visual keys directly so Vim treats them as the motion.
- Re-exported helpers (`_heading_block`, `_cell_ranges`,
`_current_cell_for_line`, `_table_bounds`) keep the integration
surface testable without going through Vim's visual-mode pipeline,
whose `'<`/`'>` mark semantics fight headless test harnesses.
Tests: 6 new in scripts/test-keymaps.lua — five pure-helper cases
plus one end-to-end `dah` deletion to verify the operator-pending
wiring.
Gates: 421 Rust / 31 Neovim / 12 Vim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match upstream vimwiki's insert-mode list editing:
<C-D> list_change_level(-1) dedent current item
<C-T> list_change_level(+1) indent current item
<C-L><C-J> list_cycle_symbol(+1) cycle marker forward
<C-L><C-K> list_cycle_symbol(-1) cycle marker backward
<C-L><C-M> list_toggle_or_add_chk toggle if has checkbox, else add `[ ]`
Cycle order matches vimwiki's canonical run:
- → * → # → 1. → 1) → a) → A) → i) → I) → (wrap)
Both Lua and VimL paths wired. Lua callbacks fire directly (no `<C-o>`
dance) since list_change_level / changeSymbol mutate the buffer via
the LSP applyEdit pipeline, which works cleanly in insert mode. VimL
keeps the `<C-o>:call …<CR>` idiom since that's the standard there.
Harness adds 5 cases covering the new insert-mode bindings plus one
direct LSP roundtrip for changeSymbol — surfaced a long-standing
stale-binary footgun in test-keymaps.sh (rebuilt only when bin was
missing; now rebuilds every run since incremental cargo is fast).
Harness also captures server log_messages and dumps them on failure
so future swallowed-error bugs (Err → log + Ok(None)) are visible.
Gates: 421 Rust / 25 Neovim / 12 Vim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
named link commands, mouse maps
Parity audit Cluster 1 — small wins.
WikiConfig (server) extended with 14 vimwiki globals:
- `index` (`g:vimwiki_index`), wired into `:VimwikiIndex` so users
with a custom index page stem (e.g. `home`) get the right file.
- `diary_frequency`, `diary_start_week_day`, `diary_caption_level`,
`diary_sort`, `diary_header` — diary keys (weekly/monthly/yearly
semantics land in Cluster 4; the keys parse now so config
migration doesn't need to wait).
- `maxhi`, `listsyms`, `listsyms_propagate`, `list_margin`,
`links_space_char`, `nested_syntaxes`, `auto_toc` — list +
highlight knobs the renderer/handlers can consult.
All keys threaded through `RawWiki` → `WikiConfig::from(RawWiki)`,
defaults centralised in `wiki_defaults()` so `empty()`, `from_root()`,
the legacy single-wiki path, and the `RawWiki` impl stay in sync.
Lua front-door (`lua/nuwiki/config.lua`) now documents the multi-wiki
`wikis = {…}` shape with every accepted per-wiki key — users who
prefer Lua tables no longer have to round-trip through
`init_options` raw JSON.
HTML renderer (`crates/nuwiki-core/src/render/html.rs`):
- New `table_spans()` pre-pass resolves vimwiki's `>` (col_span) and
`\\/` (row_span) continuation markers into proper HTML
`colspan="N"` / `rowspan="N"` attributes on the lead cell. Continuation
cells emit nothing, matching what browsers expect.
- The old behaviour (emitting `class="col-span"` / `class="row-span"`
as visual markers) is gone — replaced with real merging so the
rendered HTML matches the source semantics.
Named ex-commands:
- `:VimwikiNextLink` / `:VimwikiPrevLink` — were keymapped only
(`<Tab>`/`<S-Tab>`); now have explicit `:` commands in both editor
paths, dispatching to new `nuwiki.commands.link_next` / `link_prev`
pure-Vim/Lua helpers.
- `:VimwikiBaddLink` — adds the link target's file to the buffer
list without switching focus. Goes through
`textDocument/definition` and runs `:badd <fname>` on the resolved
URI.
Mouse maps (opt-in via `mappings.mouse = true` in Lua or
`g:nuwiki_mouse_mappings = 1` in Vim):
- `<2-LeftMouse>` follows, `<S-2-LeftMouse>` / `<C-2-LeftMouse>`
follow in split/vsplit, `<MiddleMouse>` adds to buflist,
`<RightMouse>` goes back. Mirrors upstream vimwiki's defaults.
Tests: 7 new in `parity_cluster_1.rs` covering the per-wiki config
defaults + raw JSON parse, the legacy-single-wiki path, colspan
folding into a lead cell with no leftover class markers, rowspan
across rows, no-attrs when the table has no spans, and a sanity
paragraph render. Total 421 Rust tests pass; clippy clean; both
keymap harnesses still green (20 nvim + 12 vim).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the last pending §13.1 entries:
- **`nuwiki.colorize`** (client-side, Lua + VimL) — wraps the word
at cursor (or the visual selection on Neovim) in an inline
`<span style="color:%c">…</span>` template. Matches vimwiki's
default `color_tag_template`. Bound to `<Leader>wc` in both
normal and visual mode on both editor paths; the previous
"deferred" stub is gone.
- **`color_dic` → renderer** — `HtmlConfig` gains a
`color_dic: HashMap<String, String>` field reading the same key
out of `initializationOptions` / `didChangeConfiguration`. The
HTML export path threads it into `HtmlRenderer::with_colors`;
`ColorNode` now picks `style="color:<value>"` when the colour
name is in the dict, falling back to the existing
`class="color-<name>"` when it isn't.
SPEC §13.1 is fully checked off — the table moved from "deferred
sketch" to a per-command status table marking all 11 commands done,
plus a note on the `color_dic` follow-up landing alongside. README's
"Phase 14 list & table edit commands" row now reads ✅ without the
deferred-subset caveat, and the §13.1-blocked text-objects note in
the keymaps section is rephrased as "planned follow-ups".
Tests: 4 new in `phase17_colorize.rs` covering the renderer
fallback when the dict is empty, the inline-style emission for a
listed name, the fallback path for an unlisted name in a populated
dict, and the `initializationOptions` JSON shape. Total 414 Rust
tests pass; clippy clean; keymap harness still 20/20.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the table-rewriter cluster from SPEC §13.1.
Server-side (ops module):
- `render_blank_table(cols, rows)` — pure templating: header row,
separator `|--|--|--|`, then `rows` empty data rows. Wired through
`table_insert` which dispatches a `TextEdit` at the cursor.
- `table_align_edit` — locates the `TableNode` containing the cursor
line, computes max width per column across every row, re-emits the
table with each cell space-padded to its column's width. The
parser drops `|---|---|` separator rows but sets
`TableNode.has_header`; the renderer re-inserts a padded separator
after the header so the result still parses.
- `table_move_column_edit` — column-under-cursor swap with the
left or right neighbour (`dir = "left" | "right"`). Cursor column
is computed by counting pipe separators before the cursor's byte
offset on the row's line. Column widths swap alongside the data so
the post-swap table stays aligned. No-ops cleanly when the swap
would fall off either edge.
Editor glue:
- `lua/nuwiki/commands.lua` / `autoload/nuwiki/commands.vim` — the
three "not yet implemented" stubs are replaced with real LSP
dispatchers. `table_insert` accepts optional `cols`/`rows`
arguments (defaults: 3×2).
- Default keymaps `gqq` / `gq1` / `gww` / `gw1` now call
`table_align`; `<A-Left>` / `<A-Right>` call
`table_move_column_left` / `_right`. No more "deferred"
notifications on the table keys.
Tests: 8 new in `cluster_b_table_rewriters.rs` — blank-table shape
(3×2 + 1×1), column-width alignment with separator-row repad, swap-
right shape, swap-left clamp at column 0, no-table-found fallbacks,
COMMANDS list completeness. Total 410 Rust tests pass; clippy clean;
Neovim keymap harness still 20/20.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the four-command list-rewriter cluster from SPEC §13.1.
Server-side (commands.rs ops module):
- `parse_symbol` / `render_marker` — round-trip between
`:VimwikiListChangeSymbol` arg shapes (`-`, `1.`, `a)`, `i)`,
`Dash`, `Numeric`, …) and the rendered marker text. `render_marker`
knows how to spell `a/b/…/z/aa` and `i/ii/iv/v/…/MMM` for the
alphabetic + roman variants.
- `remove_done_edit` — walks every list (recursively through
blockquotes and sublists), emits delete TextEdits for items whose
checkbox is `Done` or `Rejected`. Item span gets extended through
the trailing newline so we don't leave behind empty lines. Accepts
an optional `position` to scope the operation to the item under
cursor + its descendants.
- `renumber_edit` — finds the list containing the cursor line (or
every list when `whole_file: true`), re-sequences numeric
markers in-place. Unordered lists are left alone.
- `change_symbol_edit` — rewrites the leading marker on a single
item, or every item in a list when `whole_list: true`. Uses
`render_marker` so ordered variants get the right index.
- `change_level_edit` — re-indents the marker line (or every line of
the item subtree when `whole_subtree: true`) by ±2 spaces per
level. Dedent clamps at column 0.
- `find_list_at_line` + `find_marker_span` — pure helpers reused by
the three above.
Editor glue:
- `lua/nuwiki/commands.lua` / `autoload/nuwiki/commands.vim` —
removed all four "not yet implemented" stubs and wired them to the
real LSP commands. Lua also exposes `list_change_lvl(direction)`
for the `:VimwikiListChangeLvl decrease|increase` compat entry.
- Keymaps for `glh` / `gll` / `gLh` / `gLl` (list level single +
subtree), `glr` / `gLr` (renumber list + whole-file),
`gl<Space>` / `gL<Space>` (remove done items) now hit the real
commands instead of printing a "deferred" notification.
Tests: 16 new in `cluster_a_list_rewriters.rs` covering
symbol parsing, marker rendering (alpha + roman + numeric),
removeDone shape, scoped removeDone, no-match cases, renumber
sequencing, whole-file walk, changeSymbol single + whole-list,
changeLevel single + subtree + clamp, and COMMANDS list completeness.
Total 402 Rust tests pass; clippy clean; keymap harness still at
20/20.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closing out §13.1's smallest cluster. Three commands move from
"not yet implemented" stubs to real behaviour:
- `nuwiki.link.pasteWikilink` (server, executeCommand) — derives
the current page name from the source URI + wiki root, returns a
`WorkspaceEdit` inserting `[[<page>]]` at the requested cursor
position.
- `nuwiki.link.pasteUrl` (server) — same lookup, but the inserted
text is the page's relative HTML output URL
(`<page>.html`, including subdir segments) so the snippet survives
when the export root moves.
- `nuwiki.link.normalize` (client) — wraps the word at cursor as
`[[word]]` without following. Reuses the `wrap_cword_as_wikilink`
helper that already powers the `<CR>` two-step. Pure-VimL on the
Vim path; pure-Lua on the Neovim path. No LSP round-trip.
Keymaps:
- `+` (normal + visual) now actually calls `normalize_link` on both
editor paths instead of stubbing with a "deferred" notification.
Tests:
- 5 new Rust unit tests in `cluster_c_link_helpers.rs` covering
command-list presence + the page-name derivation for root and
subdirectory pages + the URL / wikilink shape strings.
- Neovim keymap harness gains a `links.normalize_via_+` case (20
passing now, up from 19). Vim harness inherits the same
command-presence check via its existing smoke tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback: the smart `<CR>` should give the user a chance to
review/edit the freshly-created wikilink before committing to the
follow. Matches upstream vimwiki's flow.
`follow_link_or_create` (Lua + VimL) now:
1. Cursor already inside `[[…]]` → follow immediately.
2. Cursor on a bare word → wrap it as `[[word]]`, place cursor
inside, and STOP. A second `<CR>` then follows.
3. Neither — fall through to plain definition request so users
can still chord follow without a target word.
Verified: cursor on `MyPage rest` →
1st `<CR>` → `[[MyPage]] rest` (buffer unchanged)
2nd `<CR>` → opens `MyPage.wiki` (the synthesised future page).
381 tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two missing pieces in the previous "follow creates page" fix:
1. **`wiki_root` never reached the server.** The Lua glue (and the
Vim autoload glue) was sending the config under `settings` — that
field is for `workspace/didChangeConfiguration` notifications, not
for `initialize`. The server's `Config::from_init_params` reads
`initialization_options`, so `wiki_root` arrived as `None`, the
`Wiki` aggregate was empty, and `wiki_for_uri` returned `None` →
`resolve_target_uri` bailed before reaching my synthesise path,
leaving `<CR>` with "No Location Found".
Both glues now send the same payload under both keys:
- `lua/nuwiki/lsp.lua` → adds `init_options = init_options()`
alongside the existing `settings = …`.
- `autoload/nuwiki/lsp.vim` → adds `initialization_options` to
the `lsp#register_server` call.
The server keeps reading from `initializationOptions` at startup
*and* honouring `didChangeConfiguration` later (Phase 11 plumbing).
2. **No-wiki fallback in the server.** Even with the glue fix above,
users who launch nuwiki on an ad-hoc `.wiki` file outside any
workspace folder would still get an empty `wikis` list. The
`LinkKind::Wiki` arm now falls back to a new
`synthesise_page_uri_next_to(source_uri, path)` which builds the
future page next to the current file. `<CR>` on `[[NewPage]]`
opens `./NewPage.wiki`, save creates it.
3. **`<CR>` on a plain word now wraps + follows.** Mirrors vimwiki's
`:VimwikiFollowLink`: when the cursor isn't already inside
`[[…]]`, the new `nuwiki.commands.follow_link_or_create` /
`nuwiki#commands#follow_link_or_create` first wraps the word
under cursor as `[[word]]`, places the cursor inside the link,
then dispatches `vim.lsp.buf.definition()` /
`:LspDefinition`. Both editor paths now bind `<CR>` /
`<S-CR>` / `<C-CR>` / `<C-S-CR>` to this smart variant.
Verified end-to-end:
- `[[NewPage]]` with the rebuilt binary: definition returns
`file:///tmp/wiki/NewPage.wiki` as a Location, so the editor
opens an empty buffer for it.
- A bare `MyPage` word becomes `[[MyPage]]` in the buffer before
the follow request fires.
Total 381 tests still pass; fmt + clippy clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>