diff options
| author | user <user@clank> | 2026-08-27 16:04:37 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-27 16:04:37 +0200 |
| commit | 5151e42f22b404cf41dc8b409bcdfcd4b39444ea (patch) | |
| tree | da8424903e12bf41a6144b065e06529df4f8e947 | |
| parent | docs: the landing page stops arguing (diff) | |
| download | slosh-5151e42f22b404cf41dc8b409bcdfcd4b39444ea.tar.gz slosh-5151e42f22b404cf41dc8b409bcdfcd4b39444ea.tar.xz slosh-5151e42f22b404cf41dc8b409bcdfcd4b39444ea.zip | |
README/docs: spell the dash --, as the rest of the tree does
Every C comment, commit message and generated config in this repo
writes the aside dash as a spaced double hyphen; the Markdown alone
used U+2014, some 220 of them. One spelling, and the ASCII one -- the
prose now matches the code it documents.
Mechanical (every occurrence was the spaced form, checked including
the one inside a code fence, where it was comment text), so the words
themselves are untouched.
| -rw-r--r-- | README.md | 10 | ||||
| -rw-r--r-- | docs/chrome.md | 20 | ||||
| -rw-r--r-- | docs/config.md | 58 | ||||
| -rw-r--r-- | docs/design.md | 10 | ||||
| -rw-r--r-- | docs/keys.md | 20 | ||||
| -rw-r--r-- | docs/layouts.md | 72 | ||||
| -rw-r--r-- | docs/macos-release.md | 24 | ||||
| -rw-r--r-- | docs/panes.md | 80 | ||||
| -rw-r--r-- | docs/scripting.md | 22 | ||||
| -rw-r--r-- | docs/shaders.md | 38 | ||||
| -rw-r--r-- | docs/workspaces.md | 56 |
11 files changed, 205 insertions, 205 deletions
@@ -1,6 +1,6 @@ # slosh -A terminal multiplexer — panes, tabs, sessions you can detach from — written in +A terminal multiplexer -- panes, tabs, sessions you can detach from -- written in C on top of [libghostty-vt](https://github.com/ghostty-org/ghostty). ## Install @@ -35,13 +35,13 @@ split, `C-a ?` for the keys, `C-a p` for every command by name. ## Docs -**[docs/](docs/index.md)** — [keys](docs/keys.md) · +**[docs/](docs/index.md)** -- [keys](docs/keys.md) · [panes](docs/panes.md) · [configuration](docs/config.md) · [shaders](docs/shaders.md) · [chrome](docs/chrome.md) · [layouts](docs/layouts.md) · [scripting](docs/scripting.md) · [how it works](docs/design.md) -`make docs` renders the same pages as a static site into `build/docs` — no +`make docs` renders the same pages as a static site into `build/docs` -- no dependencies, one file of Python and one stylesheet. ## What is different about it @@ -49,7 +49,7 @@ dependencies, one file of Python and one stylesheet. - **The mouse works properly.** Click the middle of a border to split toward it, drag a gap to move a boundary, drag a pane by its title to swap it. Everything drawn registers what it is as it is drawn, so a click and a preview cannot - disagree — which is also how the split target can be *part* of an edge without + disagree -- which is also how the split target can be *part* of an edge without the highlight and the hit ever drifting apart. - **The layout is recomputed, never stored.** Small terminal? The tab becomes a list of headers, and comes back to exactly the arrangement you had. @@ -57,7 +57,7 @@ dependencies, one file of Python and one stylesheet. `[re-run]` and `[close]` in its frame. - **Configuration is one KDL file** that reloads on save, can be built from `include`d pieces, and refuses a broken file instead of half-applying it. -- **Colour passes over cells** — dim, tint, rulers, spotlights, and a border that +- **Colour passes over cells** -- dim, tint, rulers, spotlights, and a border that can flash when a pane rings, with strengths written as expressions. - **Scriptable to the same depth it is usable**: one JSON object per line over the session's socket, and a pane can draw its own status bar and buttons with diff --git a/docs/chrome.md b/docs/chrome.md index 5262b18..a1d641d 100644 --- a/docs/chrome.md +++ b/docs/chrome.md @@ -1,8 +1,8 @@ # Chrome shaders A frame is a rect too, so it is a pass too. `where="chrome"` on any entry in -`shaders` or in a `states` block runs that pass over a pane's **frame** — border, -padding, title, buttons — instead of its contents. +`shaders` or in a `states` block runs that pass over a pane's **frame** -- border, +padding, title, buttons -- instead of its contents. ```kdl states { @@ -15,8 +15,8 @@ Nothing else changes: same shaders, same `amount`, same [expressions](shaders.md#strength-as-an-expression), same chain order. One new word. -You have already seen one: the default bell flash — blinks then a breathe on -the frame of a pane that rang, in theme's `bell` colour — is exactly this, a +You have already seen one: the default bell flash -- blinks then a breathe on +the frame of a pane that rang, in theme's `bell` colour -- is exactly this, a chrome tint whose amount reads `since`. (The shimmer that crosses the pane's body at the same moment is the same idea without the new word: an ordinary content pass in the same chain.) Writing your own `bell { … }` replaces both; @@ -28,7 +28,7 @@ content pass in the same chain.) Writing your own `bell { … }` replaces both; default; mixing that towards a colour paints a rectangle behind the glyphs instead of colouring them, and leaves it there until the pane changes state. `fg` also keeps the padding inside the border out of it, since a blank cell has -no foreground to colour — so the effect lands on exactly the rules, the title and +no foreground to colour -- so the effect lands on exactly the rules, the title and the buttons. **The contents are cut out of the pass.** A full-strength chrome tint cannot @@ -38,11 +38,11 @@ are two rects and two passes. ## Positions are the whole frame's `x`/`y` are counted from the frame's top-left corner, with `cols`/`rows` the size -of the pane's rect — not of one side. One pass over the whole ring, so an effect +of the pane's rect -- not of one side. One pass over the whole ring, so an effect can travel round a border instead of restarting at every corner. For anything that *travels*, though, the coordinate you want is distance along -the ring, which the language can work out — clockwise from the top-left: +the ring, which the language can work out -- clockwise from the top-left: ``` (y == 0 ? x : x == cols-1 ? cols - 1 + y @@ -55,12 +55,12 @@ with the perimeter itself `2 * cols + 2 * rows - 4`. Three things fall out of it corner it started from instead of stopping at a seam. - `(p - head + P) % P` is "how far ahead of the head", which makes an arc rotate rather than a blob slide. -- `sin(TAU * p / P)` is that position **as an angle**, one cycle to the lap — and +- `sin(TAU * p / P)` is that position **as an angle**, one cycle to the lap -- and a sine wraps at the corner for free, where a ramp needs the trick above. Any whole number of lobes (`* 3 / P`) fits the ring exactly, which is what makes a standing wave stand. -There is no way to name a subexpression, so the formula is written out in full — +There is no way to name a subexpression, so the formula is written out in full -- which is why a small effect is a long line. Thirteen of them are ready to paste in @@ -93,7 +93,7 @@ this state, where `t` only knows what time it is. The `bell` state ends when you look at the pane, because that is what answering a bell is. A pane that a small terminal has collapsed to a single row is chrome all the way -through, which makes this the only kind of pass that can still colour it — and +through, which makes this the only kind of pass that can still colour it -- and that is the state where a colour is worth the most. ## Animation costs a clock diff --git a/docs/config.md b/docs/config.md index 82e91bc..a59e8e6 100644 --- a/docs/config.md +++ b/docs/config.md @@ -11,25 +11,25 @@ $SLOSH_CONFIG, else $XDG_CONFIG_HOME/slosh/config.kdl, else **Saving it applies to every running session immediately.** A file that will not parse is refused rather than half-applied, and the running config stays. Anything -smaller than that — an unknown shader, a binding that does not parse, an -[`include`](#include) that is not there — applies the rest of the file and says +smaller than that -- an unknown shader, a binding that does not parse, an +[`include`](#include) that is not there -- applies the rest of the file and says which line it could not honour. Three things are read later than the rest, because of *when* they are needed: -`shell` and the two `scrollback` limits apply to the next pane you open — +`shell` and the two `scrollback` limits apply to the next pane you open -- shrinking a pane's history retroactively would throw away output somebody is -reading — and a shader plugin that replaced one already loaded needs a new +reading -- and a shader plugin that replaced one already loaded needs a new session. ## Two files worth knowing -- **`slosh --dump-config`** writes every setting with its *default* value — +- **`slosh --dump-config`** writes every setting with its *default* value -- not the value your config gives it. It is generated from the code, so it cannot drift, and it is a supported way to start a config: `slosh --dump-config > ~/.config/slosh/config.kdl`. To see what your own file does, `slosh --check` reads it and reports what it understood. - **`config/config.kdl`** - in the source tree is the same list with the *reasoning* attached — what each + in the source tree is the same list with the *reasoning* attached -- what each setting is for, what it cost to get right, and which ones are opinions. When the two disagree, the dump is right and that file is stale (a test says so). @@ -40,7 +40,7 @@ $ slosh --check # the config a session would read $ slosh --check themes/mine.kdl # or one you have not installed yet ``` -Every problem it found, one per line, with the file and line it happened at — +Every problem it found, one per line, with the file and line it happened at -- including a setting it does not know: ``` @@ -55,13 +55,13 @@ including a setting it does not know: The problems go to stderr and the summary with them. It exits 1 when it has anything to say, so it drops into an editor's compile -step or a pre-commit hook with no glue. A clean run prints what it read — the -files, the prefix it ended up with, how many bindings — because otherwise a +step or a pre-commit hook with no glue. A clean run prints what it read -- the +files, the prefix it ended up with, how many bindings -- because otherwise a config that was never opened and a config with nothing wrong look identical. It is the loader, not a second implementation of it: the only checker that cannot drift from what a session does is the one a session uses. The single -difference is how many problems each reports — a session has one status line and +difference is how many problems each reports -- a session has one status line and shows the first, and a linter that stopped at the first mistake would make you run it once per mistake. @@ -90,13 +90,13 @@ here, so the same line composes keys, shader chains, states or geometry. ## Themes -Every colour the compositor draws has its own name under `theme { }` — 48 of +Every colour the compositor draws has its own name under `theme { }` -- 48 of them, because six names doing thirty jobs meant the split guide could not be recoloured without also recolouring the focused frame. Six ready-made themes are in `contrib/themes`: -`amber`, `mono`, `paper`, `phosphor`, `sl0p`, `slate` — plus `default`, the +`amber`, `mono`, `paper`, `phosphor`, `sl0p`, `slate` -- plus `default`, the compiled-in palette written out, so another theme is one include-swap away from coming back. Include one and put your own two lines on top: @@ -114,18 +114,18 @@ theme { frame_focus "#00ff88" } // ...but that one colour is mine | geometry | `gap` `gap_aspect` `padding` `compact` `rounded` `title_align` `title_inset` `min_pane` `min_split` | | chrome | `status_bar` `status_line` `status_pad` `hints` `version_banner` `pane_buttons` `bell_indicator` and the marks (`zoom_mark` `zoom_on_mark` `close_mark` `min_mark` `newtab_mark` `bell_mark`) | | behaviour | `focus_follows_mouse` `scroll_lines` `scrollback` `scrollback_bytes` `toast_ms` `splash_ms` `hover_delay_ms` `double_click_ms` `word_separators` `anim_ms` `modal_scrim` `dim_unfocused` `float_shadow` `keep_dead` `in_band_shaders` `shell` `editor` `shader_dir` | -| projects | `project_roots` `project_layout` — see [workspaces](workspaces.md) | +| projects | `project_roots` `project_layout` -- see [workspaces](workspaces.md) | | colour | `theme { }` | -| effects | `shaders { }`, `states { }` — see [shaders](shaders.md) and [chrome](chrome.md) | -| keys | `keys { }` — see [keys](keys.md) | +| effects | `shaders { }`, `states { }` -- see [shaders](shaders.md) and [chrome](chrome.md) | +| keys | `keys { }` -- see [keys](keys.md) | A few of those take more than one value. `gap` is in **rows**, and `gap_aspect` says how many columns a row is worth (2 by default, because a cell is about -twice as tall as it is wide) — so both `gap` and `padding` are written in rows +twice as tall as it is wide) -- so both `gap` and `padding` are written in rows and come out looking square. `min_pane cols=24 rows=6` and `min_split cols=32 rows=8` name their two floors as properties (what each floor *means* is on the [panes page](panes.md#small-terminals)), and `project_roots` -takes one path per argument — see [workspaces](workspaces.md). +takes one path per argument -- see [workspaces](workspaces.md). `padding` is the space between a pane's frame and its contents, written the way CSS does it: @@ -138,7 +138,7 @@ padding 2 0 0 1 // top, right, bottom, left A number means the same thing however many of them you write, so `padding 1`, `padding 1 1` and `padding 1 1 1 1` are one padding rather than three. Three -values is refused — CSS reads it as top/horizontal/bottom, and a line whose +values is refused -- CSS reads it as top/horizontal/bottom, and a line whose meaning you have to look up is a line nobody can read. ## Compact @@ -148,7 +148,7 @@ compact true ``` Shared borders instead of gaps. Panes pack flush against 1-cell divider lines -that meet in real junctions — `├` `┬` `┼` — one frame rings the whole tab, and +that meet in real junctions -- `├` `┬` `┼` -- one frame rings the whole tab, and each pane's title rides the line above it, so a boundary costs one cell where the default look spends four columns (a border, a two-column gap, a border) or three rows: @@ -163,20 +163,20 @@ three rows: What changes, and what does not: -- `gap` stops applying — the boundary *is* the divider. `padding` still works +- `gap` stops applying -- the boundary *is* the divider. `padding` still works (still aspect-corrected, so `gap_aspect` keeps that job, along with shaping the keyboard's float moves), and `rounded` still picks the corners. - **Dividers drag exactly as gaps did**, corners included, and the same hover hints appear on them. A pane whose title line is a shared divider is still - dragged — by its name. + dragged -- by its name. - **Interior edges give up click-to-split**: a one-cell line cannot honestly hold both verbs, and the whole line is the resize handle. The outer frame keeps its split handles, and the keyboard splits anything, as ever. -- A pane's **status row** — a dead pane's epitaph and its `[re-run] [close]`, - a program's own OSC 5577 row — overlays the pane's last content row, there +- A pane's **status row** -- a dead pane's epitaph and its `[re-run] [close]`, + a program's own OSC 5577 row -- overlays the pane's last content row, there being no bottom border left to carry it. - **Floats keep the classic frame** (an overlay needs its own edge), and so - does a zoomed pane or a flattened tab — nothing there is packed against + does a zoomed pane or a flattened tab -- nothing there is packed against anything. - Chrome shaders run over the pane's stretch of the shared lines; a line between two panes belongs to both, which is what sharing means. @@ -189,14 +189,14 @@ scrollback_bytes 16777216 // ...and the ceiling that count runs into ``` **The default was a library's, not a decision.** lib-vt is handed no limit and -picks its own — 10,000 *bytes*, which measures at 622 lines of an 80-column pane, +picks its own -- 10,000 *bytes*, which measures at 622 lines of an 80-column pane, less than one `make` run. `scrollback` is that number replaced with the one every other multiplexer settled on. The two limits work together because either can be reached first and neither can see what the other depends on: a line count cannot know how wide your terminal is or how many styles a program used, and a byte count cannot know how many lines -that bought. Whichever bites first wins. Both are estimates — history is pruned a +that bought. Whichever bites first wins. Both are estimates -- history is pruned a page at a time (a page is about 400KB of grid), so a pane keeps a little more than it was told, never less. @@ -210,7 +210,7 @@ What the ceiling costs, measured on a pane filled with styled output: So it is invisible at ordinary widths and a brake at extraordinary ones, which is its job. It is per **pane**: sixty panes full of history is sixty times it, which -is why there is no `unlimited` — at that many panes it would be a memory leak with +is why there is no `unlimited` -- at that many panes it would be a memory leak with a friendly name. `scrollback_bytes 0` removes the ceiling if you have one very wide pane and want every line of it. @@ -243,7 +243,7 @@ states { // ...and the whole table underneath it ``` The full ranking, most urgent first: `dragging`, `drop_hover`, `drop_target`, -`dead`, `suspended`, `bell`, `scrolled`, `floating`, `unfocused`. Exactly one wins — the -first that matches — because two reasons to be grey compound into one muddy grey +`dead`, `suspended`, `bell`, `scrolled`, `floating`, `unfocused`. Exactly one wins -- the +first that matches -- because two reasons to be grey compound into one muddy grey that reads as neither. Naming a state replaces its default outright, including with nothing, which is how you turn one off. diff --git a/docs/design.md b/docs/design.md index 0236212..c85ba39 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,7 +1,7 @@ # How it works -Two decisions do most of the work. The rest — twenty-two numbered decisions, -each with what it cost to get wrong — is in +Two decisions do most of the work. The rest -- twenty-two numbered decisions, +each with what it cost to get wrong -- is in `DESIGN.md`. ## One geometry @@ -9,7 +9,7 @@ each with what it cost to get wrong — is in **Everything drawn registers what it is, as it is drawn**, and a click is a lookup in that list. Drawing and hit-testing cannot disagree, because there is only one of them. The dashed guide that shows where a split would land is drawn -from the same rect the click will resolve against, in the same pass — so a +from the same rect the click will resolve against, in the same pass -- so a preview that lies is not a bug you can have. The same rule applies to state that could be derived: the layout is a pure @@ -81,7 +81,7 @@ next person. A mismatch is refused with both versions named rather than quietly reformatting the tree. Bumping it is one line in `ruff.toml`, one in the Makefile, and a reformat commit -- which is the honest cost of a new version. -The base is LLVM, because that is what this code was written in by hand — two +The base is LLVM, because that is what this code was written in by hand -- two spaces, 80 columns, `char *p`, braces attached. The settings that differ from it were chosen by measuring the reformat rather than by taste: LLVM alone rewrote 38% of the lines, mostly by splitting `if (!p) return false;` in two and reflowing @@ -89,7 +89,7 @@ comments that are prose. Keeping those two and leaving include order alone bring it to 17%, which is alignment and table packing. Comments are not reflowed. They are prose here, wrapped by hand, and sometimes the -line breaks carry meaning — a paragraph, a list, a diagram of a pane. A comment +line breaks carry meaning -- a paragraph, a list, a diagram of a pane. A comment that runs long is a thing for a person to fix. The Python is the opposite: left entirely at ruff's defaults. The C here was diff --git a/docs/keys.md b/docs/keys.md index 88a3318..d146a51 100644 --- a/docs/keys.md +++ b/docs/keys.md @@ -8,23 +8,23 @@ it cannot disagree with your keyboard. `C-a p` opens the same list as a palette you can type into. On a terminal too short for the whole sheet it shows what fits and says how much -it did not — `+7 more, in docs/keys.md`, which is the table below. +it did not -- `+7 more, in docs/keys.md`, which is the table below. ## Defaults | key | does | |---|---| -| `Enter` | split whichever way there is more room — across the longer side, and the other axis if that will not fit | +| `Enter` | split whichever way there is more room -- across the longer side, and the other axis if that will not fit | | `\` `-` | split into columns / rows, when you mean one | | `h` `j` `k` `l`, or arrows | move focus | | `o` | the next pane | -| `H` `J` `K` `L`, or shift+arrows | move the boundary between panes — or the pane itself, when it is [floating](panes.md#floating-a-pane) | +| `H` `J` `K` `L`, or shift+arrows | move the boundary between panes -- or the pane itself, when it is [floating](panes.md#floating-a-pane) | | `0` | give every visible pane an even share | | `Space` | turn the layout a quarter turn (four brings it back) | | `z` | zoom this pane to fill the tab, and back | | `m` | minimise it into the strip along the bottom | | `f` | [float](panes.md#floating-a-pane) it above the layout, and back | -| `F` | a new floating shell — the throwaway terminal | +| `F` | a new floating shell -- the throwaway terminal | | `=` (`+`) `-` | grow / shrink a focused float about its centre (`-` splits, when the pane is tiled) | | `x` | close this pane | | `X` | close this tab, and everything in it | @@ -36,7 +36,7 @@ it did not — `+7 more, in docs/keys.md`, which is the table below. | `Tab` `shift+Tab` | next / previous tab | | `1`…`9` | go to that tab | | `s` | search: find a pane by name | -| `w` | the projects picker — every project under your roots, open or not ([workspaces](workspaces.md)) | +| `w` | the projects picker -- every project under your roots, open or not ([workspaces](workspaces.md)) | | `W` | write this tab out as this project's layout ([workspaces](workspaces.md)) | | `p` | the command palette | | `PgUp` `PgDn` `Home` `End` | [scrollback](config.md#scrollback) (the wheel works too) | @@ -60,8 +60,8 @@ press without counting first. ## Rebinding -`C-a` is a default, not a decision. If you want it back for start-of-line — and -plenty of people do — one line takes it: +`C-a` is a default, not a decision. If you want it back for start-of-line -- and +plenty of people do -- one line takes it: ```kdl keys { prefix "ctrl+b" } // or ctrl+space, or alt+x, or ... @@ -83,7 +83,7 @@ keys { Modifiers are `ctrl+ alt+ shift+ super+`, or the shorthand the cheatsheet prints: `C-` `M-` `S-`. Named keys are `left right up down enter tab escape space backspace home end pageup pagedown delete insert backslash minus slash -comma period f1`…`f12`, and the arrows can be written as `←` `→` `↑` `↓` too — +comma period f1`…`f12`, and the arrows can be written as `←` `→` `↑` `↓` too -- so a chord copied off the cheatsheet is a chord you can paste into a config. Anything else is the character you press. That includes the shifted ones, which @@ -129,10 +129,10 @@ being bound at all. | session | `palette` `help` `edit-config` `detach` `quit` `literal-prefix` | `>` and `<` are the shifted period and comma. A terminal does not report the shift -for punctuation, so `.` and `,` answer to them too — the same deal `/` has with +for punctuation, so `.` and `,` answer to them too -- the same deal `/` has with `?`, and the reason the defaults bind both halves. -`scroll-up` and `scroll-down` have no default key — the wheel does that job — +`scroll-up` and `scroll-down` have no default key -- the wheel does that job -- which is exactly the case the palette exists for. So does `clear-shaders`, which undoes whatever the program in a pane painted on it ([shaders](shaders.md#prototyping-in-a-pane)); bind it if you leave diff --git a/docs/layouts.md b/docs/layouts.md index cdb57a0..20d555d 100644 --- a/docs/layouts.md +++ b/docs/layouts.md @@ -21,14 +21,14 @@ slosh --layout project.layout ## What to call them -**`*.layout`** — a project's is `slosh.layout`. The syntax is still the same +**`*.layout`** -- a project's is `slosh.layout`. The syntax is still the same KDL subset the config uses, one parser reads both files, and for a while the files were `*.layout.kdl` on the argument that the extension should say so. The argument lost to a collision: zellij speaks the same syntax under that same extension with a different schema, and to anyone arriving from there -`.kdl` said *whose* file it is — naming the wrong owner is worse than not +`.kdl` said *whose* file it is -- naming the wrong owner is worse than not naming the parser. The name says what the file is; `slosh --check` says how -it was read. (Point your editor's KDL highlighting at `*.layout` — it *is* +it was read. (Point your editor's KDL highlighting at `*.layout` -- it *is* still KDL underneath.) The old spelling is still found by the project scan, never written. @@ -38,8 +38,8 @@ it: a file with `layout` or `tab` at the top of it is checked as a layout, and a config handed to the same flag is still checked as a config. A file too broken to parse at all is judged by its name, because by then there is nothing else left to read. One flag rather than a `--check-layout` beside it: naming the schema is the -part you wanted the checker to do, and the answer it used to give — `this is a -layout, not a config` — replied to a question about a file with the name of +part you wanted the checker to do, and the answer it used to give -- `this is a +layout, not a config` -- replied to a question about a file with the name of another flag. ``` @@ -50,7 +50,7 @@ project.layout: ok, a layout A clean layout says which schema it passed, because otherwise a layout nothing could open and a layout with nothing wrong in it read the same. A broken one reports every problem it found, one indented `file:line: what` per line, then a -summary, and exits 1 — the shape [checking a config](config.md#checking-one) +summary, and exits 1 -- the shape [checking a config](config.md#checking-one) already has, so one editor compile step and one pre-commit hook read both: ``` @@ -67,7 +67,7 @@ broken.layout: 6 problems Those are the mistakes a layout can make that still parses: a property no pane has, a `split=` that is neither direction, a `weight=` that is not a number the engine will honour, a `true`/`false` written some other way, a `purpose=` on a -split — which would tag nothing, because a split runs nothing — and a child node +split -- which would tag nothing, because a split runs nothing -- and a child node that is not a `pane`. Last comes `this layout declares no tabs`, reported at the end rather than first so it reads as the summary it is. @@ -85,29 +85,29 @@ which file is which. ## The shape -- `tab` — one per tab, in strip order. `name=`, `cwd=`, `purpose=`, +- `tab` -- one per tab, in strip order. `name=`, `cwd=`, `purpose=`, `active=true` for the one you land in. -- `pane` — a leaf, or a split when it has `pane` children. `split="cols"|"rows"` +- `pane` -- a leaf, or a split when it has `pane` children. `split="cols"|"rows"` says which way it divides; `weight=` is its share of the parent (even shares are equal weights, so leaving it out means "even"). -- `command=` — what it runs. Without one, a pane runs your shell. -- `cwd=` — where it starts. Inherited from the tab when the pane does not say. -- `suspended=true` — laid out but running nothing until you touch it, so twelve +- `command=` -- what it runs. Without one, a pane runs your shell. +- `cwd=` -- where it starts. Inherited from the tab when the pane does not say. +- `suspended=true` -- laid out but running nothing until you touch it, so twelve checked-out projects are not twelve running dev servers. The pane shows what it *would* run. -- `focus=true` — the pane you start in, within its tab. -- `floating=true` — the pane starts [floating](panes.md#floating-a-pane), with +- `focus=true` -- the pane you start in, within its tab. +- `floating=true` -- the pane starts [floating](panes.md#floating-a-pane), with `x=` `y=` `w=` `h=` as its wanted rect in cells (absent, it takes the centred default). The rect is intent: a smaller screen clamps it, and the place you - asked for survives. It still holds a seat in the tree — its `weight=` and + asked for survives. It still holds a seat in the tree -- its `weight=` and position are where un-floating lands it. A file whose every pane floats lands one as the backdrop, because an overlay needs something to be over. -- `purpose=` — a label for tooling; see below. +- `purpose=` -- a label for tooling; see below. **A relative `cwd=` in a layout *file* resolves against that file's own directory**, never against the directory you started the session from. It is the rule [`include`](config.md#include) already follows for configs, applied to the -other half of the same syntax — the two documents share a parser, so they should +other half of the same syntax -- the two documents share a parser, so they should not disagree about what a relative path is. A layout with no `cwd=` anywhere starts in the file's directory too, so the common case needs no `cwd=` at all. An absolute path is unchanged, and `~` is still your home directory. @@ -116,7 +116,7 @@ That is what lets a layout be checked in beside the project it describes and mea the same thing in everybody's clone: ```kdl -// ~/dev/api/slosh.layout — no absolute path in it anywhere +// ~/dev/api/slosh.layout -- no absolute path in it anywhere layout { tab name="api" cwd="." { pane command="nvim" @@ -129,8 +129,8 @@ layout { ``` Clone that repo somewhere else and the tab still opens on the checkout, with the -pane that said `src` still in the checkout's `src`. The alternative — absolute -paths, or a `$PROJECT` of our own to expand — makes the file personal to one +pane that said `src` still in the checkout's `src`. The alternative -- absolute +paths, or a `$PROJECT` of our own to expand -- makes the file personal to one machine, or makes you learn a variable to say "here". A layout that arrives as *text* over the socket has no file, and so no directory @@ -154,27 +154,27 @@ after a restart. is that loop, for when the thing you are rebuilding is slosh itself. **A pane is dumped with the command it is running**, whether a layout gave it one -or you typed it. `label` — what a layout said — comes first and outlives the +or you typed it. `label` -- what a layout said -- comes first and outlives the program that ran it, so a project's `command="npm run dev"` stays that in the file after the server has exited. Failing that, the pane's terminal is asked what owns it: a pty has a foreground process group, and that group *is* the job running in the pane. So arranging a project by hand and writing it down are one job rather -than two — split, start the dev server and the log tailer, `C-a W`, done. +than two -- split, start the dev server and the log tailer, `C-a W`, done. Three things are deliberately not a command. **A shell at a prompt**, because `command="zsh"` on every idle pane would restore a session where each shell runs inside a shell. **A background job**, because it does not own the terminal and a layout that resurrected `&` jobs in the foreground would describe a session nobody -had. And **an [ephemeral](panes.md#panes-that-were-given-a-command) pane** — the -editor `C-a e` opened — because restoring one reopens a file somebody finished +had. And **an [ephemeral](panes.md#panes-that-were-given-a-command) pane** -- the +editor `C-a e` opened -- because restoring one reopens a file somebody finished with. Argv is joined with shell quoting, since `command=` is handed to `/bin/sh -c`: `python3 -c 'import x; x.go()'` comes back as one argument rather than three. What a dump can honestly restore is the *shape*, and the command that made it. -What it cannot is the state inside a program — a shell's history, a half-written -commit message, an editor's undo — and it does not pretend otherwise. A pane +What it cannot is the state inside a program -- a shell's history, a half-written +commit message, an editor's undo -- and it does not pretend otherwise. A pane running the session's default shell is dumped as a pane with no command, so restoring gives you a fresh one. @@ -198,7 +198,7 @@ afterwards. `suspend` takes four words: | word | means | |---|---| -| `as-is` | what is suspended in the session right now — the default here, because a session dump should describe the session | +| `as-is` | what is suspended in the session right now -- the default here, because a session dump should describe the session | | `none` | everything comes back running | | `commands` | every pane that was given a command | | `all` | nothing starts until you touch it | @@ -207,8 +207,8 @@ afterwards. `suspend` takes four words: [workspaces](workspaces.md) saves with: the pane running this morning's dev server belongs in the file asleep, or opening the project starts a dev server every time. -The reply carries the document as `kdl` and counts beside it — `panes` and -`suspended` — so a caller can report "4 panes, 2 suspended" without parsing back +The reply carries the document as `kdl` and counts beside it -- `panes` and +`suspended` -- so a caller can report "4 panes, 2 suspended" without parsing back the document it was handed. A `tab` that does not exist is an error rather than an empty document, because an empty layout is a plausible answer for a tab you closed a minute ago and a silent one for a typo in an id. @@ -236,7 +236,7 @@ slosh cmd '{"cmd":"move-pane","id":3,"tab":2,"dir":"rows"}' # under it instead slosh cmd '{"cmd":"move-pane","id":3,"tab":0,"name":"logs"}' # into a tab of its own ``` -The pane keeps running: same pty, same scrollback, same process — a move is tree +The pane keeps running: same pty, same scrollback, same process -- a move is tree surgery, not a new pane and a funeral. The destination is a tab **id** rather than an index, because a tab whose last pane leaves is removed and every index after it shifts; an id survives that. @@ -247,18 +247,18 @@ first would zoom a pane that has left and the second would file the arrival in a strip nobody asked for. Which tab you are *looking* at does not change. `C-a >` and `C-a <` push the focused pane one tab along, `C-a b` gives it a tab of -its own, and a pane can be dragged onto a tab in the strip — see +its own, and a pane can be dragged onto a tab in the strip -- see [keys](keys.md#defaults) and [the mouse](panes.md#the-mouse). ## Purposes -A pane or tab can carry a `purpose=` label — `agent:main`, `logs`, `db` — for +A pane or tab can carry a `purpose=` label -- `agent:main`, `logs`, `db` -- for tooling to find it by. A purpose declared in a layout or over the control API is *locked*: a program inside the pane cannot overwrite it, so identity comes from the layout rather than from whatever the program decides to print. -`C-a P` gives the focused pane one from the keyboard — see -[keys](keys.md#defaults) — and **a purpose an operator typed counts as declared, +`C-a P` gives the focused pane one from the keyboard -- see +[keys](keys.md#defaults) -- and **a purpose an operator typed counts as declared, so it locks like one from a file**. The lock is not about which door the label came through, it is about the label coming from outside the pane at all: you named that pane `service:web` on purpose, and the shell in it printing a title is not an @@ -276,8 +276,8 @@ The [finder](panes.md#finding-a-pane) matches on purposes as well as titles, and ## When the layout is found rather than named -Everything above assumes you name the file. When it lives *in* the project — -`slosh.layout` beside the `.git` — you do not have to: `C-a w` lists the +Everything above assumes you name the file. When it lives *in* the project -- +`slosh.layout` beside the `.git` -- you do not have to: `C-a w` lists the projects under the roots you configured and opening one applies the layout it found there, or a default layout bound to that directory when the project has none, and `C-a W` writes this tab back out as that file. Same syntax, same diff --git a/docs/macos-release.md b/docs/macos-release.md index 24bcc04..48a001f 100644 --- a/docs/macos-release.md +++ b/docs/macos-release.md @@ -4,14 +4,14 @@ Signed, notarized macOS builds of slosh happen on a dedicated mac, not on a laptop. This file says what is on that machine, how it got there, and what to do when it stops working. -Which mac, and whose certificate, is in `macos-release.mk` — the one file of +Which mac, and whose certificate, is in `macos-release.mk` -- the one file of this flow that is not in the repo. Copy `macos-release.mk.example` to it and fill in three lines. Examples below call the builder `m1`, which is just what `BUILDER` happens to say; `$BUILDER` in a shell snippet means that host. ## Everyday use -From a checkout on any machine that can ssh to the builder — **including a +From a checkout on any machine that can ssh to the builder -- **including a Linux one**; see "Driving it from Linux" below: ```sh @@ -61,7 +61,7 @@ The two source modes are not interchangeable: | `REF=…` | a commit **origin publishes**, from a clean tree | the tag or short id | releases | `REF=` resolves through `git ls-remote origin`, so it builds what the server -has and refuses anything unpushed — including a local branch of the same name. +has and refuses anything unpushed -- including a local branch of the same name. That matters because the default mode rsyncs `.git` too, so the builder's `refs/heads/master` can be a commit only your laptop has ever seen. An artifact named after a commit nobody can fetch is worse than no artifact. @@ -71,8 +71,8 @@ Then the release continues: `contrib/brew-release --tap … v0.2.0`. ## Driving it from Linux Works, and is tested: `make -f Makefile.macos remote-dist` from a Debian box -produced a notarized `slosh-78ad926-macos-arm64.zip`, and that artifact — after -a round trip through Linux — still verifies as `accepted / source=Notarized +produced a notarized `slosh-78ad926-macos-arm64.zip`, and that artifact -- after +a round trip through Linux -- still verifies as `accepted / source=Notarized Developer ID` on a mac. This end needs only **ssh, rsync, git, GNU make** and something that can hash a @@ -82,7 +82,7 @@ file (`sha256sum`, `shasum` or `openssl`). What it does *not* need is a mac: tools, so the artifact is unpacked and assessed there, before it is fetched. The verdict is the same command on the same machine no matter who asked. * **This end hashes what arrives** and compares it to the hash the builder - vouched for. Same bytes, same verdict — that is the whole of what a + vouched for. Same bytes, same verdict -- that is the whole of what a non-mac can honestly check, and it is enough. The scripts come with the checkout; add a `macos-release.mk` and point its @@ -119,10 +119,10 @@ not an accident. Two things make it usable headlessly, and both are easy to lose: -* `security set-keychain-settings` with **no arguments** — no idle timeout and +* `security set-keychain-settings` with **no arguments** -- no idle timeout and no lock on sleep. The default six-hour timeout would relock the keychain mid-notarization for no visible reason. -* `security set-key-partition-list -S apple-tool:,apple:,codesign:` — without +* `security set-key-partition-list -S apple-tool:,apple:,codesign:` -- without it macOS wants a click before letting `codesign` use the key, and over ssh that is simply a failure. @@ -147,14 +147,14 @@ NOTARY_KEY_ID=XXXXXXXXXX NOTARY_ISSUER=<issuer-uuid from App Store Connect> ``` -That file, and not the script, is where the credential lives — which is why +That file, and not the script, is where the credential lives -- which is why the scripts contain no secrets and are in the repo. ### How the private key is moved `security export` hands over **every** identity in the login keychain, which on a laptop also means localhost dev certs and whatever Compressor once installed. -The setup script exports the bundle, then picks out the one Developer ID — +The setup script exports the bundle, then picks out the one Developer ID -- pairing certificate to key by `localKeyID`, because the friendly name on a key bag is the *person*, not the certificate, and name matching alone picks the wrong key on a machine with more than one identity. It rebuilds a `.p12` @@ -182,7 +182,7 @@ Apple's log for the submission id rather than shipping it. `make -f Makefile.macos notary-status` on the builder lists recent submissions. **`spctl --assess --type exec` says "does not seem to be an app".** That is not -a failure — it is what Gatekeeper says about every bare CLI binary ever signed, +a failure -- it is what Gatekeeper says about every bare CLI binary ever signed, because `--type exec` only understands app bundles. The check that means something, and the one `contrib/macos-remote-build` runs on the builder for every artifact, is the one a downloaded file gets: @@ -194,7 +194,7 @@ spctl -a -t open --context context:primary-signature -vv slosh ``` That path looks Apple's ticket up online, so "Notarized Developer ID" means a -stranger's mac will run it — not merely that we submitted something. +stranger's mac will run it -- not merely that we submitted something. ## What is deliberately not here diff --git a/docs/panes.md b/docs/panes.md index ea03566..d72471e 100644 --- a/docs/panes.md +++ b/docs/panes.md @@ -20,7 +20,7 @@ looking at is drawn. - **Resize** with `C-a H J K L`: the boundary moves the way you press, whichever side of it you are on. Sizes are *weights*, so a resized layout survives the window changing size. -- **`C-a 0`** gives every visible pane an even share again — each split divided +- **`C-a 0`** gives every visible pane an even share again -- each split divided between the panes behind it, so one pane beside a stack of three is a quarter of the width rather than half of it. - **`C-a Space`** turns the whole tab a quarter turn clockwise. Every split @@ -29,8 +29,8 @@ looking at is drawn. A turn that would squeeze a pane under the floor is refused with a word. - **`C-a z`** fills the tab with one pane and back; **`C-a m`** puts one away into a strip along the bottom, still running. Each put-away pane sits there - as a **miniature pane frame** — a frame is what a pane looks like here, so - a shrunken one reads as exactly what it is — and clicking it anywhere + as a **miniature pane frame** -- a frame is what a pane looks like here, so + a shrunken one reads as exactly what it is -- and clicking it anywhere brings the pane back: ``` @@ -44,12 +44,12 @@ looking at is drawn. Every mouse verb starts from the same list of rects the drawing filled in, so nothing you click can disagree with what is on screen. -- **Click the middle of a pane's border to split toward it** — the side you click +- **Click the middle of a pane's border to split toward it** -- the side you click is the side the new pane appears on. Only the middle of an edge splits: three to seven cells of it, called the handle, because a whole edge is a long target to brush past and a changed layout is an expensive accident. Resting anywhere on an edge fills the handle in solid, with an arrow in it for the way it will - split — a cell wider on the left and right edges, and no taller on the top and + split -- a cell wider on the left and right edges, and no taller on the top and bottom ones, since a row is about twice a column and a two-row bar reads as a wall. Resting *on* the handle adds a dashed line where the new boundary will land. The rest of the edge does nothing when clicked. @@ -58,23 +58,23 @@ nothing you click can disagree with what is on screen. of the row as the row allows: centred when the row is empty, and against the title when the title is holding the middle, since the title keeps its own cells for the double-click rename. Arming an edge leaves the name, the buttons and a - dead pane's epitaph legible — the heavy rule goes round them. + dead pane's epitaph legible -- the heavy rule goes round them. - **Drag the gap between panes** to move that boundary. Where two gaps cross, drag the crossing to move both at once. With `gap 0` there is no gap to - grab, so grabbing the border itself and pulling does the same thing — a + grab, so grabbing the border itself and pulling does the same thing -- a border press that never moves is still the click that splits. - **[`compact true`](config.md#compact)** trades the gaps for shared 1-cell - divider lines — tmux-style density, same mouse: dividers drag, crossings + divider lines -- tmux-style density, same mouse: dividers drag, crossings drag both ways, titles ride the line above their pane. Interior edges give up click-to-split (the line is all resize handle); the outer frame keeps it. -- **Drag a pane by its title** onto another to swap them — or to *place* it: +- **Drag a pane by its title** onto another to swap them -- or to *place* it: the pane under the pointer subdivides into a centre and four edge bands, and where you let go is what the drop means. The centre trades places, as it always has; a band means "insert me beside this pane, on this side", and fills the half of the pane the drop would hand over, so what you see - is where it lands. A side there is no room for offers no band — the same - floor a border click's split honours — except between neighbours along the + is where it lands. A side there is no room for offers no band -- the same + floor a border click's split honours -- except between neighbours along the same row or column, where a drop is a reorder and always fits. Everything it could land on greys out; the pane in your hand keeps its colour. Between the bands and the strip, the whole layout can be rebuilt without touching the keyboard. @@ -83,12 +83,12 @@ nothing you click can disagree with what is on screen. one under the pointer fills; the tab it already lives in does not offer itself. A toast says where it went, since the pane lands somewhere you are not looking. - **Name a pane and it stops listening to its program.** `pane_title` prefers a - name while there is one, so a program that keeps announcing something stale — - an agent still spinning the summary of a task it finished — is overruled by + name while there is one, so a program that keeps announcing something stale -- + an agent still spinning the summary of a task it finished -- is overruled by naming its pane, from the gesture below or from a script: `slosh cmd '{"cmd":"set-name","target":"pane","id":3,"name":"agent: gbos"}'`. An empty name hands the label back. -- **Double-click a name to rename it in place** — a pane's title or a tab in the +- **Double-click a name to rename it in place** -- a pane's title or a tab in the strip. Enter keeps it, Escape abandons it, `C-u` clears the field (a rename seeds with the current label, since it is usually an edit), empty gives it back to the program. The same two verbs are in the palette as `rename-pane` @@ -103,7 +103,7 @@ nothing you click can disagree with what is on screen. frame's corner to minimise (`▬`), zoom (`□`) or close (`✕`) it. - **A tab in the strip has no `✕`** and closes with `C-a X` instead. A mark per tab would cost two columns of every tab on every frame, forever, for a verb pressed - rarely — the same arithmetic that took the per-pane `+` away. A pane's marks + rarely -- the same arithmetic that took the per-pane `+` away. A pane's marks earn their cells because there is one set of them and it is where you are looking. - Hovering anything says what it does, in a word, in the status line. @@ -111,16 +111,16 @@ nothing you click can disagree with what is on screen. ## Floating a pane `C-a f` lifts a pane out of the layout and draws it on top of the tiled ones -— below the modals — where it can be moved and shaped freely. It **pops to +-- below the modals -- where it can be moved and shaped freely. It **pops to the centre of the tab**, because the jump is what says the float happened; a pane lifted in place would look like a pane whose neighbours flinched. Pressing `f` again puts it back in the seat it kept, with the layout exactly as it was. (`f` is what "float" sounds like; finding a pane is `C-a s`, search.) `C-a F` opens a **new floating shell** over whatever you are doing -— the throwaway terminal — in the focused pane's directory; `exit` closes it +-- the throwaway terminal -- in the focused pane's directory; `exit` closes it like any shell, and un-floating lands it beside the pane it was opened over. -- **Drag its title to move it, any other border to resize it** — a bottom +- **Drag its title to move it, any other border to resize it** -- a bottom corner resizes on both axes. Hovering says so: the edge a grab would move goes heavy in the resize colour with a double arrow at the grab point (a corner lights both edges), and the held edges stay lit through the drag. @@ -141,11 +141,11 @@ like any shell, and un-floating lands it beside the pane it was opened over. every other pane. - Focusing a float raises it above other floats; minimising works as ever and it comes back floating; zooming fills the tab and returns it on the way - out. The last tiled pane cannot float — an overlay needs something to be - over — and closing the last tiled pane lands the float in its place. + out. The last tiled pane cannot float -- an overlay needs something to be + over -- and closing the last tiled pane lands the float in its place. - **It survives being written down.** `dump-layout` and - [`save-workspace`](workspaces.md) record `floating=true x= y= w= h=` — the - *wanted* rect, which the next screen's layout clamps like any frame — and a + [`save-workspace`](workspaces.md) record `floating=true x= y= w= h=` -- the + *wanted* rect, which the next screen's layout clamps like any frame -- and a [layout file](layouts.md) can declare the same. From a [script](scripting.md), `{"cmd":"float","id":N}` toggles, `{"cmd":"float","id":N,"x":10,"y":5,"w":40,"h":12}` places (floats first @@ -153,47 +153,47 @@ like any shell, and un-floating lands it beside the pane it was opened over. `{"cmd":"new-float"}` opens one. - **Images know their place.** A kitty-graphics picture in a pane a float covers is cropped at the float's edge, or not drawn while the float sits - across its middle — a placement is one rectangle, and it must not paint + across its middle -- a placement is one rectangle, and it must not paint over what covers it. ## Links -**OSC 8 hyperlinks pass through.** A program that emits real hyperlinks — -`ls --hyperlink`, gcc's diagnostics, delta — keeps them: the compositor +**OSC 8 hyperlinks pass through.** A program that emits real hyperlinks -- +`ls --hyperlink`, gcc's diagnostics, delta -- keeps them: the compositor carries the link beside each cell and re-emits it to your terminal, which offers it with its own gesture. Anything painted over a link (a frame, a modal, a floating pane) takes the link with it, so chrome is never clickable with a pane's URL. Links survive scrollback with the text that carries them. **Plain-text URLs are your terminal's own matcher**, running over what slosh -paints — with one catch worth knowing. While any program owns the mouse (a +paints -- with one catch worth knowing. While any program owns the mouse (a multiplexer does), Ghostty only offers links when **shift** is added to the usual gesture: shift is its mouse-capture escape, and it is stripped before -the link's own modifier is checked. So the gesture inside slosh — or tmux, -or zellij — is **shift+cmd+hover** to highlight and **shift+cmd+click** to +the link's own modifier is checked. So the gesture inside slosh -- or tmux, +or zellij -- is **shift+cmd+hover** to highlight and **shift+cmd+click** to open (shift+ctrl on Linux). A URL that *wraps* onto a second row cannot be -matched this way through any multiplexer — the screen is repainted row by -row, so the terminal sees two hard lines — which is exactly what OSC 8 +matched this way through any multiplexer -- the screen is repainted row by +row, so the terminal sees two hard lines -- which is exactly what OSC 8 links, which pass through whole, are for. ## Finding a pane Tabs stop being navigation somewhere around six projects. `C-a s` opens a picker -over the whole session — every tab, including panes a small window has collapsed +over the whole session -- every tab, including panes a small window has collapsed out of sight. Type to narrow it by pane title, tab name or [purpose](layouts.md#purposes); arrows, `C-n`/`C-p` or tab to move, `C-u` to clear what you typed, enter to go. A dot marks where you already are. -The finder searches what this session has; `C-a w` lists what is on disk — +The finder searches what this session has; `C-a w` lists what is on disk -- every project under your roots, open or not ([workspaces](workspaces.md)). A project you have not opened yet has no pane for the finder to match. ## Panes that were given a command Split off a shell, do something, type `exit`: it closes, like a terminal should. -But a pane that was *told to run something* — from a [layout](layouts.md) or the -[control API](scripting.md) — keeps what it printed when that something exits, +But a pane that was *told to run something* -- from a [layout](layouts.md) or the +[control API](scripting.md) -- keeps what it printed when that something exits, with two lines saying what ran and how it ended, and two buttons: ``` @@ -203,14 +203,14 @@ with two lines saying what ran and how it ended, and two buttons: ``` **The command is written down because `[re-run]` is one button.** Without it, -pressing it is a guess — most of all in a tab that a [project's +pressing it is a guess -- most of all in a tab that a [project's layout](workspaces.md) built, where the command was never typed into that pane to be scrolled back to. A pane running the session's shell has no command to name and gets the exit line alone. `[re-run]` (or `C-a r`) runs the same command again in the same pane, keeping the previous run above it in the scrollback. So the two notes turn a pane you re-run -into a log of what ran rather than a pile of identical epitaphs — and a command +into a log of what ran rather than a pile of identical epitaphs -- and a command that failed while you were looking elsewhere still has its error message when you get back, while a mistyped command in a fresh session no longer closes it. @@ -220,14 +220,14 @@ or `none`. ## Small terminals When the panes no longer fit, the tab becomes a list of one-line headers with -the focused pane open below them — and returns to exactly the layout you had +the focused pane open below them -- and returns to exactly the layout you had when there is room again. Nothing is stored and restored: the arrangement is recomputed from the tree every frame, and the list is what that function returns when the room runs out. `min_pane` is where that starts happening; `min_split` is the smaller pane a split is willing to *create*. A split is refused when either floor would be -broken — including the axis it does not divide, since splitting cannot improve +broken -- including the axis it does not divide, since splitting cannot improve that one: a pane already too short for two rows has no room for two columns either, whatever its width says. @@ -237,7 +237,7 @@ either, whatever its width says. sent to your terminal over ssh as well as locally. Middle click pastes. - **Double-click a word to select it**, and it is copied the same way a drag is: the highlight *is* the copy. What counts as one word is `word_separators`, - which lists what a word stops at rather than what it is made of — so + which lists what a word stops at rather than what it is made of -- so `src/app.c:1234`, `https://a.b/c?d=1`, `snake_case` and a run of CJK each come out whole, while a quote or a bracket ends one. A click on a blank cell or on a separator selects nothing and leaves your clipboard as it was. @@ -247,6 +247,6 @@ either, whatever its width says. each pane, so a program can size an image without saying how many cells to cover. PNG uploads are decoded here, because the terminal core ships no image decoder. (Sixel is not supported.) -- **A pane that beeps says so** — in its title, in its tab, and until you look at +- **A pane that beeps says so** -- in its title, in its tab, and until you look at it. `bell_indicator false` makes a bell silent *and* invisible, which is a real choice. A [chrome shader](chrome.md) can make it louder. diff --git a/docs/scripting.md b/docs/scripting.md index ccad91f..edcef85 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -21,7 +21,7 @@ Panes and tabs are addressed by **id**, so a background tab is scriptable. | verb | | |---|---| | `panes` `tabs` | what exists, with ids, rects, titles, purposes, state. A pane's `tab_id` is what `move-pane` and `select-tab` want; its `tab` is where that tab sits in the strip | -| `snapshot` | the composited screen, as JSON, `format:"text"`, or `format:"bytes"` — the emitter's own output for this frame (a second call is the delta) | +| `snapshot` | the composited screen, as JSON, `format:"text"`, or `format:"bytes"` -- the emitter's own output for this frame (a second call is the delta) | | `deadline` | when the session wants its next frame, in ms, or -1 | | `send` | bytes as if typed, decoded like input (`"data":"\\x01\\\\"`) | | `raw` | bytes straight into the focused pane's pty | @@ -29,16 +29,16 @@ Panes and tabs are addressed by **id**, so a background tab is scriptable. | `split` | `dir:"cols"\|"rows"`, `id` for which pane to split | | `focus` | `id` | | `close` `rerun` | `id`, or 0 for the focused pane | -| `clear-shaders` | `id`, or 0 for the focused pane; answers `cleared:0\|1` — the way back from a pane that painted itself unreadable | +| `clear-shaders` | `id`, or 0 for the focused pane; answers `cleared:0\|1` -- the way back from a pane that painted itself unreadable | | `new-tab` `select-tab` `close-tab` `move-tab` `set-name` | tabs, by `id` or `index` | | `set-name target="pane"` | a pane's name, which outranks the title its program sets | -| `move-pane` | `id` of the pane, `tab` id to move it into (`0` for a tab of its own, with an optional `name`), `dir:"cols"\|"rows"`. The pane keeps running — same pty, same scrollback | +| `move-pane` | `id` of the pane, `tab` id to move it into (`0` for a tab of its own, with an optional `name`), `dir:"cols"\|"rows"`. The pane keeps running -- same pty, same scrollback | | `float` | bare: toggle a pane [floating](panes.md#floating-a-pane) (`id`, or 0 for the focused one). With any of `x` `y` `w` `h` it *places*: floats first when tiled, re-places when floating, never un-floats; omitted fields keep their value | | `new-float` | a fresh floating shell over the current tab, centred, in the focused pane's directory; answers `id` | | `set-name` | `target:"tab"` (the default, because that is what this verb has always meant) or `"pane"`, `id`, `name`. A pane accepts 0 for the focused one. A pane's name wins over the title the program sets, so this is how a program that keeps announcing something stale gets overruled; an empty `name` clears it and hands the label back. Refusals say `no such pane` or `no such tab`, so a mistyped target is visible in the reply | -| `set-purpose` | `target:"pane"\|"tab"`, `id` (or 0 for the focused pane, and the tab you are in), `purpose`. An empty `purpose` clears the slot *and* unlocks it, handing the label back to the program — a lock held over an empty string was a state nothing could get out of | +| `set-purpose` | `target:"pane"\|"tab"`, `id` (or 0 for the focused pane, and the tab you are in), `purpose`. An empty `purpose` clears the slot *and* unlocks it, handing the label back to the program -- a lock held over an empty string was a state nothing could get out of | | `dump-layout` `apply-layout` | see [layouts](layouts.md). `dump-layout` takes `tab` (0 for every tab), `relative_to` to write every `cwd=` under that directory instead of absolute, and `suspend` (`as-is` `none` `commands` `all`); it answers `kdl` `panes` `suspended`, and an unknown `tab` is an error rather than an empty document | -| `workspaces` | the projects on disk and which of them are open: `roots` says whether any are configured at all, and each entry has `name` `path` `purpose` `layout` (a file path, or "") `mtime` `tab` (0 when closed) — see [workspaces](workspaces.md) | +| `workspaces` | the projects on disk and which of them are open: `roots` says whether any are configured at all, and each entry has `name` `path` `purpose` `layout` (a file path, or "") `mtime` `tab` (0 when closed) -- see [workspaces](workspaces.md) | | `open-workspace` | `name` or `path`, and `suspended`; answers `tab` `purpose` `path` `created` `tabs` `honoured`. Already open means focused, with `created:false` | | `close-workspace` | `name` or `purpose`; answers `closed`, how many tabs went | | `save-workspace` | write this tab as the project's layout: `tab` (0 for the current one), `path` for a tab that is not a workspace yet, `suspend`, and `force` to overwrite a layout the project already has; answers `path` `purpose` `panes` `suspended` `replaced` | @@ -53,7 +53,7 @@ Panes and tabs are addressed by **id**, so a background tab is scriptable. ## Driving a project -Everything a program needs in a project it has never seen — open it, ask what is +Everything a program needs in a project it has never seen -- open it, ask what is in it, act on the purposes the project's own layout declared: ```bash @@ -64,7 +64,7 @@ $ slosh -s work cmd '{"cmd":"open-workspace","name":"api"}' ``` **The second call focuses what is there and says `created:false`.** Opening is -idempotent, so a script drives it in a loop without asking first — and "have I +idempotent, so a script drives it in a loop without asking first -- and "have I opened this already" is the question a script gets wrong after a crash or a re-attach. @@ -83,13 +83,13 @@ that it starts asleep; nothing in the session, the config or the calling program had to know that. `workspaces` reports each project's layout file `mtime`, so a tool that kept the -mtime it opened a workspace with can tell the file has moved on since — without +mtime it opened a workspace with can tell the file has moved on since -- without the session storing a byte on its behalf. Re-applying the changed layout is deliberately not offered: the panes it would replace have processes in them. ## A pane can draw its own chrome -By printing an escape sequence — no plugin, no config: +By printing an escape sequence -- no plugin, no config: ```bash printf '\033]5577;1;status;building 3/7\033\\' @@ -145,8 +145,8 @@ reading it first. ## Headless `slosh --script` is the whole program without a terminal: commands on stdin, -answers on stdout. It is how the test suite works — drive these events, assert -this screen — which is also why the suite is 1,500-odd real end-to-end checks that +answers on stdout. It is how the test suite works -- drive these events, assert +this screen -- which is also why the suite is 1,500-odd real end-to-end checks that finish in about thirteen seconds. ```bash diff --git a/docs/shaders.md b/docs/shaders.md index ca93f3e..a455645 100644 --- a/docs/shaders.md +++ b/docs/shaders.md @@ -6,7 +6,7 @@ and before the chrome that goes over them, which is why "contents, not chrome" falls out of the paint order rather than needing a rule. For the frame, see [chrome shaders](chrome.md). -Cells only — foreground, background, attributes. Text and width are never +Cells only -- foreground, background, attributes. Text and width are never touched, because rewriting text would desync selection and copy. ```kdl @@ -55,33 +55,33 @@ shaders { | | | |---|---| -| variables | `x` `y` — the cell, inside the rect the pass runs over | -| | `cols` `rows` — the size of that rect | -| | `curx` `cury` `cursor` — the cursor, and whether this pane has it | -| | `focused` — 0 or 1 | -| | `t` — milliseconds, for animation | -| | `since` — milliseconds the pane has been in its current state | -| | `above` `below` — lines of scrollback hidden past the viewport's top and bottom edges, both 0 at the present | +| variables | `x` `y` -- the cell, inside the rect the pass runs over | +| | `cols` `rows` -- the size of that rect | +| | `curx` `cury` `cursor` -- the cursor, and whether this pane has it | +| | `focused` -- 0 or 1 | +| | `t` -- milliseconds, for animation | +| | `since` -- milliseconds the pane has been in its current state | +| | `above` `below` -- lines of scrollback hidden past the viewport's top and bottom edges, both 0 at the present | | operators | `+ - * / %` (integer; division by zero is 0, not a crash) | | | `< > <= >= == != && \|\| !` give 0 or 1, so `(x < 10) * 200` is a rule | -| | `& \| ^ ~ << >>` on the 32-bit value, binding **tighter** than comparison — so `x & 7 == 0` asks what it looks like, unlike C | -| | `a ? b : c` — both sides are evaluated, then one is chosen | +| | `& \| ^ ~ << >>` on the 32-bit value, binding **tighter** than comparison -- so `x & 7 == 0` asks what it looks like, unlike C | +| | `a ? b : c` -- both sides are evaluated, then one is chosen | | functions | `min(a,b)` `max(a,b)` `abs(a)` `clamp(v,lo,hi)` | -| | `dist(x1,y1,x2,y2)` — counts a row double, because a cell is about twice as tall as it is wide | -| | `sin(deg)` `cos(deg)` — **degrees** in, −255..255 out, so `128 + sin(t / 4) / 2` is a breathe | -| constants | `PI` = 180, `TAU` = 360 — pi as an *angle*, which is what it is here: a half turn | +| | `dist(x1,y1,x2,y2)` -- counts a row double, because a cell is about twice as tall as it is wide | +| | `sin(deg)` `cos(deg)` -- **degrees** in, −255..255 out, so `128 + sin(t / 4) / 2` is a breathe | +| constants | `PI` = 180, `TAU` = 360 -- pi as an *angle*, which is what it is here: a half turn | `PI` being a half turn is what makes a radian-shaped formula port across as written: `sin(TAU * x / cols)` is one cycle over the pane's width, `PI / 2` is a quarter turn, `PI / 6` is thirty degrees. It is also *more* exact than radians -could be in a language with no fractions — a sixth of a turn is 30 whole degrees, +could be in a language with no fractions -- a sixth of a turn is 30 whole degrees, where `3.14159 / 6` in integers is 0. For the same reason there is no `deg2rad` or `rad2deg`: there is one angle unit, and a conversion could only lose the angle or invent a second scale to disagree with. There are no loops and no recursion, on purpose: a config cannot spin and there is nothing to sandbox. An expression that does not compile drops that one shader -with a warning — never the config, and never a half-strength version of what you +with a warning -- never the config, and never a half-strength version of what you asked for. The expression produces the **strength**, never the colour. That keeps the mixing @@ -94,7 +94,7 @@ frame, which is the honest price of animation. ## Ready-made Thirty-two presets are in -`contrib/shaders` — +`contrib/shaders` -- a cursor line, a crosshair, a torch, a phosphor CRT, sonar pings that follow your cursor. Each is a file you can paste into your config or `include`. @@ -150,7 +150,7 @@ chrome> :load shine the same, for the presets that shi ``` `:paste` prints the document back as a `shaders { }` block with `where=` on every -entry — and that block can be typed straight back in, which is the point of +entry -- and that block can be typed straight back in, which is the point of borrowing the syntax rather than inventing one. A test reads it off the screen, clears the pane, types it back and compares the cells. @@ -160,7 +160,7 @@ counts (`1 chrome, 1 content`) say where the passes went, which is the only way see that an entry's `where=` did what you meant. Nothing in the prompt parses KDL. A file goes over by path and a pasted block via a -temporary file, because the session already has the parser — and a second reader of +temporary file, because the session already has the parser -- and a second reader of a config file would be a second opinion about what it says, exactly where comments meet quoted strings. @@ -187,6 +187,6 @@ the config exactly like a built-in. Skeleton, Makefile and the rules a shader has to keep: `contrib/shader-plugin`. -It is native code in the session's process, so install ones you trust — the same +It is native code in the session's process, so install ones you trust -- the same standing as `shell` and a layout's `command=`, which can already run anything as you. diff --git a/docs/workspaces.md b/docs/workspaces.md index b90c145..e91140d 100644 --- a/docs/workspaces.md +++ b/docs/workspaces.md @@ -5,7 +5,7 @@ session. Two words rather than one because the two have different lifetimes: the project outlives every session and is checked into git, the workspace lasts until you close the tab. -[Finding a pane](panes.md#finding-a-pane) starts from the complaint this answers — +[Finding a pane](panes.md#finding-a-pane) starts from the complaint this answers -- tabs stop being navigation somewhere around six projects. The finder answers *where is that pane*; this answers the question before it: which of the things you work on is this tab about, and what does opening one mean. `C-a w`, a name, enter, @@ -17,20 +17,20 @@ A subdirectory of a configured root with one of two markers in it: | marker | what it is | |---|---| -| `slosh.layout` | a *declared* project — it says what it needs open | -| `.git` | an *inferred* one — you work here, but you have not said how | +| `slosh.layout` | a *declared* project -- it says what it needs open | +| `.git` | an *inferred* one -- you work here, but you have not said how | Anything else is not a project, and nothing is guessed from what is inside it. **`.git` counts because otherwise the list is empty on the day you configure the feature.** A picker that only knows declared projects shows nothing until you have -written a layout, and there is nowhere to write one from — you would need the +written a layout, and there is nowhere to write one from -- you would need the picker to get there. The inferred half is also the invitation: such a project reads `.git · no layout` in the picker, and `C-a W` is how it stops being one. **A directory that is a project is never descended into.** That is what keeps the scan out of `node_modules`, a vendored checkout or a submodule without a rule about -any of their names — they are under a project, so the walk stops above them. A skip +any of their names -- they are under a project, so the walk stops above them. A skip list is a list of the directories that were fashionable when it was written; this holds for the ones nobody has invented yet. @@ -43,28 +43,28 @@ project_roots "~/dev" "~/work" depth=2 project_layout "~/.config/slosh/project.layout" ``` -`project_roots` takes more than one directory because people have more than one — -work in one tree, everything else in another — and `depth` is how many levels below +`project_roots` takes more than one directory because people have more than one -- +work in one tree, everything else in another -- and `depth` is how many levels below each root to look, 2 by default, 1 to 8. Two is `~/dev/thing` and `~/dev/org/thing`, which is where checkouts actually sit. **No roots configured and the feature is dormant.** Nothing is scanned, `C-a w` says which setting is missing rather than opening an empty picker, and `workspaces` -answers `roots: false` — because "you have no projects" and "you never said where +answers `roots: false` -- because "you have no projects" and "you never said where they are" are different facts, and a tool that cannot tell them apart reports the wrong one. `project_layout` is what a project with no file of its own opens as. **Relative paths in it bind to the project being opened, not to the directory the file lives -in** — deliberately the opposite of the rule for a layout you name yourself, where +in** -- deliberately the opposite of the rule for a layout you name yourself, where a relative `cwd=` is relative to that file ([layouts](layouts.md#the-shape)). The whole point of one shared layout is that `cwd="."` means *this* project; bound to itself it would open every project in `~/.config/slosh`. Unset, a project opens as one pane running your shell in it. **Nothing sniffs `package.json` or `Cargo.toml` to guess what to run.** A table -mapping manifests to commands is wrong for somebody the day it ships — the repo -with two package managers, the one whose dev server is behind a make target — and +mapping manifests to commands is wrong for somebody the day it ships -- the repo +with two package managers, the one whose dev server is behind a make target -- and stale for everybody a year later. `project_layout` is that table with your name on it, covering the projects you have rather than the ones a heuristic imagined, and a project that disagrees with it carries its own file. @@ -80,10 +80,10 @@ project that disagrees with it carries its own file. `w` and `W` are the same shifted pair as `p`/`P`: go to one, write one down. All three are bindable as `workspaces`, `save-workspace` and `set-purpose`, and all three are in the palette under `projects`, so they work before you have rebound -anything — see [keys](keys.md#defaults) and [every action](keys.md#every-action). +anything -- see [keys](keys.md#defaults) and [every action](keys.md#every-action). The picker narrows on a project's name, its path and which marker it has, so typing -`.git` gives you the projects with no layout yet — which is the list you want when +`.git` gives you the projects with no layout yet -- which is the list you want when you are about to write one. A project that is already open shows its purpose instead, and a dot marks the workspace you are in. @@ -101,7 +101,7 @@ Tomorrow, `C-a w Enter` rebuilds that tab with the dev server in the pane you pu it in and not running. A tab with no name of its own takes the project's, because a strip reading `1 2 3` is the thing workspaces are for. -**Closing one is `C-a X`**, which closes the tab and the workspace with it — +**Closing one is `C-a X`**, which closes the tab and the workspace with it -- membership *is* the tab's purpose, so there is nothing else to put away and no second key to learn ([keys](keys.md#defaults)). `close-workspace` over the socket is the same thing for a workspace whose layout opened several tabs: it closes @@ -117,7 +117,7 @@ and the toast says what happened: the same act as arranging one: split the tab, start the dev server, start the log tailer, tag the ones that matter, `C-a W`. A pane's terminal has a foreground process group and that group is the job running in it, so nothing has to be -declared up front for it to be recorded — see +declared up front for it to be recorded -- see [writing one back out](layouts.md#writing-one-back-out) for what is deliberately *not* counted as a command (a shell at a prompt, a background job, an ephemeral pane). @@ -127,16 +127,16 @@ where the two part company. It takes one of four values: | `suspend` | writes `suspended=true` on | |---|---| -| `as-is` | whatever is suspended right now — the default for `dump-layout` | +| `as-is` | whatever is suspended right now -- the default for `dump-layout` | | `none` | nothing | -| `commands` | every pane that has a command — the default for `save-workspace` | +| `commands` | every pane that has a command -- the default for `save-workspace` | | `all` | every pane | The defaults differ because the two verbs answer different questions. [`dump-layout`](layouts.md#writing-one-back-out) is *put this session back*, so it reports the state it found. `save-workspace` is *this is what this project needs open*, and the pane running this morning's dev server is a pane that should be laid -out tomorrow rather than started — twelve checked-out projects are not twelve +out tomorrow rather than started -- twelve checked-out projects are not twelve running dev servers ([layouts](layouts.md#the-shape)). A suspended pane still shows what it would run, so the layout stays readable as a record of the commands. @@ -148,8 +148,8 @@ otherwise: a shell's history and an editor's buffers are not in it. That asymmetry is not an oversight in either direction. A key cannot pass a flag, and the operator pressing it is looking at the tab being written; the file is committed text, so the diff is the confirmation and the replacement stays -reviewable after the fact. A script has none of that — no witness, no glance at -the screen — so it says so in the request. +reviewable after the fact. A script has none of that -- no witness, no glance at +the screen -- so it says so in the request. `save-workspace` also takes `path`, for a tab that is not a workspace yet: that is how a hand-arranged tab becomes a project's layout without opening it through the @@ -157,7 +157,7 @@ picker first. `slosh --check slosh.layout` holds the result to the layout schema and prints one `file:line: what` per problem, so a project's layout goes through the -same check as everything else you commit — see +same check as everything else you commit -- see [layouts](layouts.md#what-to-call-them). ## Identity @@ -169,11 +169,11 @@ project:api.9f3c1d20 ``` The hash is of the resolved absolute path, not of the name, so two git worktrees of -one repo are two workspaces — which is what they are, and a name-keyed session +one repo are two workspaces -- which is what they are, and a name-keyed session would have merged them and then focused the wrong one. Membership is that string and nothing else: no side table of open projects to fall out of step with the tabs. -Because the session declared it, it is **locked** — no program running in a pane +Because the session declared it, it is **locked** -- no program running in a pane can relabel a project tab, however chatty its title. That is the same rule any declared purpose gets, and it is what makes the purpose safe for a tool to address. @@ -187,14 +187,14 @@ built and deliberately not claimed. Discovery is derived, never remembered: one `readdir` per root plus two `stat`s per entry, run when the picker opens rather than per frame. **Nothing is stored, so -nothing can be stale** — and no watcher can promise that. Inotify does not fire for +nothing can be stale** -- and no watcher can promise that. Inotify does not fire for a bind mount, an sshfs share or a checkout another machine wrote, so a cache that trusted it would be wrong exactly when the answer mattered. One thing a scan cannot see is a project's layout file changing under a workspace that is already open. `workspaces` reports each layout's `mtime`, so a tool that keeps the mtime it opened with can compare and notice. Re-applying the new file over -the running panes is deliberately not offered — it would kill them. +the running panes is deliberately not offered -- it would kill them. ## From another program @@ -209,9 +209,9 @@ $ slosh -s work cmd '{"cmd":"panes"}' # keep the ones whose tab_id is 3 | verb | takes | answers | |---|---|---| -| `workspaces` | — | `roots`, and a `workspaces[]` of `name` `path` `purpose` `layout` (the file, or `""`) `mtime` `tab` (0 when it is not open) | +| `workspaces` | -- | `roots`, and a `workspaces[]` of `name` `path` `purpose` `layout` (the file, or `""`) `mtime` `tab` (0 when it is not open) | | `open-workspace` | `name` or `path`; `suspended` | `tab` `purpose` `path` `created` `tabs` `honoured`. Already open, it focuses that tab and answers `created:false` | -| `close-workspace` | `name` or `purpose` | `closed` — how many tabs went | +| `close-workspace` | `name` or `purpose` | `closed` -- how many tabs went | | `save-workspace` | `tab` (0 for the current one), `path` for a tab that is not a workspace yet, `suspend`, `force` | `path` `purpose` `panes` `suspended` `replaced` | Every one of them is in the bare dispatcher too, so `slosh -s work cmd @@ -221,7 +221,7 @@ without quoting JSON ([scripting](scripting.md#the-control-socket)). `open-workspace` is idempotent, which is the property that makes it safe to call from a script that does not know whether it ran already: asking twice focuses the tab rather than building a second one. `suspended:true` opens the layout with every -pane suspended whatever the file said — the *open ten projects, run zero processes* +pane suspended whatever the file said -- the *open ten projects, run zero processes* case, which is a different question from the one the project answered about which of its own panes are expensive. |
