summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-15 20:17:50 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit179863c0a7b011d5a4bd5e064f7d021d239aa96e (patch)
tree02f56d75f3627de575b912d22418e5b4e0eb7d0f /src
parentffa2fd2d4b40bc4ff2a05263cf82ee9638590edd (diff)
downloadpardes-179863c0a7b011d5a4bd5e064f7d021d239aa96e.tar.gz
pardes-179863c0a7b011d5a4bd5e064f7d021d239aa96e.zip
Clear selections when navigating Back and Forward
Diffstat (limited to 'src')
-rw-r--r--src/panes.zig4
-rw-r--r--src/pardes.zig104
2 files changed, 47 insertions, 61 deletions
diff --git a/src/panes.zig b/src/panes.zig
index c3fa732e..88e6a8ea 100644
--- a/src/panes.zig
+++ b/src/panes.zig
@@ -3604,7 +3604,7 @@ pub const Pdf = struct {
return result;
}
- test "PDF selections retain their owning page across history navigation" {
+ test "PDF selections retain their owning page across page focus" {
if (comptime !enabled) return;
const gpa = std.testing.allocator;
var state = try State.open(gpa, "docs/design.pdf", 1);
@@ -5372,7 +5372,7 @@ pub const Pdf = struct {
const destination = owner.state.sectionDestination(core.pdf_gpa, at.col - 1) orelse return true;
switch (destination) {
.internal => |internal| {
- core.clearLookSelection(owner.pane);
+ core.clearNavigationSelection(owner.pane);
revealOutlineDestination(core, owner.pane, internal);
},
.external => |uri| if (uri.len <= 256) core.emit(.{ .open_link = .from(uri) }),
diff --git a/src/pardes.zig b/src/pardes.zig
index 2a8ae5cc..16343972 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -5961,7 +5961,6 @@ pub const Pardes = struct {
jumps: [MAX_JUMPS]Loc = undefined,
njumps: usize = 0,
jcur: usize = 0,
- jump_selection_cursor: ?struct { pane: usize, serial: u32, row: i32, col: i32 } = null,
look_src: [MAX_PANES]u32 = undefined,
n_look_src: usize = 0,
look_walk_owner: ?u32 = null,
@@ -12220,13 +12219,13 @@ pub const Pardes = struct {
fn focusPaneByPath(p: *Pardes, path: []const u8, at: look.Spot) bool {
const target = p.openPaneTarget(path, at) orelse return false;
- p.clearLookSelection(p.panes[target.pane.id].?);
+ p.clearNavigationSelection(p.panes[target.pane.id].?);
p.focusPaneLine(target.pane.id, target.pane.at, .center);
return true;
}
- /// Look starts a new selection; history navigation keeps the old one.
- pub fn clearLookSelection(p: *Pardes, pane: *Pane) void {
+ /// Navigation starts a new selection at the destination.
+ pub fn clearNavigationSelection(p: *Pardes, pane: *Pane) void {
pane.vsel = .{};
pane.msel = .{};
pane.nsel = 0;
@@ -12241,9 +12240,6 @@ pub const Pardes = struct {
state.clearSelection(p.pdf_gpa);
};
if (p.drag == .select and p.panes[p.drag.select.id] == pane) p.drag = .none;
- if (p.jump_selection_cursor) |saved| if (saved.serial == pane.serial) {
- p.jump_selection_cursor = null;
- };
}
/// Resolve without changing focus or falling back to search. PDF links use
@@ -12333,7 +12329,7 @@ pub const Pardes = struct {
const pane = p.panes[id] orelse return;
const link = probe.link orelse return;
const target = p.pdfLinkLocation(pane, link) orelse return;
- p.clearLookSelection(pane);
+ p.clearNavigationSelection(pane);
if (p.canonicalLookLocation(id, probe.text)) |visible| {
if (!std.mem.eql(u8, visible, target)) {
const content = std.fmt.allocPrint(p.gpa, "{s}\n{s}\n", .{ visible, target }) catch |err|
@@ -12367,7 +12363,7 @@ pub const Pardes = struct {
// The operand may borrow selected terminal/PDF text released below.
const txt = p.scratch.allocator().dupe(u8, operand) catch return;
const trimmed = std.mem.trim(u8, txt, " \t\r\n");
- p.clearLookSelection(pane);
+ p.clearNavigationSelection(pane);
const pl = look.parsePathLine(trimmed);
if (comptime pdf_enabled) if (panes.Pdf.lookSection(p, id, pl.path, pl.at)) return;
var realbuf: [4096]u8 = undefined;
@@ -12390,14 +12386,14 @@ pub const Pardes = struct {
.pane => |t| {
if (t.id >= MAX_PANES) return;
const target = p.panes[t.id] orelse return;
- p.clearLookSelection(target);
+ p.clearNavigationSelection(target);
p.focusPaneLine(t.id, t.at, .center);
},
.url => |u| if (u.len <= 256) p.emit(.{ .open_link = .from(u) }),
.dir => |dir| {
for (p.panes, 0..) |slot, i| {
if (slot) |tt| if (std.mem.eql(u8, tt.cwdSlice(), dir) and p.takesCommandLine(i)) {
- p.clearLookSelection(tt);
+ p.clearNavigationSelection(tt);
p.active = i;
p.emitWrite(i, "ls\r");
return;
@@ -12973,14 +12969,6 @@ pub const Pardes = struct {
p.jcur = @min(cur, w -| 1);
const pane = p.panes[p.active] orelse return;
- // After a history arrival, an unchanged selection head still belongs
- // to the selection, not to the location we just showed.
- if (p.jump_selection_cursor) |saved| {
- if (saved.pane == p.active and saved.serial == pane.serial and
- saved.row == pane.cur_row and saved.col == pane.cur_col and
- (pane.vsel.active or pane.msel.active or pane.nsel > 0)) return;
- p.jump_selection_cursor = null;
- }
const now: Loc = .{
.pane = @intCast(p.active),
.serial = pane.serial,
@@ -13012,25 +13000,14 @@ pub const Pardes = struct {
pub fn jumpBy(p: *Pardes, delta: i32) void {
const next = @as(i64, @intCast(p.jcur)) + delta;
if (p.njumps == 0 or next < 0 or next >= p.njumps) return;
- p.jcur = @intCast(next);
- const j = p.jumps[p.jcur];
+ const j = p.jumps[@intCast(next)];
+ if (j.pane >= MAX_PANES) return;
const pane = p.panes[j.pane] orelse return;
if (pane.serial != j.serial) return;
- // A modal selection stores its head in the cursor. History changes
- // the viewed location, but must not stretch that selection to it.
- for (0..pane.sel.len) |slot| p.capturePointerSelection(pane, slot) catch return;
- const preserve_cursor = pane.vsel.active or pane.msel.active or pane.nsel > 0;
- const row = pane.cur_row;
- const col = pane.cur_col;
- const pinned = pane.cur_pinned;
+ if (p.panes[p.active]) |source| p.clearNavigationSelection(source);
+ if (p.active != j.pane) p.clearNavigationSelection(pane);
+ p.jcur = @intCast(next);
p.focusPaneLine(j.pane, .{ .line = j.line, .col = j.col }, .center);
- p.jump_selection_cursor = null;
- if (preserve_cursor) {
- p.jump_selection_cursor = .{ .pane = j.pane, .serial = pane.serial, .row = row, .col = col };
- pane.cur_row = row;
- pane.cur_col = col;
- pane.cur_pinned = pinned;
- }
}
/// Recompute geometry, push grid-size changes to each emulator + pty, fire
@@ -15000,32 +14977,43 @@ test "Look ignores missing and out of bounds pane addresses" {
}
}
-test "jump history preserves modal and mouse selections" {
+test "jump history clears selections and lands at the recorded cursor" {
const p = try Pardes.init(std.testing.allocator, .{ .cols = 60, .rows = 12 });
defer p.deinit();
const pane = try p.setTestFile("alpha beta\ngamma delta\nepsilon zeta\n");
- pane.setRange(pane.file.?.content, 0, .{ .anchor = 1, .head = 5 }, true);
- pane.sel[0] = .{ .state = .done, .c0 = 2, .c1 = 5, .r0 = 1, .r1 = 1 };
- const selection = pane.primaryRange(pane.file.?.content, 0);
- try p.capturePointerSelection(pane, 0);
- const mouse = pane.sel;
p.njumps = 2;
p.jcur = 1;
p.jumps[0] = .{ .pane = 0, .serial = pane.serial, .line = 3, .col = 8 };
p.jumps[1] = .{ .pane = 0, .serial = pane.serial, .line = 1, .col = 1 };
- p.update(.{ .key = .{ .cp = 'o', .ctrl = true } });
- try std.testing.expectEqual(@as(usize, 0), p.jcur);
- try std.testing.expectEqual(@as(u32, 3), p.jumps[0].line);
- try std.testing.expectEqualDeep(selection, pane.primaryRange(pane.file.?.content, 0));
- try std.testing.expectEqualDeep(mouse, pane.sel);
- p.update(.{ .key = .{ .cp = 'i', .ctrl = true } });
- try std.testing.expectEqual(@as(usize, 1), p.jcur);
- try std.testing.expectEqualDeep(selection, pane.primaryRange(pane.file.?.content, 0));
- try std.testing.expectEqualDeep(mouse, pane.sel);
- pane.vsel.active = false;
- p.jumpBy(-1);
- try std.testing.expectEqual(@as(i32, 2), pane.cur_row);
- try std.testing.expectEqual(@as(i32, 7), pane.cur_col);
+ for ([_]u21{ 'o', 'i' }, 0..) |key, index| {
+ pane.setRange(pane.file.?.content, 0, .{ .anchor = 1, .head = 5 }, true);
+ pane.sel[0] = .{ .state = .done, .c0 = 2, .c1 = 5, .r0 = 1, .r1 = 1 };
+ try p.capturePointerSelection(pane, 0);
+ pane.select = true;
+ p.update(.{ .key = .{ .cp = key, .ctrl = true } });
+ try std.testing.expectEqual(index, p.jcur);
+ try std.testing.expectEqual(@as(usize, 2), p.njumps);
+ try std.testing.expectEqual(@as(u32, 3), p.jumps[0].line);
+ try std.testing.expect(!pane.vsel.active and !pane.msel.active and !pane.select);
+ try std.testing.expectEqual(@as(u8, 0), pane.nsel);
+ try std.testing.expectEqual(.none, pane.sel[0].state);
+ try std.testing.expect(pane.pointer_selections[0] == null);
+ try std.testing.expectEqual(@as(i32, if (index == 0) 2 else 0), pane.cur_row);
+ try std.testing.expectEqual(@as(i32, if (index == 0) 7 else 0), pane.cur_col);
+ }
+ pane.setRange(pane.file.?.content, 0, .{ .anchor = 1, .head = 5 }, true);
+ const selected = pane.primaryRange(pane.file.?.content, 0);
+ p.jumpBy(1); // No forward entry: selection stays untouched.
+ try std.testing.expectEqualDeep(selected, pane.primaryRange(pane.file.?.content, 0));
+ for ([_]Loc{
+ .{ .pane = 15, .serial = pane.serial, .line = 1, .col = 1 },
+ .{ .pane = 0, .serial = pane.serial + 1, .line = 1, .col = 1 },
+ }) |missing| {
+ p.jumps[0] = missing;
+ p.jumpBy(-1);
+ try std.testing.expectEqual(@as(usize, 1), p.jcur);
+ try std.testing.expectEqualDeep(selected, pane.primaryRange(pane.file.?.content, 0));
+ }
}
test "jump history terminal rectangle follows output and clears on reflow" {
@@ -15044,17 +15032,15 @@ test "jump history terminal rectangle follows output and clears on reflow" {
try std.testing.expectEqual(.none, pane.sel[0].state);
}
-test "mouse thumb buttons navigate once without disturbing selection gestures" {
+test "mouse thumb buttons navigate once and clear selection gestures" {
const p = try Pardes.init(std.testing.allocator, .{ .cols = 60, .rows = 12 });
defer p.deinit();
const pane = try p.setTestFile("alpha beta\ngamma delta\nepsilon zeta\n");
pane.setRange(pane.file.?.content, 0, .{ .anchor = 1, .head = 5 }, true);
- const selection = pane.primaryRange(pane.file.?.content, 0);
p.njumps = 3;
p.jcur = 2;
for (0..3) |i| p.jumps[i] = .{ .pane = 0, .serial = pane.serial, .line = @intCast(i + 1), .col = 1 };
p.drag = .{ .select = .{ .id = 0, .button = config.select_button } };
- const drag = p.drag;
p.update(.{ .mouse = .{ .button = .back, .kind = .press, .col = 0, .row = 0 } });
try std.testing.expectEqual(@as(usize, 1), p.jcur);
for ([_]Mouse.Kind{ .drag, .release, .motion }) |kind|
@@ -15063,6 +15049,6 @@ test "mouse thumb buttons navigate once without disturbing selection gestures" {
p.update(.{ .mouse = .{ .button = .forward, .kind = .press, .col = 0, .row = 0 } });
p.update(.{ .mouse = .{ .button = .forward, .kind = .release, .col = 0, .row = 0 } });
try std.testing.expectEqual(@as(usize, 2), p.jcur);
- try std.testing.expectEqualDeep(selection, pane.primaryRange(pane.file.?.content, 0));
- try std.testing.expectEqualDeep(drag, p.drag);
+ try std.testing.expect(!pane.vsel.active);
+ try std.testing.expectEqual(.none, p.drag);
}