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 = ================================================================= Pardes is tmux + vi + acme, made from scratch. The foundation is the acme model: 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 (tty mode); most are text you can move a cursor over. Modal editing (helix-style) is layered on top of that. Three modes (the BOX at the pane's top-left corner shows which): NORMAL block cursor, keys move/edit. The box is BLANK: a pane at rest has nothing waiting to eat what you type. ^ 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 — there is nothing to edit yet. Give it a file or a directory and you start in NORMAL. This tutor is in FOUR parts, ordered by what's most different from editors you may know: PART 1 — the MOUSE. Acme's three buttons; nothing like Vim/Helix. PART 2 — the TTY. A terminal is just a pane; Ctrl-b (or Shift-Esc) drops into it. PART 3 — the KEYS. Helix-style modal (with the Pardes differences). PART 4 — the REST. Panes that are not text (PDFs, images), the language backend, and the chrome you can change. PRACTICE BLOCKS (part 3): the "# keys:" line lists keystrokes (space-separated; esc/enter/bs are special, the rest type each char). The clean lines under "# before" are what you practice on; the lines under "# after" are what you should end up with. "# at: row,col" (right after # keys:) sets where the cursor starts. They are here to be TYPED, and that is all they are — nothing runs them. What pins the real behaviour is two suites. `zig build hxdiff` replays every case in test/hxcases against GOLDENS recorded from a real helix, and fails on any difference that is not written down as a waiver with a reason. `zig build hxparity` runs those cases, and a file of editing extras, twice inside pardes — 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; the most different part) = ================================================================= Acme's central idea: the THREE mouse buttons each ACT on text, and the keyboard mirrors them. Pardes inherits this. (Vim and Helix have almost none of it — their mouse is selection/scroll only.) ┌─────────┬─────────┬─────────┐ │ L │ M │ R │ │ (1) │ (2) │ (3) │ ├─────────┴─────────┴─────────┤ │ │ │ ( o ) │ the wheel scrolls the pane │ │ └─────────────────────────────┘ L select a plain click also FOCUSES the pane and, outside tty mode, PINS the modal cursor where you click so you can edit there next. On a DOCUMENT it also drops you into normal mode; a terminal keeps the mode it had, so tty stays tty. M execute run the selected text: a builtin name runs it, anything else is SENT to the shell the pane runs. This is how you run a command you typed in a text mode. R look open the word under the cursor as a path: a directory opens/focuses a terminal there and ls's it; a file opens a file pane (scrolled to a ":line" suffix if present). A bare name is tried in THIS pane's directory first, then in the directories of the panes you have BEEN in, newest first and each directory tried once — a name none of them holds is what becomes a search. WEB TOUCH (one finger): a tap is LOOK, the same action as R; a drag past a small movement threshold is natural scrolling. Once a gesture becomes a scroll it cannot also LOOK on release. The browser has no host filesystem or ptys: LOOK opens URLs or read-only Pardes .zig sources that were tracked or new/nonignored when the web build was made. The published launcher is a real Pardes terminal dump of Git's tracked-plus-new/nonignored source list: every listed path opens, and the opened source uses tree-sitter colors. A touch beginning on a tagline or pane separator instead latches a left-mouse gesture, so layout drags never scroll. Touch circles/trails and the click flash are the SDL build's debug overlay, drawn only while the Debug builtin is on. The browser draws no such thing. SELECT-THEN-ACT: - MIDDLE-drag over text selects AND runs it on release (one gesture); a no-drag middle click auto-expands to the word under the cursor. - or select with the keyboard (`v` chars / `x` lines), then press Tab to execute or Enter to look — the acme chords on the keyboard. CHORDS (acme's held-button combos): 1-2 hold LEFT on a selection, tap MIDDLE: Cut it into the yank register (the snarf buffer). 1-3 hold LEFT, tap RIGHT: Paste the register over the selection, or splice it in at a bare click point. So 1-2 then 1-3 in the SAME hold = copy (Snarf): the text comes back, the register keeps it. After a chord the left release is inert. 2-1 hold a MIDDLE drag over a command, tap LEFT: the kept left selection rides along as the command's ARGUMENT. No selection in that pane? The pane the drag started in is asked first, then the active one, then the rest — a kept left selection or a v/x one, wherever it lives. 2-3 / 3-1 / 3-2 tapping the other button during a MIDDLE (execute) or RIGHT (look) drag CANCELS the gesture. In a TTY-mode shell 1-3 pastes into the program like a terminal: the click is forwarded first (if it listens for mouse) so the paste lands under it, then the register arrives — bracketed if the program asked for bracketed paste, raw otherwise. The selection a chord leaves behind is dismissed by the next left click (it doesn't stretch to the click). THE TAG: each pane has a one-line tag: its directory or file path + builtins. (The mode is the box at the tag's left end, not a word in the tag.) Every pane shows "New" for an empty temporary file in its column. Anything holding TEXT leads with "Save". A file pane shows "Save New Del" and Save writes that file (an unsaved edit puts `*` after the filename); a pane with no file of its own — a terminal, an output buffer — needs the path, so "Save notes.txt" writes it under the pane's own directory and a bare "Save" arms a prompt already filled in with that directory. Del closes the window. Clicking a tag edits it in insert mode: type straight in, Enter looks / Tab executes body focuses that same tag in NORMAL mode, parked on the FIRST WORD of the tail rather than in the run of layout spaces before it — vim's command line with acme's words in it, and on a file that first word is "Save", so a bare `:` then Tab saves. `:w` is w onto "Save", Tab to execute it, and you land back in the body where you left off (the chord keys are the same here as anywhere else; `i` if you would rather type a command than walk to one). A terminal shows "Save New Del Filter": Save writes its plaintext scrollback to the path you give and leaves it a terminal, and Filter starts on, is local to that terminal, and projects every rendered foreground and background ANSI, 256-colour and truecolour value through keys made from the current Pardes theme; run it to restore the program's original colours. Images and PDFs show the plain "New Del", because their bytes on disk already are exactly what they are. An image tag leads with "img" and a PDF's with the word "pdf", its page out of the count, and its three commands each preceded by its current setting (part 4.1) — all of it live chrome, like the path. In that normal mode h/j/k/l do NOT move inside the tag: a tagline is a place in the LAYOUT, so they focus the neighbouring window and land on ITS tagline, still in normal mode — you walk the taglines of the screen and never drop through a body. (Same moves as SPC w h/j/k/l and Ctrl-w h/j/k/l; nothing that way = you stay put.) The ARROWS are the in-tag motion, and w/b/e/0/$/^ still walk the tag's own words. Off the TOPMOST tagline k keeps going: above it is the top bar, which is a tagline too — the screen's own — and j comes back down. The path at the head of a tag is live chrome, so it cannot be edited — but it CAN be selected, by motion or by dragging across it: 0 then E selects exactly the path, `y` yanks it, Enter looks it. (`W` would drag the alignment spaces in with it.) Typing or backspacing with the cursor inside it does nothing; edits only ever reach the words you own, to the right of the path. "Delcol" still exists as a command: type it in a tag or body and execute it to close the whole column. A SEPARATE bar across the top of the screen holds the window-agnostic builtins: New Newcol Find Grep Help Tutor Dump NextColor Debug Kill — and one more, " Restore ", which appears the moment a Dump has written one. Middle-click "New" for an empty temporary file in this pane's column, "Newcol" for a new column, "Tutor" to spawn this tutor, "Kill" to quit. From the KEYBOARD the bar is `k` off the topmost tagline: it takes the cursor, h/l and the arrows move by character, w/b/e and 0/$ by word, Enter or Tab runs the word under the cursor the way a middle-click on it does (with one difference: the mouse can carry a selection kept elsewhere as the command's argument, and the keyboard cannot), j drops back onto the tagline below and Esc leaves. There is nothing to type up here — the bar is chrome, so it has no insert mode. Debug toggles the stats box in the top-right corner (and, where fingers exist, the touch circles and the LOOK flash). NextColor steps the theme RING one place — and the ring is 228 themes, not three: ours first ("helix", the default: a near-black page and chrome that barely lifts off it, copied from helix's own; then "dark"; then "acme", the acme-light yellow), then every theme helix and zed ship, generated out of vendor/themes when pardes is built and sorted by name. At 228 a step is a BROWSE and not a way to arrive: `Theme ` goes straight to one, and "ThemeSel" (SPC t t) lists all of them into a "+Themes" buffer whose rows ARE those commands — n/N to walk, Tab to wear the one you stopped on. The bar holds only what you reach for often — the rest of the builtins are words you execute and keys you press. Nearly every one has a KEY PATH under SPC (part 3.13): "Colors" (the global syntax/ANSI master switch) is SPC t c; a terminal's Filter setting stays remembered while Colors is off. The native GUI scene effects are SPC t r/R/g, panel transitions are SPC a s/z/d/a/v, and SPC ? lists the lot. Right-click a directory in any body to open a terminal there. SEARCH: `/` in normal mode types a pattern into that pane's own tag (it stays visible while you type; Esc abandons it). Enter searches the pane — a file's text, a shell's scrollback, anything — for the pattern, plain and case-insensitive, and writes one row per matching LINE into a "+Search" pane — the FIRST hit on that line, named as a location, with the line's own text after it — and then GOES to the first of those rows. That last part is nothing new to learn: it is the step `n` is and the look Enter is, run for you in the list that has just answered. A pattern that matched nothing opens its empty buffer and goes nowhere. That is an OUTPUT BUFFER: a file pane with no file behind it, so Save there always takes a path and asks for one when you do not give it — and writing it out leaves the buffer exactly where it was, still the list the next search refills. Everything else about it is an ordinary buffer you can read, edit, select and look in. The one that does become a file is "+New", the empty scratch, which is what it was opened for. It is not a document, though, and never takes a column of its own: it opens BELOW the pane that asked for it, in that pane's column, be that a file or a shell — and a file you then open from its rows goes where files go, not under the list. `n`/`N` carry on down those rows, and walking is ALL they do: a press moves the SELECTION to the next look-able thing and opens nothing, so you can step past nine hits to reach the tenth without opening the nine. Enter — the look chord — opens the one you stopped on. The walk is a RING over PANES and not over one buffer: the panes you have looked out of come first, most recent first, then the output buffers you have not, newest first, and only when there is neither does it step the pane in front of you — so n/N work on a shell that never had a search armed, and off the end they come round to the start. A Look from a result row may move focus into the source file; the result pane remains the walk's owner, so the next n/N returns there instead of starting from the file that opened. After the FIRST press `N` is `n` backwards exactly; the first one is not a step but an arrival, since from a cursor the walk has never stood on there is nothing yet to step back from. Right-clicking a word that names no file in ANY open pane's directory searches for it — acme's button 3 — except in tty mode, where a look that resolved nothing does nothing at all rather than searching. A hit in a file reads `path:LINE:COL-ENDCOL` and then the matching line's own text — the location is the leading word, and the path is spelled relative to the buffer's own directory. It is the ordinary look target carrying the SPAN that matched. A hit in a shell or an output buffer has no file to name, so it reads `@pN:LINE:COL-ENDCOL` — pane N, then the place in it. Looking either one goes there and SELECTS the span, which is why an Enter on a row n/N stepped to lands ON the hit rather than beside it. The range is part of the PATH syntax and not part of search: type one anywhere text lives and a look on it selects. `main.zig:412-418` is whole lines, `main.zig:412:9-21` is columns on one line, `main.zig:412:9-418:1` is the general form, and the shorter spellings still mean what they always did — `main.zig:100`, `main.zig:100:7`, `@p3:12`, all of them optional. FIND: the "Find" builtin (SPC f f) arms the same tag input, but Enter walks the pane's DIRECTORY instead of its text — `fd`, in-core — and writes one matching PATH per row into the same "+Search" buffer. Rows are look targets like any other, so n/N select them one at a time and Enter opens the one you meant; the match is on the name, plain and case-insensitive, and the walk skips the directories nobody means — .git, .jj, target, node_modules, .venv, __pycache__, .zig-cache and zig-out — and stops at 512 hits, sixteen levels down, or a hundred thousand directory entries, whichever comes first. LAYOUT: columns split the screen; windows stack within a column. Panes abut with no wasted gap — a pane's own trailing edge (its last column, or last row above the next tag) IS the resize handle: hover it and a dashed line overlays that edge (keeping the underlying colors) to mark the drag. - drag a column's right edge to resize it against its neighbour - drag a window's bottom edge to resize it against the one below; either window may shrink to just its tagline (the tag row then IS the handle) — re-enlarging brings the body back intact - drag the move box (top-left of a pane; brighter when the pane is focused, and coloured by the theme) to MOVE a window between columns / reorder - the scrollbar is the gutter below the box: left-click scrolls UP to that point, right-click scrolls DOWN - the FIRST document you open — a file, an image, a PDF, this tutor — takes a new leftmost column of its own, paid for only by the column you opened it from; every unrelated column keeps its exact rectangle. Every later document splits below a doc already open, so docs share that column - closing a window (Del, or its shell exiting) hands focus back to the window you were in before it, not to whichever one comes first (Mouse actions are hard to unit-test from text; native gestures are covered by the e2e suite. Browser touch and embedded-source LOOK are driven through real Chromium input by `zig build web-snap`.) ================================================================= = PART 2 — THE TTY (a terminal is just a pane) = ================================================================= This is the headline difference from Vim/Helix: there is no "open a terminal in a split" that's 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, and dropping into tty REUSES the shell's own prompt model rather than layering an editor cursor on top. On a terminal pane: NORMAL ( ) the PROMPTS are gone — a clean acme-style page — but the command you typed at each one stays, pulled over to the left edge, because that is the part worth reading. A prompt you never typed at leaves a blank row behind. You navigate the page with the same h/j/k/l/w/b/e as a file. INSERT (^) the same page; keys type an insertion overlay (the command you're composing). TTY ($) the REAL shell — prompts back, and keys go straight to the pty as terminal input. ESC: insert -> normal; from body NORMAL it hops to the pane you were in before this one, whichever it was (the same Last as SPC j j) — so held down it alternates between two panes. In raw TTY it still goes to the program. Ctrl-b: toggles TTY on a terminal, and is the only way back OUT of it. SHIFT-ESC gets you IN, and once in TTY it does what ESC does in normal mode — hops away, leaving this pane in TTY so coming back lands you in the program you left. Use `pardes --tty-toggle=g` to make Ctrl-g the toggle instead — any letter but `c`, which stays SIGINT. Entering is tty-native: if the shell is at a prompt and your modal cursor sits on the input line, it first moves the shell's REAL cursor to that spot, counting back the prompt columns that were hidden from you, via the shell's OSC 133 semantic prompt (ghostty promptClickMove; our rc opts in with cl=line). The shell moves its own cursor with arrow keys it understands — Pardes doesn't fake one on top. ENTER / TAB (in normal) are NOT a way into tty — they're the acme mouse chords on the keyboard: Enter = "look" (open the path under the cursor), Tab = "execute". Both act on the selection if one is active. So the flow is: navigate the hidden-prompt page in normal, hit the tty toggle where you want to keep typing, and you're IN the live shell at that spot. No separate "terminal mode cursor" to reconcile. (The cursor positioning is enterTty's promptClickMove; it needs a live shell at a prompt. Mouse/tty behavior is exercised by the e2e suite.) ================================================================= = PART 3 — THE KEYS (helix-style modal, Pardes differences) = ================================================================= Same core as Helix: a block cursor you move with h/j/k/l, motions w/b/e, insert with i/a/o, and a SELECTION every edit acts on. Two differential suites — `zig build hxdiff`, which replays these keys against goldens recorded from a real helix, and `zig build hxparity`, which runs them twice inside pardes and demands a file pane and a shell agree — fail the build on any difference nobody has written a waiver for. So where this file disagrees with helix's own manual, helix is right and this file is the bug. The deliberate divergences: - Selection is LINE-first: `x` grows a line selection downward, and an edit with nothing selected acts on the line (or the character) the cursor is on. `v` is the CHARACTER range when you want one. - NO verb+noun (Vim's dw, cw) — because the motion has already made the selection. A traversal motion SELECTS what it crossed (3.3), so `wd` is what `dw` was; `miw` then `d` deletes a word from inside it, and `x` then `d` deletes whole lines. - A plain ESC never reaches tty. From a BODY in normal mode it hops to the pane you were in before this one; everywhere else it leaves insert, or abandons a leader path, a tag, an armed input. Dropping a terminal into the live shell is the tty toggle (Ctrl-b by default, or Shift-Esc; see Part 2). - Enter / Tab in normal mirror the mouse: Enter = look (open the path under the cursor), Tab = execute. `:` runs a command from the tag (see Part 1). `u` undo, `U` redo. - `/` is a plain case-insensitive SUBSTRING search (Part 1), not helix's regex one. There IS a regex engine and it lives on `s` and `S` alone (3.7). - Alt-c moves this pane to a fresh column instead of helix's change-noyank, and Ctrl-b pages up in a document but toggles tty on a terminal. Those two are written down as waivers, not accidents. - Editing a file pane MUTATES real content; a terminal pane yanks rendered text and pastes it as an insertion run (shell output can't be deleted, only pasted text can). PRACTICE: run the keys on the "# before" lines; you should get the "# after" lines. ----------------------------------------------------------------- = 3.1 MOVING THE CURSOR (h j k l), AND COUNTS = ----------------------------------------------------------------- k * h = left, l = right h l * j = down, k = up (also: arrow keys) j The cursor sits ON a character (block). Motions only move. # keys: l l l # before abcdef # after abcdef A number typed FIRST is a count. Not everything takes one, and the set that does is worth knowing: the motions (h/j/k/l, w/b/e, W/B/E), the four f/F/t/T and the `Alt-.` that repeats them, `gg`/`gj`/`gk`/`g|`, `x`, `o` and `O`, `>` and `<`, `p` and `P`, Ctrl-a and Ctrl-x, `]p`/`[p` and `]space`/`[space`, and the three cursor-list keys `C`, `Alt-C` and `)`/`(`. Everywhere else the number is swallowed — `3d` deletes once, `3i` types once, `3ge` goes to the last line like `ge`. `0` is still line-start and never the start of a count, and Ctrl-d/u/f/b ignore one on purpose: a page is a page. # keys: 3 l i Z esc # before abcdef # after abcZdef ----------------------------------------------------------------- = 3.2 ENTERING INSERT: i a A I o O = ----------------------------------------------------------------- `i` insert at cursor. `a` insert AFTER the cursor. `A` insert at end of line. `I` insert at first non-blank. `o` open line BELOW + insert. `O` open line ABOVE + insert. (All match Vim and Helix.) # keys: i X esc # before bcdef # after Xbcdef Cursor on 'b' (col 0). `i` inserts before 'b'. # keys: a Z esc # before abc # after aZbc Cursor on 'a' (col 0). `a` inserts after 'a'. # keys: A Z esc # before abc # after abcZ `A` jumps to end then inserts — appends 'Z'. # keys: I Z esc # before abc # after Zabc `I` goes to the first non-blank ('a'), inserts before it. # keys: o line2 esc # before line1 # after line1 line2 `o` makes a blank line below, enters insert, "line2" typed, Esc. # keys: O top esc # before bot # after top bot ----------------------------------------------------------------- = 3.3 WORD MOTIONS: w b e (W B E long) = ----------------------------------------------------------------- `w` next word start, `b` prev word start, `e` next word END. W/B/E treat punctuation as part of the word. Same letters as Vim. NOT the same MEANING, though, and this is the one thing to unlearn: a word motion in helix SELECTS what it crossed. `w` from 'f' does not just park on 'b' — it leaves "foo " selected with the cursor at its end. That is why d needs no motion after it (3.6): the motion already made the selection. # keys: w d # before foo bar # after bar `w` from 'f'(col0) selects "foo " up to 'b'(col4); `d` deletes it. # keys: e d # before foo bar # after bar `e` selects to the END of "foo" and no further, so the space stays. # keys: b d # at: 0,4 # before foo bar # after bar Cursor on 'b'(col4, "bar"). `b` selects back to 'f'(col0). The consequence to remember: `i` inserts at the START of whatever is selected, not where the cursor sits. After `w` on "foo bar", `i` types at column 0. `;` collapses the selection onto the cursor first, so `w;i` is the vim reading of `wi`. # keys: w ; i Z esc # before foo bar # after fooZ bar ----------------------------------------------------------------- = 3.4 LINE BOUNDS AND GOTO: 0 $ ^ and the g prefix = ----------------------------------------------------------------- `0` start of line, `$` end, `^` first non-blank. These three MOVE without selecting, unlike the word motions above — so `$i` really does insert at the end of the line. # keys: $ i Z esc # before abcde # after abcdZe `$` to last char 'e', insert before it. (Cursor ON a char, so "end" = last char, not past it.) # keys: 0 i Z esc # before abcde # after Zabcde # keys: ^ i Z esc # before abcde # after Zabcde `g` is a PREFIX, and it is helix's g rather than vim's: gg first line ge LAST line (what vim spells G) Ngg line N Ng| column N gh line start gl line end gs first non-blank gj VISUAL line down gk VISUAL line up gt the view's top row gc its middle gb its bottom (top and bottom are scrolloff-clamped: three rows in, so they land where the cursor could have scrolled to anyway) `G` ALONE DOES NOTHING. helix's goto_line only acts with a count, so `12G` is line 12 and a bare `G` is a keystroke that went nowhere — `ge` is the key that takes you to the last line — its START, the way `gg` takes you to the first line's. `gj`/`gk` are the VISUAL pair: with soft wrap on (`SPC t w`, part 4.1, on to start) one long line is drawn as several rows, and these two walk those rows — the automatic breaks, not the newlines — keeping the column they had inside the row. Plain `j`/`k` stay whole FILE lines, so on a wrapped paragraph `j` jumps past every row of it and `gj` steps one. With wrap off a line is one row and the two pairs are the same key. Five more gotos live under the same prefix — gd gD gy gi gr — and they belong to the language backend, in part 4.2. ----------------------------------------------------------------- = 3.5 FINDING A CHARACTER: f F t T = ----------------------------------------------------------------- `f` extends the selection forward THROUGH the next on this line, `F` backwards. `t` and `T` are the same two but stop one short of it. A count takes the Nth — `2fo` is the second `o`. `Alt-.` repeats whichever of the four you used last. They select, the way w/b/e do, so d is the natural next key: `fx` and then `d` deletes everything up to and including the next x. # keys: f d d # before abcdef # after ef `fd` selects "abcd"; `d` deletes it. # keys: t d d # before abcdef # after def `td` stops one short, so only "abc" goes. ----------------------------------------------------------------- = 3.6 SELECTION + EDIT: x v d c y p, and the rest = ----------------------------------------------------------------- The Pardes difference is LINE-first selection: `x` grows a line selection downward; d/c/y act on it. `v` is still available for a character range when you need one. `x` start/extend a line selection `X` stretch the selection you have out to whole lines `Alt-x` the opposite — shrink it back inside them `v` character-range select mode; motions extend it `;` collapse the selection onto the cursor; `Alt-;` flip its ends `%` select the whole file `d` delete the selected lines (yanked) `Alt-d` delete them WITHOUT yanking `c` clear the line + insert (change) `y` yank the selection — and with nothing selected the selection is the CHARACTER under the cursor, not the line, so `x` first if a line is what you meant `p` `P` paste the yank after / before. A LINE yank comes back as a whole line below or above; a character yank splices in beside the cursor `R` replace the selection with the yank `r` overwrite every character of the selection with # keys: x d # at: 1,0 # before keep gone # after keep Cursor on "gone" (row 1). `x` selects it, `d` deletes it. # keys: x x d # at: 1,0 # before a b c # after a `x` selects "b", `x` extends to "c", `d` removes both. # keys: x y j p # before orig dupe # after orig dupe orig `x` selects "orig", `y` yanks it, `j` to "dupe", `p` pastes below. # keys: x c typed esc # before old # after typed `x` selects "old", `c` clears it to an empty line + insert, type. # keys: c Q esc # at: 0,1 # before ab # after aQ No selection: cursor on 'b'(col1). `c` deletes 'b' + insert; type 'Q'. And the ones that rewrite a selection where it stands: `~` toggle case `` ` `` lowercase ``Alt-` `` uppercase `J` join this line with the next, on one space `>` `<` indent / unindent by four spaces (a count = that many) `Ctrl-a` `Ctrl-x` add to / subtract from the SELECTION read as a decimal integer. With nothing selected the selection is one character, so `41` with the cursor on the `4` becomes `51` — select the whole number first if you meant 42 `Ctrl-c` comment or uncomment every line the selection touches. The token comes from the file's EXTENSION: `//` for .zig and .c, `#` for .py and .sh, `--` for .lua, `;` for .el, `%` for .tex and .erl, `!` for fortran, `"` for .vim — and `#` for any extension the table does not name, which includes having none. In raw tty mode Ctrl-c is still the shell's SIGINT. `=` ask the language backend to format the selection (part 4.2) # keys: x ~ # before abc # after ABC # keys: J # before one two # after one two # keys: x > # before abc # after abc ----------------------------------------------------------------- = 3.7 MORE THAN ONE CURSOR: s S C , ) ( _ = ----------------------------------------------------------------- There IS multi-cursor, and it is helix's: a selection is a LIST of ranges with one PRIMARY among them, and every ordinary key is replayed once per range — type in insert and you type in all of them. Sixty-four ranges is the ceiling; matches past it are dropped. The two keys that make a LIST out of one range are regex: `s` keep every match inside each selection, as its own cursor `S` keep the pieces BETWEEN the matches instead Both arm the pane's tag the way `/` does, and both preview LIVE: the selections redraw on every character you type. A pattern that does not compile yet falls back to the selection you had BEFORE you pressed `s` rather than clearing everything, which is what makes typing one character at a time bearable — every unfinished prefix of a pattern is one of those. Enter commits, Esc puts back exactly what you had. A pattern with no uppercase in it matches case-blind, which is helix's smart case. Two things differ from helix's regex: `.` matches a newline here like any other byte, and `^`/`$` assert at the scanner's position rather than at a line boundary. The keys that then work on the LIST rather than on the text: `C` copy the primary selection down a line; `Alt-C` up `,` keep only the primary; `Alt-,` drop it, promote the next `)` `(` rotate which one is primary, forward / back `Alt-s` split every selection into one range per line `Alt--` merge them all into one span `Alt-_` merge only the ones that touch `_` trim the whitespace off each; empty ones vanish `SPC y` joins every cursor's text for the system clipboard and `SPC Y` takes the primary's alone (3.13). (No practice blocks: the "# keys:" notation has one cursor in it.) ----------------------------------------------------------------- = 3.8 PAIRS AND TEXTOBJECTS: the m prefix = ----------------------------------------------------------------- `mm` jump to the bracket matching the one under the cursor `mi` select INSIDE a textobject `ma` select AROUND it — delimiters included `ms` surround the selection with and its partner `mr` replace the enclosing pair with 's `md` delete the enclosing pair The objects are `w` a word, `W` a long word, `p` a paragraph, and the pairs `( ) [ ] { } < >` with the quotes `'` `"` and the backquote. Quotes are LINE-SCOPED — a string does not span lines here, so neither does the object. helix's tree-sitter objects (a function, a class, an argument) are not in this set. For SURROUNDING, any character at all is its own pair: `msx` wraps the selection in x's. `miw` and then `d` is how you delete a word without a verb+noun grammar. ----------------------------------------------------------------- = 3.9 ] AND [ : STEPPING BY THING = ----------------------------------------------------------------- `]p` `[p` next / previous paragraph (blank lines delimit one) `]space` add a blank line below; `[space` above (a count = N) `]d` `[d` next / previous diagnostic — and if no list is up yet, asking the language backend for one is part of the press `]D` `[D` the last / the first of them ----------------------------------------------------------------- = 3.10 PIPING A SELECTION: | = ----------------------------------------------------------------- `|` in a file body arms the pane's tag with a bare "|" and takes a command line. Enter runs it under `/bin/sh -c` once per selection: the selection's bytes go in on stdin, and its stdout replaces them. Every cursor is filtered in the same pass, and the whole thing is ONE undo. `%` then `| sort` sorts a file. A pane whose BYTES ARE ITS OWN, and only that: a real file, or the scratch that is on its way to being one. So `|` is inert in a terminal (shell output cannot be rewritten) and in a rendered output buffer like "+Search", whose next refill would discard the filtered rows anyway, and in both the tag stays as it was. Not to be confused with EXECUTING a word, which sends it to the pane's own shell and leaves the text alone. This one is a filter, and what it returns lands back in the buffer. ----------------------------------------------------------------- = 3.11 INSERT MODE ITSELF = ----------------------------------------------------------------- Backspace, Delete, Enter and Tab are what those keys ARE, not bindings — Enter carries the previous line's indentation down with it. The rest is helix's: Ctrl-h / Ctrl-j / Ctrl-d Backspace / Enter / Delete, spelled out Ctrl-w, Alt-Backspace delete the word behind the cursor Alt-d, Alt-Delete delete the word in front of it Ctrl-u kill back to the start of the line Ctrl-k kill forward to the end of it Ctrl-w is the WINDOW prefix in normal and tty mode; insert mode wins it, which is helix's own arrangement — so a tag being typed into swallows it. TAB has one extra job: pressed straight after a `.` in a source file the language backend speaks, it asks what could go there (part 4.2). Everywhere else — and when the answer comes back empty — it indents to the next four-column stop. ----------------------------------------------------------------- = 3.12 VIEWPORT + PANES = ----------------------------------------------------------------- zt / zz / zb scroll so the cursor is at top/center/bottom (`zc` is `zz` spelled the other way) zj / zk scroll the view one line, cursor pushed along Ctrl-d / Ctrl-u half-page down / up Ctrl-f full page down. (Ctrl-b pages up on a file pane; on a terminal it is the default tty toggle. If you start with --tty-toggle=g, Ctrl-g takes that role.) Ctrl-o / Ctrl-i walk the jump history back / forward — the same two builtins as SPC j o and SPC j i, and SPC j l shows the stack itself as a buffer. (Ctrl-i and Tab are the same byte on an old terminal; where that is so, Tab keeps meaning execute.) Ctrl-w h/j/k/l focus the pane left/down/up/right (SPC w h/j/k/l does the same; Ctrl-w also reaches a tty pane, where SPC belongs to the shell) Esc hop to the pane you were in before this one, so held down it alternates between two (body normal mode; the same builtin as SPC j j). A PDF pane is the one exception — there Esc cancels the selection and the search highlights and stays in the document, and Shift-Esc is the hop out (part 4.1) Alt-n new terminal below the active one Alt-c move the active pane into a fresh column — any pane, not just a terminal, and a no-op if it is alone in its column or the sixth column already exists (No practice blocks: these are viewport/layout, not text edits.) ----------------------------------------------------------------- = 3.13 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; the keys you have typed so far show on the active pane's transient body/message row until it fires. Esc abandons it — so does any key that leads nowhere, rather than leaving the next keystroke armed. The groups are shared first letters and nothing cleverer: f files, h docs, c columns, t toggles, a animations, s session, j jumps, l language, w windows. SPC ? Help: every builtin and the keys that run it SPC k Kill (quit) SPC d Del (close this pane) SPC f s Save SPC f n New (empty temp file) SPC f f Find (file NAMES) SPC f g Grep (their CONTENTS) SPC f c Config: where the startup file is read from (4.3) SPC h t Tutor (this file) SPC c n / c d Newcol / Delcol SPC t d Debug SPC t c Colors (syntax/ansi recolor) SPC t n NextColor SPC t t ThemeSel (the list of 228) SPC t r/R/g Crt / Ripple / Glitch (gui and macOS only) SPC t f FontSel (gui and macOS only) SPC a s/z/d/a/v PanelSlide / PanelZoom / PanelDissolve / PanelAscii / PanelVertical SPC a e/f/w PanelEdges / PanelFall / PanelWave SPC a c/r/t PanelCurtain / PanelScramble / PanelType — glyphs marching in, churning into place, and typed in reading order (native tty, SDL GUI, and macOS; press again to turn off) SPC t w Wrap: soft line breaks in a file pane, ON to start with — so this is the switch that lets a long line run off the edge again, hscroll and all. A wrapped row ends in "↩", in the chrome's colour, because a break is the one thing about it you cannot see SPC t b Tagbottom: taglines under their panes, not over SPC t p/l/a Petscii / Palette / Ascii — an image pane's renderer (4.1) SPC t i/s/z PdfTint / PdfSections / PdfFit — a PDF's (4.1) SPC y / SPC Y the selection to the SYSTEM clipboard (every cursor's text joined, or the primary's alone) SPC p / SPC P paste the system clipboard after / before the selection; SPC R replaces the selection with it SPC s d / s r Dump / Restore the session SPC w h/j/k/l Left/Down/Up/Right: focus the pane that way SPC j o / j i Back / Forward: walk the jump history SPC j j Last: the pane you were in before this one — what body-normal ESC runs, so it alternates SPC j l Jumplist: that same stack, as a buffer you can click SPC l ... the language group — part 4.2 has all ten Those five clipboard words are helix's own letters, and the only words in pardes that touch the desktop's clipboard at all: ordinary y d c p P R and the 1-2 / 1-3 chords stay in pardes' own register, so deleting a character can never throw away what you copied from a browser. In a terminal the copy goes out as OSC 52 and the paste asks for it back the same way — plenty of terminals refuse that read, so there SPC y works and SPC p may honestly do nothing. A handful of builtins have no key path at all. Three of them take a NAME, and a key path can never carry one: `Theme `, `Font ` and `Shell ` — the last being what the NEXT terminal you open will run (fish, until you say otherwise; the ones already open keep what they have). The other two are Look and Exec, which already have two keys and two buttons between them. All five are words: write them anywhere and execute them. `?` works at ANY depth: SPC ? lists everything, SPC t ? lists only what the "t" group holds. Help writes into a "+Help" OUTPUT BUFFER, the same kind of pane "/" search results land in — so it opens below the pane you asked from, never in a column of its own — and ordinary text, so the names in it are live: middle-click "Tutor" and it opens. (No practice blocks: these run builtins, not text edits.) ================================================================= = PART 4 — THE REST = ================================================================= ----------------------------------------------------------------- = 4.1 PANES THAT ARE NOT TEXT: PDFs and images = ----------------------------------------------------------------- Look at a .pdf and you get a PDF pane: the real document, rendered by MuPDF as one continuous strip of pages and placed like any other document (a leftmost column of its own the first time, split below a doc after that). Its tag is live, and reads pdf 3/128 width PdfFit filtered PdfTint PdfSections /path/to/it.pdf — the page you are on out of the count, then each of the three commands with its CURRENT setting written in front of it, then the path. Those are ordinary words: middle-click "PdfSections" and it runs. PdfFit (SPC t z) `width`, a reading column you scroll down, or `height`, the whole page at once panned sideways PdfTint (SPC t i) disabled -> filtered -> full -> disabled. `full` repaints the page in the theme's two colours; `filtered` starts from that and adds back three quarters of each channel's distance from the source's own luminance, so a COLOUR page keeps its hues and a grayscale scan comes out identical to `full`; `disabled` is the pixels as they are PdfSections (SPC t s) the document's OUTLINE, into a "+PdfSections" buffer below the PDF. An ordinary row is `file.pdf:PAGE:N` and the title, indented by depth; n/N walk them and Enter goes to that section. An entry that is a web LINK has no page to name, so its row is the bare URL and the title, and looking it opens a browser. The modal keys work on the PAGE rather than on a cursor: j/k scroll, Ctrl-d/u and Ctrl-f/b page, `Ngg` goes to page N, `ge` is the last page. `/` searches the document's real text through MuPDF and n/N walk the hits. Esc is the document's own CANCEL — it drops the selection and the search highlights and leaves you where you were reading — so the hop to the pane before this one is Shift-Esc here, the same chord that leaves a raw tty. The rest depends on the shell being able to place real PIXELS. Where it can, drag with the left button to select text (SPC y takes it), and h/l pan sideways with `0` and `$` for the edges — though there is nothing to pan while the page already fits, which in the default `width` fit is most of the time. Where it CANNOT, there is no text selection at all and the vertical keys step whole pages instead of scrolling. PDF support is a BUILD option: `-Dmupdf` is on for the native builds and off for the browser, and with it off a .pdf opens as the ordinary file it is on disk. `-Djpx` (also on) is what makes a SCANNED pdf render at all rather than as a stack of blank pages. An IMAGE — .png .jpg .jpeg .gif .bmp .ppm .pgm .tga — opens an image pane. Its tag reports `petscii:on/off`, `palette:commodore/terminal`, and `ascii:on/off` before the path, so every pane-local choice is visible even though only the builtins mutate it. Where the shell can place real pixels it does; where it cannot, the petscii matcher draws the picture out of block glyphs. The three toggles all act on THAT matcher, so with real pixels on screen only the first of them changes anything: Petscii (SPC t p) glyph art even when real pixels were available Palette (SPC t l) the C64's sixteen colours, or the terminal's own Ascii (SPC t a) whether the printable ASCII bitmaps join the matcher's glyph set. They start IN, so the first press takes them out and leaves the blocks ----------------------------------------------------------------- = 4.2 THE LANGUAGE BACKEND = ----------------------------------------------------------------- Pardes has ZLS compiled INTO it — not spawned, not spoken to over a socket, just called. So there is no server to start and nothing to wait for, and equally nothing kept warm: there is no cache at all, and every query re-parses from scratch — that is once per keypress, not once per file. It reads one language, `.zig`. On a file it does not read, the code queries below do nothing whatsoever: no error row, no message. (Four of them answer anyway, because they are not about this file's code — `SPC l i` and `SPC l w` describe the backend itself, and `SPC l S` and `SPC l D` walk the .zig files next door.) helix's five gotos keep helix's own letters, under `g`: gd definition gD declaration gy type definition gi implementation gr references Ctrl + left click the same as gd, with the mouse ONE answer jumps straight there. Several fill a "+Search" buffer, and from there it is the ordinary walk: n/N step the rows, Enter opens the one you stopped on. The rest are BUILTINS — words you can middle-click as readily as press — under `SPC l`, each on helix's letter with the group prefix in front of it: SPC l k Hover what this thing is, into "+Hover" SPC l r Rename arms the tag; the new name goes after the `/`. One undo for all of it SPC l a CodeAction the titles of what could be done, into "+Lsp" SPC l h SelectRefs every reference, as rows SPC l s Symbols this file's symbols, parent-qualified SPC l S WsSymbols arms the tag; a substring match over the .zig files under the asking file's OWN directory SPC l d Diagnostics this file's; a clean file says so in one row SPC l D WsDiagnostics the same over that same directory SPC l i Lspinfo what the backend IS: its version, what it answers, what it refuses, and the last two dozen queries with how long each took SPC l w Lspwhy re-runs `gd` where the cursor is now, out loud: every step it took and where it stopped ]d / [d step the diagnostics — asking for them if none are up ]D / [D the last / the first = format: a "+Lsp" buffer with one row per changed line, `- old + new` on each. It SHOWS you the formatting; it does not apply it Three limits worth knowing before you trust an answer. `gr`, `SelectRefs` and `Rename` are all the same resolution underneath, and it is THIS FILE ONLY — none of them claims a workspace. The two `S`/`D` walks start at the asking file's directory, not at the project root, and stop at 512 files. And a rename you did not want is one `u` away, because it lands as a single undo. And in INSERT mode, Tab straight after a `.` asks what could go there. What comes back is not a popup and nothing is typed for you: it is a buffer of rows, one per candidate, each pointing at where that candidate is DECLARED. Picking one is looking at it. An empty answer indents instead, a moment late. ----------------------------------------------------------------- = 4.3 CHROME, AND THE ONE FILE PARDES READS = ----------------------------------------------------------------- Themes: 228 of them, described in Part 1. NextColor steps the ring, `Theme ` goes straight to one, ThemeSel (SPC t t) lists them all into a buffer you walk. A native build can also wear one complete ZON file at runtime: `ThemeFile themes/mine.zon` resolves from the pardes config directory and, where document watching is available, reloads after every valid save. A broken save leaves the last good colors on screen. `DumpThemes` writes copyable references under themes/builtin; copy one out, rename it and edit that rather than guessing the fields. Fonts, on the SDL and macOS builds only — in a terminal the font belongs to the terminal and in a browser to the page. FontSel (SPC t f) lists every MONOSPACE face on the machine into a "+Fonts" buffer of `Font ` rows. n/N step those rows and select each one WHOLE, since a command line has no place inside it to pick out; Tab (or a middle click) runs the row you stopped on and puts that face on. So walking with n and pressing Tab is trying faces on, and stopping is choosing. `Font ` on its own takes the font file's stem. `TaglineSize 70` changes the tagline face and visible band to 70 percent of the body face, live; values 1 through 100 are accepted, `Config` reports the active value, and both native GUI renderers retain the same body cell grid while drawing that separate role. Native panel transitions are the five SPC a choices listed in 3.13. They start off. Only one is selected; running it again turns it off. Slide and zoom animate geometry. Dissolve switches changed cells at stable thresholds. ASCII increments or decrements each changed printable byte toward its new value, using fast ease-out skips for long distances and exact steps nearby; unchanged glyphs and non-ASCII graphemes pass through. Vertical raises a pane being added and drops a deleted pane back down inside its own box; every survivor stays still. The native GUI scene switches Crt, Ripple and Glitch may be combined. `EffectCode PanelAscii` (or any other effect builtin as its argument) opens the exact backend sources that were embedded when this binary was built. Before its first frame a native pardes reads ONE file: `init` inside its `pardes` config directory — $XDG_CONFIG_HOME/pardes/init when that is set to an absolute path, else ~/.config/pardes/init, and on macOS ~/Library/Application Support/pardes/init. `SPC f c` opens a refreshable "+Config" buffer containing the resolved path and every live setting: theme, shell, requested/effective font, panel/scene effects, hover delay, and the ordinary display toggles. The path is printed whether or not anything is there, which is the case you ask in. (A right click on that row opens the file when there IS one. When there is not, the click has nothing to open and falls back to searching for the text — pardes will not create it for you.) Resting the pointer over look-able text for that reported delay paints a quiet preview of the exact operand a right click would expand. It is only a preview: it does not focus, select, move a cursor, or run Look. Set `look_preview_delay_frames` to null in src/config.zig to compile it out. The format is one BUILTIN COMMAND per line, spelled exactly the way you would execute it anywhere else: Theme gruvbox_dark_hard ThemeFile themes/mine.zon Shell zsh TaglineSize 70 Tagbottom Blank, unknown or malformed lines are ignored in silence, and a bad line does not stop the ones after it. Two cautions. A word that is not a builtin is never handed to a shell — but `Exec` IS a builtin, so `Exec ` in this file types that command into a shell before your first frame, and `Look` will run an `` @`...` `` word the same way. And every toggle here TOGGLES: `Wrap` in your config turns soft wrap OFF, because it starts on. It cannot remap a KEY, either: the keymap is src/config.zig, compiled in, and rebinding is an edit there and a rebuild — which is why a wrong binding is a compile error instead of a line that quietly did nothing. The browser build reads no such file, has no ptys, and has no language backend compiled in. It is not without a filesystem, though: LOOK there resolves against a read-only archive of working-tree .zig sources that was generated when the build was made. What you get is a dump, replayed, with those sources openable. (The core still ASKS for the rest. A host that answers gets terminals; that is the seam, not a special case.) ================================================================= = PART 5 — THE CONTROL FILESYSTEM (pardes --fs) = ================================================================= Started as `pardes --fs`, this session serves plan9 acme's control files over FUSE: a directory per pane, holding `body`, `tag`, `ctl`, `addr`, `data`, `event` and the rest, plus `index`, `cons` and `new/` at the top. Every pane shell gets `$PARDES_FS` (where it is mounted) and `$PARDES_PANE` (which pane it is), so a script run in a pane can talk to its own window with no arguments: echo hello >> $PARDES_FS/$PARDES_PANE/body append to this pane cat $PARDES_FS/index one line per pane echo hi > $PARDES_FS/new/body open a pane holding "hi" That IS the extension mechanism: no plugin API, no interpreter, no rebuild — a program that opens files. Two things about it are worth knowing before you use it. 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 the words in that pane's tag can be the program's own commands (examples/acmefs/life.py puts Step/Run/Clear there). The keyboard is not suppressed, so the pane stays editable either way, and when the program exits the clicks come back. 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 lives under $XDG_RUNTIME_DIR at 0700 and why `--fs` is opt-in. examples/README.md is the client's guide; docs/acme-fs.md compares this implementation with acme's, file by file. ================================================================= = SUMMARY = ================================================================= MOUSE (acme): L select/focus+pin M execute R look(open) select-then-act: middle-drag, or v/x then Tab chords: 1-2 cut 1-3 paste (both in one hold = snarf) 2-1 = middle-exec with the left selection as argument every tag with text leads with Save (a terminal also has Filter); images and PDFs show "New Del" top bar = New Newcol Find Grep Help Tutor Dump ... Debug Kill (keyboard: k off the topmost tagline) WEB TOUCH: one-finger tap = LOOK; drag = natural scroll LOOK opens URLs or embedded working-tree .zig source (read-only) published TTY dump lists every source; tree-sitter colors it tag/separator start = left drag; touch HUD requires Debug TTY: a terminal IS a pane; Ctrl-b (or Shift-Esc) toggles the shell, landing its cursor where you navigated (prompt start if the input is empty, mid-text otherwise) Filter in its tag toggles a pane-local, theme-keyed palette --fs: acme's control files over FUSE: $PARDES_FS//{body,tag, ctl,addr,data,event,...} and $PARDES_FS/{index,cons,new} a script holding a pane's `event` open owns that pane's middle and right clicks; writing a record back runs it KEYS (helix): h j k l w b e 0 $ ^ gg ge f F t T v x X d c y p i a I A o O u undo U redo Enter=look Tab=execute w b e f F t T SELECT what they cross, so `i` after one types at the SELECTION's start; `;` collapses first a leading NUMBER is a count on the 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, one cursor per match, previewing as you type; C , ( ) Alt-s Alt-- Alt-_ _ work the LIST m i/a = textobjects (w W p, the bracket pairs and the three quotes), m s/r/d = surround, mm = match ] and [ = paragraph, blank line, diagnostic | = filter every selection through /bin/sh -c (a savable file pane only) : = the tag as a command line (arrows/words move in it, hjkl walk to the neighbouring TAGLINE, y/Enter act on the selection; the mode+path selects but never edits; k off the TOPMOST tagline = the top bar, j back down) zt zz zb zj zk Ctrl-d/u Ctrl-f/b Ctrl-o/i Ctrl-w hjkl Alt-n Alt-c Esc = the pane you were in before this one (body normal; in a PDF it cancels that document's own selection and search highlights instead, and Shift-Esc hops) n/N = select the next/prev look-able text, across panes (a ring); Enter opens what you landed on SPC = the leader: a key path runs a builtin (SPC ? lists them; 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 hjkl focus, SPC j j the pane before this one, SPC t * the toggles, SPC l * the language backend) NOT TEXT: .pdf = the real document through MuPDF — j/k scrolls it, `/` searches it, Esc cancels selection+highlights and Shift-Esc leaves the pane, 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 running in the background and nothing cached: gd gD gy gi gr, the ten SPC l words, ]d/[d and ]D/[D, `=` to see a format diff, and Tab after a dot in insert vs Helix: selection is LINE-first (x), plus v for characters; `/` is a case-insensitive SUBSTRING search, one hit per line, and not a regex one — the regex lives on s and S; the textobjects are w W p, the bracket pairs and the three quotes, with none of helix's tree-sitter ones. vs Vim: no verb+noun (dw) — because the MOTION already selected, so `wd` is what `dw` was; body-normal Esc hops focus; bare G is a no-op and `ge` goes to the start of the last line. vs both: a terminal is just a pane; the same keys edit text and navigate the shell screen, and the tty toggle reuses the shell's own prompt to position you precisely. To spawn THIS tutor again from anywhere: middle-click "Tutor" in the top bar (next to Help), or press SPC h t. Quit the tutor: this is a file pane — `:q` isn't wired; close the window (middle-click "Del" in its tag), "Kill" (top bar) to quit everything, or Ctrl-c the app.