2026-05-10 16:01:58 +00:00
|
|
|
# nuwiki
|
|
|
|
|
|
2026-05-12 14:20:18 +00:00
|
|
|
**A vimwiki-compatible Vim/Neovim plugin backed by a Rust language server.**
|
2026-05-10 16:01:58 +00:00
|
|
|
|
2026-05-12 14:20:18 +00:00
|
|
|
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.
|
2026-05-10 16:01:58 +00:00
|
|
|
|
2026-05-12 14:20:18 +00:00
|
|
|
- ✅ 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.
|
2026-05-10 16:01:58 +00:00
|
|
|
|
2026-05-12 14:20:18 +00:00
|
|
|
See [`SPEC.md`](./SPEC.md) for the architecture, design decisions, and
|
2026-05-12 14:45:39 +00:00
|
|
|
the full phase log.
|
2026-05-10 17:20:46 +00:00
|
|
|
|
2026-05-12 14:20:18 +00:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 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, -- <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:
|
|
|
|
|
|
|
|
|
|
```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<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`,
|
2026-05-12 14:45:39 +00:00
|
|
|
`il`) are planned follow-ups now that the table/list rewriter
|
|
|
|
|
commands they depend on have shipped.
|
2026-05-12 14:20:18 +00:00
|
|
|
|
|
|
|
|
### 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
|
2026-05-11 14:32:22 +00:00
|
|
|
|
2026-05-10 17:20:46 +00:00
|
|
|
| Phase | Name | Status |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| 0 | Scaffolding | ✅ done |
|
|
|
|
|
| 1 | Core AST | ✅ done |
|
|
|
|
|
| 2 | Syntax Plugin Interface | ✅ done |
|
|
|
|
|
| 3 | Vimwiki Lexer | ✅ done |
|
2026-05-10 17:58:41 +00:00
|
|
|
| 4 | Vimwiki Parser | ✅ done |
|
2026-05-10 19:12:01 +00:00
|
|
|
| 5 | Renderer | ✅ done |
|
2026-05-10 19:44:44 +00:00
|
|
|
| 6 | LSP Foundation | ✅ done |
|
2026-05-10 21:14:01 +00:00
|
|
|
| 7 | Semantic Tokens | ✅ done |
|
2026-05-11 00:27:06 +00:00
|
|
|
| 8 | Navigation | ✅ done |
|
2026-05-11 00:38:09 +00:00
|
|
|
| 9 | Editor Glue | ✅ done |
|
2026-05-11 00:47:40 +00:00
|
|
|
| 10 | CI/CD release pipeline | ✅ done |
|
2026-05-10 17:20:46 +00:00
|
|
|
|
2026-05-12 14:20:18 +00:00
|
|
|
#### v1.1 — full vimwiki replacement
|
2026-05-11 14:32:22 +00:00
|
|
|
|
|
|
|
|
| Phase | Name | Status |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| 11 | Plumbing prerequisites | ✅ done |
|
2026-05-11 14:54:55 +00:00
|
|
|
| 12 | Tags | ✅ done |
|
2026-05-11 16:27:39 +00:00
|
|
|
| 13 | Workspace edits + executeCommand | ✅ done |
|
2026-05-12 14:45:39 +00:00
|
|
|
| 14 | List & table edit commands | ✅ done |
|
2026-05-11 20:49:32 +00:00
|
|
|
| 15 | Link health + TOC/index generation | ✅ done |
|
2026-05-11 21:04:55 +00:00
|
|
|
| 16 | Diary | ✅ done |
|
2026-05-11 21:37:36 +00:00
|
|
|
| 17 | HTML export commands | ✅ done |
|
2026-05-11 21:46:33 +00:00
|
|
|
| 18 | Multi-wiki | ✅ done |
|
2026-05-11 21:57:05 +00:00
|
|
|
| 19 | Editor glue v2 (`:Vimwiki*` compat, keymaps, text objects, folding) | ✅ done |
|
2026-05-11 14:32:22 +00:00
|
|
|
|
2026-05-12 14:20:18 +00:00
|
|
|
---
|
2026-05-10 16:01:58 +00:00
|
|
|
|
|
|
|
|
## 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
|
2026-05-12 14:20:18 +00:00
|
|
|
intentionally submitted for inclusion in this work shall be dual licensed
|
|
|
|
|
as above, without any additional terms or conditions.
|