╭ changed ──────╮ │src/test/ui.zig│ ╰───────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │25 }; │ │26 │ │27 var session = ui.Session{ .arena = &arena, .page_arena = &arena, .is_terminal = true }; │ │28 - session.data.current_page = ui.RoutablePage.repoCommitsRoute(identity, .object, oid0, 0).?; │ │28 + session.data.current_page = ui.RoutablePage.repoCommitsRoute(identity, .object, oid0, 0, "").?;│ │29 │ │30 var view = try Commits.View.init(allocator, &data, &session); │ │31 defer view.deinit(allocator); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │56 const RP = ui.RoutablePage; │ │57 │ │58 // the route layer holds the value already url-encoded ("feature%2Ffoo"). │ │59 - const route = RP.repoCommitsRoute("alice/ziglings", .branch, "feature%2Ffoo", 0).?; │ │59 + const route = RP.repoCommitsRoute("alice/ziglings", .branch, "feature%2Ffoo", 0, "").?; │ │60 const url = try route.toUrl(&arena); │ │61 try std.testing.expectEqualStrings("/repo/alice/ziglings/commits/branch:feature%2Ffoo", url);│ │62 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │68 try std.testing.expectEqualStrings("feature%2Ffoo", parsed_route.value.slice());│ │69 } │ │70 │ │71 + │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │179 // the `Params` key spellings the url emitters use │ │180 const tag_filter_seg = @tagName(Params.ParamKey.tag) ++ ":"; │ │181 const start_seg = @tagName(Params.ParamKey.start) ++ ":"; │ │182 - // marks a files route's trailing path: the value is the raw remainder of│ │183 - // the url, so it must come last │ │182 + // marks a files or commits route's trailing path: the value is the raw │ │183 + // remainder of the url, so it must come last │ │184 const path_seg = "path:"; │ │185 │ │186 // a repo route is "username/reponame", optionally followed by the files │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │242 ref_or_oid: ?RefOrOid = null, │ │243 value: Array(ref_route_max_len) = .{}, │ │244 start: usize = 0, │ │245 + // the file the diff pane is filtered to ("" = every file). │ │246 + path: Array(repo_route_max_len) = .{}, │ │247 }; │ │248 │ │249 // build a `.repo_files` route (a null ref_kind = the bare default-branch│ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │262 │ │263 // build a `.repo_commits` route (a null ref_or_oid = the default branch). │ │264 // always carries the `commits` marker so the bare route doesn't collide │ │263 - // with the files root. │ │264 - pub fn repoCommitsRoute(identity: []const u8, ref_or_oid: ?RefOrOid, value: []const u8, start: usize) ?RoutablePage { │ │265 - if (ref_or_oid == null and value.len != 0) return null; │ │265 + // with the files root. a non-empty `path` filters the diff pane to that │ │266 + // file and requires a ref. │ │267 + pub fn repoCommitsRoute(identity: []const u8, ref_or_oid: ?RefOrOid, value: []const u8, start: usize, path: []const u8) ?RoutablePage {│ │268 + if (ref_or_oid == null and (value.len != 0 or path.len != 0)) return null; │ │269 return .{ .repo_commits = .{ │ │270 .name = Array(repo_identity_max_len).from(identity) orelse return null, │ │271 .ref_or_oid = ref_or_oid, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │272 .value = Array(ref_route_max_len).from(value) orelse return null,│ │273 .start = start, │ │274 + .path = Array(repo_route_max_len).from(path) orelse return null, │ │275 } }; │ │276 } │ │277 │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │370 try out.writer.print("{s}/" ++ commits_seg, .{prefix}); │ │371 if (c.ref_or_oid) |kind| if (c.value.len != 0) try out.writer.print("/{s}:{s}", .{ @tagName(kind), c.value.slice() });│ │372 if (c.start != 0) try out.writer.print("/" ++ start_seg ++ "{d}", .{c.start}); │ │373 + if (c.path.len != 0) try out.writer.print("/" ++ path_seg ++ "{s}", .{c.path.slice()}); │ │374 break :blk out.written(); │ │375 }, │ │376 .repo_refs => |r| blk: { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │511 return repoFilesRoute(pair, ref.kind, ref.value, dir, start); │ │512 } │ │513 if (std.mem.eql(u8, tab, commits_seg)) { │ │509 - const word = params.scanTail(&segments) catch return null; │ │510 - if (word != null or !params.only(ref_tab_keys)) return null; │ │514 + params.scanPairs(&segments) catch return null; │ │515 + if (!params.only(ref_tab_keys)) return null; │ │516 const start = params.start() orelse return null; │ │512 - const ref = (params.ref() catch return null) orelse return repoCommitsRoute(pair, null, "", start);│ │513 - return repoCommitsRoute(pair, ref.kind, ref.value, start); │ │517 + const file = pathValue(segments.rest()) orelse return null; │ │518 + const ref = (params.ref() catch return null) orelse │ │519 + return if (file.len == 0) repoCommitsRoute(pair, null, "", start, "") else null; │ │520 + return repoCommitsRoute(pair, ref.kind, ref.value, start, file); │ │521 } │ │522 return null; // unknown sub-path │ │523 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │538 .repo_commits => |a_c| std.mem.eql(u8, a_c.name.slice(), b.repo_commits.name.slice()) and │ │539 a_c.ref_or_oid == b.repo_commits.ref_or_oid and │ │540 std.mem.eql(u8, a_c.value.slice(), b.repo_commits.value.slice()) and │ │534 - a_c.start == b.repo_commits.start, │ │541 + a_c.start == b.repo_commits.start and │ │542 + std.mem.eql(u8, a_c.path.slice(), b.repo_commits.path.slice()), │ │543 .repo_refs => |a_r| std.mem.eql(u8, a_r.name.slice(), b.repo_refs.name.slice()) and a_r.kind == b.repo_refs.kind and std.mem.eql(u8, a_r.from.slice(), b.repo_refs.from.slice()),│ │544 .repo_issues => |a_i| std.mem.eql(u8, a_i.name.slice(), b.repo_issues.name.slice()) and │ │545 std.mem.eql(u8, a_i.tag.slice(), b.repo_issues.tag.slice()) and │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯