╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Files.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
144 .text_box => |*tb| tb.options.border_style = if (box.getFocus().child_id == id) .single else .hidden, 145 else => {}, 146 } 147 } 148 // clear the incoming min_size so rows size to their content rather than 149 // stretching to fill the page height; max_size still bounds the scroll 150 // viewport so a long listing clips and scrolls. 151 try self.scroll.build(allocator, .{ 152 .min_size = .{ .width = null, .height = null }, 153 .max_size = constraint.max_size, 154 }, root_focus); 155 } 156 157 pub fn input(self: *View, allocator: std.mem.Allocator, key: inp.Key, root_focus: *Focus) !void { 158 _ = allocator; 159 // up/down move the selection. Enter and clicks on a link row are turned 160 // into navigation by the host (crossPageLink); the ".." row and the 161 // browser Back button (or Escape on the TUI) handle ascending. 162 switch (key) { 163 .arrow_down => try self.moveSelection(root_focus, 1), 164 .arrow_up => try self.moveSelection(root_focus, -1), 165 else => {}, 166 } 167 } 168 169 fn moveSelection(self: *View, root_focus: *Focus, delta: isize) !void { 170 const box = self.innerBox(); 171 const keys = box.children.keys(); 172 const cur_id = box.getFocus().child_id orelse return; 173 const cur = box.children.getIndex(cur_id) orelse return; 174 if (delta < 0 and cur == 0) return; 175 if (delta > 0 and cur + 1 >= keys.len) return; 176 const next = if (delta < 0) cur - 1 else cur + 1; 177 try root_focus.setFocus(keys[next]); 178 if (box.children.values()[next].rect) |rect| self.scroll.scrollToRect(rect); 179 } 180 181 pub fn clearGrid(self: *View) void { 182 self.scroll.clearGrid(); 183 } 184 185 pub fn getGrid(self: View) ?Grid { 186 return self.scroll.getGrid(); 187 } 188 189 pub fn getFocus(self: *View) *Focus { 190 return self.scroll.getFocus(); 191 } 192 193 pub fn getSelectedIndex(self: View) ?usize { 194 const box = &self.scroll.child.box; 195 const child_id = box.focus.child_id orelse return null; 196 return box.children.getIndex(child_id); 197 } 198 }; 199 200 // the "a:" link for the files route at `dir` within `identity` ("owner/name"). 201 fn dirLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, dir: []const u8) ![]const u8 { 202 const route = ui.RoutablePage.repoFilesRoute(identity, dir) orelse return error.RouteTooLong; 203 const url = try route.urlAlloc(page_arena); 204 return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{url}); 205 } 206 207 // `dir`/`name`, or just `name` at the root. 208 fn childDir(arena: std.mem.Allocator, dir: []const u8, name: []const u8) ![]const u8 { 209 return if (dir.len == 0) name else std.fmt.allocPrint(arena, "{s}/{s}", .{ dir, name }); 210 } 211 212 // `dir` with its last segment removed ("" when `dir` has a single segment). 213 fn parentDir(dir: []const u8) []const u8 { 214 const slash = std.mem.lastIndexOfScalar(u8, dir, '/') orelse return ""; 215 return dir[0..slash]; 216 } 217