- Lua 96.2%
- Shell 3.8%
|
All checks were successful
ci/woodpecker/push/test Pipeline was successful
The tag was cut before the pre-commit hook and the trimmed-down pipeline existed, so it pointed three commits short of what this version actually is. None of them changes what the plugin does — they change what holds it to it — but a changelog that does not say the gate moved from CI to a hook is a changelog that sends the next person to the wrong file. Regenerated, so the 0.1.0 section covers everything the tag is about to point at, and the tag is moved onto this commit. |
||
|---|---|---|
| .githooks | ||
| .woodpecker | ||
| bin | ||
| doc | ||
| lua/rustmind | ||
| plugin | ||
| tests | ||
| .gitignore | ||
| .stylua.toml | ||
| CHANGELOG.md | ||
| cliff.toml | ||
| CONTRIBUTING.de.md | ||
| CONTRIBUTING.md | ||
| CONTRIBUTING.pt-BR.md | ||
| LICENSE | ||
| README.md | ||
nvim-rustmind
A mind map drawn beside the Markdown it comes from, and editable from either side.
A RustMind map is a .md file — headings are topics, nesting is
structure, and nothing in it is unreadable to a person or to git diff. Your
editor already opens one. What it cannot do is show it as a map.
┌──────────────────┐
┌────────┤ Ownership │
┌──────────────────┐ │ └──────────────────┘
┌────┤ Rust ├────┤
│ └──────────────────┘ │ ┌──────────────────┐
│ └────────┤ Traits │
┌───┴───────────────┐ └──────────────────┘
│ Study Plan │
└───┬───────────────┘ ┌──────────────────┐
│ ┌────────┤ Unit │
│ ┌──────────────────┐ │ └──────────────────┘
└────┤ Testing ├────┤
└──────────────────┘ │ ┌──────────────────┐
└────────┤ Mutation │
└──────────────────┘
Two repositories are involved, and it is worth being clear about which does what before you install either:
| Repository | What it is |
|---|---|
rustmind |
The program. It reads the Markdown, works out the layout, and draws the picture. |
nvim-rustmind |
This plugin. It opens a window, puts the picture in it, and sends your keystrokes back to the program. |
You need both. The plugin on its own draws nothing.
Installing, step by step
Four steps, in order. Step 2 is the one people skip: the plugin draws nothing
without the program, and ":RustMind did nothing" is not an obvious way to be
told so.
Step 1 — check what you already have
nvim --version | head -1
You need Neovim 0.10 or newer. The plugin is built on vim.system() and
vim.uv, and neither exists in 0.9. If yours is older, upgrade Neovim first;
nothing below will work otherwise.
cargo --version
You need a Rust toolchain to build the program in step 2. If that command is not found, install one from rustup.rs:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Then open a new terminal, or run source "$HOME/.cargo/env", so that
~/.cargo/bin is on your $PATH.
Step 2 — install the rustmind program
git clone https://forgejo.singularjourney.org/devjonatas/rustmind.git
cd rustmind
cargo install --path crates/rustmind-cli
The first build takes a few minutes. It installs one binary, rustmind, into
~/.cargo/bin.
Check it worked:
rustmind --version
If that says command not found, the binary was built but your shell
cannot see it. Add ~/.cargo/bin to your $PATH:
# bash or zsh — put this in ~/.bashrc or ~/.zshrc
export PATH="$HOME/.cargo/bin:$PATH"
# fish — run this once
fish_add_path ~/.cargo/bin
Only want the command-line part?
cargo install --path crates/rustmind-cliis exactly that — it does not build the desktop application, so you do not need the graphics or audio libraries the rest of the project wants.
Step 3 — install the plugin
Pick one of these. If you already use a plugin manager, use that one.
With lazy.nvim
Put this in your plugin list (~/.config/nvim/lua/plugins/rustmind.lua, or
wherever your setup keeps them):
{
"nvim-rustmind",
url = "https://forgejo.singularjourney.org/devjonatas/nvim-rustmind.git",
ft = "markdown",
opts = {},
keys = {
{ "<leader>mm", "<cmd>RustMind<cr>", desc = "Mind map beside this file" },
{ "<leader>me", "<cmd>RustMindEmbed<cr>", desc = "Draw the map into the file" },
},
}
Then restart Neovim and run :Lazy sync.
Three things about that snippet, because each one catches somebody out:
- The
url =line is not optional. lazy.nvim's short"owner/name"form means GitHub and only GitHub. This plugin is not on GitHub, so it needs the full URL — and"nvim-rustmind"on the first line is then just the name lazy.nvim files it under. ft = "markdown"means the plugin is not loaded until you open a Markdown file. That is what you want; drop the line if you would rather have it loaded at startup.opts = {}is handed torequire("rustmind").setup(). An empty table is enough — every setting has a working default.
With vim-plug
Plug 'https://forgejo.singularjourney.org/devjonatas/nvim-rustmind.git'
Restart Neovim, run :PlugInstall, and then add this to your init.lua:
require("rustmind").setup({})
vim-plug does not call setup() for you, which lazy.nvim's opts does.
With no plugin manager at all
Neovim can load plugins from a directory without any help. See :help packages.
git clone https://forgejo.singularjourney.org/devjonatas/nvim-rustmind.git \
~/.local/share/nvim/site/pack/rustmind/start/nvim-rustmind
That is the whole installation — anything under pack/*/start/ is loaded when
Neovim starts. Put it under pack/*/opt/ instead and it waits until you run
:packadd nvim-rustmind.
You do not have to call setup(); the defaults work as they are. Call it only
when you want to change something.
Working on the plugin itself
If you have a clone on disk and want Neovim to use that — which is what you want while changing it — point lazy.nvim at the directory:
{
"nvim-rustmind",
dir = "~/src/nvim-rustmind",
ft = "markdown",
opts = {},
}
Step 4 — check that it worked
Open Neovim and run:
:checkhealth rustmind
You want two green lines:
rustmind ~
- OK found rustmind 0.0.1
- OK Neovim is new enough
If instead you see:
| What it says | What it means | What to do |
|---|---|---|
`rustmind` is not on your PATH |
Step 2 did not finish, or ~/.cargo/bin is not on $PATH. |
Run rustmind --version in a terminal. If that fails too, go back to step 2. If it works there but not in Neovim, Neovim was started before you changed $PATH — restart it. |
the binary is there but would not run |
A rustmind was found, but running it failed. |
The message says what it printed. Most often it is a half-finished build; run cargo install --path crates/rustmind-cli again. |
E5108: ... module 'rustmind' not found |
The plugin is not installed, or your plugin manager has not synced. | Run :Lazy sync / :PlugInstall, then restart Neovim. |
If :checkhealth rustmind says nothing at all, the plugin was not loaded.
Check :Lazy (or :scriptnames) to see whether Neovim knows about it.
Your first map, step by step
1. Make one
A map is a Markdown file with a little frontmatter at the top. The program can write you one:
rustmind new plan.md
That gives you:
---
layout: mindmap
direction: clockwise
version: 1
---
# Root {#root}
You can also just type it. The parts that matter:
- The
---fence at the top withlayout:in it. That is what tells the plugin this Markdown file is a map and not a shopping list. # Headingfor each topic.#is the root,##is a branch of it,###is a branch of that, and so on. Nesting in the Markdown is nesting in the map.{#some-id}after a heading. Optional, but useful: it fixes that topic's id so that renaming the topic does not change it. Without one, the id is worked out from the title.
Fill it in:
---
layout: mindmap
direction: clockwise
version: 1
---
# Study Plan {#root}
## Rust {#rust}
### Ownership {#own}
## Testing {#testing}
2. Open the picture
:RustMind
A split opens beside the file with the map drawn in it. Type in the Markdown and the picture follows along — you do not have to save, and you do not have to ask for a redraw.
3. Work from the picture
Move the cursor into the picture and put it anywhere inside a box — the border, the title, or the empty space beside it all count as being "on" that topic. Then press a key:
| Key | |
|---|---|
o |
add a topic under this one |
O |
add a topic beside this one |
r |
rename it, in a prompt |
i |
rename it in place, typing into the box |
dd |
delete it, and everything under it |
za |
fold it away, or unfold it |
< / > |
move it up or down a level |
K / J |
move it earlier or later among its siblings |
<CR> |
jump to its heading in the Markdown |
R |
redraw now |
q |
close the picture |
In visual mode, select some rows and d, <, >, K, J or za apply
to every topic those rows cover.
Nothing here writes your file. An edit made from the picture goes into the
Markdown buffer as lines — so it shows as modified, u undoes it, and it is
saved when you save. That is on purpose: a plugin that writes your file because
you pressed a key is a plugin you stop trusting.
4. The commands
| Command | |
|---|---|
:RustMind |
open the picture beside this file, or close it |
:RustMindRefresh |
draw it again now |
:RustMindEmbed [file] |
write the picture into a Markdown file, beside the words |
Putting the picture in the file
:RustMindEmbed writes the drawing into a Markdown file between markers it
owns, so a map can illustrate the page of notes it belongs to:
# What I am studying
The plan for the quarter.
<!-- rustmind:begin -->
```text
┌───────────────────┐
│ Study Plan │
└───────────────────┘
```
<!-- rustmind:end -->
Everything outside the markers is yours and is never touched. Running it again replaces the block rather than stacking a second one on.
With no argument it writes into the map's own file. Give it a file name —
:RustMindEmbed ~/notes/quarter.md — and it writes into that one instead,
which is how a map ends up illustrating a page that is not the map.
Settings
Every setting has a default that works. Change one only when you want to.
require("rustmind").setup({
executable = "rustmind", -- a path, or a name on $PATH
layout = "tree", -- freeform | tree | radial
direction = "left-right", -- clockwise | counter-clockwise | top-down |
-- bottom-up | left-right | right-left | balanced
debounce = 140, -- ms of quiet before redrawing
columns = nil, -- nil follows the width of the preview window
split = "vertical", -- vertical | horizontal | tab
live = true, -- follow the buffer in insert mode too
})
With lazy.nvim, the same thing goes in opts:
opts = { split = "horizontal", debounce = 250 },
A few of them are worth a sentence:
executable— set this to a full path ifrustmindis somewhere your$PATHdoes not reach, for instance"~/src/rustmind/target/release/rustmind".debounce— every redraw runs the program, so redrawing on every keystroke would make typing feel like wading. Raise this on a slow machine or a very large map.columns— leave itniland the picture is drawn to fit the split. A map has a width below which its boxes start landing on top of one another, and a topic drawn under another is a topic you cannot reach — so the drawing refuses to go below that width. A narrow window gets a picture that scrolls sideways rather than a picture with topics missing.live— with this off, the picture only catches up when you leave insert mode.
Colours
Every group is default-linked, so a colour scheme that defines them wins.
| Group | Drawn |
|---|---|
RustMindDepth0 … RustMindDepth5 |
boxes and branches, cycled by depth |
RustMindTitle |
titles |
RustMindTag |
labels under a title |
RustMindRelation |
links that are not parent-and-child |
RustMindBoundary |
outlines around sets of topics |
To change one yourself:
vim.api.nvim_set_hl(0, "RustMindTitle", { fg = "#ffd866", bold = true })
When something is wrong
| What you see | Why | What to do |
|---|---|---|
:RustMind does nothing, no error |
The plugin is not loaded. | :checkhealth rustmind. If that is empty too, the plugin was never installed — go back to step 3. |
| The picture opens but is empty | The file is not a map the program can read. | Check the --- fence at the very top, and that it has a layout: line inside it. |
| The picture stops following what you type | The file is mid-edit and does not parse. | This is deliberate: a half-typed heading is not worth blanking the window for. Finish the line and it catches up. |
rustmind: the cursor is not on a topic |
The cursor is in the picture, but in empty space. | Move it inside a box — any part of the box will do. |
| The picture is wider than the window | The map does not fit in the window. | Scroll sideways, widen the split, or use a direction that grows the other way. |
:help rustmind says E149: Sorry, no help for rustmind |
Neovim has not read the help tags. | :helptags ALL, then try again. |
Where the work happens
Almost nothing is decided in this plugin. Layout, drawing, the map of which
topic is where, and what each outline verb does all live in the rustmind
program, reached over two commands — rustmind tui --json and rustmind edit.
That line is drawn on purpose. A verb that quietly drops a subtree is a lost afternoon of somebody's notes, and the way to know it does not is to mutate the code and watch a test fail. So the program gets unit, integration, spider and mutation testing — and so, now, does the Lua here:
nvim -l tests/run.lua # unit, integration, end-to-end and spider tests
bin/coverage.sh # line coverage, with a floor of 90%
bin/mutants.sh # mutation testing
bin/verify.sh --full # all of it, in one command
All of that runs on every commit, from .githooks/pre-commit — install it once
with git config core.hooksPath .githooks. It is the gate, not the pipeline:
these tests drive a real editor against the real binary, and the place that is
true is your machine, not a headless container. CI runs the unit tests, which
need neither.
What is left in this repository is windows, keys and highlights. CONTRIBUTING.md explains how the pieces fit together and how to add to them.
:help rustmind has the same ground as this file, in :help form.
Contributing
See CONTRIBUTING.md — also in português and Deutsch.
License
MIT OR Apache-2.0, the same as RustMind itself.