From b8c5e8772e646fa21f6afdd2a875a1bd52848bd5 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 18:53:37 -0300 Subject: A column can be empty, as acme's can; new panes go where acme's makenewwindow puts them Newcol makes an empty column; closing a column's last pane leaves it empty with the keyboard on its tag; Delcol and Joincol alone take a column away; the session's last pane closing quits. The +New stand-in and replaceStillborn / stillborn_joiner are gone. Every new pane goes through exec.placeNew, which follows acme's makenewwindow (active column, empty column whole, blank space, halving the biggest) or, with Placement pardes, the old rules. ColumnTags is gone: column tags are always shown. A grip drag shows acme's box cursor. Co-Authored-By: Claude Opus 5.5 --- src/look.zig | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'src/look.zig') diff --git a/src/look.zig b/src/look.zig index 020aacb8..b6a89b64 100644 --- a/src/look.zig +++ b/src/look.zig @@ -1251,20 +1251,15 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void { const free = p.freeSlot() orelse return p.reportError(id, "look", error.NoPaneSlots); const nt = p.newShell(free, dir) catch |err| return p.reportError(id, "look", err); nt.greet = true; - const src = layout.splitParent(p, id); - const f = layout.findPane(p, src).?; - layout.insert(p, f.col, f.idx + 1, free); - layout.splitBelow(p, src, nt); - p.active = free; - p.stillborn_joiner = nt.serial; + exec.placeNew(p, id, id, free, .shell); }, .file => |target| { if (comptime pdf_enabled) if (target.kind == .pdf) { if (focusPaneByPath(p, target.path, target.at)) return; const free = p.freeSlot() orelse return p.reportError(id, "look", error.NoPaneSlots); - const nt = panes.Pdf.openPane(p, free, target.path, target.at.line) catch |err| + _ = panes.Pdf.openPane(p, free, target.path, target.at.line) catch |err| return p.reportError(id, "look", err); - exec.placeDoc(p, id, free, nt); + exec.placeNew(p, id, id, free, .doc); return; }; // focus an existing pane on this path (rescrolled), else open @@ -1273,7 +1268,7 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void { const nt = panes.File.open(p, free, target.path, target.at.line) catch |err| return p.reportError(id, "look", err); if (target.at.col > 0) nt.body.cur_col = @intCast(target.at.col - 1); - exec.placeDoc(p, id, free, nt); + exec.placeNew(p, id, id, free, .doc); layout.compute(p); nt.file.?.scroll -|= @max(1, p.rects[free].h -| BOX_H) / 2; if (target.at.end_line != 0) { @@ -1285,9 +1280,9 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void { .image => |target| { if (focusPaneByPath(p, target.path, .{})) return; const free = p.freeSlot() orelse return p.reportError(id, "look", error.NoPaneSlots); - const nt = panes.Image.create(p, free, target.path, &.{}) catch |err| + _ = panes.Image.create(p, free, target.path, &.{}) catch |err| return p.reportError(id, "look", err); - exec.placeDoc(p, id, free, nt); + exec.placeNew(p, id, id, free, .doc); }, } } -- cgit v1.3 From b660adabce36e90cbfbb52d95715528983376ab5 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 22:02:04 -0300 Subject: A pane whose whole text fits in its rows never scrolls to keep a margin The scroll margin reveals text; with none off screen there is nothing to reveal. ensureCursorVisible and a centred look both leave such a pane at its top. A longer file keeps helix's clamp (hxdiff and hxparity unchanged). Co-Authored-By: Claude Opus 5.5 --- src/look.zig | 3 ++- src/panes.zig | 18 +++++++++++++++++- src/pardes.zig | 18 ++++++++++++++++++ test/snapshots/find.golden | 11 +---------- test/snapshots/lookrange.golden | 38 ++++++-------------------------------- test/snapshots/lsp-client.golden | 16 +--------------- test/snapshots/rsearch.golden | 11 ++--------- test/snapshots/rsearch.snap | 19 +++++++++---------- 8 files changed, 56 insertions(+), 78 deletions(-) (limited to 'src/look.zig') diff --git a/src/look.zig b/src/look.zig index b6a89b64..a413bf50 100644 --- a/src/look.zig +++ b/src/look.zig @@ -968,7 +968,8 @@ pub fn focusPaneLine(p: *Pardes, id: usize, at: Spot, landing: enum { center, ke if (pane.file) |*f| { if (at.line > panes.File.nlines(p.gpa, f)) return; if (landing == .center) { - const next = (at.line - 1) -| pane.rows / 2; // center, clamp at top + // center, clamp at top; a text that fits is shown whole + const next = if (pane.textFits()) 0 else (at.line - 1) -| pane.rows / 2; if (next != f.scroll) { f.scroll = next; f.syntax_dirty = true; diff --git a/src/panes.zig b/src/panes.zig index a7e5c668..47582692 100644 --- a/src/panes.zig +++ b/src/panes.zig @@ -512,6 +512,16 @@ pub const Pane = struct { } } + /// Every line of the file's text fits in the body's rows; the empty + /// line after a final newline is no text. + pub fn textFits(pane: *Pane) bool { + if (pane.file) |*f| { + const lines = File.nlines(pane.gpa, f) - @intFromBool(std.mem.endsWith(u8, f.content, "\n")); + return lines <= pane.rows; + } + return false; + } + pub fn ensureCursorVisible(pane: *Pane) void { const off = pane.scroll(); var in_context = false; @@ -539,7 +549,13 @@ pub const Pane = struct { pane.scrollBy(pane.body.cur_row - margin - off); } else if (pane.body.cur_row > last - margin) { var to = pane.body.cur_row + margin; - if (pane.file) |*f| to = @min(to, @as(i32, @intCast(File.nlines(pane.gpa, f) -| 1))); + if (pane.file) |*f| { + // The margin only reveals text: a pane its whole text + // fits in never scrolls for it. A longer one keeps + // helix's clamp at the buffer's last line. + const lines: i32 = @intCast(File.nlines(pane.gpa, f)); + to = @min(to, if (off == 0 and pane.textFits()) @max(pane.body.cur_row, lines - 1 - @intFromBool(std.mem.endsWith(u8, f.content, "\n"))) else lines - 1); + } if (comptime Pdf.enabled) { if (pane.pdf) |pv| to = @min(to, @as(i32, @intCast(modal.lineCount(pv.text) -| 1))); diff --git a/src/pardes.zig b/src/pardes.zig index 23341904..18eedccb 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -1354,6 +1354,24 @@ test "BootShell keep leaves the untouched boot shell a dragged document joins; r } } +test "a pane its whole text fits in never scrolls to keep a margin" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 30 }); + defer p.deinit(); + const pane = try p.setTestFile("one\ntwo\nthree\n"); + // A second pane under it, as acme's placement puts one: right under the + // three lines, which leaves the file exactly three body rows. + const below = p.freeSlot().?; + _ = try p.openTutorView(below); + exec.placeNew(p, 0, 0, below, .doc); + p.active = 0; + p.sync(); + try std.testing.expectEqual(@as(u16, 3), p.rects[0].h - pane.tag_rows); + for ("jj") |c| p.update(.{ .key = .{ .cp = c, .text = &.{c} } }); + p.sync(); + try std.testing.expectEqual(@as(i32, 2), pane.body.cur_row); + try std.testing.expectEqual(@as(i32, 0), pane.scroll()); +} + test "dragging a column's only pane to another leaves that column in place, empty" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 32 }); defer p.deinit(); diff --git a/test/snapshots/find.golden b/test/snapshots/find.golden index 50557fad..e6e1bac1 100644 --- a/test/snapshots/find.golden +++ b/test/snapshots/find.golden @@ -65,15 +65,9 @@ |12: /tmp/pardes-snap/find/cwd/sub/nest/zqmark-gamma.txt Save Tty Collapse Del |13: 1 gamma body == snap third grid=120x41 cursor=7,16 -|6: 2 sub/nest/zqmark-gamma.txt -|7: 3 zqmark-alpha.txt -|8: 4 |15: /tmp/pardes-snap/find/cwd/zqmark-alpha.txt Save Tty Collapse Del |16: 1 alpha body == snap back grid=120x41 cursor=7,13 -|6: 1 sub/ZQMARK-beta.txt -|7: 2 sub/nest/zqmark-gamma.txt -|8: 3 zqmark-alpha.txt == snap word grid=120x41 cursor=7,13 |3: 1 Find Find |4: Find / @@ -81,11 +75,8 @@ |3: 1 Find Find / |4: |13: 1 gamma body Last -== snap nomatch grid=120x41 cursor=7,7 +== snap nomatch grid=120x41 cursor=7,8 |3: 1 Find -|6: 2 sub/nest/zqmark-gamma.txt -|7: 3 zqmark-alpha.txt -|8: 4 |13: 1 gamma body |18: /tmp/pardes-snap/find/cwd/+Search Save Tty Collapse Del |19: 1 diff --git a/test/snapshots/lookrange.golden b/test/snapshots/lookrange.golden index 19a83f2c..7d802b0b 100644 --- a/test/snapshots/lookrange.golden +++ b/test/snapshots/lookrange.golden @@ -85,37 +85,11 @@ |19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-9 #e5eee5,#0d1410, 10-15 #f7e4a5,#5e4923, 16-18 #e5eee5,#0d1410, 19-149 d,#0d1410, |20: 0-0 d,#608269, 1-1 d,#0d1410, 2-18 #59675c,#0d1410, 19-149 d,#0d1410, |21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-11 #e5eee5,#0d1410, 12-17 #f7e4a5,#5e4923, 18-149 d,#0d1410, -== snap hit2 grid=150x31 cursor=17,12 -|8: 4 dddd -|9: 5 eeee -|10: 6 ffff -|11: 7 gg NEEDLE gg -|12: 8 hhhh NEEDLE -|13: 9 iiii -|14: -|15: -|16: +== snap hit2 grid=150x31 cursor=17,15 == style hit2 grid=150x31 -|8: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|9: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|10: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-18 #e5eee5,#0d1410, 19-149 d,#0d1410, -|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-11 #e5eee5,#0d1410, 12-17 #f5f7ee,#3c614a, 18-149 d,#0d1410, -|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -== snap back grid=150x31 cursor=15,12 -|8: 3 cccc dccc -|9: 4 dddd -|10: 5 eeee -|11: 6 ffff -|12: 7 gg NEEDLE gg -|13: 8 hhhh NEEDLE -|14: 9 iiii +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-18 #e5eee5,#0d1410, 19-149 d,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-11 #e5eee5,#0d1410, 12-17 #f5f7ee,#3c614a, 18-149 d,#0d1410, +== snap back grid=150x31 cursor=15,14 == style back grid=150x31 -|8: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-15 #e5eee5,#0d1410, 16-149 d,#0d1410, -|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-9 #e5eee5,#0d1410, 10-15 #f5f7ee,#3c614a, 16-18 #e5eee5,#0d1410, 19-149 d,#0d1410, -|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-149 d,#0d1410, -|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-9 #e5eee5,#0d1410, 10-15 #f5f7ee,#3c614a, 16-18 #e5eee5,#0d1410, 19-149 d,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-149 d,#0d1410, diff --git a/test/snapshots/lsp-client.golden b/test/snapshots/lsp-client.golden index e4ea4c90..17eb0d40 100644 --- a/test/snapshots/lsp-client.golden +++ b/test/snapshots/lsp-client.golden @@ -25,19 +25,5 @@ |20: 5 main.rs:5:13-15 |21: 6 let y = add(3, 4); |22: 7 -== snap gr-opened grid=100x25 cursor=21,6 -|3: 2 -|4: 3 fn main() { -|5: 4 let x = add(1, 2); -|6: 5 let y = add(3, 4); -|7: 6 let _ = x + y; -|8: 7 } -|9: 8 +== snap gr-opened grid=100x25 cursor=21,7 == snap gr-back grid=100x25 cursor=21,6 -|3: 1 mod lib; -|4: 2 -|5: 3 fn main() { -|6: 4 let x = add(1, 2); -|7: 5 let y = add(3, 4); -|8: 6 let _ = x + y; -|9: 7 } diff --git a/test/snapshots/rsearch.golden b/test/snapshots/rsearch.golden index 6e078612..872693bb 100644 --- a/test/snapshots/rsearch.golden +++ b/test/snapshots/rsearch.golden @@ -17,17 +17,14 @@ |14: 3 code.txt:3:1-7 |15: 4 count_x again |16: 5 -== snap select grid=210x31 cursor=14,9 -|7: 2 open sub/dep.txt:3 here -|8: 3 count_x again -|9: 4 pick two words +== snap select grid=210x31 cursor=14,10 |10: ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ |16: /tmp/pardes-snap/rsearch/cwd/+Search Save Tty Collapse Del LocationsConfig |17: 1 code.txt:4:1-8 |18: 2 pick two words |19: 3 == snap path-open grid=210x31 cursor=7,22 -|10: +|10: 4 pick two words |19: /tmp/pardes-snap/rsearch/cwd/sub/dep.txt Save Tty Collapse Del |20: 1 d1 |21: 2 d2 @@ -36,10 +33,6 @@ |24: 5 d5 |25: 6 d6 == snap step1 grid=210x31 cursor=13,9 -|7: 1 run count_x now -|8: 2 open sub/dep.txt:3 here -|9: 3 count_x again -|10: 4 pick two words == snap step2 grid=210x31 cursor=13,9 == snap step3 grid=210x31 cursor=13,9 == snap selclick grid=210x31 cursor=14,10 diff --git a/test/snapshots/rsearch.snap b/test/snapshots/rsearch.snap index d880e557..0ce257bd 100644 --- a/test/snapshots/rsearch.snap +++ b/test/snapshots/rsearch.snap @@ -50,17 +50,16 @@ snap select # right-CLICK the path `sub/dep.txt:3` (line 2, col 18): isFileChar keeps / . : # so the click expands the whole token and it RESOLVES — a look, not a search — # opening dep.txt scrolled to line 3 (DEPTARGET). The "don't break paths" case. -press right 18 8 -release right 18 8 +press right 18 9 +release right 18 9 wait 10000 DEPTARGET stable 700 15000 snap path-open # A click does not merely LIST the hits, it goes to one: this re-search of -# `count_x` (line 3, col 12: acme sized code.txt to its four lines, and the -# first search scrolled line 1 off) also STEPS onto hit 1, so the cursor lands -# in the match, not where the click was. -press right 12 9 -release right 12 9 +# `count_x` (line 1, col 15) also STEPS onto hit 1, so the cursor lands in the +# match, not where the click was. +press right 15 8 +release right 15 8 stable 700 15000 snap step1 # ...and the same word clicked in its OTHER place (line 3, col 10) opens NO @@ -75,9 +74,9 @@ press right 10 10 release right 10 10 stable 700 15000 snap step3 -# acme sized code.txt to its four lines, and on so tight a pane a click on -# line 4 scrolls it to keep its context rows: drag its last row (the resize -# handle) down four rows first, so the rows below stay where they are clicked. +# acme sized code.txt to its four lines, so line 4 is on its last row, and a +# pane's last row is its resize handle: a press there would drag the seam, not +# sweep text. Drag that handle down four rows first. press left 40 11 drag left 40 15 release left 40 15 -- cgit v1.3