aboutsummaryrefslogtreecommitdiffstats
path: root/docs/panes.md
blob: 3a2804684705938650675821ffc8dc41b2fb09e0 (plain) (blame)
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
# Panes and tabs

A tab is a tree of splits. Panes keep running in every tab; only the tab you are
looking at is drawn.

## Arranging

- **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.
- **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

- **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
  when the title holds the middle (the title keeps its cells for the
  double-click rename). Arming an edge leaves the name, the buttons and a
  dead pane's epitaph legible: the heavy rule goes round them.
- **Drag the gap between panes** to move that boundary. Where two gaps cross,
  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.
- **...or onto a tab in the strip** to move it into that tab, or onto the `+` for a
  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.
- **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
  frame's corner to minimise (`▬`), zoom (`□`) or close (`✕`) it.

## The tab bar

The strip of tabs sits along the top by default. It does not have to:

```kdl
tab_bar_side "left"   // or "right"; "top" is the default
tab_bar_width 18      // columns the sidebar takes
tab_bar_index "right" // the tab number at the far end, not against the name
tab_bar_pad 0         // blank rows above the first tab
tab_gap 0             // ...and between one tab and the next
tab_bar_chrome true   // frame it like a pane
tab_bar_status_lines 1 // rows one status may wrap onto
newtab_button true    // the `+` at the end of the list
newtab_pad 1          // ...and the air before it
```

A **sidebar lists one tab per row** down the chosen edge, and the panes trade
the strip's row for its columns. Everything the strip does travels with it:
click to switch, double-click to rename in place, drag a pane onto a tab or
onto the `+` — which, given a row of its own and the columns to spare, spells
itself out as `+ new tab`. That button sits `newtab_pad` rows below the last
tab (columns after it, in the top strip): it is the one row in the list that
does something other than "go here", and flush against the tabs it is what you
hit when you meant the last one. The air is paint, not layout — the panes keep
their rectangle whatever it is, and it is given up before the button is when
the strip runs out of room. Set `newtab_button false` to drop the button
altogether; `new-tab` is still a [chord](keys.md) and a palette entry, and a
pane can no longer be dropped onto it. The pane count and the prefix badge move
to the sidebar's bottom rows; the [status line](config.md) along the bottom
keeps the full width. A terminal too narrow to give up the columns falls back
to the top row until it grows.

**The sidebar is framed like a pane**, so it belongs to the chrome rather than
floating beside it. Under [`compact`](config.md#compact) the frame's inner line
_is_ the tab's outer ring: the two meet in real junctions (`┬`, `┴`) and read
as one figure, the same way a divider running into the ring does. Set
`tab_bar_chrome false` to give those two columns back to the labels.

**Under each tab, the sidebar says what the panes inside are up to.** A pane
that announced a status over [OSC 5577](scripting.md#a-pane-can-draw-its-own-chrome)
puts that line under its tab — visible from every other tab, which is the
point — and a pane that died shows how instead (`exited: status 3`). At most
`tab_bar_status` rows per tab (3 by default, 0 turns it off); more than that
and the last row is an ellipsis rather than a status pretending the list is
complete. A short terminal cuts the list the same way, and says so the same
way: **the statuses give way before the tabs do**, because the list is how you
navigate and a status is only an annotation on it — a talkative pane must not
be able to cost you a tab you can click.

**`tab_gap` puts air between one tab and the next**, which is what stops a
list of tabs that each carry a paragraph of status from reading as one column
of text. It is the space *between* tabs only: above the first is `tab_bar_pad`
and before the `+` is `newtab_pad`, so none of the three is paid twice. Like
them it is paint rather than layout, and like them it is given up a row at a
time when the sidebar runs short — a gap must never be what costs you the tab
it was separating.

**A status can be given more than one row.** `tab_bar_status_lines 3` word-wraps
it instead of cutting it, which is the difference between `add a summary…` and
the sentence — three rows of a sixteen-column sidebar is forty-eight cells. The
rows under a tab then read as a paragraph per pane rather than a line per pane,
so **alternate panes sit on alternate bands** (`tab_status_stripe`, derived from
the theme unless you name it): with the shape no longer saying where one pane's
status ends, the band does. At one line there is nothing to disambiguate and no
band is drawn, which leaves a translucent terminal's own background alone. Every
row of a status is the same door — clicking any of them jumps to that pane — and
they all count against `tab_bar_status`. **Clicking a status row jumps to the
pane that said it**, across tabs. A status is set in italic rather than indented under its tab — the slant
says "this belongs to the row above" without spending columns a narrow sidebar
has none of, and says it on a monochrome theme too. The colour is part way
from `tab_count` to `tab_hover` — the live thing in the sidebar should not read
in the flat grey of the chrome around it, and should not shout over the tab it
belongs to either. Unset, `tab_status_fg` is mixed from those two, so a theme
gets a status colour in its own family without naming one (a monochrome theme
mixes two greys and stays grey). Name it, or `tab_status_bg` for a band, and
yours wins; the band is unset by default, which leaves a translucent
terminal's own background alone. The top strip has one row and no room for any of this, which is half of
why the sidebar exists.

**The tab number can sit at the far end of the row.** `tab_bar_index "right"`
draws `api` on the left and `3` hard against the right edge, instead of the
default `3:api`:

```
│ tests        1 │        │ 1:tests        │
│ verylongproj 2 │   vs   │ 2:verylongproje│
│ api          3 │        │ 3:api          │
```

Every name then starts in the same column and the numbers line up in one of
their own, which is worth a column of a sidebar and is not available to the top
strip at all — a tab there is exactly as wide as its label, so it has no far end
to align against, and the setting is ignored. A row too narrow to spare the
columns goes back to the prefix, because the name is the part you came for, and
a name long enough to reach the number always stops one cell short of it:
`verylongproje2` is a tab called verylongproje2 as far as any reader can tell.

**Every tab is a plate, not a word on the background.** The one you are in is
filled with the accent (`tab_active_bg`); the rest sit on `tab_idle_bg`, 43% of
the way from the theme's background to `tab_idle`, the grey it writes their
labels in — a fraction chosen as a contrast ratio, about 1.9:1 against the
background whether the theme is dark or light. That label grey is brighter than
the one on idle frames and titles, because a tab you are not in is a place to
go and has to read from across the screen, where a dimmed border only has to be
findable. Two things come of the quiet fill: a tab has edges, so where one ends, how
far the row you are about to click reaches, and — in the sidebar especially —
which rows are tabs rather than statuses are all visible; and the active fill
has something to be brighter *than*, which on a strip of short labels is most
of how the eye finds it. Unset, it is mixed from two colours every theme
defines, so each gets a plate in its own family — paper lightens, phosphor
greens. Name it and yours wins: set it to your own `default_bg` for the bare
strip, or somewhere further along for a louder one.

**An unnamed tab borrows the directory its focused pane is in** — the
kernel's answer, so a `cd` moves the label with you, and home shows as `~`.
A real name or a [purpose](layouts.md#purposes) wins the moment one exists;
when a tab's panes sit in different directories, the focused one names the
tab. This holds wherever the bar sits, top row included.

## 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**. Pressing `f` again puts it back in the seat it kept,
with the layout exactly as it was.

`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.

**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
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
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.

## Finding a pane

Tabs stop being navigation somewhere around six projects. `C-a s` opens a picker
over the whole session: every tab, including panes a small window has collapsed
out of sight. Type to narrow it by pane title, tab name or
[purpose](layouts.md#purposes); arrows, `C-n`/`C-p` or tab to move, `C-u` to clear
what you typed, enter to go.
A dot marks where you already are.

The finder searches what this session has; `C-a w` lists what is on disk,
every project under your roots, open or not ([workspaces](workspaces.md)). A
project you have not opened yet has no pane for the finder to match.

## Panes that were given a command

Split off a shell, do something, type `exit`: it closes, like a terminal should.
But a pane that was _told to run something_, from a [layout](layouts.md) or the
[control API](scripting.md), keeps what it printed when that something exits,
with two lines saying what ran and how it ended, and two buttons:

```
 [ran: npm run dev]
 [process exited: status 3]
╰ exited: status 3 ───────────────────────────────[re-run]─[close]─╯
```

`[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
its error message when you get back.

Which panes stay is one setting, `keep_dead`: `commands` (the default), `all`,
or `none`.

## 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.

`min_pane` is where that starts happening; `min_split` is the smaller pane a
split is willing to _create_. A split is refused when either floor would be
broken, including the axis it does not divide, since splitting cannot improve
that one: a pane already too short for two rows has no room for two columns
either, whatever its width says.

## Text, images, bells

- **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`,
  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.** 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
  choice. A [chrome shader](chrome.md) can make it louder.