╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Patches.zig│ ╰───────────────────────╯ ╭──────────────────────────────────────────────────────────────────╮ │727 return .{ │ │728 .identity = empty.identity, │ │729 .default_target_branch = empty.default_target_branch,│ │746 - .label = empty.label, │ │730 .search = empty.search, │ │731 .selected_id = empty.selected_id, │ │732 .comment_id = empty.comment_id, │ ╰──────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 995 │ │ 996 // tabs switching between the patches page's views │ │ 997 pub fn initHeader(allocator: std.mem.Allocator, session: *ui.Session, data: *const Self) !Header {│ │1015 - var header = try Header.init(allocator, session, data.search, null); │ │ 998 + var header = try Header.init(allocator, session, data.search, " search "); │ │ 999 errdefer header.deinit(allocator); │ │1000 const selected_index = View.viewIndex(data.view); │ │1001 const page_selected = std.meta.activeTag(session.data.current_page) == .repo_patches; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1003 // a list tab per status, labeled with its listing's patch count │ │1004 const status_labels = [_][]const u8{ open_tab_label, closed_tab_label, merged_tab_label }; │ │1005 for ([_]Status{ .open, .closed, .merged }, status_labels, 0..) |status, status_label, index| { │ │1023 - const route = try thread.searchRoute(ui.RoutablePage.repoPatchesRoute(data.identity, status, data.label, ""), data.search);│ │1006 + const route = try thread.searchRoute(ui.RoutablePage.repoPatchesRoute(data.identity, status, ""), data.search); │ │1007 const link = try ui.inPageTabLink(session, route, page_selected and selected_index == index); │ │1008 var label_buf: [64]u8 = undefined; │ │1009 const label = try thread.countLabel(&label_buf, status_label, data.window(status).*); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1012 │ │1013 // labels tab │ │1014 { │ │1032 - const labels_route = try thread.searchRoute(ui.RoutablePage.repoThreadLabelsRoute(.patch, data.identity, data.label), data.search);│ │1015 + const labels_route = try thread.searchRoute(ui.RoutablePage.repoThreadLabelsRoute(.patch, data.identity), data.search); │ │1016 const labels_link = try ui.inPageTabLink(session, labels_route, page_selected and selected_index == View.viewIndex(.labels)); │ │1017 try header.addTab(allocator, labels_tab_label, labels_link, View.viewIndex(.labels)); │ │1018 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/ui/widget.zig│ ╰─────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1042 pub const SearchBox = struct { │ │1043 text_input: wgt.TextInput, │ │1044 session: *ui.Session, │ │1045 + │ │1046 + // the text width, narrowed toward the minimum when the row is tight │ │1047 + const min_width = 18; │ │1048 + const max_width = 30; │ │1049 │ │1050 // the room the box asks its row for │ │1047 - pub const min_size: layout.MaybeSize = .{ .width = 20, .height = 3 }; │ │1051 + pub const min_size: layout.MaybeSize = .{ .width = min_width + 2, .height = 3 }; │ │1052 │ │1053 pub fn init(allocator: std.mem.Allocator, session: *ui.Session, label: []const u8, name: []const u8, text_value: ?[]const u8) !SearchBox { │ │1050 - var text_input = try wgt.TextInput.init(allocator, .{ .top_label = .{ .text = label }, .name = name, .round_corners = true, .visible_width = 18, .render_content = session.is_terminal }); │ │1054 + var text_input = try wgt.TextInput.init(allocator, .{ .top_label = .{ .text = label }, .name = name, .round_corners = true, .visible_width = max_width, .render_content = session.is_terminal });│ │1055 errdefer text_input.deinit(allocator); │ │1056 text_input.getFocus().mode = .all; │ │1057 if (text_value) |value| try text_input.setContent(allocator, value); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1063 } │ │1064 │ │1065 pub fn build(self: *SearchBox, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { │ │1066 + // the row leaves room for its other children's minimums, so this is what can be spared │ │1067 + const room = (constraint.max_size.width orelse max_width + 2) -| 2; │ │1068 + self.text_input.options.visible_width = std.math.clamp(room, min_width, max_width); │ │1069 self.text_input.options.bottom_label.text = if (root_focus.grandchild_id == self.getFocus().id) " press enter " else "";│ │1070 try self.text_input.build(allocator, constraint, root_focus); │ │1071 if (!self.session.is_terminal) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭─────────────────────────────────────────────────────────╮ │16 const Focus = xitui.focus.Focus; │ │17 const inp = @import("../input.zig"); │ │18 const srch_thrd = @import("../../search_thread.zig");│ │19 +const srch = @import("../../search.zig"); │ │20 const diff3 = @import("../../diff3.zig"); │ │21 const Comment = @import("../Repo/Comment.zig"); │ │22 const Attachment = @import("../Repo/Attachment.zig");│ ╰─────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │158 return try ui.Author.init(admin_moment, arena, commit_object.content.commit.metadata.author orelse "");│ │159 } │ │160 │ │160 -pub fn statusSet( │ │161 +fn statusSet( │ │162 comptime Data: type, │ │163 comptime DB: type, │ │164 statuses: DB.SortedMap(.read_only), │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────╮ │168 return try DB.SortedSet(.read_only).init(cursor);│ │169 } │ │170 │ │170 -pub fn labelStatusSet( │ │171 +fn labelStatusSet( │ │172 comptime Data: type, │ │173 comptime DB: type, │ │174 label_statuses: DB.SortedMap(.read_only), │ ╰──────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │179 const key = Data.Event.labelStatusKey(&key_buffer, label, status) catch return null; │ │180 const cursor = (try label_statuses.getCursor(key)) orelse return null; │ │181 return try DB.SortedSet(.read_only).init(cursor); │ │182 +} │ │183 + │ │184 +// the sets `status`'s listing intersects: each label's set for the status (each │ │185 +// within the status set, which joins only without labels), then `extra`. none │ │186 +// when one is missing, which lists nothing. │ │187 +pub fn statusSets( │ │188 + comptime Data: type, │ │189 + comptime hash_kind: hash.HashKind, │ │190 + aa: std.mem.Allocator, │ │191 + haxy_moment: evt.EventDB(hash_kind).HashMap(.read_only), │ │192 + labels: []const []const u8, │ │193 + // null when one of them is missing │ │194 + extra: ?[]const evt.EventDB(hash_kind).SortedSet(.read_only), │ │195 + status: Data.Status, │ │196 +) ![]const evt.EventDB(hash_kind).SortedSet(.read_only) { │ │197 + const DB = evt.EventDB(hash_kind); │ │198 + var sets: std.ArrayList(DB.SortedSet(.read_only)) = .empty; │ │199 + if (labels.len == 0) { │ │200 + const cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, Data.Event.status_to_id_set_key)) orelse return &.{}; │ │201 + try sets.append(aa, (try statusSet(Data, DB, try DB.SortedMap(.read_only).init(cursor), status)) orelse return &.{}); │ │202 + } else { │ │203 + const cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, Data.Event.label_status_to_id_set_key)) orelse return &.{};│ │204 + const label_statuses = try DB.SortedMap(.read_only).init(cursor); │ │205 + for (labels) |label| try sets.append(aa, (try labelStatusSet(Data, DB, label_statuses, label, status)) orelse return &.{}); │ │206 + } │ │207 + try sets.appendSlice(aa, extra orelse return &.{}); │ │208 + return sets.items; │ │209 } │ │210 │ │211 +// whether `order_key` is in every one of `sets`, and there is at least one │ │212 +pub fn inEverySet(comptime DB: type, sets: []const DB.SortedSet(.read_only), order_key: []const u8) !bool { │ │213 + if (sets.len == 0) return false; │ │214 + for (sets) |set| { │ │215 + if (!try set.contains(order_key)) return false; │ │216 + } │ │217 + return true; │ │218 +} │ │219 + │ │220 pub fn loadLabels( │ │221 comptime Data: type, │ │222 comptime hash_kind: hash.HashKind, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯