╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │461 } │ │462 │ │463 fn diffScroll(self: *View) *wgt.Scroll(ui.Widget) { │ │614 - return &self.diffOuter().children.values()[0].widget.scroll; │ │464 + return &self.diffOuter().children.values()[0].widget.diff_view.scroll;│ │465 } │ │466 │ │467 fn diffInner(self: *View) *wgt.Box(ui.Widget) { │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │572 try self.addMessageBox(allocator, inner, commit, .whole); │ │573 }, │ │574 .diff => |d| { │ │725 - if (d.path.len != 0) { │ │726 - // the file's path, then a row returning to this commit's unfiltered diff. │ │727 - var label = try wgt.TextBox.init(allocator, d.path, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none });│ │728 - errdefer label.deinit(allocator); │ │729 - label.getFocus().mode = .all; │ │730 - try inner.children.put(allocator, label.getFocus().id, .{ .widget = .{ .text_box = label }, .rect = null, .min_size = null }); │ │731 - try self.addNavLink(allocator, inner, "← all files", commit.oid, 0, ""); │ │732 - } else { │ │575 + if (d.path.len == 0) { │ │576 try self.addMessageBox(allocator, inner, commit, .preview); │ │577 if (commit.author != .unknown) { │ │578 var tb = try ui.authorBox(allocator, self.session.page_arena, commit.author); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │582 try self.addViewFilesLink(allocator, inner, commit.oid); │ │583 } │ │584 │ │742 - // a "previous" row and a "next" row at the bottom reload the page │ │743 - // on the adjacent diff window. │ │744 - if (commit.has_prev) try self.addNavLink(allocator, inner, "← previous", commit.oid, commit.window_start -| diff_page, d.path);│ │745 - for (commit.hunks) |hunk| { │ │746 - if (d.path.len == 0) if (hunk.path) |hunk_path| try self.addPathBox(allocator, inner, hunk_path, commit.oid); │ │747 - try self.addHunkBox(allocator, inner, hunk.lines); │ │748 - } │ │749 - if (commit.has_more) try self.addNavLink(allocator, inner, "next →", commit.oid, commit.window_start + diff_page, d.path); │ │585 + try (Diff{ │ │586 + .route = .{ .commit = .{ .location = self.data.location, .oid = commit.oid } }, │ │587 + .path = d.path, │ │588 + .window = commit.window, │ │589 + }).appendWindow(allocator, self.session, inner); │ │590 }, │ │591 } │ │592 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │613 } │ │614 if (key == .arrow_up and self.contentAtTop() and self.header().focusCloneUrl(root_focus)) return; │ │615 if (self.diffActive()) { │ │776 - try self.diffInput(key, root_focus); │ │616 + if (key == .arrow_left and self.diffScroll().x == 0) { │ │617 + self.focusList(root_focus); │ │618 + } else { │ │619 + try self.diffOuter().children.values()[0].widget.diff_view.input(allocator, key, root_focus);│ │620 + } │ │621 } else { │ │622 try self.listInput(key, root_focus); │ │623 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │633 } │ │634 switch (key) { │ │635 .enter => if (self.selectedCommitIndex() != null) │ │792 - try self.focusDiff(root_focus) │ │636 + self.focusDiff(root_focus) │ │637 else if (self.data.next_start) |next| { │ │638 if (self.data.location.commitsRoute(.object, next, 0, "")) |route|│ │639 try self.session.navigate(route); │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │796 - }, │ │797 - .arrow_right => try self.focusDiff(root_focus), │ │798 - else => {}, │ │799 - } │ │800 - } │ │801 - │ │802 - fn diffInput(self: *View, key: Key, root_focus: *Focus) !void { │ │803 - const sc = self.diffScroll(); │ │804 - switch (key) { │ │805 - // left scrolls horizontally, then leaves for the list once flush left. │ │806 - .arrow_left => { │ │807 - if (sc.x > 0) { │ │808 - sc.x -= 1; │ │809 - self.diffScroll().clampToContent(); │ │810 - } else try self.focusList(root_focus); │ │811 - }, │ │812 - .arrow_right => { │ │813 - sc.x += 1; │ │814 - self.diffScroll().clampToContent(); │ │815 - }, │ │816 - .arrow_up => try self.moveDiff(root_focus, -1), │ │817 - .arrow_down => try self.moveDiff(root_focus, 1), │ │818 - .page_up => try self.pageDiff(root_focus, -10), │ │819 - .page_down => try self.pageDiff(root_focus, 10), │ │820 - .home => try self.jumpDiff(root_focus, false), │ │821 - .end => try self.jumpDiff(root_focus, true), │ │822 - // Enter / a click on a "previous"/"next" row follow its "a:" link in │ │823 - // the host (it reloads the adjacent window), so they don't reach here. │ │824 - .mouse => |mouse| switch (mouse.action) { │ │825 - .scroll => |dir| try self.moveDiff(root_focus, if (dir == .up) -1 else 1),│ │826 - else => {}, │ │640 }, │ │641 + .arrow_right => self.focusDiff(root_focus), │ │642 else => {}, │ │643 } │ │644 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │645 │ │832 - // move one step in `delta` (+ down, - up). in the terminal, `delta` is a │ │833 - // line count unless there is a new hunk visible (in which case it is a │ │834 - // hunk count). on the web `delta` is always a hunk count. │ │835 - fn moveDiff(self: *View, root_focus: *Focus, delta: isize) !void { │ │836 - const inner = self.diffInner(); │ │837 - const keys = inner.children.keys(); │ │838 - if (keys.len == 0) return; │ │839 - const cur: usize = if (root_focus.grandchild_id) |g| (inner.children.getIndex(g) orelse 0) else 0; │ │840 - const target = @as(isize, @intCast(cur)) + delta; │ │841 - const in_range = target >= 0 and target < @as(isize, @intCast(keys.len)); │ │842 - │ │843 - if (self.session.is_terminal) { │ │844 - if (in_range and self.hunkVisible(@intCast(target))) { │ │845 - root_focus.setFocus(keys[@intCast(target)]); │ │846 - return; │ │847 - } │ │848 - │ │849 - const sc = self.diffScroll(); │ │850 - sc.y += delta * 5; // magnify because it's a line count │ │851 - self.diffScroll().clampToContent(); │ │852 - if (in_range and self.hunkVisible(@intCast(target))) { │ │853 - root_focus.setFocus(keys[@intCast(target)]); │ │854 - } │ │855 - } else { │ │856 - if (in_range) { │ │857 - root_focus.setFocus(keys[@intCast(target)]); │ │858 - } │ │859 - } │ │860 - } │ │861 - │ │862 - // page a fixed number of lines, then focus the leading visible hunk │ │863 - // (bottom-most when paging down, top-most when paging up). in the │ │864 - // terminal, `delta` is a line count, and on the web it's a hunk count. │ │865 - fn pageDiff(self: *View, root_focus: *Focus, delta: isize) !void { │ │866 - if (self.session.is_terminal) { │ │867 - const sc = self.diffScroll(); │ │868 - sc.y += delta * 5; // magnify because it's a line count │ │869 - self.diffScroll().clampToContent(); │ │870 - try self.focusVisible(root_focus, delta > 0); │ │871 - } else { │ │872 - const inner = self.diffInner(); │ │873 - const keys = inner.children.keys(); │ │874 - if (keys.len == 0) return; │ │875 - const cur: isize = if (root_focus.grandchild_id) |g| @intCast(inner.children.getIndex(g) orelse 0) else 0;│ │876 - const target: usize = @intCast(std.math.clamp(cur + delta, 0, @as(isize, @intCast(keys.len - 1)))); │ │877 - root_focus.setFocus(keys[target]); │ │878 - } │ │879 - } │ │880 - │ │881 - // jump to the first or last hunk. on the web the browser scrolls to the │ │882 - // focused hunk; on the terminal pin the scroll to the top/bottom too. │ │883 - fn jumpDiff(self: *View, root_focus: *Focus, to_end: bool) !void { │ │884 - const inner = self.diffInner(); │ │885 - const keys = inner.children.keys(); │ │886 - if (keys.len == 0) return; │ │887 - if (self.session.is_terminal) { │ │888 - const sc = self.diffScroll(); │ │889 - sc.y = if (to_end) std.math.maxInt(isize) else 0; │ │890 - self.diffScroll().clampToContent(); │ │891 - } │ │892 - root_focus.setFocus(if (to_end) keys[keys.len - 1] else keys[0]); │ │893 - } │ │894 - │ │895 - // whether hunk `index` is at least partly within the diff viewport, per the │ │896 - // last build's layout rects (content space) and the current scroll offset. │ │897 - fn hunkVisible(self: *View, index: usize) bool { │ │898 - const inner = self.diffInner(); │ │899 - const sc = self.diffScroll(); │ │900 - const vp = sc.grid orelse return false; │ │901 - const r = inner.children.values()[index].rect orelse return false; │ │902 - const top = sc.y; │ │903 - const bottom = sc.y + @as(isize, @intCast(vp.size.height - sc.bar_h)); │ │904 - return (r.y + @as(isize, @intCast(r.size.height))) > top and r.y < bottom; │ │905 - } │ │906 - │ │907 - // focus the visible hunk at the scroll's leading edge. `prefer_last` picks │ │908 - // the bottom-most visible (for downward motion), else the top-most. │ │909 - fn focusVisible(self: *View, root_focus: *Focus, prefer_last: bool) !void { │ │910 - const inner = self.diffInner(); │ │911 - const sc = self.diffScroll(); │ │912 - const vp = sc.grid orelse return; │ │913 - const top = sc.y; │ │914 - const bottom = sc.y + @as(isize, @intCast(vp.size.height - sc.bar_h)); │ │915 - var chosen: ?usize = null; │ │916 - for (inner.children.keys(), inner.children.values()) |id, *child| { │ │917 - const r = child.rect orelse continue; // content-space layout rect │ │918 - const r_top = r.y; │ │919 - const r_bot = r.y + @as(isize, @intCast(r.size.height)); │ │920 - if (r_bot <= top or r_top >= bottom) continue; // not visible │ │921 - chosen = id; │ │922 - if (!prefer_last) break; // first visible │ │923 - } │ │924 - if (chosen) |id| root_focus.setFocus(id); │ │925 - } │ │926 - │ │646 // enter the diff pane. the host arrives here on right-arrow or Enter from the │ │647 // list. a diff with no rows can't be entered. setFocus handles the too-narrow │ │648 // case where the pane isn't laid out yet (it gets selected, then focused after │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────╮ │649 // the next build, landing on its remembered hunk). │ │931 - fn focusDiff(self: *View, root_focus: *Focus) !void { │ │650 + fn focusDiff(self: *View, root_focus: *Focus) void { │ │651 if (self.diffInner().children.count() == 0) return; │ │652 root_focus.setFocus(self.diffOuter().getFocus().id);│ │653 } │ ╰─────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────╮ │654 │ │655 // return to the list. │ │937 - fn focusList(self: *View, root_focus: *Focus) !void { │ │656 + fn focusList(self: *View, root_focus: *Focus) void { │ │657 root_focus.setFocus(self.listScroll().getFocus().id);│ │658 } │ │659 │ ╰──────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │713 fn commitRowLink(page_arena: *std.heap.ArenaAllocator, location: ui.RoutablePage.RepoLocation, commit: Commit, content: Content) ![]const u8 {│ │714 const route = (switch (content) { │ │715 .message => location.commitMessageRoute(.object, commit.oid), │ │997 - .diff => |diff| location.commitsRoute(.object, commit.oid, commit.window_start, diff.path), │ │716 + .diff => |diff| location.commitsRoute(.object, commit.oid, commit.window.start, diff.path), │ │717 }) orelse return error.RouteTooLong; │ │718 const url = try route.toUrl(page_arena); │ │719 return std.fmt.allocPrint(page_arena.allocator(), "ai:{s}", .{url}); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯