gffranco de8b8fa30d
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
feat(13.1): colorize + color_dic → ColorNode renderer
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

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%