diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-15 20:17:50 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 179863c0a7b011d5a4bd5e064f7d021d239aa96e (patch) | |
| tree | 02f56d75f3627de575b912d22418e5b4e0eb7d0f /src | |
| parent | ffa2fd2d4b40bc4ff2a05263cf82ee9638590edd (diff) | |
| download | pardes-179863c0a7b011d5a4bd5e064f7d021d239aa96e.tar.gz pardes-179863c0a7b011d5a4bd5e064f7d021d239aa96e.zip | |
Clear selections when navigating Back and Forward
Diffstat (limited to 'src')
| -rw-r--r-- | src/panes.zig | 4 | ||||
| -rw-r--r-- | src/pardes.zig | 104 |
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); } |
