diff options
| -rw-r--r-- | src/config.zig | 124 | ||||
| -rw-r--r-- | src/tutor.txt | 625 | ||||
| -rw-r--r-- | test/snapshots/builtins.golden | 82 | ||||
| -rw-r--r-- | test/snapshots/leader.golden | 44 | ||||
| -rw-r--r-- | test/snapshots/tutor.golden | 4 |
5 files changed, 490 insertions, 389 deletions
diff --git a/src/config.zig b/src/config.zig index d3da47c1..53def6a1 100644 --- a/src/config.zig +++ b/src/config.zig @@ -661,7 +661,7 @@ pub const Runtime = struct { inactive_dim: u8 = 0, /// The grip's button and the scrollbar under it, one width, as a percent /// of the theme's `rail_px` (acme's 12px Scrollwid at a 17px tagline): - /// a pixel shell's (docs/themes.md). + /// a pixel shell's (docs/typ/themes.typ). grip_width: u16 = 150, /// How the fx track's animations move (animation.Motion): off for /// reduced motion. @@ -2148,3 +2148,125 @@ test "docs/typ names only words, keys and default tags that exist" { }; } } + +/// The tutor's own spellings, which the docs/typ test cannot read: a +/// builtin in backticks (`Save`, `Kill make`), a tag on a line of its own +/// after "| ", a leader path after SPC (`SPC f s`), a chord as Ctrl-, Alt- +/// or Shift- and one key. +const doc_tutor = struct { + /// Read before the bindings in this file: a raw terminal's paste + /// (Pardes.handleKey). + const elsewhere = [_]Chord{ .{ .cp = 'V', .ctrl = true }, .{ .cp = 'V', .ctrl = true, .shift = true } }; + + /// The first word in each pair of backticks that looks like a builtin: + /// a capital, then letters and digits with a lower-case one among them. + fn words(gpa: std.mem.Allocator, text: []const u8, out: *std.ArrayList([]const u8)) !void { + var it = std.mem.splitScalar(u8, text, '`'); + _ = it.next(); // before the first backtick + while (it.next()) |inside| { + _ = it.next() orelse break; // what follows the closing one + const first = inside[0 .. std.mem.indexOfAny(u8, inside, " +") orelse inside.len]; + if (first.len < 2 or !std.ascii.isUpper(first[0])) continue; + var lower = false; + for (first) |c| { + if (!std.ascii.isAlphanumeric(c)) break; + lower = lower or std.ascii.isLower(c); + } else if (lower) try out.append(gpa, first); + } + } + + /// The keys pressed after each SPC, up to the first that is no single + /// key: a path, or the start of one. + fn paths(gpa: std.mem.Allocator, text: []const u8, out: *std.ArrayList([]const u8)) !void { + var toks = std.mem.tokenizeAny(u8, text, " \n"); + while (toks.next()) |t| { + if (!std.mem.eql(u8, t, "SPC")) continue; + var keys: std.ArrayList(u8) = .empty; + while (toks.peek()) |k| { + // `o,` and `j)` end a path written in a sentence + const ends = k.len == 2 and std.mem.indexOfScalar(u8, ",.;:)", k[1]) != null; + if ((k.len != 1 and !ends) or k[0] == '/') break; + try keys.append(gpa, k[0]); + _ = toks.next(); + if (ends) break; + } + if (keys.items.len > 0) try out.append(gpa, keys.items); + } + } + + fn path(keys: []const u8) bool { + for (std.enums.values(Builtin)) |b| if (leader_path.get(b)) |p| + if (std.mem.startsWith(u8, p, keys)) return true; + return false; + } + + /// Every Ctrl-, Alt- or Shift- chord: its modifiers, then one key. + fn chords(gpa: std.mem.Allocator, text: []const u8, out: *std.ArrayList(Chord)) !void { + const named = [_]struct { []const u8, u21 }{ .{ "Esc", Key.escape }, .{ "Enter", Key.enter }, .{ "Tab", Key.tab } }; + var i: usize = 0; + while (i < text.len) : (i += 1) { + if (i > 0 and std.ascii.isAlphanumeric(text[i - 1])) continue; + var c: Chord = .{ .cp = 0 }; + var at = i; + while (true) { + const rest = text[at..]; + if (std.mem.startsWith(u8, rest, "Ctrl-")) c.ctrl = true else if (std.mem.startsWith(u8, rest, "Alt-")) c.alt = true else if (std.mem.startsWith(u8, rest, "Shift-")) c.shift = true else break; + at += std.mem.indexOfScalar(u8, rest, '-').? + 1; + } + if (at == i or at >= text.len) continue; + c.cp = text[at]; + for (named) |n| if (std.mem.startsWith(u8, text[at..], n[0])) { + c.cp = n[1]; + }; + try out.append(gpa, c); + i = at; + } + } +}; + +test "the tutor names only words, keys, paths and default tags that exist" { + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + const gpa = arena.allocator(); + const tutor = @embedFile("tutor.txt"); + + var found: std.ArrayList([]const u8) = .empty; + try doc_tutor.words(gpa, tutor, &found); + try std.testing.expect(found.items.len >= 20); + for (found.items) |w| if (!doc_typ.word(w)) { + std.debug.print("tutor.txt: `{s}` is no builtin\n", .{w}); + return error.DocDrift; + }; + + var tags: usize = 0; + var lines = std.mem.splitScalar(u8, tutor, '\n'); + while (lines.next()) |line| if (std.mem.startsWith(u8, line, "| ")) { + tags += 1; + if (!doc_typ.tag(line[2..])) { + std.debug.print("tutor.txt: \"{s}\" is no default tag\n", .{line}); + return error.DocDrift; + } + }; + try std.testing.expect(tags >= 5); + + found.clearRetainingCapacity(); + try doc_tutor.paths(gpa, tutor, &found); + try std.testing.expect(found.items.len >= 10); + for (found.items) |p| if (!doc_tutor.path(p)) { + std.debug.print("tutor.txt: SPC {s} leads nowhere\n", .{p}); + return error.DocDrift; + }; + + var chords: std.ArrayList(Chord) = .empty; + try doc_tutor.chords(gpa, tutor, &chords); + try std.testing.expect(chords.items.len >= 10); + for (chords.items) |c| { + const elsewhere = for (doc_tutor.elsewhere) |e| { + if (doc_typ.same(e, c)) break true; + } else false; + if (!elsewhere and !doc_typ.bound(c)) { + std.debug.print("tutor.txt: a chord on {u} (ctrl {} alt {} shift {}) is bound nowhere\n", .{ c.cp, c.ctrl, c.alt, c.shift }); + return error.DocDrift; + } + } +} diff --git a/src/tutor.txt b/src/tutor.txt index f8aa7aef..37938574 100644 --- a/src/tutor.txt +++ b/src/tutor.txt @@ -4,264 +4,292 @@ The model is acme's: EVERYTHING is text, editable and executable the same way. Modal editing (helix-style) lives on top of that. - Press j until you reach the introduction. + Press j until you reach part 1. + + The lessons follow the guide (docs/typ/guide.typ, the "Guide" + chapter of the pardes book), and each ends by naming its section. + + Words written in backticks, like `Save`, are builtins: type one in + any tag and middle-click it. Lines starting with "| " are tags as + pardes draws them, words only. + + PRACTICE BLOCKS (part 9): the "# keys:" line lists keystrokes + (space-separated; esc/enter/bs are special, the rest type each char). + The lines under "# before" are what you practise on; "# after" is + what you should end up with. Nothing runs them. What pins the real + behaviour is `zig build hxdiff`, which replays test/hxcases against + goldens recorded from a real helix, and `zig build hxparity`, which + runs each case in a file pane and in a shell and demands they agree. ================================================================= -= INTRODUCTION = += PART 1 — MODES = ================================================================= - A pane is just text on screen. There is no separate "buffer" type — - what you see is what you edit. Some panes happen to be live terminals; - most are text you move a cursor over. Modal editing is layered on top. + A pane is text on screen, a tag over a body. Some panes are live + terminals; most are text you move a cursor over. Each pane has its + own mode and keeps it while you are elsewhere. The BOX at the left + of a pane's tag shows it: - Three modes (the BOX at the pane's top-left corner shows which): - NORMAL block cursor, keys move and edit. The box is BLANK. + NORMAL keys move and select. The box is BLANK. ^ INSERT keys type text at the cursor. - $ TTY keys go straight to the shell. (terminals only) + $ RAW keys go straight to the program (terminals only). - A bare `pardes` opens one shell already in TTY. Give it a file or a - directory and you start in NORMAL. + Where each pane starts: a file or a PDF in NORMAL; a terminal from + `Tty` in RAW, one from Alt-n in NORMAL; a command pane in RAW while + its command runs. A bare `pardes` starts a shell in RAW with an + empty text pane under it; `pardes FILE` starts on the file. - SIX PARTS, ordered by what is most different from editors you know: - 1 — THE MOUSE. Acme's three buttons; nothing like vim. - 2 — PANES. Moving between them. Esc, Shift-Esc, Ctrl-w. - 3 — THE TTY. Raw input, prompt-aware Esc, and the mode tag. - 4 — DETACHED. The core outliving the terminal showing it. - 5 — THE KEYS. Helix-style modal, and where it differs. - 6 — THE REST. PDFs, images, the language backend, scripting. + Esc and Shift-Esc, by mode: - PRACTICE BLOCKS (part 5): the "# keys:" line lists keystrokes - (space-separated; esc/enter/bs are special, the rest type each char). - The lines under "# before" are what you practice on; "# after" is what - you should end up with. They are here to be TYPED and nothing runs - them. What pins the real behaviour is `zig build hxdiff`, which replays - test/hxcases against goldens recorded from a real helix, and - `zig build hxparity`, which runs each case twice — once in a file pane, - once in a shell — and demands the two agree. + NORMAL Esc back to the previous pane (`Last`, SPC j j) + Shift-Esc the same; in a terminal it switches to RAW + INSERT Esc back to NORMAL + Shift-Esc the same; in a terminal it switches to RAW + RAW Esc goes to the program, except at an idle, + EMPTY shell prompt: back to the previous pane + Shift-Esc always back to the previous pane + a PDF Esc clears the selection and search highlights + Shift-Esc back to the previous pane + + Leaving a RAW terminal leaves it RAW. Ctrl-b switches a terminal + between RAW and NORMAL; `Mode` in its tag steps RAW, NORMAL, + INSERT. "The previous pane" is the last other pane on the jump + list, else the next pane down its column. - Hold j to reach part 1. + Shift-Esc needs a terminal that reports modifiers on Escape (the + kitty keyboard protocol); elsewhere it arrives as a plain Escape. + + -> Guide: Modes ================================================================= -= PART 1 — THE MOUSE (acme chording) = += PART 2 — THE MOUSE = ================================================================= Three buttons, three verbs: - LEFT select, and focus the pane. Also pins the cursor. - MIDDLE EXECUTE the word or selection (a builtin, or a shell line). - RIGHT LOOK: open the file, directory or URL under the pointer. + B1 LEFT select; a plain click puts the cursor there and + gives the pane the keyboard. In a tag: type there. + B2 MIDDLE EXECUTE the word or selection: a builtin, or a shell + line. + B3 RIGHT LOOK: open the file, address, directory or URL under + the pointer, or else find the word's next place. + + A B2 or B3 click with no drag takes the word around it: letters, + digits and . - + / : @ _ ~, a trailing : dropped. So a right click + anywhere on src/bar.c:12:5: in a compiler error opens that file at + line 12, column 5. Drag to say exactly what you mean. - CHORDS — hold one button and click another without letting go: + CHORDS: hold one button and click another without letting go: 1-2 (hold left, click middle) CUT the left selection 1-3 (hold left, click right) PASTE over it - both, in one hold SNARF (copy) + 1-2 then 1-3, in one hold COPY 2-1 (hold middle, click left) execute the middle word WITH the left selection as its argument - Every tag with text leads with Save; a terminal also has Togglettymode - and Filter. Tty opens a terminal; New belongs to each column tag. - Images show "Tty Del Collapse"; PDFs also have PdfSections and PdfTint. - Every pane has Collapse: hide its body, then execute again to expand it. - The top bar is: - - Newcol Joincol Find Grep Help Changelog Tutor Dump Themes - Config Debug Exit - - Reach it from the keyboard with `k` off the topmost tagline; `j` comes - back down. - - KEYBOARD EQUIVALENTS, because the buttons are not the only way in: + KEYBOARD EQUIVALENTS, in NORMAL mode: Enter = LOOK (right button) Tab = EXECUTE (middle button) - Both act on the selection when there is one. Neither is a way into TTY. + Both act on the selection when there is one, else the word under + the cursor. + + -> Guide: The mouse ================================================================= -= PART 2 — PANES: MOVING BETWEEN THEM = += PART 3 — TAGS = ================================================================= - Panes live in columns. Everything here works in any mode, because - getting OUT of a pane must work from inside a pane that owns its keys. + Three kinds of tag, every word live. The workspace tag on top: + +| Newcol Joincol Find Grep Help Changelog Tutor Dump Themes Config Debug Exit + + Each column's tag: + +| New Tty Find Grep Joincol Delcol - FOCUS A NEIGHBOUR - Ctrl-w h/j/k/l focus the pane left / down / up / right - SPC w h/j/k/l the same, in body normal mode only - Use Ctrl-w in a terminal: there SPC belongs to the shell. + Each pane's tag, after its path: a file's, a terminal's, a PDF's, + an image's: + +| Save Tty Collapse Del +| Tty Save Mode Filter Collapse Del +| Tty PdfSections PdfTint Collapse Del +| Tty Collapse Del + + Commands from the workspace or a column tag run in the session's + directory; commands from a pane's tag run in the pane's directory. + + A tag is text, with undo: type a word into it and middle-click it, + or delete the defaults. `:` in NORMAL mode moves the keyboard to the + pane's tag and back. Clicking the path at the start of a pane's tag + drafts a new name: Enter confirms, `Save` writes there. `Collapse` + folds a pane to its tag; again unfolds it. The grip, the box left of + the tag, marks unsaved text; drag it up or down to resize the pane, + or onto another column to move it there. + + -> Guide: Tags + + +================================================================= += PART 4 — PANES AND COLUMNS = +================================================================= + + FOCUS A NEIGHBOUR (not in INSERT or RAW mode) + Ctrl-w h/j/k/l the pane left / down / up / right; up from a + column's top pane reaches its tag + SPC w h/j/k/l the same GO BACK - Esc hop to the pane you were in BEFORE this one. Held down - it alternates between two. This is the `Last` builtin, - also SPC j j. - Ctrl-o walk the jump history BACK (the `Back` builtin) - Ctrl-i walk it FORWARD (`Forward`; SPC j l shows - the stack as a buffer) - Ctrl-i and Tab are the same byte on an old terminal. Where they are, - Tab keeps meaning execute and Ctrl-i does nothing — the right way - round, since Tab-executes is the older and more used of the two. + Esc the previous pane (`Last`, also SPC j j) + Ctrl-o walk the jump history BACK (`Back`) + Ctrl-i walk it FORWARD (`Forward`) + SPC j l the jump list as a pane (`Jumplist`) + Ctrl-i and Tab are the same byte on an old terminal; there Tab + keeps meaning execute and Ctrl-i does nothing. + + MAKE, MOVE AND CLOSE + Alt-n a new terminal (in NORMAL mode) + Alt-c move the pane into a new column (not when it is + alone in its column, nor past 16 columns) + `New` a scratch pane, +New, in this pane's directory + `Newcol` an empty column right of this one + `Tty` a terminal in this pane's directory + `Del` close the pane; from the keyboard (SPC d), between + two panes, it asks which one gets the rows: k or j + `Delcol` close the column and its panes + `Joincol` fold this column into the one on its right + + THE ACTIVE COLUMN is where new panes go: the column you last typed + or left-clicked in, or the one that got the last new pane. A look + moves the keyboard but not the active column, so after a look jumps + to a file open in another column, the next new pane still lands in + the old one. Type or click to move it. - WHEN Esc MEANS SOMETHING ELSE - Three panes have their own claim on Escape, so there is a second - spelling that always leaves: + Closing a column's last pane leaves the column empty; closing the + session's last pane quits pardes. - Shift-Esc leave this pane, whatever Escape means inside it + -> Guide: The active column and the keyboard + + +================================================================= += PART 5 — LOOKING = +================================================================= - in INSERT Esc returns to normal. Press it again to hop. - in a PDF Esc cancels the selection and the search highlights - and stays in the document. Shift-Esc hops out. - in raw TTY Esc goes to the program — vim gets its Escape. - Shift-Esc hops out. But see part 3: at a shell - PROMPT, plain Esc hops too. + Type an address anywhere, then right-click it or press Enter: - Shift-Esc needs a terminal that reports modifiers on Escape (the kitty - keyboard protocol). Where the host does not, it arrives as a plain - Escape and means whatever Escape means in that pane. + notes.txt:12 line 12 + notes.txt:12:5 line 12, byte column 5 + notes.txt:/re/ the next match; notes.txt:0/re/ the first + :40 this pane, line 40 + @p3:12 pane 3 (by serial), line 12 + src/ a directory: ls in an idle terminal there - MAKE AND MOVE PANES - Alt-n new terminal below the active one - Alt-c move the active pane into a fresh column — any pane, not - just a terminal. A no-op if it is alone in its column, - or if the sixth column already exists. - New in a column tag: a new scratch pane - Newcol in the top bar: a pane in a new column - Tty new terminal in the calling pane's directory - Togglettymode in a terminal tag: switch editor/raw mode (Ctrl-B) - Joincol delete this column, move its panes to the one right - Del in a pane's own tag: close it. From the keyboard, between - two panes, it asks which one gets the space: k or j - DelAbove close it, giving the space to the pane above (Del k) - DelBelow close it, giving the space to the pane below (Del j) + A relative path is looked for in this pane's directory first, then + in the directory of each pane on the jump list, most recent first. - RESIZE, AS ACME MOVES A WINDOW - the grip (the box left of a pane's tag) drag it: drop it in another - column to move the pane there, or up or down its own - column to move its top, the pane above taking or giving - the rows -- down to its tag alone - the rule in the GUI, the line between two panes drags their seam - a column's right edge drags the column's width - A pane's last row is text like any other, never a handle. + n and N step through everything a look would open, across panes, + as a ring; Enter opens what you land on. - A new file opens in a new COLUMN only if both panes would still get - 100 columns of width. Otherwise it stacks in the one you are in. + -> Guide: Looking ================================================================= -= PART 3 — THE TTY (a terminal is just a pane) = += PART 6 — COMMANDS AND TERMINALS = ================================================================= - There is no "open a terminal in a split" separate from "edit a file in - a split". A TERMINAL IS A PANE. The same modal keys that edit a file - navigate the terminal's screen. + Middle-click a line that is no builtin (make, git log) and it runs. + In a terminal idle at an empty prompt, a line from its own text is + typed into its shell; anywhere else it runs in a COMMAND PANE, a + terminal of its own whose tag says running, then exit N: - On a terminal pane: - NORMAL ( ) the PROMPTS are gone — a clean acme page — but the - command you typed at each one stays, pulled to the left - edge, because that is the part worth reading. Navigate - with the same h/j/k/l/w/b/e as a file. - INSERT (^) the same page; keys type an insertion overlay. - TTY ($) the REAL shell. Prompts back, keys straight to the pty. +| Kill Save Collapse Del - ENTERING TTY - Ctrl-b toggle raw TTY/editor mode on a terminal pane - Shift-Esc also enters raw TTY from editor mode - Togglettymode in the pane tag switches modes in either direction + The next command for the same directory reuses a finished command + pane; one still running is never reused. `Kill` stops what pardes + started (`Kill make`: those starting with make). `Exit` quits pardes. - `pardes --tty-toggle=g` chooses Ctrl-g for toggling instead. - Entering TTY moves the shell's real cursor to the place you selected - on its prompt input line, using its OSC 133 prompt marks. + A TERMINAL IS A PANE. In NORMAL mode its prompts are hidden and the + same keys that edit a file move over its text; in RAW mode the real + shell has the keys. pardes --tty-toggle=g picks another letter + than b for the toggle. - RAW INPUT - Ctrl-b switches to editor mode. Other keys go to the child, - including Ctrl-o, Ctrl-w, Alt shortcuts, and - modified Escape. The two paste chords are the exception: Ctrl-V - types the yank register at the program and Ctrl-Shift-V types the - desktop clipboard. Plain Esc at a detected shell prompt hops - to the previous pane. While a program owns the terminal, Esc goes - to that program too. Use the Togglettymode tag to leave raw input - in place. Desktop paste events still feed the child. + `Tty+bash` another terminal, on that shell + `Save` asks for a path, then writes the scrollback there + `Filter` maps the program's colours through the theme + Ctrl-V in RAW: types what you yanked (with nothing yanked, + the program gets the key); Ctrl-Shift-V types the + desktop clipboard - On Linux, Tty9p (SPC n 9 from editor mode) opens a terminal with - this session mounted through kernel v9fs. It asks sudo in that pane, - then starts your normal shell. $PARDES_MOUNT names its mounted tree. + A program that tracks the mouse (htop, vim with mouse=a) gets B1 + and the wheel; B2 and B3 stay pardes's. Hold Shift to swap them. - `Filter` in a terminal's tag toggles a pane-local, theme-keyed palette. + -> Guide: Command panes, Terminals ================================================================= -= PART 4 — DETACHED SESSIONS (the core outlives the terminal) = += PART 7 — UNSAVED PANES, DIFFS, AND pardes FILE = ================================================================= - A pardes session is a core — the text, the undo history, the layout, - the pane shells — and a frontend that draws it. Normally they are one - process. They do not have to be. - - STARTING ONE - pardes --detach a session named by this process's pid - pardes --detach=work ...named `work` - pardes --attach become a frontend of the one session there is - pardes --attach=work ...of the session called `work` - pardes-gui --attach=work an SDL window is a frontend too + `Del` on a pane with unsaved text refuses once and lists it in + +Unsaved; the same `Del` again, with nothing edited since, discards + it. `Exit`, `Restore` and `Delcol` refuse once the same way. - And from inside a running editor, as ordinary acme words — type one in - a tag and execute it, or press its chord: + Run git diff as a command, or open a .patch: it shows as a coloured + diff. Right-click a diff --git, --- or +++ line to open the file, a + @@ line for the hunk's first new line, a hunk line's first column + for that line in the new file. - Attach hand this window to the session there is (SPC s a) - Attach work ...to `work` - Detach leave the session, and leave it running (SPC s D) + In a pane's shell, pardes FILE opens FILE in this session, and + EDITOR='pardes --wait' makes git commit open its message in a pane + and carry on when you close it. - WHAT MAKES IT DIFFERENT FROM tmux - The pane shells belong to the SESSION, not to the frontend. Attach, - detach, kill the terminal, attach from another one, and the build that - was running in pane 3 is still running and has been scrolling into the - core the whole time. Nothing is replayed to you; it never stopped. + -> Guide: Unsaved panes, Reviewing diffs, pardes FILE and --wait - `Attach` switches IN PLACE. The window, the terminal and the process - stay; what changes is where the state lives. Only on a successful - handshake does the frontend give up its own core — so a failed attach - costs you nothing. - `Detach` is the smaller half: only the frontend that ran it leaves. - The session, its shells and every other attached frontend are - untouched, so leaving is a SUCCESS — the terminal prints where to come - back to and exits 0. In a session with nothing to detach from it says - so on the message row and does nothing. +================================================================= += PART 8 — SESSIONS AND CONFIG = +================================================================= - MORE THAN ONE FRONTEND - N frontends attached to one session all see the SAME screen, at the - smallest common grid — `screen -x`, not N sessions. Two windows of - different sizes converge on the smaller and the larger letterboxes. + A session is a core (text, undo, layout, the pane shells) and a + frontend that draws it. They do not have to be one process: - WHAT A DAEMON CAN ALSO DO - A detached session serves acme's control filesystem like any other: + pardes --detach=work a session with no screen of its own + pardes --attach=work show it here + pardes-gui --attach=work an SDL window is a frontend too - pardes --detach=work 9P on the session's default unix socket + From inside: `Attach` work (SPC s a) switches this window to it, and + `Detach` (SPC s D) leaves it running, shells and all. Frontends + attached together share ONE screen at the smallest common size. + The session ends when its last pane closes. - Every native session is scriptable over 9P — see part 6. + `Config` (SPC f c) opens the startup file, ~/.config/pardes/init: a + builtin a line, run at start (Theme atelier, Shell zsh). `Dump` and + `Restore` save and reload the workspace. - The socket lives in $XDG_RUNTIME_DIR (else ~/.local/state/pardes), - created 0700, never /tmp: it carries keystrokes into a live editor. + -> Guide: Sessions, Config ================================================================= -= PART 5 — THE KEYS (helix-style modal) = += PART 9 — THE KEYS (helix-style modal) = ================================================================= - Same core as helix: a block cursor you move with h/j/k/l, motions that - SELECT what they cross, and an edit that acts on the selection. There - is no verb+noun — the motion already selected, so `wd` is what `dw` - was in vim. + Same core as helix: a block cursor you move with h/j/k/l, motions + that SELECT what they cross, and an edit that acts on the + selection. There is no verb+noun: the motion already selected, so + wd is what dw was in vim. ----------------------------------------------------------------- -= 5.1 MOTION AND COUNTS = += 9.1 MOTION AND COUNTS = ----------------------------------------------------------------- k h left, l right, j down, k up (also arrows) h l w/b/e word forward/back/end (W/B/E long) - j 0 line start, $ end, ^ first non-blank + j 0 line start, gl line end, ^ first non-blank gg first line, ge START of the last line f/F/t/T find a character forward/back - Bare G does NOTHING. `ge` is the start of the last line. + Bare G does NOTHING; a count first (5G) goes to that line. $ is not + line end: it keeps the selections a shell command succeeds on. # keys: l l l # before @@ -269,12 +297,12 @@ abcdef # after abcdef - A number typed FIRST is a count, and only some keys take one: the - motions, f/F/t/T and the `Alt-.` that repeats them, gg/gj/gk/g|, `x`, - `o`/`O`, `>`/`<`, `p`/`P`, Ctrl-a/Ctrl-x, ]p/[p and ]space/[space, and - the cursor-list keys C, Alt-C and )/(. Everywhere else it is swallowed: - `3d` deletes once, `3i` types once. `0` is always line-start and never - the start of a count. Ctrl-d/u/f/b ignore counts: a page is a page. + A number typed FIRST is a count, and only some keys take one, + among them the motions, f/F/t/T and the Alt-. that repeats them, + gg/gj/gk, x, o/O, > and <, p/P, R, Ctrl-a/Ctrl-x, the cursor-list + keys C and ( ), q and the . repeat. Elsewhere it is swallowed: 3d + deletes once, 3i types once. 0 is always line start and never starts a + count. Ctrl-d/u/f ignore counts: a page is a page. # keys: 3 l i Z esc # before @@ -283,11 +311,11 @@ abcdef abcZdef ----------------------------------------------------------------- -= 5.2 ENTERING INSERT = += 9.2 ENTERING INSERT = ----------------------------------------------------------------- - `i` at the cursor. `a` after it. `I` first non-blank. `A` end of - line. `o` open below. `O` open above. Esc returns to normal. + i at the cursor. a after it. I first non-blank. A end of line. + o open below. O open above. Esc returns to NORMAL. # keys: i X esc # before @@ -303,33 +331,36 @@ line1 line2 ----------------------------------------------------------------- -= 5.3 MOTIONS SELECT = += 9.3 MOTIONS SELECT = ----------------------------------------------------------------- - w/b/e and f/F/t/T leave a SELECTION behind them. So `i` after a motion - types at the selection's START, not where the cursor stopped. `;` - collapses a selection to a single cursor first. + w/b/e and f/F/t/T leave a SELECTION behind them, so i after a + motion types at the selection's START. ; collapses the selection + to the cursor first; w left the cursor on the space. + +# keys: w i Z esc +# before +one two +# after +Zone two # keys: w ; i Z esc # before one two # after -one Ztwo +oneZ two ----------------------------------------------------------------- -= 5.4 SELECT AND EDIT = += 9.4 SELECT AND EDIT = ----------------------------------------------------------------- - x select the LINE (again: extend by one more) - v character-wise select, then move + x select the LINE (again: one more) + v extend by characters, then move d delete the selection c change it y yank it p / P paste after / before u undo U redo > < indent / outdent Ctrl-a / Ctrl-x increment / decrement - Selection is LINE-first here — `x` — with `v` for characters. That is - the main divergence from helix. - # keys: x c typed esc # before replace me @@ -337,165 +368,113 @@ replace me typed ----------------------------------------------------------------- -= 5.5 THE REST, IN ONE PLACE = += 9.5 THE REST, IN ONE PLACE = ----------------------------------------------------------------- - s / S regex, one cursor per match, previewing as you type. - C , ( ) Alt-s Alt-- Alt-_ _ work the resulting LIST. - m i / m a textobjects: w W p, the bracket pairs, the three quotes - m s/r/d surround add / replace / delete; mm jump to the match - ] [ step by paragraph, blank line, diagnostic - | filter every selection through /bin/sh -c (savable file - panes only) - : the tag as a command line - / case-insensitive SUBSTRING search, one hit per line — not - a regex. The regex lives on s and S. - n / N select the next/previous LOOK-able text, across panes, as - a ring. Enter opens what you land on. + s / S regex, one cursor per match, previewing as you type; + C , ( ) Alt-s Alt-- Alt-_ _ work the resulting LIST + m i / m a textobjects: w W p, the bracket pairs, the quotes + m s/r/d surround add / replace / delete; m m the match + ] p, [ p step by paragraph; ] d, [ d by diagnostic + ] space a blank line below; [ space above + | filter every selection through the shell + / case-insensitive SUBSTRING search, one hit a line; the + regex lives on s and S + n / N the next/previous look-able text, as above + Ctrl-c comment or uncomment the lines VIEWPORT - zt zz zb scroll so the cursor is at top / center / bottom - zj zk scroll one line, cursor pushed along - Ctrl-d/u half page down / up - Ctrl-f full page down. Ctrl-b pages up on a FILE pane; on a - terminal it is the tty toggle (part 3). + z t, z z, z b scroll the cursor to the top / centre / bottom + z j, z k scroll a line, the cursor pushed along + Ctrl-d/u half a page down / up + Ctrl-f a page down. Ctrl-b pages up in a file; in a + terminal it is the RAW toggle. + + LANGUAGE SERVERS run as child processes, one per language on PATH + (zls, rust-analyzer, clangd, gopls, typescript-language-server, + pyright): g d definition, g r references, g D g y g i, SPC l k + hover, SPC l r rename, SPC l i what is running, ] d diagnostics, + = format. ----------------------------------------------------------------- -= 5.6 SPC — THE LEADER = += 9.6 SPC — THE LEADER = ----------------------------------------------------------------- - Nearly every builtin has a NAME you can execute anywhere text lives and - a KEY PATH you can press. SPC in body normal mode starts the path; what - you have typed shows on the pane's message row until it fires. Esc + Nearly every builtin has a NAME you can execute wherever text lives + and a KEY PATH you can press. SPC in NORMAL mode starts the path; + what you have typed shows on the message row until it fires. Esc abandons it, and so does any key that leads nowhere. SPC ? list every path - SPC d Del Exit (quit) remains available in the topbar. - SPC f s Save SPC f f Find SPC f n New - SPC y Y p P R the system clipboard - SPC w h/j/k/l focus a neighbouring pane - SPC j j the pane before this one SPC j o/i back / forward - SPC s a Attach SPC s D Detach - SPC t * the toggles SPC l * the language + SPC d `Del` (`Exit` has no path: use the top tag) + SPC f s `Save` SPC f f `Find` SPC f n `New` + SPC y yank to the clipboard SPC p paste from it + SPC w h focus left (and j, k, l) + SPC j j `Last` SPC j o / SPC j i back / forward + SPC s a `Attach` SPC s D `Detach` + SPC t ... the toggles SPC l ... the language SPC h t this tutor + -> Guide: Keys + ================================================================= -= PART 6 — THE REST = += PART 10 — SCRIPTING = ================================================================= - NOT TEXT - .pdf the real document through MuPDF. j/k scroll it, `/` searches - it, Esc cancels selection and highlights, Shift-Esc leaves. - PdfFit / PdfTint / PdfSections sit in its own tag. - images pixels where the shell has them, petscii glyphs where it - does not (SPC t p / l / a). - - LANGUAGE — ZLS, compiled in, .zig only. Nothing runs in the background - and nothing is cached: gd gD gy gi gr, the ten SPC l words, ]d/[d and - ]D/[D, `=` for a format diff, and Tab after a dot in insert. - - THE CONTROL FILESYSTEM — the extension mechanism, and there is no - plugin API, no interpreter and no rebuild. A program that opens files - IS an extension. + Every session serves its panes as files over 9P: a program that + opens files IS an extension, with no plugin API. Pane shells get + $PARDES_9P (the socket) and $PARDES_PANE (their pane's serial). - pardes the 9P socket opens by default + 9ns --mntgen mount every session (9ns is cloud9's) + m=$NINE_MOUNT/pardes/<pid or name> + cat $m/index a line per pane + echo notes.txt:12 > $m/look a right click + echo Save > $m/pane/3/ctl a builtin on pane 3 - A directory per pane holding `name`, `body`, `tag`, `addr`, `dot`, - `data`, `sel`, `dirty`, `event`, `pty/` and the rest, plus `index`, - `status`, `look`, `exec` and `log` at the root. Every pane shell - receives `$PARDES_9P` (the socket) and `$PARDES_PANE` (its serial): + While a program holds a pane's event file open, middle and right + clicks in that pane come to it instead of acting, and writing a + click back makes pardes perform it: arbitrary command execution by + design. The socket sits in your private runtime directory. - /pane/<serial>/body - /index - /pane/new open it to make a pane; rmdir closes one - /look /exec `FILE:12` and `Save`, as the mouse does - - A terminal pane also has `pty/`: - - /pane/3/pty/ctl winsize, sig - /pane/3/pty/data terminal input/output - - Over 9P the same tree answers plan9port, from anywhere: - - 9p -a $XDG_RUNTIME_DIR/pardes-9p-work.sock ls / - 9p -a $XDG_RUNTIME_DIR/pardes-9p-work.sock read index - - ...and pardes is a 9P CLIENT too, so one session can read another's: - - pardes --mount=peer=work - /n/peer/pane/1/body Look opens the other session's body - - TWO THINGS WORTH KNOWING. While a program holds a pane's `event` file - open, MIDDLE AND RIGHT CLICKS IN THAT PANE BELONG TO IT: pardes reports - them and performs nothing, so that pane's tag can carry the program's - own words. The keyboard is never suppressed, and the clicks come back - when it exits. - - And writing an event record back — `origin type q0 q1` — makes pardes - perform the Look or Exec it names. That is arbitrary command execution - by design. The socket sits in the user's private runtime directory. - - docs/fs.md describes the filesystem and mount paths. + -> The Scripting chapter (docs/typ/scripting.typ) ================================================================= = SUMMARY = ================================================================= - MOUSE L select/focus M execute R look(open) - 1-2 cut 1-3 paste both in one hold = snarf - 2-1 = execute the middle word with the left selection + MODES blank NORMAL, ^ INSERT, $ RAW; each pane keeps its own + Esc: back to the previous pane (RAW: only at an empty + prompt; INSERT: back to NORMAL) + Shift-Esc: out of RAW always; in a terminal's NORMAL, + into RAW; Ctrl-b toggles RAW and NORMAL + + MOUSE L select/focus M execute R look + 1-2 cut 1-3 paste 1-2 then 1-3 copy + 2-1 execute the middle word with the left selection keyboard: Enter = look, Tab = execute - PANES Ctrl-w h/j/k/l focus a neighbour (SPC w h/j/k/l in a body) - Esc the pane you were in before this one - Shift-Esc leave, whatever Escape means in here + PANES Ctrl-w h/j/k/l focus a neighbour (NORMAL mode) Ctrl-o / Ctrl-i jump history back / forward - Alt-n new terminal below Alt-c pane into a new column - - TTY a terminal IS a pane - Ctrl-b toggles raw input; Shift-Esc enters from editor mode - Togglettymode in the tag switches modes in either direction - Esc goes to the program — except at a shell PROMPT, where - it hops to the previous pane - Ctrl-b switches to editor mode - Ctrl-V types the register, Ctrl-Shift-V the clipboard - All other keys belong to the child - - DETACHED pardes --detach[=name] the core, no terminal - pardes --attach[=name] a frontend for it - Attach / Detach the same, as words (SPC s a / s D) - the pane shells belong to the SESSION and never stop - N frontends share ONE screen at the smallest common grid - the default 9P socket works in a daemon too + Alt-n new terminal Alt-c pane into a new column KEYS h j k l w b e 0 gl ^ gg ge f F t T v x d c y p i a I A o O u undo U redo - motions SELECT, so `i` types at the selection's start and - `;` collapses first - a leading NUMBER is a count on motions, x, o/O, > <, p P, - Ctrl-a/x and ]space — not on the other edits - bare G does nothing; `ge` is the START of the last line + motions SELECT: i types at the selection's start, and + ; collapses first + bare G does nothing; ge is the START of the last line s / S regex cursors; m i/a textobjects; m s/r/d surround - ] [ paragraph, blank line, diagnostic; | pipe a selection / is a case-insensitive SUBSTRING search, not a regex SPC is the leader (SPC ? lists every path) - SCRIPTING 9P is served by default on $PARDES_9P - /pane/<id>/{name,body,tag,ctl,addr,data,sel,event,pty/} - a script holding `event` owns that pane's middle and right - clicks; writing a record back runs it - - vs HELIX selection is LINE-first (x), v for characters; `/` is a - substring search, not a regex. - vs VIM no verb+noun — the motion already selected, so `wd` is - what `dw` was; Esc hops focus; bare G is a no-op. - vs BOTH a terminal is just a pane, and the core can outlive the - terminal that is showing it. - - Spawn this tutor again: middle-click "Tutor" in the top bar, or SPC h t. + vs HELIX selection is LINE-first (x), v for characters; / is a + substring search; n/N walk look-able text. + vs VIM no verb+noun: the motion already selected, so wd is + what dw was; Esc goes back a pane; bare G does nothing. + vs BOTH a terminal is just a pane, and the session can outlive + the terminal that is showing it. - Quit: this is a file pane — `:q` is not wired. Close it with Del in its - tag, "Exit" in the top bar to quit everything, or Ctrl-c the app. - "Kill" does not quit: it stops the commands pardes started, as acme's does. + Open this tutor again: middle-click `Tutor` in the top tag, or + SPC h t. It is a file pane: close it with `Del` in its tag. `Exit` + in the top tag quits everything; `Kill` only stops commands. diff --git a/test/snapshots/builtins.golden b/test/snapshots/builtins.golden index 0f13ed7c..27b43a3a 100644 --- a/test/snapshots/builtins.golden +++ b/test/snapshots/builtins.golden @@ -34,48 +34,48 @@ |19: 4 |20: 5 The model is acme's: EVERYTHING is text, editable and executable |21: 6 the same way. Modal editing (helix-style) lives on top of that. -|22: 7 Press j until you reach the introduction. +|22: 7 Press j until you reach part 1. |23: 8 -|24: 9 -|25: 10 ================================================================= -|26: 11 = INTRODUCTION = -|27: 12 ================================================================= -|28: 13 -|29: 14 A pane is just text on screen. There is no separate "buffer" type — -|30: 15 what you see is what you edit. Some panes happen to be live terminals; -|31: 16 most are text you move a cursor over. Modal editing is layered on top. -|32: 17 -|33: 18 Three modes (the BOX at the pane's top-left corner shows which): -|34: 19 NORMAL block cursor, keys move and edit. The box is BLANK. -|35: 20 ^ INSERT keys type text at the cursor. -|36: 21 $ TTY keys go straight to the shell. (terminals only) -|37: 22 -|38: 23 A bare `pardes` opens one shell already in TTY. Give it a file or a -|39: 24 directory and you start in NORMAL. -|40: 25 -|41: 26 SIX PARTS, ordered by what is most different from editors you know: -|42: 27 1 — THE MOUSE. Acme's three buttons; nothing like vim. -|43: 28 2 — PANES. Moving between them. Esc, Shift-Esc, Ctrl-w. -|44: 29 3 — THE TTY. Raw input, prompt-aware Esc, and the mode tag. -|45: 30 4 — DETACHED. The core outliving the terminal showing it. -|46: 31 5 — THE KEYS. Helix-style modal, and where it differs. -|47: 32 6 — THE REST. PDFs, images, the language backend, scripting. +|24: 9 The lessons follow the guide (docs/typ/guide.typ, the "Guide" +|25: 10 chapter of the pardes book), and each ends by naming its section. +|26: 11 +|27: 12 Words written in backticks, like `Save`, are builtins: type one in +|28: 13 any tag and middle-click it. Lines starting with "| " are tags as +|29: 14 pardes draws them, words only. +|30: 15 +|31: 16 PRACTICE BLOCKS (part 9): the "# keys:" line lists keystrokes +|32: 17 (space-separated; esc/enter/bs are special, the rest type each char). +|33: 18 The lines under "# before" are what you practise on; "# after" is +|34: 19 what you should end up with. Nothing runs them. What pins the real +|35: 20 behaviour is `zig build hxdiff`, which replays test/hxcases against +|36: 21 goldens recorded from a real helix, and `zig build hxparity`, which +|37: 22 runs each case in a file pane and in a shell and demands they agree. +|38: 23 +|39: 24 +|40: 25 ================================================================= +|41: 26 = PART 1 — MODES = +|42: 27 ================================================================= +|43: 28 +|44: 29 A pane is text on screen, a tag over a body. Some panes are live +|45: 30 terminals; most are text you move a cursor over. Each pane has its +|46: 31 own mode and keeps it while you are elsewhere. The BOX at the left +|47: 32 of a pane's tag shows it: |48: 33 -|49: 34 PRACTICE BLOCKS (part 5): the "# keys:" line lists keystrokes -|50: 35 (space-separated; esc/enter/bs are special, the rest type each char). -|51: 36 The lines under "# before" are what you practice on; "# after" is what -|52: 37 you should end up with. They are here to be TYPED and nothing runs -|53: 38 them. What pins the real behaviour is `zig build hxdiff`, which replays -|54: 39 test/hxcases against goldens recorded from a real helix, and -|55: 40 `zig build hxparity`, which runs each case twice — once in a file pane, -|56: 41 once in a shell — and demands the two agree. +|49: 34 NORMAL keys move and select. The box is BLANK. +|50: 35 ^ INSERT keys type text at the cursor. +|51: 36 $ RAW keys go straight to the program (terminals only). +|52: 37 +|53: 38 Where each pane starts: a file or a PDF in NORMAL; a terminal from +|54: 39 `Tty` in RAW, one from Alt-n in NORMAL; a command pane in RAW while +|55: 40 its command runs. A bare `pardes` starts a shell in RAW with an +|56: 41 empty text pane under it; `pardes FILE` starts on the file. |57: 42 -|58: 43 Hold j to reach part 1. +|58: 43 Esc and Shift-Esc, by mode: |59: 44 -|60: 45 -|61: 46 ================================================================= -|62: 47 = PART 1 — THE MOUSE (acme chording) = -|63: 48 ================================================================= -|64: 49 -|65: 50 Three buttons, three verbs: -|66: 51 LEFT select, and focus the pane. Also pins the cursor. +|60: 45 NORMAL Esc back to the previous pane (`Last`, SPC j j) +|61: 46 Shift-Esc the same; in a terminal it switches to RAW +|62: 47 INSERT Esc back to NORMAL +|63: 48 Shift-Esc the same; in a terminal it switches to RAW +|64: 49 RAW Esc goes to the program, except at an idle, +|65: 50 EMPTY shell prompt: back to the previous pane +|66: 51 Shift-Esc always back to the previous pane diff --git a/test/snapshots/leader.golden b/test/snapshots/leader.golden index bde9f7bf..59f6930c 100644 --- a/test/snapshots/leader.golden +++ b/test/snapshots/leader.golden @@ -25,29 +25,29 @@ |15: 4 |16: 5 The model is acme's: EVERYTHING is text, editable and executable |17: 6 the same way. Modal editing (helix-style) lives on top of that. -|18: 7 Press j until you reach the introduction. +|18: 7 Press j until you reach part 1. |19: 8 -|20: 9 -|21: 10 ================================================================= -|22: 11 = INTRODUCTION = -|23: 12 ================================================================= -|24: 13 -|25: 14 A pane is just text on screen. There is no separate "buffer" type — -|26: 15 what you see is what you edit. Some panes happen to be live terminals; -|27: 16 most are text you move a cursor over. Modal editing is layered on top. -|28: 17 -|29: 18 Three modes (the BOX at the pane's top-left corner shows which): -|30: 19 NORMAL block cursor, keys move and edit. The box is BLANK. -|31: 20 ^ INSERT keys type text at the cursor. -|32: 21 $ TTY keys go straight to the shell. (terminals only) -|33: 22 -|34: 23 A bare `pardes` opens one shell already in TTY. Give it a file or a -|35: 24 directory and you start in NORMAL. -|36: 25 -|37: 26 SIX PARTS, ordered by what is most different from editors you know: -|38: 27 1 — THE MOUSE. Acme's three buttons; nothing like vim. -|39: 28 2 — PANES. Moving between them. Esc, Shift-Esc, Ctrl-w. -|40: 29 3 — THE TTY. Raw input, prompt-aware Esc, and the mode tag. +|20: 9 The lessons follow the guide (docs/typ/guide.typ, the "Guide" +|21: 10 chapter of the pardes book), and each ends by naming its section. +|22: 11 +|23: 12 Words written in backticks, like `Save`, are builtins: type one in +|24: 13 any tag and middle-click it. Lines starting with "| " are tags as +|25: 14 pardes draws them, words only. +|26: 15 +|27: 16 PRACTICE BLOCKS (part 9): the "# keys:" line lists keystrokes +|28: 17 (space-separated; esc/enter/bs are special, the rest type each char). +|29: 18 The lines under "# before" are what you practise on; "# after" is +|30: 19 what you should end up with. Nothing runs them. What pins the real +|31: 20 behaviour is `zig build hxdiff`, which replays test/hxcases against +|32: 21 goldens recorded from a real helix, and `zig build hxparity`, which +|33: 22 runs each case in a file pane and in a shell and demands they agree. +|34: 23 +|35: 24 +|36: 25 ================================================================= +|37: 26 = PART 1 — MODES = +|38: 27 ================================================================= +|39: 28 +|40: 29 A pane is text on screen, a tag over a body. Some panes are live == snap deleted grid=100x41 cursor=7,3 |5: |6: diff --git a/test/snapshots/tutor.golden b/test/snapshots/tutor.golden index 2976f99a..c78fe3d8 100644 --- a/test/snapshots/tutor.golden +++ b/test/snapshots/tutor.golden @@ -13,8 +13,8 @@ | table | 6 the same way. Modal editing (helix-style) lives on top of t↩ | hat. -| 7 Press j until you reach the introduction. +| 7 Press j until you reach part 1. | 8 -| 9 +| 9 The lessons follow the guide (docs/typ/guide.typ, the "Guide" | /tmp/pardes-snap/tutor/cwd Tty+bash Save Mode Filter Collapse Del | ls |
