╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │485 } │ │486 │ │487 pub fn input(self: *Header, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void {│ │332 - _ = allocator; │ │488 + const search_box = self.searchBox(); │ │489 + if (self.box.getFocus().child_id == search_box.getFocus().id) { │ │490 + // tab, and right-arrow past the last character, step to the tabs │ │491 + if (key == .tab or (key == .arrow_right and search_box.atEnd())) { │ │492 + root_focus.setFocus(self.tab_ids.keys()[0]); │ │493 + return; │ │494 + } │ │495 + return search_box.input(allocator, key, root_focus); │ │496 + } │ │497 const current_tab = self.currentTabIndex() orelse return; │ │498 + if (key == .back_tab or (key == .arrow_left and current_tab == 0)) { │ │499 + // remembered here as well as in build, which may not run between keys │ │500 + self.selected_tab = current_tab; │ │501 + root_focus.setFocus(search_box.getFocus().id); │ │502 + return; │ │503 + } │ │504 if (inp.moveTab(key, current_tab, self.tab_ids.count())) |new_tab| { │ │505 root_focus.setFocus(self.tab_ids.keys()[new_tab]); │ │506 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │507 + } │ │508 + │ │509 + // the typed query when enter should submit it: the box holds focus. the │ │510 + // caller owns the returned text. │ │511 + pub fn submittedText(self: *Header, allocator: std.mem.Allocator) !?[]u8 { │ │512 + const search_box = self.searchBox(); │ │513 + if (self.box.getFocus().child_id != search_box.getFocus().id) return null;│ │514 + return try search_box.text(allocator); │ │515 + } │ │516 + │ │517 + fn searchBox(self: *Header) *widget.SearchBox { │ │518 + return &self.box.children.values()[search_index].widget.search_box; │ │519 } │ │520 │ │521 pub fn clearGrid(self: *Header) void { │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────╮ │531 } │ │532 │ │533 pub fn getSelectedIndex(self: Header) ?usize { │ │352 - const index = self.currentTabIndex() orelse return null;│ │353 - return self.tab_ids.values()[index]; │ │534 + const tab = self.selectedTab() orelse return null; │ │535 + return self.tab_ids.values()[tab]; │ │536 + } │ │537 + │ │538 + // the focused tab, or the one focus last left behind │ │539 + fn selectedTab(self: Header) ?usize { │ │540 + return self.currentTabIndex() orelse self.selected_tab; │ │541 } │ │542 │ │543 fn currentTabIndex(self: Header) ?usize { │ ╰─────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │680 return if (supports_conflicts) Data.resolveRoute(identity, selected, picks) else null; │ │681 } │ │682 │ │496 - fn listLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, status: Status, tag: []const u8, id: []const u8) ![]const u8 { │ │497 - const route = listRoute(identity, status, tag, id) orelse return error.RouteTooLong; │ │683 + // a list link keeps the filters the page carries: the tag, and the │ │684 + // search query when one is set │ │685 + fn listLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, status: Status, tag: []const u8, id: []const u8, search: []const u8) ![]const u8 {│ │686 + var route = listRoute(identity, status, tag, id) orelse return error.RouteTooLong; │ │687 + if (search.len != 0) route = route.withSearch(search) orelse return error.RouteTooLong; │ │688 return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{try route.toUrl(page_arena)}); │ │689 } │ │690 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │501 - fn tagLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, status: Status, tag: []const u8) ![]const u8 { │ │691 + fn tagLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, status: Status, tag: []const u8, search: []const u8) ![]const u8 { │ │692 const encoded = try ui.urlEncodeRef(page_arena.allocator(), tag); │ │503 - return listLink(page_arena, identity, status, encoded, ""); │ │693 + return listLink(page_arena, identity, status, encoded, "", search); │ │694 } │ │695 │ │696 fn addToolButton(allocator: std.mem.Allocator, row: *wgt.Box(Widget), label: []const u8, bottom_label: []const u8, action: []const u8) !void {│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │813 var text_box = try wgt.TextBox.init(allocator, back_label, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); │ │814 errdefer text_box.deinit(allocator); │ │815 text_box.getFocus().mode = .all; │ │626 - text_box.getFocus().kind = .{ .custom = try listLink(self.session.page_arena, self.data.identity, entryStatus(entry), "", entry.id) }; │ │816 + text_box.getFocus().kind = .{ .custom = try listLink(self.session.page_arena, self.data.identity, entryStatus(entry), "", entry.id, self.data.search orelse "") };│ │817 try inner_box.children.put(allocator, text_box.getFocus().id, .{ .widget = .{ .text_box = text_box }, .rect = null, .min_size = null }); │ │818 self.title_id = text_box.getFocus().id; │ │819 } else { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │833 var tag_iter = Event.tagIterator(entry.record.event.tags); │ │834 while (tag_iter.next()) |tag| { │ │835 if (tag.len == 0) continue; │ │646 - try items.append(allocator, .{ .text = tag, .link = try tagLink(self.session.page_arena, self.data.identity, entryStatus(entry), tag) }); │ │836 + try items.append(allocator, .{ .text = tag, .link = try tagLink(self.session.page_arena, self.data.identity, entryStatus(entry), tag, self.data.search orelse "") });│ │837 } │ │838 if (items.items.len > 0) { │ │839 var tags = try TagFlow.init(allocator); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1516 return if (supports_conflicts) Data.resolveRoute(identity, selected, picks) else null; │ │1517 } │ │1518 │ │1329 - fn listLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, status: Status, tag: []const u8, id: []const u8) ![]const u8 { │ │1330 - const route = listRoute(identity, status, tag, id) orelse return error.RouteTooLong; │ │1519 + // a list link keeps the filters the page carries: the tag, and the │ │1520 + // search query when one is set │ │1521 + fn listLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, status: Status, tag: []const u8, id: []const u8, search: []const u8) ![]const u8 {│ │1522 + var route = listRoute(identity, status, tag, id) orelse return error.RouteTooLong; │ │1523 + if (search.len != 0) route = route.withSearch(search) orelse return error.RouteTooLong; │ │1524 return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{try route.toUrl(page_arena)}); │ │1525 } │ │1526 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1547 const route = draftsRoute(data.identity) orelse return error.RouteTooLong; │ │1548 return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{try route.toUrl(page_arena)}); │ │1549 } │ │1357 - return listLink(page_arena, data.identity, status_maybe orelse @enumFromInt(0), data.tag, id); │ │1550 + return listLink(page_arena, data.identity, status_maybe orelse @enumFromInt(0), data.tag, id, data.search orelse ""); │ │1551 } │ │1552 │ │1360 - fn tagLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, status: Status, tag: []const u8) ![]const u8 { │ │1553 + fn tagLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, status: Status, tag: []const u8, search: []const u8) ![]const u8 {│ │1554 const encoded = try ui.urlEncodeRef(page_arena.allocator(), tag); │ │1362 - return listLink(page_arena, identity, status, encoded, ""); │ │1555 + return listLink(page_arena, identity, status, encoded, "", search); │ │1556 } │ │1557 │ │1558 pub fn init(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !This { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1589 defer items.deinit(allocator); │ │1590 // when filtered, the first item clears the filter │ │1591 if (data.tag.len != 0) │ │1399 - try items.append(allocator, .{ .text = "✕", .link = try listLink(session.page_arena, data.identity, @enumFromInt(0), "", "") }); │ │1592 + try items.append(allocator, .{ .text = "✕", .link = try listLink(session.page_arena, data.identity, @enumFromInt(0), "", "", data.search orelse "") });│ │1593 for (data.tags) |tag| │ │1401 - try items.append(allocator, .{ .text = tag, .link = try tagLink(session.page_arena, data.identity, @enumFromInt(0), tag) }); │ │1594 + try items.append(allocator, .{ .text = tag, .link = try tagLink(session.page_arena, data.identity, @enumFromInt(0), tag, data.search orelse "") }); │ │1595 try tf.setItems(allocator, items.items); │ │1596 try stack.children.put(allocator, tf.getFocus().id, .{ .tag_flow = tf }); │ │1597 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯