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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
|
// sl0ppty — every value here is also the compiled-in default, so this file is
// documentation as much as configuration. Copy it to
// ~/.config/sl0ppty/config.kdl (or point $SL0PPTY_CONFIG at it) and edit.
//
// A broken file costs a warning on stderr and nothing else: the defaults
// stand. Saving this file re-reads it into every running session on its own;
// `sl0ppty --no-reload` starts one that does not watch, and
// `sl0ppty cmd '{"cmd":"reload"}'` asks for a re-read explicitly. And
// refuses a file it cannot parse rather than half-applying it.
// ── geometry ────────────────────────────────────────────────────────────────
// Blank cells between panes and around the screen edge, in ROWS.
gap 1
// Columns per row. Terminal cells are about twice as tall as they are wide, so
// a gap of 1 row and 2 columns looks square.
gap_aspect 2
// Cells between a pane's frame and its content.
padding 0
// ╭╮╰╯ versus ┌┐└┘
rounded true
// "left" | "center" | "right"
title_align "center"
// Cells between a frame's corner and the start of its title, for the edge the
// title is anchored to. The title always carries a space on each side on top
// of this, so it can never end up welded to the rule beside it.
title_inset 2
// ── the bell ────────────────────────────────────────────────────────────────
//
// A BEL from a program marks the pane it came from, and the tab that pane is
// in -- a bell exists for the pane you are *not* looking at, and one that only
// showed on screen would be useless in a background tab. Looking at the pane
// is what puts it out. Off makes a bell silent *and* invisible, which is a
// real choice rather than an oversight.
// ── the frame's own buttons ─────────────────────────────────────────────────
//
// Minimise, zoom and close, in the top-right of every pane's frame: the mark
// and one space each, seven columns for the three of them. They are drawn
// right to left and stop when they run out of room, so a narrow pane keeps
// close and zoom and loses minimise first.
//
// The split button was removed from this frame once already, on the grounds
// that one glyph cannot mean four directions and the border can. These are two
// verbs a single glyph *can* mean, which is why they are here and splitting is
// still done by clicking an edge. They do cost six columns of every frame, so
// this turns them off -- the keybindings (C-a z, C-a x) are unaffected, because
// the setting is about the affordance and not the verb.
// A word in the middle of the status line for whatever the pointer is on:
// which way a border would split, what a one-character button does, that the
// gap between two panes can be dragged. The frame's affordances are mostly one
// character wide and a border that splits when clicked looks exactly like a
// border, so this is where they say so. It gives way to the rest of the line
// when there is no room between the two ends -- a hint that overwrites what it
// is explaining is worse than no hint.
hints true
pane_buttons true
// Plain single-width ASCII, after box-drawing and block characters were each
// tried and each looked unevenly spaced: the cells are even, but a glyph that
// does not fill its own cell hands that space to the gap beside it, and how
// much it hands over depends on the font. ASCII of a similar weight sidesteps
// the question. Emoji are the worst case -- many terminals draw them two
// columns wide while chrome here is booked as one.
//
// A mark may be more than one character; the button is measured rather than
// assumed, so "[]" and "][" work if you prefer them. Put the character itself
// here; this file has no \u escape.
zoom_mark "#"
zoom_on_mark "*"
close_mark "x"
min_mark "_"
// A pane whose program exits stays where it is, keeping everything it
// printed, with a line in its own backlog saying why it ended and two buttons
// in its frame: [re-run] to run the same command again in the same pane and
// on top of the same scrollback, and [close] to let it go. `sl0ppty` closes
// with the last pane, so a session no longer disappears the instant the shell
// in it exits.
//
// false is the old behaviour: the pane is closed on the next paint, and with
// the last pane the session. That suits a one-shot layout, and costs you the
// last thing a failing command printed.
keep_dead true
bell_indicator true
// What to draw. A short string rather than a fixed glyph, because the obvious
// choice is an emoji bell and emoji are drawn two columns wide by many
// terminals while chrome here is booked as one -- so the default is narrow and
// anyone who knows their own terminal can say otherwise. (This file has no
// \u escape, so put the character itself here.)
bell_mark "•"
// The one-row bar with tabs, the +tab button and the pane count.
status_bar true
// The line along the bottom: which session and tab you are in, what the
// focused pane calls itself, and whether it is in a state worth knowing about
// - scrolled back, on an alternate screen, or not started yet. On the right it
// says which of *this tab's* panes is open, as `pane 2/5`; the strip above
// counts the whole session, so the two disagree as soon as you have two tabs.
status_line true
// How far the strip and the line are held off the left and right edges. This
// is separate from `gap`, which is the space around the panes, and wider than
// it by default so the two bars read as chrome outside the layout rather than
// as another row of it.
status_pad 4
// Focus follows the pointer. It never steals focus mid-chord (while the prefix
// is held), never reaches past the finder, never interrupts a drag, and never
// expands a collapsed pane just because the pointer crossed its header.
focus_follows_mouse true
// The floor below which a pane is not small but useless. A split that cannot
// give every child this much collapses into a stack instead, with one pane
// expanded and its siblings as one-row headers. Recomputed every frame, so
// widening the terminal always restores exactly the layout you had.
min_pane cols=24 rows=6
// The smallest pane a split is allowed to *produce*. min_pane above is where
// the layout gives up and collapses; this is where splitting stopped being
// worth offering, which is a larger number and a different question — two
// twenty-five column panes technically fit and are not worth having.
//
// Below this the border stops being a button: no guide arms, clicking it does
// nothing, and the keyboard says so with a toast. The control API and layout
// files are deliberately exempt, because a script asking for a pane is saying
// what it wants rather than being offered something, and the responsive
// collapse is what catches it there.
//
// Never used below min_pane: refusing to offer what the layout would collapse
// anyway is the floor under the floor. Raise it if you like fewer, bigger
// panes; a column split needs 2*cols + 2 columns of pane to be offered, a row
// split 2*rows + 1.
min_split cols=32 rows=8
// Rows per wheel notch, and per scroll-up/scroll-down.
scroll_lines 3
// How long the pointer must rest on a pane border before the split guide
// arms. Contact alone is not enough: a pointer crossing a border on its way
// somewhere else should not make the screen flash. 0 arms immediately.
hover_delay_ms 250
// How close together two clicks on a pane's title must be to count as a
// double-click, which renames that pane in place. Enter keeps the new name,
// Escape abandons it, and an empty name gives the pane back to whatever its
// program calls itself.
double_click_ms 400
// ── shaders ─────────────────────────────────────────────────────────────────
//
// Colour passes over a pane's contents (never the frame, title or tab strip).
// Both are strengths from 0 to 255, where 0 is off and 255 is fully applied.
// Both resolve default-coloured text through theme's default_fg/default_bg,
// so set those to your terminal's real colours or a shaded pane will shift.
// What a pane looks like while it is in a particular state. Each state takes
// the same chain of passes `shaders` does.
//
// A pane is usually in several states at once — unfocused *and* scrolled,
// suspended *and* something you could drop onto — so exactly one wins: the
// first that matches, tested in this order.
//
// dragging the pane you have hold of
// drop_hover the one under the pointer, where it would land
// drop_target the others, all of them somewhere it could go
// dead its program exited; waiting to be re-run or closed
// suspended laid out, never started (its label is its contents)
// scrolled looking at scrollback rather than at the present
// unfocused the weakest thing that can be true of a pane
//
// The order is fixed rather than read from this block, because it is a ranking
// by urgency and not a preference: a pane you are holding should not be
// recoloured by anything, and a mode the whole screen is in outranks an
// ambient hint about one pane. They do not stack, for the same reason two
// reasons to be grey would 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: states { drop_target { } }
//
// Only drop_hover, drop_target and dead have defaults, shown here. `dead` is
// gentler than the drag greying on purpose: a dead pane is something you are
// still meant to read.
states {
drop_hover { grayscale amount=200; dim amount=140 }
drop_target { grayscale amount=200; dim amount=140 }
dead { grayscale amount=200; dim amount=90 }
// dragging { }
// suspended { grayscale amount=180; dim amount=90 }
// scrolled { tint amount=30 color="#ffcc88" }
// unfocused { dim amount=90 }
}
// While a pane is being dragged its border stays solid and every other pane's
// turns dashed, marking them as places it could be dropped. That is chrome,
// not a shader, so it happens whatever the states above are set to.
// Colour passes every pane gets, in the order written — a chain is a sequence,
// so `grayscale` then `tint` is not `tint` then `grayscale`. Each node is one
// pass; `amount` is 0..255 and defaults to 128. Listing a shader twice is how
// you get two of it (two rulers, say). An unknown name is refused with a
// warning and skipped, never guessed at.
//
// dim amount darken towards black
// grayscale amount drain colour, weighted for the eye
// tint amount color="#rrggbb" pull towards a colour
// vignette amount darken towards the pane's edges
// gradient amount direction=0..3 fade towards the background
// 0 down, 1 up, 2 right, 3 left
// zebra amount band=1 darken alternate bands of `band` rows;
// band=1 is also a CRT scanline
// ruler amount at=80 color= mark a column, background only, so the
// text on it stays readable
// margin amount at=100 everything from that column recedes
// spotlight amount radius=12 brightness falls away from the cursor
//
// Empty by default: these are yours to ask for, not opinions we hold.
//
// shaders {
// vignette amount=70
// zebra amount=22
// ruler amount=60 at=80 color="#ff5fd7"
// spotlight amount=90 radius=14
// }
// How long an announcement ("copied 13 chars", a pane's OSC 9 notification,
// "config reloaded") stays in the bottom-right corner.
toast_ms 2500
// What a new pane runs. Unset means $SHELL.
// shell "/bin/bash"
// ── theme ───────────────────────────────────────────────────────────────────
// Every colour the compositor draws has its own name here. They started as six
// names doing thirty jobs, which meant the split guide could not be recoloured
// without also recolouring the focused frame, because they were the same
// entry. Several still share a default: that says they look right together,
// not that they are the same thing.
theme {
// What a cell's "terminal default" colour is taken to be when a shader has
// to compute on it. See the shaders section above.
default_fg "#ffffff"
default_bg "#000000"
// ── panes ───────────────────────────────────────────────────────────────
frame_focus "#ff5fd7" // border of the focused pane
frame_idle "#45454a" // border of every other pane
title "#ffffff" // its title, and its OSC 5577 status text
title_idle "#45454a" // the same, on a pane that is not focused
// A pane's own OSC 5577 buttons.
button_fg "#141418"
button_bg "#ff5fd7"
button_bg_idle "#55555c" // buttons on a pane that is not focused
// ── things that appear under the pointer ────────────────────────────────
guide "#ff5fd7" // split guide: armed edge, dashed boundary, arrow
resize "#ff5fd7" // the handle in the gap between two panes
drop_target "#ff5fd7" // frame of a pane a drag could land on
// ── a pane's scrollback indicator ───────────────────────────────────────
scroll_fg "#141418"
scroll_bg "#ff5fd7"
// ── rows of a tab that has collapsed into a list ────────────────────────
header "#45454a"
header_hover "#ff5fd7" // the rule, under the pointer
header_hover_title "#ffffff" // the name, under the pointer
// ── the strip along the top ─────────────────────────────────────────────
// The tab you are in is filled rather than merely bold: bold alone is easy
// to miss on a strip of short labels. It keeps that fill while the pointer
// is on it -- it is still the tab you are in -- so its hover lands on the
// text instead.
tab_active_fg "#141418"
tab_active_bg "#ff5fd7"
tab_active_hover_fg "#ffffff"
tab_idle "#45454a"
tab_hover "#ff5fd7"
prefix_fg "#141418" // the "C-a" badge, while the prefix is pending
prefix_bg "#ff5fd7"
tab_count "#45454a" // panes in the whole session
// ── the line along the bottom ───────────────────────────────────────────
status "#45454a" // session, tab, pane, and the pane index
status_state "#ffffff" // scrolled / alt screen / not started / exited
hint "#ffffff" // what the pointer is on, in the middle
// ── marks on a pane's frame ─────────────────────────────────────────────
bell "#ff5fd7" // a pane rang and has not been looked at
dead "#ff875f" // its program exited: the line in its backlog,
// and the words in its frame and the status line
pane_button "#45454a" // the frame's own close/zoom/minimise marks
pane_button_hover "#ff5fd7"
// ── the row of minimised panes ──────────────────────────────────────────
minbar "#45454a"
minbar_hover "#ff5fd7"
// ── the pane finder ─────────────────────────────────────────────────────
finder_fg "#ffffff"
finder_bg "#45454a"
finder_sel_fg "#141418"
finder_sel_bg "#ff5fd7"
// ── the in-place editor a double-click opens on a pane title ────────────
rename_fg "#141418"
rename_bg "#ff5fd7"
// ── announcements ───────────────────────────────────────────────────────
toast_fg "#141418"
toast_bg "#ff5fd7"
}
// ── keys ────────────────────────────────────────────────────────────────────
//
// Everything is `prefix` then a key. Pressing the prefix twice sends the
// prefix itself to the pane. Modifiers are ctrl+ alt+ shift+ super+, and named
// keys are left/right/up/down/enter/tab/escape/space/backspace/home/end/
// pageup/pagedown/delete/insert/backslash/minus/slash/comma/period.
//
// Actions: split-cols split-rows close-pane focus-left focus-right focus-up
// focus-down focus-next resize-left resize-right resize-up resize-down
// scroll-up scroll-down scroll-page-up scroll-page-down scroll-top
// scroll-bottom zoom minimize rerun new-tab next-tab prev-tab select-tab-1..9 finder detach quit
// literal-prefix none
keys {
prefix "ctrl+a"
bind "\\" "split-cols"
bind "-" "split-rows"
bind "x" "close-pane"
// Run a dead pane's command again, in the same pane and on top of the
// same scrollback. Refused on a pane that is still running.
bind "r" "rerun"
bind "z" "zoom"
bind "m" "minimize"
bind "h" "focus-left"
bind "l" "focus-right"
bind "k" "focus-up"
bind "j" "focus-down"
bind "left" "focus-left"
bind "right" "focus-right"
bind "up" "focus-up"
bind "down" "focus-down"
bind "o" "focus-next"
// The boundary moves in the direction you press, whichever side of it the
// focused pane is on.
bind "shift+h" "resize-left"
bind "shift+l" "resize-right"
bind "shift+k" "resize-up"
bind "shift+j" "resize-down"
bind "c" "new-tab"
bind "n" "next-tab"
bind "p" "prev-tab"
bind "1" "select-tab-1"
bind "2" "select-tab-2"
bind "3" "select-tab-3"
bind "f" "finder"
// Scrollback. The wheel does this too, unless the program in the pane
// wants the mouse itself, or is a full-screen program with no scrollback
// of ours to show — then the wheel means arrow keys to it.
bind "pageup" "scroll-page-up"
bind "pagedown" "scroll-page-down"
bind "home" "scroll-top"
bind "end" "scroll-bottom"
bind "d" "detach"
bind "q" "quit"
}
|