╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────────────────╮ │src/ui/Repo/Discussions.zig│ ╰───────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │265 var header = try Header.init(allocator); │ │266 errdefer header.deinit(allocator); │ │267 const aa = session.page_arena.allocator(); │ │268 + const selected_index = View.viewIndex(data.view); │ │269 + const page_selected = std.meta.activeTag(session.data.current_page) == .repo_discussions;│ │270 │ │271 // recent discussions │ │272 { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │273 const route = ui.RoutablePage.repoDiscussionsRoute(data.identity, data.tag, "") orelse return error.RouteTooLong;│ │272 - const link = try std.fmt.allocPrint(aa, "ai:{s}", .{try route.toUrl(session.page_arena)}); │ │274 + const link = try ui.inPageTabLink(session, route, page_selected and selected_index == 0); │ │275 var label_buf: [64]u8 = undefined; │ │276 const label = try std.fmt.bufPrint(&label_buf, "recent ({d})", .{data.recent.count}); │ │277 try header.addTab(allocator, label, link, 0); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │280 // tags tab, labeled with the active tag filter │ │281 { │ │282 const tags_route = ui.RoutablePage.repoThreadTagsRoute(.discuss, data.identity, data.tag) orelse return error.RouteTooLong;│ │281 - const tags_link = try std.fmt.allocPrint(aa, "ai:{s}", .{try tags_route.toUrl(session.page_arena)}); │ │283 + const tags_link = try ui.inPageTabLink(session, tags_route, page_selected and selected_index == View.viewIndex(.tags)); │ │284 const label = if (data.tag.len == 0) "tags" else blk: { │ │285 const decoded = std.Uri.percentDecodeInPlace(try aa.dupe(u8, data.tag)); │ │286 break :blk try std.fmt.allocPrint(aa, "tags ({s})", .{decoded}); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │297 .remove => ui.RoutablePage.repoThreadRemoveRoute(.discuss, data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong,│ │298 else => ui.RoutablePage.repoThreadNewRoute(.discuss, data.identity) orelse return error.RouteTooLong, │ │299 }; │ │298 - const link = try std.fmt.allocPrint(aa, "ai:{s}", .{try route.toUrl(session.page_arena)}); │ │300 + const link = try ui.inPageTabLink(session, route, page_selected and selected_index == View.viewIndex(.new)); │ │301 const label: []const u8 = switch (data.view) { │ │302 .edit => "edit", │ │303 .new_comment => "reply", │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Events.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │422 self.clearGrid(); │ │423 if (self.header().getSelectedIndex()) |index| │ │424 self.contentStack().getFocus().child_id = self.contentStack().children.keys()[index]; │ │425 - const view = self.selectedView(); │ │426 - var selected_kind: ?evt.EventKind = null; │ │427 - var selected_id: []const u8 = ""; │ │428 - if (view == self.data.view) { │ │429 - if (self.data.selected) |selected| { │ │430 - selected_kind = selected.kind; │ │431 - selected_id = selected.id; │ │432 - } │ │433 - } │ │434 - if (ui.RoutablePage.repoEventsRoute(self.data.identity, view, selected_kind, selected_id)) |route| │ │435 - self.session.data.current_page = route; │ │425 try self.refreshDetail(allocator); │ │426 │ │438 - if (root_focus.grandchild_id) |id| if (self.box.getFocus().children.contains(id)) { │ │439 - if (self.selectedEventIndex()) |index| { │ │440 - const event = self.eventWindow().events[index]; │ │441 - if (ui.RoutablePage.repoEventsRoute(self.data.identity, view, event.kind, event.id)) |route| │ │442 - self.session.data.current_page = route; │ │443 - } │ │444 - }; │ │445 - │ │427 const list = self.listBox(); │ │428 for (list.children.keys(), list.children.values()) |id, *child| switch (child.widget) { │ │429 .text_box => |*text_box| text_box.options.border_style = if (list.getFocus().child_id == id) .single else .hidden,│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │585 var tab = try wgt.TextBox(ui.Widget).init(allocator, label, .{ .border_style = .hidden, .rounded_corners = true, .wrap_kind = .none }); │ │586 errdefer tab.deinit(allocator); │ │587 tab.getFocus().focusable = true; │ │607 - tab.getFocus().kind = .{ .custom = try std.fmt.allocPrint(session.page_arena.allocator(), "ai:{s}", .{try route.toUrl(session.page_arena)}) }; │ │588 + const selected = std.meta.activeTag(session.data.current_page) == .repo_events and data.view == view; │ │589 + tab.getFocus().kind = .{ .custom = try ui.inPageTabLink(session, route, selected) }; │ │590 tab_ids[i] = tab.getFocus().id; │ │591 try box.children.put(allocator, tab.getFocus().id, .{ .widget = .{ .text_box = tab }, .rect = null, .min_size = .{ .width = label.len + 2, .height = null } });│ │592 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────────╮ │src/ui/Repo/Files.zig│ ╰─────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │266 const link = if (entry.is_dir) │ │267 try dirLink(session.page_arena, data, path) │ │268 else │ │269 - try fileLink(session.page_arena, data, path, entry.loaded); │ │269 + try fileLink(session.page_arena, data, path, entry.loaded, lineNumber(entry.window_start));│ │270 try addRow(allocator, &list_box, label, link); │ │271 } │ │272 // select the file the route named, else prefer a README so its │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │444 │ │445 // swap the detail pane to the selected entry when the selection changes. │ │446 try self.refreshDetail(allocator); │ │447 - │ │448 - // mirror the selected file into the url so it updates as the selection │ │449 - // moves, but only while focus is inside this view (so landing on the tab │ │450 - // keeps the page's base route). │ │451 - if (root_focus.grandchild_id) |g| { │ │452 - if (self.box.getFocus().children.contains(g)) { │ │453 - var buf: [ui.RoutablePage.repo_route_max_len]u8 = undefined; │ │454 - var sel_path = self.data.dir; │ │455 - var sel_line: usize = 0; │ │456 - if (self.selectedEntry()) |entry| { │ │457 - if (!entry.is_dir and entry.loaded) { │ │458 - sel_path = if (self.data.dir.len == 0) │ │459 - entry.name │ │460 - else │ │461 - std.fmt.bufPrint(&buf, "{s}/{s}", .{ self.data.dir, entry.name }) catch self.data.dir; │ │462 - sel_line = lineNumber(entry.window_start); │ │463 - } │ │464 - } │ │465 - if (ui.RoutablePage.repoFilesRoute(self.data.identity, self.data.ref_or_oid, self.data.ref_or_oid_value, sel_path, sel_line)) |route|│ │466 - self.session.data.current_page = route; │ │467 - } │ │468 - } │ │447 │ │448 // the selected list row shows a border (the focused TextBox upgrades it │ │449 // to a double border itself); the rest stay borderless. │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │528 // the placeholder carries the file's "a:" link, so enter │ │529 // (or a click) here loads the file like on its list row. │ │530 const path = try childDir(self.session.page_arena.allocator(), self.data.dir, entry.name);│ │553 - const link = try fileLink(self.session.page_arena, self.data, path, false); │ │531 + const link = try fileLink(self.session.page_arena, self.data, path, false, 0); │ │532 try self.addContentBox(allocator, inner, "(press enter to load this file)", link); │ │533 } else if (entry.is_binary) { │ │534 try self.addContentBox(allocator, inner, "(binary file)", ""); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │812 // (crossPageLink ignores it, so a click selects the row in place and shows its │ │813 // contents); an unloaded file gets an "a:" link, so activating it reloads the │ │814 // page with that file selected and its content read. │ │837 -fn fileLink(page_arena: *std.heap.ArenaAllocator, data: *const Self, path: []const u8, loaded: bool) ![]const u8 { │ │838 - const route = ui.RoutablePage.repoFilesRoute(data.identity, data.ref_or_oid, data.ref_or_oid_value, path, 0) orelse return error.RouteTooLong; │ │815 +fn fileLink(page_arena: *std.heap.ArenaAllocator, data: *const Self, path: []const u8, loaded: bool, line: usize) ![]const u8 { │ │816 + const route = ui.RoutablePage.repoFilesRoute(data.identity, data.ref_or_oid, data.ref_or_oid_value, path, line) orelse return error.RouteTooLong;│ │817 const prefix = if (loaded) "ai:" else "a:"; │ │818 return std.fmt.allocPrint(page_arena.allocator(), "{s}{s}", .{ prefix, try route.toUrl(page_arena) }); │ │819 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯