From 2b1fac6fecbf7d7cc08f9dad24687b05e9968c9a Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 9 Aug 2026 10:11:03 -0300 Subject: tagbottom: the message row sits just above the tagline, not at the top --- src/builtins.zig | 6 ++--- src/pardes.zig | 35 +++++++++++++++----------- test/snapshots/tagbottom.golden | 10 ++++---- test/snapshots/tagbottom.snap | 13 +++++----- test/snapshots/tagbottomimage.golden | 6 ++--- test/snapshots/tagbottomimage.snap | 4 +-- test/snapshots/tagbottomshort.golden | 38 ++++++++++++++++++++++++++++ test/snapshots/tagbottomshort.snap | 49 ++++++++++++++++++++++++++++++++++++ 8 files changed, 128 insertions(+), 33 deletions(-) create mode 100644 test/snapshots/tagbottomshort.golden create mode 100644 test/snapshots/tagbottomshort.snap diff --git a/src/builtins.zig b/src/builtins.zig index 934b3996..ee542e77 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -194,9 +194,9 @@ pub const Wrap = struct { /// Taglines at the BOTTOM of every pane instead of the top. Global like Wrap /// and for the same reason — a layout where some panes label themselves above /// and others below is unreadable, so it is one switch for the whole screen. -/// The transient message row moves the other way to keep out of its way (see -/// render), which puts the two strips of chrome at the ends they always were, -/// just swapped. +/// The transient message row gives that row up and takes the one above it (see +/// render), so it is the pane's last BODY row either way and a prompt stays +/// beside the tag it is typed into. pub const Tagbottom = struct { pub fn run(c: Ctx) void { c.p.tag_bottom = !c.p.tag_bottom; diff --git a/src/pardes.zig b/src/pardes.zig index 592dd059..ef606f23 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -12084,17 +12084,17 @@ pub const Pardes = struct { try p.renderPane(arena, pane, p.rects[id], id == p.active); } - // ---- the transient message row: the end away from the tag ---- + // ---- the transient message row: the pane's last body row ---- // // An OVERLAY, not geometry: no rect moves, no pane shrinks, and a pane // with neither a message nor an armed prompt is not touched at all. // Drawn after every pane's body so it lands OVER whatever that row was - // showing, and painted in the tag bar's own colours — it is the - // tagline's twin at the other end of the pane, and reading as chrome is - // what keeps it from being mistaken for content. Out here rather than - // at the end of renderPane because renderPane returns early for a - // native PDF page and for an image, and a `/` on a PDF is a real search - // whose prompt has to be visible like any other. + // showing, and painted across the whole row — it is the tagline's twin, + // and reading as one strip rather than a stamp on a body line is what + // keeps it from being mistaken for content. Out here rather than at the + // end of renderPane because renderPane returns early for a native PDF + // page and for an image, and a `/` on a PDF is a real search whose + // prompt has to be visible like any other. // // Exactly two things can occupy the row and an ARMED PROMPT beats a // MESSAGE, because they are not the same kind of thing: a message is a @@ -12104,15 +12104,19 @@ pub const Pardes = struct { // // ponytail: the row is draw-only. A click on it lands wherever the body // under it says (tag clicks map to tag_col on the TAG row), so a prompt - // that moved down here cannot be clicked into or swept the way one on - // the tagline could — the keyboard still edits it in full. Upgrade path + // that moved off the tagline cannot be clicked into or swept the way it + // could up there — the keyboard still edits it in full. Upgrade path // is a hit test here that maps a press on this row to tag_col + the // marker offset, i.e. the tag row's own mapping with a constant added. for (&p.panes, 0..) |*slot, id| { const pane = slot.* orelse continue; const r = p.rects[id]; // no body row (a one-row pane, or one squeezed out entirely): the - // tag row is not ours to overwrite, so the message just waits + // tag row is not ours to overwrite, so the message just waits. One + // guard for both placements, because the row picked below is the + // last BODY row either way — "the pane has a body row" is the whole + // condition, and it is what keeps r.h - 1 - BOX_H from underflowing + // or landing above the pane. if (r.w <= config.GUTTER or r.h <= BOX_H) continue; // the same one input model renderPane cut off the tagline; only one // of the two can ever be armed (a body key arms one, exitTagEdit @@ -12128,10 +12132,13 @@ pub const Pardes = struct { if (text.len == 0 and !leader_here) continue; const tx = r.x + config.GUTTER; const tw = r.w - config.GUTTER; - // ...at the OTHER end from the tagline, whichever end that is: - // Tagbottom moved the tag onto this row, so the twin moves to the - // pane's first. They stay a pair with the body between them. - const row = if (p.tag_bottom) r.y else r.y + r.h - 1; + // the pane's last BODY row: its last row outright, or one up from + // that when Tagbottom has taken the last for the tagline. The first + // cut of Tagbottom sent this row to the pane's FIRST instead — the + // far end, symmetric with the tag — which put the prompt you are + // typing as far as the pane allows from the tag you are typing + // into. Beside the tagline is where it is read, so it stays there. + const row = if (p.tag_bottom) r.y + r.h - 1 - BOX_H else r.y + r.h - 1; // In the EDITOR's colours, not the tag bar's: this row is the one // place the program talks back to you about the buffer you are in, // and it reads as part of that buffer rather than as another strip diff --git a/test/snapshots/tagbottom.golden b/test/snapshots/tagbottom.golden index 27c6d42a..486390e2 100644 --- a/test/snapshots/tagbottom.golden +++ b/test/snapshots/tagbottom.golden @@ -277,7 +277,7 @@ | | | /tmp/pardes-snap/tagbottom/cwd/bottom.txt Save New Del -== snap search-prompt grid=100x30 cursor=6,11 +== snap search-prompt grid=100x30 cursor=6,28 |New Newcol Find Grep Help Tutor Dump NextColor Debug Kill | 2 bbb | 3 ccc @@ -289,7 +289,7 @@ | 9 iii | 10 jjj | /tmp/pardes-snap/tagbottom/cwd/top.txt bottom.txt Save New Del -| /ZZ +| 1 ZZONE | 2 ZZTWO | | @@ -306,7 +306,7 @@ | | | -| +| /ZZ | /tmp/pardes-snap/tagbottom/cwd/bottom.txt Save New Del == snap leader grid=100x30 cursor=9,13 |New Newcol Find Grep Help Tutor Dump NextColor Debug Kill @@ -320,7 +320,7 @@ | 9 iii | 10 jjj | /tmp/pardes-snap/tagbottom/cwd/top.txt bottom.txt Save New Del -| SPC +| 1 ZZONE | 2 ZZTWO | | @@ -337,7 +337,7 @@ | | | -| +| SPC | /tmp/pardes-snap/tagbottom/cwd/bottom.txt Save New Del == snap cut-down grid=100x30 cursor=7,4 |New Newcol Find Grep Help Tutor Dump NextColor Debug Kill diff --git a/test/snapshots/tagbottom.snap b/test/snapshots/tagbottom.snap index 389e0c36..5d30f7c6 100644 --- a/test/snapshots/tagbottom.snap +++ b/test/snapshots/tagbottom.snap @@ -1,6 +1,6 @@ -# Tagbottom: every pane's tagline on its LAST row instead of its first, and -# the transient message row (prompts, messages, the SPC indicator) at the other -# end with it. One global bool, so the whole screen flips at once. +# Tagbottom: every pane's tagline on its LAST row instead of its first, and the +# transient message row (prompts, messages, the SPC indicator) stepping up to +# the row just above it. One global bool, so the whole screen flips at once. # # The two things worth pinning here are not pixels. The first is that a click # still lands where it looks like it lands: the Sel coordinate space is @@ -96,9 +96,10 @@ release left 1 4 stable 400 8000 snap gutter-click # `/` in the lower pane's body arms the search input. The prompt is CUT off the -# tagline and drawn on the message row with the cursor — and with Tagbottom -# those two are at opposite ends of the pane: the tail keeps its builtins on -# row 29 while `/ZZ` and the cursor sit up on row 11, the lower pane's first. +# tagline and drawn on the message row with the cursor — and with Tagbottom the +# two are adjacent: the tail keeps its builtins on row 29 while `/ZZ` and the +# cursor sit on row 28, the last body row. The tagline under it stays whole — +# the prompt must not have eaten the row it was cut from. press left 10 14 release left 10 14 stable 400 5000 diff --git a/test/snapshots/tagbottomimage.golden b/test/snapshots/tagbottomimage.golden index 5508d01a..72b31809 100644 --- a/test/snapshots/tagbottomimage.golden +++ b/test/snapshots/tagbottomimage.golden @@ -91,9 +91,9 @@ |27: 0-0 d,#121212, 1-9 d,#080808, 10-11 #6c6c6c,#959595, 12-12 #6c6c6c,#352879, 13-13 #352879,#6c6c6c, 14-14 #352879,#6c5eb5, 15-65 #352879,#352879, 66-74 d,#080808, 75-75 d,#444444, 76-76 d,#080808, 77-149 #bdbdbd,#080808, |28: 0-0 d,#121212, 1-9 d,#080808, 10-10 #6c6c6c,#352879, 11-11 #352879,#6c6c6c, 12-65 #352879,#352879, 66-74 d,#080808, 75-75 d,#444444, 76-76 d,#080808, 77-149 #bdbdbd,#080808, |29: 0-1 #ffffff,#626262, 2-56 #949494,#121212, 57-74 d,#121212, 75-76 #ffffff,#262626, 77-129 #949494,#121212, 130-149 d,#121212, -== snap search-prompt grid=150x30 cursor=6,1 +== snap search-prompt grid=150x30 cursor=6,28 |New Newcol Find Grep Help Tutor Dump NextColor Debug Kill -| /ZZ +| (f🬱]0V7R*%m🬶🬁G🬬tN#XR9q🬢tC#f/🬜_GR(🬞🬙tCRp$o*7^+[#H7s🬢YDZ🬝▟ | iU)dSotT[r27T"\*Fh▐🬅g,🬰p🬛▙🬲&*3/9xy🬛▀0kCg▛🬱C,>C}PC06m🬝▟█ pic.ppm | "S▄;$gs,Xa▟T🬔s▖┼W▘?by🬋🬭uCR3/A3🬶lO▖▞🬃🬘╳🬟#=🬺🬕yoaJ@>4🬝🬵 | ─m!fAAD🬒jW>7JkR▄▜🬫🬞🬢FWx╲▟2ZT🬥/▖jteR?q🬶^🬅▀_@N─u4🬹🬝🬹 @@ -120,7 +120,7 @@ | eP🬗<┼A🬝🬹█ | ▀(u🬬🬝▟ | fc🬎🬹█ -| 🬝🬹 +| /ZZ | img /tmp/pardes-snap/tagbottomimage/cwd/pic.ppm New Del /tmp/pardes-snap/tagbottomimage/cwd New Del == snap del-image grid=150x30 cursor=5,6 |New Newcol Find Grep Help Tutor Dump NextColor Debug Kill diff --git a/test/snapshots/tagbottomimage.snap b/test/snapshots/tagbottomimage.snap index e46e19ba..8ac06d55 100644 --- a/test/snapshots/tagbottomimage.snap +++ b/test/snapshots/tagbottomimage.snap @@ -32,8 +32,8 @@ snapstyle img-bottom # `/` on an image pane. There is no text to search, and that is the point: the # prompt is drawn by the message-row pass, which runs OUTSIDE renderPane # precisely so an early-returning pane still gets one. With Tagbottom it lands -# on the pane's FIRST row (grid 1), the end away from the tag — over the top -# row of the art, not over the tagline that is now beneath it. +# on the last row of the art (grid 28), just above the tagline on grid 29 — +# over the picture, never over the tag it was cut from. key / send ZZ stable 600 8000 diff --git a/test/snapshots/tagbottomshort.golden b/test/snapshots/tagbottomshort.golden new file mode 100644 index 00000000..599dd29b --- /dev/null +++ b/test/snapshots/tagbottomshort.golden @@ -0,0 +1,38 @@ +== snap roomy grid=80x10 cursor=6,8 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| 1 one +| 2 two +| 3 three +| 4 four +| 5 five +| 6 six +| +| /ZZ +| /tmp/pardes-snap/tagbottomshort/cwd/s.txt Save New Del +== snap two-body grid=80x4 cursor=6,2 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| 1 one +| /ZZ +| /tmp/pardes-snap/tagbottomshort/cwd/s.txt Save New Del +== snap one-body grid=80x3 cursor=6,1 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /ZZ +| /tmp/pardes-snap/tagbottomshort/cwd/s.txt Save New Del +== snap tag-only grid=80x2 cursor=64,1 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/tagbottomshort/cwd/s.txt Save New Del +== snap regrown grid=80x10 cursor=6,8 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| 1 one +| 2 two +| 3 three +| 4 four +| 5 five +| 6 six +| +| /ZZ +| /tmp/pardes-snap/tagbottomshort/cwd/s.txt Save New Del +== snap leader-one-body grid=80x3 cursor=7,1 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| SPC +| /tmp/pardes-snap/tagbottomshort/cwd/s.txt Save New Del diff --git a/test/snapshots/tagbottomshort.snap b/test/snapshots/tagbottomshort.snap new file mode 100644 index 00000000..a6ca2828 --- /dev/null +++ b/test/snapshots/tagbottomshort.snap @@ -0,0 +1,49 @@ +# The Tagbottom message row against a pane with almost no body. That row is the +# pane's LAST BODY row — the tagline's neighbour once Tagbottom has taken the +# last row — so it only exists while the pane HAS a body row. Two rows is the +# smallest pane that does. One row is a tagline and nothing else, and there the +# message waits: not drawn over the tag, and not on the row above the pane, +# which is what `r.y + r.h - 1 - BOX_H` gives you when the guard is off by one. +file s.txt one\ntwo\nthree\nfour\nfive\nsix +start 10 80 s.txt +wait 8000 s.txt +stable 700 20000 +key space t b +stable 700 15000 +# `/` with room to spare: the prompt is cut off the tagline (grid 9) and drawn +# on grid 8, the last body row, with the cursor. +key / +send ZZ +stable 600 8000 +stable 400 5000 +snap roomy +# two body rows left; the prompt takes the lower one and line 1 still shows +resize 4 80 +stable 700 10000 +snap two-body +# one body row, and the prompt takes it. The tagline underneath must still be +# drawn in full — this is where an off-by-one eats it. +resize 3 80 +stable 700 10000 +snap one-body +# the tagline alone. No body row, so nothing of the prompt is drawn anywhere — +# not over the tag, not on the row above the pane. The tagline itself is +# untouched: the marker sits after the builtins, so cutting the tail there +# leaves every column of it exactly as `tag-only` would look unarmed. +resize 2 80 +stable 700 10000 +snap tag-only +# ...and grown back, still armed, back on the last body row +resize 10 80 +stable 700 10000 +snap regrown +key esc +settle 100 +stable 400 5000 +# the SPC leader indicator rides the same row, right-aligned, and shrinks onto +# the single body row with it +resize 3 80 +stable 700 10000 +key space +stable 400 8000 +snap leader-one-body -- cgit v1.3