9ec1adb3e7
CI / cargo fmt --check (push) Successful in 37s
CI / cargo clippy (push) Successful in 35s
CI / cargo test (push) Successful in 32s
CI / cargo fmt --check (pull_request) Successful in 29s
CI / cargo clippy (pull_request) Successful in 46s
CI / cargo test (pull_request) Successful in 52s
CI / editor keymaps (push) Successful in 1m36s
CI / editor keymaps (pull_request) Successful in 1m21s
Relocate start-nvim.sh, start-vim.sh and test-personal-wiki.sh from the repo root into scripts/, alongside the existing test harnesses. Each script now derives REPO_ROOT one level up, test-personal-wiki.sh execs the moved start-vim.sh, and the README + usage comments point at the new paths. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
377 lines
14 KiB
Markdown
377 lines
14 KiB
Markdown
# 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 wire up keystrokes and display
|
|
results.
|
|
|
|
## Features
|
|
|
|
### Syntax & rendering
|
|
|
|
- Full vimwiki syntax (`.wiki`) — headings, lists with checkboxes,
|
|
numbered / lettered / Roman lists, tables (with `|---|`, `|:--|--:|:--:|`
|
|
alignment markers and `>` / `\/` colspan/rowspan), inline math, code
|
|
fences, tags, comments, and links (wiki, interwiki, diary, file, local,
|
|
raw URL, transclusion).
|
|
- Transclusions support attributes: `{{image.png|alt|style="border:1px"}}`.
|
|
- HTML export with templates, CSS, RSS feed, and incremental
|
|
`:VimwikiAll2HTML`.
|
|
|
|
### Editor intelligence (LSP)
|
|
|
|
- Goto-definition (follow wikilinks), backlinks, hover preview,
|
|
completion on `[[`, document outline, workspace symbol search.
|
|
- Semantic-token highlighting (no syntax-file fallback needed).
|
|
- Broken-link diagnostics and an orphan-page finder.
|
|
- Folding: server-driven `foldingRange` by default, regex fallback when
|
|
the server isn't ready.
|
|
|
|
### Editing
|
|
|
|
- Insert-mode smart `<CR>`: continues list markers (`-`, `*`, `1.`,
|
|
`a)`, `i)`, …) and checkboxes; breaks out of an empty bullet; inserts
|
|
a fresh row when inside a `|…|` table.
|
|
- Insert-mode `<Tab>` / `<S-Tab>` jumps between table cells, adding a
|
|
new row past the last column.
|
|
- `<C-D>` / `<C-T>` indent/dedent the current list item; `<C-L><C-J>`
|
|
/ `<C-L><C-K>` cycle through the marker styles; `<C-L><C-M>` toggles
|
|
or adds a checkbox.
|
|
- Five text-object pairs in operator-pending + visual mode:
|
|
`ah`/`ih` (heading section), `aH`/`iH` (heading + subtree),
|
|
`al`/`il` (list item), `a\`/`i\` (table cell), `ac`/`ic` (table column).
|
|
- Two-step `<CR>` on a bare word: wraps it as `[[word]]`, second press
|
|
follows the link.
|
|
|
|
### Diary
|
|
|
|
- Daily diary out of the box (`<Leader>ww`, `<C-Down>` / `<C-Up>`).
|
|
- Configurable cadence: set `diary_frequency = 'weekly'` (or `'monthly'`
|
|
/ `'yearly'`) per wiki and the commands address `2026-W19.wiki`,
|
|
`2026-05.wiki`, `2026.wiki` instead. Navigation stays at the same
|
|
cadence as the entry under the cursor.
|
|
- Diary index page generation (`:VimwikiDiaryGenerateLinks`).
|
|
|
|
### Other
|
|
|
|
- Multi-wiki with cross-wiki `[[wn.name:Page]]` interwiki links and a
|
|
`:VimwikiUISelect` picker.
|
|
- TOC generation, tag search, tag-to-page link lists.
|
|
- `:Vimwiki*` command compat + canonical `:Nuwiki*` aliases.
|
|
- Plain Vim and Neovim both supported with feature parity (text
|
|
objects, folding, smart insert-mode bindings).
|
|
|
|
---
|
|
|
|
## Installation
|
|
|
|
The plugin ships a `nuwiki-ls` binary that the editor talks to over LSP
|
|
stdio. The `install()` 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
|
|
```
|
|
|
|
Plain Vim users also need an LSP client —
|
|
[`vim-lsp`](https://github.com/prabirshrestha/vim-lsp) is the
|
|
recommended path, with [`coc.nvim`](https://github.com/neoclide/coc.nvim)
|
|
as a fallback. Neovim 0.11+ uses its built-in LSP client and needs
|
|
no extra plugin.
|
|
|
|
### Try it without touching your config
|
|
|
|
```sh
|
|
./scripts/start-nvim.sh # spawns Neovim against an isolated sample wiki
|
|
./scripts/start-vim.sh # same, for plain Vim (clones vim-lsp on first run)
|
|
```
|
|
|
|
Both scripts build the LSP binary, seed a scratch wiki, and launch the
|
|
editor against an isolated state dir under `$XDG_CACHE_HOME/nuwiki-dev`.
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
Defaults are vimwiki-compatible. Every key has a sensible default — the
|
|
example below shows the full surface for reference.
|
|
|
|
```lua
|
|
require('nuwiki').setup({
|
|
-- Single-wiki shorthand (vimwiki-compatible).
|
|
wiki_root = '~/vimwiki',
|
|
file_extension = '.wiki',
|
|
syntax = 'vimwiki', -- 'vimwiki' | 'markdown' (future)
|
|
log_level = 'warn',
|
|
|
|
-- Multi-wiki: each entry honours every per-wiki key vimwiki understands.
|
|
-- Pick either this OR the single-wiki shorthand above. If both are set,
|
|
-- `wikis` wins.
|
|
wikis = {
|
|
{
|
|
name = 'personal',
|
|
root = '~/vimwiki',
|
|
index = 'index', -- index page stem
|
|
file_extension = '.wiki',
|
|
-- Diary
|
|
diary_rel_path = 'diary',
|
|
diary_index = 'diary',
|
|
diary_frequency = 'daily', -- 'daily' | 'weekly' | 'monthly' | 'yearly'
|
|
diary_sort = 'desc', -- 'desc' | 'asc'
|
|
diary_header = 'Diary',
|
|
-- HTML export
|
|
html_path = '~/vimwiki/_html',
|
|
template_path = '~/vimwiki/_templates',
|
|
template_default = 'default',
|
|
template_ext = '.tpl',
|
|
css_name = 'style.css',
|
|
auto_export = false, -- export on save
|
|
auto_toc = false, -- auto-refresh TOC on save
|
|
exclude_files = {}, -- glob patterns
|
|
-- Checkbox progression characters: " .oOX" by default.
|
|
listsyms = ' .oOX',
|
|
listsyms_propagate = true,
|
|
-- Inline highlighting: 6 heading levels by default.
|
|
maxhi = 1,
|
|
-- Embed other syntaxes inside `{{{lang …}}}` fences.
|
|
nested_syntaxes = {},
|
|
},
|
|
},
|
|
|
|
-- 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, <A-Left>, <A-Right>, <Tab> in insert mode
|
|
diary = true, -- <C-Down>, <C-Up>
|
|
html_export = true, -- <Leader>wh, <Leader>whh
|
|
text_objects = true, -- ah, ih, aH, iH, al, il, a\, i\, ac, ic
|
|
mouse = false, -- <2-LeftMouse>, <RightMouse>, … (opt-in)
|
|
},
|
|
|
|
-- 'lsp' (server-driven foldingRange, default) | 'expr' (regex) | 'off'.
|
|
folding = 'lsp',
|
|
|
|
-- Broken-link diagnostic severity: 'off' | 'hint' | 'warn' | 'error'.
|
|
diagnostic = {
|
|
link_severity = 'warn',
|
|
},
|
|
})
|
|
```
|
|
|
|
Vim users without Lua set the equivalent globals before loading 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
|
|
let g:nuwiki_no_folding = 0 " set to 1 to skip foldexpr setup
|
|
let g:nuwiki_mouse_mappings = 0 " set to 1 to enable mouse maps
|
|
```
|
|
|
|
---
|
|
|
|
## Usage
|
|
|
|
### Commands
|
|
|
|
Both `:Vimwiki*` (migration compat) and `:Nuwiki*` (canonical native)
|
|
are registered as buffer-local commands on every `.wiki` buffer.
|
|
Selected highlights — type `:Vimwiki<Tab>` for the full list.
|
|
|
|
| Command | What it does |
|
|
|---|---|
|
|
| `:VimwikiIndex [N]` | Open wiki N's index page (default: 1) |
|
|
| `:VimwikiTabIndex [N]` | Same, in a new tab |
|
|
| `:VimwikiUISelect` | Pick a wiki from a list (multi-wiki only) |
|
|
| `:VimwikiGoto {page}` | Open `{page}.wiki` by name |
|
|
| `:VimwikiMakeDiaryNote` | Open today's diary entry (or this week / month / year, depending on `diary_frequency`) |
|
|
| `:VimwikiMakeYesterdayDiaryNote` / `:VimwikiMakeTomorrowDiaryNote` | Step the diary back / forward by one period |
|
|
| `:VimwikiDiaryIndex` | Open the diary index page |
|
|
| `:VimwikiDiaryGenerateLinks` | Rebuild the diary index from current entries |
|
|
| `:VimwikiDiaryNextDay` / `:VimwikiDiaryPrevDay` | Chronological diary nav (same cadence as the current entry) |
|
|
| `:VimwikiTOC` | Generate / refresh the table of contents on the current page |
|
|
| `:VimwikiGenerateLinks` | Insert a flat list of every page in the wiki |
|
|
| `:VimwikiCheckLinks` | Send broken links to the quickfix list |
|
|
| `:VimwikiSearchTags {tag}` | Quickfix listing every occurrence of `:tag:` |
|
|
| `:VimwikiGenerateTagLinks [tag]` | Insert a section linking every page that has `<tag>` |
|
|
| `:VimwikiRebuildTags` | Force a full workspace re-index |
|
|
| `:VimwikiRenameFile` | Rename the current page, rewriting incoming links |
|
|
| `:VimwikiDeleteFile` | Delete the current page |
|
|
| `:Vimwiki2HTML` | Render the current page to HTML |
|
|
| `:Vimwiki2HTMLBrowse` | Render the current page and open it in the default browser |
|
|
| `:VimwikiAll2HTML[!]` | Export the whole wiki (`!` forces; otherwise incremental) |
|
|
| `:VimwikiRss` | Write `rss.xml` for diary entries |
|
|
| `:VimwikiFollowLink` | Follow the link under the cursor |
|
|
| `:VimwikiBacklinks` | Show all references to the current page |
|
|
| `:VimwikiNextLink` / `:VimwikiPrevLink` | Jump to the next / previous wikilink on the page |
|
|
| `:VimwikiBaddLink` | Add the target of the link under the cursor to the buffer list |
|
|
|
|
### 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** (normal mode)
|
|
|
|
| Key | Action |
|
|
|---|---|
|
|
| `<CR>` | Smart follow — inside `[[…]]` jumps; on a bare word wraps as `[[word]]` (second `<CR>` follows) |
|
|
| `<S-CR>` | Follow in horizontal split |
|
|
| `<C-CR>` | Follow in vertical split |
|
|
| `<C-S-CR>` | Follow in a new tab |
|
|
| `<BS>` | Jump back (`<C-o>`) |
|
|
| `<Tab>` / `<S-Tab>` | Next / previous wikilink on or after the cursor |
|
|
| `+` | Wrap word / visual selection as a wikilink (no follow) |
|
|
|
|
**Lists** (normal mode)
|
|
|
|
| Key | Action |
|
|
|---|---|
|
|
| `<C-Space>` / `<C-@>` / `<Nul>` | Toggle the checkbox under the cursor (the three aliases cover terminal byte differences) |
|
|
| `gln` / `glp` | Cycle the checkbox state forward / backward |
|
|
| `glx` | Toggle the rejected marker `[-]` |
|
|
| `gnt` | Jump to the next unfinished task |
|
|
| `glh` / `gll` | Dedent / indent the current list item |
|
|
| `gLh` / `gLl` | Dedent / indent including the item's sublist |
|
|
| `glr` | Renumber the current ordered list |
|
|
| `gLr` | Renumber every ordered list in the buffer |
|
|
| `gl<Space>` | Remove every completed item from the current list |
|
|
| `gL<Space>` | Remove every completed item in the buffer |
|
|
| `o` / `O` | Open a new line and continue the list marker (preserves any checkbox) |
|
|
|
|
**Lists** (insert mode)
|
|
|
|
| Key | Action |
|
|
|---|---|
|
|
| `<CR>` | Continue the list with the same marker (preserves checkbox), or break out on an empty bullet |
|
|
| `<C-D>` / `<C-T>` | Dedent / indent the current item |
|
|
| `<C-L><C-J>` / `<C-L><C-K>` | Cycle the marker style (`-` → `*` → `#` → `1.` → `1)` → `a)` → `A)` → `i)` → `I)`) |
|
|
| `<C-L><C-M>` | Toggle / add a checkbox on the current item |
|
|
|
|
**Headers**
|
|
|
|
| Key | Action |
|
|
|---|---|
|
|
| `=` / `-` | Increase / decrease the heading level |
|
|
| `]]` / `[[` | Next / previous heading (any level) |
|
|
| `]=` / `[=` | Next / previous sibling heading |
|
|
| `]u` / `[u` | Jump to the parent heading |
|
|
|
|
**Tables**
|
|
|
|
| Key | Action |
|
|
|---|---|
|
|
| `gqq` / `gq1` / `gww` / `gw1` | Align the table under the cursor |
|
|
| `<A-Left>` / `<A-Right>` | Move the column under the cursor left / right |
|
|
| `<Tab>` / `<S-Tab>` (insert) | Jump to the next / previous cell; `<Tab>` past the last cell inserts a fresh row |
|
|
| `<CR>` (insert) | Insert a fresh empty row below the current one |
|
|
|
|
**Wiki / diary / export** (normal mode)
|
|
|
|
| Key | Action |
|
|
|---|---|
|
|
| `<Leader>ww` | Open today's diary |
|
|
| `<Leader>ws` | Pick a wiki |
|
|
| `<Leader>wi` | Open the diary index |
|
|
| `<Leader>w<Leader>w` / `<Leader>w<Leader>y` / `<Leader>w<Leader>t` | Today / yesterday / tomorrow |
|
|
| `<Leader>w<Leader>i` | Rebuild the diary index page |
|
|
| `<Leader>wn` / `<Leader>wd` / `<Leader>wr` | Goto / delete / rename page |
|
|
| `<Leader>wh` / `<Leader>whh` | Export the current page to HTML / open in browser |
|
|
| `<Leader>wha` | Export every page |
|
|
| `<Leader>wc` | Wrap word (or visual selection) in a colour span |
|
|
| `<C-Down>` / `<C-Up>` | Next / previous diary entry |
|
|
|
|
**Text objects** (operator-pending + visual)
|
|
|
|
| Object | Selection |
|
|
|---|---|
|
|
| `ah` / `ih` | The heading's section (`ih` excludes the heading line) |
|
|
| `aH` / `iH` | The heading's section plus every subheading underneath |
|
|
| `al` / `il` | The list item under the cursor (`il` excludes the marker / checkbox) |
|
|
| `a\` / `i\` | The table cell under the cursor (`i\` excludes the surrounding `\|`) |
|
|
| `ac` / `ic` | The table column under the cursor (`ic` excludes the header separator row) |
|
|
|
|
### Health check (Neovim)
|
|
|
|
```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. To migrate:
|
|
|
|
1. Drop the original `vimwiki` plugin from your config.
|
|
2. Install nuwiki.
|
|
3. Point `wiki_root` (or each `wikis[i].root`) at your existing
|
|
directory.
|
|
|
|
Existing pages, diary entries, tags, and templates work without
|
|
modification. The first workspace scan after launch may show an empty
|
|
`:VimwikiCheckLinks` result until the background index completes;
|
|
subsequent runs are instant.
|
|
|
|
---
|
|
|
|
## 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.
|