╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Files.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
810 811 // for the parent's "scroll up at the top jumps to the header" check. inside 812 // the detail pane up-arrow stays in the pane (scrolling); when the list holds 813 // focus, report its selection directly so up-arrow at the top row ascends. 814 pub fn getSelectedIndex(self: *View) ?usize { 815 if (self.detailActive()) return 1; 816 const lb = self.listBox(); 817 const cid = lb.getFocus().child_id orelse return null; 818 return lb.children.getIndex(cid); 819 } 820 }; 821 822 // join `lines` into one string, each prefixed with its 1-based file line number 823 // (right-aligned in a column wide enough for the last number, then a space), 824 // like the diffs view. `start` is the window's 0-based first line, so numbers 825 // continue across paginated windows. 826 fn numberedContent(arena: std.mem.Allocator, lines: []const []const u8, start: usize) ![]const u8 { 827 if (lines.len == 0) return ""; 828 const width = std.fmt.count("{d}", .{start + lines.len}); 829 const indent = try arena.alloc(u8, width); 830 @memset(indent, ' '); 831 832 var out: std.ArrayList(u8) = .empty; 833 for (lines, 0..) |line, i| { 834 if (i != 0) try out.append(arena, '\n'); 835 const num_str = try std.fmt.allocPrint(arena, "{d}", .{start + i + 1}); 836 try out.appendSlice(arena, indent[0 .. width - num_str.len]); 837 try out.appendSlice(arena, num_str); 838 try out.append(arena, ' '); 839 try out.appendSlice(arena, line); 840 } 841 return out.toOwnedSlice(arena); 842 } 843 844 // the index of the "README"/"README.md" file entry (case-insensitive), or null 845 // if none. used to pick the default selection at the repo root. 846 fn readmeIndex(entries: []const Entry) ?usize { 847 for (entries, 0..) |entry, i| { 848 if (entry.is_dir) continue; 849 if (std.ascii.eqlIgnoreCase(entry.name, "README") or std.ascii.eqlIgnoreCase(entry.name, "README.md")) 850 return i; 851 } 852 return null; 853 } 854 855 // the index of the entry the route named (data.selected_file), or null if none. 856 fn selectedFileIndex(data: *const Self) ?usize { 857 const name = data.selected_file orelse return null; 858 for (data.entries, 0..) |entry, i| { 859 if (!entry.is_dir and std.mem.eql(u8, entry.name, name)) return i; 860 } 861 return null; 862 } 863 864 // an "a:" link to the files route at `path`, pinned to the listing's ref, so 865 // following it navigates to that directory's listing. 866 fn dirLink(page_arena: *std.heap.ArenaAllocator, data: *const Self, path: []const u8) ![]const u8 { 867 const route = ui.RoutablePage.repoFilesRoute(data.identity, data.ref_or_oid, data.ref_or_oid_value, path, 0) orelse return error.RouteTooLong; 868 return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{try route.urlAlloc(page_arena)}); 869 } 870 871 // an "ai:" link to the file's route: crossPageLink ignores it, so a wasm click 872 // selects the row in place and shows its contents in the detail pane. 873 fn fileLink(page_arena: *std.heap.ArenaAllocator, data: *const Self, path: []const u8) ![]const u8 { 874 const route = ui.RoutablePage.repoFilesRoute(data.identity, data.ref_or_oid, data.ref_or_oid_value, path, 0) orelse return error.RouteTooLong; 875 return std.fmt.allocPrint(page_arena.allocator(), "ai:{s}", .{try route.urlAlloc(page_arena)}); 876 } 877 878 // `dir`/`name`, or just `name` at the root. 879 pub fn childDir(arena: std.mem.Allocator, dir: []const u8, name: []const u8) ![]const u8 { 880 return if (dir.len == 0) name else std.fmt.allocPrint(arena, "{s}/{s}", .{ dir, name }); 881 } 882 883 // `dir` with its last segment removed ("" when `dir` has a single segment). 884 fn parentDir(dir: []const u8) []const u8 { 885 const slash = std.mem.lastIndexOfScalar(u8, dir, '/') orelse return ""; 886 return dir[0..slash]; 887 } 888