╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │548 .fork_files => |*f| f.find = find, │ │549 + else => return null, │ │550 + } │ │551 + return route; │ │552 + } │ │553 + │ │554 + // carry a search query on a commits route, url-encoding it into the route. │ │555 + pub fn withSearch(self: RoutablePage, query: []const u8) ?RoutablePage { │ │556 + const search = encodeParam(search_route_max_len, query) orelse return null;│ │557 + var route = self; │ │558 + switch (route) { │ │559 + .repo_commits => |*c| c.search = search, │ │560 else => return null, │ │561 } │ │562 return route; │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │563 } │ │564 │ │565 + // root a commits route's list at `oid` rather than the ref's own commit. │ │566 + pub fn withFrom(self: RoutablePage, oid: []const u8) ?RoutablePage { │ │567 + var route = self; │ │568 + switch (route) { │ │569 + .repo_commits => |*c| c.from = Array(ref_route_max_len).from(oid) orelse return null, │ │570 + else => return null, │ │571 + } │ │572 + return route; │ │573 + } │ │574 + │ │575 + fn encodeParam(comptime max_len: usize, value: []const u8) ?Array(max_len) { │ │576 + var encoded: Array(max_len) = .{}; │ │577 + var writer: std.Io.Writer = .fixed(&encoded.bytes); │ │578 + std.Uri.Component.percentEncode(&writer, value, isUnreserved) catch return null; │ │579 + encoded.len = @intCast(writer.buffered().len); │ │580 + return encoded; │ │581 + } │ │582 + │ │583 pub fn repoPatchRevFilesRoute(identity: []const u8, id: []const u8, path: []const u8, line: usize) ?RoutablePage {│ │584 _ = evt.parseEventId(id) catch return null; │ │585 return .{ .repo_files = .{ │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │970 try out.writer.print("{s}/" ++ commits_seg, .{prefix}); │ │971 if (c.ref_or_oid) |kind| if (c.value.len != 0) try out.writer.print("/{s}:{s}", .{ @tagName(kind), c.value.slice() });│ │972 if (c.base_oid.len != 0) try out.writer.print("/" ++ base_seg ++ "{s}", .{c.base_oid.slice()}); │ │973 + if (c.search.len != 0) try out.writer.print("/" ++ search_seg ++ "{s}", .{c.search.slice()}); │ │974 + if (c.from.len != 0) try out.writer.print("/" ++ from_seg ++ "{s}", .{c.from.slice()}); │ │975 switch (c.content) { │ │976 .diff => |d| { │ │977 if (d.start != 0) try out.writer.print("/" ++ start_seg ++ "{d}", .{d.start}); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1526 } │ │1527 if (std.mem.eql(u8, tab, commits_seg)) { │ │1528 params.scanPairs(&segments) catch return null; │ │1491 - if (!params.only(&.{ .start, .branch, .tag, .object, .base })) return null; │ │1529 + if (!params.only(&.{ .start, .branch, .tag, .object, .base, .search, .from })) return null; │ │1530 const base_oid = params.values.get(.base) orelse ""; │ │1531 + const search = params.values.get(.search) orelse ""; │ │1532 + const from = params.values.get(.from) orelse ""; │ │1533 const start = params.start() orelse return null; │ │1534 const ref = (params.ref() catch return null) orelse { │ │1535 + if (search.len != 0 or from.len != 0) return null; │ │1536 const file = pathValue(segments.rest()) orelse return null; │ │1537 return if (file.len == 0) repoCommitsRoute(pair, null, "", start, "", base_oid) else null;│ │1538 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1539 + // the index covers branch and tag tips, so only they can be │ │1540 + // searched or paged by commit; an object roots the list itself │ │1541 + if ((search.len != 0 or from.len != 0) and ref.kind == .object) return null; │ │1542 // the message page has no diff window or file filter │ │1499 - if (std.mem.eql(u8, segments.rest(), message_seg)) │ │1500 - return if (start == 0) repoCommitMessageRoute(pair, ref.kind, ref.value, base_oid) else null; │ │1501 - const file = pathValue(segments.rest()) orelse return null; │ │1502 - return repoCommitsRoute(pair, ref.kind, ref.value, start, file, base_oid); │ │1543 + var route = if (std.mem.eql(u8, segments.rest(), message_seg)) │ │1544 + (if (start == 0) repoCommitMessageRoute(pair, ref.kind, ref.value, base_oid) else null) orelse return null │ │1545 + else │ │1546 + repoCommitsRoute(pair, ref.kind, ref.value, start, pathValue(segments.rest()) orelse return null, base_oid) orelse return null;│ │1547 + if (search.len != 0) route.repo_commits.search = Array(search_route_max_len).from(search) orelse return null; │ │1548 + if (from.len != 0) route.repo_commits.from = Array(ref_route_max_len).from(from) orelse return null; │ │1549 + return route; │ │1550 } │ │1551 if (std.mem.eql(u8, tab, "diff")) { │ │1552 params.scanPairs(&segments) catch return null; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1584 a_c.ref_or_oid == b.repo_commits.ref_or_oid and │ │1585 std.mem.eql(u8, a_c.value.slice(), b.repo_commits.value.slice()) and │ │1586 std.mem.eql(u8, a_c.base_oid.slice(), b.repo_commits.base_oid.slice()) and │ │1587 + std.mem.eql(u8, a_c.search.slice(), b.repo_commits.search.slice()) and │ │1588 + std.mem.eql(u8, a_c.from.slice(), b.repo_commits.from.slice()) and │ │1589 switch (a_c.content) { │ │1590 .diff => |a_d| switch (b.repo_commits.content) { │ │1591 .diff => |b_d| a_d.start == b_d.start and std.mem.eql(u8, a_d.path.slice(), b_d.path.slice()),│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Fork.zig│ ╰───────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │133 .id = &id_hex, │ │134 } }; │ │135 const files = try Files.init(.xit, .{}, arena, &fork_repo, io, arena.child_allocator, location, requested_ref, requested_value, files_path, files_line, files_find); │ │136 - var commits = try Commits.init(.xit, .{}, arena, &fork_repo, io, arena.child_allocator, haxy_moment, location, requested_ref, requested_value, commits_content, &commits_base_oid); │ │136 + var commits = try Commits.init(.xit, .{}, arena, &fork_repo, io, arena.child_allocator, haxy_moment, location, requested_ref, requested_value, commits_content, &commits_base_oid, "", "");│ │137 commits.commit_count = if (newest_revision) |revision| revision.record.commit_count else 0; │ │138 const diff_start: usize = switch (route) { │ │139 .fork_diff => |d| d.start, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │250 if (stack.getSelected()) |selected| switch (selected.*) { │ │251 .repo_patch_detail => |*view| if (view.focusFirst(root_focus)) return,│ │252 .repo_files => |*view| if (view.focusHeader(root_focus)) return, │ │253 - .repo_commits => |*view| if (view.focusCloneUrl(root_focus)) return, │ │253 + .repo_commits => |*view| if (view.focusHeader(root_focus)) return, │ │254 else => {}, │ │255 }; │ │256 next_index = stack_index; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │ 99 .repo_commits => |*c| c.base_oid.slice(), │ │100 else => "", │ │101 }; │ │102 + const commits_search: []const u8 = switch (route) { │ │103 + .repo_commits => |*c| c.search.slice(), │ │104 + else => "", │ │105 + }; │ │106 + const commits_from: []const u8 = switch (route) { │ │107 + .repo_commits => |*c| c.from.slice(), │ │108 + else => "", │ │109 + }; │ │110 // the refs tab windows one column at a time: `refs_from` (a url-encoded │ │111 // ref name) roots `refs_kind`'s column, the other stays at its first window.│ │112 const refs_kind: ui.RoutablePage.RefKind = switch (route) { │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │267 else if (patchrev_id.len != 0) diff: { │ │268 const diff_route = ui.RoutablePage.repoPatchRevDiffRoute(repo_identity.identity, patchrev_id, 0, "") orelse return error.RouteTooLong; │ │269 break :diff .{ .diff = try Diff.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, diff_route.repo_diff) }; │ │262 - } else .{ .commits = try Commits.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, session.haxy_moment, location, requested_ref_or_oid, requested_ref_value, commits_content, commits_base_oid) }; │ │270 + } else .{ .commits = try Commits.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, session.haxy_moment, location, requested_ref_or_oid, requested_ref_value, commits_content, commits_base_oid, commits_search, commits_from) };│ │271 const target_branch = if (files_data.ref_or_oid == .branch) files_data.ref_or_oid_value else ""; │ │272 break :blk .{ │ │273 files_data, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯