Pull the view interface, actions and modes out of the canvas example
The canvas example did everything itself; now it runs the way the app will. The key engine turns keys into commands, the focused view answers with actions, and the commands every view shares (undo, marks, adding, deleting, folds, tree moves, modes) answer the rest. A view can still give a key its own meaning, since it gets the first say. `:` lines become actions the same way (app::calls). - View trait: kind, contexts (its key tables), layout, render, command and mouse. layout comes before render because a view keeps the frame it drew, to match clicks against, and follows the cursor with its camera; drawing changes nothing. CanvasView owns the camera, the last scene and the mouse drag; it reads the rest from a ViewContext. - Edits travel as data (EditOp), so views never touch the document. Buffer::apply is the one place a document changes: the edit, its undo step, where the cursor goes, marks and `''`. - Mode holds each mode's own state: move mode carries its Grab (whole branch or not), the command line lives in Mode::Command. - Undo groups: a whole move-mode session or mouse drag is one undo step, built up as it goes. After an undo the cursor lands on a node the change added or removed, before the others it touched. - Actions run in order, and a problem stops the rest. Small improvements on the way: zz centers the middle of the node, :find says which match it's on, :unlink with no links says so, folding a node without children says why nothing happened, and a captured idea is placed next to a new Inbox even on the first :in. The example has end-to-end tests now: they type key sequences and mouse gestures into it on a test terminal, and `cargo test` runs them. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
57ba2c275d
commit
6eedbe0ce2
21 files changed
+2085
-763
No files matched your search
@@ -392,36 +392,53 @@ cumulus/
|
||||
└─ src/
|
||||
├─ main.rs command-line arguments, then app::run
|
||||
├─ lib.rs the module list
|
||||
├─ model/ ids, text, geometry, look, node, portal, link, document, edit, history, fold
|
||||
├─ model/ ids, text, geometry, look, node, portal, link, document, edit, op (edits as
|
||||
│ data), history, fold
|
||||
├─ input/ key (vim notation), command (Command enum + names), bindings (default tables),
|
||||
│ keymap (+ keys.toml), engine (counts, registers, operators), which_key, mouse
|
||||
│ keymap (+ keys.toml), engine (counts, registers, operators), mouse
|
||||
├─ command/ registry, parse, complete, history
|
||||
├─ view/ mod.rs (the View trait), canvas/ (camera, boxes, lines, route, render, hit,
|
||||
│ navigate, placement), outline, note, board
|
||||
├─ ui/ text_editor (+ vim normal), which_key, command_line, status_bar, document_bar,
|
||||
│ card, menu, picker, help, theme, glyphs
|
||||
├─ 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
|
||||
├─ config/ paths, settings, keys_file, theme_file (TOML)
|
||||
├─ storage/ document_file (RON), atomic save, backups, lock, state, watch (change polling)
|
||||
└─ app/ App, mode, action, buffers, panes (split tree), jumps, marks, registers,
|
||||
portals (cache, backlinks, renames), update, render,
|
||||
terminal (setup, teardown, panic hook)
|
||||
└─ 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)
|
||||
```
|
||||
|
||||
**Flow:** a terminal event comes in → `input` turns the key or mouse event into
|
||||
a `Command` → the focused view turns that command into `Action`s →
|
||||
`App::update` applies them (the only place data changes) → the screen is drawn
|
||||
from the state.
|
||||
**Flow:** a terminal event comes in → the key engine turns keys into a typed
|
||||
`Command` (with its count and register) → the focused view answers it with
|
||||
`Action`s, and `app::commands::shared` answers the commands the view leaves
|
||||
(undo, marks, adding, deleting, folds, modes…), so a view can give a key its own
|
||||
meaning → a `:` line is parsed into a `Call` and `app::calls` answers it →
|
||||
`update` carries out the actions in order, stopping at the first problem:
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
```rust
|
||||
trait View { // expected shape; stage 5 settles it from what stages 2–4 needed
|
||||
trait View { // settled in stage 5
|
||||
fn kind(&self) -> ViewKind;
|
||||
fn keys(&self, mode: &Mode) -> &[KeyTable]; // its tables, most specific first
|
||||
fn command(&mut self, command: Command, ctx: &Ctx) -> Vec<Action>;
|
||||
fn mouse(&mut self, gesture: Gesture, ctx: &Ctx) -> Vec<Action>;
|
||||
fn render(&self, frame: &mut Frame, area: Rect, ctx: &Ctx); // reads state, draws, changes nothing
|
||||
fn contexts(&self, mode: &Mode) -> &'static [Context]; // its key tables, most specific first
|
||||
fn layout(&mut self, area: Rect, ctx: &ViewContext); // fit to the area, follow the cursor
|
||||
fn render(&self, frame: &mut Frame, ctx: &ViewContext); // draws the layout, changes nothing
|
||||
fn command(&mut self, typed: &Typed, ctx: &ViewContext) -> Option<Vec<Action>>; // None: not mine
|
||||
fn mouse(&mut self, gesture: Gesture, ctx: &ViewContext) -> Vec<Action>;
|
||||
}
|
||||
// ViewContext: the document, the cursor, the mode, the theme and the view settings.
|
||||
```
|
||||
|
||||
What changed from the first sketch: `layout` comes before `render`, because a
|
||||
view keeps the frame it drew (the canvas matches clicks against it) and moves
|
||||
its camera to follow the cursor, and drawing should change nothing; `command`
|
||||
takes the whole `Typed` (counts matter) and returns `None` for commands it
|
||||
leaves to the shared ones.
|
||||
|
||||
## Build order
|
||||
|
||||
Hardest parts first, so they shape a simple interface that every view shares
|
||||
|
||||
Reference in new issue
Block a user