╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │429 │ │430 // the ref banner at the top. │ │431 { │ │432 - var header = try Header.View.init(allocator, &data.header, session); │ │433 - errdefer header.deinit(allocator); │ │434 - try outer.children.put(allocator, header.getFocus().id, .{ .widget = .{ .repo_commits_header = header }, .rect = null, .min_size = .{ .width = null, .height = 3 } }); │ │432 + var header_view = try Header.View.init(allocator, &data.header, session, data.identity); │ │433 + errdefer header_view.deinit(allocator); │ │434 + try outer.children.put(allocator, header_view.getFocus().id, .{ .widget = .{ .repo_commits_header = header_view }, .rect = null, .min_size = .{ .width = null, .height = 3 } });│ │435 } │ │436 │ │437 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .horiz }); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │574 │ │575 fn contentBox(self: *View) *wgt.Box(ui.Widget) { │ │576 return &self.box.children.values()[content_index].widget.box; │ │577 + } │ │578 + │ │579 + fn header(self: *View) *Header.View { │ │580 + return &self.box.children.values()[header_index].widget.repo_commits_header;│ │581 + } │ │582 + │ │583 + fn headerActive(self: *View) bool { │ │584 + return self.box.getFocus().child_id == self.header().getFocus().id; │ │585 } │ │586 │ │587 fn listScroll(self: *View) *wgt.Scroll(ui.Widget) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │762 } │ │763 │ │764 pub fn input(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { │ │757 - _ = allocator; │ │765 + if (self.headerActive()) { │ │766 + if (key == .arrow_down) { │ │767 + root_focus.setFocus(self.contentBox().getFocus().id); │ │768 + } else { │ │769 + try self.header().input(allocator, key, root_focus); │ │770 + } │ │771 + return; │ │772 + } │ │773 + if (key == .arrow_up and self.contentAtTop() and self.header().focusCloneUrl(root_focus)) return;│ │774 if (self.diffActive()) { │ │775 try self.diffInput(key, root_focus); │ │776 } else { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │949 return self.box.getFocus(); │ │950 } │ │951 │ │936 - // for the parent's "scroll up at the top jumps to the header" check. in the │ │937 - // diff pane that means the first hunk is focused and the diff can't scroll up │ │938 - // any further; otherwise up-arrow stays in the pane (scrolling or moving │ │939 - // between hunks). when the list holds focus, report its selection directly. │ │940 - pub fn getSelectedIndex(self: *View) ?usize { │ │952 + fn contentAtTop(self: *View) bool { │ │953 if (self.diffActive()) { │ │954 const inner = self.diffInner(); │ │943 - const cid = inner.getFocus().child_id orelse return 1; │ │955 + const cid = inner.getFocus().child_id orelse return true; │ │956 const first_focused = inner.children.count() > 0 and cid == inner.children.keys()[0];│ │945 - return if (first_focused and self.diffScroll().y == 0) 0 else 1; │ │957 + return first_focused and self.diffScroll().y == 0; │ │958 } │ │959 const lb = self.listBox(); │ │948 - const cid = lb.getFocus().child_id orelse return null; │ │949 - return lb.children.getIndex(cid); │ │960 + const cid = lb.getFocus().child_id orelse return true; │ │961 + return lb.children.getIndex(cid) == 0; │ │962 + } │ │963 + │ │964 + // only the clone-url row sits directly below the repository header. │ │965 + pub fn getSelectedIndex(self: *View) ?usize { │ │966 + if (self.headerActive()) return 0; │ │967 + return if (self.header().hasCloneUrl() or !self.contentAtTop()) 1 else 0; │ │968 + } │ │969 + │ │970 + pub fn focusCloneUrl(self: *View, root_focus: *Focus) bool { │ │971 + return self.header().focusCloneUrl(root_focus); │ │972 } │ │973 }; │ │974 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │1005 return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{url});│ │1006 } │ │1007 │ │ 986 -// the "viewing <ref_or_oid> <value>" banner shown above the log. │ │1008 +// the "<ref_or_oid> <value>" banner shown above the log. │ │1009 pub const Header = struct { │ │1010 content: []const u8, │ │1011 │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1013 pub fn init(aa: std.mem.Allocator, ref_or_oid: ui.RoutablePage.RefOrOid, value: []const u8) !Header {│ │1014 const decoded = std.Uri.percentDecodeInPlace(try aa.dupe(u8, value)); │ │1015 return .{ │ │ 994 - .content = try std.fmt.allocPrint(aa, "viewing {s} {s}", .{ @tagName(ref_or_oid), decoded }),│ │1016 + .content = try std.fmt.allocPrint(aa, "{s} {s}", .{ @tagName(ref_or_oid), decoded }), │ │1017 }; │ │1018 } │ │1019 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1021 box: wgt.Box(ui.Widget), │ │1022 data: *const Header, │ │1023 │ │1002 - pub fn init(allocator: std.mem.Allocator, data: *const Header, session: *ui.Session) !Header.View { │ │1003 - _ = session; │ │1024 + pub fn init(allocator: std.mem.Allocator, data: *const Header, session: *ui.Session, identity: []const u8) !Header.View {│ │1025 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .horiz }); │ │1026 errdefer box.deinit(allocator); │ │1027 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1028 + if (!session.data.is_local and session.data.clone_http_port != null and session.data.clone_ssh_port != null) { │ │1029 + var clone_url = try ui.CloneUrl.View.init(allocator, session, identity); │ │1030 + errdefer clone_url.deinit(allocator); │ │1031 + const min_width = clone_url.minWidth(); │ │1032 + box.getFocus().child_id = clone_url.getFocus().id; │ │1033 + try box.children.put(allocator, clone_url.getFocus().id, .{ .widget = .{ .clone_url = clone_url }, .rect = null, .min_size = .{ .width = min_width, .height = 3 } });│ │1034 + } │ │1035 + │ │1036 + var spacer = try ui.Spacer.init(allocator); │ │1037 + errdefer spacer.deinit(allocator); │ │1038 + try box.children.put(allocator, spacer.getFocus().id, .{ .widget = .{ .spacer = spacer }, .rect = null, .min_size = null }); │ │1039 + │ │1040 var text_box = try wgt.TextBox(ui.Widget).init(allocator, data.content, .{ .border_style = .hidden, .wrap_kind = .none }); │ │1041 errdefer text_box.deinit(allocator); │ │1009 - try box.children.put(allocator, text_box.getFocus().id, .{ .widget = .{ .text_box = text_box }, .rect = null, .min_size = null }); │ │1042 + try box.children.put(allocator, text_box.getFocus().id, .{ .widget = .{ .text_box = text_box }, .rect = null, .min_size = null, .shrink = true }); │ │1043 │ │1044 return .{ .box = box, .data = data }; │ │1045 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1054 } │ │1055 │ │1056 pub fn input(self: *Header.View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void {│ │1024 - _ = self; │ │1025 - _ = allocator; │ │1026 - _ = key; │ │1027 - _ = root_focus; │ │1057 + const clone_url = self.cloneUrl() orelse return; │ │1058 + try clone_url.input(allocator, key, root_focus); │ │1059 + } │ │1060 + │ │1061 + pub fn focusCloneUrl(self: *Header.View, root_focus: *Focus) bool { │ │1062 + const clone_url = self.cloneUrl() orelse return false; │ │1063 + root_focus.setFocus(clone_url.getFocus().id); │ │1064 + return true; │ │1065 + } │ │1066 + │ │1067 + pub fn hasCloneUrl(self: *Header.View) bool { │ │1068 + return self.cloneUrl() != null; │ │1069 + } │ │1070 + │ │1071 + fn cloneUrl(self: *Header.View) ?*ui.CloneUrl.View { │ │1072 + for (self.box.children.values()) |*child| switch (child.widget) { │ │1073 + .clone_url => |*clone_url| return clone_url, │ │1074 + else => {}, │ │1075 + }; │ │1076 + return null; │ │1077 } │ │1078 │ │1079 pub fn clearGrid(self: *Header.View) void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯