diff options
| author | user <user@clank> | 2026-08-16 19:13:28 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-16 19:13:28 +0200 |
| commit | c58d873b409c7a166a64ceabf178692b0b4467c2 (patch) | |
| tree | 45ed8190996bfa1c7b59309c1b07d50f5dadae19 /docs | |
| parent | sl0ppty: the prompt speaks config, not a dialect of it (diff) | |
| download | slosh-c58d873b409c7a166a64ceabf178692b0b4467c2.tar.gz slosh-c58d873b409c7a166a64ceabf178692b0b4467c2.tar.xz slosh-c58d873b409c7a166a64ceabf178692b0b4467c2.zip | |
sl0ppty: a pane can change tabs
sl0ppty cmd '{"cmd":"move-pane","id":3,"tab":2}'
sl0ppty cmd '{"cmd":"move-pane","id":3,"tab":2,"dir":"rows"}'
sl0ppty cmd '{"cmd":"move-pane","id":3,"tab":0,"name":"logs"}'
The mechanism only. There is no key and no gesture for it yet -- that is the next
conversation, and guessing at it now would mean building the wrong one twice.
**The pane keeps running.** Measured, not assumed: `pid=3062557` before the move
and `pid=3062557` after, in a different tab, with its scrollback intact and still
answering what it is sent. That is the whole reason to move a pane rather than open
one where you wanted it and close this one.
It is tree surgery and it reuses the surgery that was already here. `place_beside`
is the second half of `split_node_with` -- everything after "make the leaf" --
lifted out so that a leaf which already exists somewhere else can be grafted the
same way, flat sibling or new split, on either side. It takes the tab rather than
`cur(a)` now, because replacing a root has to land on the right one and `cur(a)` was
only right while the sole caller was splitting the pane you were looking at.
`detach_leaf` is `close_leaf` without the funeral.
Three decisions worth naming:
**The destination is a tab id, not an index.** A tab whose last pane leaves has
nothing to be, so it is removed -- and every index after it moves. An id survives
that; an index quietly means a different tab. The move re-finds the destination by
id *after* the removal for exactly this reason, and there is a test that empties a
tab and checks the pane landed where it was sent.
**A move drops what the old tab thought about the pane** -- a zoom naming it, and
its minimised flag. Both are the tab's opinion and not the pane's: carried across,
the first zooms a pane that has left and the second files the arrival in a strip
nobody asked for.
**It does not follow the pane.** Which tab you are looking at is a question about
intent, and this is the mechanism; `move-pane` answers with the tab id so a caller
can select it if that is what it meant. "Into a tab of its own" is its own call
rather than new-tab-then-move, because a pane that is the only thing in its tab
would otherwise have its tab removed from under the new one, and the order those
two happen in is precisely the bug.
Verified: `tests/test_move_pane.py`, 25 checks -- the pane still running and still
talking afterwards, placement beside and under the destination's focus, focus
landing on the arrival, the emptied tab, a tab of its own, every refusal (no such
pane, no such tab, already there, already alone), the dropped zoom and minimise,
and after three moves a layout with no overlaps that dumps and rebuilds as itself.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/layouts.md | 20 | ||||
| -rw-r--r-- | docs/scripting.md | 1 |
2 files changed, 21 insertions, 0 deletions
diff --git a/docs/layouts.md b/docs/layouts.md index 36029ab..7166751 100644 --- a/docs/layouts.md +++ b/docs/layouts.md @@ -64,6 +64,26 @@ sl0ppty -s work cmd '{"cmd":"apply-layout","kdl":"layout { tab { pane } }","repl Without `replace`, the tabs the file describes are added to what is already there. +## Moving a pane between tabs + +```bash +sl0ppty cmd '{"cmd":"move-pane","id":3,"tab":2}' # beside that tab's focus +sl0ppty cmd '{"cmd":"move-pane","id":3,"tab":2,"dir":"rows"}' # under it instead +sl0ppty cmd '{"cmd":"move-pane","id":3,"tab":0,"name":"logs"}' # into a tab of its own +``` + +The pane keeps running: same pty, same scrollback, same process — a move is tree +surgery, not a new pane and a funeral. The destination is a tab **id** rather than +an index, because a tab whose last pane leaves is removed and every index after it +shifts; an id survives that. + +Two things a move deliberately drops, both of them the old tab's opinion rather +than the pane's: a zoom that named it, and its minimised flag. Carried across, the +first would zoom a pane that has left and the second would file the arrival in a +strip nobody asked for. Which tab you are *looking* at does not change. + +There is no key for this yet — the mechanism first, the presentation after. + ## Purposes A pane or tab can carry a `purpose=` label — `agent:main`, `logs`, `db` — for diff --git a/docs/scripting.md b/docs/scripting.md index de7e712..b311a3e 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -31,6 +31,7 @@ Panes and tabs are addressed by **id**, so a background tab is scriptable. | `close` `rerun` | `id`, or 0 for the focused pane | | `clear-shaders` | `id`, or 0 for the focused pane; answers `cleared:0\|1` — the way back from a pane that painted itself unreadable | | `new-tab` `select-tab` `close-tab` `move-tab` `set-name` | tabs, by `id` or `index` | +| `move-pane` | `id` of the pane, `tab` id to move it into (`0` for a tab of its own, with an optional `name`), `dir:"cols"\|"rows"`. The pane keeps running — same pty, same scrollback | | `set-purpose` | `target:"pane"\|"tab"`, `id`, `purpose` | | `dump-layout` `apply-layout` | see [layouts](layouts.md) | | `notify` | put a line in the session's status area | |
