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

nuwiki

A vimwiki-compatible Vim/Neovim plugin backed by a Rust language server.

nuwiki replaces the original vimwiki plugin while keeping its file format, keymaps, and :Vimwiki* command surface intact. The substantive work happens in a Rust LSP daemon — Vim and Neovim are thin client layers that just wire up keystrokes and display results.

  • Full vimwiki syntax (.wiki) — headings, lists with checkboxes, tables, math, code, tags, links (wiki / interwiki / diary / file / raw URL / transclusion).
  • Goto-definition, backlinks, hover, completion, workspace symbol search, semantic highlighting — all over LSP.
  • Diary (today/yesterday/tomorrow/next/prev), tags (search + link generation), TOC generation, broken-link diagnostics, orphan finder.
  • Multi-wiki with cross-wiki interwiki links ([[wn.name:Page]]).
  • HTML export with templates, CSS, auto-export on save, RSS feed.
  • Folding (LSP foldingRange + regex fallback), header & link text objects, the <CR> two-step link-create-and-follow.
  • :Vimwiki* command compat + :Nuwiki* canonical aliases.

See SPEC.md for the architecture, design decisions, and the full phase log.


Installation

The plugin ships a nuwiki-ls binary that the editor talks to over LSP stdio. The install() Lua helper downloads a pre-built release for your platform, falling back to cargo build --release if no asset matches.

lazy.nvim

{
  'gffranco/nuwiki',
  build = ":lua require('nuwiki').install()",
  ft = { 'vimwiki' },
  opts = {
    wiki_root = '~/vimwiki',
  },
}

vim-plug

Plug 'gffranco/nuwiki', { 'do': 'vim -e -s -c "source scripts/download_bin.vim" -c "q"' }

In init.lua / init.vim:

require('nuwiki').setup({ wiki_root = '~/vimwiki' })

Dein

call dein#add('gffranco/nuwiki', {
  \ 'build': 'vim -e -s -c "source scripts/download_bin.vim" -c "q"'
  \ })

Plain Vim (no plugin manager)

git clone https://code.gfran.co/gffranco/nuwiki ~/.vim/pack/gffranco/start/nuwiki
cd ~/.vim/pack/gffranco/start/nuwiki && cargo build --release -p nuwiki-ls
mkdir -p bin && ln -s target/release/nuwiki-ls bin/nuwiki-ls

Then ensure an LSP client is loaded (Vim path prefers vim-lsp, falls back to coc.nvim). Neovim 0.11+ uses its built-in LSP client — no extra plugin needed.

Trying it without touching your config

./start-nvim.sh           # spawns Neovim with a scratch wiki under
                          # $XDG_CACHE_HOME/nuwiki-dev
./start-vim.sh            # same, but for plain Vim — clones vim-lsp
                          # into the cache on first run

Both scripts build the LSP binary, seed a sample wiki, and launch the editor against an isolated state dir. Pass a wiki file path to open it directly; NUWIKI_DEV_WIKI=/path/to/wiki points at your own sandbox instead.


Configuration

Defaults are vimwiki-compatible. Full schema (with type annotations and defaults) lives in lua/nuwiki/config.lua.

require('nuwiki').setup({
  -- Single-wiki shorthand (vimwiki-compatible).
  wiki_root      = '~/vimwiki',
  file_extension = '.wiki',
  syntax         = 'vimwiki',     -- 'vimwiki' | 'markdown' (future)
  log_level      = 'warn',

  -- Or the multi-wiki form. The single-wiki keys above desugar into
  -- a one-entry `wikis = [...]` list, so you can pick either shape.
  wikis = {
    {
      name           = 'personal',
      root           = '~/vimwiki',
      file_extension = '.wiki',
      diary_rel_path = 'diary',
      diary_index    = 'diary',
      -- HTML export (Phase 17)
      html_path        = '~/vimwiki/_html',
      template_path    = '~/vimwiki/_templates',
      template_default = 'default',
      template_ext     = '.tpl',
      css_name         = 'style.css',
      auto_export      = false,    -- export on save
      exclude_files    = {},       -- glob patterns
    },
  },

  -- Keymap groups — flip subgroups off independently. Defaults all on.
  mappings = {
    enabled       = true,
    wiki_prefix   = true,  -- <Leader>w*
    links         = true,  -- <CR>, <S-CR>, <Tab>, <BS>, +, …
    lists         = true,  -- <C-Space>, gln/glp/glx, gl<Space>, o/O
    headers       = true,  -- =, -, ]], [[, ]=, [=, ]u, [u
    table_editing = true,  -- gqq, gq1, gww, gw1, <A-Left>, <A-Right>
    diary         = true,  -- <C-Down>, <C-Up>
    html_export   = true,  -- <Leader>wh, <Leader>whh
    text_objects  = true,  -- ah, ih
  },

  -- Folding strategy: 'lsp' (server-driven foldingRange — default on
  -- Neovim 0.11+), 'expr' (pure regex), or 'off'.
  folding = 'lsp',

  -- Broken-link diagnostic severity: 'off' | 'hint' | 'warn' | 'error'.
  diagnostic = {
    link_severity = 'warn',
  },
})

Vim users without Lua should set the equivalent globals before sourcing the plugin:

let g:nuwiki_wiki_root      = '~/vimwiki'
let g:nuwiki_file_extension = '.wiki'
let g:nuwiki_log_level      = 'warn'
let g:nuwiki_no_default_mappings = 0   " set to 1 to skip the keymap layer

Usage

Commands

Both :Vimwiki* (migration compat) and :Nuwiki* (canonical native) are registered as buffer-local commands on every .wiki buffer. Selected highlights — see :Vimwiki<Tab> for the full list.

Command What it does
:VimwikiIndex [N] Open wiki N's index.wiki (default: 1)
:VimwikiUISelect Pick a wiki from a list (multi-wiki only)
:VimwikiMakeDiaryNote Open today's diary page
:VimwikiDiaryNextDay / :VimwikiDiaryPrevDay Chronological diary nav
:VimwikiTOC Generate / refresh table of contents on the current page
:VimwikiGenerateLinks Flat list of every page in the wiki
:VimwikiCheckLinks Send broken links to the quickfix list
:VimwikiSearchTags {tag} Quickfix of all occurrences of :tag:
:VimwikiGenerateTagLinks [tag] Insert a section linking all pages with <tag>
:VimwikiRebuildTags Force a full workspace re-index
:VimwikiRenameFile Rename current page; rewrites incoming links
:VimwikiDeleteFile Delete current page (server-side DeleteFile)
:Vimwiki2HTML Render current page to HTML
:Vimwiki2HTMLBrowse Render + open in default browser
:VimwikiAll2HTML[!] Export the whole wiki (! forces, otherwise incremental)
:VimwikiRss Write rss.xml of diary entries
:VimwikiFollowLink Follow link under cursor (<CR>)
:VimwikiBacklinks Show references to current page

Default keymaps

All buffer-local. Disable any group via mappings.<group> = false in Neovim, or let g:nuwiki_no_default_mappings = 1 in Vim.

Links

Key Action
<CR> Smart follow: inside [[…]] → jump; on a bare word → wrap as [[word]] (second <CR> follows)
<S-CR> Follow in horizontal split
<C-CR> Follow in vertical split
<C-S-CR> Follow in new tab
<BS> Jump back (<C-o>)
<Tab> / <S-Tab> Next / previous wikilink on or after the cursor

Lists

Key Action
<C-Space> / <C-@> / <Nul> Toggle checkbox (terminal byte aliases)
gln / glp Cycle checkbox state (forward / backward)
glx Toggle the rejected marker [-]
gnt Jump to the next unfinished task
o / O Open a new line and continue the list marker (and checkbox if any)

Headers

Key Action
= / - Add / remove a heading level
]] / [[ Next / previous heading
]= / [= Next / previous sibling heading
]u / [u Jump to parent heading

Wiki / diary / export

Key Action
<Leader>ww Today's diary
<Leader>ws Pick a wiki (vim.ui.select / inputlist)
<Leader>wi Diary index
<Leader>w<Leader>w / <Leader>w<Leader>y / <Leader>w<Leader>t Today / yesterday / tomorrow
<Leader>w<Leader>i Rebuild diary index page
<Leader>wn / <Leader>wd / <Leader>wr Goto / delete / rename page
<Leader>wh / <Leader>whh Export current page → HTML / open in browser
<Leader>wha Export every page
<C-Down> / <C-Up> Next / previous diary entry

Text objects

Key Selection
ah / ih A heading (with / without the heading line itself)

Additional text objects (aH, iH, a\, i\, ac, ic, al, il) are planned follow-ups now that the table/list rewriter commands they depend on have shipped.

Health check

:checkhealth nuwiki

Reports the binary path, the LSP client status, registered executeCommand entries, whether foldingRange was negotiated, and whether the configured HTML output directory is writable.

Migrating from vimwiki

The file extension, syntax, command names, and default keymaps all match vimwiki. Drop the original vimwiki plugin from your config, install nuwiki, and point wiki_root at the same directory. Existing pages, diary entries, tags, and templates Just Work.

Two known behavior deltas vs. upstream vimwiki:

  • nuwiki's index is built lazily on workspace scan; the very first :VimwikiCheckLinks after launch may be empty until the background task completes. Subsequent runs are instant.
  • gln and glp both call nuwiki.list.cycleCheckbox, which is forward-only at present. A reverse cycle lands once the v1.1 list rewriters do.

Repository layout

nuwiki/
├── Cargo.toml              workspace root
├── crates/
│   ├── nuwiki-core/        parser, AST, renderer (editor-independent)
│   ├── nuwiki-lsp/         LSP protocol bridge
│   └── nuwiki-ls/          stdio LSP binary entry point
├── plugin/                 universal Vim/Neovim entry point
├── lua/nuwiki/             Neovim Lua glue
├── autoload/nuwiki/        Vim VimL glue
├── ftdetect/               filetype detection for .wiki
├── ftplugin/vimwiki.vim    per-buffer settings + commands + keymaps
├── syntax/vimwiki.vim      static fallback highlighting
├── doc/                    `:h nuwiki`
├── scripts/                test harness + build helpers
└── .gitea/workflows/       CI / release pipelines

Development

Build + test

cargo build --workspace
cargo test  --workspace                 # 381 Rust tests
./scripts/test-keymaps.sh               # 19 Neovim keymap cases (LSP-driven)
./scripts/test-keymaps-vim.sh           # 12 Vim keymap cases (pure-VimL)

CI runs all four on every push (.gitea/workflows/ci.yaml). MSRV: Rust 1.83.

Trying changes against a real wiki

./start-nvim.sh                         # isolated state under
./start-vim.sh                          # $XDG_CACHE_HOME/nuwiki-dev

Both regenerate their minimal config on every launch and seed a scratch wiki with sample pages, links, checkboxes, and tags.

Implementation status

v1.0 — foundation

Phase Name Status
0 Scaffolding done
1 Core AST done
2 Syntax Plugin Interface done
3 Vimwiki Lexer done
4 Vimwiki Parser done
5 Renderer done
6 LSP Foundation done
7 Semantic Tokens done
8 Navigation done
9 Editor Glue done
10 CI/CD release pipeline done

v1.1 — full vimwiki replacement

Phase Name Status
11 Plumbing prerequisites done
12 Tags done
13 Workspace edits + executeCommand done
14 List & table edit commands done
15 Link health + TOC/index generation done
16 Diary done
17 HTML export commands done
18 Multi-wiki done
19 Editor glue v2 (:Vimwiki* compat, keymaps, text objects, folding) done

License

Dual-licensed under either of:

at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work shall be dual licensed as above, without any additional terms or conditions.

S
Description
No description provided
Readme 1.1 MiB
v0.5.2 Latest
2026-07-02 12:02:20 +00:00
Languages
Vim Script 49.1%
Lua 36.7%
Shell 11.2%
HTML 3%