summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 22:02:04 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commitb660adabce36e90cbfbb52d95715528983376ab5 (patch)
tree655cbad2aee854430700359d454dce7f626dc651
parent0acd945d85887da88f2863219e706000ca738cdd (diff)
downloadpardes-b660adabce36e90cbfbb52d95715528983376ab5.tar.gz
pardes-b660adabce36e90cbfbb52d95715528983376ab5.zip
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 <[email protected]>
-rw-r--r--src/look.zig3
-rw-r--r--src/panes.zig18
-rw-r--r--src/pardes.zig18
-rw-r--r--test/snapshots/find.golden11
-rw-r--r--test/snapshots/lookrange.golden38
-rw-r--r--test/snapshots/lsp-client.golden16
-rw-r--r--test/snapshots/rsearch.golden11
-rw-r--r--test/snapshots/rsearch.snap19
8 files changed, 56 insertions, 78 deletions
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