summaryrefslogtreecommitdiff
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
commitfb3f5ab5a7758a628a6e343f24a6510a58802599 (patch)
tree8387bf3c7fd4561da29681418ffb4e595e3b3cac
parente61abc001db6902807f8241465c113c834ecf86b (diff)
downloadpardes-fb3f5ab5a7758a628a6e343f24a6510a58802599.tar.gz
pardes-fb3f5ab5a7758a628a6e343f24a6510a58802599.zip
The guide walks a newcomer through pardes day to day, and the cheatsheet says what Esc and Shift-Esc really do
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--docs/helix-keys.md8
-rw-r--r--docs/lsp.md73
-rw-r--r--docs/tags.md222
-rw-r--r--docs/typ/cheatsheet.typ14
-rw-r--r--docs/typ/guide.typ303
-rw-r--r--docs/ui-review.md4
-rw-r--r--test/snapshots/find.snap2
-rw-r--r--test/snapshots/lspcomplete.snap2
8 files changed, 317 insertions, 311 deletions
diff --git a/docs/helix-keys.md b/docs/helix-keys.md
index b1035ad6..6249fc15 100644
--- a/docs/helix-keys.md
+++ b/docs/helix-keys.md
@@ -126,8 +126,8 @@ language-backend queries, and the shell pipe.
| `Esc` (insert) | back to normal mode, cursor right after the insertion (no vim left-step) | | helix-verified |
| `Enter` (normal) | acme **look** chord: EXPLICIT selection, else file-ish word under cursor | pardes-specific, keep (helix normal-mode Enter unbound). Covers helix `gf`. Implicit motion residue falls back to the cursor word | pardes-specific |
| `Tab` (normal) | acme **execute** chord | pardes-specific, keep; explicit-selection rule as Enter | pardes-specific |
-| `:` (normal, body or tag) | in the body, focuses the pane's tag in normal mode at its remembered cursor (the first time, on `Save`); in the tag, goes back to the body; in a column or workspace tag, back to the active pane. The tag's normal and insert modes ARE the body's: every motion, selection, edit and undo works there, and `0` goes to the line's start, the path's. Tab runs the word under the cursor or the selection and Enter looks it up (in a column or workspace tag Enter runs it too), and either hands the keyboard back to the body first. Clicks choose a new cursor position and type into the tag. | Each tag keeps its own cursor during the session. The computed path/marker/page is reachable and yankable but read-only: an edit into it is refused, and typing into a file's path drafts a new name. File-name changes are staged as described in [editable tags](tags.md). | pardes-specific |
-| `Ctrl-w k` / `SPC w k` (pane with nothing above) | focuses its column's tag, then the workspace tag. `Ctrl-w j` walks back to the panes, `Ctrl-w h`/`l` walk the column tags. Headers edit exactly as a pane tag does. | Column commands target that column's active pane, or its first pane when coming from elsewhere. Workspace and column text are independently editable and persist in dumps. See [editable tags](tags.md). | pardes-specific |
+| `:` (normal, body or tag) | in the body, focuses the pane's tag in normal mode at its remembered cursor (the first time, on `Save`); in the tag, goes back to the body; in a column or workspace tag, back to the active pane. The tag's normal and insert modes ARE the body's: every motion, selection, edit and undo works there, and `0` goes to the line's start, the path's. Tab runs the word under the cursor or the selection and Enter looks it up (in a column or workspace tag Enter runs it too), and either hands the keyboard back to the body first. Clicks choose a new cursor position and type into the tag. | Each tag keeps its own cursor during the session. The computed path/marker/page is reachable and yankable but read-only: an edit into it is refused, and typing into a file's path drafts a new name. File-name changes are staged as described in [editable tags](typ/guide.typ). | pardes-specific |
+| `Ctrl-w k` / `SPC w k` (pane with nothing above) | focuses its column's tag, then the workspace tag. `Ctrl-w j` walks back to the panes, `Ctrl-w h`/`l` walk the column tags. Headers edit exactly as a pane tag does. | Column commands target that column's active pane, or its first pane when coming from elsewhere. Workspace and column text are independently editable and persist in dumps. See [editable tags](typ/guide.typ). | pardes-specific |
| `Ctrl-w` + `h/j/k/l`/arrows | directional pane focus prefix — editor normal mode only | pardes' own window handling (helix window mode skipped, section C). Runs the SAME `Left`/`Down`/`Up`/`Right` builtins `SPC w h/j/k/l` runs; Raw **tty** mode forwards Ctrl-w to the child. Insert mode owns `Ctrl-w` = delete-word-back, so a tag being TYPED into swallows it; from a tag in normal mode it moves focus to the neighbour's BODY, as from a body, and from the top pane `Up` reaches the column and workspace tags | pardes-specific |
| `Alt-n` | new terminal below (outside raw tty) | shadows helix `Alt-n` (select next sibling), which pardes spells `Alt-right` alone | pardes-specific |
| `Alt-c` | move active terminal to a fresh column (outside raw tty) | helix `Alt-c` is change-noyank; the pardes window op wins (do-not-touch contract). `Alt-d` + `i` covers the behavior | waived (`alt-c-window-op`) |
@@ -135,7 +135,7 @@ language-backend queries, and the shell pipe.
| `SPC y` `SPC Y` `SPC p` `SPC P` `SPC R` | helix's clipboard menu on helix's own letters: yank the selection to the system clipboard (`ClipYank`) or the PRIMARY selection alone (`ClipYankMain`), paste the system clipboard after (`ClipPaste`) / before (`ClipPasteBefore`) the selection, replace the selection with it (`ClipReplace`) | the same as `"+y`, `"+p`, `"+P`, `"+R`: register `+` (and `*`) IS the desktop's clipboard, the only register that reaches it. `SPC y` writes `+` alone, as helix's does, not the default register too. Builtins rather than bare chords because a leader path names a builtin: they land in Help's index and are executable words like every other verb. The clipboard holds one text, so N values go out joined by newlines, as helix sends them. On a tty the write is OSC 52 out and the READ is OSC 52 back, which many terminals refuse or gate — so `SPC y` works there and `SPC p` can be a no-op | out of corpus |
| a paste from the OUTER terminal | one `Event.paste`, spliced in at the cursor | the tty shell enables bracketed paste and coalesces `paste_start`..`paste_end` into a single event; before that the bytes arrived as individual key presses and normal mode RAN them, which is how a pasted `d` deleted a line. The bytes deliberately never enter the yank register — clipboard and default register are separate stores in both directions | pardes-specific |
| `/` (any pane) | pardes' own plain-substring search into a `+Search` output buffer: the pattern is typed on a line of its own on the pane's notice band, Enter fills the buffer, and its rows are ordinary look targets. Enter also GOES to the first row — the buffer is focused and then the step `n` is and the look Enter is run in it (`Pardes.lookFirstHit`), so `/foo` lands on the first hit with the matched span selected. A pattern that matched nothing opens its empty buffer and moves nothing | KEEP, do not touch; not in the corpus (helix `/` is regex search). Find and Grep answer with OTHER files and deliberately do NOT jump. The stepping half is the next row | pardes-specific |
-| `n` / `N` (any pane) | MOTION, not a jump: move the SELECTION to the next / previous look-able text and open NOTHING. Enter — the look chord — on what it leaves selected is what opens it | KEEP, do not touch; not in the corpus (helix's `n`/`N` walk regex search hits). What a step selects is the pane's GRAIN (`output_pane.Grain`, read in `Pardes.lookSpanIn`): in FREE TEXT — a terminal, a file, a PDF, a prose answer buffer — the largest whitespace-delimited run `look.resolve` can act on (`look.lookableSpan`, wrapper punctuation peeled off both ends), several to a line; in a RESULTS BUFFER one stop per ROW, the largest run its head resolves as (`look.lookableLineSpan`), because a row there IS one location and the words after it are the match rather than a second place to go; in a COMMAND list the whole line. The walk is a RING across PANES: every pane that has performed a Look, most recent first (`Pardes.look_src`), then the output buffers that have not, newest first, and only when both are empty the active pane. Exhausting a pane enters the next at its first (forward) / last (backward) span and the end wraps to the start, so `N` is the exact inverse of `n`. What it lands on becomes an EXPLICIT `vsel` with the cursor on its FIRST column, in the pane the walk focuses. ONE motion in every pane kind and every buffer kind — a PDF steps the `+Search` buffer its own search filled, `n` to select the row and Enter to jump. The single thing a buffer may change is that grain, and it changes it by BEING a kind of buffer rather than by a branch: `output_pane.Traits.steps` (a list of locations) makes a step take one row at a time, and `Traits.commands` makes it take the WHOLE LINE, because a command list (`Themes`/`Fonts`) holds words to run and there is no path inside `Theme gruvbox` to pick out. Tab on what `n` selected wears the theme, which is the same middle click on the row is. `]d`/`[d` are helix's diagnostic motions, a different binding, and they do still jump to each diagnostic (`docs/lsp.md`) | pardes-specific |
+| `n` / `N` (any pane) | MOTION, not a jump: move the SELECTION to the next / previous look-able text and open NOTHING. Enter — the look chord — on what it leaves selected is what opens it | KEEP, do not touch; not in the corpus (helix's `n`/`N` walk regex search hits). What a step selects is the pane's GRAIN (`output_pane.Grain`, read in `Pardes.lookSpanIn`): in FREE TEXT — a terminal, a file, a PDF, a prose answer buffer — the largest whitespace-delimited run `look.resolve` can act on (`look.lookableSpan`, wrapper punctuation peeled off both ends), several to a line; in a RESULTS BUFFER one stop per ROW, the largest run its head resolves as (`look.lookableLineSpan`), because a row there IS one location and the words after it are the match rather than a second place to go; in a COMMAND list the whole line. The walk is a RING across PANES: every pane that has performed a Look, most recent first (`Pardes.look_src`), then the output buffers that have not, newest first, and only when both are empty the active pane. Exhausting a pane enters the next at its first (forward) / last (backward) span and the end wraps to the start, so `N` is the exact inverse of `n`. What it lands on becomes an EXPLICIT `vsel` with the cursor on its FIRST column, in the pane the walk focuses. ONE motion in every pane kind and every buffer kind — a PDF steps the `+Search` buffer its own search filled, `n` to select the row and Enter to jump. The single thing a buffer may change is that grain, and it changes it by BEING a kind of buffer rather than by a branch: `output_pane.Traits.steps` (a list of locations) makes a step take one row at a time, and `Traits.commands` makes it take the WHOLE LINE, because a command list (`Themes`/`Fonts`) holds words to run and there is no path inside `Theme gruvbox` to pick out. Tab on what `n` selected wears the theme, which is the same middle click on the row is. `]d`/`[d` are helix's diagnostic motions, a different binding, and they do still jump to each diagnostic (`docs/typ/guide.typ`, Keys) | pardes-specific |
| insert: printable text | file: real edit; terminal: typed run splice | | helix-verified (file) |
| insert: `Enter` | newline; keeps the current full indent levels and adds one 4-space logical tab when the text before the cursor ends in `(`, `[`, `{`, or `)` (including `})`) | plain lines match `insert_newline`; delimiter heuristic is pardes-specific | helix-verified (plain) / pardes-specific (delimiter) |
| insert: `Backspace` (+ `Shift-Backspace`) | delete prev char, joins lines at col 0; in a line's leading blanks, back to the previous 4-column indent stop (a whole unit when on one; a tab still goes alone) | matches `delete_char_backward` and its dedent; `Ctrl-h` alias in B | helix-verified |
@@ -271,7 +271,7 @@ compiles a runtime pattern with no allocator, called the way sam searches
haystack, so `^` and `$` match at every line's start and end and `.` never
crosses a newline, and a pattern naming `\n` runs over the whole selection.
Where helix's `^` is only the selection's start, this is sam's. mvzr
-backtracks without bound of its own, so the step budget docs/fs.md gives
+backtracks without bound of its own, so the step budget docs/typ/reference.typ gives
for `addr` holds here too: a search that runs out of it (about 300 ms)
keeps the matches it found so far.
diff --git a/docs/lsp.md b/docs/lsp.md
deleted file mode 100644
index 22cf1b90..00000000
--- a/docs/lsp.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# Language intelligence
-
-Native hosts, including detached sessions, run language queries on workers.
-Every language, Zig included, goes through the protocol client in
-`src/lsp/lsp_client.zig`, which runs its server as a child process: Zig's is
-`zls`, which must be on PATH. Web and board builds have no language backend.
-
-`Lspinfo` (`SPC l i`) reports backend versions, server state, capabilities,
-recent timings and errors. `Lspwhy` (`SPC l w`) traces resolution at the cursor.
-
-## Commands
-
-| Keys | Action |
-|---|---|
-| `gd`, `gD`, `gy`, `gi`, `gr` | Definition, declaration, type, implementation, references |
-| `SPC l k` | Hover |
-| `SPC l r` | Rename |
-| `SPC l a` | Code action |
-| `SPC l h` | Select references |
-| `SPC l s`, `SPC l S` | Document and workspace symbols |
-| `SPC l d`, `SPC l D` | Document and workspace diagnostics |
-| `]d`, `[d`, `]D`, `[D` | Next, previous, last and first diagnostic |
-| `=` | Format |
-| Ctrl-click | Definition |
-| Insert-mode Tab after `.` | Candidate declarations |
-| `SPC l c`, `SPC l C` | Incoming and outgoing calls |
-| `SPC l t`, `SPC l T` | Supertypes and subtypes |
-
-A single goto result jumps directly; several results open a reusable search
-buffer. `n`/`N` select result rows and Enter opens them. Hover opens prose.
-Locations use one-based `path:line:column` or `path:line:column-endcolumn`.
-Paths below the requesting pane's directory are relative to that directory;
-other paths stay absolute.
-
-Completion lists declarations and inserts nothing. An unanswered Tab indents
-only if the cursor has not moved since the request. Multicursor Tab indents
-without starting a query.
-
-Formatting and same-file rename apply as one undo transaction. A workspace
-rename spanning other files opens a preview; it does not partially apply the
-rename.
-
-## Configuration and testing
-
-`PARDES_LSP_ZIG`, `_RS`, `_C`, `_GO`, `_TS` and `_PY` override the server
-executable for each language. An empty value disables that server. `Lspinfo`
-shows the effective settings.
-
-The protocol client keeps one child server per configured language, negotiates
-position encoding, and handles both push and pull diagnostics. Startup and
-request waits have deadlines; failed starts back off before retrying. Worker
-status messages reach the host event queue.
-
-`zig build lspbench -- probe gd <file> <line>:<column>` queries the same backend from
-the command line. `zig build lspbench` measures it. The Zig snapshot tests
-(lsp, lspcomplete, lspdebug and the rest) run the real `zls`; the protocol
-ones use a Zig mock server with deterministic answers while exercising the real client,
-including process startup, framing, edits and undo. `zig build fs-test` also
-checks language-worker delivery in TTY and detached sessions over 9P.
-
-## Ownership
-
-`host_io.Lsp.Job` owns copies of the source, path, argument and root. Workers
-never read the live core. A request ID and pane serial reject obsolete replies;
-mutating replies also require the original file revision. Restore cancels
-owned work before replacing the core.
-
-Backends implement `query`, `speaks` and `supports` in `src/lsp/`. The first
-backend supporting the file and query answers; status queries visit all of
-them. Results are written to the caller's writer. Use `lsp.row` or `spanRow`
-for locations, `edit` for rename ranges, and `put` for replacement text.
-Edit records use half-open byte offsets into the request's source snapshot.
-The core validates the complete response before applying it.
diff --git a/docs/tags.md b/docs/tags.md
deleted file mode 100644
index 5d5bfab2..00000000
--- a/docs/tags.md
+++ /dev/null
@@ -1,222 +0,0 @@
-# Tags and columns
-
-Pardes has three levels of command text: the workspace tag, a tag per
-column, and each pane's tag. The rule for the workspace and column tags:
-
-- A builtin that acts on a pane (`Del`, `Save`, `Undo`) acts on the
- column's active pane (its first pane when focus comes from another
- column); from the workspace tag, on the pane with the keyboard.
-- A column's own words (`New`, `Tty`, `Delcol`) act on that column.
-- A shell command runs as a command pane of its own, with `run` and `exit`
- records, in the session's directory (where pardes started), as acme's row
- and column tags have none of their own. It is never typed into the
- terminal that has the keyboard. `Tty` starts there too.
-- Over 9P (`/tagexec`, `col/<n>/exec`) there is no click to say which pane
- is meant, so a pane's word written there is refused, pointing at
- `pane/<n>/ctl`; the tag's own words and shell commands run as above.
-
-One run from a pane's tag or text starts in that pane's directory, and a
-shell command there is typed into the pane when it is a terminal at its
-prompt. Over 9P the tags are `/tag`, `/col/<n>/tag` and
-`/pane/<n>/tag` ([fs.md](fs.md#columns-and-tags)).
-
-## Default words
-
-- Workspace: `Newcol Joincol Find Grep Help Changelog Tutor Dump Themes
- Config Debug Exit`.
-- Column: `New Tty Find Grep Joincol Delcol`.
-- File pane: `Save Tty Collapse Del`; a source file with a grammar adds
- `TreeContext`, a result list `LocationsConfig`. PDF: `manual.pdf [1/12]
- Tty PdfSections PdfTint Collapse Del`.
-- Terminal: `Tty+bash Save Mode Filter Collapse Del`. `Tty+bash` is one word
- for `Tty bash`, opening another terminal on that shell (only `Tty` reads a
- `+` so). `Mode` cycles raw terminal input, normal mode and insert mode.
-- Command pane: `<dir> (<line>) running`, then `exit N`, and `Kill`.
-
-`Undo`, `Redo` and `Mode` (on files) work typed or clicked though they are
-not in the default tags. Customized tags keep their text.
-
-A tag word runs as acme's does, in the pane's directory with no file named:
-`wc` alone waits on stdin. There is no `$%`; name the file (`wc notes.txt`),
-or select the name and middle-click `wc`, which takes a held selection as its
-argument.
-
-A program that tracks the mouse gets B1 and the wheel; B2 and B3 stay
-pardes's; Shift swaps each. In raw terminal input, a program that asked for
-the mouse (xterm's 1000, 1002 or 1003: htop, vim with `mouse=a`, codex)
-gets B1's clicks and drags and the wheel over its grid, reported in the
-format it chose and with its modifiers; Shift-B1 selects as ever and
-Shift-wheel scrolls pardes's scrollback. B2 and B3 execute and look there
-as in any pane, sweeps and chords included; Shift-B2 and Shift-B3 go to the
-program as its buttons 2 and 3, the Shift left out. A full-screen program
-that does not track the mouse gets the wheel as arrow keys (xterm's
-alternate scroll, 1007). Tags, grips and gutters stay pardes's.
-
-`Repl python` in a terminal's tag binds it as that language's REPL
-([fs.md](fs.md#repls)). `Repl` takes the languages a code fence names: ada,
-bash, c, c_sharp, clojure, cpp, css, elixir, erlang, fortran, go, haskell,
-html, java, javascript, json, kotlin, ocaml, markdown, pascal, php,
-powershell, python, ruby, rust, scala, typst, zig, and aliases such as `py`
-and `sh`.
-
-## Pane builtins
-
-`Collapse` folds a pane to its tagline, giving its rows to the nearest
-expanded pane above (else below); again, it takes them back. Its text and
-process are kept.
-
-`Del` closes a pane, giving its rows to one neighbour. `Del k` (`DelAbove`)
-gives them to the nearest expanded pane above, `Del j` (`DelBelow`) below,
-each falling back to the other side. Bare `Del` from the keyboard (`SPC d`),
-with expanded panes above and below, asks on the notice band (`k`/Up above,
-`j`/Down below, any other key keeps the pane); a click, a 9P write or an
-`init` line never asks and gives the rows above.
-
-A pane with unsaved text is refused once: a notice `1 unsaved pane — Del
-again to discard`, the pane listed in `+Unsaved`, and over 9P the write
-fails (EIO). The same `Del` again, nothing edited since, discards. `Delcol`
-refuses a column holding such a pane the same way, without opening
-`+Unsaved` or moving focus. `Exit` and `Restore` do the same over the whole
-session. A `+New` scratch under 100 bytes is never asked about.
-
-`Edit` runs sam's command language on the body ([fs.md](fs.md#edit)).
-`Undo` and `Redo` step the body through its last 256 edits, as `u` and `U`.
-
-Unsaved text shows on the pane's grip, as acme's modbutton, not in the tag.
-In a terminal the grip is two cells: the pane's mode (blank normal, `^`
-insert, `$` tty mode), then `*` while unsaved.
-
-## Reviewing diffs
-
-Open a `.diff` or `.patch` file, or run `git diff` (or `git show`, `diff -u
-old new`) as a command from any tag: once the command has finished, output
-that starts as a diff does (a `diff --git` line, or a `--- ` line with `+++
-` and `@@` under it) is shown as one. Each hunk's code is coloured in the language of the file its
-section names (`+++ b/<path>`, or `--- a/<path>` for a deleted file), its
-old side (context and removed lines) and new side (context and added lines)
-each parsed as one text, so a string or comment across lines colours as it
-does in the file. Added and removed rows are tinted to the pane's edge,
-their `+`/`-` in the tint's hue (the theme's ANSI green and red where it
-has them); a file in no language pardes knows keeps the plain line colours.
-Only the hunks in view are parsed, a few dozen lines at a time, each once.
-
-Then right-click to jump: a diff line looks up the path and line it
-names, as any look does. The hover shows the whole line; the look is of
-the address it names, exactly as if that text had been selected by hand
-and right-clicked, with the same resolution (the pane's directory, then
-the places the jumplist has been), placement and errors. On a `diff
---git`, `---` or `+++` line, anywhere on it, markers included, that is the
-file: `diff --git a/x b/y` and `+++` name the new one, `---` the old one
-unless the `+++` under it names another (a rename, a new file, `diff -u
-old/x new/x`), the old name being the one likely gone. A `@@ -a,b +c,d
-@@` line names `path:c`. On a hunk line, the `+`, `-` or space in its
-first column names `path:` its new line; for a removed line, the new line
-now standing where it was (the one after it, or the hunk's last when it
-went from the end of the file). The code after it is words, looked at as
-ever. A 9P `look` of a whole line of the diff is the same look.
-
-The path is the repository's name for the file. In a git section (one
-with a `diff --git` line) git's side prefix is dropped when both paths on
-that line carry one, different ones: `a/`/`b/`, or with
-`diff.mnemonicPrefix` `c/`, `i/`, `w/`, `o/`. `--no-prefix` writes none,
-so there `a/x a/x` is a real directory `a`, kept. A plain `diff -u`'s names
-are used as written, past the timestamp.
-
-## Editing tags
-
-A tag is text like a body, with the body's normal and insert modes and undo.
-A pane tag may hold several lines and wraps, taking a row per shown line up
-to eight and leaving its body at least one; a collapsed pane shows the first.
-Up on its first row in insert mode (or Alt-Up in either mode) folds a tag to
-one row, Down on its last (Alt-Down) unfolds it. Column and workspace tags are
-one line: a newline typed, pasted or written into one becomes a space.
-
-The path (and a PDF's page) at the start of a pane tag is computed and
-read-only; `0` goes to its start, and motions select and yank across it.
-Typing into a file's path, or clicking it, starts a draft of a new name:
-Enter or Tab confirms it, Escape or leaving the pane cancels. Confirming
-changes the buffer's save target and marks it unsaved; nothing on disk is
-renamed until `Save`. A pane-tag command confirms a valid draft first.
-
-Left-click a tag to type at the click, in insert mode. Esc is normal mode:
-there Tab executes the word under the cursor (or the selection); Enter looks
-it up in a pane's tag and runs it in a column or workspace tag. Executing
-gives the keyboard back to the body first. Paste goes to the focused tag.
-
-A look at a relative path is tried in this pane's directory, then in the
-directories of places you've visited (the jump list).
-
-`:` in a body's normal mode focuses its tag (the first time, on `Save`); `:`
-in a tag goes back. The window keys (`Ctrl-w`, `SPC w` with `h/j/k/l`) move
-between panes; up from the top pane reaches its column's tag, then the
-workspace's, and Left/Right walk the column tags. Search, `s`/`S`, pipe and
-Save's path prompt get a line on the pane's notice band; in a tag `s`, `S`
-and `|` act on the tag's own text, while `/` searches the body.
-
-## Moving and resizing
-
-Drag a pane's grip up or down its column and its top follows, the pane
-above giving or taking rows, down to its tag alone; drop it in another
-column and it moves there. In the GUI the 2 px rule between panes drags too.
-A column's right edge drags its width. Drag a column's grip past a
-neighbour's middle to move the whole column there; short of that it moves
-the column's left edge.
-
-A terminal keeps its tag and 2 body rows: no drag, squeeze or smaller
-window takes it below that, and `pty/ctl`'s `winsize` gives a pty 2 rows at
-least. A text pane can be dragged down to its tag.
-
-## Empty columns
-
-A column can hold no pane, as acme's can: its tag stands over blank space in
-the theme's `empty_col` colour. `Newcol` makes one right of the keyboard's
-and gives its tag the keyboard. Closing a column's last pane leaves the
-column empty, the keyboard on its tag. Only `Delcol` and `Joincol` take a
-column away; `Joincol` keeps the right column's tag, its panes below. A pane
-dragged onto an empty column fills it. `Delcol` of the last column leaves
-the workspace tag alone; `Newcol` or `New` starts again. Unlike acme, pardes
-quits when the session's last pane closes.
-
-## Where new panes go
-
-Every new pane goes through one placement, chosen by `Placement acme` (the
-default) or `Placement pardes` (bare flips it; `SPC c p`).
-
-No placement leaves a pane shorter than its tag and 2 body rows. Where the
-chosen place has not that room, the column's tallest pane is halved; where
-no one pane can give it but the column holds every pane's minimum with the
-new one's, the rows are shared out again; otherwise the pane is refused,
-`no space for a pane in that column: each keeps its tag and 2 rows` (ENOSPC
-over 9P). A pane alone in its column always fits.
-
-`Placement acme` is acme's makenewwindow. The active column is the one last
-typed or clicked in, dropped into, whose tag was given the keyboard, or that
-was given the last new pane; a Look moves the keyboard, not the active
-column. A new pane goes into the column whose tag the command came from,
-else the active column, never into a new column:
-
-- an empty column it takes whole;
-- from a tag, or 9P's `pane/new`, it takes the bottom half of the column's
- last pane;
-- from a pane's text (a Look, `Tty`, `Alt-n`, a Grep or Find listing), it
- goes under the text of the pane with the most blank rows when that is
- more than 15 rows, or more than 3 and more than half the biggest pane;
- otherwise it halves the biggest pane, or the asking pane when that is in
- the column and not much smaller;
-- `New` goes to the bottom half of its own column's last pane;
-- a command pane or `+Errors` pane goes to the last column's last pane (a
- command from a column's tag to that column, reusing a finished command
- pane only there). With no room anywhere, `+Errors` text is logged as `msg`
- records.
-
-`Placement pardes`: an empty column whose tag asked, or has the keyboard, is
-filled; a scratch goes under the pane that asked; a shell under it or the
-nearest pane with room; a document beside the last one read, or in a column
-of its own on the left when there is none and the column is at least 200
-cells wide; a command pane at the foot of the last column.
-
-## Saved workspaces
-
-`Dump` and `Restore` keep workspace and column tags (empty ones too) and
-empty columns ([config.md](config.md#dumps)). The column row stays above
-the panes with `Tagbottom` on; on screens under three rows it is left out.
diff --git a/docs/typ/cheatsheet.typ b/docs/typ/cheatsheet.typ
index d9c3f573..5069614a 100644
--- a/docs/typ/cheatsheet.typ
+++ b/docs/typ/cheatsheet.typ
@@ -4,8 +4,6 @@
// against the registry, the keymap and the default tags.
#import "style.typ": key, btn, chord, word, tag, addr, file, cmd, doc
-= pardes #h(1fr) _acme's tags and three buttons, helix's keys, terminals as panes, the editor as a 9P filesystem_
-
== The mouse
/ #btn("B1"): select, and focus the pane. On a tag: type there.
/ #btn("B2"): *execute* the word or selection: a builtin, else a shell line.
@@ -46,7 +44,7 @@ Typed anywhere, then #btn("B3") or #key("Enter"):
Addresses are sam's: `#n`, `/re/`, `?re?`, `$`, `a,b`. #doc("fs", section: "look-and-exec")
== Keys: normal mode
-Helix-style: motions *select*, then an edit acts on the selection. Mode box: blank normal, `^` insert, `$` tty.
+Helix-style: motions *select*, then an edit acts on the selection. Mode box: blank normal, `^` insert, `$` raw terminal.
#table(columns: 2,
[#key("h j k l") #key("w b e")], [move; words select],
[#key("g g") #key("g e")], [first line, last line],
@@ -71,9 +69,9 @@ Helix-style: motions *select*, then an edit acts on the selection. Mode box: bla
== Keys: panes
#table(columns: 2,
- [#key("Ctrl-w") #key("h j k l")], [focus a neighbour (anywhere)],
- [#key("Esc")], [normal mode: hop to the previous pane],
- [#key("Shift-Esc")], [leave, whatever Esc means here],
+ [#key("Ctrl-w") #key("h j k l")], [focus a neighbour (not in insert or raw `$`)],
+ [#key("Esc")], [back to the previous pane (from raw `$` only at an empty shell prompt)],
+ [#key("Shift-Esc")], [back to the previous pane from raw `$` or a PDF; in a terminal's normal mode, into raw],
[#key("Ctrl-o") #key("Ctrl-i")], [jump history back, forward],
[#key("Alt-n")], [new terminal below],
[#key("Alt-c")], [move the pane to a new column],
@@ -97,8 +95,8 @@ Helix-style: motions *select*, then an edit acts on the selection. Mode box: bla
)
== Terminals and commands
-- A terminal is a pane. #key("Ctrl-b") toggles raw input (`$`) and editor mode, where the same keys move over its text, prompts hidden.
-- Raw: keys go to the program; #key("Esc") at a shell prompt hops away, #key("Shift-Esc") always.
+- A terminal is a pane. #key("Ctrl-b") switches it between raw (`$`) and normal mode, where the same keys move over its text, prompts hidden.
+- Raw: keys go to the program; #key("Esc") at an empty shell prompt goes back to the previous pane, #key("Shift-Esc") always.
- #word("Mode") cycles raw, normal, insert. #word("Filter") maps its colours to the theme. #word("Tty+bash") opens another on that shell.
- #btn("B2") on any non-builtin line (`make`, `git log`) runs it with #word("Shell") `-c` in the pane's directory, in a *command pane*: #tag("Kill Save Collapse Del", path: "/home/me/src (make) exit 0")
- #word("Kill") stops what pardes started (`Kill make`: those lines); #word("Exit") quits.
diff --git a/docs/typ/guide.typ b/docs/typ/guide.typ
new file mode 100644
index 00000000..080ed5ea
--- /dev/null
+++ b/docs/typ/guide.typ
@@ -0,0 +1,303 @@
+// The guide: pardes day to day, read once in about ten minutes. The
+// cheatsheet is the index of keys and words; the reference has every
+// 9P file; scripting has the recipes.
+#import "style.typ": key, keys, btn, chord, word, tag, addr, file, cmd, doc, pairs
+
+pardes is a screen of columns. Each column holds panes, and each pane is a
+tag (a line of words) over a body: a file, a terminal, a PDF or an image.
+Above the columns runs the workspace tag, and each column has a tag of its
+own. Any text anywhere can be clicked: #btn("B2") runs it, #btn("B3") looks
+at it. The keys are helix's, in modes.
+
+= Modes <modes>
+
+Each pane has its own mode and keeps it while you are elsewhere: leave a
+terminal at its `$` and it is still `$` when you come back. The box at the
+left of a pane's tag shows the mode.
+
+#pairs(
+ [normal (box blank)], [keys move and select; an edit acts on the selection. File and PDF panes start here, and so does a terminal from #key("Alt-n").],
+ [insert (`^`)], [keys type. #keys("i", "a", "o") and the rest enter it.],
+ [raw (`$`), terminals only], [keys go to the program. A terminal from #word("Tty"), the shell a bare `pardes` starts with, and a command pane start here.],
+)
+
+#key("Ctrl-b") switches a terminal between raw and normal; in normal mode
+the shell's prompts are hidden and its text is a page to move over and
+copy from. #word("Mode") in the tag steps raw, normal, insert. Esc and
+Shift-Esc depend on the mode:
+
+#pairs(
+ [normal], [#key("Esc"): back to the previous pane (#word("Last")). #key("Shift-Esc"): the same, except in a terminal, where it switches to raw.],
+ [insert], [#key("Esc"): back to normal. #key("Shift-Esc"): the same, except in a terminal, where it switches to raw.],
+ [raw `$`], [#key("Esc"): to the program, except at an idle, empty shell prompt, where it goes back to the previous pane. #key("Shift-Esc"): always back to the previous pane. Either way the terminal stays `$`.],
+ [a PDF], [#key("Esc"): clears the selection and the search highlights. #key("Shift-Esc"): back to the previous pane.],
+)
+
+"The previous pane" is the last other pane on the jump list; with none, the
+next pane down its column (wrapping), else any other pane. In raw mode
+every key but #key("Ctrl-b"), Esc and the paste chords goes to the
+program, so #key("Ctrl-w") and #key("Alt-n") need you out of raw mode
+first (and #key("Ctrl-w") out of insert mode, where it deletes a word).
+
+= The mouse <mouse>
+
+#pairs(
+ btn("B1"), [select; a plain click puts the cursor there and gives the pane the keyboard. In a tag it starts typing at the click, in insert mode. A double click selects the word, the line (at a line's start or end), or up to the matching bracket or quote.],
+ btn("B2"), [execute: a builtin word runs, anything else is a shell line.],
+ btn("B3"), [look: open the file, address, directory or URL, or else find the word's next place.],
+)
+
+A #btn("B2") or #btn("B3") click with no drag takes the word under it,
+where a word is the run of letters, digits, non-ASCII and `. - + / : @ _ ~`
+around the click; a trailing `:` is dropped. So #btn("B3") anywhere on
+`src/bar.c:12:5:` in a compiler's `src/bar.c:12:5: error` opens
+`src/bar.c` at line 12, byte column 5, while a click on `error` finds
+`error`. Drag instead to say exactly what you mean. #key("Enter") and
+#key("Tab") in normal mode look and execute the selection, or the word
+under the cursor. The chords are on the cheatsheet.
+
+= Tags <tags>
+
+There are three kinds, and which directory a command runs in depends on
+which one you click:
+
+#tag("Newcol Joincol Find Grep Help Changelog Tutor Dump Themes Config Debug Exit")
+#tag("New Tty Find Grep Joincol Delcol")
+#tag("Save Tty Collapse Del", path: "/home/me/notes.txt")
+
+- The workspace tag and the column tags run commands in the session's
+ directory (where pardes started). A column's own words (#word("New"),
+ #word("Tty"), #word("Delcol")) act on that column; a pane word
+ (#word("Save"), #word("Del")) acts on the column's pane with the
+ keyboard, or its first; from the workspace tag, on the pane with the
+ keyboard.
+- A pane's tag runs commands in the pane's directory: a file's directory, a
+ terminal's current directory.
+
+A tag is text with undo: type a word into it and click it, or delete the
+defaults. #key(":") moves the keyboard between a body and its tag. A pane
+tag may wrap onto several lines; column and workspace tags are one line.
+The path at the start of a pane's tag is computed: typing into it, or
+clicking it, drafts a new name, #key("Enter") confirms and the next
+#word("Save") writes there; #key("Esc") cancels. #word("Collapse") folds a
+pane to its tag. Unsaved text shows on the grip, the box left of the tag.
+Drag the grip up or down to resize, or onto another column to move the
+pane there.
+
+= The active column and the keyboard <active-column>
+
+Two things are easy to confuse:
+
+- *The pane with the keyboard* is where your keys go.
+- *The active column* is where new panes go. It is the column you last
+ typed in, #btn("B1")-clicked in, dropped a pane into or gave the keyboard
+ to its tag, or the one that got the last new pane. A look does not move
+ it.
+
+They usually agree. Here is how they split. You are typing in column 1, so
+column 1 is active. You click #word("New") in column 2's tag: a scratch
+pane opens in column 2 and takes the keyboard, and column 2 is now
+active. In it you #btn("B3") `x.txt`, which is already open in column 1:
+the keyboard jumps to that pane in column 1, but the active column stays
+2, so the next pane a look opens lands in column 2, away from where you
+are. Type or click in column 1 and it is active again.
+
+== Where new panes go <new-panes>
+
+#word("Placement") picks the rules: `acme` (the default) or `pardes`.
+Under `acme`, a new pane goes into the column whose tag asked, else the
+active column, never into a new column. An empty column it takes whole;
+#word("New") and 9P's #file("pane/new") take the bottom half of the
+column's last pane; a pane opened from a pane's text (a look, #word("Tty"),
+#key("Alt-n")) goes under the pane with the most blank rows, or halves the
+biggest; a command pane goes to the last column. #word("New") in a pane's
+tag opens a `+New` scratch named in that pane's directory, in that pane's
+column; from a column tag, in that column and the session's directory.
+Every new pane but a command pane takes the keyboard. No pane is made
+shorter than its tag and two rows; where there is no room the pane is
+refused.
+
+A column can be empty, as in acme: closing its last pane leaves it, its
+tag over blank space. #word("Delcol") and #word("Joincol") take columns
+away. Closing the session's last pane quits pardes.
+
+= Looking <looking>
+
+#btn("B3") (or #key("Enter")) on a path opens it, or goes to the pane that
+already shows it; `file:12` goes to line 12. The address forms are on the
+cheatsheet. A directory types `ls` into a terminal idle there, else opens
+a terminal there. A URL opens in the browser.
+
+A relative path is looked for where the click was, then where you have
+been: first in the looking pane's own directory, then in the directory of
+each pane on the jump list, most recent first. The first that names a
+file, or a pane open on that path, wins. `./x` and `../x` look only in the
+pane's own directory.
+
+= Command panes <command-panes>
+
+#btn("B2") on a line that is no builtin (`make`, `git log`) runs it with
+the #word("Shell") setting's `-c`, in the directory of the tag or pane it
+came from. In a terminal idle at an empty prompt, a line from that
+terminal's own text is typed into its shell. Anywhere else it runs in a
+command pane, a terminal of its own:
+
+#tag("Kill Save Collapse Del", path: "/home/me/src (make) exit 0")
+
+Its tag says `running`, then `exit N`. The next command for the same
+directory reuses a command pane that has finished (from a column tag, only
+one in that column), below what it showed; a pane still running, or one a
+background job still prints to, is never reused, so a second command
+meanwhile gets a pane of its own. #word("Kill") stops what pardes started
+(`Kill make`: those whose line starts with `make`); #word("Exit") quits
+pardes.
+
+= Unsaved panes <unsaved-panes>
+
+A pane whose text is unsaved is marked on its grip. #word("Del") on it
+refuses once: a notice says `1 unsaved pane — Del again to discard` and the
+pane is listed in `+Unsaved`. The same #word("Del") again, with nothing
+edited since, discards it. #word("Exit"), #word("Restore") and
+#word("Delcol") refuse once over unsaved panes the same way. A `+New`
+scratch under 100 bytes and a command's output never hold anything up.
+
+From the keyboard (#key("SPC d")), #word("Del") on a pane with panes above
+and below it also asks which neighbour takes its rows: #key("k") above,
+#key("j") below, any other key keeps the pane. A click never asks, and
+gives the rows to the pane above.
+
+= Terminals <terminals>
+
+A terminal is a pane like any other.
+
+#tag("Tty+bash Save Mode Filter Collapse Del", path: "/home/me/src")
+
+- `Tty+bash` opens another terminal on that shell; a bare #word("Tty")
+ runs the #word("Shell") setting, `$SHELL` when it is executable, else
+ `/bin/sh`.
+- #word("Save") asks for a path on the pane's notice band and writes the
+ terminal's scrollback there as text; `Save path` writes it at once.
+- #word("Filter") maps the program's colours through the theme.
+- In raw mode Ctrl-V types what you yanked into the program (with nothing
+ yanked, the program gets the key), and Ctrl-Shift-V the desktop
+ clipboard.
+- A program that tracks the mouse (htop, vim with `mouse=a`) gets
+ #btn("B1")'s clicks and drags and the wheel over its grid; #btn("B2") and
+ #btn("B3") stay pardes's. Hold Shift to swap: #btn("B1", shift: true)
+ selects and Shift-wheel scrolls pardes's scrollback, while
+ #btn("B2", shift: true) and #btn("B3", shift: true) go to the program as
+ its buttons 2 and 3. A full-screen program that does not track the mouse
+ gets the wheel as arrow keys. Tags, grips and gutters stay pardes's.
+- `Repl python` in a terminal's tag makes #btn("B2") on a `.py` pane send
+ the text to that REPL instead of running it.
+
+= Reviewing diffs <reviewing-diffs>
+
+Open a `.diff` or `.patch`, or run `git diff` (or `git show`, `diff -u`) as
+a command: once it has finished, output that starts as a diff is shown as
+one, each hunk coloured in its file's language and its added and removed
+rows tinted. Then #btn("B3") to jump, with the usual look resolution:
+
+- a `diff --git`, `---` or `+++` line opens the file (the new one, or the
+ old one on a `---` line unless the `+++` under it names another);
+- a `@@ -a,b +c,d @@` line goes to line `c` of the new file;
+- the `+`, `-` or space in a hunk line's first column goes to that line in
+ the new file (for a removed line, the line now standing where it was);
+- the code after it is ordinary words.
+
+git's `a/` and `b/` prefixes are dropped (and `c/ i/ w/ o/` with
+`diff.mnemonicPrefix`); a `--no-prefix` diff's paths are kept as written.
+
+= pardes FILE and --wait <editor>
+
+In a pane's shell, `pardes FILE` opens FILE in this session and returns at
+once, as acme's `B` does. A FILE not there yet opens an empty pane named
+for it; #word("Save") creates it, making its directories first. Something
+the session refuses (a bad name) is printed and the command exits 1.
+
+`pardes --wait FILE` (`-w`) returns when the pane showing FILE is closed
+(exit 0) or the session goes away (exit 1), as acme's `E` does. Set
+`EDITOR='pardes --wait'` (`GIT_EDITOR` follows it), and `git commit`,
+`crontab -e` and fish's Ctrl-O open in a pane and read the file once you
+close it. Bare `pardes` inside a pane refuses and names `--nested`, which
+starts a separate session whose shells do not forward to it.
+
+= Keys <keys>
+
+Motions select what they cross, and an edit acts on the selection: #key("w")
+then #key("d") deletes a word. #key("x") selects lines, #key("v") extends
+characters, #key(";") collapses to the cursor. #key("s") makes a cursor
+per regex match inside the selection, and every edit then acts at each.
+#key("/") is a case-insensitive substring search, one hit a line; the
+regexes are on #key("s") and #key("S"). #keys("n", "N") step through
+everything a look would open, across panes. Line end is #key("g l"), and
+#key("$") is helix's keep-pipe. #key("SPC") starts the leader,
+#key("SPC ?") lists every path, and #word("Help") lists every key and
+builtin. #word("Tutor") (#key("SPC h t")) practises them.
+
+The language servers run as child processes, one per language when it is
+on `PATH`: `zls`, `rust-analyzer`, `clangd`, `gopls`,
+`typescript-language-server` and `pyright-langserver`
+(`PARDES_LSP_ZIG`, `_RS`, `_C`, `_GO`, `_TS`, `_PY` name another, empty
+turns one off):
+
+#pairs(
+ [#keys("g d", "g D", "g y", "g i", "g r")], [definition, declaration, type, implementation, references; Ctrl-#btn("B1") is a definition too],
+ [#keys("SPC l k", "SPC l r", "SPC l a", "SPC l h")], [hover, rename, code action, select the references],
+ [#keys("SPC l s", "SPC l S", "SPC l d", "SPC l D")], [symbols and diagnostics, of the file and the workspace],
+ [#keys("SPC l c", "SPC l C", "SPC l t", "SPC l T")], [incoming and outgoing calls, supertypes and subtypes],
+ [#keys("] d", "[ d")], [next and previous diagnostic],
+ [#key("=")], [format],
+ [#keys("SPC l i", "SPC l w")], [#word("Lspinfo"): the servers' state; #word("Lspwhy"): why the last query found what it did],
+)
+
+One answer jumps; several open a list where #key("Enter") on a row goes
+there. In insert mode #key("Tab") after a `.` lists the candidate
+declarations and inserts nothing. A format or a rename within the file is
+one undo step; a rename that reaches other files opens a preview instead
+of changing them.
+
+= Sessions <sessions>
+
+```
+pardes --detach=work & a session with no screen of its own
+pardes --attach=work show it here
+pardes-gui --attach=work the SDL window can attach too
+```
+
+The session owns the panes, shells and files; frontends come and go.
+#word("Attach") `work` (#key("SPC s a")) switches this window to that
+session, and #word("Detach") (#key("SPC s D")) leaves it running, shells
+and all. Bare `--detach` names the session after its pid; bare `--attach`
+needs exactly one session. Every attached frontend sees the same screen,
+at the smallest common size. With none attached, `size C R` on the root
+ctl sets the screen and messages clear by the clock. A detached session
+ends when its last pane closes, as any session does.
+
+= Config <config>
+
+#word("Config") (#key("SPC f c")) opens the startup file,
+`~/.config/pardes/init` (`$XDG_CONFIG_HOME/pardes/init` when that is
+absolute; on macOS `~/Library/Application Support/pardes/init`). Each line
+is one builtin, run at start as if executed; `#` starts a comment, and a
+line that fails is skipped silently. Text that is no builtin does not run
+as a shell command.
+
+```
+Theme atelier
+Shell zsh
+Placement pardes
+```
+
+#word("DumpConfig") opens every live setting as the line that sets it, so
+it pastes back into `init` as is. The settings are listed with the root
+ctl in the reference (#doc("fs", section: "settings")). Keys are
+compile-time, in `src/config.zig`.
+
+#word("Dump") writes the workspace to `pardes-<date>-<time>.zon` in
+#word("DumpDir"), and `Restore path` (or `pardes -l dump.zon`) brings it
+back: panes, columns, tags, selections, theme and changed settings; a
+terminal returns with its last MiB of output and a new shell in its old
+directory. Undo history and REPL bindings are not kept. A crash appends
+two lines (build, time, platform, pid; the panic message) to `crashes`
+beside `init`.
diff --git a/docs/ui-review.md b/docs/ui-review.md
index 6702ce31..cbc3c0d8 100644
--- a/docs/ui-review.md
+++ b/docs/ui-review.md
@@ -55,7 +55,7 @@ Pointer targets and the TTY grid are unchanged.
`test/font_size.py` checks startup sizing, fractional sizing, retaining the
size when omitted, and rejecting invalid requests without partial changes.
-Six [classic-inspired themes](themes.md) add `forge`, `lagoon`, `solarium`,
+Six [classic-inspired themes](typ/themes.typ) add `forge`, `lagoon`, `solarium`,
`spectrum`, `harvest` and `clay`. ANSI colors retain their terminal meanings
instead of borrowing a similarly positioned syntax color. Optional SDL
`Pet cat` and `Pet frog` companions walk, idle and reverse in the unused
@@ -98,7 +98,7 @@ tests and 797 SDL tests; SDL image/PDF and Kitty PDF rendering harnesses pass.
## Column and editable-tag follow-up
The follow-up adds editable workspace and column command rows, compact pane
-tags, staged buffer-name edits, and caret reveal for long tags. See [editable tags](tags.md) for the
+tags, staged buffer-name edits, and caret reveal for long tags. See [editable tags](typ/guide.typ) for the
exact interaction and save-target rules.
`test/column_tags.py` exercises isolated SDL and TTY sessions against the same
diff --git a/test/snapshots/find.snap b/test/snapshots/find.snap
index 09826a79..20a5e36f 100644
--- a/test/snapshots/find.snap
+++ b/test/snapshots/find.snap
@@ -6,7 +6,7 @@
# opens a path anywhere else — is what OPENS it. Focus then follows the file
# that opened, but the look was made FROM the results buffer, so the next n
# resumes stepping that same list. Where each pane lands is acme's placement
-# (docs/tags.md, where new panes go): under the text of the pane with the
+# (docs/typ/guide.typ, where new panes go): under the text of the pane with the
# most blank rows. The walk skips .git, matches the NAME
# case-insensitively, and sorts (readdir order is not stable). `SPC f s` is
# Save, which moved out of `w` to make this group.
diff --git a/test/snapshots/lspcomplete.snap b/test/snapshots/lspcomplete.snap
index eaa26c61..e25c6525 100644
--- a/test/snapshots/lspcomplete.snap
+++ b/test/snapshots/lspcomplete.snap
@@ -75,7 +75,7 @@ snap completion
# this golden diverges. What TWENTY do is only worse in degree — fifteen
# stacked panes, the file crushed to one visible line, and from the sixteenth
# on freeSlot returns null and the keystroke is eaten for the rest of the
-# session — and that story lives in docs/lsp.md rather than in three seconds of
+# session — and that story is told here rather than replayed in three seconds of
# every suite run. One file pane, one results pane, same as after the first.
key tab
settle 150