1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
|
# Configuration
One file, in [KDL](https://kdl.dev):
```
$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.
**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.
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.
## Two files worth knowing
- **`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).
## Checking one
```bash
$ 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 —
including a setting it does not know:
```
cannot open /home/you/.config/slosh/themes/nope.kdl
config.kdl:2: bad prefix: ctrl+nope
config.kdl:3: bad key: wobble
config.kdl:5: padding takes 1, 2 or 4 values (all, vertical horizontal, or top right bottom left), not 3
config.kdl:6: unknown setting: wobble
themes/mine.kdl:7: unknown shader: bloom
~/.config/slosh/config.kdl: 6 problems
```
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 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.
## include
A config can be made of 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.
- **What you include is the base**: the file with the `include` line in it wins
wherever they disagree, and a later include wins over an earlier one. That
holds wherever the line sits in the file.
- `keys` blocks add to what came before; a `shaders` or `states` block replaces
the one it inherited.
- Every included file is watched, so saving the theme reloads the session too.
## 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.
Six ready-made themes are in
`contrib/themes`:
`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:
```kdl
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`) |
| 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) |
| 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`
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:
```kdl
padding 1 // every side
padding 0 2 // vertical, horizontal
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
meaning you have to look up is a line nobody can read.
## Compact
```kdl
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:
```
╭────── 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.
- **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.
- 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.
## Scrollback
```kdl
scrollback 10000 // lines of history per pane; 0 keeps none
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,
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
page at a time (a page is about 400KB of grid), 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:
```kdl
dim_unfocused 60 // the one knob most people want
float_shadow 110 // the shade a floating pane casts; 0 for none
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.
|