diff options
| author | Peter Geissler <peter@haxx.in> | 2026-09-02 12:32:45 +0200 |
|---|---|---|
| committer | Peter Geissler <peter@haxx.in> | 2026-09-02 12:32:45 +0200 |
| commit | 794e22914666775cab0b1b730a000b2529cec6b5 (patch) | |
| tree | 77cb83a5f6417d92bc21e3f7ea0a49be45b2ac9b /docs | |
| parent | docs: big overhaul (diff) | |
| download | slosh-794e22914666775cab0b1b730a000b2529cec6b5.tar.gz slosh-794e22914666775cab0b1b730a000b2529cec6b5.tar.xz slosh-794e22914666775cab0b1b730a000b2529cec6b5.zip | |
docs: deprecate boring design narrative, more trimming and culling
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/chrome.md | 55 | ||||
| -rw-r--r-- | docs/design.md | 107 | ||||
| -rw-r--r-- | docs/index.md | 1 | ||||
| -rw-r--r-- | docs/layouts.md | 150 | ||||
| -rw-r--r-- | docs/nav | 1 | ||||
| -rw-r--r-- | docs/workspaces.md | 125 |
6 files changed, 35 insertions, 404 deletions
diff --git a/docs/chrome.md b/docs/chrome.md index 1450dcb..45b223a 100644 --- a/docs/chrome.md +++ b/docs/chrome.md @@ -15,33 +15,13 @@ Nothing else changes: same shaders, same `amount`, same [expressions](shaders.md#strength-as-an-expression), same chain order. One new word. -You have already seen one: the default bell flash (a blink, then a breathe on -the frame of a pane that rang, in theme's `bell` colour) is exactly this, a -chrome tint whose amount reads `since`. (The shimmer that crosses the pane's -body at the same moment is the same idea without the new word: an ordinary -content pass in the same chain.) Writing your own `bell { … }` replaces both; -`bell_indicator false` silences them with the mark. - -## Two rules - -**`channel="fg"`, almost always.** A border's *background* is the terminal's own -default; mixing that towards a colour paints a rectangle behind the glyphs -instead of colouring them, and leaves it there until the pane changes state. -`fg` also keeps the padding inside the border out of it, since a blank cell has -no foreground to colour, so the effect lands on exactly the rules, the title and -the buttons. - -**The contents are cut out of the pass.** A full-strength chrome tint cannot -reach your text, and a full-strength content tint cannot reach the border. They -are two rects and two passes. - ## Positions are the whole frame's `x`/`y` are counted from the frame's top-left corner, with `cols`/`rows` the size of the pane's rect, not of one side. One pass over the whole ring, so an effect can travel round a border instead of restarting at every corner. -For anything that *travels*, though, the coordinate you want is distance along +For anything that _travels_, though, the coordinate you want is distance along the ring, which the language can work out. Clockwise from the top-left: ``` @@ -49,17 +29,6 @@ the ring, which the language can work out. Clockwise from the top-left: : y == rows-1 ? 2 * cols + rows - 3 - x : 2 * cols + 2 * rows - 4 - y) ``` -with the perimeter itself `2 * cols + 2 * rows - 4`. Three things fall out of it: - -- `min(d, P - d)` is distance the short way round, so an effect crosses the - corner it started from instead of stopping at a seam. -- `(p - head + P) % P` is "how far ahead of the head", which makes an arc rotate - rather than a blob slide. -- `sin(TAU * p / P)` is that position **as an angle**, one cycle to the lap, and - a sine wraps at the corner for free, where a ramp needs the trick above. Any - whole number of lobes (`* 3 / P`) fits the ring exactly, which is what makes a - standing wave stand. - There is no way to name a subexpression, so the formula is written out in full, which is why a small effect is a long line. @@ -67,15 +36,12 @@ Thirteen of them are ready to paste in `contrib/chrome`: a shine running round the border, a sine comet with softer shoulders, marching ants, a standing wave, drifting sparkles, ripples leaving a corner in both -directions, a rotating arc, a bit-drift pattern, a heartbeat, a static gradient, -corner glints, a sheen across the title rule and an XOR barcode along it. Its -README has the two techniques they share: the angle idiom above, and overdrive -then clip (`* 14 - 3000` after a sine) to turn a glow into an arc. +directions, etc. ## A border that says something -Hung off a [state](config.md#pane-states), this is chrome doing the job chrome is -for, in no room and no words: +Depending on a pane's [state](config.md#pane-states), we can visually communicate +through it's chrome: ```kdl states { @@ -88,21 +54,14 @@ states { } ``` -`since` is what makes a *flash* possible: it is how long the pane has been in +`since` is what makes a _flash_ possible: it is how long the pane has been in this state, where `t` only knows what time it is. The `bell` state ends when you -look at the pane, because that is what answering a bell is. - -A pane that a small terminal has collapsed to a single row is chrome all the way -through, which makes this the only kind of pass that can still colour it. That -is the state where a colour is worth the most. +look at the pane, because that is what answering a bell is. :-) ## Animation costs a clock Anything that reads `t` or `since` asks the session to keep painting, at -`anim_ms` (50ms, so 20fps) and only while such a pass is actually on screen. A -pulse hung off `dead` costs a frame clock while a pane is dead and nothing for -the rest of the session; the same pass in the global `shaders` block costs it for -the whole session. +`anim_ms` (50ms, so 20fps) and only while such a pass is actually on screen. `anim_ms 0` turns the clock off, which leaves an animated shader advancing only when something else causes a frame. diff --git a/docs/design.md b/docs/design.md deleted file mode 100644 index 458858c..0000000 --- a/docs/design.md +++ /dev/null @@ -1,107 +0,0 @@ -# How it works - -Two decisions do most of the work; the rest follow from them. - -## One geometry - -**Everything drawn registers what it is, as it is drawn**, and a click is a -lookup in that list. Drawing and hit-testing cannot disagree, because there is -only one of them. The dashed guide that shows where a split would land is drawn -from the same rect the click will resolve against, in the same pass, so a -preview that lies is not a bug you can have. - -The same rule applies to state that could be derived: the layout is a pure -function of the tree and the terminal's size, recomputed every frame. Nothing -stores where a pane is. That is why a small terminal can collapse a tab to a list -of headers and come back to exactly the layout you had, and why a pane cannot be -left greyed out by the one code path that forgot to clear a flag. - -Sizes are **weights**, so an even split is equal weights and resizing is not a -special case of anything. - -## It runs headless - -`slosh --script` is the whole thing without a terminal: commands in, the -composited screen out as JSON. The interactive client and the test suite drive -the same object through the same commands, so there is exactly one implementation -of what a key does. - -That is what makes the tests real: they type keys and assert on the screen a -client would have seen, rather than on the internals. 1,500-odd checks in about -thirteen seconds, plus 260 more from unit tests on the parts that are pure -functions (the KDL parser, the shader pass, the expression compiler). - -## The shape of it - -``` -src/app.c the session core: pane and tab lifecycle, input routing -src/app_layout.c the layout: splits, tree edits, resizing, focus -src/app_draw.c drawing the chrome, the shader passes, graphics -src/app_ui.c the finder, the palette, modals, renaming -src/app_session.c layout files, workspaces, dumping a session out -src/pane.c one pty + one libghostty-vt terminal, composed into the screen -src/screen.c our cell buffer, and the minimal byte stream to update a terminal -src/server.c the session process: socket, poll loop, config watcher -src/client.c the attached terminal: bytes in, bytes out -src/config.c the config, the keymap, the cheatsheet's names -src/shader.c the colour passes; src/expr.c compiles their strengths -src/kdl.c a hand-rolled KDL subset, for configs and layouts -``` - -About 15k lines of C. The vendored terminal core does VT parsing, scrollback, -selection, images and key encoding; everything above is ours. - -## How it is formatted - -`clang-format` for the C (`.clang-format`) and `ruff` for the Python -(`ruff.toml`), imports sorted and then laid out. `make fmt` applies both, -`make fmt-check` only reports, and `make hooks` installs a pre-commit hook that -formats what you staged, so what lands in git is already formatted. - -Getting the two tools: - -``` -make tools # a venv in .venv with the pinned ruff -apt install clang-format # ...or brew, or your package manager -``` - -clang-format is a system package everywhere. ruff usually is not, and -`pip install --user ruff` is refused outright on a distro python (PEP 668), so -`make tools` builds a venv in `.venv/` and everything looks there **before** -`$PATH`. That order matters: `ruff.toml` pins the version with ruff's own -`required-version`, so a system ruff of the wrong one refuses to run, and -preferring `$PATH` would strand somebody whose `.venv` has exactly the version the -repo asked for. The venv survives `make clean`; `rm -rf .venv` starts over. - -The pin is there because a formatter whose output moves between releases turns -"formatted" into "formatted by whoever committed last", and the diff lands on the -next person. A mismatch is refused with both versions named rather than quietly -reformatting the tree. Bumping it is one line in `ruff.toml`, one in the Makefile, -and a reformat commit, which is the honest cost of a new version. - -The base is LLVM, because that is what this code was written in by hand: two -spaces, 80 columns, `char *p`, braces attached. The settings that differ from it -were chosen by measuring the reformat rather than by taste: LLVM alone rewrote 38% -of the lines, mostly by splitting `if (!p) return false;` in two and reflowing -comments that are prose. Keeping those two and leaving include order alone brings -it to 17%, which is alignment and table packing. - -Comments are not reflowed. They are prose here, wrapped by hand, and sometimes the -line breaks carry meaning: a paragraph, a list, a diagram of a pane. A comment -that runs long is a thing for a person to fix. - -The Python is the opposite: left entirely at ruff's defaults. The C here was -hand-formatted into a house style worth arguing for; the Python was not, and -every knob turned would be an argument to have again later. It cost a 60% -reformat of the test suite, once. - -**Python imports are sorted and C includes are not**, which is not an -inconsistency. A C include order can be load-bearing (`_GNU_SOURCE` ahead of the -system headers, a grouping somebody wrote by hand) while Python import order -is not. Where it genuinely is, a statement between the imports ends the block ruff -will sort, and the tests lean on exactly that: `sys.path.insert(...)` sits between -the standard library and `from harness import ...`, so the harness import cannot be -hoisted above the line that makes it importable. - -The vendored terminal core is never formatted: it is pinned by commit (D12), and -reformatting it would turn every re-vendor into a merge. diff --git a/docs/index.md b/docs/index.md index c46eb7a..a40d11b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -77,6 +77,5 @@ goes away. Reattach with the same command. - **[Layouts](layouts.md)**: a session as a file you check in. - **[Workspaces](workspaces.md)**: projects on disk, opened by name. - **[Scripting](scripting.md)**: the control socket, and what a pane can draw. -- **[How it works](design.md)**: the two decisions that shape the rest. About 15k lines of C, no dependencies beyond libc and the vendored core. diff --git a/docs/layouts.md b/docs/layouts.md index 3818e77..0aae0d8 100644 --- a/docs/layouts.md +++ b/docs/layouts.md @@ -1,7 +1,6 @@ # Layouts -A session can be a file, so a project's window layout is checked in with the -project. +A project's window layout can be defined programatiicaly (and checked in/managed with version control): ```kdl layout { @@ -22,29 +21,15 @@ slosh --layout project.layout ## What to call them **`*.layout`**, and a project's is `slosh.layout`. The syntax is the same KDL -subset the config uses — one parser reads both files — but the extension is -not `.kdl`: zellij speaks KDL layouts with a different schema, and naming the -wrong owner is worse than not naming the parser. Point your editor's KDL -highlighting at `*.layout`. The old `*.layout.kdl` spelling is still found by -the project scan, never written. +subset the config uses. Point your editor's KDL highlighting at `*.layout`. -A filename cannot enforce anything, so **the document decides which schema it is -held to, not the flag that read it**. `slosh --check` reads what is in front of -it: a file with `layout` or `tab` at the top is checked as a layout, a config -handed to the same flag is still checked as a config, and a file too broken to -parse at all is judged by its name, there being nothing else left to read. +## How to verify them ``` $ slosh --check project.layout project.layout: ok, a layout ``` -A clean layout says which schema it passed, because otherwise a layout nothing -could open and a layout with nothing wrong in it read the same. A broken one -reports every problem it found, one indented `file:line: what` per line, then a -summary, and exits 1: the shape [checking a config](config.md#checking-one) -already has, so one editor compile step and one pre-commit hook read both: - ``` $ slosh --check broken.layout broken.layout:2: unknown pane property: cmd @@ -70,7 +55,7 @@ slosh: config.kdl: this is a config, not a layout: `gap` is a setting A config handed to `--layout` is named for what it is instead of refused for having no tabs, which is true of it in the least useful way. -## The shape +## The layout nodes - `tab`: one per tab, in strip order. `name=`, `cwd=`, `purpose=`, `active=true` for the one you land in. @@ -84,18 +69,12 @@ having no tabs, which is true of it in the least useful way. _would_ run. - `focus=true`: the pane you start in, within its tab. - `floating=true`: the pane starts [floating](panes.md#floating-a-pane), with - `x=` `y=` `w=` `h=` as its wanted rect in cells. All four absent takes the - centred default; `w=` `h=` alone is that size, centred — and centred again - on every resize, until an explicit move or resize pins it. The rect is - intent: a smaller screen clamps it, and the place you asked for survives. It still holds a seat in the tree: its `weight=` and - position are where un-floating lands it. A file whose every pane floats - lands one as the backdrop, because an overlay needs something to be over. + `x=` `y=` `w=` `h=` as its wanted rect in cells. All four absent takes the centred default. - `purpose=`: a label for tooling; see below. **A relative `cwd=` in a layout _file_ resolves against that file's own directory**, never against the directory you started the session from. It is -the rule [`include`](config.md#include) already follows, applied to the other -half of the same syntax. A layout with no `cwd=` anywhere +the same rule [`include`](config.md#include) already follows. A layout with no `cwd=` anywhere starts in the file's directory too, so the common case needs no `cwd=` at all. An absolute path is unchanged, and `~` is still your home directory. @@ -115,122 +94,7 @@ layout { } ``` -Clone that repo somewhere else and the tab still opens on the checkout, with the -pane that said `src` still in the checkout's `src`. - -A layout that arrives as _text_ over the socket has no file, and so no -directory to be relative to; relative paths in it resolve against wherever the -session is. - -A full annotated example is -`config/example.layout`. - -## Writing one back out - -```bash -slosh -s work cmd '{"cmd":"dump-layout"}' > project.layout -``` - -writes the session as a layout file: tabs, splits, proportions, directories, -commands, which pane you were in. So a session can be checked in, or put back -after a restart. -`contrib/slosh-dev` -is that loop, for when the thing you are rebuilding is slosh itself. - -**A pane is dumped with the command it is running**, whether a layout gave it one -or you typed it. `label`, what a layout said, comes first and outlives the -program that ran it, so a project's `command="npm run dev"` stays that in the file -after the server has exited. Failing that, the pane's terminal is asked what owns -it: a pty has a foreground process group, and that group _is_ the job running in -the pane. So arranging a project by hand and writing it down are one job rather -than two: split, start the dev server and the log tailer, `C-a W`, done. - -Three things are deliberately not a command. **A shell at a prompt**, because -`command="zsh"` on every idle pane would restore a session where each shell runs -inside a shell. **A background job**, because it does not own the terminal and a -layout that resurrected `&` jobs in the foreground would describe a session nobody -had. And **an [ephemeral](panes.md#panes-that-were-given-a-command) pane** (the -editor `C-a e` opened) because restoring one reopens a file somebody finished -with. - -Argv is joined with shell quoting, since `command=` is handed to `/bin/sh -c`: -`python3 -c 'import x; x.go()'` comes back as one argument rather than three. - -What a dump can honestly restore is the _shape_, and the command that made it. -What it cannot is the state inside a program (a shell's history, a half-written -commit message, an editor's undo) and it does not pretend otherwise. A pane -running the session's default shell is dumped as a pane with no command, so -restoring gives you a fresh one. - -Three arguments shape what comes out: - -```bash -slosh -s work cmd '{"cmd":"dump-layout","tab":3}' -slosh -s work cmd '{"cmd":"dump-layout","relative_to":"~/dev/api","suspend":"commands"}' -``` - -| argument | what it says | -| ------------- | -------------------------------------------------------- | -| `tab` | a tab **id**, or `0` (the default) for the whole session | -| `relative_to` | a directory every `cwd=` is written relative to | -| `suspend` | which panes come back laid out but not running | - -`relative_to` is the writing end of the reading rule above: point it at the -project and the dump comes out with `cwd="."` and `cwd="src"` in it rather than -your home directory, which is what makes the file committable without an edit -afterwards. `suspend` takes four words: - -| word | means | -| ---------- | ---------------------------------------------------------------------------------------------------------------- | -| `as-is` | what is suspended in the session right now, the default here, because a session dump should describe the session | -| `none` | everything comes back running | -| `commands` | every pane that was given a command | -| `all` | nothing starts until you touch it | - -`commands` is the one you want for a layout you will open often, and it is what -[workspaces](workspaces.md) saves with: the pane running this morning's dev server -belongs in the file asleep, or opening the project starts a dev server every time. - -The reply carries the document as `kdl` and counts beside it (`panes` and -`suspended`) so a caller can report "4 panes, 2 suspended" without parsing back -the document it was handed. A `tab` that does not exist is an error rather than an -empty document, because an empty layout is a plausible answer for a tab you closed -a minute ago and a silent one for a typo in an id. - -A dump records `focus=true` in **every** tab, not only the one you were looking -at, so restoring a six-tab session puts you back where you were in all six. - -## Applying one to a running session - -```bash -slosh -s work cmd '{"cmd":"apply-layout","path":"project.layout"}' -slosh -s work cmd '{"cmd":"apply-layout","kdl":"layout { tab { pane } }","replace":true}' -``` - -Without `replace`, the tabs the file describes are added to what is already -there. - -## Moving a pane between tabs - -```bash -slosh cmd '{"cmd":"move-pane","id":3,"tab":2}' # beside that tab's focus -slosh cmd '{"cmd":"move-pane","id":3,"tab":2,"dir":"rows"}' # under it instead -slosh 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. - -`C-a >` and `C-a <` push the focused pane one tab along, `C-a b` gives it a tab of -its own, and a pane can be dragged onto a tab in the strip; see -[keys](keys.md#defaults) and [the mouse](panes.md#the-mouse). +A full annotated example is `config/example.layout`. ## Purposes @@ -23,6 +23,5 @@ Driving it scripting.md Inside - design.md windows.md macos-release.md diff --git a/docs/workspaces.md b/docs/workspaces.md index 134b8b2..a685748 100644 --- a/docs/workspaces.md +++ b/docs/workspaces.md @@ -1,6 +1,6 @@ # Workspaces -slosh has the concept of **workspaces**. Essentially a workspace is a +slosh has the concept of **workspaces**. Essentially a workspace is a pane layout that represents a project. slosh will scan your config's `project_roots` folder(s) for two kinds of sub folders: ones that have a `slosh.layout` file, and ones that have a `.git` but no `slosh.layout`. in the latter case our project picker @@ -15,89 +15,33 @@ project_roots "~/dev" "~/work" depth=2 project_layout "~/.config/slosh/project.layout" ``` -`project_roots` takes more than one directory because people have more than one -(work in one tree, everything else in another), and `depth` is how many levels below -each root to look, 2 by default, 1 to 8. +`project_roots` takes one or more director{y,ies}, and `depth` is how many levels below +each root to look, 2 by default. ## From the keyboard -| key | does | -|---|---| -| `w` | the projects picker | -| `W` | write this tab as this project's layout | -| `P` | set this pane's purpose | - -`w` and `W` are the same shifted pair as `p`/`P`: go to one, write one down. All -three are bindable as `workspaces`, `save-workspace` and `set-purpose`, and all -three are in the palette under `projects`, so they work before you have rebound -anything; see [keys](keys.md#defaults) and [every action](keys.md#every-action). +| key | action | does | +| --- | -------------- | --------------------------------------- | +| `w` | workspaces | the projects picker | +| `W` | save-workspace | write this tab as this project's layout | +| `P` | set-purpose | set this pane's purpose | The picker narrows on a project's name, its path and which marker it has, so typing `.git` gives you the projects with no layout yet, which is the list you want when you are about to write one. A project that is already open shows its purpose instead, and a dot marks the workspace you are in. -The flow the feature is shaped around, once through: - -``` -C-a w picker: ~/work newthing .git · no layout -Enter your project_layout, cwd bound to newthing -C-a Enter, npm run dev arrange it, run things -C-a P service:web Enter tag the pane that matters -C-a W "wrote slosh.layout · 4 panes, 2 suspended" -``` - -Tomorrow, `C-a w Enter` rebuilds that tab with the dev server in the pane you put -it in and not running. A tab with no name of its own takes the project's, because a -strip reading `1 2 3` is the thing workspaces are for. - -**Closing one is `C-a X`**, which closes the tab and the workspace with it: -membership *is* the tab's purpose, so there is nothing else to put away and no -second key to learn ([keys](keys.md#defaults)). `close-workspace` over the socket -is the same thing for a workspace whose layout opened several tabs: it closes -every tab carrying that purpose and says how many. +**Closing one is `C-a X`**, which closes the tab and the workspace with it. ## Saving one -`C-a W` writes the focused tab to `slosh.layout` in the project's directory, -and the toast says what happened: -`wrote slosh.layout · 4 panes, 2 suspended`. +`C-a W` writes the focused tab to `slosh.layout` in the project's directory. **What each pane is running goes in with it.** That is what makes writing a layout the same act as arranging one: split the tab, start the dev server, start the log tailer, tag the ones that matter, `C-a W`. A pane's terminal has a foreground process group and that group is the job running in it, so nothing has to be -declared up front for it to be recorded; see -[writing one back out](layouts.md#writing-one-back-out) for what is deliberately -*not* counted as a command (a shell at a prompt, a background job, an ephemeral -pane). - -**A project's layout is not a verbatim dump of the session**, and `suspend` is -where the two part company. It takes one of four values: - -| `suspend` | writes `suspended=true` on | -|---|---| -| `as-is` | whatever is suspended right now, the default for `dump-layout` | -| `none` | nothing | -| `commands` | every pane that has a command, the default for `save-workspace` | -| `all` | every pane | - -The defaults differ because the two verbs answer different questions. -[`dump-layout`](layouts.md#writing-one-back-out) is *put this session back*, so it -reports the state it found. `save-workspace` is *this is what this project needs -open*, and the pane running this morning's dev server is a pane that should be laid -out tomorrow rather than started: twelve checked-out projects are not twelve -running dev servers ([layouts](layouts.md#the-shape)). A suspended pane still shows -what it would run, so the layout stays readable as a record of the commands. - -Neither can restore what is *inside* a program, and the file does not pretend -otherwise: a shell's history and an editor's buffers are not in it. - -**Over the API, writing over a layout that is already there is refused without -`force`; from the keyboard `C-a W` replaces it and the toast says `replaced`.** -The asymmetry is deliberate: the operator pressing the key is looking at the -tab being written, and the file is committed text, so the diff is the -confirmation. A script has no witness, so it says so in the request. +declared up front for it to be recorded. `save-workspace` also takes `path`, for a tab that is not a workspace yet: that is how a hand-arranged tab becomes a project's layout without opening it through the @@ -105,12 +49,11 @@ picker first. `slosh --check slosh.layout` holds the result to the layout schema and prints one `file:line: what` per problem, so a project's layout goes through the -same check as everything else you commit; see -[layouts](layouts.md#what-to-call-them). +same check as everything else. ## Identity -A workspace *is* its tab's [purpose](layouts.md#purposes): +A workspace _is_ its tab's [purpose](layouts.md#purposes): ``` project:api.9f3c1d20 @@ -122,27 +65,7 @@ would have merged them and then focused the wrong one. Membership is that string and nothing else: no side table of open projects to fall out of step with the tabs. Because the session declared it, it is **locked**: no program running in a pane -can relabel a project tab, however chatty its title. That is the same rule any -declared purpose gets, and it is what makes the purpose safe for a tool to address. - -A tab that a layout gave some *other* purpose keeps it and is not a member. -Honouring it is the point: overwriting a declared purpose is precisely what the -lock forbids, and a project layout that names a tab `agent:main` meant it. -`open-workspace` counts those in `honoured`, so a caller can see that a tab was -built and deliberately not claimed. - -## Nothing watches - -Discovery is derived, never remembered: one `readdir` per root plus two `stat`s per -entry, run when the picker opens rather than per frame. **Nothing is stored, so -nothing can be stale**, and no watcher can promise that. Inotify does not fire for -a bind mount, an sshfs share or a checkout another machine wrote, so a cache that -trusted it would be wrong exactly when the answer mattered. - -One thing a scan cannot see is a project's layout file changing under a workspace -that is already open. `workspaces` reports each layout's `mtime`, so a tool that -keeps the mtime it opened with can compare and notice. Re-applying the new file over -the running panes is deliberately not offered: it would kill them. +can relabel a project tab, however chatty its title. ## From another program @@ -155,12 +78,12 @@ $ slosh -s work cmd '{"cmd":"open-workspace","name":"api"}' $ slosh -s work cmd '{"cmd":"panes"}' # keep the ones whose tab_id is 3 ``` -| verb | takes | answers | -|---|---|---| -| `workspaces` | -- | `roots`, and a `workspaces[]` of `name` `path` `purpose` `layout` (the file, or `""`) `mtime` `tab` (0 when it is not open) | -| `open-workspace` | `name` or `path`; `suspended` | `tab` `purpose` `path` `created` `tabs` `honoured`. Already open, it focuses that tab and answers `created:false` | -| `close-workspace` | `name` or `purpose` | `closed`, how many tabs went | -| `save-workspace` | `tab` (0 for the current one), `path` for a tab that is not a workspace yet, `suspend`, `force` | `path` `purpose` `panes` `suspended` `replaced` | +| verb | takes | answers | +| ----------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| `workspaces` | -- | `roots`, and a `workspaces[]` of `name` `path` `purpose` `layout` (the file, or `""`) `mtime` `tab` (0 when it is not open) | +| `open-workspace` | `name` or `path`; `suspended` | `tab` `purpose` `path` `created` `tabs` `honoured`. Already open, it focuses that tab and answers `created:false` | +| `close-workspace` | `name` or `purpose` | `closed`, how many tabs went | +| `save-workspace` | `tab` (0 for the current one), `path` for a tab that is not a workspace yet, `suspend`, `force` | `path` `purpose` `panes` `suspended` `replaced` | Every one of them is in the bare dispatcher too, so `slosh -s work cmd workspaces` and `slosh -s work cmd open-workspace api` do the same from a shell @@ -169,12 +92,6 @@ without quoting JSON ([scripting](scripting.md#the-control-socket)). `open-workspace` is idempotent: asking twice focuses the tab rather than building a second one, so a script that does not know whether it already ran is safe. `suspended:true` opens the layout with every -pane suspended whatever the file said: the *open ten projects, run zero processes* +pane suspended whatever the file said: the _open ten projects, run zero processes_ case, which is a different question from the one the project answered about which of its own panes are expensive. - -The useful part is the last step. Having opened a workspace, filter -[`panes`](scripting.md#the-control-socket) by that `tab_id` and act on the purposes -the project's own layout declared: the project decided that `service:web` is its -dev server and that it starts suspended, and neither the session nor the program -driving it had to know that beforehand. |
