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
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
|
/* The opinionated defaults, and the file that overrides them.
*
* Everything here has a working value compiled in, so a missing or broken
* config file costs you a warning, never a terminal (the fail-open property
* D9 asks of the CLI, applied to the mux itself).
*/
#ifndef SLOSH_CONFIG_H
#define SLOSH_CONFIG_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "expr.h"
#include "shader.h"
#include "slosh.h"
/* Pane states a shader can be hung off, in the order they are tested. Only
* states a pane can actually be seen in — which `dead` now is: a pane whose
* program exits is kept until it is dismissed, so there is a frame to draw.
* It ranks just under the drag states, above every ambient one: nothing about
* a pane is more worth knowing than that it is over. */
typedef enum {
PSTATE_DRAGGING, /* the pane you have hold of */
PSTATE_DROP_HOVER, /* the one under the pointer, where it would land */
PSTATE_DROP_TARGET, /* the others, all of them somewhere it could go */
PSTATE_DEAD, /* its program exited; waiting to be re-run or closed */
PSTATE_SUSPENDED, /* laid out, never started */
/* A BEL arrived and nobody has looked since. Above the ambient states
* because it is *news* — the whole point of a bell is to be noticed on a
* pane you were not watching — and below `dead`, because a pane that rang
* and then exited is a pane that exited. It ends when you look at the pane,
* which is what answering a bell is. */
PSTATE_BELL,
PSTATE_SCROLLED, /* looking at scrollback rather than the present */
/* Lifted out of the layout, drawn on top. Above `unfocused`, so a float is
* never dimmed by dim_unfocused: full strength is what keeps the thing on
* top reading as lifted off the page — the same reason `dragging` keeps no
* default. Ships no chain of its own either; the shadow does the telling
* apart, and a config that wants colour writes `states { floating { } }`. */
PSTATE_FLOATING,
PSTATE_UNFOCUSED,
PSTATE_COUNT,
} pane_state_t;
/* The config name for a state, e.g. "drop_target". */
const char *pane_state_name(pane_state_t s);
typedef enum {
ACT_NONE = 0,
ACT_SPLIT,
ACT_SPLIT_COLS,
ACT_SPLIT_ROWS,
ACT_CLOSE_PANE,
ACT_RERUN,
ACT_ZOOM,
ACT_MINIMIZE,
ACT_FLOAT,
ACT_NEW_FLOAT,
ACT_FLOAT_GROW,
ACT_FLOAT_SHRINK,
ACT_SET_PURPOSE,
ACT_RENAME_PANE,
ACT_RENAME_TAB,
ACT_ROTATE_LAYOUT,
ACT_FOCUS_LEFT,
ACT_FOCUS_RIGHT,
ACT_FOCUS_UP,
ACT_FOCUS_DOWN,
ACT_FOCUS_NEXT,
ACT_NEW_TAB,
ACT_CLOSE_TAB,
ACT_NEXT_TAB,
ACT_PREV_TAB,
ACT_FINDER,
ACT_PALETTE,
ACT_WORKSPACES,
ACT_SAVE_WORKSPACE,
ACT_SCROLL_UP,
ACT_SCROLL_DOWN,
ACT_SCROLL_PAGE_UP,
ACT_SCROLL_PAGE_DOWN,
ACT_SCROLL_TOP,
ACT_SCROLL_BOTTOM,
ACT_SEARCH,
ACT_RESIZE_LEFT,
ACT_RESIZE_RIGHT,
ACT_RESIZE_UP,
ACT_RESIZE_DOWN,
ACT_EQUALIZE,
ACT_CLEAR_SHADERS,
ACT_PANE_TO_NEXT_TAB,
ACT_PANE_TO_PREV_TAB,
ACT_PANE_TO_NEW_TAB,
ACT_DETACH,
ACT_QUIT,
ACT_HELP,
ACT_EDIT_CONFIG,
ACT_THEMES,
ACT_LITERAL_PREFIX,
ACT_SELECT_TAB_1, /* .. +8, so digits stay one entry each */
} action_t;
typedef struct {
int key; /* GhosttyKey */
uint16_t mods; /* MOD_* */
action_t action;
/* Fires without the leader, taking that chord away from every program in
* every pane. Deliberately possible and deliberately opt-in: it is the
* user's terminal and their keyboard, and a multiplexer that refuses to get
* out of the way is its own kind of wrong. */
bool direct;
} binding_t;
typedef enum { ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT } align_t;
/* tab_bar_side: where the strip of tabs lives. Top is the classic row; left
* and right turn it into a sidebar, one tab per row. */
enum {
TAB_BAR_TOP = 0,
TAB_BAR_LEFT,
TAB_BAR_RIGHT,
};
/* tab_bar_index: where a tab's number sits. `prefix` is `1:api`, the number
* against the name; `right` puts it at the far end of the row, which only a
* sidebar has -- the top strip's tabs are as wide as their labels, so there
* is no "far end" to put anything at. */
enum {
TAB_INDEX_PREFIX = 0,
TAB_INDEX_RIGHT,
};
/* size_follows: which attached client sizes the shared screen. */
enum {
SIZE_FOLLOWS_ACTIVE = 0, /* newest attach, then last input */
SIZE_FOLLOWS_SMALLEST, /* min over clients: filler, never a crop */
SIZE_FOLLOWS_LARGEST, /* max over clients: crops, never filler */
};
/* How many files one config may be built from: itself plus its includes. Small
* and fixed, because a config assembled from more than this many pieces is a
* thing nobody can read. */
#define CONFIG_FILES_MAX 16
/* How many project roots one config may name. `~/dev` and `~/work` is the case;
* a person with more than this many places they keep code has a search problem
* rather than a list problem. */
#define PROJECT_ROOTS_MAX 8
/* How many complaints one load keeps. A config with more than this many
* problems in it has one problem, and the list is long enough to say so. */
#define CONFIG_MSGS_MAX 32
/* Room for the colour table in config.c, checked against it there: the flags
* beside it live in the config, so the header needs a ceiling rather than the
* table. Generous, because overshooting costs one byte per unused slot. */
#define THEME_COLORS_MAX 128
/* How many named themes a picker will list, and how long a name may be. A
* theme is a file in a directory; both numbers are "more than anyone has". */
#define THEMES_MAX 64
#define THEME_NAME_MAX 64
/* How many directories a theme is looked for in: yours, two derived from
* where the binary is, and the prefix this build was compiled with. */
#define THEME_DIRS_MAX 4
typedef struct {
/* geometry */
uint16_t gap, gap_aspect;
/* Space between a pane's frame and its contents, per side, in the same unit
* `gap` uses: ROWS. A horizontal one is multiplied by `gap_aspect` on the way
* out, so `padding 1` is a square-looking ring rather than a squashed one —
* and so a number means the same thing wherever it appears, however many of
* them were written. Written as 1, 2 or 4 values, CSS order. */
uint16_t pad_top, pad_right, pad_bottom, pad_left;
/* Shared borders instead of gaps: panes pack flush against 1-cell divider
* lines, one outer frame rings the tab, and each pane's title rides the
* line above it. `gap` stops applying — the boundary *is* the divider —
* while `padding` still works and `gap_aspect` keeps its other jobs
* (square-looking padding, the float nudge step). The dividers carry the
* same resize drag the gaps did; interior edges give up their
* click-to-split — a one-cell line cannot be both verbs — and the outer
* frame keeps it. */
bool compact;
bool rounded;
align_t title_align;
/* Cells between a frame's corner and the start of its title. The title
* always carries a space on each side on top of this, so a title can never
* end up welded to the rule beside it. */
uint16_t title_inset;
/* Mark a pane's titlebar when a BEL arrives there and nobody has looked
* since. Off makes a bell silent *and* invisible, which is a real choice. */
/* Buttons in the top-right of a pane's frame: zoom, and close. Off leaves
* the keyboard bindings alone; it is about the affordance, not the verb. */
/* A word in the middle of the status line for whatever the pointer is on.
* Discoverability for a frame whose affordances are mostly one character
* wide; off if you already know them. */
bool hints;
/* What slosh this is, in the middle of the status line, whenever there is
* no hint to put there. The slot is otherwise empty most of the time, and
* "which build am I attached to" is the first question when a session is
* behaving oddly -- a session keeps the binary it started with, so the
* answer is not whatever was built last. */
bool version_banner;
bool pane_buttons;
/* Marks of similar visual weight, so the even spacing between the buttons
* reads as even: a glyph that floats in a mostly-empty cell donates its own
* whitespace to the gap beside it and looks further away than its
* neighbours, which is what an en dash did here. */
/* 32 bytes because a mark is a grapheme cluster, not a character: a
family emoji is eighteen bytes on its own. Truncating one mid-
codepoint used to put invalid UTF-8 on the wire. */
char zoom_mark[32], zoom_on_mark[32], close_mark[32], min_mark[32];
/* The new-tab button at the end of the strip. Drawn with a space each side,
* so a one-character mark is still a three-cell target. */
char newtab_mark[32];
/* Whether that button is drawn at all. On by default -- it is the only
* pointer-reachable way to make a tab -- but the verb is also a chord and a
* palette entry, so anyone who works from the keyboard can have the row (or
* the columns) back and stop hitting it by accident. Setting newtab_mark to
* "" does the same thing by omission; this says it on purpose. */
bool newtab_button;
/* Air between the last tab and that button: blank *rows* in a sidebar,
* blank *columns* in the top strip -- the axis the list runs along, either
* way. The button sits at the end of a list of tabs whose rows all do
* something else, and a destructive-enough neighbour one cell away is a
* misclick waiting to happen; one cell of nothing is the cheapest fix.
* Dropped rather than the button itself when the strip is out of room. */
uint16_t newtab_pad;
bool bell_indicator;
/* What to draw. A short string rather than a fixed glyph: 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 terminal can say otherwise. */
char bell_mark[32];
/* What a pane that says it is *busy* looks like in the sidebar: the frames
* of a spinner, separated by spaces. One frame is a static mark and costs
* nothing; several make it turn, and only then does the session keep a
* frame clock -- and only while such a row is actually on screen. Empty
* draws no mark at all and leaves the colour to say it. */
char busy_mark[64];
/* Milliseconds a spinner frame is held. Its own knob rather than anim_ms,
* which is how often the session repaints while something animates: this is
* how fast the thing being repainted moves, and a spinner stepping at the
* repaint rate is a blur. */
uint16_t busy_ms;
/* Which dead panes stay, showing what they printed and offering to run
* again, and which just go.
*
* The line is whether the pane was given a *command*. A pane told to run
* `npm run dev` is part of the session's shape and its death is news: you
* want the error, and you want the button that runs it again. A pane that
* is a shell -- one you split off to do something in -- is finished when
* you type `exit`, and leaving its corpse on screen to be dismissed is
* exactly the fussiness a terminal should not have.
*
* Not *who made it*: a shell is a shell whether it came from a layout file
* or from C-a \. That also means a session restored from dump-layout
* behaves like the one it was dumped from, since the dump writes `command=`
* only for panes that had one. */
enum { KEEP_DEAD_NONE, KEEP_DEAD_COMMANDS, KEEP_DEAD_ALL } keep_dead;
uint16_t min_pane_cols, min_pane_rows;
/* The smallest pane a split is allowed to *produce*. min_pane is the point
* below which the layout gives up and collapses a pane; this is the point
* below which splitting stopped being worth offering, which is a larger
* number and a different question. Never used below min_pane: refusing to
* offer something the layout would collapse anyway is the floor. */
uint16_t min_split_cols, min_split_rows;
uint16_t scroll_lines; /* rows per wheel notch */
/* How much history a pane keeps. `scrollback` is the number people mean --
* lines -- and `scrollback_bytes` is the ceiling that keeps one very wide,
* heavily styled pane from spending the machine to honour it: lib-vt applies
* whichever limit is reached first, and both depend on things a line count
* cannot see. 0 lines is no scrollback; 0 bytes is no ceiling.
*
* Not a `uint16_t`, because 65,535 is a number somebody will want to exceed
* and a silently wrapped limit is worse than a refused one. */
size_t scrollback;
size_t scrollback_bytes;
uint16_t toast_ms; /* how long an announcement stays up */
/* How long the logo splash stays over a freshly attached screen, 0 for
* never. Attach-only: a headless or scripted session has nobody to greet. */
uint16_t splash_ms;
uint16_t hover_delay_ms; /* how long the pointer must rest to arm a guide */
/* How often a selection drag held past a pane's top or bottom edge scrolls
* another step. The step size is how far past the edge the pointer is, so
* this is the metronome and the hand is the throttle. */
uint16_t select_scroll_ms;
uint16_t double_click_ms; /* how close two clicks must be to be a double */
/* What a double-click's word stops at, listed as *separators* rather than as
* word characters: anything unlisted belongs to a word, so text nobody
* enumerated -- CJK, accents, emoji -- needs no table. Whitespace always
* separates, listed or not. */
char word_separators[64];
/* How far everything behind a modal is pushed back, 0..255, 0 being off.
* A modal that floats over a screen as bright as it is reads as another
* pane rather than as something in front. */
uint8_t modal_scrim;
/* How far the panes you are *not* in are pushed back, 0..255 and 0 for not
* at all. A knob rather than only a `states { unfocused { } }` chain,
* because "dim the other panes" is a thing people want by name and should
* not have to know the shader vocabulary to ask for -- and because the
* argument against shipping it on was that turning it off should be
* obvious, which a single number is and a chain is not.
*
* Writing the state chain by hand still wins: the knob only fills it in
* when the config has not. */
uint8_t dim_unfocused;
/* How dark the cell of shade a floating pane casts on what it covers,
* 0..255 and 0 for no shadow. The shadow is what tells a float from a tile
* at a glance — the frame is the same frame — which is why it ships on and
* why it is a number rather than a state chain: turning it off should not
* require knowing the states block exists (the dim_unfocused argument). */
uint8_t float_shadow;
/* Colour passes every pane gets, in the order they were written. Ordinary
* shaders rather than policy: the session has no opinion about these, you
* asked for them. */
shader_t shaders[SHADE_MAX];
size_t nshaders;
/* What a pane looks like when it is in a particular state. A pane is
* usually in several at once — unfocused *and* scrolled, suspended *and*
* something you could drop onto — so exactly one wins, the first that
* matches in the order below. That order is fixed rather than taken from
* the config, 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.
*
* Stacking them instead would let two reasons to be grey compound into one
* muddy grey that reads as neither, which is the mistake the drag policy
* already had to avoid by hand. */
shader_t state_shaders[PSTATE_COUNT][SHADE_MAX];
size_t state_n[PSTATE_COUNT];
/* Whether any file in this load named `scrolled` (or `bell`) itself. The
* default wash over scrollback and the default bell flash are derived from
* the theme *after* each file's theme block -- scroll_bg and bell
* respectively -- so a theme moves them with their indicators. But a chain
* somebody wrote, in this file or in one it included, must stand. A local
* flag cannot say that across an include chain, so they live here and are
* cleared with the rest of the defaults. */
bool scrolled_declared;
bool bell_declared;
/* The same two things again, over a pane's *frame* rather than its contents:
* any entry in `shaders` or in a state's block carrying `where="chrome"`
* lands here instead. Separate chains rather than a flag on each shader,
* because a pass is per rect: the contents and the frame are two rects and
* therefore two passes, and a chain is exactly the list one pass runs. It
* also keeps the flag out of the plugin ABI, which has no business knowing
* where a session chose to run an effect. */
shader_t chrome_shaders[SHADE_MAX];
size_t nchrome_shaders;
shader_t chrome_state_shaders[PSTATE_COUNT][SHADE_MAX];
size_t chrome_state_n[PSTATE_COUNT];
/* How often to repaint while something on screen is animated -- which means
* a shader whose amount reads the clock. Nothing else needs a frame clock:
* every other repaint has an event behind it. 0 turns animation off, in the
* sense that an animated shader then only advances when something else
* causes a frame. */
uint16_t anim_ms;
bool status_bar; /* the strip along the top: tabs, prefix, pane count */
/* Where the strip goes when it is on: the top row, or a sidebar on either
* edge. A sidebar lists one tab per row and moves the strip's indicators
* (pane count, prefix badge) to its bottom rows; the status line below is
* untouched and keeps the full width either way. */
int tab_bar_side; /* TAB_BAR_* */
uint16_t tab_bar_width; /* columns a sidebar takes; ignored for top */
/* Where a tab's number goes (TAB_INDEX_*). A sidebar row is wider than the
* name on it, and `1:api` spends the start of every row on a number nobody
* reads until they want to press it -- against the right edge the names all
* start in the same column and the numbers line up as their own column.
* Ignored on the top strip, where a tab is only as wide as its label and
* there is no room to align anything in. */
int tab_bar_index;
/* Air inside a sidebar, between its frame and the list in it, per side and
* in CELLS: rows above and below, columns left and right. Written as 1, 2
* or 4 values in CSS order like `padding`, and in cells rather than
* `padding`'s rows-times-gap_aspect, because this is not a ring around a
* block of text -- the two sides do different jobs. The columns are an
* indent for labels and statuses, and the rows are air above the first tab
* and below the last thing in the strip.
*
* The left column is 1 by default, which is where every label's leading
* space used to be hard-coded; 0 puts the names against the frame.
*
* It is *inside the plate*: a tab's fill still spans the whole row, because
* the row is the click target and a highlight that stops short of the edge
* reads as a highlighted word rather than a chosen row.
*
* `tab_bar_pad` is the older name for the top alone and still works; where
* both are written the more specific one wins. Ignored for the top strip,
* which has exactly one row and no air to give. */
uint16_t tab_pad_top, tab_pad_right, tab_pad_bottom, tab_pad_left;
/* Blank rows between one tab and the next, so a list of tabs each carrying
* a paragraph of status reads as separate entries rather than as one
* column of text. Sidebars only: the top strip is a single row, where the
* space between tabs is the label's own padding and there is none to give.
* Paint, not layout -- the panes' rectangle is the same at 0 as at 3 --
* and dropped a row at a time when the list is short of room, because the
* gap must never be what costs you the tab it was separating. */
uint16_t tab_gap;
/* Status rows under each of a sidebar's tabs: what the panes inside said
* through OSC 5577 (or how they died), at most this many rows per tab with
* an ellipsis when there was more. The sidebar is what makes this possible
* at all -- the top strip has one row and no room to say any of it. 0 turns
* it off. */
uint16_t tab_bar_status;
/* How many rows one pane's status may wrap onto, rather than being cut at
* the sidebar's width. 1 is the old behaviour and the default: a status is
* one row, truncated with an ellipsis.
*
* Above 1 it is word-wrapped, which buys a narrow sidebar a great deal --
* three rows of sixteen cells is forty-eight, and the difference between
* "add a summary\u2026" and the whole sentence. The cost is that the rows
* under a tab stop being a list of panes and become a paragraph per pane,
* so the stripe below is what keeps one pane's status apart from the next
* one's. Counts against tab_bar_status the same as any other row. */
uint16_t tab_bar_status_lines;
/* Frame the sidebar the way panes are framed, so it reads as part of the
* chrome rather than as text floating beside it. In compact mode its lines
* are shared with the tab area's outer ring -- the same junction-forming
* strokes the dividers use -- so the two are one figure. Ignored for top,
* which has no chrome to flow into. */
bool tab_bar_chrome;
bool status_line; /* the line along the bottom: what you are looking at */
/* How far the strip and the line are held off the left and right edges.
* Separate from `gap`, which is the space around the *panes*: the two happen
* to match by default and there is no reason they must. */
uint16_t status_pad;
bool focus_follows_mouse;
/* Several terminals may attach to one session at once (the default).
* false restores the classic rule: a new client displaces the one before
* it, which is what anyone using attach-from-elsewhere as "move my session
* here and *close it there*" actually wants. */
bool multi_attach;
/* Say so when several clients share the session: a small tag in each
* client's own top-right corner with the viewer count, and -- on a client
* whose view is a crop being panned to follow the active cursor -- the
* crop's offset into the shared screen. Per-client chrome, stamped after
* projection, which is why it is not part of the shared status bar. */
bool attach_indicator;
/* Whose size the shared screen takes when several clients are attached:
* the active client (newest attach, then last input -- the default), or
* the smallest / largest attached one. Smallest is the tmux rule: nobody
* is cropped and the roomiest terminal pays in filler; largest gives the
* big display everything and small ones a cursor-following crop. */
int size_follows;
/* Whether Ctrl-D at an idle shell prompt exits that shell, the way the tty
* line discipline would do it on POSIX.
*
* Windows has no VEOF: the console driver does not know the character, so
* cmd.exe and PowerShell simply ignore it and the one keystroke that closes
* a pane everywhere else does nothing. Typing `exit` works, which is the
* tell -- the intent was never ambiguous, only unspoken. So slosh speaks it,
* and that is exactly what it sends: the pane exits through the same door as
* a typed `exit`, with the shell's own cleanup and exit status, and
* `keep_dead` decides what is left behind. Closing the pane from outside
* would skip all three.
*
* Off on POSIX, where the line discipline already does this properly and
* an emulation could only get in the way -- ^D there is also EOF for `cat`
* and every REPL, and none of that is ours to intercept. On for anyone who
* wants the same key to mean the same thing on both.
*
* Narrow on purpose, because a wrong guess costs a pane. It fires only with
* the shell itself in the foreground (no child process to be sending EOF
* to), on the primary screen (in an editor or a pager ^D is half a page
* down), and on an empty line -- where POSIX would send EOF rather than
* discard what you typed. */
bool ctrl_d_exits;
/* Whether the program running in a pane may set that pane's shader chains
* over OSC 5577. Off, because a program restyling the session it happens to
* be running in is a hazard and not a feature (D13) -- and on for anyone
* prototyping a chain, because the alternative is edit, save, look, and the
* whole point of a colour pass is that you can see it.
*
* Scoped to the pane that asked: it cannot touch another pane, the chrome
* outside itself, or anything the config said about everybody else. */
bool in_band_shaders;
/* What a cell's "terminal default" colour resolves to when a shader has to
* compute on it. We cannot know the client's real default, and most text is
* default-coloured, so a shader would otherwise leave nearly everything
* alone. Only consulted while shading: an unshaded pane still defers to the
* terminal exactly as before. */
color_t default_fg, default_bg;
/* theme
*
* One name per decision. These began 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 is a statement about what looks right together, not about
* them being the same thing. */
/* panes */
color_t frame_focus, frame_idle;
color_t title_focus, title_idle;
/* things you can press: the pane's own OSC 5577 buttons */
color_t button_fg, button_bg, button_bg_idle;
/* affordances that appear under the pointer */
color_t guide; /* the split guide: armed edge and dashed boundary */
color_t resize; /* the handle in the gap between two panes */
color_t drop_target; /* the frame of a pane a drag could land on */
/* the scrollback indicator in a pane's frame */
color_t scroll_fg, scroll_bg;
/* rows of a tab that has collapsed into a list */
color_t header, header_hover, header_hover_title;
/* the strip along the top */
color_t tab_active_fg, tab_active_bg;
/* The active tab keeps its fill while the pointer is on it -- it is still
* the tab you are in -- so its hover shows in the text instead. */
color_t tab_active_hover_fg;
color_t tab_idle, tab_hover;
/* The fill under every tab that is not the active one, so a tab reads as a
* plate rather than as words floating on the background -- and so the
* active tab's fill reads as *this one* against the rest, which is a
* comparison it could not make against nothing. Derived 43% of the way from
* the background to `tab_idle`, the tab's own label grey, so it stays in a
* theme's family without any theme naming it -- and lands at about the same
* 1.9:1 against the background whether that background is dark or light.
* Set it to the theme's `default_bg` for the bare look. */
color_t tab_idle_bg;
color_t prefix_fg, prefix_bg; /* the "C-a" badge */
color_t tab_count; /* how many panes in the session */
/* The sidebar's status rows. Their distinction from the labels above them
* is carried by the slant (see draw_tab_sidebar), because a monochrome
* theme has no colour to spend on it; these are for themes that do. A bg of
* none leaves the terminal's own background alone, which is what a
* translucent one wants. */
color_t tab_status_fg, tab_status_bg;
/* A status whose program says it is still happening. Brighter than the
* ambient one, because "running" and "finished" are the two states worth
* telling apart from another tab, and the difference between them should
* not be something you have to read the words to work out. Derived from the
* theme, like tab_status_fg. */
color_t tab_status_busy;
/* The spinner beside such a row, which is its own entry because it is its
* own decision: the text has to stay readable, and a mark does not -- so a
* theme can shout with the glyph and keep the words calm, or the other way
* round. Unset it follows tab_status_busy, which is what it looked like
* when the two were one colour. */
color_t tab_status_spinner;
/* The other stripe. With wrapped statuses, where one pane's rows end and
* the next one's begin is no longer obvious from the shape -- both are
* just italic text down the same column -- so alternate panes sit on
* alternate bands. Unset and derived from the theme, like tab_status_fg,
* and only ever drawn when a status can actually wrap: a single-row status
* has no ambiguity to resolve, and painting a band behind it would take a
* translucent terminal's own background away for nothing. */
color_t tab_status_stripe;
/* the line along the bottom */
color_t status, status_state;
/* the pane finder */
color_t finder_fg, finder_bg, finder_sel_fg, finder_sel_bg;
/* scrollback search: every match on screen is tinted `search_*`, and the one
* the bar is on is tinted `search_cur_*` instead, so "all of them" and "this
* one" read as two things rather than one. */
color_t search_fg, search_bg, search_cur_fg, search_cur_bg;
/* the in-place editor a double-click opens on a pane's title */
color_t rename_fg, rename_bg;
/* the bell mark in a pane's titlebar */
color_t bell;
/* what a dead pane says: the line in its backlog, and the word in the
* status line and its own frame */
color_t dead;
/* the frame's own buttons */
color_t pane_button, pane_button_hover;
/* the row of minimised panes along the bottom */
color_t minbar, minbar_hover;
/* the hint in the middle of the status line */
color_t hint;
/* Modals: a surface that floats over the layout, with its own frame. Named
* separately from the pane colours it resembles because it is a different
* *surface* — it sits on top of everything, it is opaque, and it has to
* stay legible against a background that has been dimmed underneath it.
* Borrowing the pane button colour here is what made the close button
* invisible: dim on dim. */
color_t modal_fg, modal_bg;
color_t modal_border, modal_title;
color_t modal_button, modal_button_hover;
/* announcements */
color_t toast_fg, toast_bg;
/* the multi-client tag ("2 clients", per-client top-right chrome). Muted
* by default: it is standing information, not an announcement. */
color_t attach_fg, attach_bg;
/* keys */
int prefix_key;
uint16_t prefix_mods;
binding_t *binds;
size_t nbinds;
/* Every amount expression this config compiled. A shader_t is copied by
* value all over the place and several copies can point at one program, so
* ownership lives here — with the config that parsed them, freed when it is
* replaced. */
expr_prog_t **exprs;
size_t nexprs;
/* what a new pane runs; NULL means $SHELL */
char *shell;
/* What `edit-config` opens the file with; NULL means $EDITOR, then vi. */
char *editor;
/* Where projects live: `project_roots "~/dev" "~/work" depth=2`. A project is
* a subdirectory of one of these with a `slosh.layout` in it -- or a
* `.git`, which gets `project_layout` instead. Empty means the feature is
* dormant and costs nothing.
*
* `depth` is levels below each root, and a directory that *is* a project is
* never descended into, so 2 covers `~/dev/work/api` without walking any
* checkout's insides. */
char *project_roots[PROJECT_ROOTS_MAX];
size_t nproject_roots;
int project_depth;
/* Named themes. `theme_name "phosphor"` reads `phosphor.kdl` out of the
* theme directory and applies it *before* the rest of the file, exactly as
* an include does: the theme is the base, anything you write beside it
* wins. NULL is "nobody asked", and the compiled-in palette stands.
*
* `theme_dir` is where to look, resolved against the file that said it when
* it is relative; NULL means the pair of directories config_theme_dirs
* derives -- `themes/` beside your config, then the ones `make install`
* put in `<prefix>/share/slosh/themes`. Two rather than one so a theme that
* ships with slosh is reachable by name without being copied first, and a
* theme of yours with the same name shadows it. */
char *theme_dir;
char *theme_name;
/* Which colours were named by hand rather than inherited from the theme the
* config asked for, indexed like the table in config.c. Only the dump reads
* it, and only so that `--dump-config` can write `theme_name` plus your two
* overrides instead of freezing sixty resolved colours into a file -- which
* would pin the theme's palette at the moment you dumped it and quietly
* make every later change to that theme invisible. */
bool theme_set[THEME_COLORS_MAX];
/* Loader scratch: true while a named theme file is being read, which is how
* the flags above tell "the theme said it" from "you said it". */
bool in_theme;
/* The layout a project with no file of its own opens as. Yours rather than a
* guess about your stack: relative paths in it bind to whichever project is
* being opened, so one file is the shape you start every project in. NULL is
* one pane running your shell. */
char *project_layout;
/* Every file this config was built from, in the order they were read: the one
* that was loaded and everything it included, whether or not each existed.
* Kept because the watcher needs it — a theme you can include is a theme you
* expect to reload when you save it — and recorded even for a file that is
* not there, so a session started before you wrote one still notices it
* appearing. */
char *files[CONFIG_FILES_MAX];
size_t nfiles;
/* Everything this load had to complain about, in the order it found them,
* each already carrying the file and line it happened at. A session shows the
* first (there is one status line); `--check` shows them all, which is the
* whole difference between a warning and a linter. */
char msgs[CONFIG_MSGS_MAX][192];
size_t nmsgs;
/* Loader scratch: the file being read right now, so a complaint can say which
* one it came from without every call site being handed the path. Meaningless
* once loading has finished. */
const char *loading;
} config_t;
/* Defaults, then <config dir>/config.kdl on top. Never fails: on a bad file
* it keeps the defaults and writes the reason to `err`. */
void config_defaults(config_t *c);
bool config_load(config_t *c, const char *path, char *err, size_t errcap);
/* Apply a named theme over a config that is already loaded, as if the config
* had asked for it: the same lookup, the same file, the same derivations. How
* a live theme switch is made, and why switching is not a special path through
* the loader -- a theme applied at runtime and one named in a config have to
* end up at the same palette, or the session and its file disagree. */
bool config_apply_theme(config_t *c, const char *name, char *err,
size_t errcap);
/* Where named themes are looked for, in order. Absolute and expanded. */
size_t config_theme_dirs(const config_t *c, char (*out)[512], size_t max);
/* Every theme that can be named, sorted, each one listed once -- a name in an
* earlier directory shadows the same name in a later one, which is what makes
* a theme of yours able to replace a shipped one. */
size_t config_themes(const config_t *c, char (*out)[THEME_NAME_MAX],
size_t max);
/* Write `theme_name "x"` into a config file, replacing the line already there
* or adding one. For a switch somebody asked to keep: the session can hold a
* theme for itself, but only the file can hold it past a restart. */
bool config_write_theme_name(const char *path, const char *name, char *err,
size_t errcap);
/* $SLOSH_CONFIG, else $XDG_CONFIG_HOME/slosh/config.kdl, else ~/.config/… */
const char *config_default_path(void);
/* Every complaint from the last load, oldest first: "config.kdl:12: ...".
* Returns how many were written. */
size_t config_messages(const config_t *c, const char **out, size_t max);
void config_free(config_t *c);
/* The files this config was read from, oldest first: `files[0]` is the one that
* was loaded, the rest are what it included. Returns how many were written. */
size_t config_files(const config_t *c, const char **out, size_t max);
/* Shader chains from text rather than from a file: one entry, several separated by
* `;`, or a whole `shaders { ... }` block. Every shape a config file has, because
* the point of using the config's syntax at a prompt is that the two directions
* match -- what you prototype is what you paste, and what you paste is something
* you can type back.
*
* Each pass goes where its own `where=` says; `default_chrome` is only what a pass
* that keeps quiet means. Here because the parser belongs with the config: a chain
* typed at a running pane and a chain in a config file have to mean the same
* thing, or prototyping teaches you something that then does not work.
*
* Both arrays hold SHADE_MAX and `exprs` twice that; the compiled programs belong
* to the caller, since a pane's chain outlives no load in particular. Returns how
* many entries were understood, writing the first refusal to `err`. */
size_t config_parse_chain_doc(const char *text, color_t default_color,
bool default_chrome, shader_t *content,
size_t *ncontent, shader_t *chrome,
size_t *nchrome, expr_prog_t **exprs,
size_t *nexprs, char *err, size_t errcap);
/* The same, from a file: a `shaders { }` block as `contrib/chrome` and
* `contrib/shaders` ship them, read with the parser that already knows this
* format. Entries are routed to the two chains by their own `where=`, defaulting
* to content as a config file does, so a preset lands where its author said.
* Anything else at the top level is ignored -- hand it a whole config.kdl and
* only the shaders apply. Both arrays hold SHADE_MAX; `exprs` holds twice that.
* Returns how many entries were understood. */
size_t config_parse_chain_file(const char *path, color_t default_color,
shader_t *content, size_t *ncontent,
shader_t *chrome, size_t *nchrome,
expr_prog_t **exprs, size_t *nexprs, char *err,
size_t errcap);
/* Whether a name is one of the config's own settings. For the layout loader, which
* shares this syntax and needs to tell a config apart from a session file -- one
* list, so the two answers cannot disagree. */
bool config_is_setting(const char *name);
/* What a chord does after the leader. Direct bindings answer here too, so
* pressing the leader first never makes a binding stop working. */
action_t config_lookup(const config_t *c, int key, uint16_t mods);
/* What a chord does on its own, with no leader pressed. ACT_NONE for the
* overwhelming majority of keys, which is what lets them reach the pane. */
action_t config_lookup_direct(const config_t *c, int key, uint16_t mods);
/* Whether any binding fires without the leader, so the cheatsheet knows
* whether it has a section to draw. */
bool config_has_direct(const config_t *c);
/* The reverse of config_parse_chord: what to call a binding on screen. A
* shifted letter is written the way you would type it ("H"), because "S-h" is
* a description of a keystroke rather than a keystroke. */
void config_chord_name(int key, uint16_t mods, char *out, size_t cap);
/* A phrase for the cheatsheet, and which group it belongs under. `group` is
* one of a fixed set, in the order they should be shown. */
const char *config_action_label(action_t a);
/* The name this action is written as in a config file. */
const char *config_action_name(action_t a);
/* The whole config as a file you could have written, with the values it
* currently holds. Generated rather than kept on disk, because a checked-in
* copy of the defaults is a second source of truth and drifts. Caller frees. */
char *config_render(const config_t *c);
/* The same, for a config nobody has edited: what a fresh install would do.
* Caller frees. */
char *config_dump_defaults(void);
/* The palette in force as a theme file, which is how a theme is started from
* the one you are looking at. The config dump writes `theme_name` rather than
* sixty colours, so this is the only place they are all spelled out. */
char *config_render_theme(const config_t *c);
char *config_dump_theme(void);
const char *config_action_group(action_t a);
/* "ctrl+a", "alt+left", "\\", "f" -> key + mods. False if unparseable. */
bool config_parse_chord(const char *text, int *out_key, uint16_t *out_mods);
#endif /* SLOSH_CONFIG_H */
|