╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────────╮ │src/ui/Repo/Files.zig│ ╰─────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │254 const link = if (entry.is_dir) try dirLink(session.page_arena, data, try childDir(aa, data.dir, entry.name)) else "";│ │255 try addRow(allocator, &list_box, label, link); │ │256 } │ │242 - // default the selection to the first row, but prefer a README in │ │243 - // this directory so its contents greet the visitor in the detail │ │244 - // pane. a non-root directory has a leading ".." row, so the entry │ │257 + // select the file the route named, else prefer a README so its │ │258 + // contents greet the visitor in the detail pane, else the first │ │259 + // row. a non-root directory has a leading ".." row, so the entry │ │260 // index is offset by one there. │ │261 if (list_box.children.count() > 0) { │ │262 const base: usize = if (data.dir.len != 0) 1 else 0; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │248 - const sel = if (readmeIndex(data.entries)) |i| i + base else 0; │ │263 + const entry_idx = selectedFileIndex(data) orelse readmeIndex(data.entries); │ │264 + const sel = if (entry_idx) |i| i + base else 0; │ │265 list_box.getFocus().child_id = list_box.children.keys()[sel]; │ │266 } │ │267 break :blk try wgt.Scroll(ui.Widget).init(allocator, .{ .box = list_box }, .{ .direction = .vert, .web_native = !session.is_terminal });│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │378 │ │379 // swap the detail pane to the selected entry when the selection changes. │ │380 try self.refreshDetail(allocator); │ │381 + │ │382 + // mirror the selected file into the url so it updates as the selection │ │383 + // moves, but only while focus is inside this view (so landing on the tab │ │384 + // keeps the page's base route). a file selection carries the file path; a │ │385 + // directory / ".." row (or no selection) stays at the directory. the path │ │386 + // is joined in a stack buffer since build runs every frame on the web. │ │387 + if (root_focus.grandchild_id) |g| { │ │388 + if (self.box.getFocus().children.contains(g)) { │ │389 + var buf: [ui.RoutablePage.repo_route_max_len]u8 = undefined; │ │390 + const sel_path = if (self.selectedEntry()) |entry| blk: { │ │391 + if (entry.is_dir) break :blk self.data.dir; │ │392 + break :blk if (self.data.dir.len == 0) │ │393 + entry.name │ │394 + else │ │395 + std.fmt.bufPrint(&buf, "{s}/{s}", .{ self.data.dir, entry.name }) catch self.data.dir; │ │396 + } else self.data.dir; │ │397 + if (ui.RoutablePage.repoFilesRoute(self.data.identity, self.data.ref_or_oid, self.data.ref_or_oid_value, sel_path)) |route|│ │398 + self.session.data.current_page = route; │ │399 + } │ │400 + } │ │401 │ │402 // the selected list row shows a border (the focused TextBox upgrades it │ │403 // to a double border itself); the rest stay borderless. │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │626 if (entry.is_dir) continue; │ │627 if (std.ascii.eqlIgnoreCase(entry.name, "README") or std.ascii.eqlIgnoreCase(entry.name, "README.md"))│ │628 return i; │ │629 + } │ │630 + return null; │ │631 +} │ │632 + │ │633 +// the index of the entry the route named (data.selected_file), or null if none. │ │634 +fn selectedFileIndex(data: *const Self) ?usize { │ │635 + const name = data.selected_file orelse return null; │ │636 + for (data.entries, 0..) |entry, i| { │ │637 + if (!entry.is_dir and std.mem.eql(u8, entry.name, name)) return i; │ │638 } │ │639 return null; │ │640 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │647 } │ │648 │ │649 // `dir`/`name`, or just `name` at the root. │ │605 -fn childDir(arena: std.mem.Allocator, dir: []const u8, name: []const u8) ![]const u8 { │ │650 +pub fn childDir(arena: std.mem.Allocator, dir: []const u8, name: []const u8) ![]const u8 { │ │651 return if (dir.len == 0) name else std.fmt.allocPrint(arena, "{s}/{s}", .{ dir, name });│ │652 } │ │653 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯