From 988b288b870dd0165f09b991839c2c37fcd3fd62 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 21:38:57 -0300 Subject: A diff line looks up the path and line it names, as any look does The look on a diff line was its own resolver: a walk up to .git/.jj from the diff's directory, its own "no such file here" and "deleted by this diff". A review tool's cached patch, in no repository, missed every file though the session's terminals sat in the checkout. Now a diff line only expands and transforms. Its expansion (what the hover shows) is the whole line for a header, a `@@` line, or a hunk line pointed at on its prefix, and its text is the look address the line names (look.diffAddress): `path` for `diff --git`/`---`/`+++`, `path:c` for `@@ -a,b +c,d @@`, `path:N` for a hunk line's new line (a removed line's, the new line now where it was). That text goes through the regular look exactly as if selected by hand: same resolution, placement and errors. A 9P look of a whole diff line takes the same transform. Kept: the line classification, hunk arithmetic, git prefix stripping and timestamp cutting (diff.zig). Gone: the repo-root walk, its messages, Target.old_side and deletesAhead, and the click's row stash (Pardes.diff_look). A test compares a B3 on a cached patch's line with a look of the hand-selected `entry/src/entry.rs:2` in the same session. Co-Authored-By: Claude Opus 5.5 --- src/CHANGELOG.md | 8 ++ src/diff.zig | 32 +++---- src/exec.zig | 12 ++- src/look.zig | 275 +++++++++++++++++++++---------------------------------- src/pardes.zig | 4 - 5 files changed, 131 insertions(+), 200 deletions(-) (limited to 'src') diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index ae796a4d..a1caffb4 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +- A look on a diff line is the look of the `path:line` it names, exactly as + if that text were selected by hand: the same resolution, placement and + errors. A patch cached outside any checkout opens the file the way a + hand-selected path would, and the diff's own path finding and its + messages are gone. + ## 0.0.12 - A selection deep in a very long line no longer slows every frame: a 5 MB diff --git a/src/diff.zig b/src/diff.zig index e2b4afd4..362695d5 100644 --- a/src/diff.zig +++ b/src/diff.zig @@ -331,22 +331,14 @@ pub const Target = struct { path: []const u8, /// From 1; 0 for the file alone (a `---`/`+++` line). line: usize, - /// The section deletes the file, and this is its old side. - old_side: bool = false, -}; -/// Whether the section `row` is in deletes its file by a line still ahead -/// of it (`deleted file mode`, `+++ /dev/null`): its `diff --git` and `---` -/// lines come first, and are about a file this diff deletes all the same. -fn deletesAhead(lines: []const []const u8, row: usize) bool { - for (lines[row + 1 ..]) |raw| { - const l = std.mem.trimEnd(u8, raw, "\r"); - if (std.mem.startsWith(u8, l, "diff ") or std.mem.startsWith(u8, l, "@@")) return false; - if (std.mem.startsWith(u8, l, "deleted file mode") or std.mem.eql(u8, l, "+++ /dev/null") or - (std.mem.startsWith(u8, l, "+++ /dev/null") and l.len > 13 and (l[13] == '\t' or l[13] == ' '))) return true; + /// The look address for it, as one would select it by hand: `path` + /// or `path:line`. + pub fn address(t: Target, buf: []u8) ?[]const u8 { + if (t.line == 0) return std.fmt.bufPrint(buf, "{s}", .{t.path}) catch null; + return std.fmt.bufPrint(buf, "{s}:{d}", .{ t.path, t.line }) catch null; } - return false; -} +}; /// What a look at `lines[row]` opens, or null for a line that is no part /// of a file section (a commit message, `index ...`). The whole line is @@ -374,7 +366,7 @@ pub fn targetAt(lines: []const []const u8, row: usize, continued_rows: bool) ?Ta .other, .meta, .no_newline => { // `diff --git a/x b/y`: the file, as its `+++` would. if (got.kind == .meta and std.mem.startsWith(u8, line, "diff ") and w.git_path.len > 0) - return .{ .kind = got.kind, .path = w.git_path, .line = 0, .old_side = deletesAhead(lines, row) }; + return .{ .kind = got.kind, .path = w.git_path, .line = 0 }; return null; }, .old_path, .new_path => { @@ -388,18 +380,18 @@ pub fn targetAt(lines: []const []const u8, row: usize, continued_rows: bool) ?Ta if (!isDevNull(new) and (isDevNull(own) or !std.mem.eql(u8, new, own))) return .{ .kind = got.kind, .path = new, .line = 0 }; } - if (!isDevNull(own)) return .{ .kind = got.kind, .path = own, .line = 0, .old_side = got.kind == .old_path and (w.deleted() or deletesAhead(lines, row)) }; + if (!isDevNull(own)) return .{ .kind = got.kind, .path = own, .line = 0 }; if (w.path().len == 0) return null; - return .{ .kind = got.kind, .path = w.path(), .line = 0, .old_side = w.deleted() }; + return .{ .kind = got.kind, .path = w.path(), .line = 0 }; }, .hunk => { if (w.path().len == 0) return null; - if (w.deleted()) return .{ .kind = got.kind, .path = w.path(), .line = @max(1, got.old), .old_side = true }; + if (w.deleted()) return .{ .kind = got.kind, .path = w.path(), .line = @max(1, got.old) }; return .{ .kind = got.kind, .path = w.path(), .line = @max(1, got.new) }; }, .context, .added, .removed => { if (w.path().len == 0) return null; - if (w.deleted()) return .{ .kind = got.kind, .path = w.path(), .line = @max(1, got.old), .old_side = true }; + if (w.deleted()) return .{ .kind = got.kind, .path = w.path(), .line = @max(1, got.old) }; var at = got.new; // Gone from the end of the file: nothing stands after it. if (got.kind == .removed and w.new_count > 0 and at >= w.new_start + w.new_count) @@ -454,7 +446,6 @@ test "diff every line of a section that deletes its file is about a file this di if (row < 15) continue; // the deleting section: `diff --git a/old.py` on const t = targetAt(&lines, row, false) orelse continue; try testing.expectEqualStrings("old.py", t.path); - try testing.expect(t.old_side); _ = line; rows += 1; } @@ -505,7 +496,6 @@ test "diff a look on each kind of line names the new file and its line" { const gone = targetAt(&lines, 21, false).?; try testing.expectEqualStrings("old.py", gone.path); try testing.expectEqual(@as(usize, 2), gone.line); - try testing.expect(gone.old_side); try testing.expect(targetAt(&lines, 1, false) == null); } diff --git a/src/exec.zig b/src/exec.zig index c673aab2..ea20f969 100644 --- a/src/exec.zig +++ b/src/exec.zig @@ -170,8 +170,9 @@ pub fn pointerOperand(p: *Pardes, pane: *Pane, clicked: Pane.Sel) PointerOperand return result; } // In a diff, a file or hunk header, and a hunk line pointed at on its - // `+`/`-`/` ` prefix, are looked at whole: the place the line names - // (look.diffLook). A hunk line's code is words, looked at as ever: + // `+`/`-`/` ` prefix, expand to the whole line (the hover shows it) and + // the look is of the address it names (look.diffAddress), a regular + // look from there. A hunk line's code is words, looked at as ever: // the code's, the prefix never one of them. var word_from: usize = 0; if (clicked.r0 >= pane.tag_rows and clicked.r0 == clicked.r1 and clicked.c0 == clicked.c1 and row >= 0) diff: { @@ -179,7 +180,9 @@ pub fn pointerOperand(p: *Pardes, pane: *Pane, clicked: Pane.Sel) PointerOperand const at: usize = @intCast(row); if (at >= lines.len) break :diff; const line = lines[at]; - const target = @import("diff.zig").targetAt(lines, at, pane.file == null) orelse break :diff; + var address_buf: [4096 + 32]u8 = undefined; + const address = look.diffAddress(pane, lines, at, &address_buf) orelse break :diff; + const target = @import("diff.zig").targetAt(lines, at, pane.file == null).?; const on_prefix = col == 0 and line.len > 0 and std.mem.indexOfScalar(u8, "+- ", line[0]) != null; switch (target.kind) { .context, .added, .removed => if (!on_prefix) { @@ -188,8 +191,7 @@ pub fn pointerOperand(p: *Pardes, pane: *Pane, clicked: Pane.Sel) PointerOperand }, else => {}, } - result.text = line; - p.diff_look = .{ .serial = pane.serial, .row = at }; + result.text = p.scratch.allocator().dupe(u8, address) catch break :diff; const hi = std.math.cast(i32, line.len) orelse return result; if (hi == 0) return result; var expanded = clicked; diff --git a/src/look.zig b/src/look.zig index 36b91545..3cd1faa5 100644 --- a/src/look.zig +++ b/src/look.zig @@ -1539,82 +1539,26 @@ pub fn diffLines(p: *Pardes, pane: *Pane) ?[]const []const u8 { return p.diff_output.lines(p, pane); } -const DiffLook = union(enum) { none, done, at: []const u8 }; - -/// A look in a diff pane at one of its lines opens the file and line that -/// line names (diff.targetAt): the new file at a `@@` line's first new -/// line, at a context or added line's own line, and at a removed line's -/// place, the new line now standing where it was. `---`/`+++` open their -/// file. The path is the repository's name (diff.Walk.side: git's side -/// prefixes dropped by git's rules, a plain diff's names as written), -/// found from the repository root (walking up from the diff's directory to -/// a `.git` or `.jj`), else the directory. A file that is not there opens -/// nothing and says so, by that name. -/// -/// Which line: the one a click just gave (`clicked`: exec.pointerOperand -/// expanded to it and kept its row), else, for a look written over 9P, a -/// line the text is the whole of, the cursor's first, then the first after -/// it. A word on a hunk line is looked at as ever. -fn diffLook(p: *Pardes, id: usize, pane: *Pane, operand: []const u8, clicked: bool, buf: []u8) DiffLook { +/// The look address a diff line names (diff.targetAt): `path` for a +/// `diff --git`, `---` or `+++` line, `path:N` for a `@@` line (its first +/// new line) and a hunk line (its new line; a removed line's, the new line +/// now standing where it was). The look of it is a regular one, as of that +/// text selected by hand. Null for a line no part of a file section. +pub fn diffAddress(pane: *const Pane, lines: []const []const u8, row: usize, buf: []u8) ?[]const u8 { + const target = diff.targetAt(lines, row, pane.file == null) orelse return null; + return target.address(buf); +} + +/// A look written over 9P of a whole line of a diff pane (the cursor's line +/// first, then the first after it): its address, as a click there makes. +fn diffLineAddress(p: *Pardes, pane: *Pane, operand: []const u8, buf: []u8) ?[]const u8 { const text = std.mem.trimEnd(u8, operand, "\r\n"); - if (std.mem.trim(u8, text, " \t").len == 0) return .none; - const lines = diffLines(p, pane) orelse return .none; - const row: usize = row: { - if (p.diff_look) |at| if (at.serial == pane.serial and at.row < lines.len and - std.mem.eql(u8, std.mem.trimEnd(u8, lines[at.row], "\r"), text)) - { - p.diff_look = null; - break :row at.row; - }; - if (clicked) return .none; - const cur: usize = @intCast(@max(0, pane.body.cur_row)); - for (0..lines.len) |k| { - const i = (cur + k) % lines.len; - if (std.mem.eql(u8, std.mem.trimEnd(u8, lines[i], "\r"), text)) break :row i; - } - return .none; - }; - const target = diff.targetAt(lines, row, pane.file == null) orelse return .none; - const base = if (pane.file) |f| std.fs.path.dirname(f.path) orelse "/" else pane.cwdSlice(); - var realbuf: [4096]u8 = undefined; - const found = diffFile(base, target.path, &realbuf) orelse { - var cut: [miss_path_cap + "…".len]u8 = undefined; - if (target.old_side) - missed(p, id, "{s}: deleted by this diff", .{missText(target.path, &cut)}) - else - missed(p, id, "{s}: no such file here", .{missText(target.path, &cut)}); - return .done; - }; - const place = if (target.line > 0) - std.fmt.bufPrint(buf, "{s}:{d}", .{ found, target.line }) catch return .none - else - std.fmt.bufPrint(buf, "{s}", .{found}) catch return .none; - return .{ .at = place }; -} - -/// The file a diff names, from `base` (the diff's directory): tried in the -/// repository `base` is in, then in `base`. -fn diffFile(base: []const u8, path: []const u8, out: *[4096]u8) ?[]const u8 { - if (comptime !platform_has_fs) return null; - if (path.len == 0) return null; - if (path[0] == '/') { - const found = fs.resolveOs(path, out) orelse return null; - return if (found.dir) null else found.path; - } - var root_buf: [4096]u8 = undefined; - var dir: ?[]const u8 = base; - const root: ?[]const u8 = while (dir) |d| : (dir = std.fs.path.dirname(d)) { - var probe: [4096]u8 = undefined; - const git = std.fmt.bufPrint(&probe, "{s}/.git", .{d}) catch break null; - if (fs.resolveOs(git, &root_buf) != null) break d; - const jj = std.fmt.bufPrint(&probe, "{s}/.jj", .{d}) catch break null; - if (fs.resolveOs(jj, &root_buf) != null) break d; - } else null; - for ([_]?[]const u8{ root, base }) |maybe| { - const at = maybe orelse continue; - var joined: [4096]u8 = undefined; - const full = std.fmt.bufPrint(&joined, "{s}/{s}", .{ at, path }) catch continue; - if (fs.resolveOs(full, out)) |found| if (!found.dir) return found.path; + if (std.mem.trim(u8, text, " \t").len == 0) return null; + const lines = diffLines(p, pane) orelse return null; + const cur: usize = @intCast(@max(0, pane.body.cur_row)); + for (0..lines.len) |k| { + const i = (cur + k) % lines.len; + if (std.mem.eql(u8, std.mem.trimEnd(u8, lines[i], "\r"), text)) return diffAddress(pane, lines, i, buf); } return null; } @@ -1639,24 +1583,15 @@ pub fn lookAt(p: *Pardes, id: usize, operand_raw: []const u8) void { p.noteHaptic(.look); noteLookSource(p, id); const command = std.mem.trim(u8, operand, " \t\r\n"); - // A click on a diff line (a context line ` Del` included) is a look at - // the place it names, never the word run. + // A whole line of a diff written over 9P (a context line ` Del` + // included) is a look at the address it names, never the word run. var diff_buf: [4096 + 32]u8 = undefined; - const looked: []const u8 = looked: { - switch (diffLook(p, id, pane, operand_raw, true, &diff_buf)) { - .none => {}, - .done => return, - .at => |place| break :looked place, - } + const looked: []const u8 = diffLineAddress(p, pane, operand_raw, &diff_buf) orelse looked: { if (config.commandWord(command)) |cmd| { if (exec.execute(p, id, cmd)) |dst| p.active = dst; return; } - switch (diffLook(p, id, pane, operand_raw, false, &diff_buf)) { - .none => break :looked operand, - .done => return, - .at => |place| break :looked place, - } + break :looked operand; }; // The operand may borrow selected terminal/PDF text released below. const txt = p.scratch.allocator().dupe(u8, looked) catch return; @@ -2084,13 +2019,14 @@ const DiffFixture = struct { return pane; } - /// A right click's look at `row` of the diff pane (slot 0). + /// A right click's look at `row` of the diff pane (slot 0): the look + /// of the address that line names. fn click(p: *Pardes, diff_pane: *Pane, row: usize) void { p.active = 0; const lines = diffLines(p, diff_pane).?; - p.diff_look = .{ .serial = diff_pane.serial, .row = row }; + var buf: [4096]u8 = undefined; p.look_missed = false; - lookAt(p, 0, lines[row]); + lookAt(p, 0, diffAddress(diff_pane, lines, row, &buf).?); } }; @@ -2149,11 +2085,9 @@ test "Look on each kind of diff line opens the new file at the line it names" { try std.testing.expect(std.mem.endsWith(u8, p.panes[p.active].?.file.?.path, "/src/a.zig")); try std.testing.expect(!p.look_missed); - // No click: a 9P look writes a line, found after the cursor, or the - // text is on the cursor's line. + // No click: a 9P look writes a whole line, found from the cursor on. p.active = 0; pane.body.cur_row = 0; - p.diff_look = null; lookAt(p, 0, "+line 12"); try expectAt(p, "/src/a.zig", 12); // A word on a hunk line is a word, looked for in the diff as ever. @@ -2168,43 +2102,6 @@ test "Look on each kind of diff line opens the new file at the line it names" { try std.testing.expectEqual(@as(usize, 0), p.active); } -test "Look on a diff line of a file that is not here says so and opens nothing" { - if (!platform_has_fs) return; - const diff_text = - \\diff --git a/src/elsewhere.zig b/src/elsewhere.zig - \\--- a/src/elsewhere.zig - \\+++ b/src/elsewhere.zig - \\@@ -1 +1 @@ - \\-const a = 1; - \\+const a = 2; - \\diff --git a/old.zig b/old.zig - \\deleted file mode 100644 - \\--- a/old.zig - \\+++ /dev/null - \\@@ -1 +0,0 @@ - \\-const gone = 1; - \\ - ; - var fx: DiffFixture = undefined; - try fx.init(true, &.{}, diff_text); - defer fx.tmp.cleanup(); - const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 30 }); - defer p.deinit(); - const pane = try fx.open(p, diff_text); - var before: usize = 0; - for (p.panes) |slot| before += @intFromBool(slot != null); - DiffFixture.click(p, pane, 5); - try std.testing.expect(p.look_missed); - try std.testing.expectEqual(@as(usize, 0), p.active); - var after: usize = 0; - for (p.panes) |slot| after += @intFromBool(slot != null); - try std.testing.expectEqual(before, after); - try std.testing.expectEqualStrings("Look: src/elsewhere.zig: no such file here", pane.msg[0..pane.msg_len]); - DiffFixture.click(p, pane, 11); - try std.testing.expect(p.look_missed); - try std.testing.expectEqualStrings("Look: old.zig: deleted by this diff", pane.msg[0..pane.msg_len]); -} - test "Look on plain diff -u output: names as written, timestamps cut, next to the diff" { if (!platform_has_fs) return; const diff_text = "--- old/x.zig\t2026-09-30 10:00:00.000000000 +0000\n" ++ @@ -2250,22 +2147,22 @@ test "Look on a diff: headers and a hunk line's prefix are the whole line, hover pane.tag_expanded = false; // `word`: what the pointer there expands to when it is not the whole // line; `line` 0: that word is looked at in the diff, nothing opened. - const Point = struct { row: u16, col: u16, line: usize, word: ?[]const u8 = null }; + const Point = struct { row: u16, col: u16, line: usize, word: ?[]const u8 = null, addr: []const u8 = "" }; const points = [_]Point{ - .{ .row = 0, .col = 0, .line = 1 }, // `diff`: the new side - .{ .row = 0, .col = 35, .line = 1 }, // on b/src/new.zig - .{ .row = 4, .col = 1, .line = 1 }, // `---`: renamed, so the new name - .{ .row = 4, .col = 5, .line = 1 }, // the `a/` of the old name - .{ .row = 5, .col = 0, .line = 1 }, // `+++` - .{ .row = 5, .col = 4, .line = 1 }, // its `b/` - .{ .row = 5, .col = 10, .line = 1 }, // its path - .{ .row = 6, .col = 0, .line = 2 }, // the first `@@` - .{ .row = 6, .col = 9, .line = 2 }, // `+2,2` - .{ .row = 6, .col = 13, .line = 2 }, // the second `@@` - .{ .row = 6, .col = 20, .line = 2 }, // the function after it - .{ .row = 7, .col = 0, .line = 2 }, // a context line's ` ` - .{ .row = 8, .col = 0, .line = 3 }, // a removed line's `-`: where it was - .{ .row = 9, .col = 0, .line = 3 }, // an added line's `+` + .{ .row = 0, .col = 0, .line = 1, .addr = "src/new.zig" }, // `diff`: the new side + .{ .row = 0, .col = 35, .line = 1, .addr = "src/new.zig" }, // on b/src/new.zig + .{ .row = 4, .col = 1, .line = 1, .addr = "src/new.zig" }, // `---`: renamed, so the new name + .{ .row = 4, .col = 5, .line = 1, .addr = "src/new.zig" }, // the `a/` of the old name + .{ .row = 5, .col = 0, .line = 1, .addr = "src/new.zig" }, // `+++` + .{ .row = 5, .col = 4, .line = 1, .addr = "src/new.zig" }, // its `b/` + .{ .row = 5, .col = 10, .line = 1, .addr = "src/new.zig" }, // its path + .{ .row = 6, .col = 0, .line = 2, .addr = "src/new.zig:2" }, // the first `@@` + .{ .row = 6, .col = 9, .line = 2, .addr = "src/new.zig:2" }, // `+2,2` + .{ .row = 6, .col = 13, .line = 2, .addr = "src/new.zig:2" }, // the second `@@` + .{ .row = 6, .col = 20, .line = 2, .addr = "src/new.zig:2" }, // the function after it + .{ .row = 7, .col = 0, .line = 2, .addr = "src/new.zig:2" }, // a context line's ` ` + .{ .row = 8, .col = 0, .line = 3, .addr = "src/new.zig:3" }, // a removed line's `-`: where it was + .{ .row = 9, .col = 0, .line = 3, .addr = "src/new.zig:3" }, // an added line's `+` .{ .row = 9, .col = 3, .line = 0, .word = "three" }, // its code: a word .{ .row = 7, .col = 2, .line = 0, .word = "two" }, }; @@ -2291,7 +2188,8 @@ test "Look on a diff: headers and a hunk line's prefix are the whole line, hover try std.testing.expectEqualStrings(word, operand.text.?); try std.testing.expectEqualStrings(word, lines[at.row][@intCast(span.lo)..@intCast(span.hi)]); } else { - try std.testing.expectEqualStrings(lines[at.row], operand.text.?); + // The hover shows the line; the look is of the address it names. + try std.testing.expectEqualStrings(at.addr, operand.text.?); try std.testing.expectEqual(@as(i32, 0), span.lo); try std.testing.expectEqual(@as(i32, @intCast(lines[at.row].len)), span.hi); } @@ -2355,28 +2253,65 @@ test "Look on a diff under each git prefix opens the repository's file, and a re } } -test "Look on a diff says a missing or deleted file by its repository name, a plain diff's as written" { + +test "Look on a diff line is the look of the address it names, selected by hand: a cached patch, the checkout elsewhere" { if (!platform_has_fs) return; - const git_text = "diff --git c/src/gone.zig w/src/gone.zig\n--- c/src/gone.zig\n+++ w/src/gone.zig\n@@ -1 +1 @@\n-a\n+b\n" ++ - "diff --git a/lib/old.zig b/lib/old.zig\ndeleted file mode 100644\n--- a/lib/old.zig\n+++ /dev/null\n@@ -1 +0,0 @@\n-a\n"; - const plain_text = "--- a/x.zig\t2026-09-30 10:00:00 +0000\n+++ b/x.zig\t2026-09-30 11:00:00 +0000\n@@ -1 +1 @@\n-a\n+b\n"; - var fx: DiffFixture = undefined; - try fx.init(true, &.{.{ "x.zig", "b\n" }}, git_text); - defer fx.tmp.cleanup(); - const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 30 }); - defer p.deinit(); - var pane = try fx.open(p, git_text); - DiffFixture.click(p, pane, 5); - try std.testing.expect(p.look_missed); - try std.testing.expectEqualStrings("Look: src/gone.zig: no such file here", pane.msg[0..pane.msg_len]); - DiffFixture.click(p, pane, 11); - try std.testing.expectEqualStrings("Look: lib/old.zig: deleted by this diff", pane.msg[0..pane.msg_len]); - DiffFixture.click(p, pane, 6); // `diff --git` of the deleted file - try std.testing.expectEqualStrings("Look: lib/old.zig: deleted by this diff", pane.msg[0..pane.msg_len]); - // A plain diff's `b/x.zig` is a path as written: there is no b/ here, - // and the x.zig at the top is not it. - pane = try fx.open(p, plain_text); - DiffFixture.click(p, pane, 4); - try std.testing.expect(p.look_missed); - try std.testing.expectEqualStrings("Look: b/x.zig: no such file here", pane.msg[0..pane.msg_len]); + const io = std.testing.io; + const patch = "diff --git a/entry/src/entry.rs b/entry/src/entry.rs\n--- a/entry/src/entry.rs\n+++ b/entry/src/entry.rs\n" ++ + "@@ -1,2 +1,2 @@\n-fn old() {}\n+fn new() {}\n fn kept() {}\n"; + // The review tool's cache, with no checkout in it. + var cache: DiffFixture = undefined; + try cache.init(false, &.{}, patch); + defer cache.tmp.cleanup(); + // The checkout, elsewhere, with a file of it open in the session. + var checkout = std.testing.tmpDir(.{}); + defer checkout.cleanup(); + try checkout.dir.createDir(io, ".git", .default_dir); + try checkout.dir.createDirPath(io, "entry/src"); + try checkout.dir.writeFile(io, .{ .sub_path = "entry/src/entry.rs", .data = "fn new() {}\nfn kept() {}\n" }); + try checkout.dir.writeFile(io, .{ .sub_path = "Cargo.toml", .data = "[workspace]\n" }); + var path_buf: [4096]u8 = undefined; + const cargo = path_buf[0..try checkout.dir.realPathFile(io, "Cargo.toml", &path_buf)]; + + const Seen = struct { active_path: []u8, row: i32, missed: bool, msg: []u8, panes: usize }; + var seen: [2]Seen = undefined; + for (0..2) |way| { + const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 30 }); + defer p.deinit(); + const pane = try cache.open(p, patch); + // The checkout's file, opened by a look from the diff pane. + lookAt(p, 0, cargo); + p.update(.tick); + try std.testing.expect(std.mem.endsWith(u8, p.panes[p.active].?.file.?.path, "Cargo.toml")); + p.active = 0; + p.update(.tick); + p.look_missed = false; + if (way == 0) { + DiffFixture.click(p, pane, 6); // ` fn kept() {}`, new line 2 + } else { + lookAt(p, 0, "entry/src/entry.rs:2"); // the same, selected by hand + } + const shown = p.panes[p.active].?; + var n: usize = 0; + for (p.panes) |slot| n += @intFromBool(slot != null); + seen[way] = .{ + .active_path = try std.testing.allocator.dupe(u8, if (shown.file) |f| f.path else ""), + .row = shown.body.cur_row, + .missed = p.look_missed, + .msg = try std.testing.allocator.dupe(u8, pane.msg[0..pane.msg_len]), + .panes = n, + }; + } + defer for (seen) |s| { + std.testing.allocator.free(s.active_path); + std.testing.allocator.free(s.msg); + }; + try std.testing.expectEqualStrings(seen[1].active_path, seen[0].active_path); + try std.testing.expectEqual(seen[1].row, seen[0].row); + try std.testing.expectEqual(seen[1].missed, seen[0].missed); + try std.testing.expectEqualStrings(seen[1].msg, seen[0].msg); + try std.testing.expectEqual(seen[1].panes, seen[0].panes); + // And it works: the checkout's file, at the line. + try std.testing.expect(std.mem.endsWith(u8, seen[0].active_path, "/entry/src/entry.rs")); + try std.testing.expectEqual(@as(i32, 1), seen[0].row); } diff --git a/src/pardes.zig b/src/pardes.zig index fbc19b8a..f479f44d 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -4376,10 +4376,6 @@ pub const Pardes = struct { chord_hold: bool = false, /// The look just run found nothing (look.zig missed). look_missed: bool = false, - /// The diff line a right click just expanded to, by row: a look at its - /// text goes to the place that row names, not the first equal line - /// (look.zig diffLook). - diff_look: ?struct { serial: u32, row: usize } = null, look_hover_wait: ?look.LookHoverWait = null, /// The place a look-hover last found nothing to open: the pointer /// resting there does not ask again every frame (look.noteLookHover). -- cgit v1.3