# nuwiki **A vimwiki-compatible Vim/Neovim plugin backed by a Rust language server.** nuwiki replaces the original [vimwiki](https://github.com/vimwiki/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 `` two-step link-create-and-follow. - ✅ `:Vimwiki*` command compat + `:Nuwiki*` canonical aliases. See [`SPEC.md`](./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 ```lua { 'gffranco/nuwiki', build = ":lua require('nuwiki').install()", ft = { 'vimwiki' }, opts = { wiki_root = '~/vimwiki', }, } ``` ### vim-plug ```vim Plug 'gffranco/nuwiki', { 'do': 'vim -e -s -c "source scripts/download_bin.vim" -c "q"' } ``` In `init.lua` / `init.vim`: ```lua require('nuwiki').setup({ wiki_root = '~/vimwiki' }) ``` ### Dein ```vim call dein#add('gffranco/nuwiki', { \ 'build': 'vim -e -s -c "source scripts/download_bin.vim" -c "q"' \ }) ``` ### Plain Vim (no plugin manager) ```sh 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`](https://github.com/prabirshrestha/vim-lsp), falls back to [`coc.nvim`](https://github.com/neoclide/coc.nvim)). Neovim 0.11+ uses its built-in LSP client — no extra plugin needed. ### Trying it without touching your config ```sh ./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`](./lua/nuwiki/config.lua). ```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, -- w* links = true, -- , , , , +, … lists = true, -- , gln/glp/glx, gl, o/O headers = true, -- =, -, ]], [[, ]=, [=, ]u, [u table_editing = true, -- gqq, gq1, gww, gw1, , diary = true, -- , html_export = true, -- wh, 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: ```vim 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` 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 `` | | `: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 (``) | | `:VimwikiBacklinks` | Show references to current page | ### Default keymaps All buffer-local. Disable any group via `mappings. = false` in Neovim, or `let g:nuwiki_no_default_mappings = 1` in Vim. **Links** | Key | Action | |---|---| | `` | Smart follow: inside `[[…]]` → jump; on a bare word → wrap as `[[word]]` (second `` follows) | | `` | Follow in horizontal split | | `` | Follow in vertical split | | `` | Follow in new tab | | `` | Jump back (``) | | `` / `` | Next / previous wikilink on or after the cursor | **Lists** | Key | Action | |---|---| | `` / `` / `` | 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 | |---|---| | `ww` | Today's diary | | `ws` | Pick a wiki (`vim.ui.select` / `inputlist`) | | `wi` | Diary index | | `ww` / `wy` / `wt` | Today / yesterday / tomorrow | | `wi` | Rebuild diary index page | | `wn` / `wd` / `wr` | Goto / delete / rename page | | `wh` / `whh` | Export current page → HTML / open in browser | | `wha` | Export every page | | `` / `` | 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 ```vim :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 ```sh 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 ```sh ./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: - Apache License, Version 2.0 ([`LICENSE-APACHE`](./LICENSE-APACHE)) - MIT license ([`LICENSE-MIT`](./LICENSE-MIT)) 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.