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