╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │193 │ │194 // the "key:value" segments of a url tail, collected in any order │ │195 const Params = struct { │ │196 - const ParamKey = enum { start, line, branch, tag, object, base, patchrev, theirs }; │ │196 + const ParamKey = enum { start, line, branch, tag, object, base, patchrev, theirs, find };│ │197 │ │198 // a branch:/tag:/object: param; the value stays url-encoded │ │199 const RefParam = struct { kind: RefOrOid, value: []const u8 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────╮ │271 const tag_filter_seg = @tagName(Params.ParamKey.tag) ++ ":";│ │272 const start_seg = @tagName(Params.ParamKey.start) ++ ":"; │ │273 const line_seg = @tagName(Params.ParamKey.line) ++ ":"; │ │274 + const find_seg = @tagName(Params.ParamKey.find) ++ ":"; │ │275 const base_seg = @tagName(Params.ParamKey.base) ++ ":"; │ │276 const theirs_seg = @tagName(Params.ParamKey.theirs) ++ ":"; │ │277 const issue_seg = "issue:"; │ ╰─────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │305 │ │306 // caps a url-encoded ref name in a route (a longer name doesn't route).│ │307 pub const ref_route_max_len = 512; │ │308 + │ │309 + // caps the files tab's url-encoded search term. │ │310 + pub const find_route_max_len = 100 * 3; │ │311 │ │312 // the identity stored by every repo route. Local routes elide it. │ │313 pub const RepoIdentity = struct { │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │341 ref_kind: ?RefOrOid = null, │ │342 ref_value: Array(ref_route_max_len) = .{}, │ │343 patchrev_id: Array(evt.event_id_size * 2) = .{}, │ │344 + // the url-encoded search term ("" = the plain directory listing). with│ │345 + // one set, `path` names the selected result rather than a directory. │ │346 + find: Array(find_route_max_len) = .{}, │ │347 path: Array(repo_route_max_len) = .{}, │ │348 // the selected file's first visible line (1-based; 0 = unset). │ │349 line: usize = 0, │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────╮ │395 pub const ForkFilesRoute = struct { │ │396 fork: ForkRoute, │ │397 oid: Array(ref_route_max_len) = .{}, │ │398 + find: Array(find_route_max_len) = .{},│ │399 path: Array(repo_route_max_len) = .{},│ │400 line: usize = 0, │ │401 }; │ ╰───────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │528 } }; │ │529 } │ │530 │ │531 + // carry a search term on a files route, url-encoding it into the route. │ │532 + // null when it doesn't fit. │ │533 + pub fn withFind(self: RoutablePage, search: []const u8) ?RoutablePage { │ │534 + var find: Array(find_route_max_len) = .{}; │ │535 + var writer: std.Io.Writer = .fixed(&find.bytes); │ │536 + std.Uri.Component.percentEncode(&writer, search, isUnreserved) catch return null; │ │537 + find.len = @intCast(writer.buffered().len); │ │538 + var route = self; │ │539 + switch (route) { │ │540 + .repo_files => |*f| f.find = find, │ │541 + .fork_files => |*f| f.find = find, │ │542 + else => return null, │ │543 + } │ │544 + return route; │ │545 + } │ │546 + │ │547 pub fn repoPatchRevFilesRoute(identity: []const u8, id: []const u8, path: []const u8, line: usize) ?RoutablePage {│ │548 _ = evt.parseEventId(id) catch return null; │ │549 return .{ .repo_files = .{ │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │918 .user_auth => |name| try std.fmt.allocPrint(arena.allocator(), user_segment ++ "{s}/auth", .{name.slice()}), │ │919 .repo_files => |f| blk: { │ │920 const prefix = try repoUrlPrefix(arena, f.name.slice()); │ │897 - if (f.ref_kind == null and f.patchrev_id.len == 0 and f.line == 0) break :blk if (prefix.len == 0) "/" else prefix; │ │921 + if (f.ref_kind == null and f.patchrev_id.len == 0 and f.find.len == 0 and f.line == 0) break :blk if (prefix.len == 0) "/" else prefix;│ │922 var out: std.Io.Writer.Allocating = .init(arena.allocator()); │ │923 try out.writer.print("{s}/" ++ files_seg, .{prefix}); │ │924 if (f.ref_kind) |kind| if (f.ref_value.len != 0) try out.writer.print("/{s}:{s}", .{ @tagName(kind), f.ref_value.slice() }); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │925 if (f.patchrev_id.len != 0) try out.writer.print("/patchrev:{s}", .{f.patchrev_id.slice()});│ │926 + if (f.find.len != 0) try out.writer.print("/" ++ find_seg ++ "{s}", .{f.find.slice()}); │ │927 if (f.line != 0) try out.writer.print("/" ++ line_seg ++ "{d}", .{f.line}); │ │928 if (f.path.len != 0) try out.writer.print("/" ++ path_seg ++ "{s}", .{f.path.slice()}); │ │929 break :blk out.written(); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1078 var out: std.Io.Writer.Allocating = .init(arena.allocator()); │ │1079 try out.writer.print(fork_segment ++ "{s}/" ++ patch_seg ++ "{s}/" ++ files_seg, .{ f.fork.name.slice(), f.fork.id.slice() });│ │1080 if (f.oid.len != 0) try out.writer.print("/object:{s}", .{f.oid.slice()}); │ │1081 + if (f.find.len != 0) try out.writer.print("/" ++ find_seg ++ "{s}", .{f.find.slice()}); │ │1082 if (f.line != 0) try out.writer.print("/" ++ line_seg ++ "{d}", .{f.line}); │ │1083 if (f.path.len != 0) try out.writer.print("/" ++ path_seg ++ "{s}", .{f.path.slice()}); │ │1084 break :blk out.written(); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1144 } │ │1145 if (std.mem.eql(u8, tab, files_seg)) { │ │1146 params.scanPairs(&segments) catch return null; │ │1121 - if (!params.only(&.{ .line, .object })) return null; │ │1147 + if (!params.only(&.{ .line, .object, .find })) return null; │ │1148 const line = params.line() orelse return null; │ │1149 const ref = params.ref() catch return null; │ │1150 const oid = if (ref) |value| if (value.kind == .object) value.value else return null else "";│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯