summaryrefslogtreecommitdiff
path: root/docs/config.md
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-26 18:58:37 -0300
committerGabriel Schneider <[email protected]>2026-08-27 09:47:39 -0300
commit29ac9be75fdcafbd7d05c15aa9eb8490d74caa98 (patch)
tree6629cc215d6953090f6b29a7414b28cb9990e105 /docs/config.md
parent11f380f6d7222f2cad93c2cdf13701ea1f903d47 (diff)
downloadpardes-29ac9be75fdcafbd7d05c15aa9eb8490d74caa98.tar.gz
pardes-29ac9be75fdcafbd7d05c15aa9eb8490d74caa98.zip
An edited row keeps its colours, four copies of forkShell become one, and Esc stops recentring
## A terminal row's ANSI colours survive being edited The loudest colour bug this editor had: one keystroke anywhere in a coloured shell row turned EVERY column of it grey. `EditAnchors` anchored a buffer line only when it was BYTE-IDENTICAL to the shell row it stood over, so a single differing byte dropped the whole row's colour projection. Worst shape is invisible: append past the pane's right edge, where the text is clipped, and the row looks the same and only its colour goes. Anchoring is byte-level now. An edit leaves the row's own bytes at both ends, and being the same bytes they keep the same colours; only what was typed has no cell under it, so only that takes none. Live, on real `fastfetch`: a 32-column blue run split into 6 + 26 around one typed character. Three defects underneath it, all found by machinery rather than by reading: * A JOIN removes a buffer line while the buffer's covered span grows, so `lines == covered` and both aligned guesses — Nth line over the Nth covered row, and the same counted from the bottom — resolved to the SAME wrong row. Every untouched row below a join went plain. Anchoring is now a streaming monotone matching: one shell-row cursor that only ever moves forward, advanced once per buffer line, linear in the buffer where the version before it was quadratic. * An EMPTY line is not evidence. Splitting a row makes one, it equals every blank row in the span, and left free to look ahead it claimed the blank row below the last output and took every coloured row in between out of reach of the lines that owned them. * Reflow under a scrolled viewport. `PageList.getTopLeft(.viewport)` returns the viewport pin verbatim, x and all, while `PageList.pin` forces x to 0 — so after a reflow remapped a tracked pin into the middle of a row, the text pass dumped row 0 from that column while the colour pass paired the fragment with the row's FIRST cells. Row 0 wore its left half's colours until the pane snapped back to live output. `bodyText` dumps from column zero now, which is also what ghostty's own renderer draws. Also here: DECSCNM (reverse video) was silently dropped whenever `tty_filter` was off, because the raw path resolved a `.none` colour by role and never consulted the mode. The test that found the first two is the one worth keeping: random editing against an ABSOLUTE oracle — every row's own text names the colour it must have — because the differential oracle it replaced was blind by construction. It skipped the edited row, which is the row the user is complaining about. ## Esc returns to a pane without moving its view Esc in body normal mode runs `Last`, "the pane you were in before this one", and that went through `focusPaneLine`, which recentred a file on the target line unconditionally. So returning to a buffer repainted the whole screen to show a line that was already on it. `focusPaneLine` takes a landing now: `.center` for the three callers going somewhere you have not been (a look target, a path a pane already holds, `@pN:LINE:COL`), `.keep` for Esc. `.keep` leaves the view alone and lets `ensureCursorVisible` — which already existed and already scrolls by the minimum into the `scroll_off` band — be the only thing that may move anything. Not `line = 0`, which `focusPaneLine` already understands as "focus and touch nothing": a background pane's view can move while you are away, because the wheel scrolls the pane under the POINTER and a resize reveals no cursor, so the recorded cursor plus a minimal nudge is what actually gets you back. Ctrl-o and Ctrl-i keep centring, and the asymmetry is structural rather than arbitrary: `Last` only ever CROSSES panes, so the pane it lands on already holds the view you left it with, while `jumpBy` can land in the SAME pane, where a long in-file jump would arrive on the very top or bottom row with `scroll_off` lines of context on one side. Helix splits the same pair the same way — its jumplist centres, its buffer switch does not. One deliberate consequence: under `.keep` a PDF's page is not restored AT ALL, because a page reveal IS that pane's view and a reveal of the page you are already on still snaps `document_scroll_y` to that page's start, discarding where you had read to. When something moved the pane while you were away — the wheel again — Esc leaves it where the wheel left it, and Ctrl-o is how you reach the recorded page. ## host_io.zig: the machine-local half of a host, once `host.zig` is the seam. The part of the answer that is identical on every host with an operating system under it — fork a pane's shell, put bytes on a disk — was written FOUR times: in tty.zig, gui.zig, macos.zig and detached/server.zig. What those copies had in common says what they were for: all four were missing FD_CLOEXEC on the pty master, so in every shell pardes has shipped, a program in one pane could read another pane's terminal. One copy now, and the wire got smaller for it: `ServerMsg.spawn` is gone. A frontend never asked the server to fork anything — the server has an operating system under it and forks through `host_io` like every other host — and `decodeClient` lost the scratch buffer that message needed.
Diffstat (limited to 'docs/config.md')
-rw-r--r--docs/config.md126
1 files changed, 96 insertions, 30 deletions
diff --git a/docs/config.md b/docs/config.md
index 11346f4e..58797dfe 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -12,11 +12,14 @@ main command file is named `init`:
On the two unixes `XDG_CONFIG_HOME` counts only when it is ABSOLUTE, as the
XDG base-directory specification requires; an empty or relative value falls
-back to the home-directory form. Windows never consults it. An `init` of 1 MiB
-or more, or one that cannot be read, is treated as no file at all — the path
-still resolves, because "nothing is there yet" is the answer `Config` exists
-to give. There is one case with no path at all: a native launch with no `HOME`
-set, which `Config` reports as such.
+back to the home-directory form (`user_config.xdgBase`, and the test beside
+it). Windows never consults it. An `init` that does not fit the `max_bytes`
+read limit — 1 MiB, `src/user_config.zig` — or that cannot be read at all is
+treated as no file: `load` takes the `readFileAlloc` error and keeps going. The
+path still resolves, because "nothing is there yet" is the answer `Config`
+exists to give. There is one case with no path at all: a native launch with no
+`HOME` set (or, on Windows, neither `%LOCALAPPDATA%` nor `%USERPROFILE%`),
+which `Config` reports as `no per-user config path`.
`Config` (`SPC f c`, or the word executed anywhere) opens one refreshable
`+Config` pane. It reports the startup path and every live config-like value:
@@ -26,10 +29,16 @@ and size, tagline scale, panel transition,
scene effects, hover delay, platform, native-image support, and (on SDL) whether
the executable uses live-built shaders or the paired prebuilt shader snapshot.
Platform-dependent rows say `unsupported` instead of looking like an off or
-empty supported setting: TTY reports Font and scene shaders as unsupported;
-web reports Font, panel transitions, and scene shaders as unsupported. Tagline
-font size is its own row and capability — GUI font selection is native-only,
-while the browser still applies the compiled tagline percentage to its DOM.
+empty supported setting. The four fields of `runtime_config.Capabilities` gate
+them and are stated once as plain data in `builtins.capabilities`:
+`font_picker` is the SDL GUI and
+native macOS only, `scene_shaders` the same two, `panel_transitions` every
+hosted shell, and `tagline_font_size` everything but the TTY and the board. So
+the TTY reports Font, TaglineSize and the scene shaders as unsupported; the
+browser reports Font, panel transitions and the scene shaders as unsupported,
+and its TaglineSize row reads `82% (build-time only)` — tagline font size is
+its own capability precisely because GUI font SELECTION is native-only while
+the browser still applies the compiled percentage to its DOM glyphs.
The startup path is printed whether or not a file exists — that is usually when
it is most useful — and is ordinary selectable text, so a right click on it
opens the file. Shell follows the same requested/effective/pending model as
@@ -64,19 +73,25 @@ Wrap
```
A line matches a builtin whose name takes NO argument only as that whole word:
-`Kill` runs, `Kill something` does not. Builtins that take one (`Theme`,
-`ThemeFile`, `Font`, `TaglineSize`, `Shell`, `Restore`, `Find`, `Grep`,
-`Rename`, `WsSymbols`, `Look`, `Exec`, `EffectCode`) take everything after the
-name as the argument.
+`Kill` runs, `Kill something` does not. A builtin that takes one
+(`takes_arg` in `src/builtins.zig`, or a `settings` row whose action is
+`shell`, `theme`, `font` or `tagline_size` in `src/runtime_config.zig`) takes
+everything after the name as the argument. On a native build that is `Theme`,
+`ThemeFile`, `Font`, `TaglineSize`, `Shell`, `Save`, `Restore`, `Attach`,
+`Find`, `Grep`, `Rename`, `WsSymbols`, `Look`, `Exec`, `Msg` and `EffectCode`.
+(`Peek`, `Poke`, `Hexdump` and `Gpio` take one too, but they exist only where
+`board_memory.enabled` holds, and that build has no config file.)
`Theme <name>` wants one of the 228 names in the ring. Do not derive the
spelling — read it off `ThemeSel` (`SPC t t`), which lists every one as the
-exact `Theme <name>` line that selects it. The generator lowercases, folds
-punctuation runs to a single `_` and then TRIMS leading and trailing ones
-(`penumbra+.toml` is `penumbra`, not `penumbra_`), and it suffixes every theme
-that came from zed with `_zed` so it cannot collide with a helix theme of the
-same name (zed's "Ayu Mirage" is `ayu_mirage_zed`; `ayu_mirage` is helix's).
-A name that is not in the ring is ignored.
+exact `Theme <name>` line that selects it. `slug` in `tools/gen_themes.zig`
+lowercases, folds punctuation runs to a single `_` and then TRIMS leading and
+trailing ones (`penumbra+.toml` is `penumbra`, not `penumbra_`), and every
+variant read out of a zed `.json` gets `_zed` on the end so it cannot collide
+with a helix theme of the same name — zed's "Ayu Mirage" is `ayu_mirage_zed`
+and `ayu_mirage` is helix's `ayu_mirage.toml`. The suffix goes on all of them
+rather than only the eight that clash today, so a name cannot move when either
+project gains or loses a file. A name that is not in the ring is ignored.
## Runtime theme files
@@ -114,10 +129,12 @@ Execute `DumpThemes` to write every theme compiled into the executable to:
The command replaces those generated reference files but leaves unrelated
files alone. Copy one into `themes/`, rename it, change its `.name`, and use it
as the starting point for a custom theme. The dumped file is also the complete
-format: one ZON struct with the theme name, the fifteen RGB roles, nullable
-`bg`/`fg`, and either a 16-color RGB `palette` or `null`. RGB values are
-three-byte arrays, and hex literals are accepted. There is no inheritance or
-partial override layer.
+format, and it is `pardes.Theme` serialised by `std.zon.stringify`: the theme
+name, thirteen required RGB roles (`sel_bg`, `sel_fg`, `tag_bg`, `tag_fg`,
+`box`, `box_dim`, `kw`, `str`, `num`, `comment`, `lineno`, `scroll_track`,
+`scroll_thumb`), nullable `bg`/`fg`, and either a 16-color RGB `palette` or
+`null`. RGB values are three-byte arrays, and hex literals are accepted. There
+is no inheritance or partial override layer.
`Shell <name>` sets the binary that the NEXT terminal pane execs; panes
already open keep the shell they are running. A bare name is resolved against
@@ -158,10 +175,16 @@ Mono, then installed `NotoSansMono-Regular`, `DejaVuSansMono`,
`SymbolsNerdFont-Regular`, `NotoSansSymbols2-Regular`,
`NotoSansSymbols-Regular`, and `DejaVuSans`, in that order, missing entries
skipped; the rasterized glyphs are retained in its GPU atlas. The macOS shell
-has none of that and needs none: it embeds no font, substitutes the system
-monospaced face (else Menlo) when the NAME you asked for will not load, and
-leaves per-codepoint fallback to CoreText's own cascade when it draws. What it
-caches is glyph ids, not pixels.
+has none of that and needs none. It embeds no font. Its default face is
+`NSFont.monospacedSystemFont`, resolved through the descriptor rather than by
+name, and `Menlo` only if that face turns out not to be fixed-pitch — a
+proportional face in a fixed grid is a broken screen, not a cosmetic problem
+(`PardesView.defaultFace`). A `Font <name>` arrives as a PATH the core already
+resolved by walking the font directories, and a file CoreText cannot measure
+leaves the face already on screen rather than substituting one, because the
+alternative is a terminal with no way back out (`PardesView.fromFile`).
+Per-codepoint fallback is CoreText's own cascade at draw time. What the shell
+caches is `CGGlyph` ids, not pixels.
Blank, unknown, malformed, or unsuccessful lines are ignored silently, and a
bad line does not prevent later lines from running. Top-level text that is not
@@ -263,9 +286,9 @@ prebuilt executable.
## Delayed Look preview
The preview is enabled by default. Moving the pointer onto selectable text and
-leaving it still for `look_preview_delay_frames` (2 animation ticks, roughly
-33 ms at the 60 Hz animation cadence) paints a
-subtle theme-derived preview of the exact operand a right-click Look would receive.
+leaving it still for `look_preview_delay_frames` — 2 animation ticks, and
+`animation.frame_ms` is 16, so about 32 ms — paints a subtle theme-derived
+preview of the exact operand a right-click Look would receive.
Repeated motion reports in the same semantic grid cell do not restart the
delay. The preview uses the same side-effect-free word/path expansion as Look;
it does not focus a pane, move a cursor, install a selection, activate a PDF
@@ -277,3 +300,46 @@ Set this compile-time option in `src/config.zig` to disable the feature:
```zig
pub const look_preview_delay_frames: ?u16 = null;
```
+
+## Build-time configuration
+
+Everything above is chosen at runtime or in `src/config.zig`. The build itself
+takes these, and this is the whole list — every `b.option` in `build.zig`,
+besides `-Dtarget` and `-Doptimize` from `standardTargetOptions` and
+`standardOptimizeOption`:
+
+| option | values | default |
+|---|---|---|
+| `-Dplatform` | `tty`, `gui`, `web`, `macos`, `esp32p4` | absent builds the tty cli and the SDL gui together |
+| `-Dstatic` | bool | `false` |
+| `-Dmupdf` | bool | on for a native target, off for web and esp32p4 |
+| `-Djpx` | bool | `true` — JPEG 2000, and with it scanned PDFs |
+| `-Dtree-sitter` | `disabled`, `zig`, `minimal`, `full` | `full` natively, `zig` for web, `disabled` for esp32p4 |
+| `-Dtheme-animation` | bool | on everywhere except `-Dplatform=esp32p4` |
+| `-Dprebuilt-shaders` | bool | on for a bare `zig build`, off when `-Dplatform` names a shell |
+| `-Dtracy` | path to a Tracy source checkout | off |
+| `-Dmacos-identity` | codesigning identity for `pardes.app` | `-` (ad-hoc) |
+| `-Ddump` | a `dump.zon` to embed in the web shell | none |
+| `-Dtest-filter` | substring; run only tests whose name contains it | none |
+| `-Desp32p4-cols` | u16, the board's grid width in cells | `56` |
+| `-Desp32p4-rows` | u16, the board's grid height in cells | `14` |
+| `-Desp32p4-cpu-mhz` | u16: `90`, `180` or `360` | `90`, the bootloader default |
+| `-Desp32p4-port` | serial port the board is wired to | `/dev/ttyUSB0` |
+| `-Desp32p4-prof` | bool; per-phase cycle counts for every frame | `false` |
+| `-Desp32p4-firmware` | bool; also build the flashable image and its board steps | `false` |
+
+The five `-Desp32p4-*` options that are not `-Desp32p4-firmware` are registered
+unconditionally rather than inside the `-Desp32p4-firmware` block that consumes
+them, so `zig build --help` lists them and passing one without the flag is not
+an "unknown option" error.
+
+Two build inputs reach the running binary as ordinary values rather than as
+behaviour. `build.zig` reads `.version` from `build.zig.zon` through an untyped
+`@import("build.zig.zon")` — one place to bump — and `gitCommit(b)` reads the
+revision at configure time. Both land in `pardes_config` and are re-exported as
+`pardes.version` and `pardes.commit`, and `pardes --version` prints
+`pardes <version> (<commit>)`, or `pardes <version>` alone when there is no
+commit: a tarball, a container with no `git`, or a checkout outside version
+control all yield null, and the flag has to work anyway. Neither is a question
+asked at runtime — a binary that shelled out to `git` would describe whatever
+tree it was standing in rather than the one it came from.