╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │580 return content.children.getIndex(cid) == diff_index; │ │581 } │ │582 │ │583 + // what the pane shows for the commit at `sel`. the page's content only │ │584 + // applies to the commit it walks from; the rest show their plain diff. │ │585 + fn paneContent(self: *View, sel: usize) Self.Content { │ │586 + return if (sel == 0) self.data.content else .{ .diff = .{} }; │ │587 + } │ │588 + │ │589 // the selected commit's index, or null when the "next" row is selected.│ │590 fn selectedCommitIndex(self: *View) ?usize { │ │591 const lb = self.listBox(); │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │607 if (root_focus.grandchild_id) |g| { │ │608 if (self.box.getFocus().children.contains(g)) { │ │609 if (self.selectedCommitIndex()) |sel| { │ │610 + const oid = self.data.commits[sel].oid; │ │611 + const window_start = self.data.commits[sel].window_start; │ │612 // mirror the commit's current diff window so the url stays │ │613 // linkable (0 for any commit but the windowed start one). │ │544 - if (ui.RoutablePage.repoCommitsRoute(self.data.identity, .object, self.data.commits[sel].oid, self.data.commits[sel].window_start, if (sel == 0) self.data.path else "")) |route|│ │545 - self.session.data.current_page = route; │ │614 + // selecting another commit leaves the message behind, like │ │615 + // it leaves the path filter behind. │ │616 + const mirrored = switch (self.paneContent(sel)) { │ │617 + .message => ui.RoutablePage.repoCommitMessageRoute(self.data.identity, .object, oid), │ │618 + .diff => |d| ui.RoutablePage.repoCommitsRoute(self.data.identity, .object, oid, window_start, d.path), │ │619 + }; │ │620 + if (mirrored) |route| self.session.data.current_page = route; │ │621 } │ │622 } │ │623 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │647 diff_min_width; │ │648 self.contentBox().children.values()[diff_index].min_size = .{ .width = diff_width, .height = null };│ │649 │ │575 - // cap the message row so it wraps within the pane, leaving room for the │ │576 - // pane's border and its scrollbar column. │ │577 - if (self.message_id) |id| { │ │578 - if (self.diffInner().children.getPtr(id)) |child| { │ │650 + // the message is the pane's only wrapping row, and wrapping needs a │ │651 + // bounded width, which the pane's scroll doesn't grant (it scrolls │ │652 + // horizontally too). cap it to the pane, leaving room for the border │ │653 + // and the scrollbar column. re-capped each build so it tracks resizes. │ │654 + for (self.diffInner().children.values()) |*child| switch (child.widget) { │ │655 + .text_box => |text_box| if (text_box.options.wrap_kind == .word) { │ │656 child.max_size = .{ .width = diff_width -| 3, .height = null }; │ │580 - } │ │581 - } │ │657 + }, │ │658 + else => {}, │ │659 + }; │ │660 │ │661 // the web bounds the layout to the browser viewport like the terminal; │ │662 // each Scroll's web-native mode hands its full content to a real │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │679 │ │680 fn populateDiff(self: *View, allocator: std.mem.Allocator, sel: usize) !void {│ │681 const commit = self.data.commits[sel]; │ │604 - // the path filter only applies to the top commit (the walk root). │ │605 - const path = if (sel == 0) self.data.path else ""; │ │682 const inner = self.diffInner(); │ │683 │ │684 for (inner.children.values()) |*child| child.widget.deinit(allocator); │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │685 inner.children.clearAndFree(allocator); │ │686 inner.getFocus().child_id = null; │ │611 - self.message_id = null; │ │687 │ │613 - if (path.len != 0) { │ │614 - // the file's path, then a row returning to this commit's unfiltered diff. │ │615 - var label = try wgt.TextBox(ui.Widget).init(allocator, path, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); │ │616 - errdefer label.deinit(allocator); │ │617 - label.getFocus().focusable = true; │ │618 - try inner.children.put(allocator, label.getFocus().id, .{ .widget = .{ .text_box = label }, .rect = null, .min_size = null }); │ │619 - try self.addNavLink(allocator, inner, "← all files", commit.oid, 0, ""); │ │620 - } else { │ │621 - try self.addMessageBox(allocator, inner, commit.message); │ │622 - try self.addViewFilesLink(allocator, inner, commit.oid); │ │623 - } │ │688 + switch (self.paneContent(sel)) { │ │689 + .message => { │ │690 + try self.addNavLink(allocator, inner, "← back to diff", commit.oid, 0, ""); │ │691 + try self.addMessageBox(allocator, inner, commit, .whole); │ │692 + }, │ │693 + .diff => |d| { │ │694 + if (d.path.len != 0) { │ │695 + // the file's path, then a row returning to this commit's unfiltered diff. │ │696 + var label = try wgt.TextBox(ui.Widget).init(allocator, d.path, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none });│ │697 + errdefer label.deinit(allocator); │ │698 + label.getFocus().focusable = true; │ │699 + try inner.children.put(allocator, label.getFocus().id, .{ .widget = .{ .text_box = label }, .rect = null, .min_size = null }); │ │700 + try self.addNavLink(allocator, inner, "← all files", commit.oid, 0, ""); │ │701 + } else { │ │702 + try self.addMessageBox(allocator, inner, commit, .preview); │ │703 + try self.addViewFilesLink(allocator, inner, commit.oid); │ │704 + } │ │705 │ │625 - // a "previous" row and a "next" row at the bottom reload the page on the │ │626 - // adjacent diff window. │ │627 - if (commit.has_prev) try self.addNavLink(allocator, inner, "← previous", commit.oid, commit.window_start -| diff_page, path); │ │628 - for (commit.hunks) |hunk| { │ │629 - if (path.len == 0) if (hunk.path) |hunk_path| try self.addPathBox(allocator, inner, hunk_path, commit.oid); │ │630 - try self.addHunkBox(allocator, inner, hunk.lines); │ │706 + // a "previous" row and a "next" row at the bottom reload the page │ │707 + // on the adjacent diff window. │ │708 + if (commit.has_prev) try self.addNavLink(allocator, inner, "← previous", commit.oid, commit.window_start -| diff_page, d.path); │ │709 + for (commit.hunks) |hunk| { │ │710 + if (d.path.len == 0) if (hunk.path) |hunk_path| try self.addPathBox(allocator, inner, hunk_path, commit.oid); │ │711 + try self.addHunkBox(allocator, inner, hunk.lines); │ │712 + } │ │713 + if (commit.has_more) try self.addNavLink(allocator, inner, "next →", commit.oid, commit.window_start + diff_page, d.path); │ │714 + }, │ │715 } │ │632 - if (commit.has_more) try self.addNavLink(allocator, inner, "next →", commit.oid, commit.window_start + diff_page, path); │ │716 │ │717 // point the pane at its first row so focus recovery can land here. │ │718 if (inner.children.count() > 0) inner.getFocus().child_id = inner.children.keys()[0]; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │949 return std.fmt.allocPrint(page_arena.allocator(), "ai:{s}", .{url}); │ │950 } │ │951 │ │952 +// the "a:" link to the page showing `oid`'s message on its own. │ │953 +fn messageLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, oid: []const u8) ![]const u8 { │ │954 + const route = ui.RoutablePage.repoCommitMessageRoute(identity, .object, oid) orelse return error.RouteTooLong;│ │955 + const url = try route.toUrl(page_arena); │ │956 + return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{url}); │ │957 +} │ │958 + │ │959 // the "viewing <ref_or_oid> <value>" banner shown above the log. │ │960 pub const Header = struct { │ │961 content: []const u8, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯