aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Geissler <peter@haxx.in>2026-09-02 11:55:35 +0200
committerPeter Geissler <peter@haxx.in>2026-09-02 11:55:35 +0200
commit93e8b6a98de3f9e8f3176199e7974f4bc8a66a1f (patch)
tree08ac7f10722b6f324febd1b8ea3d06f371b5ccb9
parentweb: the hero button says try it, like the section it goes to (diff)
downloadslosh-93e8b6a98de3f9e8f3176199e7974f4bc8a66a1f.tar.gz
slosh-93e8b6a98de3f9e8f3176199e7974f4bc8a66a1f.tar.xz
slosh-93e8b6a98de3f9e8f3176199e7974f4bc8a66a1f.zip
docs: big overhaul
-rw-r--r--docs/actions.md107
-rw-r--r--docs/config.md153
-rw-r--r--docs/index.md36
-rw-r--r--docs/keys.md153
-rw-r--r--docs/layouts.md48
-rw-r--r--docs/nav1
-rw-r--r--docs/panes.md173
-rw-r--r--docs/shaders.md156
8 files changed, 357 insertions, 470 deletions
diff --git a/docs/actions.md b/docs/actions.md
new file mode 100644
index 0000000..85f11a2
--- /dev/null
+++ b/docs/actions.md
@@ -0,0 +1,107 @@
+# Actions
+
+An **action** is a thing slosh can do to a pane, a tab, or the session. split,
+zoom, next tab, quit, etc. Every action has a name, and that name is what you bind a
+key to ([keys](keys.md)) or type into the command palette (`C-a p`).
+
+Everything below can be bound, and everything below can be run from the palette
+without being bound at all. The **key** column is the default binding, if there
+is one; an action with no default key is reached from the palette until you give
+it one.
+
+## Panes
+
+| action | key | does |
+| ------------------ | ------- | ---------------------------------------------- |
+| `split` | `Enter` | split whichever way there is more room |
+| `split-cols` | `\` | split into columns |
+| `split-rows` | `-` | split into rows |
+| `close-pane` | `x` | close this pane |
+| `rerun` | `r` | run a finished pane's command again |
+| `zoom` | `z` | fill the tab with this pane, and back |
+| `minimize` | `m` | put it away in the strip along the bottom |
+| `float` | `f` | [float](panes.md#floating-a-pane) it, and back |
+| `new-float` | `F` | a new floating shell, the throwaway terminal |
+| `set-purpose` | `P` | tag it with a [purpose](layouts.md#purposes) |
+| `rename-pane` | — | rename this pane (double-click does it too) |
+| `clear-shaders` | — | clear whatever the program painted on it |
+| `pane-to-next-tab` | `>` | push it to the tab after |
+| `pane-to-prev-tab` | `<` | push it to the tab before |
+| `pane-to-new-tab` | `b` | break it out into a tab of its own |
+
+## Focus
+
+| action | key | does |
+| ------------- | -------- | ------------------- |
+| `focus-left` | `h`, `←` | move focus left |
+| `focus-right` | `l`, `→` | move focus right |
+| `focus-up` | `k`, `↑` | move focus up |
+| `focus-down` | `j`, `↓` | move focus down |
+| `focus-next` | `o` | the next pane |
+| `finder` | `s` | find a pane by name |
+
+## Size
+
+| action | key | does |
+| --------------- | -------------- | -------------------------------------------------------- |
+| `resize-left` | `H`, `shift+←` | move the boundary left, or the pane if it is floating |
+| `resize-right` | `L`, `shift+→` | move the boundary right |
+| `resize-up` | `K`, `shift+↑` | move the boundary up |
+| `resize-down` | `J`, `shift+↓` | move the boundary down |
+| `equalize` | `0` | give every visible pane an even share |
+| `float-grow` | `=` (`+`) | grow a focused float about its centre |
+| `float-shrink` | `-` | shrink a focused float (through `split-rows` when tiled) |
+| `rotate-layout` | `Space` | turn the layout a quarter turn (four brings it back) |
+
+## Tabs
+
+| action | key | does |
+| ------------------------------- | ----------- | --------------- |
+| `new-tab` | `c` | new tab |
+| `rename-tab` | — | rename this tab |
+| `close-tab` | `X` | close this tab |
+| `next-tab` | `Tab` | next tab |
+| `prev-tab` | `shift+Tab` | previous tab |
+| `select-tab-1` … `select-tab-9` | `1` … `9` | go to that tab |
+
+## Scroll
+
+| action | key | does |
+| ------------------ | ------ | ------------------- |
+| `scroll-up` | — | up a line |
+| `scroll-down` | — | down a line |
+| `scroll-page-up` | `PgUp` | up a page |
+| `scroll-page-down` | `PgDn` | down a page |
+| `scroll-top` | `Home` | to the oldest line |
+| `scroll-bottom` | `End` | back to the present |
+
+## Projects
+
+| action | key | does |
+| ---------------- | --- | --------------------------------------------- |
+| `workspaces` | `w` | go to a project ([workspaces](workspaces.md)) |
+| `save-workspace` | `W` | write this tab out as this project's layout |
+
+## Session
+
+| action | key | does |
+| ---------------- | ------------ | ------------------------------------- |
+| `palette` | `p` | the command palette |
+| `help` | `?` | the cheatsheet |
+| `edit-config` | `e` | edit the config, in a pane |
+| `detach` | `d` | detach, leave it running |
+| `quit` | `q` | quit the session |
+| `literal-prefix` | leader twice | send the leader itself to the program |
+
+## Notes
+
+`scroll-up` and `scroll-down` have no default key — the mouse 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
+`in_band_shaders` on. `rename-pane` and `rename-tab` are unbound too, because a
+double-click on the title is the fast path.
+
+`>` and `<` are the shifted period and comma. A terminal does not report the
+shift for punctuation, so `.` and `,` answer to them as well: the same deal `/`
+has with `?`, and the reason the defaults bind both halves.
diff --git a/docs/config.md b/docs/config.md
index d768e3a..4e5b1cc 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -7,33 +7,19 @@ $SLOSH_CONFIG, else $XDG_CONFIG_HOME/slosh/config.kdl, else
~/.config/slosh/config.kdl
```
-`C-a e` opens it in a pane, writing a starting file first if you have none.
+`C-a e` (`edit_config`) opens it in a pane, writing a starting file first if you have none.
-**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
-which line it could not honour.
+**Saving it applies to every running session immediately.**
-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
-(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
-session.
+Some tips:
-## 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
- 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).
+ `slosh --dump-config > ~/.config/slosh/config.kdl`.
+- To see what your own file does, `slosh --check` reads it and reports what it understood.
-## Checking one
+## Sanity checking your slosh config
```bash
$ slosh --check # the config a session would read
@@ -56,27 +42,17 @@ 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
-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: a session's status line shows the first problem, the checker shows
-them all.
+files, the prefix it ended up with, how many bindings).
## include
-A config can be made of files:
+A config can be composed of multiple files:
```kdl
include "themes/amber.kdl"
include "keys/vim.kdl" "shaders/crt.kdl"
```
-which is how you switch a theme by editing one line rather than pasting one in.
-Nothing about it is theme-specific: an include is another config file applied
-here, so the same line composes keys, shader chains, states or geometry.
-
- A relative path is relative to **the file doing the including**, never to the
directory you started the session from. `~` is your home directory, an absolute
path is itself, and an included file may include others.
@@ -89,9 +65,7 @@ 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
-them, because six names doing thirty jobs meant the split guide could not be
-recoloured without also recolouring the focused frame.
+Every colour the compositor draws has its own name under `theme { }`.
Six ready-made themes are in
`contrib/themes`:
@@ -104,26 +78,24 @@ include "~/.config/slosh/themes/phosphor.kdl"
theme { frame_focus "#00ff88" } // ...but that one colour is mine
```
-`contrib/theme-tour` cycles a running session through all of them.
-
## The knobs, briefly
-| group | what is in it |
-|---|---|
-| 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`) |
+| group | what is in it |
+| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| 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` `ctrl_d_exits` `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)) |
-| colour | `theme { }` |
-| effects | `shaders { }`, `states { }` (see [shaders](shaders.md) and [chrome](chrome.md)) |
-| keys | `keys { }` (see [keys](keys.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)) |
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
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`
+_means_ is on the [panes page](panes.md#small-terminals)), and `project_roots`
takes one path per argument; see [workspaces](workspaces.md).
`padding` is the space between a pane's frame and its contents, written the way
@@ -144,34 +116,25 @@ whose meaning you have to look up is a line nobody can read.
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
-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:
+Shared borders instead of gaps.
```
-╭────── nvim ───── ▬ □ ✕ ─┬─── npm run dev ── ▬ □ ✕ ─╮
-│ │ │
-│ ├────── shell ──── ▬ □ ✕ ─┤
-│ │ │
-╰──────────────────────────┴──────────────────────────╯
+╭────── nvim ───── ▬ □ ✕ ─┬── npm run dev ── ▬ □ ✕ ─╮
+│ │ │
+│ ├────── shell ──── ▬ □ ✕ ─┤
+│ │ │
+╰─────────────────────────┴─────────────────────────╯
```
What changes, and what does not:
-- `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.
+- `gap` stops applying.
- **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.
- **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
- 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
anything.
@@ -182,63 +145,43 @@ What changes, and what does not:
```kdl
scrollback 10000 // lines of history per pane; 0 keeps none
-scrollback_bytes 16777216 // ...and the ceiling that count runs into
+scrollback_bytes 16777216 // .. and the byte ceiling
```
-**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,
-less than one `make` run. `scrollback` is that number replaced with the one every
-other multiplexer settled on.
-
-Whichever limit bites first wins: a line count cannot know how wide your
-terminal is, and a byte count cannot know how many lines it bought. Both are
-estimates: history is pruned a page (about 400KB of grid) at a time, so a pane
-keeps a little more than it was told, never less.
-
-What the ceiling costs, measured on a pane filled with styled output:
-
-| pane width | 10,000 lines costs | with the default ceiling |
-|---|---|---|
-| 80 columns | ~6 MB | every line |
-| 200 columns | ~15 MB | about 2% fewer |
-| 400 columns | ~32 MB | about half |
-
-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
-a friendly name. `scrollback_bytes 0` removes the ceiling if you have one very wide
-pane and want every line of it.
-
-Both apply to **the next pane you open**, not to panes already holding history:
-shrinking one retroactively would throw away output somebody is reading.
-
## Pane states
-A pane is tinted by what is *true* of it, which is the part you cannot discover
-by looking:
+A pane can be tinted depending on what "state" it is in. This way you easily
+make different states visually apparent.
+
+For common tinting configuration we have some short hands:
```kdl
dim_unfocused 60 // the one knob most people want
float_shadow 110 // the shade a floating pane casts; 0 for none
+```
+But they can also be more fine grainly configured:
+
+```kdl
states { // ...and the whole table underneath it
dead { grayscale amount=200; dim amount=90 }
suspended { grayscale amount=170; dim amount=60 }
scrolled { tint amount=22 color="#7aa2f7" } // follows theme's scroll_bg;
- // plus derived edge fades: the top and bottom rows melt towards an edge
- // while there is more content past it (`above` / `below` in expressions),
- // and turn solid at the end of the buffer, nothing more to scroll to
unfocused { dim amount=60 } // writing this replaces dim_unfocused
// floating { } // a float; above unfocused, so floats are never dimmed
- // bell: a shimmer across the body (a soft sheen, gone within a second)
- // and a chrome pass on the frame (blinks, then a breathe) in theme's
- // bell colour, until the pane is looked at. See the chrome shaders page;
- // an empty bell { } removes it, bell_indicator false silences bell + mark.
}
```
-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 that reads as neither. Naming a state replaces its default outright, including
-with nothing, which is how you turn one off.
+The full ranking, most urgent first:
+
+- `dragging`
+- `drop_hover`
+- `drop_target`,
+- `dead`
+- `suspended`
+- `bell`
+- `scrolled`
+- `floating`
+- `unfocused`
+
+Exactly one wins, the first that matches.
diff --git a/docs/index.md b/docs/index.md
index 960ebf7..c46eb7a 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -5,6 +5,10 @@ in C on top of [libghostty-vt](https://github.com/ghostty-org/ghostty).
## Install
+### All platforms
+
+We provide binary builds for all supported platform. Get the latest slosh release binaries from our [release](https://github.com/sl0p-foo/slosh/releases) page.
+
### macOS
With [Homebrew](https://brew.sh):
@@ -16,20 +20,6 @@ brew install slosh
### Linux
-Build from source. That needs [zig](https://ziglang.org) 0.16 and nothing
-else: no cmake, no autotools, no libraries. The result is one static binary
-that runs on any distro.
-
-```bash
-make vendor # build the vendored terminal core (once, a couple of minutes)
-make all # a few seconds
-make test # optional: the whole suite, about thirteen seconds
-sudo make install
-```
-
-(The source build works on a mac too; Homebrew is simply the shorter road
-there.)
-
#### Arch
A pacman repository serves built, signed packages. Enroll the signing key
@@ -52,19 +42,17 @@ Server = https://slosh.foo/arch/$arch
and `pacman -Sy slosh`. Details, and the PKGBUILD for building it yourself:
`contrib/arch/README.md` in the source tree.
-### Windows
+### Source
-Windows 10/11, x86-64 and ARM64, as one `slosh.exe`. Panes are ConPTYs and
-sessions are AF_UNIX sockets, so everything documented here works there too.
-
-Cross-compiled from a mac or a Linux box, then copied across:
+Build from source. That needs [zig](https://ziglang.org) 0.16.
```bash
-make -f Makefile.windows ARCH=x86_64 # or ARCH=aarch64
+make vendor # build the vendored terminal core (once, a couple of minutes)
+make all # a few seconds
+make test # optional: the whole suite, about thirteen seconds
+sudo make install
```
-The details, and the three decisions the port rests on: [windows](windows.md).
-
## Run
```bash
@@ -76,12 +64,12 @@ slosh ls # what is running
Sessions keep running when you detach, or when your terminal or ssh connection
goes away. Reattach with the same command.
-Then: `C-a \` to split, `C-a ?` for the cheatsheet, `C-a p` for every command
-by name.
+**TIP:** press `C-a ?` for the key combo cheatsheet, or `C-a p` for the command palette.
## Where to go
- **[Keys](keys.md)**: the leader, the defaults, rebinding, the palette.
+- **[Actions](actions.md)**: every action you can bind or run from the palette.
- **[Panes and tabs](panes.md)**: splitting, moving, zooming, the mouse.
- **[Configuration](config.md)**: one file, live reload, themes, `include`.
- **[Shaders](shaders.md)**: colour passes over a pane's contents.
diff --git a/docs/keys.md b/docs/keys.md
index 84422d1..cee8862 100644
--- a/docs/keys.md
+++ b/docs/keys.md
@@ -3,48 +3,43 @@
Everything is the **leader** (`C-a` by default) and then a key. Pressing the
leader twice sends it to the program in the pane.
-`C-a ?` shows a cheatsheet built from the bindings your config actually has, so
-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.
+`C-a ?` shows a cheatsheet built from the bindings your config actually has. `C-a p` opens the same list as a palette you can type into.
## Defaults
-| key | does |
-|---|---|
-| `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) |
-| `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 |
-| `=` (`+`) `-` | 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 |
-| `r` | run a finished pane's command again |
-| `P` | set this pane's [purpose](layouts.md#purposes) |
-| `>` `<` | push this pane to the next / previous tab (a toast says where it went) |
-| `b` | break this pane out into a tab of its own |
-| `c` | new tab |
-| `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` | 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) |
-| `e` | edit the config, in a pane |
-| `?` | this list |
-| `d` | detach, leave it running |
-| `q` | quit the session |
-| `C-a` | send a literal `C-a` |
+| key | does |
+| -------------------------------- | ------------------------------------------------------------------------------------------------------- |
+| `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) |
+| `0` | give every visible pane an even share |
+| `Space` | turn the layout a quarter turn |
+| `z` | (un)zoom this pane |
+| `m` | minimize pane to bar in the bottom |
+| `f` | [float](panes.md#floating-a-pane) it above the layout, and back |
+| `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 |
+| `r` | run a finished pane's command again |
+| `P` | set this pane's [purpose](layouts.md#purposes) |
+| `>` `<` | push this pane to the next / previous tab (a toast says where it went) |
+| `b` | break this pane out into a tab of its own |
+| `c` | new tab |
+| `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` | 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) |
+| `e` | edit the config, in a pane |
+| `?` | this list |
+| `d` | detach, leave it running |
+| `q` | quit the session |
+| `C-a` | send a literal `C-a` |
**`x`/`X`, `p`/`P`, `w`/`W` and `f`/`F` are deliberately shifted pairs on one
letter.** Close this pane / close this tab; run a command / tag this pane; go
@@ -52,23 +47,15 @@ to a project / write this project down; float this pane / float a fresh one.
The shifted half is the same verb on a bigger thing, so the shift is less to
remember than a second letter would have been.
-`X` says how many panes went with the tab, since most of them were not the one you
-were looking at. On the **last** tab it refuses and points at `q` instead: a key
-that closes a tab four times and ends your session the fifth is one you cannot
-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 the default leader key, but if you want it back for start-of-line (and
+plenty of people do), it's easy to reconfigure:
```kdl
keys { prefix "ctrl+b" } // or ctrl+space, or alt+x, or ...
```
-Everything that names the prefix follows what you bound: the badge in the status
-bar, the cheatsheet's `C-a then:` caption, the palette's key column.
-
A `keys` block **adds to** the defaults rather than replacing them, so binding
one key leaves the rest alone. `"none"` takes a binding away:
@@ -79,17 +66,43 @@ 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,
+Modifiers are:
+
+- `ctrl`
+- `alt`
+- `shift`
+- `super`
+
+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`
+
+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
carry their own shift: `"?"` is `"shift+slash"`, `"H"` is `"shift+h"`, `"|"` is
-`"shift+backslash"`. A punctuation binding written that way answers both
-encodings, because whether your terminal reports `?` with a shift modifier or as
-a bare byte is up to your terminal and not to what you meant.
+`"shift+backslash"`.
`slosh --check` reads a config and says what it could not honour, one problem
per line with a file and a line number. Worth running after editing bindings by
@@ -114,29 +127,7 @@ under "without the leader", so the `C-a then:` caption above stays true.
## Every action
-Any of these can be bound, and any of them can be run from the palette without
+The name in the second half of a `bind` is an **action**. The full list, every action,
+its default key, and what it does can be found on the **[actions](actions.md)** page.
+Any of them can be bound here, and any of them can be run from the palette without
being bound at all.
-
-| group | actions |
-|---|---|
-| panes | `split` `split-cols` `split-rows` `close-pane` `rerun` `zoom` `minimize` `float` `new-float` `rotate-layout` `clear-shaders` `pane-to-next-tab` `pane-to-prev-tab` `pane-to-new-tab` `set-purpose` `rename-pane` |
-| focus | `focus-left` `focus-right` `focus-up` `focus-down` `focus-next` `finder` |
-| size | `resize-left` `resize-right` `resize-up` `resize-down` `equalize` `float-grow` `float-shrink` |
-| tabs | `new-tab` `rename-tab` `close-tab` `next-tab` `prev-tab` `select-tab-1` … `select-tab-9` |
-| projects | `workspaces` `save-workspace` |
-| scroll | `scroll-up` `scroll-down` `scroll-page-up` `scroll-page-down` `scroll-top` `scroll-bottom` |
-| 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
-`?`, and the reason the defaults bind both halves.
-
-`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
-`in_band_shaders` on.
-
-`workspaces`, `save-workspace` and `set-purpose` are the other way round: all
-three are bound by default, to `w`, `W` and `P`, so the palette is a second way
-to reach them rather than the only one.
diff --git a/docs/layouts.md b/docs/layouts.md
index e34de92..3818e77 100644
--- a/docs/layouts.md
+++ b/docs/layouts.md
@@ -81,7 +81,7 @@ having no tabs, which is true of it in the least useful way.
- `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.
+ _would_ run.
- `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. All four absent takes the
@@ -92,7 +92,7 @@ having no tabs, which is true of it in the least useful way.
lands one as the backdrop, because an overlay needs something to be over.
- `purpose=`: a label for tooling; see below.
-**A relative `cwd=` in a layout *file* resolves against that file's own
+**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, applied to the other
half of the same syntax. A layout with no `cwd=` anywhere
@@ -118,7 +118,7 @@ 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`.
-A layout that arrives as *text* over the socket has no file, and so no
+A layout that arrives as _text_ over the socket has no file, and so no
directory to be relative to; relative paths in it resolve against wherever the
session is.
@@ -141,7 +141,7 @@ is that loop, for when the thing you are rebuilding is slosh itself.
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
+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.
@@ -156,7 +156,7 @@ 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 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
running the session's default shell is dumped as a pane with no command, so
@@ -169,23 +169,23 @@ slosh -s work cmd '{"cmd":"dump-layout","tab":3}'
slosh -s work cmd '{"cmd":"dump-layout","relative_to":"~/dev/api","suspend":"commands"}'
```
-| argument | what it says |
-|---|---|
-| `tab` | a tab **id**, or `0` (the default) for the whole session |
-| `relative_to` | a directory every `cwd=` is written relative to |
-| `suspend` | which panes come back laid out but not running |
+| argument | what it says |
+| ------------- | -------------------------------------------------------- |
+| `tab` | a tab **id**, or `0` (the default) for the whole session |
+| `relative_to` | a directory every `cwd=` is written relative to |
+| `suspend` | which panes come back laid out but not running |
`relative_to` is the writing end of the reading rule above: point it at the
project and the dump comes out with `cwd="."` and `cwd="src"` in it rather than
your home directory, which is what makes the file committable without an edit
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 |
-| `none` | everything comes back running |
-| `commands` | every pane that was given a command |
-| `all` | nothing starts until you touch it |
+| word | means |
+| ---------- | ---------------------------------------------------------------------------------------------------------------- |
+| `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 |
`commands` is the one you want for a layout you will open often, and it is what
[workspaces](workspaces.md) saves with: the pane running this morning's dev server
@@ -226,7 +226,7 @@ shifts; an id survives that.
Two things a move deliberately drops, both of them the old tab's opinion rather
than the pane's: a zoom that named it, and its minimised flag. Carried across, the
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.
+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
@@ -236,25 +236,15 @@ its own, and a pane can be dragged onto a tab in the strip; see
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
+_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,
-so it locks like one from a file**: you named that pane `service:web` on
-purpose, and the shell in it printing a title is not an argument for renaming
-it.
-
-**Setting a purpose to nothing unlocks it**, from a layout, the API or `C-a P`
-with the field cleared, and hands the label back to the program to describe
-itself again — the same shape as clearing a pane's name.
-
The [finder](panes.md#finding-a-pane) matches on purposes as well as titles, and
`{"cmd":"panes"}` reports them.
## When the layout is found rather than named
-Everything above assumes you name the file. When it lives *in* the project,
+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
diff --git a/docs/nav b/docs/nav
index c087d12..d812a1a 100644
--- a/docs/nav
+++ b/docs/nav
@@ -9,6 +9,7 @@ Start
Using it
keys.md
+ actions.md
panes.md
workspaces.md
diff --git a/docs/panes.md b/docs/panes.md
index 04127bd..2066f2a 100644
--- a/docs/panes.md
+++ b/docs/panes.md
@@ -1,55 +1,28 @@
# Panes and tabs
-A tab is a tree of splits. Panes keep running in every tab; only the one you are
+A tab is a tree of splits. Panes keep running in every tab; only the tab you are
looking at is drawn.
## Arranging
-- **Split** with `C-a Enter` and it picks the axis: across the pane's longer side,
- measured as you see it (a cell is about twice as tall as it is wide, which is
- what `gap_aspect` says), falling back to the other axis when that one will not
- fit and saying which way it went. `C-a \` and `C-a -` when you mean one.
- Splitting the same direction again gives three equal shares rather than
- 1/2 + 1/4 + 1/4.
+- **Split**
+ keyboard: `C-a Enter`
+ mouse: hover near existing pane border in the middle to reveal clickable split actions.
- **A new pane starts in the directory the pane it came out of is in**, not where
- the session was started. Landing somewhere else is wrong on screen and worse
- on disk: a saved [project layout](workspaces.md) would carry an absolute
- `cwd=` pointing somewhere else entirely.
-- **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
- 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
- changes axis and a row split's children reverse, because the pane on top of a
- stack is the pane on the right once you turn it. Four turns come back exactly.
- 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**, and clicking it anywhere brings the pane
- back:
-
-```
-╭────────────╮ ╭─────────────────╮
-│ ▬ server │ │ ▬ agent [!] │
-╰────────────╯ ╰─────────────────╯
-```
+ the session was started.
+- **Resize** with `C-a` `H J K L`: the boundary moves the way you press. Sizes are _weights_,
+ so a resized layout survives the window changing size. Of course you can also drag in the gutters of the panes
+ to freely resize them! (or the pane's border in `compact` mode)
+- **Equalize** panes using **`C-a 0`** (`equalize`), it will attempt to equalize the space between all panes.
+- **Rotate your current layout with** **`C-a Space`** (`rotate_layout`); it turns the whole tab a quarter turn clockwise.
+- You can (temporarily) **Zoom** in on panes with **`C-a z`** (`zoom`); it fills the tab with one pane and triggering it again unzooms.
+- **Minimizing** panes can be done with **`C-a m`** (`minimize`); it minimizes the active pane into a strip along the bottom, still running.
+ Each put-away pane sits there as a **miniature pane frame**, and clicking it anywhere brings the pane
+ back. BEL notifications are drawn in the minitature pane frame as they arrive.
## The mouse
-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
- 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
- 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.
+- **Click the middle of a pane's border to split toward it**
- **The top row is the drag handle, and its handle splits upward.** Clicking a
pane's header to focus it never splits it. Its handle goes as near the middle
of the row as the row allows: centred when the row is empty, beside the title
@@ -57,106 +30,41 @@ nothing you click can disagree with what is on screen.
double-click rename). Arming an edge leaves the name, the buttons and a
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
- 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. Same mouse, tmux-style density: 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 the crossing to move both at once. With `gap 0` and **[`compact true`](config.md#compact)**
+ there is no gap to grab, so grabbing the border itself and pulling does the same thing.
+- **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
- 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.
+ and where you let go is what the drop means.
- **...or onto a tab in the strip** to move it into that tab, or onto the `+` for a
- tab of its own. The tabs light up as destinations while you carry a pane and the
- 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
- 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.
+ tab of its own.
- **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`
and `rename-tab`, for hands that are on the keyboard.
- A name is not a [purpose](layouts.md#purposes): the purpose is a separate label
- with a key of its own, `C-a P`, so a rename and a tag cannot be confused with
- each other. The purpose editor draws in the same title cell but labels itself
- `purpose <what you type>`, so you can see which of the two you are editing.
+ A name is not a [purpose](layouts.md#purposes): the purpose is a separate label.
- **Drag a tab along the strip** to reorder it. The strip rearranges as you go
rather than dropping it at the end.
-- Click a tab to switch, `+` to open one, a pane to focus it, and the marks in a
+- **Click a tab to switch**, `+` to open one, a pane to focus it, and the marks in a
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, forever, for a verb pressed rarely.
-- Hovering anything says what it does, in a word, in the status line.
## 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
-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**, the throwaway terminal, over
-whatever you are doing, in the focused pane's directory; `exit` closes it
-like any shell, and un-floating lands it beside the pane it was opened over.
+the centre of the tab**. Pressing `f` again puts it back in the seat it kept,
+with the layout exactly as it was.
-- **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.
- A float's border never splits: a split is a statement about the
- arrangement, and a float is precisely not arranged.
-- **`C-a H J K L` move a focused float** instead of moving a boundary, since
- a float has no boundary to move. **`=` (or `+`) grows it and `-` shrinks
- it** about its own centre, so it stays under your eyes; on a tiled pane
- `-` still splits. Equalize lives on `0` because `+` arrives as a bare `=`
- on terminals that do not report shift for punctuation.
-- **It casts a shadow** on what it covers, which is how you tell a float from
- a tile at a glance. `float_shadow 0` in the config turns it off, and a
- `states { floating { … } }` chain colours floats on top of that.
-- **Where you put it is where it stays.** Squeezed by a small window it is
- clamped in; grow the window back and it is exactly where you left it. On a
- window too small to lay out at all it becomes a row in the flat list like
- 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.
-- **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
- [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
- if tiled, re-places if floating; omitted fields mean "keep"), and
- `{"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
- over what covers it.
+`C-a F` opens a **new floating shell**, the throwaway terminal, over
+whatever you are doing, in the focused pane's directory; `exit` (or `^D`) closes
+it like any shell, and un-floating lands it beside the pane it was opened over.
## Links
**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.
+offers it with its own gesture.
**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
@@ -164,7 +72,7 @@ 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
-open (shift+ctrl on Linux). A URL that *wraps* onto a second row cannot be
+open (shift+ctrl on Linux). A URL that _wraps_ onto a second row cannot be
matched this way through any multiplexer, because the screen is repainted row
by row and the terminal sees two hard lines. That is exactly what OSC 8
links, which pass through whole, are for.
@@ -185,7 +93,7 @@ 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
+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:
@@ -195,12 +103,6 @@ with two lines saying what ran and how it ended, and two buttons:
╰ exited: status 3 ───────────────────────────────[re-run]─[close]─╯
```
-**The command is written down because `[re-run]` is one button.** Without it,
-pressing it is a guess — most of all in a tab a [project's
-layout](workspaces.md) built, where the command was never typed 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: a pane you re-run becomes a log of
what ran, and a command that failed while you were looking elsewhere still has
@@ -209,16 +111,14 @@ its error message when you get back.
Which panes stay is one setting, `keep_dead`: `commands` (the default), `all`,
or `none`.
-## Small terminals
+## Responsive Layout for 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
-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.
+when there is room again.
`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
+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
that one: a pane already too short for two rows has no room for two columns
either, whatever its width says.
@@ -228,17 +128,18 @@ either, whatever its width says.
- **Select text to copy it.** Release the mouse and it is on your clipboard,
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`,
+ 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
`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.
-- **Images work.** Kitty graphics pass through to your terminal, including the
+- **Images work.** This is still in it's early stages but Kitty graphics pass
+ through to your terminal, including the
half of the protocol other multiplexers drop, where a program uploads an image
once and places it every frame. Your terminal's cell size is carried through to
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
- it. `bell_indicator false` makes a bell silent *and* invisible, which is a real
+ 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/shaders.md b/docs/shaders.md
index 8be513a..c435321 100644
--- a/docs/shaders.md
+++ b/docs/shaders.md
@@ -1,16 +1,15 @@
# Shaders
-A colour pass over a pane's cells: a pure function from (cell, position,
-strength) to that cell's colours. It runs after the pane's contents are composed
-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).
+slosh has the concept of `shaders`. shaders are (mathematical) functions applied
+to the `cell`s of a pane. it runs after the pane has been composed and can be used
+to visually alter the foreground and background colours (and other attributes) of
+the final output that is send to the user's terminal. The actual text is never
+altered by shaders, only the markup.
-Cells only: foreground, background, attributes. Text and width are never
-touched, because rewriting text would desync selection and copy.
+Here's a basic example of a shader chain setup:
```kdl
-shaders { // every pane gets these, in the order written
+shaders {
vignette amount=70
ruler amount=60 at=80 color="#7aa2f7"
}
@@ -20,30 +19,31 @@ A chain is a sequence: `grayscale` then `tint` is not `tint` then `grayscale`.
Listing a shader twice is how you get two of it. An unknown name is refused with
a word and skipped, never guessed at.
-## The built-ins
+## The built-in shaders
+
+| shader | takes | does |
+| ----------- | ------------------------- | ---------------------------------------------- |
+| `dim` | `amount` | darken towards black |
+| `grayscale` | `amount` | drain colour, weighted for the eye |
+| `tint` | `amount` `color=` | pull towards a colour |
+| `vignette` | `amount` | darken towards the pane's edges |
+| `gradient` | `amount` `direction=0..3` | fade towards the background |
+| `zebra` | `amount` `band=1` | darken alternate bands; `band=1` is a scanline |
+| `ruler` | `amount` `at=80` `color=` | mark a column, background only |
+| `margin` | `amount` `at=100` | everything past a column recedes |
+| `spotlight` | `amount` `radius=12` | brightness falls away from the cursor |
-| shader | takes | does |
-|---|---|---|
-| `dim` | `amount` | darken towards black |
-| `grayscale` | `amount` | drain colour, weighted for the eye |
-| `tint` | `amount` `color=` | pull towards a colour |
-| `vignette` | `amount` | darken towards the pane's edges |
-| `gradient` | `amount` `direction=0..3` | fade towards the background |
-| `zebra` | `amount` `band=1` | darken alternate bands; `band=1` is a scanline |
-| `ruler` | `amount` `at=80` `color=` | mark a column, background only |
-| `margin` | `amount` `at=100` | everything past a column recedes |
-| `spotlight` | `amount` `radius=12` | brightness falls away from the cursor |
+`amount` is `0..255` and defaults to `128`. The numbers beside the other properties are examples.
-`amount` is 0..255 and defaults to 128. The numbers beside the other properties
-are examples rather than defaults: left out, `band` is 1, `radius` is 10, and
-`at` and `direction` are 0. Two props belong to the *pass* rather
-than the shader and work on any of them: `where="content"|"chrome"` and
-`channel="both"|"fg"|"bg"`.
+There are two special properties that belong to the _pass_ rather
+than the shader and work on any of them:
+
+- `where`: `where="content"|"chrome"` selects if the shader should apply to the chrome (border) of the pane or to the content (body) of the pane.
+- `channel`: `channel="both"|"fg"|"bg"` selects if the shader should be applied to foreground, background or both.
## Strength as an expression
`amount` can be an expression instead of a number, evaluated for every cell.
-That is how you get an effect nobody built in, without building anything:
```kdl
shaders {
@@ -53,30 +53,23 @@ 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 |
-| 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 |
-| 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 |
-
-`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, where `3.14159 / 6` in integers is 0. There is one angle unit, so
-there is no `deg2rad` or `rad2deg`.
+| | |
+| --------- | ----------------------------------------------------------------------------------------------------------- |
+| 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 |
+| | `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 |
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
@@ -84,27 +77,23 @@ 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
-in C, gives an expression no way to produce an invalid cell, and makes the result
-cacheable: a program that does not read the clock is evaluated once per cell into
-a map and reused, so a shader you wrote in your config costs about what a
-compiled one does. Reading `t` or `since` costs a per-cell evaluation every
-frame, which is the honest price of animation.
+in C, gives an expression no way to produce an invalid cell.
## Ready-made
Thirty-two presets are in
`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`.
-
-`contrib/shader-tour` cycles a running session through them, and
-`contrib/shadertoy.html` previews the language in a browser (a test cross-checks
-that preview against the real compiler, so it cannot lie to you).
+your cursor. Each is a file you can paste into your config or `include`. These
+are some over the top examples, of course. But it's a good showcase of what
+you can do with the slosh shaders!
## Prototyping in a pane
-Edit, save, look, guess again is a slow way to arrive at a colour. A program can
-set the chains for the pane it is running in, in the same syntax the config uses:
+To allow for more rapid prototyping of shaders in live slosh session we have introduced
+some ANSI escape sequences (OSC) to directly configurable shaders by sending terminal
+sequences. Of course this is **not** enabled by default as we don't want random software
+to start reconfiguring our shaders. enable `in_band_shaders` for this functionality.
```bash
printf '\033]5577;1;shader;chrome;tint color="#7aa2f7" amount="abs(t / 8 %% 510 - 255)"\033\\'
@@ -113,28 +102,20 @@ printf '\033]5577;1;shader;chrome;\033\\' # that rect: back to normal
printf '\033]5577;1;shader;\033\\' # no rect named, so both of them
```
-The field after `shader` is which rect an entry means **when it does not say**
-(an entry's own `where=` wins), and the rest of the payload is a document in the
-config's syntax: one entry, several separated by `;`, or a whole `shaders { }` block.
-What it says replaces both chains, as naming the block in a config does. The reply
-counts what went where. The session answers on the program's stdin, `\033]5577;1;shader-reply;ok\033\\`
-or `shader-reply;error;bad amount for tint: ...`, so a typo says so instead of
-looking like a shader that does nothing.
+The field after `shader` is which rect an entry should be appliedi and the rest of
+the payload is in the config's syntax: one entry, several separated by `;`, or a whole `shaders { }` block.
+The session answers on the program's stdin, `\033]5577;1;shader-reply;ok\033\\` or
+`shader-reply;error;bad amount for tint: ...`, so a typo says so instead of looking like a shader that does nothing.
**Three ways to take it all back**, because the program that painted a pane is
not always in a state to put it back:
-| | |
-|---|---|
-| `printf '\033]5577;1;shader;\033\\'` | from the program, one exchange, both rects |
+| | |
+| ------------------------------------------------------------ | ---------------------------------------------------------------- |
+| `printf '\033]5577;1;shader;\033\\'` | from the program, one exchange, both rects |
| `printf '\033]5577;1;shader-load;/path/to/preset.kdl\033\\'` | a `shaders { }` file, read by the session and routed by `where=` |
-| `slosh cmd '{"cmd":"clear-shaders","id":3}'` | from outside; `id` 0 or absent means the focused pane |
-| the `clear-shaders` action | on a key you bind, or from the palette |
-
-None of them is gated on `in_band_shaders`. A chain can outlive the setting that
-allowed it (paint a pane, then turn the setting off), and a way out that the
-setting can take away is not one. They clear what the *pane* set: the config's own
-chains and the session's own dimming are not this pane's doing.
+| `slosh cmd '{"cmd":"clear-shaders","id":3}'` | from outside; `id` 0 or absent means the focused pane |
+| the `clear-shaders` action | on a key you bind, or from the palette |
`contrib/shader-repl` is that loop with a prompt on it, and **what you type at it
is what a config file says**, not a dialect of it:
@@ -154,22 +135,7 @@ of borrowing the syntax rather than inventing one. The reply counts
(`1 chrome, 1 content`) say where the passes went, which is the only way to 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 would be a second opinion.
-
-It is a readline prompt: editing, up/down and ctrl-r work as in a shell, and
-history is kept between runs in `$XDG_DATA_HOME/slosh/shader-repl.history`
-(the chains only, since `:quit` is not something you want to press up past).
-Tab completes the commands, the shader names, the property keys, the
-expression language's variables, constants and functions, and filenames after
-`:load`; `:help` prints the same list at once.
-
-**Off by default.** It needs `in_band_shaders true`, because a program that can
-restyle the session it happens to be running in is a hazard first and a
-convenience second: `cat` the wrong file and your panes go dark. With it on, a
-pane can only paint *itself*: not its neighbour, and not anything the config
-said about anybody else.
+**Off by default.** It needs `in_band_shaders true`.
## Your own, compiled