aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_finder.py (follow)
Commit message (Collapse)AuthorAge
* slosh: floating panes and the drop grammaruser2026-08-20
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* sl0ppty: ruff format the Pythonuser2026-08-17
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The bumpy one, again. Every `.py` outside `vendor/` plus the three `contrib/` scripts that are Python with no extension: 60 files, 6,714 lines rewritten, no behaviour changed. Nothing here is worth reading -- the next commit adds it to `.git-blame-ignore-revs` so `git blame` walks past it. It is a bigger reformat than the C was, 60% of the lines against 17%, and for a reason worth writing down rather than tuning away: ruff and black break a call that does not fit onto one argument per line, where this code aligned the continuation under the opening paren. The test suite is thousands of `check("some prose", cond, detail)` calls, so nearly every one of them changed shape and the suite grew 3,308 lines. Line length was the one lever that moved it -- 120 columns would have been a 40% reformat and no growth at all -- and it was left at 88, because picking an unusual width to dodge a one-time diff is paying forever to avoid paying once. Import sorting came with it. Two files had `import os, pty, uuid, select, struct, sys, termios, time, fcntl, signal` on one line and now have ten. Verified after, not before, because a formatter that alters behaviour would be worse than none: - `make retest`, 1801 checks green -- and this reformats the suite that does the checking, including `harness.py`, which every one of those files imports. - `make test-live`, the tty tests the fast suite skips. They matter here more than usual: `live_input.py` and `live_m0.py` are two of the files whose comma-imports were split, and nothing in the fast suite would have noticed if that broke them. - All 60 files still compile, `make fmt` twice in a row changes nothing the second time, and `ruff check --select I` has nothing left to say.
* sl0ppty: make the finder a modal, and a picker worth usinguser2026-08-15
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | It was a bare coloured rectangle painted over whatever pane it landed on: no border, no title, no scrim, no way out you could see. It read as output from the program underneath rather than as a window in front of it, which is the one thing an overlay has to get right. It is now a modal like the cheatsheet -- same frame, same scrim, same close button in the place a pane keeps one -- differing in the way that matters: the cheatsheet is read, so any key dismisses it, while this one is used, so it keeps the keyboard until it is answered. The list was the other half of the problem. It showed `1:` for a tab with no name, which looks like something failed to load; a row for a pane with no purpose was three columns of nothing; there was no mark for the pane you are already in, no count, and nothing to say a list had more below the tenth row -- so a list that scrolled looked like a list that ended. An empty result was an empty box, which reads as a broken finder rather than a wrong query. Now: a query field that looks like one (so it shows where the typing goes before you type), the current pane marked, tab/title/purpose told apart by weight, the row under the pointer lit, "N of M" in the footer, arrows for what is off each end, and "no pane matches that" when there is nothing. Keys, because a picker in this shape is reached for by hands that already know one: C-n/C-p, tab/shift-tab, home/end, page up/down, C-u to clear the query, and wrapping at both ends -- one press of Up for the last entry is worth more in a short list than overshoot protection. Backspace deleted a *byte*, so one press through a multi-byte character left the rest of it in the query; it deletes a character now, the way the rename editor already did. The finder owns the pointer as well as the keyboard: a press that is not on one of its rows dismisses it and stops there, rather than also landing on the layout behind and focusing the pane you were clicking away from. Found on the way: CSI Z (shift+tab) was not parsed at all, so it reached neither a binding nor the program in the pane -- every terminal sends back-tab that way, and it is the one chord whose shift lives in the final byte rather than in a modifier param.
* sl0ppty: the new-tab button is a bare +, and yours to chooseuser2026-08-14
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | It read "+tab" because of this, in the code, right above it: Not a bare "+": the frame already has one, for splitting a pane. Two verbs that look identical is a UI bug the fork shipped and noticed. That was true when it was written and has not been since f2f3d67, which removed the frame's "+" when the border became the button. The collision it was avoiding no longer exists, and the word was left explaining itself to nobody -- while every other affordance in this UI is a bare mark: _ # x on a frame, and a hint under the pointer for anyone who wants the word. So: "+", padded to three cells, because their own comment applies -- a one-cell target is a thing you miss with a mouse. Also configurable, as `newtab_mark`, which it should have been already: it was the only mark here that was not (zoom_mark, zoom_on_mark, close_mark, min_mark, bell_mark all are). Try "[+]" or "new" if you disagree; a session picks it up on save. Four tests were asserting the literal "+tab". Three of them only needed the new string, but two were using it to *locate* the button -- bar.index("+tab") -- which is exactly the mistake of asserting something incidental: what the button says is now config, and a test that spells it out fails the day somebody changes their mind. They look it up through the hit list now, which is what they meant. test_no_more_plus keeps the invariant that makes the bare mark safe, and now says so: there is exactly one "+" in the UI, so it means one thing. If one ever returns to a frame, that button needs its word back.
* sl0ppty: the status line counts this tab's panesuser2026-08-14
| | | | | | | | | | | | | | | | | | | | | | | | | The strip along the top counts every pane in the session, which is the right number for a strip that lists every tab. It is the wrong number for "how big is the thing I am working in", and nothing answered that. So the line along the bottom carries its own count, of this tab only. The two disagree as soon as there is more than one tab, which is the point of having both — and the test says so directly rather than checking each in isolation. It sits hard against the right edge with the pane's state inboard of it, which is the order the strip above already uses for its own pair: the number is always there, the mode only sometimes, and a thing that comes and goes should not shove a thing that does not. Both counts now say "1 pane" rather than "1 panes". The top has been ungrammatical since it was written and a test had faithfully pinned it that way; adding a second place to say it made that worth fixing rather than copying. One of my own tests had to go: it asserted the bottom line contained no pane count at all, on the reasoning that the top already had one. That was right when it was written and is exactly what this change reverses, so it now checks the thing that is still true — the bottom line is not a second copy of the tab strip.
* sl0ptty: M5 — responsive collapse, status bar, pane findersl0p2026-08-13
D6 as promised: layout(tree, rect) recomputed every frame, and a split that cannot give every child a floor (24 cols / 6 rows) collapses *itself* into a stack — one expanded child, every sibling a one-row header. No swap layouts, no remembered position, no synthesized anything. Three properties fall out of the design rather than being coded: - the expanded child is the one holding focus, so focusing a collapsed pane expands it: from the finder, by clicking its header, or by walking into it; - a hidden pane is never resized, so its program does not reflow off-screen; - a narrow/wide cycle returns the exact rects it started with, and a pane added afterwards is not stacked. That last one is the fork's 'panes stacked after a responsive collapse/expand cycle' bug, asserted directly. It cannot regress here without the layout function growing memory it does not have. A collapsed header shows the pane's title and its OSC 5577 status, so a stacked agent is still legible — the monitored strip, without a 'rail' mode. Below the size where headers fit, a node shows only the focused subtree: at 12x4 you get one pane instead of rects that do not fit on the screen. Every size from 200x60 down to 12x4 is asserted for overlap and containment. Status bar: tabs, a +tab button, live pane count, and the held prefix shown as a mode. The button is '+tab', not '+', because the frame already has a '+' for splitting — two verbs sharing a glyph is a UI bug the fork shipped, noticed, and wrote down; the collision showed up here as two hit-list tests failing. Pane finder (C-a f): substring filter over title, purpose and tab name across every tab, keyboard and mouse, painted last so its rows win the hit list. It is how you reach a pane the screen has collapsed away, which is tested. Deferred to M6: drag-to-reorder, which wants the same drag-state machine as pane resizing and should be built with it. 213 checks green (186 headless + 27 live).