╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │120 // renders from the snapshot), so gate it out of the wasm build. │ │121 if (!builtin.cpu.arch.isWasm()) { │ │122 for (buf[0..count], oids[0..count], 0..) |*commit, oid, i| { │ │120 - // the selected commit (the first, == start_oid) shows `after` hunks │ │121 - // when the url asks; the rest show one default page. │ │122 - const max_hunks = if (i == 0 and after > 0) after else diff_page; │ │123 - const rendered = renderCommitDiff(io, gpa, aa, &repo, oid, max_hunks) catch │ │124 - RenderedDiff{ .hunks = &.{}, .shown_hunks = 0, .has_more = false }; │ │123 + // the selected commit (the first, == start_oid) shows the window the │ │124 + // url asks for; the rest show their first window. │ │125 + const start = if (i == 0) after else 0; │ │126 + const rendered = renderCommitDiff(io, gpa, aa, &repo, oid, start, diff_page) catch│ │127 + RenderedDiff{ .hunks = &.{}, .has_prev = false, .has_more = false }; │ │128 commit.hunks = rendered.hunks; │ │126 - commit.shown_hunks = rendered.shown_hunks; │ │129 + commit.window_start = start; │ │130 + commit.has_prev = rendered.has_prev; │ │131 commit.has_more = rendered.has_more; │ │132 } │ │133 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────╮ │141 │ │142 const RenderedDiff = struct {│ │143 hunks: []const Hunk, │ │140 - shown_hunks: usize, │ │144 + has_prev: bool, │ │145 has_more: bool, │ │146 }; │ │147 │ ╰──────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │144 -// render a commit's diff against its first parent into `arena`-owned hunks,│ │145 -// emitting at most `max_hunks` hunks (has_more is set when more remain) │ │148 +// render the window [start, start+len) of a commit's diff against its first│ │149 +// parent into `arena`-owned hunks. has_prev/has_more flag adjacent windows.│ │150 fn renderCommitDiff( │ │151 io: std.Io, │ │152 gpa: std.mem.Allocator, │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │153 arena: std.mem.Allocator, │ │154 repo: *rp.Repo(.xit, .{}), │ │155 oid: [xit.hash.hexLen(.sha1)]u8, │ │152 - max_hunks: usize, │ │156 + start: usize, │ │157 + len: usize, │ │158 ) !RenderedDiff { │ │154 - const empty = RenderedDiff{ .hunks = &.{}, .shown_hunks = 0, .has_more = false }; │ │159 + const empty = RenderedDiff{ .hunks = &.{}, .has_prev = false, .has_more = false }; │ │160 │ │161 // load the commit so we can diff it against its first parent. │ │157 - var start = [_][xit.hash.hexLen(.sha1)]u8{oid}; │ │158 - var commit_iter = repo.log(io, gpa, start[0..1]) catch return empty; │ │162 + var start_oids = [_][xit.hash.hexLen(.sha1)]u8{oid}; │ │163 + var commit_iter = repo.log(io, gpa, start_oids[0..1]) catch return empty; │ │164 defer commit_iter.deinit(); │ │165 const commit_object = (commit_iter.next(gpa) catch return empty) orelse return empty;│ │166 defer commit_object.deinit(); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │173 var file_iter = repo.filePairs(io, gpa, .{ .tree = .{ .tree_diff = &tree_diff } }) catch return empty;│ │174 │ │175 var hunks: std.ArrayList(Hunk) = .empty; │ │176 + var index: usize = 0; // running hunk index across all files │ │177 var has_more = false; │ │178 │ │179 file_loop: while (file_iter.next() catch null) |pair_val| { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────╮ │189 var hunk = hunk_val; │ │190 defer hunk.deinit(gpa); │ │191 │ │186 - if (hunks.items.len >= max_hunks) { │ │192 + const i = index; │ │193 + index += 1; │ │194 + if (i < start) continue; // before this window│ │195 + if (hunks.items.len >= len) { │ │196 has_more = true; │ │197 break :file_loop; │ │198 } │ ╰───────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │206 } │ │207 } │ │208 │ │200 - const shown = hunks.items.len; │ │201 - return .{ .hunks = try hunks.toOwnedSlice(arena), .shown_hunks = shown, .has_more = has_more }; │ │209 + return .{ .hunks = try hunks.toOwnedSlice(arena), .has_prev = start > 0, .has_more = has_more };│ │210 } │ │211 │ │212 // append a hunk's header and its edit lines, each prefixed with a right-aligned │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │307 try addRow(allocator, &list_box, commit.message, ""); │ │308 } │ │309 if (data.next_start) |next| { │ │302 - try addRow(allocator, &list_box, "next", try commitsLink(session.page_arena, data.identity, next, 0)); │ │310 + try addRow(allocator, &list_box, "next →", try commitsLink(session.page_arena, data.identity, next, 0));│ │311 } │ │312 if (list_box.children.count() > 0) list_box.getFocus().child_id = list_box.children.keys()[0]; │ │313 break :blk try wgt.Scroll(ui.Widget).init(allocator, .{ .box = list_box }, .{ .direction = .vert }); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │374 try box.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │375 } │ │376 │ │369 - // the focusable "load more" row. it's a navigation link to this commit's │ │370 - // route with a larger `after`, so activating it (the host follows the "a:" │ │371 - // link) reloads the page showing more hunks — the same on the TUI and web. │ │372 - fn addLoadMore(self: *View, allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), oid: []const u8, next_after: usize) !void { │ │373 - const link = try commitsLink(self.session.page_arena, self.data.identity, oid, next_after); │ │374 - var tb = try wgt.TextBox(ui.Widget).init(allocator, "load more", .{ .border_style = .hidden, .rounded_corners = true, .wrap_kind = .none }); │ │377 + // a focusable window-navigation row ("previous"/"next"). it's a link to this │ │378 + // commit's route at `target_after`, so activating it (the host follows the │ │379 + // "a:" link) reloads the page on the adjacent window — same on TUI and web. │ │380 + fn addNavLink(self: *View, allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), label: []const u8, oid: []const u8, target_after: usize) !void {│ │381 + const link = try commitsLink(self.session.page_arena, self.data.identity, oid, target_after); │ │382 + var tb = try wgt.TextBox(ui.Widget).init(allocator, label, .{ .border_style = .hidden, .rounded_corners = true, .wrap_kind = .none }); │ │383 errdefer tb.deinit(allocator); │ │384 tb.getFocus().focusable = true; │ │385 tb.getFocus().kind = .{ .custom = link }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │436 if (root_focus.grandchild_id) |g| { │ │437 if (self.box.getFocus().children.contains(g)) { │ │438 if (self.selectedCommitIndex()) |sel| { │ │431 - // selection moves reset the diff expansion (after = 0). │ │432 - if (ui.RoutablePage.repoCommitsRoute(self.data.identity, self.data.commits[sel].oid, 0)) |route| │ │439 + // mirror the commit's current diff window so the url stays │ │440 + // linkable (0 for any commit but the windowed start one). │ │441 + if (ui.RoutablePage.repoCommitsRoute(self.data.identity, self.data.commits[sel].oid, self.data.commits[sel].window_start)) |route|│ │442 self.session.data.current_page = route; │ │443 } │ │444 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯