Commit Graph

26 Commits

Author SHA1 Message Date
gffranco cd9d587aab Add missing :Nuwiki* aliases and drop dead deferred-stub code
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>
2026-05-30 15:54:05 -03:00
gffranco 8ab6015405 Major clean up and improvements to vimwiki compatibility and documentation.
CI / cargo fmt --check (push) Successful in 33s
CI / cargo clippy (push) Successful in 23s
CI / cargo test (push) Successful in 33s
CI / editor keymaps (push) Successful in 1m25s
Reviewed-on: #1
2026-05-30 18:35:40 +00:00
gffranco d6672fe218 fix(conceal): reveal cursor line in normal mode for natural navigation
concealcursor=nc kept the cursor's own line concealed in normal mode, so
the hidden characters of a wikilink ([[, target, ]]) still occupied buffer
columns — each l/h moved one hidden position with no visible movement,
making the cursor feel stuck. Drop the `n` flag (keep `c` for incsearch) so
the cursor line shows raw markup in normal mode while other lines stay
concealed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 20:03:32 -03:00
gffranco 59b48b4786 feat(folding): open every heading fold by default on file open
CI / cargo fmt --check (push) Failing after 32s
CI / cargo clippy (push) Successful in 15s
CI / cargo test (push) Successful in 21s
CI / editor keymaps (push) Successful in 1m6s
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>
2026-05-26 23:02:17 -03:00
gffranco 8f8c2884a9 feat(vim): conceal markup delimiters when conceallevel=2
Set conceallevel=2 and concealcursor=nc on .wiki buffers and add
contained `conceal` matches for the bold/italic/code delimiters and
wikilink brackets/targets. The static-fallback rendering now mirrors
what the Neovim semantic-token path produced: *bold* shows as bold
without the asterisks, [[target|desc]] shows just "desc", etc.

The wikilink region uses an inline `contains=` (no line continuation)
because some Vim builds (notably Homebrew Vim 9.2) raise E10 on the
leading `\\ contains=...` form even though the surrounding region
parses fine.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:01:55 -03:00
gffranco b3e2a72023 fix(lsp): add coc.nvim dispatch path to all Vim-side LSP calls
CI / cargo fmt --check (push) Successful in 27s
CI / cargo clippy (push) Successful in 37s
CI / cargo test (push) Successful in 33s
CI / editor keymaps (push) Successful in 1m18s
Commands were hardwired to vim-lsp (lsp#send_request, :LspDefinition,
:LspReferences, :LspRename). Users running coc.nvim got "vim-lsp not
loaded" on every command despite the plugin correctly detecting coc.

Changes:

autoload/nuwiki/commands.vim
- Add s:has_coc() (checks CocActionAsync exists)
- Add s:coc_wrap() adapter — converts coc's (err, result) callback into
  the vim-lsp notification shape {response:{result:…}} so all existing
  on_notification handlers work unchanged
- s:exec(): try vim-lsp first, fall back to CocActionAsync('runCommand',
  …) with arguments spread as positional params, then error if neither
- Add s:jump_definition() helper — :LspDefinition vs CocActionAsync
- Add nuwiki#commands#backlinks() — :LspReferences vs CocActionAsync
- badd_link(): coc path uses CocAction('getDefinition') to resolve the
  target URI without navigating
- follow_link_or_create(): replace inline :LspDefinition checks with
  s:jump_definition()
- rename_file(): :LspRename vs CocActionAsync('rename')

ftplugin/vimwiki.vim
- VimwikiFollowLink / NuwikiFollowLink: dispatch through
  nuwiki#commands#follow_link_or_create() instead of :LspDefinition
- VimwikiBacklinks / NuwikiBacklinks / VWB: dispatch through
  nuwiki#commands#backlinks() instead of :LspReferences

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:39:27 -03:00
gffranco 9354e1c176 fix(mappings): add global entry-point keymaps and correct <Leader>ww/<Leader>wt targets
CI / cargo fmt --check (push) Successful in 17s
CI / cargo clippy (push) Successful in 31s
CI / cargo test (push) Successful in 53s
CI / editor keymaps (push) Successful in 1m30s
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>
2026-05-21 21:34:41 -03:00
gffranco a8b89a5303 fix(vim): correct broken symlink, missing commands, and bad VimwikiGoBackLink
CI / cargo fmt --check (push) Successful in 40s
CI / cargo clippy (push) Successful in 49s
CI / cargo test (push) Successful in 30s
CI / editor keymaps (push) Successful in 1m16s
- README: fix plain-Vim install symlink (ln -s target/… → ln -s ../target/…
  so the relative path resolves correctly from inside bin/)
- ftplugin: add missing VimwikiUISelect command on the Vim path
- ftplugin: add missing -count attribute on NuwikiTabIndex (Vim path)
- ftplugin: fix VimwikiGoBackLink (Neovim path) — `normal!` with no
  argument causes E471; use `execute "normal! \<C-o>"` instead

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 20:40:20 -03:00
gffranco ae96562969 parity(vim): port text objects + folding, fix smart_return textlock
CI / cargo fmt --check (push) Successful in 53s
CI / cargo clippy (push) Successful in 1m27s
CI / cargo test (push) Successful in 1m24s
CI / editor keymaps (push) Successful in 2m27s
Close the remaining Vim-vs-Neovim functional gaps:

  * Text objects — new `autoload/nuwiki/textobjects.vim` mirroring
    `lua/nuwiki/textobjects.lua`. All five pairs (ah/ih, aH/iH,
    al/il, a\/i\, ac/ic) wired in the Vim path of `ftplugin/vimwiki.vim`
    via the classic `:<C-u>call` idiom that works cleanly in both
    operator-pending and visual modes.

  * Folding — new `autoload/nuwiki/folding.vim` providing a regex
    `foldexpr` over headings, plus a tidy `foldtext`. Wired in the
    Vim path; opts out via `let g:nuwiki_no_folding = 1`.

  * `smart_return` was using `setline()` / `append()` inside an
    `<expr>` callback — fine on Neovim but plain Vim's stricter
    textlock raised E565. Rewrote as pure keystrokes (matches the
    Lua version): table rows insert via `<CR>...<Esc>0li`, empty
    list lines break via `<Esc>0DA<CR>`.

  * Function-name parity: added
    `nuwiki#commands#heading_add_level`,
    `nuwiki#commands#heading_remove_level`,
    `nuwiki#commands#table_move_column_{left,right}` as thin
    aliases over the original short names so the public
    `nuwiki#commands#*` surface matches `require('nuwiki.commands').*`.

Test harness:
  * `scripts/test-keymaps-vim.vim` extended from 12 to 30 cases:
    4 smart_return, 3 smart_tab/<S-Tab>, 3 named-command exists,
    4 text-object helpers, 1 `dah` end-to-end, 2 folding, and the
    original 12 pure-VimL cases. (Visual-mode mark inspection in
    `vim -e -s` stays unreliable, so text objects are exercised
    at the helper level plus one operator-pending end-to-end.)

Gates: 456 Rust / 39 Neovim / 30 Vim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:30:15 +00:00
gffranco 2562a046db parity(6): insert-mode <Tab>/<S-Tab> table cell nav
`<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>
2026-05-12 21:37:53 +00:00
gffranco e347f605ff parity(5): smart <CR> in insert mode (VimwikiReturn)
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>
2026-05-12 21:34:36 +00:00
gffranco 46ae618b5f parity(2): insert-mode list bindings (<C-D>/<C-T>/<C-L><C-?>)
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>
2026-05-12 21:11:53 +00:00
gffranco ee0309b3c2 parity(1): vimwiki per-wiki config keys, table colspan/rowspan,
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>
2026-05-12 15:10:27 +00:00
gffranco de8b8fa30d feat(13.1): colorize + color_dic → ColorNode renderer
CI / cargo fmt --check (push) Successful in 26s
CI / cargo clippy (push) Successful in 1m24s
CI / cargo test (push) Successful in 1m24s
CI / editor keymaps (push) Successful in 1m45s
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>
2026-05-12 14:45:39 +00:00
gffranco 4245424d2f feat(13.1-B): table rewriters — insert, align, moveColumn
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>
2026-05-12 14:41:51 +00:00
gffranco 9d34c8baa2 feat(13.1-A): list rewriters — removeDone, renumber, changeSymbol/Level
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>
2026-05-12 14:37:11 +00:00
gffranco cebc806ce3 feat(13.1-C): link helpers — pasteWikilink, pasteUrl, normalize
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>
2026-05-12 14:32:35 +00:00
gffranco 347e2f02f7 fix(follow-link): create missing page + wrap word on <CR>
CI / cargo fmt --check (push) Successful in 35s
CI / cargo clippy (push) Successful in 1m8s
CI / cargo test (push) Successful in 1m16s
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>
2026-05-12 01:44:26 +00:00
gffranco 9d86218b13 fix(keymap): C-Space toggle fires across all terminal byte spellings
CI / cargo fmt --check (push) Successful in 25s
CI / cargo clippy (push) Successful in 1m5s
CI / cargo test (push) Successful in 1m12s
User reported Ctrl+Space didn't toggle list items. Three things
contributed:

1. **Terminal byte ambiguity.** Different terminals encode Ctrl+Space
   differently — GUI / kitty protocol sends `<C-Space>`, xterm and
   most legacy terms send NUL which Vim sees as `<C-@>` (or, in some
   Neovim builds, `<Nul>` in keymap.lhs). The previous keymap
   registered only `<C-Space>` + `<C-@>`. Added the `<Nul>` alias too
   (both Lua and VimL paths) so the binding fires regardless of what
   byte the user's terminal produces.

2. **Deprecated `vim.lsp.util.make_position_params`.** Neovim 0.12
   requires `position_encoding` explicitly. The old no-arg call now
   prints a deprecation warning, and in stricter setups returns nil
   — which made the command's `arguments` malformed and the
   `executeCommand` request silently fail. `position_params()` now
   pulls `offset_encoding` from the resolved client and passes both
   args, with a `pcall`-guarded fallback to the no-arg form for
   Neovim < 0.10.

3. **Deprecated `client.request(...)` dot-call.** Same Neovim 0.12
   change — `Client:request()` is the method form. Use the colon
   call now; both arities are preserved for back-compat.

Verified end-to-end: feeding all three of `<C-Space>` / `<C-@>` /
`<Nul>` via `nvim_feedkeys` toggles the checkbox; `:messages` no
longer carries the deprecation warnings. 381 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 01:28:23 +00:00
gffranco d58a34a2d7 feat(vim): close keymap gap with Neovim (47 → 63 maps)
CI / cargo fmt --check (push) Successful in 30s
CI / cargo clippy (push) Successful in 1m8s
CI / cargo test (push) Successful in 1m14s
The previous commit registered the parity surface on Neovim but only
ported ~75% to plain Vim. Filling the remaining 16:

autoload/nuwiki/commands.vim — new functions:
- `wiki_ui_select` — Vim has no `vim.ui.select`, so route through
  `inputlist()` after fetching the wiki list via the LSP.
- `open_below_with_bullet` / `open_above_with_bullet` — port of the
  Lua `o` / `O` continuation that preserves the list marker (and a
  `[ ]` checkbox when present) on the new line.
- `next_header` / `prev_header` / `next_sibling_header` /
  `prev_sibling_header` / `parent_header` — port of the Lua heading
  jumper. Pure VimL, no LSP round-trip.

ftplugin/vimwiki.vim (Vim path) new mappings:
- `<Leader>ws` → `wiki_ui_select`
- `]=` / `[=` → next / prev sibling header
- `]u` / `[u` → parent header
- `o` / `O` → bullet continuation
- `gl<Space>` / `gL<Space>` → :VimwikiRemove*CB stubs
- `gq1` / `gw1` → :VimwikiTableAlignQ1/W1 stubs
- x-mode variants for `+`, `<Leader>wc`, `gln`, `glp`, `glx`

Replaced the regex-based `]]` / `[[` (which just searched `^\s*=\+\s`)
with calls into the new heading nav so they skip non-heading `=`
runs and respect heading levels.

Verified end-to-end: `silent map <buffer>` reports 63 maps on Vim,
matching Neovim's 63. All 377 Rust tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:50:49 +00:00
gffranco 9e6faa5554 feat(mappings): vimwiki-parity default keymaps for Vim + Neovim
CI / cargo fmt --check (push) Successful in 17s
CI / cargo clippy (push) Successful in 1m19s
CI / cargo test (push) Successful in 1m22s
The Phase 19 keymap layer only covered the high-value subset and used
`g=`/`g-` instead of vimwiki's `=`/`-` for header levels. Users
reporting "mappings don't seem to be working" were either on the Vim
path (which registered zero keymaps) or hitting bindings that don't
exist (e.g. `<Tab>`, `<S-CR>`, `gll`, `gqq`).

Cross-referenced upstream vimwiki/ftplugin/vimwiki.vim and rewired
both `lua/nuwiki/keymaps.lua` and `ftplugin/vimwiki.vim` (Vim path)
to match its default surface. Mappings backed by commands that exist
on the server dispatch directly; §13.1-deferred mappings register
with the same lhs but stub out to a notification — so users get
parity *signalling*, not silence.

New on Neovim (and matched on Vim where possible):

Links group:
- `<S-CR>` / `<C-CR>` / `<C-S-CR>` — follow in split / vsplit / tab
- `<Tab>` / `<S-Tab>` — jump to next / prev `[[…]]` (pure Lua search)
- `+` (n + x) — :VimwikiNormalizeLink stub
- `<Leader>wc` (n + x) — :VimwikiColorize stub

Lists group:
- `<C-@>` (n + x) — terminal-alt for `<C-Space>`
- `gln` / `glp` increment + decrement (deferred backward; same fn for now)
- `glh` / `gll` / `gLh` / `gLl` — list level stubs
- `glr` / `gLr` — list renumber stubs
- `gl<Space>` / `gL<Space>` — checkbox remove stubs
- `o` / `O` — open below/above and continue the bullet (pure Lua)
- visual-mode variants of `<C-Space>`, `gln`, `glp`, `glx`

Header group (key group, now buffer-local):
- `=` / `-` — promote / demote (was `g=`/`g-`, matched to vimwiki)
- `]]` / `[[` — next / prev header (pure Lua)
- `]=` / `[=` — next / prev sibling header (pure Lua)
- `]u` / `[u` — parent header (pure Lua)

Table group: `gqq`, `gq1`, `gww`, `gw1`, `<A-Left>`, `<A-Right>` stubs.

Wiki prefix: `<Leader>w<Leader>m` (vimwiki's tomorrow), `<Leader>w<Leader>i` (rebuild diary index).

Config schema (`config.options.mappings`) updates to vimwiki's group
names — `links` / `lists` / `headers` / `table_editing` / `diary` /
`html_export` / `text_objects` / `wiki_prefix`. Old names
(`list_editing` / `header_nav`) are retired; rename only — keeps the
opt-out shape intact.

Vim path: same set ported to buffer-local VimL maps. `g:nuwiki_no_default_mappings`
opts out the whole layer for users who prefer their own bindings. Lua
side uses the Neovim 0.11+ `vim.keymap.set` API; both call straight
into the same `nuwiki#commands#…` autoload (Vim) / `nuwiki.commands`
(Lua) layer that drives the LSP.

Total 377 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:46:40 +00:00
gffranco 04df170791 feat(vim): :Vimwiki* / :Nuwiki* commands for plain Vim via vim-lsp
CI / cargo fmt --check (push) Successful in 30s
CI / cargo clippy (push) Successful in 1m16s
CI / cargo test (push) Successful in 1m27s
The Phase 19 ftplugin only registered the command surface on Neovim
and finished early for plain Vim, leaving `start-vim.sh` users with
only commentstring + suffixesadd — exactly the "no commands" report.

`autoload/nuwiki/commands.vim` now ships the Vim-side dispatch:
- `s:exec` sends `workspace/executeCommand` through vim-lsp's
  `lsp#send_request`. Optional callback for commands that return data.
- `s:open_uri_from` reads `{ uri }` out of the LSP response and runs
  `:edit` (or `:tabedit` for the tab variants).
- `s:results_to_qf` lifts `checkLinks` / `findOrphans` / `tags.search`
  arrays into the quickfix list and opens `:copen` — same UX as the
  Neovim path.
- `s:open_browser` fires `open` / `xdg-open` after `export.browse`.
- §13.1 deferred commands stub out with a "not yet implemented"
  notification so users get the same signal as on Neovim.

`ftplugin/vimwiki.vim` defines the same `:Vimwiki*` / `:Nuwiki*`
command set on the plain-Vim path, each delegating to its
`nuwiki#commands#…` autoload counterpart. `:VimwikiFollowLink` /
`:VimwikiBacklinks` map straight to vim-lsp's `:LspDefinition` /
`:LspReferences`. coc.nvim users can still use `:CocCommand` directly
and ignore the aliases entirely.

Verified with `vim -e -s -u .vimrc index.wiki`:
  filetype=vimwiki, did_ftplugin=1,
  :VimwikiTOC, :NuwikiIndex, :VimwikiToggleListItem all defined.

Total 377 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:23:06 +00:00
gffranco 09b9bd98a3 fix(ftplugin): load on .wiki buffers without manual filetype override
CI / cargo fmt --check (push) Successful in 35s
CI / cargo clippy (push) Successful in 1m12s
CI / cargo test (push) Successful in 1m26s
Three reasons the plugin wasn't activating on `nvim --clean -u init.lua
foo.wiki`:

1. **File-name mismatch.** Vim's filetype-plugin runtime looks for
   `ftplugin/<filetype>.vim` matching the *filetype name*. Our
   filetype is `vimwiki` (per `ftdetect`) but the file was named
   `ftplugin/nuwiki.vim` — so `:runtime! ftplugin/vimwiki.vim` from
   the built-in FileType autocmd never found it, and none of the
   `:Vimwiki*` / `:Nuwiki*` commands got defined. Same wart on the
   syntax side. Renamed:
   - `ftplugin/nuwiki.vim`  → `ftplugin/vimwiki.vim`
   - `syntax/nuwiki.vim`    → `syntax/vimwiki.vim`

2. **`setfiletype` deferred to Neovim's bundled rule.** Neovim ships
   a default `*.wiki → mediawiki` rule via `vim.filetype.add`, and
   `:setfiletype vimwiki` is a no-op once a filetype has been set.
   - `ftdetect/nuwiki.vim` now uses `set filetype=vimwiki` (force).
   - `lua/nuwiki/init.lua`'s `setup()` also calls
     `vim.filetype.add({ extension = { wiki = 'vimwiki', ... } })`
     so the modern table-based detection picks us before the
     bundled rule fires. Honours `config.options.file_extension`
     so a user with a custom extension gets covered automatically.

3. **`foldmethod`/`foldexpr` are window-local options.**
   `ftplugin.lua` was setting them with `nvim_set_option_value({ buf
   = bufnr })`, which threw `'buf' cannot be passed for window-local
   option 'foldmethod'` and aborted the rest of the per-buffer
   attach. Switched to `vim.opt_local.*` (window-aware), applied on
   the current window when the ftplugin fires, and re-applied via a
   `BufWinEnter` autocmd so `:split` / `:vsplit` keep the fold mode.

Verified end-to-end with:

  nvim --clean -u init.lua sample.wiki
  → filetype = vimwiki
  → :VimwikiTOC, :NuwikiIndex, … all defined
  → foldmethod = expr, foldexpr = v:lua.vim.lsp.foldexpr()
  → ftdetect, plugin, ftplugin, syntax all in :scriptnames

Total 377 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:16:57 +00:00
gffranco 63e5b6d514 phase 19: editor glue v2
CI / cargo fmt --check (push) Successful in 21s
CI / cargo clippy (push) Successful in 1m8s
CI / cargo test (push) Successful in 1m18s
Server-side:
- New `folding.rs` module with `folding_ranges(ast, total_lines)`.
  Emits one fold per top-level heading (start → line before the
  next same-or-higher-level heading, or EOF) plus one per top-level
  list block. Nested headings fold inside their parent thanks to the
  level-aware end-line computation; sublists fold implicitly via
  their parent item's source span. `FoldingRangeKind::Region` is set
  on every fold so collapsing UIs render them as section folds.
- `Backend::folding_range` handler wires the module into
  `textDocument/foldingRange`; `ServerCapabilities.folding_range_provider`
  advertises it.
- `folding::line_count` exposed for the handler and tests; treats a
  trailing newline as a virtual empty line (the line LSP positions
  use for EOF).

Editor glue (Neovim primary, Vim minimal):
- `lua/nuwiki/commands.lua` — async `workspace/executeCommand`
  wrappers for every server command shipped through Phase 18. The
  open-* family handles `{ uri }` responses by opening the file
  (with `tab` / `split` variants). `check_links` and `find_orphans`
  hand results to `setqflist` + `:copen` for quickfix-style review.
  §13.1-deferred commands (`list.changeSymbol`, table rewriters,
  `link.pasteWikilink/pasteUrl`, `colorize`) stub out with
  `vim.notify` so users get a clear "not yet implemented" signal
  instead of an LSP "unknown command" error.
- `lua/nuwiki/keymaps.lua` — buffer-local default mappings. Subgroups
  (`list_editing`, `header_nav`, `diary`, `html_export`,
  `text_objects`) flip independently via the new
  `mappings.<group>` config. Heading promote/demote uses `g=`/`g-`
  to avoid clobbering Vim's built-in `=`/`-` operators.
- `lua/nuwiki/textobjects.lua` — `ah`/`ih` (around/inside heading)
  using a buffer scan for the heading-block boundary. The four
  remaining text objects from SPEC §12.10 wait until §13.1 lands
  the table/list rewriters they share infrastructure with.
- `lua/nuwiki/folding.lua` — pure regex `foldexpr` + `foldtext`
  fallback for clients without `foldingRange`. Same heading-block
  model as the server.
- `lua/nuwiki/ftplugin.lua` — single per-buffer attach entry point.
  `folding = 'lsp'` (default) uses Neovim 0.11+'s `vim.lsp.foldexpr`,
  falling back to the regex on older versions; `'expr'` forces the
  fallback; `'off'` skips folding setup.
- `lua/nuwiki/config.lua` — extends defaults with `mappings = {...}`
  (P10 keymap layer) and `folding` (P14 resolved).
- `ftplugin/nuwiki.vim` — declares every `:Vimwiki*` / `:Nuwiki*`
  command from SPEC §12.10 by inlining `lua require(...).fn()`
  bodies (no script-local function indirection so commands stay
  callable after re-source). Plain-Vim users get only the buffer
  options; they're expected to drive the LSP via vim-lsp / coc's
  built-in commands.

Health check (§12.10 additions):
- `:checkhealth nuwiki` now reports the count of `executeCommand`
  entries the server advertises, whether `foldingRange` capability
  was negotiated, and whether the configured HTML output directory
  exists + is writable. Default keymap subgroup status is also
  surfaced.

Tests: 8 new in `phase19_folding.rs` covering empty docs, single
heading → EOF, sibling headings each getting their own fold,
nested heading bounded by parent, top-level list folds, single-line
heading non-fold, fold-kind classification, and `line_count`
semantics. Total 377 tests pass; fmt + clippy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 21:57:05 +00:00
gffranco ee61d40872 phase 9: editor glue — plugin, LSP wiring, install, health, docs
CI / cargo fmt --check (push) Successful in 30s
CI / cargo clippy (push) Successful in 1m23s
CI / cargo test (push) Successful in 1m29s
P5 + P6 resolved (SPEC §11): Neovim 0.11+ and Vim 9.1+.

Universal Vim entry:
- plugin/nuwiki.vim dispatches Vim vs Neovim. Neovim path waits for
  the user's `require('nuwiki').setup()` call; Vim path starts the
  LSP client on `FileType vimwiki` and exposes `:NuwikiInstall`.
- ftdetect/nuwiki.vim associates `*.wiki` with the `vimwiki` filetype.
- ftplugin/nuwiki.vim sets commentstring + comments + formatoptions +
  suffixesadd + iskeyword, with a clean `b:undo_ftplugin`.
- syntax/nuwiki.vim ships default `@vimwiki*` highlight links for the
  semantic-token legend (with `level1..6` + `centered` modifiers),
  plus a minimal regex fallback so static highlighting works before
  the LSP attaches.

Lua glue (Neovim 0.11+):
- init.lua exposes setup() / install() / health() — wiring only per
  SPEC §6.2.
- config.lua holds the §7.5 schema (wiki_root / file_extension /
  syntax / log_level) and a tbl_deep_extend apply().
- lsp.lua registers via `vim.lsp.config{} + vim.lsp.enable` on 0.11+;
  falls back to a FileType autocmd calling `vim.lsp.start` on older
  builds. Root dir walks up for `.git` / `.nuwiki`, then uses
  wiki_root.
- install.lua does target-triple detection (Linux gnu/musl x x86_64/
  aarch64, macOS arm/x86, Windows), curl+tar download from the
  release URL, cp/chmod into bin/, with a cargo fallback. Honours
  `g:nuwiki_build_from_source`.
- health.lua implements `:checkhealth nuwiki` per §7.6.

VimL glue (Vim 9.1+):
- autoload/nuwiki/lsp.vim follows §7.4 preference order:
  vim-lsp (calls `lsp#register_server`) → coc.nvim (prints the
  coc-settings.json snippet) → error.
- scripts/download_bin.vim mirrors the Lua installer for Dein /
  vim-plug build hooks — same target triples, same download →
  fallback → cargo build chain.

Help: doc/nuwiki.txt with tags for install (lazy.nvim / vim-plug /
Dein), config options, health, LSP feature list, `:NuwikiInstall`.

Rust workspace unchanged this phase; 172 tests still green locally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 00:38:09 +00:00
gffranco cf336ee839 phase 0: scaffold workspace, plugin layout, and CI
CI / cargo fmt --check (push) Successful in 46s
CI / cargo clippy (push) Successful in 1m33s
CI / cargo test (push) Successful in 53s
Lay down the empty repo skeleton defined in SPEC.md §5 so subsequent
phases have somewhere to land:

- Cargo workspace (resolver v2, edition 2021, MSRV 1.83) with
  nuwiki-core, nuwiki-lsp, nuwiki-ls — dep direction matches §6.2.
- Vim/Neovim plugin directory layout (plugin, lua, autoload, ftdetect,
  ftplugin, syntax, doc, scripts) with header-only stubs.
- .gitea/workflows/ci.yaml running fmt, clippy -D warnings, and tests
  pinned to Rust 1.83.
- README, dual MIT/Apache-2.0 license texts, .gitignore.

Verified: cargo check / fmt --check / clippy / test all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:01:58 +00:00