╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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 "";│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1151 const file_path = pathValue(segments.rest()) orelse return null; │ │1126 - return forkFilesRoute(identity, id, oid, file_path, line); │ │1152 + var route = forkFilesRoute(identity, id, oid, file_path, line) orelse return null; │ │1153 + if (params.values.get(.find)) |find| route.fork_files.find = Array(find_route_max_len).from(find) orelse return null;│ │1154 + return route; │ │1155 } │ │1156 if (std.mem.eql(u8, tab, commits_seg)) { │ │1157 params.scanPairs(&segments) catch return null; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1467 if (!params.only(&.{ .line, .patchrev })) return null; │ │1468 return repoPatchRevFilesRoute(pair, id, pathValue(segments.rest()) orelse return null, params.line() orelse return null);│ │1469 } │ │1442 - if (!params.only(&.{ .line, .branch, .tag, .object })) return null; │ │1470 + if (!params.only(&.{ .line, .branch, .tag, .object, .find })) return null; │ │1471 const line = params.line() orelse return null; │ │1472 const dir = pathValue(segments.rest()) orelse return null; │ │1445 - const ref = (params.ref() catch return null) orelse │ │1446 - return if (dir.len == 0) repoFilesRoute(pair, null, "", "", line) else null; │ │1447 - return repoFilesRoute(pair, ref.kind, ref.value, dir, line); │ │1473 + const find = params.values.get(.find) orelse ""; │ │1474 + const ref = params.ref() catch return null; │ │1475 + // a find route's `path` names the selected result, so unlike a │ │1476 + // directory path it doesn't need a ref beside it │ │1477 + var route = if (ref) |r| │ │1478 + repoFilesRoute(pair, r.kind, r.value, dir, line) orelse return null │ │1479 + else if (dir.len == 0 or find.len != 0) │ │1480 + repoFilesRoute(pair, null, "", "", line) orelse return null │ │1481 + else │ │1482 + return null; │ │1483 + if (find.len != 0) { │ │1484 + route.repo_files.find = Array(find_route_max_len).from(find) orelse return null; │ │1485 + route.repo_files.path = Array(repo_route_max_len).from(dir) orelse return null; │ │1486 + } │ │1487 + return route; │ │1488 } │ │1489 if (std.mem.eql(u8, tab, commits_seg)) { │ │1490 params.scanPairs(&segments) catch return null; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1530 a_f.ref_kind == b.repo_files.ref_kind and │ │1531 std.mem.eql(u8, a_f.ref_value.slice(), b.repo_files.ref_value.slice()) and │ │1532 std.mem.eql(u8, a_f.patchrev_id.slice(), b.repo_files.patchrev_id.slice()) and │ │1533 + std.mem.eql(u8, a_f.find.slice(), b.repo_files.find.slice()) and │ │1534 std.mem.eql(u8, a_f.path.slice(), b.repo_files.path.slice()) and │ │1535 a_f.line == b.repo_files.line, │ │1536 .repo_commits => |a_c| std.mem.eql(u8, a_c.name.slice(), b.repo_commits.name.slice()) and│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1584 .fork_files => |a_f| std.mem.eql(u8, a_f.fork.name.slice(), b.fork_files.fork.name.slice()) and │ │1585 std.mem.eql(u8, a_f.fork.id.slice(), b.fork_files.fork.id.slice()) and │ │1586 std.mem.eql(u8, a_f.oid.slice(), b.fork_files.oid.slice()) and │ │1587 + std.mem.eql(u8, a_f.find.slice(), b.fork_files.find.slice()) and │ │1588 std.mem.eql(u8, a_f.path.slice(), b.fork_files.path.slice()) and │ │1589 a_f.line == b.fork_files.line, │ │1590 .fork_commits => |a_c| std.mem.eql(u8, a_c.fork.name.slice(), b.fork_commits.fork.name.slice()) and│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Fork.zig│ ╰───────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │119 const files_line: usize = switch (route) { │ │120 .fork_files => |f| f.line, │ │121 else => 0, │ │122 + }; │ │123 + const files_find: []const u8 = switch (route) { │ │124 + .fork_files => |*f| f.find.slice(), │ │125 + else => "", │ │126 }; │ │127 const commits_content: ui.RoutablePage.RepoCommitsRoute.Content = switch (route) {│ │128 .fork_commits => |c| c.content, │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯