╭ changed ────────────╮ │src/ui/Repo/Files.zig│ ╰─────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │354 │ │355 // the search box and the clone url at the top. │ │356 { │ │357 - var header_view = try Header.View.init(allocator, data, session); │ │357 + var header_view = try ui.widget.SearchHeader.init(allocator, session, data.location, " find file ", "find", data.find, data.find_available); │ │358 errdefer header_view.deinit(allocator); │ │359 - try outer.children.put(allocator, header_view.getFocus().id, .{ .widget = .{ .repo_files_header = header_view }, .rect = null, .min_size = .{ .width = null, .height = 3 } });│ │359 + try outer.children.put(allocator, header_view.getFocus().id, .{ .widget = .{ .search_header = header_view }, .rect = null, .min_size = .{ .width = null, .height = 3 } }); │ │360 } │ │361 │ │362 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .horiz }); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │504 return &self.box.children.values()[content_index].widget.box; │ │505 } │ │506 │ │507 - fn header(self: *View) *Header.View { │ │508 - return &self.box.children.values()[header_index].widget.repo_files_header;│ │507 + fn header(self: *View) *ui.widget.SearchHeader { │ │508 + return &self.box.children.values()[header_index].widget.search_header; │ │509 } │ │510 │ │511 fn headerActive(self: *View) bool { │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │701 if (self.headerActive()) { │ │702 if (key == .arrow_down) { │ │703 root_focus.setFocus(self.contentBox().getFocus().id); │ │704 - } else { │ │705 - try self.header().input(allocator, key, root_focus); │ │704 + return; │ │705 } │ │707 - return; │ │706 + if (key == .enter) if (try self.header().submittedText(allocator)) |term| { │ │707 + defer allocator.free(term); │ │708 + return self.submit(term); │ │709 + }; │ │710 + return self.header().input(allocator, key, root_focus); │ │711 } │ │712 if (key == .arrow_up and self.contentAtTop() and self.header().focusHeader(root_focus)) return;│ │713 if (self.detailActive()) { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │715 } else { │ │716 try self.listInput(key, root_focus); │ │717 } │ │718 + } │ │719 + │ │720 + // navigate to the typed term's results, pinned to the listing's ref. an│ │721 + // empty term leaves the results for the plain listing. │ │722 + fn submit(self: *View, term: []const u8) !void { │ │723 + if (term.len == 0 and self.data.find == null) return; │ │724 + const route = self.data.filesRoute("", 0) orelse return; │ │725 + try self.session.navigate(route.withFind(term) orelse return); │ │726 } │ │727 │ │728 fn listInput(self: *View, key: Key, root_focus: *Focus) !void { │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │886 │ │887 // only the header's own row sits directly below the repository header. │ │888 pub fn atTop(self: *View) bool { │ │878 - return self.headerActive() or (!self.header().hasHeaderFocus() and self.contentAtTop());│ │889 + return self.headerActive() or (!self.header().hasFocusable() and self.contentAtTop()); │ │890 } │ │891 │ │892 pub fn focusHeader(self: *View, root_focus: *Focus) bool { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 992 } │ │ 993 return std.fmt.allocPrint(aa, " " ++ ellipsis ++ "{s} ", .{dir[start..]}); │ │ 994 } │ │ 984 - │ │ 985 -// the search box and the clone url shown above the listing. │ │ 986 -pub const Header = struct { │ │ 987 - pub const View = struct { │ │ 988 - box: wgt.Box(ui.Widget), │ │ 989 - data: *const Self, │ │ 990 - session: *ui.Session, │ │ 991 - │ │ 992 - pub fn init(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !Header.View { │ │ 993 - var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .horiz }); │ │ 994 - errdefer box.deinit(allocator); │ │ 995 - │ │ 996 - // the search box only appears where an index can serve it. │ │ 997 - if (data.find_available) { │ │ 998 - var text_input = try wgt.TextInput.init(allocator, .{ .label = " find file ", .name = "find", .rounded_corners = true, .visible_width = 18, .render_content = session.is_terminal }); │ │ 999 - errdefer text_input.deinit(allocator); │ │1000 - text_input.getFocus().mode = .all; │ │1001 - if (data.find) |term| try text_input.setContent(allocator, term); │ │1002 - box.getFocus().child_id = text_input.getFocus().id; │ │1003 - try box.children.put(allocator, text_input.getFocus().id, .{ .widget = .{ .text_input = text_input }, .rect = null, .min_size = .{ .width = 20, .height = 3 } }); │ │1004 - } │ │1005 - │ │1006 - // the spacer pushes the clone url to the right edge. │ │1007 - { │ │1008 - var spacer = try ui.widget.Spacer.init(allocator); │ │1009 - errdefer spacer.deinit(allocator); │ │1010 - try box.children.put(allocator, spacer.getFocus().id, .{ .widget = .{ .spacer = spacer }, .rect = null, .min_size = null }); │ │1011 - } │ │1012 - │ │1013 - if (session.data.host_kind == .server) { │ │1014 - if (try ui.widget.CopyableText.initClone(allocator, session, data.location)) |value| { │ │1015 - var clone_url = value; │ │1016 - errdefer clone_url.deinit(allocator); │ │1017 - const min_width = clone_url.minWidth(); │ │1018 - if (box.getFocus().child_id == null) box.getFocus().child_id = clone_url.getFocus().id; │ │1019 - try box.children.put(allocator, clone_url.getFocus().id, .{ .widget = .{ .copyable_text = clone_url }, .rect = null, .min_size = .{ .width = min_width, .height = 3 }, .max_size = .{ .width = min_width, .height = 3 } });│ │1020 - } │ │1021 - } │ │1022 - │ │1023 - return .{ .box = box, .data = data, .session = session }; │ │1024 - } │ │ 995 │ │1026 - pub fn deinit(self: *Header.View, allocator: std.mem.Allocator) void { │ │1027 - self.box.deinit(allocator); │ │1028 - } │ │1029 - │ │1030 - pub fn build(self: *Header.View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { │ │1031 - self.clearGrid(); │ │1032 - if (self.findInput()) |text_input| { │ │1033 - text_input.options.bottom_label = if (root_focus.grandchild_id == text_input.getFocus().id) " press enter " else ""; │ │1034 - } │ │1035 - try self.box.build(allocator, constraint, root_focus); │ │1036 - if (!self.session.is_terminal) { │ │1037 - if (self.findInput()) |text_input| { │ │1038 - try self.session.text_inputs.put(self.session.arena.allocator(), text_input.getFocus().id, text_input); │ │1039 - } │ │1040 - } │ │1041 - } │ │1042 - │ │1043 - pub fn input(self: *Header.View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { │ │1044 - const current = self.box.getFocus().child_id orelse return; │ │1045 - if (self.findInput()) |text_input| { │ │1046 - if (current == text_input.getFocus().id) { │ │1047 - switch (key) { │ │1048 - .enter => return self.submit(allocator, text_input), │ │1049 - .arrow_right => if (text_input.cursor == text_input.content.items.len) { │ │1050 - if (self.cloneUrl()) |clone_url| { │ │1051 - root_focus.setFocus(clone_url.getFocus().id); │ │1052 - return; │ │1053 - } │ │1054 - }, │ │1055 - else => {}, │ │1056 - } │ │1057 - return text_input.input(allocator, key, root_focus); │ │1058 - } │ │1059 - } │ │1060 - const clone_url = self.cloneUrl() orelse return; │ │1061 - if (key == .arrow_left and clone_url.selected == 0) { │ │1062 - if (self.findInput()) |text_input| { │ │1063 - root_focus.setFocus(text_input.getFocus().id); │ │1064 - return; │ │1065 - } │ │1066 - } │ │1067 - try clone_url.input(allocator, key, root_focus); │ │1068 - } │ │1069 - │ │1070 - // navigate to the typed term's results, pinned to the listing's ref. an │ │1071 - // empty term leaves the results for the plain listing. │ │1072 - fn submit(self: *Header.View, allocator: std.mem.Allocator, text_input: *wgt.TextInput) !void { │ │1073 - const term = try text_input.text(allocator); │ │1074 - defer allocator.free(term); │ │1075 - if (term.len == 0 and self.data.find == null) return; │ │1076 - const route = self.data.filesRoute("", 0) orelse return; │ │1077 - try self.session.navigate(route.withFind(term) orelse return); │ │1078 - } │ │1079 - │ │1080 - // enter the header, preferring the search box. │ │1081 - pub fn focusHeader(self: *Header.View, root_focus: *Focus) bool { │ │1082 - if (self.findInput()) |text_input| { │ │1083 - root_focus.setFocus(text_input.getFocus().id); │ │1084 - return true; │ │1085 - } │ │1086 - const clone_url = self.cloneUrl() orelse return false; │ │1087 - root_focus.setFocus(clone_url.getFocus().id); │ │1088 - return true; │ │1089 - } │ │1090 - │ │1091 - pub fn hasHeaderFocus(self: *Header.View) bool { │ │1092 - return self.cloneUrl() != null or self.findInput() != null; │ │1093 - } │ │1094 - │ │1095 - fn cloneUrl(self: *Header.View) ?*ui.widget.CopyableText { │ │1096 - for (self.box.children.values()) |*child| switch (child.widget) { │ │1097 - .copyable_text => |*copyable_text| return copyable_text, │ │1098 - else => {}, │ │1099 - }; │ │1100 - return null; │ │1101 - } │ │1102 - │ │1103 - fn findInput(self: *Header.View) ?*wgt.TextInput { │ │1104 - for (self.box.children.values()) |*child| switch (child.widget) { │ │1105 - .text_input => |*text_input| return text_input, │ │1106 - else => {}, │ │1107 - }; │ │1108 - return null; │ │1109 - } │ │1110 - │ │1111 - pub fn clearGrid(self: *Header.View) void { │ │1112 - self.box.clearGrid(); │ │1113 - } │ │1114 - │ │1115 - pub fn getGrid(self: Header.View) ?Grid { │ │1116 - return self.box.getGrid(); │ │1117 - } │ │1118 - │ │1119 - pub fn getFocus(self: *Header.View) *Focus { │ │1120 - return self.box.getFocus(); │ │1121 - } │ │1122 - }; │ │1123 -}; │ │1124 - │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯