aboutsummaryrefslogtreecommitdiffstats
path: root/src/cmd.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-20 23:22:33 +0200
committeruser <user@clank>2026-08-20 23:22:33 +0200
commitb33e8c13ff7981effc7ec2735e6aeaaaf1705af7 (patch)
tree4933eb476b5d09e9d8131986c84bb966bca317a2 /src/cmd.c
parentfuzz: libFuzzer harness for all six parser surfaces (diff)
downloadslosh-b33e8c13ff7981effc7ec2735e6aeaaaf1705af7.tar.gz
slosh-b33e8c13ff7981effc7ec2735e6aeaaaf1705af7.tar.xz
slosh-b33e8c13ff7981effc7ec2735e6aeaaaf1705af7.zip
slosh: floating panes and the drop grammar
A pane can float: lifted out of the layout, drawn over the tiled panes and under the modals, freely moved and shaped. The model is a flag and a rect on the leaf, exactly like `minimized` -- the seat in the tree is kept, layout skips it, the siblings absorb its share, and un-floating returns it home. The drawn rect is the wanted one clamped to the tab every frame, never written back, so a terminal squeeze round-trips exactly; an explicit move or resize *does* write back, because the hand is the author of the intent. Floating pops the pane to the centre: the jump is what says it happened. Painted after everything tiled, so the hit list -- searched backwards -- resolves an overlap to the float you can see: the z-order and the click order are the same list. It casts a shadow (float_shadow, one number), and `floating` joins the states table above `unfocused`, so a float is never dimmed as merely-unfocused. The hands: the title moves it, any other border resizes it (a bottom corner on both axes; the grabbed edge pins at walls and floors, never sliding the window), H J K L move a focused float, the =/+ key grows it about its centre, `-` shrinks it (its split was a refusal anyway), and hovering shows which edges a grab would take, from the same derivation the press uses. Equalize moved to `0` to free the key; `C-a f` floats (finder went to `s`, where it reads as search); `C-a F` opens a new floating shell -- the throwaway terminal; the function row f1..f12 became nameable in configs along the way. Guards throughout: the last tiled pane cannot float or minimize away, closing it lands the top float, splits are refused with their own words, floats are not swap targets. Dropping got a grammar: while a pane is carried, the pane under the pointer subdivides into a swap centre and four edge bands meaning "insert me beside this pane, on this side" -- detach_leaf + place_beside, the surgery cross-tab moves already had. Bands are thirds (the first map of quarters showed the swap owning half of each axis), the hovered band fills the half the drop would hand over, the release resolves against the painted frame's hits so it cannot disagree with the fill, and a drop between siblings along the axis is a reorder that always fits -- refusing it was refusing the most ordinary drag there is. Also: rename-pane/rename-tab as palette actions (renaming was a double-click and nothing else, the one verb the palette could not reach), and C-u clears the rename editor like it clears the picker's query. Design log in PLAN-FLOATING-PANES.md; to fold into DESIGN.md as D22/M10-11.
Diffstat (limited to 'src/cmd.c')
-rw-r--r--src/cmd.c38
1 files changed, 36 insertions, 2 deletions
diff --git a/src/cmd.c b/src/cmd.c
index d3415ae..24baec9 100644
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -120,6 +120,22 @@ static char *cmd_json(app_t *a, screen_t *s, input_parser_t *in,
free(txt);
return j.buf;
}
+ /* The emitter's own output: what a client's terminal would be sent for
+ * this frame. The model and the bytes are two different things, and
+ * asserting on the model while the bytes are wrong is a lesson this
+ * session has already paid for once (the graphics cmd says the same).
+ * Rendering updates the diff state, so a second call is the delta —
+ * which is itself worth testing. */
+ if (strcmp(fmt, "bytes") == 0) {
+ screen_render(s);
+ json_t j;
+ json_init(&j);
+ json_obj_open(&j, NULL);
+ json_bool(&j, "ok", true);
+ json_str(&j, "bytes", s->out ? s->out : "", s->out_len);
+ json_obj_close(&j);
+ return j.buf;
+ }
return jok_raw("screen", screen_dump_json(s));
}
if (strcmp(cmd, "deadline") == 0) {
@@ -156,9 +172,11 @@ static char *cmd_json(app_t *a, screen_t *s, input_parser_t *in,
return jok_int(NULL, 0);
}
if (strcmp(cmd, "split") == 0) {
+ uint32_t id = (uint32_t)jv_geti(req, "id", 0);
bool rows = strcmp(jv_gets(req, "dir", "cols"), "rows") == 0;
- if (!app_split_pane(a, (uint32_t)jv_geti(req, "id", 0), rows))
- return jerr("no such pane");
+ if (!app_split_pane(a, id, rows))
+ return jerr(app_pane_floating(a, id) ? "cannot split a floating pane"
+ : "no such pane");
return jok_int("id", app_focused_pane_id(a));
}
if (strcmp(cmd, "focus") == 0) {
@@ -166,6 +184,22 @@ static char *cmd_json(app_t *a, screen_t *s, input_parser_t *in,
return jerr("no such pane");
return jok_int("id", app_focused_pane_id(a));
}
+ /* The throwaway terminal, scripted: a fresh shell floating over the
+ * current tab. */
+ if (strcmp(cmd, "new-float") == 0) {
+ uint32_t id = app_new_float(a);
+ if (!id) return jerr("cannot open a pane");
+ return jok_int("id", id);
+ }
+ /* Toggle, like the button and the palette entry: M10c grows placement
+ * arguments here, this is the same one verb everything else runs. */
+ if (strcmp(cmd, "float") == 0) {
+ uint32_t id = (uint32_t)jv_geti(req, "id", 0);
+ /* Two refusals share the word: no such pane, and no tiled pane left to
+ * float over. */
+ if (!app_toggle_float(a, id)) return jerr("cannot float that");
+ return jok_int("floating", app_pane_floating(a, id) ? 1 : 0);
+ }
if (strcmp(cmd, "edit-config") == 0) {
return app_edit_config(a) ? jok_int("id", app_focused_pane_id(a))
: jerr("no room for another pane");