Edit titles in their boxes, with vim keys inside the text

Titles are typed right in their boxes on the canvas: the box grows with
the text, the terminal's cursor sits in it (a bar while typing, a block
on the letter with vim keys), and the border turns the insert color.
`i`, `A` and Enter edit at the end, `I` at the start, `cc` and `S` type
the title anew. The document follows the text as it's typed, so the
layout stays current, and the whole edit is one undo step.

Esc gives vim's normal mode inside the text (TextEditor, in ui/): h l w
b e 0 ^ $, f t F T with ; and ,, the d c y operators with any of those
motions or the iw aw i" a" i( a( text objects, x X s S D C Y, r ~, p P,
counts, u and Ctrl-r for the edit's own changes, and `.` for the last
one, typing included. j k gg G and J work on lines, for bodies. It
follows vim where it's quirky too: cw acts like ce, dw stops at the end
of the line, Esc steps back onto the last letter. Keys have two new
tables, `text` and `insert`, and in text an operator takes motions as
targets (`dw`, `c$`, `2d3fx`), which which-key lists.

Rapid entry: after `o`, `O` or `a`, the new idea's title is typed at
once; Enter starts the next idea, Tab puts the one being typed under the
idea above, Shift-Tab takes it out a level, and Enter on an empty idea
stops. An idea left empty goes away without a trace in the undo history
(a group whose edits cancel out leaves no step); a title left empty
comes back as it was. Inside a title, `o` and `O` start a new sibling.

Registers are shared by the whole app, so text yanked in one title
pastes into another; `"a`-`"z` and `"_` work, and `"+` stays inside
cumulus until the app core connects the clipboard.

The mouse while editing: a click in the text moves the text cursor, a
click anywhere else keeps the text and finishes the edit, and a
double-click on a title starts editing at that letter.

`>>` and `<<` now move nodes: under the node above, as its last child,
or out a level, right after the old parent, with counts taking in the
next siblings.

The command line's input became TextInput, line-aware so the editor can
use it for bodies too; bodies themselves get edited in the Note view.
The example's tests type every key above into the app and check the
text and the cursor after each one against what vim does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
skkeyeandClaude Opus 5.5 committed 2026-10-01 17:45:40 -04:00
1 parent 6eedbe0ce2
commit 84aefbecdb
31 files changed
+3289 -341

No files matched your search

+71 -17
View File
@@ -91,8 +91,13 @@ lines.
deletes this node and the next two, with their branches, as vim's `3dd`
takes three lines. Which-key says how many as you type (`2d3`: 6 nodes).
- Rapid entry is what happens after `o`, `O` or `a`: Enter starts the next
sibling, Tab / Shift-Tab indent and outdent it, Enter on an empty line stops.
Esc switches to vim motions inside the text; a second Esc leaves.
sibling, Tab / Shift-Tab indent and outdent the idea being typed, and Enter
on an empty idea stops (the empty idea goes away). Esc switches to vim keys
inside the text; a second Esc leaves. Inside a title, `o` and `O` start a
new sibling too.
- `>>` puts the node under the one above it, as its last child; `<<` takes it
out a level, right after its old parent. Both place the node below its new
neighbors, and keep its branch with it.
- `C` (connect mode) edits the connection to another node. Aimed at your own
parent, it styles or cuts the branch line; cutting makes the node loose.
- `M` (move mode) moves the branch; `M` again switches to just the node.
@@ -104,6 +109,36 @@ lines.
bar shows key hints, and `?` opens searchable help.
- Keys can be changed in `keys.toml`, in vim notation.
**Editing text** (settled in stage 6)
- A title is typed right in its box on the canvas (zoomed to Normal while it's
edited). The box grows with the text, the terminal's cursor sits in it, and
the border takes the insert color while you type.
- The cursor's shape follows the mode, as in vim: a bar while typing (and in
the command line), a block on the letter with vim keys, and the terminal's
own shape elsewhere.
- The document follows the text as you type, so every view stays current. The
whole edit is one undo step (in rapid entry, one per idea); inside the text,
`u` and `Ctrl-r` undo and redo the edit's own changes.
- A title left empty comes back as it was, with a note saying so; a new idea
left empty goes away.
- Registers are shared by the whole app, as in vim: text yanked in one title
pastes into another. `"+` stays inside cumulus until stage 8 connects it to
the system clipboard.
- `.` inside the text repeats the last change there, typing included. `.` on
nodes comes with node yank and paste, in stage 8.
- As in vim: `cw` changes to the end of the word, `dw` stops at the end of the
line, `j` and `k` move by lines of text rather than wrapped rows, Esc steps
back onto the last letter typed.
- Tab and Shift-Tab only move a new idea; on an existing title they say that
`>>` and `<<` do it.
- The mouse while editing: a click in the text moves the text cursor; a click
anywhere else keeps the text, finishes the edit and does what a click does; a
double-click on a title starts editing at that letter.
- A portal's title is its document's name, so it isn't edited in place: rename
that document instead.
- The editor handles bodies (several lines) too, but they're edited in the Note
view, in stage 7.
**Files and saving**
- Documents are RON files ending in `.cum`. Configs are TOML: `config.toml`,
`keys.toml` and `themes/<name>.toml`.
@@ -228,18 +263,20 @@ backlinks, and the backlink paths in every document it points to.
breadcrumb (or a message), and the keys typed so far (`2g` above).
**Mouse** (on the canvas; other views get click, double-click and scroll):
click selects, double-click edits the title, dragging a node moves it with its
branch, dragging empty space pans, scrolling pans, Ctrl+drag selects a
rectangle, Ctrl+click adds or removes one node, right-click opens a menu built
from the same actions as the keys. Zoom is on the `+` and `-` keys.
click selects, double-click edits the title (from the letter clicked),
dragging a node moves it with its branch, dragging empty space pans, scrolling
pans, Ctrl+drag selects a rectangle, Ctrl+click adds or removes one node,
right-click opens a menu built from the same actions as the keys. Zoom is on
the `+` and `-` keys.
## Modes and keys
```rust
enum Mode {
Normal,
Insert(TextEdit), // typing a title or body; rapid entry lives here
TextNormal(TextEdit), // vim motions inside that text
Text(TextEdit), // a title or body being edited: typing (INSERT) or vim
// keys inside it (TEXT); the editor knows which, so the
// two can't disagree. Rapid entry lives here too.
Visual(Visual), // anchor + Rectangle or Branches
Move(Grab), // a node or a branch being moved
Connect(Connect), // the source node and the link being styled
@@ -261,8 +298,8 @@ text).
| **Modes** | `v` `V` visual · `M` move · `C` connect · `:` command · Esc back to normal |
| **Leader** `<Space>` | `c` color · `s` style · `B` border · `t` tags · `p` new portal · `i` inbox · `b` documents · `v` then `c`/`o`/`n` for canvas / outline / note · `w` which-key on/off · `?` help |
| **Panes, documents** | `Ctrl-w v` `Ctrl-w s` split · `Ctrl-w hjkl` focus · `Ctrl-w c` close · `Ctrl-w o` only · `Ctrl-w =` even sizes · `gt` `gT` `]b` `[b` next / previous document · `ZZ` close pane |
| **Typing** | `Ctrl-w` delete word · `Ctrl-u` delete line · arrows, Home, End · Enter (title: done or next sibling; body: new line) · Tab Shift-Tab (new node: child / back out) · Esc to vim motions |
| **Inside text** | `h j k l w b e 0 ^ $ f t x r J o O p P u Ctrl-r .` · `d` `c` `y` with motions and `iw aw i" a" i( a(` · `i a I A` back to typing |
| **Typing** | `Backspace` `Delete` · `Ctrl-w` delete word · `Ctrl-u` delete line · arrows, Home, End · Enter (title: done, or after `o O a` the next idea; body: new line) · Tab Shift-Tab (a new idea: under the one above / out a level) · Esc to vim keys |
| **Inside text** | `h j k l w b e 0 ^ $ gg G` (and arrows, Home, End) · `f t F T ; ,` · `d` `c` `y` with any motion and `iw aw i" a" i( a(` · `x X s S D C Y` · `r ~ J p P` · `u Ctrl-r .` · `i a I A` back to typing · `o O` (title: a new sibling; body: a new line) · Esc done |
| **Move mode** | `hjkl` 1 cell · `HJKL` 5 cells · `M` branch ↔ node · Enter or Esc done |
| **Connect mode** | move to the target as usual · Enter link · `d` remove · `>` attach as child · `<Space>` style menu (`s` named, `a` arrows, `l` line, `c` color, `t` label) · Esc cancel |
| **Visual** | `d y > < M C` and `<Space>c` `<Space>s` `<Space>t` act on the whole selection · `o` swaps ends |
@@ -340,7 +377,9 @@ glyphs = "unicode" # "ascii" for fonts without box-drawing characters
completion_popup = "as-you-type" # "on-tab": only after Tab, like vim's wildmenu
# keys.toml: sections motions, normal, canvas, outline, note, visual, move,
# connect, list; commands by name (src/input/command.rs); "nothing" unbinds
# connect, list, text (vim keys inside text), insert (typing: one key each,
# the others type themselves); commands by name (src/input/command.rs);
# "nothing" unbinds
leader = "<Space>"
[normal]
"<Space>x" = "delete-branch"
@@ -399,13 +438,15 @@ cumulus/
├─ command/ registry, parse, complete, history
├─ view/ mod.rs (the View trait), canvas/ (camera, boxes, lines, route, scene, render,
│ navigate, placement, view), outline, note, board
├─ ui/ text_editor (+ vim normal), which_key, command_line, line_input, status_bar,
│ document_bar, card, menu, picker, help, theme, glyphs
├─ ui/ text_input (a line of text and its cursor), text_editor/ (vim inside text:
│ motion, object, layout), which_key, command_line, theme, glyphs; later
│ status_bar, document_bar, card, menu, picker, help
├─ config/ paths, settings, keys_file, theme_file (TOML)
├─ storage/ document_file (RON), atomic save, backups, lock, state, watch (change polling)
└─ app/ action, mode, buffer, commands (keys every view shares), calls (`:` commands);
later App, panes (split tree), jumps, registers, portals (cache, backlinks,
renames), update, render, terminal (setup, teardown, panic hook)
└─ app/ action, mode, buffer, commands (keys every view shares), calls (`:` commands),
text_edit (a title being edited, rapid entry), registers; later App, panes
(split tree), jumps, portals (cache, backlinks, renames), update, render,
terminal (setup, teardown, panic hook)
```
**Flow:** a terminal event comes in → the key engine turns keys into a typed
@@ -418,8 +459,16 @@ document actions go to the document's `Buffer` (the only place a document
changes: the edit, its undo step, the cursor and the marks), the rest change
the mode or the app → the screen is drawn from the state.
While a title is edited, keys go to the edit (`app::text_edit`) instead: when
typing, a key the `insert` table binds is a command (Enter, Tab, Backspace…)
and any other key types itself; with vim keys, the key engine reads the `text`
table, where operators take motions as targets too (`dw`, `c$`, `dfx`). The
edit answers with actions like everything else.
Edits travel as data (`EditOp`), so views never touch the document. A `Group`
makes several edits one undo step: a whole move-mode session, a mouse drag.
makes several edits one undo step: a whole move-mode session, a mouse drag, a
text edit, an idea in rapid entry. A group whose edits cancel out (an idea
added, then left empty) leaves no step at all.
```rust
trait View { // settled in stage 5
@@ -488,5 +537,10 @@ one commit.
- **Model:** tree rules, undo/redo round trips, folding.
- **Canvas:** camera conversions, routing and junctions (small text snapshots),
hit-testing, nearest-node moves.
- **Text editing:** vim keys inside text driven key by key (motions, operators,
text objects, `.`, undo), rapid entry, empty titles, registers.
- **The example:** its tests drive the whole loop on a test terminal, keys and
mouse in and the screen out, so what a stage says works is checked end to
end.
- **Files:** RON round trip, load errors, config parsing, and backlink and
rename fix-ups, in temporary folders.