╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │576 } │ │577 } │ │578 │ │663 - // cap the list at list_max_width only while the diff pane fits beside it. │ │664 - // the box drops the diff when the width can't hold both minimums, so when │ │579 + // cap the list at list_max_width only while the detail pane fits beside it. │ │580 + // the box drops the pane when the width can't hold both minimums, so when │ │581 // it's that narrow we lift the cap and let the list fill the whole width. │ │666 - const both_panes_fit = if (constraint.max_size.width) |w| w >= list_max_width + diff_min_width else true; │ │582 + const both_panes_fit = if (constraint.max_size.width) |w| w >= list_max_width + detail_min_width else true; │ │583 self.contentBox().children.values()[list_index].max_size = if (both_panes_fit) .{ .width = list_max_width, .height = null } else null;│ │584 │ │669 - // stretch the diff pane across the rest of the width so it fills the area │ │585 + // stretch the detail pane across the rest of the width so it fills the area │ │586 // rather than shrinking to its content; its scroll fills the pane. when │ │587 // too narrow for both, it fills the whole width. │ │672 - const diff_width: usize = if (constraint.max_size.width) |w| │ │588 + const detail_width: usize = if (constraint.max_size.width) |w| │ │589 (if (both_panes_fit) w - list_max_width else w) │ │590 else │ │675 - diff_min_width; │ │676 - self.contentBox().children.values()[diff_index].min_size = .{ .width = diff_width, .height = null }; │ │591 + detail_min_width; │ │592 + self.contentBox().children.values()[detail_index].min_size = .{ .width = detail_width, .height = null }; │ │593 │ │594 // the message is the pane's only wrapping row, and wrapping needs a │ │595 // bounded width, which the pane's scroll doesn't grant (it scrolls │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │596 // horizontally too). cap it to the pane, leaving room for the border │ │597 // and the scrollbar column. re-capped each build so it tracks resizes. │ │682 - for (self.diffInner().children.values()) |*child| switch (child.widget) { │ │598 + for (self.detailInner().children.values()) |*child| switch (child.widget) {│ │599 .text_box => |text_box| if (text_box.options.wrap_kind == .word) { │ │684 - child.max_size = .{ .width = diff_width -| 3, .height = null }; │ │600 + child.max_size = .{ .width = detail_width -| 3, .height = null }; │ │601 }, │ │602 else => {}, │ │603 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │606 // each Scroll's web-native mode hands its full content to a real │ │607 // scrollable element, so we no longer build unbounded here. │ │608 // │ │693 - // crossing panes only re-selects the content box's child (focusDiff / │ │609 + // crossing panes only re-selects the content box's child (focusDetail /│ │610 // focusList); when the window is too narrow to show both, the pane that│ │611 // held focus is dropped here. the framework recovers focus after the │ │612 // top-level build by re-deriving it down the selected-child chain, so │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │614 try self.box.build(allocator, constraint, root_focus); │ │615 } │ │616 │ │701 - fn refreshDiff(self: *View, allocator: std.mem.Allocator) !void { │ │617 + fn refreshDetail(self: *View, allocator: std.mem.Allocator) !void { │ │618 const sel = self.selectedCommitIndex() orelse return; │ │703 - if (self.diffed_index) |d| if (d == sel) return; │ │704 - try self.populateDiff(allocator, sel); │ │705 - self.diffed_index = sel; │ │619 + if (self.detailed_index) |d| if (d == sel) return; │ │620 + try self.populateDetail(allocator, sel); │ │621 + self.detailed_index = sel; │ │622 } │ │623 │ │708 - fn populateDiff(self: *View, allocator: std.mem.Allocator, sel: usize) !void { │ │624 + fn populateDetail(self: *View, allocator: std.mem.Allocator, sel: usize) !void {│ │625 const commit = self.data.commits[sel]; │ │710 - const inner = self.diffInner(); │ │626 + const inner = self.detailInner(); │ │627 │ │628 for (inner.children.values()) |*child| child.widget.deinit(allocator); │ │629 inner.children.clearAndFree(allocator); │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │630 inner.getFocus().child_id = null; │ │631 │ │716 - switch (self.paneContent(sel)) { │ │717 - .message => { │ │718 - try addLink(allocator, inner, "← back to diff", try commitsLink(self.session.page_arena, self.data, commit.oid, 0, "")); │ │719 - try self.addMessageBox(allocator, inner, commit, .whole); │ │720 - }, │ │721 - .diff => |d| { │ │722 - if (d.path.len == 0) { │ │723 - try self.addMessageBox(allocator, inner, commit, .preview); │ │724 - if (commit.author != .unknown or commit.committer != .unknown) { │ │725 - var row = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .horiz }); │ │726 - errdefer row.deinit(allocator); │ │727 - if (commit.author != .unknown) { │ │728 - var tb = try ui.authorBox(allocator, self.session.page_arena, commit.author); │ │729 - errdefer tb.deinit(allocator); │ │730 - try row.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │731 - } │ │732 - if (commit.committer != .unknown) { │ │733 - var tb = try ui.authorBox(allocator, self.session.page_arena, commit.committer); │ │734 - errdefer tb.deinit(allocator); │ │735 - tb.options.label = " committer "; │ │736 - try row.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │737 - } │ │738 - row.getFocus().child_id = row.children.keys()[0]; │ │739 - try inner.children.put(allocator, row.getFocus().id, .{ .widget = .{ .box = row }, .rect = null, .min_size = null }); │ │740 - } │ │741 - { │ │742 - var tb = try wgt.TextBox.init(allocator, commit.timestamp, .{ .border_style = .single, .round_corners = true, .wrap_kind = .none, .label = " timestamp " }); │ │743 - errdefer tb.deinit(allocator); │ │744 - tb.getFocus().mode = .all; │ │745 - try inner.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │746 - } │ │747 - if (commit.merge) if (self.data.handle.location.commitsMergeRoute(commit.oid)) |route| { │ │748 - try addLink(allocator, inner, "view commits from this merge", try std.fmt.allocPrint(self.session.page_arena.allocator(), "a:{s}", .{try route.toUrl(self.session.page_arena)}));│ │749 - }; │ │750 - if (commit.stats) |stats| { │ │751 - var text: std.Io.Writer.Allocating = .init(allocator); │ │752 - defer text.deinit(); │ │753 - try text.writer.print("lines changed: {d}", .{stats.lines_added + stats.lines_changed}); │ │754 - if (stats.lines_removed != 0) try text.writer.print("\nlines removed: {d}", .{stats.lines_removed}); │ │755 - const bytes_added = stats.bytes_added >= stats.bytes_removed; │ │756 - const bytes = if (bytes_added) stats.bytes_added - stats.bytes_removed else stats.bytes_removed - stats.bytes_added; │ │757 - try text.writer.print("\nbytes {s}: {d}\nfiles changed: {d}", .{ │ │758 - if (bytes_added) "added" else "removed", bytes, stats.files_added + stats.files_changed, │ │759 - }); │ │760 - if (stats.files_removed != 0) try text.writer.print("\nfiles removed: {d}", .{stats.files_removed}); │ │761 - var tb = try wgt.TextBox.init(allocator, text.written(), .{ .border_style = .single, .round_corners = true, .wrap_kind = .none, .label = " stats " }); │ │762 - errdefer tb.deinit(allocator); │ │763 - tb.getFocus().mode = .all; │ │764 - try inner.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │765 - } │ │766 - try addLink(allocator, inner, "view files at this commit", try filesObjectLink(self.session.page_arena, self.data.handle.location, commit.oid)); │ │632 + if (sel == 0 and self.data.message) { │ │633 + try addLink(allocator, inner, "← back to commit", try commitsLink(self.session.page_arena, self.data, commit.oid)); │ │634 + try self.addMessageBox(allocator, inner, commit, .whole); │ │635 + } else { │ │636 + const pa = self.session.page_arena; │ │637 + { │ │638 + var row = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .horiz }); │ │639 + errdefer row.deinit(allocator); │ │640 + const diff_route = self.data.handle.location.commitDiffRoute(commit.oid, commit.parent_oid) orelse return error.RouteTooLong; │ │641 + try addLink(allocator, &row, "view diff", try std.fmt.allocPrint(pa.allocator(), "a:{s}", .{try diff_route.toUrl(pa)})); │ │642 + try addLink(allocator, &row, "view files at this commit", try filesObjectLink(pa, self.data.handle.location, commit.oid)); │ │643 + row.getFocus().child_id = row.children.keys()[0]; │ │644 + try inner.children.put(allocator, row.getFocus().id, .{ .widget = .{ .box = row }, .rect = null, .min_size = null }); │ │645 + } │ │646 + if (commit.merge) if (self.data.handle.location.commitsMergeRoute(commit.oid)) |route| { │ │647 + try addLink(allocator, inner, "view commits from this merge", try std.fmt.allocPrint(pa.allocator(), "a:{s}", .{try route.toUrl(pa)})); │ │648 + }; │ │649 + if (commit.author != .unknown or commit.committer != .unknown) { │ │650 + var row = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .horiz }); │ │651 + errdefer row.deinit(allocator); │ │652 + if (commit.author != .unknown) { │ │653 + var tb = try ui.authorBox(allocator, pa, commit.author); │ │654 + errdefer tb.deinit(allocator); │ │655 + try row.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │656 } │ │768 - │ │769 - try (Diff{ │ │770 - .route = .{ .commit = .{ .location = self.data.handle.location, .oid = commit.oid, .base_oid = self.data.base_oid } }, │ │771 - .path = d.path, │ │772 - .window = commit.window, │ │773 - }).appendWindow(allocator, self.session, inner); │ │774 - }, │ │657 + if (commit.committer != .unknown) { │ │658 + var tb = try ui.authorBox(allocator, pa, commit.committer); │ │659 + errdefer tb.deinit(allocator); │ │660 + tb.options.label = " committer "; │ │661 + try row.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │662 + } │ │663 + row.getFocus().child_id = row.children.keys()[0]; │ │664 + try inner.children.put(allocator, row.getFocus().id, .{ .widget = .{ .box = row }, .rect = null, .min_size = null }); │ │665 + } │ │666 + { │ │667 + var tb = try wgt.TextBox.init(allocator, commit.timestamp, .{ .border_style = .single, .round_corners = true, .wrap_kind = .none, .label = " timestamp " }); │ │668 + errdefer tb.deinit(allocator); │ │669 + tb.getFocus().mode = .all; │ │670 + try inner.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │671 + } │ │672 + if (commit.stats) |stats| { │ │673 + var text: std.Io.Writer.Allocating = .init(allocator); │ │674 + defer text.deinit(); │ │675 + try text.writer.print("lines changed: {d}", .{stats.lines_added + stats.lines_changed}); │ │676 + if (stats.lines_removed != 0) try text.writer.print("\nlines removed: {d}", .{stats.lines_removed}); │ │677 + const bytes_added = stats.bytes_added >= stats.bytes_removed; │ │678 + const bytes = if (bytes_added) stats.bytes_added - stats.bytes_removed else stats.bytes_removed - stats.bytes_added; │ │679 + try text.writer.print("\nbytes {s}: {d}\nfiles changed: {d}", .{ │ │680 + if (bytes_added) "added" else "removed", bytes, stats.files_added + stats.files_changed, │ │681 + }); │ │682 + if (stats.files_removed != 0) try text.writer.print("\nfiles removed: {d}", .{stats.files_removed}); │ │683 + var tb = try wgt.TextBox.init(allocator, text.written(), .{ .border_style = .single, .round_corners = true, .wrap_kind = .none, .label = " stats " }); │ │684 + errdefer tb.deinit(allocator); │ │685 + tb.getFocus().mode = .all; │ │686 + try inner.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │687 + } │ │688 + try self.addMessageBox(allocator, inner, commit, .preview); │ │689 } │ │690 │ │691 // point the pane at its first row so focus recovery can land here. │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │694 // reset the scroll to the top for the newly-shown commit: directly on the│ │695 // terminal (the wasm offset), and via a version bump on the web (so the │ │696 // renderer's scroll id changes and JS drops the preserved position). │ │783 - const sc = self.diffScroll(); │ │697 + const sc = self.detailScroll(); │ │698 sc.x = 0; │ │699 sc.y = 0; │ │700 sc.getFocus().version +%= 1; │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │718 return header_view.input(allocator, key, root_focus); │ │719 } │ │720 if (direction == .up and self.contentAtTop() and self.focusHeader(root_focus)) return;│ │807 - if (self.diffActive()) { │ │808 - const diff_view = &self.diffOuter().children.values()[0].widget.diff_view; │ │809 - if (key == .arrow_left and self.diffScroll().x == 0) { │ │810 - if (!diff_view.moveInRow(root_focus, false)) self.focusList(root_focus); │ │721 + if (self.detailActive()) { │ │722 + const detail_view = &self.detailOuter().children.values()[0].widget.diff_view; │ │723 + if (key == .arrow_left and self.detailScroll().x == 0) { │ │724 + if (!detail_view.moveInRow(root_focus, false)) self.focusList(root_focus); │ │725 } else { │ │812 - try diff_view.input(allocator, key, root_focus); │ │726 + try detail_view.input(allocator, key, root_focus); │ │727 } │ │728 } else { │ │729 try self.listInput(key, root_focus); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │736 if (text.len == 0 and self.data.search == null) return; │ │737 // an object view or a base-bounded list searches the default branch, so the url says so │ │738 const route = if (self.data.default_branch.len != 0) │ │825 - self.data.handle.location.commitsRoute(.branch, self.data.default_branch, 0, "", "") │ │739 + self.data.handle.location.commitsRoute(.branch, self.data.default_branch, "") │ │740 else │ │827 - self.data.handle.location.commitsRoute(self.data.ref_or_oid, self.data.ref_or_oid_value, 0, "", self.data.base_oid);│ │741 + self.data.handle.location.commitsRoute(self.data.ref_or_oid, self.data.ref_or_oid_value, self.data.base_oid); │ │742 try self.session.navigate((route orelse return).withSearch(text) orelse return); │ │743 } │ │744 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │745 fn listInput(self: *View, key: Key, root_focus: *Focus) !void { │ │746 // up/down (and the scroll wheel) move the selection a row; page up/down │ │833 - // jump a fixed amount. right/Enter cross into the diff pane. Enter/clicks │ │747 + // jump a fixed amount. right/Enter cross into the detail pane. Enter/clicks │ │748 // on the "next" row become navigation in the host before reaching here. │ │749 if (inp.rowDelta(key, @intCast(self.listBox().children.count()))) |delta| { │ │750 ui.widget.moveRowFocus(self.listBox(), self.listScroll(), root_focus, delta);│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │752 } │ │753 switch (key) { │ │754 .enter => if (self.selectedCommitIndex() != null) │ │841 - self.focusDiff(root_focus) │ │755 + self.focusDetail(root_focus) │ │756 else if (self.data.next_start) |next| { │ │757 if (pageRoute(self.data, next)) |route| try self.session.navigate(route);│ │758 }, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯