summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-15 18:59:33 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commitb7914a0bd59334f618b62e0b98271211bbec46b9 (patch)
tree53af62ce0ae022e580e35aadbd952e1656dcfa30 /src/pardes.zig
parent3f4f26e0e40d06427e72f72e760d01adcdc0326c (diff)
downloadpardes-b7914a0bd59334f618b62e0b98271211bbec46b9.tar.gz
pardes-b7914a0bd59334f618b62e0b98271211bbec46b9.zip
Clear previous selections before resolving Look destinations
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig62
1 files changed, 58 insertions, 4 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index c78d4702..8bccfd89 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -9446,6 +9446,9 @@ pub const Pardes = struct {
p.runBuiltin(config.exec_cmd, rid, "", std.mem.trim(u8, ln, " \t\r"));
} else if (span) |selected| {
p.lookAt(rid, ln[selected.start..selected.end]);
+ // This is the newly visited result, not a selection carried into
+ // the destination. Look cleared the previous source selection.
+ rp.msel = .{ .active = true, .r0 = r, .r1 = r };
}
p.armLookWalk(rid);
return true;
@@ -12217,10 +12220,32 @@ 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.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 {
+ pane.vsel = .{};
+ pane.msel = .{};
+ pane.nsel = 0;
+ pane.select = false;
+ pane.normal.clear();
+ pane.append_at = null;
+ pane.sticky_col = -1;
+ for (0..pane.sel.len) |slot| pane.clearPointerSelection(slot);
+ pane.sel = @splat(.{});
+ if (comptime pdf_enabled) if (pane.pdf) |*state| {
+ state.clearDrag();
+ 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
/// the same location rules as an ordinary Look before offering a choice.
fn resolveLookTarget(p: *Pardes, id: usize, txt: []const u8, realbuf: *[4096]u8) look.Target {
@@ -12308,6 +12333,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);
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|
@@ -12329,15 +12355,19 @@ pub const Pardes = struct {
}
}
- pub fn lookAt(p: *Pardes, id: usize, txt: []const u8) void {
+ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void {
const pane = p.panes[id] orelse return;
p.noteHaptic(.look);
p.noteLookSource(id);
- const trimmed = std.mem.trim(u8, txt, " \t\r\n");
- if (config.commandWord(trimmed)) |cmd| {
+ const command = std.mem.trim(u8, operand, " \t\r\n");
+ if (config.commandWord(command)) |cmd| {
if (p.execute(id, cmd)) |dst| p.active = dst;
return;
}
+ // 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);
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;
@@ -12357,11 +12387,15 @@ pub const Pardes = struct {
_ = p.searchStep(id, 1);
}
},
- .pane => |t| p.focusPaneLine(t.id, t.at, .center),
+ .pane => |t| {
+ p.clearLookSelection(p.panes[t.id].?);
+ 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.active = i;
p.emitWrite(i, "ls\r");
return;
@@ -14931,6 +14965,26 @@ test "board heap: every allocation failure during boot is a clean OutOfMemory" {
);
}
+test "Look copies its operand before releasing pointer selection storage" {
+ 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");
+ const operand = try p.gpa.dupe(u8, "/test.txt:3:2");
+ pane.sel[0] = .{ .state = .done, .source_id = 1 };
+ pane.pointer_selections[0] = .{
+ .gesture = pane.sel[0],
+ .rows = try p.gpa.alloc(Pane.PointerRow, 0),
+ .raw_text = operand,
+ };
+ p.drag = .{ .select = .{ .id = 0, .button = config.select_button } };
+ p.lookAt(0, operand);
+ try std.testing.expectEqual(@as(i32, 2), pane.cur_row);
+ try std.testing.expectEqual(@as(i32, 1), pane.cur_col);
+ try std.testing.expect(pane.pointer_selections[0] == null);
+ try std.testing.expectEqual(.none, pane.sel[0].state);
+ try std.testing.expectEqual(.none, p.drag);
+}
+
test "jump history preserves modal and mouse selections" {
const p = try Pardes.init(std.testing.allocator, .{ .cols = 60, .rows = 12 });
defer p.deinit();