- Rust 98.4%
- Shell 1.1%
- NSIS 0.4%
The tag was cut before the pre-commit hook, the trimmed-down pipeline, `bin/release.sh` and the check that says when `dist/` has gone stale existed, so it pointed several commits short of what this version actually is. None of them touches a line of Rust — the binaries and the packages built from that tree are unchanged — but a changelog that does not mention where the gate moved to is a changelog that sends the next person to the pipeline. Regenerated, so the 0.1.0 section covers everything the tag is about to point at, and the tag is moved onto this commit. |
||
|---|---|---|
| .agents | ||
| .cargo | ||
| .githooks | ||
| .woodpecker | ||
| apps/rustmind-desktop | ||
| bin | ||
| crates | ||
| docs | ||
| packaging | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| cliff.toml | ||
| clippy.toml | ||
| CONTRIBUTING.de.md | ||
| CONTRIBUTING.md | ||
| CONTRIBUTING.pt-BR.md | ||
| flake.nix | ||
| LICENSE | ||
| NOTICE | ||
| README.md | ||
| rust-toolchain.toml | ||
| rustfmt.toml | ||
RustMind
A Rust mind-map engine that uses Markdown as the single source of truth.
[RustMind]
|
+---------------+---------------+
| | |
[Parser] [Layout] [Render]
Markdown engines SVG
| | |
+---------------+---------------+
|
[Document Model]
RustMind lets you author, version-control, and render mind maps from plain Markdown files. The same file can be edited in your favorite text editor, validated and exported from the command line, or opened in a visual desktop application.
Features
- Markdown-first: mind maps are stored as extended Markdown with YAML frontmatter, headings, nested lists, IDs, tags, relations, and position/style overrides.
- Deterministic SVG rendering: the same document always produces the same SVG output.
- Multiple layouts: freeform (manual positions), tree (top-down, bottom-up, left-right, right-left), and radial (clockwise and counter-clockwise).
- Complete CLI:
new,validate,render,layout,tui,edit,format,export. - Neovim plugin: the map drawn in characters beside the Markdown it comes from, following what you type and editable from either side.
- Visual editor: Iced-based desktop app with canvas, zoom, pan, node selection, drag-to-move, and undo/redo.
- Undo/redo: all document mutations in the desktop app go through a command history.
- Clean architecture: domain crates have no GUI dependencies; screen pixels are only used at rendering and input boundaries.
Installation
You need a Rust toolchain of 1.98 or newer — that is the oldest version this workspace is built and tested against, and the pipeline runs on it to keep the claim honest.
git clone https://forgejo.singularjourney.org/devjonatas/rustmind.git
cd rustmind
# Build everything
cargo build --workspace
# Build only the CLI
cargo build -p rustmind-cli
# Build only the desktop app
cargo build -p rustmind-desktop
The CLI binary is named rustmind and is produced by the rustmind-cli crate.
The desktop binary is named rustmind-desktop and is produced by
apps/rustmind-desktop.
Quick start
# Create a new mind map
cargo run -p rustmind-cli -- new my-map.md
# Edit the file with your favorite editor, then validate it
cargo run -p rustmind-cli -- validate my-map.md
# Apply a radial layout and write positions back to the file
cargo run -p rustmind-cli -- layout my-map.md --type radial
# Render to SVG (stdout)
cargo run -p rustmind-cli -- render my-map.md --format svg > my-map.svg
# Or export directly to a file with the same base name
cargo run -p rustmind-cli -- export my-map.md
# Canonicalize / reformat the Markdown
cargo run -p rustmind-cli -- format my-map.md
# Run the desktop app
cargo run -p rustmind-desktop
CLI reference
| Command | Description |
|---|---|
rustmind new <file> |
Create a new mind map with default frontmatter and a root node. |
rustmind validate <file> |
Parse the file and report errors. |
rustmind render <file> [--format svg|png|pdf|opml|terminal] [--output <file>] |
Render the document. Defaults to SVG on stdout. terminal is an indented outline. |
rustmind layout <file> [--type radial|tree|freeform] [--direction <dir>] |
Apply a layout and write positions back to the file. Preserves manual positions. |
rustmind tui <file> [--columns <n>] [--json] [--embed|--embed-into <file>] [--type <layout>] [--direction <dir>] |
Draw the map in characters. --json adds the runs worth colouring and, for every topic, the stretch of every row its box covers. --embed writes the picture into a Markdown file beside the words. |
rustmind edit <file> --node <id> --op <verb> [--title <text>] [--under <id>] |
Carry out one outline verb and print the id the cursor belongs on. Verbs: add-child, add-sibling, rename, delete, toggle-collapse, promote, demote, move-up, move-down, reparent. |
rustmind format <file> |
Parse and serialize the file back to canonical Markdown. |
rustmind import xmind <file> [--output <file>] [--arrange] |
Convert an XMind workbook (.xmind, new JSON or old XML) to a RustMind map. What could not be converted is reported on stderr. |
rustmind export <file> [--format svg|png|pdf|opml] |
Export next to the source file, e.g. <file>.pdf. |
Direction values depend on the layout:
radial:clockwise,counter-clockwisetree:top-down,bottom-up,left-right,right-leftfreeform: ignored
Markdown format
A RustMind document is a Markdown file with optional YAML frontmatter.
---
layout: mindmap
direction: clockwise
version: 1
---
# Root {#root}
## Child A {#child-a}
### Grandchild {#grandchild}
## Child B {#child-b}
@relation child-a -> child-b {type: dependency, label: "depends on"}
@position root {x: 0.0, y: 0.0, mode: manual}
@style root {fill: "#e3f2fd", shape: rounded_rect}
Frontmatter
| Key | Description |
|---|---|
title |
Document title. |
author |
Document author. |
layout |
Default layout: mindmap, tree, or freeform. |
direction |
Default direction for the layout. |
root |
Optional explicit root node ID. |
version |
Document format version. |
Nodes
Headings and nested lists both create nodes. IDs are written with {#id};
tags with {tag1,tag2}. If no ID is provided, a slug is generated from the
title.
Relations
Relations are declared on their own lines:
@relation source-id -> target-id
@relation source-id -> target-id {type: dependency, label: "label text"}
Supported relation types: relation, dependency, reference, causes,
blocks, implements, extends.
Pictures and icons
A node can carry a Font Awesome icon and a picture:
@icon root star
@image root .rustmind/images/logo.png
Pictures are PNG, JPEG, GIF, WebP or SVG, stored relative to the workspace so
they travel with the map. Import one from Inspector → Picture → Import…;
the file is copied into .rustmind/images/.
Markers and labels
Markers state something about a node — priority, progress, task state or mood — and come from a fixed catalog, so a map means the same thing in every renderer. At most one marker per group sticks: setting a second priority replaces the first.
@marker root priority-high, task-doing
| Group | Ids |
|---|---|
| Priority | priority-high, priority-medium, priority-low |
| Progress | progress-start, progress-half, progress-done |
| Task | task-todo, task-doing, task-done, task-blocked |
| Mood | mood-idea, mood-question, mood-star, mood-heart |
Labels are free text and ride in the heading itself:
## Backend {infra, q3} {#backend}
Both are edited under Inspector → Markers & labels.
Links
A node can carry links: web addresses, and jumps to another topic written
node:<id>. One per line, so a URL may contain commas:
@link backend https://example.com/spec
@link docs node:backend
They are edited under Inspector → Links. Clicking a topic link selects the
target and centers the map on it; clicking a web address hands it to the
desktop's browser — only http, https and mailto links are opened, since
opening a link starts whatever program the desktop registered for its scheme.
A node with a note or a link shows a small badge in its top-right corner.
Filtering
The toolbar filter (Ctrl + F) dims what does not match instead of hiding it,
so the layout stays put while you type. A bare word looks in the title and the
note, #word looks in the labels, and @word looks in the markers — a group
name such as @priority catches every marker in the group. Terms combine:
roadmap #infra @priority
Position, size and style overrides
@position node-id {x: 120.0, y: 80.0, mode: manual}
@size node-id {width: 220.00, height: 90.00}
@style node-id {fill: "#ff0000", stroke: "#000000", shape: diamond}
@size is a floor, not a cap: a topic never shrinks below the text it holds,
so a smaller number just lets the title decide again.
Branch style
@edge node-id {type: bezier, color: "#ff8800", width: 2, dash: dashed, taper: true}
@edge styles the line arriving at a topic, and everything below it wears
the same line — set it on a top-level topic to restyle a whole branch. A
topic deeper down can still say something different for its own subtree.
dash is solid, dashed or dotted; branch_color: true makes the line
wear the colour its branch's topics wear.
bend: 12 -30 pulls the middle of the line that far off the straight run
between the two topics, which is what dragging the ring in the middle of a
selected topic's line writes. It is an offset rather than a place, so the curve
keeps its bulge when either topic is moved; Endireitar a linha in the
inspector takes it back off.
taper: true draws the branch thick where it leaves the parent and thinner at
every level below, until the far leaves hang from a hairline — the way a tree
grows. A tapered branch is a filled shape rather than a stroke, so it carries
no arrowhead and no dashes: the thickness says which way it flows. To taper the
whole map, put taper: true in the frontmatter, or use Format → Taper every
branch.
Drawing on the map
F8, or Caneta in the toolbar, turns the map into paper: dragging draws
instead of moving topics. Pick an ink and a width beside the button, or reach
for the eraser, which rubs out the stroke under the pointer. Each scribble is
one entry on the undo stack.
@stroke pen-1 {color: "#e04a4a", width: 3, points: "10 20 14 26 21 30"}
A stroke belongs to the map rather than to a topic: it stays where it was put when a topic is dragged out from under it, which is what drawing on top of something means. Points are pairs, in document coordinates, kept to two decimals — finer than the screen shows.
Packaging
bin/release.sh cuts a release: it builds for Linux, cross-builds for Windows,
and writes into dist/ a tarball, a .deb, an .rpm, an AppImage, a Windows
setup and zip, and the checksums of all of them. Each package whose tool is
missing on the machine is skipped with a line saying which — the tarball is
always produced, and it is what somebody packaging RustMind themselves starts
from.
bin/release.sh # the version in Cargo.toml
bin/release.sh --no-windows # skip the cross build
bin/release.sh --check # is dist/ still what the tree would produce?
bin/package.sh is the half of it that only packages, for when the binaries
are already built.
--check exists because dist/ goes stale in a way nothing else notices.
README.md, LICENSE and NOTICE are copied into the tarball and the
Windows zip, so editing the README leaves the packages quietly wrong while
every binary in them is still perfect. Packaging writes dist/PROVENANCE — the
hash of everything that went in — and --check compares it against the tree.
It answers on content rather than on the commit, so an unrelated commit does
not make it cry wolf.
Flatpak and Nix live beside the script, because their own tools drive the build:
flatpak-builder build/ packaging/com.rustmind.RustMind.yml
nix build # or: nix run .#rustmind, nix develop
Reading other tools' files
rustmind import xmind workbook.xmind
rustmind import freemind plan.mm
rustmind import opml outline.opml
rustmind import docx report.docx
rustmind import mindmanager plan.mmap
rustmind import mindnode plan.mindnode
rustmind import textbundle map.textbundle
The same formats have buttons in the project browser. An import is never
lossless — every tool has features the others do not — so anything that could
not be carried over is reported rather than dropped in silence. A Word document
is read by its heading levels: Heading 1 becomes a root, Heading 2 its
child, and the paragraphs under a heading become that topic's note.
TextBundle
A TextBundle is a folder holding text.markdown, info.json and an assets/
directory — a map and the pictures it uses, in one thing to move. Arquivo →
Exportar como TextBundle writes one, the browser's Importar TextBundle
reads one back, and the CLI has both:
rustmind export map.md --format textbundle
rustmind export map.md --format docx
rustmind export map.md --format xlsx
rustmind import textbundle map.textbundle --output back.md
Pictures are copied in and the map's @image paths are pointed at the copies,
so the folder stands on its own; on the way back they land in the workspace's
.rustmind/images. A name already taken is numbered rather than overwritten.
Map shot
Recorte, beside the pen, puts a frame in your hand: drag a rectangle over the map and everything outside it dims. Exportar o recorte then writes what is inside as SVG, PNG or PDF.
The frame decides the picture, not the topics: a shot of a corner of the map is a shot of whatever happens to be there. A topic reaching over the edge is kept and clipped by the frame, the way a photograph clips; a line with one end outside is left out, since it would be drawn from nowhere.
Audio notes
The inspector's Recording row records a note for the selected topic: the
same button starts and stops, counting up while the microphone is on. It lands
in the workspace's .rustmind/audio/ as a 16-bit WAV and the topic gets a
speaker badge, since a recording is otherwise invisible on a map.
@audio root .rustmind/audio/root.wav
WAV because the map has to outlive this app: a format every player already reads is worth more here than a smaller file. Taking a recording off a topic leaves the file alone — it is something somebody made.
Equations
The inspector's Equation button opens a box for LaTeX. What it draws is an SVG written into the workspace and set as the topic's picture, so a formula travels through every renderer the way any other picture does — and the source stays on the topic, because an SVG cannot be edited back into LaTeX.
\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
It is not TeX. Variables, numbers, operators, fractions, roots, sub- and
superscripts are what a formula on a topic needs; the rest of LaTeX is left to
LaTeX. The parsing is pulldown-latex, the layout and the SVG are ours — the
alternative was a TeX engine that has to be installed.
Illustrations
The inspector's Illustrations button opens the drawings that ship with the
app — 151 hand-drawn doodles that pair with the sketch style. Choosing one
copies it into the workspace's .rustmind/images/ and points the topic's
@image at it, so the map keeps its picture with or without this app.
They are the Mega Doodles Pack by Maria Letta, under CC BY-SA 4.0. The credit
is beside them in the picker and in NOTICE: a published map with one in it is
handing the drawing on, and the licence asks for the name to travel with it.
The picker windows
The icon and illustration pickers are windows, not rectangles drawn over the
map: a tiling compositor can float, move and tile a window, and can do none of
those to a drawing. They carry the application id rustmind-picker, which is
what a rule is written against:
# Hyprland
windowrulev2 = float, class:^(rustmind-picker)$
Imported textures
Five fill textures are drawn in code — dots, grid, diagonal, cross. A sixth
kind comes from a file: the button beside the texture picker imports a GIMP
.pat, which lands in the workspace's .rustmind/textures/ and joins the
list. It is written the same way in the file:
@style root {texture: bricks}
A name that is not one of the five is an imported pattern. A map naming one this machine has not imported still draws — the topic loses its texture, the way a map naming a missing font still draws — because the map is right and the machine is the one missing a file.
Stickers
Adesivo, beside the pen, opens the icon picker and sticks the chosen icon
on the map — loose from any topic, in the middle of what is on screen. Drag it
to move it, +/− to size it, Del to peel it off.
@sticker sticker-1 {icon: star, x: 120, y: -40, size: 48, color: "#e0a52f"}
The x/y are the middle of it, because a sticker grows and shrinks about
where it was put rather than walking off from its top-left corner.
Boundaries, areas and summaries
Three ways to group topics without changing the tree.
An @boundary draws an outline around a set of topics — either a whole
branch or a hand-picked list:
@boundary discovery {subtree: research, title: "Discovery", fill: "#89b4fa22", stroke: "#89b4fa"}
@boundary delivery {nodes: build prototype tests, title: "Delivery", shape: rounded, dash: dotted}
| Field | Values | Default |
|---|---|---|
subtree |
a topic id: it and everything below it | — |
nodes |
topic ids, space-separated | — |
title |
label drawn at the top-left | none |
kind |
boundary, zone |
boundary |
shape |
rounded, rect, ellipse |
rounded |
fill |
hex, alpha allowed (#89b4fa22) |
hollow |
stroke |
hex | theme edge |
width |
outline width | 1.5 |
dash |
solid, dashed, dotted |
dashed |
corner_radius |
rounding for rounded |
14 |
padding |
gap between the topics and the outline | 16 |
collapsed |
zones only: fold the area away | false |
Either subtree or nodes is required — an outline around nothing is a parse
error rather than an empty box. A boundary that names a topic which is not
there drops it and keeps the rest; one left with no members at all is dropped.
A zone (kind: zone) is the same outline with a fold: collapsed: true
hides its topics and the lines that reached them, and leaves a title bar where
the outline was.
A @summary braces a run of siblings and hangs one topic off the brace:
@summary s1 {parent: plan, from: 0, to: 2, topic: result, stroke: "#f9e2af", width: 2}
from and to are child indices, inclusive; a range written backwards reads
the same, and one running past the last child stops there. The brace brackets
the whole branches, not just the sibling boxes, and sits on the far side from
the parent — so it never crosses the topics it is summarising. The summary
topic is placed at the brace's tip and drops its parent line: the brace is its
connection.
In the app: the Groups section of the inspector, and Format → Outline this branch / Make an area / Summarise from here.
Title alignment
@style node-id {text_align: right, text_valign: bottom}
text_align is left, center (the default) or right; text_valign is
top, middle (the default) or bottom. Both are in the desktop app under
Format in the menu bar and in the inspector's typography section. Vertical
alignment shows on a topic whose box is taller than its title — one given a
@size, or one carrying a picture, markers or labels.
Neovim
The map drawn beside the Markdown it comes from, in a split that follows what you type — insert mode included — and that you can work the outline from.
┌──────────────────┐
┌────────┤ Ownership │
┌──────────────────┐ │ └──────────────────┘
┌────┤ Rust ├────┤
│ └──────────────────┘ │ ┌──────────────────┐
│ └────────┤ Traits │
┌───┴───────────────┐ └──────────────────┘
│ Study Plan │
└───┬───────────────┘ ┌──────────────────┐
│ ┌────────┤ Unit │
│ ┌──────────────────┐ │ └──────────────────┘
└────┤ Testing ├────┤
└──────────────────┘ │ ┌──────────────────┐
└────────┤ Mutation │
└──────────────────┘
The plugin is a repository of its own, nvim-rustmind. With lazy.nvim:
{ "<owner>/nvim-rustmind", ft = "markdown", opts = {} }
Or with Neovim's own package system, no plugin manager:
git clone <plugin repo> \
~/.local/share/nvim/site/pack/rustmind/start/nvim-rustmind
nvim -c 'helptags ALL' -c q
Then run :RustMind in a map. :checkhealth rustmind says whether it found
the binary.
| 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 |
In the picture, the cursor is on a topic whenever it is anywhere inside that
topic's box: o adds one under it, O one beside it, r renames it in a
prompt and i in place, dd deletes it, za folds it, < and > move it
between levels, K and J among its siblings, and <CR> jumps to its heading
in the Markdown. In visual mode the same verbs apply to every topic the
selected rows cover.
Nothing there writes your file: an edit goes into the Markdown buffer, so it
shows as modified and u undoes it.
:RustMindEmbed is the other half — it puts the picture into a Markdown file
between markers it owns, leaving everything around them alone, 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 │
└───────────────────┘
The same thing from the command line, without the editor:
```bash
rustmind tui map.md --columns 100 # draw it
rustmind tui map.md --embed-into notes.md # draw it into a page of notes
The plugin's own README and :help rustmind have the settings, the highlight
groups and the rest.
Desktop app
The first time you launch the desktop app it asks for a workspace folder. That
folder becomes the root for all your RustMind projects. Each project is a
sub-folder and can contain any number of .md map files.
Managing projects and maps
The browser is where projects and maps are made, renamed and removed. Every project row and every map row carries the same two buttons:
| Button | |
|---|---|
| Pencil | Rename it. The field starts with the name it has, and a name already taken gets a number rather than landing on top of what is there. |
| Bin | Delete it. |
Deleting asks first. The row is replaced by the question and by what the answer costs — deleting a project takes every map in it — and only the second press removes anything. There is no undo: a map is a file, and the file is gone.
File → Delete this map… asks the same question from inside a map. It shows you the browser with the question already on that map's own row, so the thing about to go is on screen while you answer. Say no and nothing has happened — the row opens the map again.
Configuration is stored in ~/.config/rustmind/config.toml:
workspace_path = "/home/you/RustMindWorkspace"
first_run = false
Fonts and icon fonts imported from Settings → Fonts & icons are copied into
<workspace>/.rustmind/, so they travel with the maps that use them. See
docs/FONTS.md.
UI themes
Settings → UI theme dresses the app in any of the built-in palettes — Dracula, the four Catppuccins, Gruvbox, Tokyo Night, Nord, One Dark, Solarized, Everforest, Kanagawa, Rosé Pine, Nightfox, Monokai — and gives the open map the matching map theme at the same time, so the chrome and the map agree.
Beside the picker, Import UI theme… reads a palette from a TOML file and Export UI theme… writes the active one back out:
name = "Midnight Harbor"
dark = true
background = "#101820"
surface = "#1b2733"
text = "#e6edf3"
muted = "#8b9aa8"
primary = "#4fa3d1"
success = "#63c187"
warning = "#e0b352"
danger = "#e06c75"
colors = ["#4fa3d1", "#63c187", "#e0b352", "#e06c75", "#a78bfa"]
Only name is required; anything left out takes a dark default. An imported
theme is kept in ~/.config/rustmind/ui-themes/ and reloaded at start-up, and
from then on it sits beside the built-ins in the picker, in the palette gallery,
and as a map theme. See docs/THEMES.md.
Quick entry
Ctrl + E, or the Quick entry button in the browser, opens a box for the
moment before a map exists: type what you are thinking and watch the map build
itself next to the text.
Ctrl + Alt + E does the same from any window — it brings RustMind forward and
opens the box, so a thought that arrives while you are somewhere else has
somewhere to go. Whether the shortcut can be taken at all is the desktop's
call: X11, Windows and macOS hand it over, Wayland does not, and when it
refuses the settings say so rather than leaving a key that does nothing.
Projeto
Backend
API
Frontend
Indenting makes a child of the line above; bullets (-, *, +, •) and
list numbers are optional and stripped; blank lines only separate. When the
text leaves several top-level lines, the name typed for the map holds them —
or, with no name, the first line does. Pick the layout and theme next to the
text, then Create map writes it into the selected project and opens it.
Importing XMind
rustmind import xmind plano.xmind # writes plano.md, laid out
rustmind import xmind plano.xmind -o m.md # somewhere else
Both workbook formats are read: content.json from XMind 8 and later, and
content.xml from XMind 2012 and earlier. Topics, notes, labels, markers,
hyperlinks (links to another topic included) and relationships come through;
the structure class picks the layout.
An import is never lossless, so nothing is dropped in silence — boundaries, summaries, sheets past the first and markers with no equivalent here are each reported on stderr:
warning: the sheet "Rascunho" was not imported; a RustMind map holds one sheet
warning: the marker "people-red" has no equivalent here (1 topic)
Exporting
File → Export writes the map as SVG, PNG, PDF or OPML, chosen by the extension you give the file.
SVG, PNG and PDF are the map as drawn, pictures included — a focused branch exports focused, because that is what is on screen. The PDF goes through the same SVG renderer as the others and keeps text as text and shapes as vectors, so it is not a picture of a PNG.
OPML is the document instead of the drawing: every topic, whatever is focused
or collapsed, since an outline has no geometry. Titles, notes, labels, markers
and links travel; anything RustMind-specific rides in an attribute prefixed
with _, which readers that do not know it ignore.
Starting maps
The browser's Start from: row creates a map already laid out for a job — SWOT, project, meeting, study — in the interface language, because a map that starts in the wrong language is a map someone has to retype. A skeleton is just an outline in the same indented form quick entry reads, so adding one is writing a few lines of text.
The Theme menu lists every theme the map knows — the built-in palettes included — with a check on the one in use, so a whole new colour scheme is two clicks away from the map itself. Settings → Map theme shows the same palettes as cards of swatches, for picking by colour rather than by name. Either way, shapes, spacing and fonts stay where they were.
Hand-drawn style
The toolbar's Sketch button (or View → Hand-drawn style) redraws the
map as if by hand: wobbling outlines, drawn twice the way a pen goes back over
a line it just made. It is a property of the map, not of its theme — the same
colours can be wanted clean or sketched — and it saves as sketch: true in the
frontmatter.
The wobble is generated once in rustmind-scene and read by every renderer, so
the screen, the SVG and the PNG draw the same line, and it is seeded from each
node's id, so a map looks the same on every open and every export.
The text is written by hand too, in Caveat (SIL Open Font License, embedded), a topic that asked for a face of its own keeping it. A hand-drawn box with typeset text in it looks like a drawing somebody gave up on halfway.
Presentation
F5 turns the map into slides: the whole map first, then one slide per branch
of the root, in the order the root's children are in — so rehearsing a talk is
rearranging the map. The camera eases from one slide to the next rather than
cutting, and the slide's topic is selected as it arrives, which is what points
at what is being talked about.
Arrows, Space, PageUp/PageDown, Home and End move; Esc leaves and
puts the camera back where it was. While presenting, the keyboard drives the
deck and nothing else — a stray Tab in front of an audience must not add a
node.
Zen mode
F10 clears everything but the map: no menu bar, no toolbar, no panels, and
fullscreen. Esc (or the ✕ in the corner) leaves and puts the panels back the
way they were found, not the way the defaults describe them.
The one strip that stays carries a night toggle, which darkens the canvas background without touching the map's theme — it dims the room, not the drawing — and a clock: a stopwatch, or a 25- or 5-minute countdown. The clock only ticks while it runs, so an idle app stays idle.
Outline view
Ctrl + Shift + O swaps the canvas for the same topics as an indented list,
and back. The selection is shared, so switching keeps you where you were, and a
collapsed node hides its subtree in both views. With a branch focused, the
outline lists that branch alone.
In the outline, Tab and Shift + Tab indent and outdent (indenting makes the
node a child of the sibling above it), the arrow keys walk the list, and
Alt + ↑ / Alt + ↓ reorder — the same commands the map uses, so undo works
across both views.
Controls
| Action | Control |
|---|---|
| Pan | Left-click and drag on empty canvas. |
| Zoom | Mouse wheel (zooms around cursor) or Ctrl + / Ctrl -. |
| Select node | Left-click on a node, or click an entry in the navigator sidebar. |
| Drag node | Left-click and drag a selected node. |
| Add child / sibling | Toolbar buttons, Tab (child), Enter (sibling), or context menu. |
| Duplicate node | Context menu. |
| Edit node | Toolbar Edit button, F2, or context menu. |
| Delete node | Toolbar Delete button, Delete, or context menu. |
| Collapse / expand | Toolbar Collapse button or context menu. |
| Cycle shape | Toolbar Shape button or context menu. |
| Lock / unlock position | Context menu. |
| Reset position | Context menu. |
| Apply layout | Toolbar Radial / Tree buttons. |
| Add relation | Inspector panel. |
| Filter nodes | Toolbar filter field or Ctrl + F. |
| Focus a branch | Toolbar Focus button or Ctrl + B (press again to leave). |
| Quick entry | Ctrl + E, or the Quick entry button in the browser. |
| Map / outline | Toolbar toggle or Ctrl + Shift + O. |
| Zen mode | Toolbar Zen button or F10; Esc leaves. |
| Presentation | Toolbar Present button or F5; arrows move, Esc leaves. |
| Indent / outdent | Tab / Shift + Tab, in the outline. |
| Undo / redo | Toolbar buttons or Ctrl + Z / Ctrl + Y. |
| Fit scene | Toolbar Fit button or context menu. |
| Reset camera | Toolbar Reset button or context menu. |
| Context menu | Right-click on the canvas or a node. |
| Save map | Ctrl + S. |
Architecture
The workspace is organized into focused crates:
| Crate / App | Responsibility |
|---|---|
rustmind-core |
Document model, geometry, node styles, themes, commands, undo/redo primitives. |
rustmind-parser |
Extended Markdown parser and deterministic serializer. |
rustmind-scene |
Intermediate scene graph and text measurement heuristics. |
rustmind-layout |
Layout engines: FreeformLayout, TreeLayout, RadialLayout. |
rustmind-svg |
Deterministic SVG renderer. |
rustmind-tui |
The map drawn in characters, plus the outline verbs an editor drives it with. |
rustmind-cli |
Command-line interface built with clap. |
rustmind-iced |
Reusable Iced UI state, canvas program, and message loop. |
apps/rustmind-desktop |
Desktop application entry point. |
Domain crates never depend on Iced or any GUI library. The GUI crate depends on domain crates and only converts between screen and document space at the rendering and input boundaries.
Development workflow
# Format, check, test, and lint (run these before committing)
cargo fmt --all -- --check
cargo check --workspace
cargo test --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo doc --workspace
The Woodpecker CI pipeline runs the same steps.
Testing
- Unit tests live next to the code they exercise.
- Snapshot tests use
instafor parser output, layouts, and SVG. - Property-based tests use
proptestfor geometry and serialization. - CLI integration tests live in
crates/rustmind-cli/tests/cli.rsand exercise the binary viaassert_cmd. - Spider tests crawl every combination of a feature rather than one example of it — every layout, every direction, every shape of map — and assert what has to hold for all of them.
- The Neovim plugin is a repository of its own and tests itself there. What
is tested here is this side of the line: the
tuiandeditcommands.
Documentation
Every public API item is documented with rustdoc comments. Build and open the workspace docs with:
cargo doc --workspace --open
Roadmap
This project is under active development. The three things this section used to
list as coming — settings that persist, a terminal renderer, readers for other
tools' files — are all here now: the desktop remembers your workspace and where
each map was last looked at, rustmind tui draws in characters, and rustmind import reads XMind, FreeMind, MindManager, MindNode, OPML, DOCX and
TextBundle.
What is still open is tracked in the repository's backlog notes rather than here, because a roadmap in a README is a roadmap that goes stale — as this one did. CONTRIBUTING.md is the place to start if you want to move one of those items.
Contributing
See CONTRIBUTING.md for guidelines.
License
Dual-licensed under MIT OR Apache-2.0.