PARDES TUTOR tmux + vi + acme, from scratch. 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. ================================================================= = INTRODUCTION = ================================================================= 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. Three modes (the BOX at the pane's top-left corner shows which): NORMAL block cursor, keys move and edit. The box is BLANK. ^ INSERT keys type text at the cursor. $ TTY keys go straight to the shell. (terminals only) A bare `pardes` opens one shell already in TTY. Give it a file or a directory and you start in NORMAL. 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. A terminal is a pane. Ctrl-b and Shift-Esc. 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. 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. Hold j to reach part 1. ================================================================= = PART 1 — THE MOUSE (acme chording) = ================================================================= 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. 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) 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 Filter. Images and PDFs show "New Del". The top bar is: New Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Kill 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: Enter = LOOK (right button) Tab = EXECUTE (middle button) Both act on the selection when there is one. Neither is a way into TTY. ================================================================= = PART 2 — PANES: MOVING BETWEEN THEM = ================================================================= 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. 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. 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. WHEN Esc MEANS SOMETHING ELSE Three panes have their own claim on Escape, so there is a second spelling that always leaves: Shift-Esc leave this pane, whatever Escape means inside it 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. 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. 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 Newcol in the top bar: a pane, or a pane in a new column Joincol delete this column, move its panes to the one right Del in a pane's own tag: close it 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. ================================================================= = PART 3 — THE TTY (a terminal is just a pane) = ================================================================= 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. 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. THE TOGGLE Ctrl-b in and out of raw TTY, on a terminal pane Shift-Esc the same toggle, spelled for hosts that report modifiers on Escape `pardes --tty-toggle=g` makes it Ctrl-g instead — any letter but `c`, which stays SIGINT. Entering TTY is tty-NATIVE, and this is the part worth understanding. If the shell is at a prompt and your modal cursor sits on the input line, pardes first moves the shell's REAL cursor to that spot, counting back the prompt columns that were hidden from you. It does that with arrow keys the shell already understands, through the shell's own OSC 133 semantic prompt marks — it does not fake a cursor on top. So you navigate the clean page, hit the toggle where you want to keep typing, and you are IN the live shell at that spot. LEAVING Ctrl-b back to normal on this pane Shift-Esc hop AWAY, leaving this pane in TTY — so coming back lands you in the program you left Esc goes to the program... unless the leaf process is a SHELL sitting at its prompt, in which case it hops away exactly like Shift-Esc That last rule is the one people ask about. A shell prompt is a pane you can leave, so plain Esc leaves it; a full-screen program (vim, a pager, an agent) has taken the tty, so Esc belongs to the program and you need Shift-Esc. Pardes knows which by asking whether the pane's leaf process is still the prompt it forked. PASTING INTO THE PROGRAM Ctrl-V type the DEFAULT register — what `y` put there Ctrl-Shift-V ask the SYSTEM clipboard SPC p and the mouse chords cannot reach here: the pty owns every keystroke and every button. `Filter` in a terminal's tag toggles a pane-local, theme-keyed palette. ================================================================= = PART 4 — DETACHED SESSIONS (the core outlives the terminal) = ================================================================= 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 And from inside a running editor, as ordinary acme words — type one in a tag and execute it, or press its chord: 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) 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. `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. 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. WHAT A DAEMON CAN ALSO DO A detached session serves acme's control filesystem like any other: pardes --detach=work --fs the tree under $XDG_RUNTIME_DIR pardes --detach=work --fs9 the same tree, as 9P on a unix socket Either, both or neither. That is what makes a daemon scriptable while nobody is looking at it — see part 6. The socket lives in $XDG_RUNTIME_DIR (else ~/.local/state/pardes), created 0700, never /tmp: it carries keystrokes into a live editor. ================================================================= = PART 5 — 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. ----------------------------------------------------------------- = 5.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 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. # keys: l l l # before 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. # keys: 3 l i Z esc # before abcdef # after abcZdef ----------------------------------------------------------------- = 5.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. # keys: i X esc # before abc # after Xabc # keys: o line2 esc # before line1 # after line1 line2 ----------------------------------------------------------------- = 5.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. # keys: w ; i Z esc # before one two # after one Ztwo ----------------------------------------------------------------- = 5.4 SELECT AND EDIT = ----------------------------------------------------------------- x select the LINE (again: extend by one more) v character-wise select, 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 # after typed ----------------------------------------------------------------- = 5.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. 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). ----------------------------------------------------------------- = 5.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 abandons it, and so does any key that leads nowhere. SPC ? list every path SPC k Kill SPC d Del 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 h t this tutor ================================================================= = PART 6 — THE REST = ================================================================= 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. pardes --fs acme's control files over FUSE pardes --fs9 the same tree as 9P on a unix socket A directory per pane holding `body`, `tag`, `ctl`, `addr`, `data`, `event`, `pty/` and the rest, plus `index`, `cons` and `new/` at the top. Every pane shell is told `$PARDES_FS` and `$PARDES_PANE`, so a script in a pane addresses its own window with no arguments: echo hello >> $PARDES_FS/$PARDES_PANE/body cat $PARDES_FS/index echo hi > $PARDES_FS/new/body A terminal pane also has `pty/`: echo 'winsize 100 30' >> $PARDES_FS/3/pty/ctl echo 'sig INT' >> $PARDES_FS/3/pty/ctl echo 'make -j8' >> $PARDES_FS/3/pty/data 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: 9p work.sock /1/body the `9p` word: opens it in a pane 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 (examples/acmefs/life.py puts Step/Run/Clear there). 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 same door acme has always had, which is why the mount sits under $XDG_RUNTIME_DIR at 0700 and why both flags are opt-in. examples/README.md is the client's guide; docs/acme-fs.md compares this implementation with acme's file by file; docs/9p.typ is the 9P note. ================================================================= = 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 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 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 or Shift-Esc toggles the shell, landing its cursor where you navigated Esc goes to the program — except at a shell PROMPT, where it hops away like Shift-Esc Ctrl-V default register, Ctrl-Shift-V system clipboard 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 --fs and --fs9 work in a daemon too KEYS h j k l w b e 0 $ ^ 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 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 --fs acme's files over FUSE; --fs9 the same over 9P $PARDES_FS//{body,tag,ctl,addr,data,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. Quit: this is a file pane — `:q` is not wired. Close it with Del in its tag, "Kill" in the top bar to quit everything, or Ctrl-c the app.