╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ search_thread.zig ────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
50 aa: std.mem.Allocator, 51 query_text: []const u8, 52 ) !srch.Query(DB) { 53 const cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, indexKey(kind))) orelse { 54 // no index means no words match, which one empty term expresses 55 var results: srch.Query(DB) = .{ .terms = .empty }; 56 try results.terms.append(aa, .{ .postings = &.{} }); 57 return results; 58 }; 59 return srch.Query(DB).init(try DB.SortedMap(.read_only).init(cursor), aa, query_text); 60 } 61 62 pub const label_prefix = "label:"; 63 pub const assignee_prefix = "assignee:"; 64 65 // a thread search split into its filter terms and the words left for the index 66 pub const Filters = struct { 67 labels: []const []const u8, 68 assignees: []const []const u8, 69 // the remaining terms joined by single spaces, "" when none 70 words: []const u8, 71 }; 72 73 // split `search` on whitespace into label and assignee terms and plain words. 74 // a prefix with no value is dropped. 75 pub fn parseFilters(aa: std.mem.Allocator, search: []const u8) !Filters { 76 var labels: std.ArrayList([]const u8) = .empty; 77 var assignees: std.ArrayList([]const u8) = .empty; 78 var words: std.ArrayList([]const u8) = .empty; 79 var iter = std.mem.tokenizeAny(u8, search, &std.ascii.whitespace); 80 while (iter.next()) |term| { 81 if (std.mem.startsWith(u8, term, label_prefix)) { 82 if (term.len > label_prefix.len) try labels.append(aa, term[label_prefix.len..]); 83 } else if (std.mem.startsWith(u8, term, assignee_prefix)) { 84 if (term.len > assignee_prefix.len) try assignees.append(aa, term[assignee_prefix.len..]); 85 } else try words.append(aa, term); 86 } 87 return .{ .labels = labels.items, .assignees = assignees.items, .words = try std.mem.join(aa, " ", words.items) }; 88 } 89 90 // `search` with its `prefix` terms replaced by one for `value`, put first 91 pub fn withFilter(aa: std.mem.Allocator, search: []const u8, prefix: []const u8, value: []const u8) ![]const u8 { 92 var terms: std.ArrayList([]const u8) = .empty; 93 try terms.append(aa, try std.mem.concat(aa, u8, &.{ prefix, value })); 94 var iter = std.mem.tokenizeAny(u8, search, &std.ascii.whitespace); 95 while (iter.next()) |term| { 96 if (!std.mem.startsWith(u8, term, prefix)) try terms.append(aa, term); 97 } 98 return std.mem.join(aa, " ", terms.items); 99 } 100 101 // the text a thread is indexed under, none once it is removed: the title and 102 // labels first, so the tokenizer's caps can only drop the tail of a long 103 // description. 104 fn text(aa: std.mem.Allocator, record: anytype) ![]const u8 { 105 if (record.removed) return ""; 106 return std.mem.concat(aa, u8, &.{ record.event.title, " ", record.event.labels, " ", record.event.description }); 107 } 108