summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 23:04:26 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commitf058514afb2f0e62a46158dc8c361c99ecd5149c (patch)
tree16e0e9ed0754bc1361dba9ae8c3589cd1d87ec81 /src
parentf88eadfdf2629ff0d1086b6cf9299be2713ee3b6 (diff)
downloadpardes-f058514afb2f0e62a46158dc8c361c99ecd5149c.tar.gz
pardes-f058514afb2f0e62a46158dc8c361c99ecd5149c.zip
The tutor follows the guide's order, points at its sections, and no longer teaches $ as line end or the terminal tag's old words; a test checks the words, paths, chords and tags it names
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/config.zig124
-rw-r--r--src/tutor.txt625
2 files changed, 425 insertions, 324 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.