Plugin to show RustMind mind maps in nvim.
  • Lua 96.2%
  • Shell 3.8%
Find a file
Jonatas Oliveira 108489d7b8
All checks were successful
ci/woodpecker/push/test Pipeline was successful
chore(release): fold the tooling changes into 0.1.0
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.
2026-09-17 06:17:05 +02:00
.githooks build: hold every commit to the whole list, before it exists 2026-09-16 18:10:26 +02:00
.woodpecker ci: run the unit tests, and leave the rest to the hook 2026-09-16 18:10:26 +02:00
bin docs: stop saying bin/verify.sh is what CI runs 2026-09-17 06:17:05 +02:00
doc docs: a step-by-step install, and a guide for somebody's first contribution 2026-09-16 16:04:36 +02:00
lua/rustmind refactor(nvim): the preview's window goes with its buffer 2026-09-16 16:04:29 +02:00
plugin feat(nvim): the map beside the Markdown it comes from 2026-09-16 08:20:37 +02:00
tests build: hold every commit to the whole list, before it exists 2026-09-16 18:10:26 +02:00
.gitignore build: run format, docs, tests, coverage and mutants in CI 2026-09-16 16:04:36 +02:00
.stylua.toml build: run format, docs, tests, coverage and mutants in CI 2026-09-16 16:04:36 +02:00
CHANGELOG.md chore(release): fold the tooling changes into 0.1.0 2026-09-17 06:17:05 +02:00
cliff.toml chore(release): 0.1.0 2026-09-16 16:05:35 +02:00
CONTRIBUTING.de.md docs: say that the mutants can be scoped to a diff 2026-09-16 18:14:36 +02:00
CONTRIBUTING.md docs: say that the mutants can be scoped to a diff 2026-09-16 18:14:36 +02:00
CONTRIBUTING.pt-BR.md docs: say that the mutants can be scoped to a diff 2026-09-16 18:14:36 +02:00
LICENSE Initial commit 2026-09-16 06:19:44 +00:00
README.md ci: run the unit tests, and leave the rest to the hook 2026-09-16 18:10:26 +02:00

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-cli is 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 to require("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 with layout: in it. That is what tells the plugin this Markdown file is a map and not a shopping list.
  • # Heading for 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 if rustmind is somewhere your $PATH does 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 it nil and 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
RustMindDepth0RustMindDepth5 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.