╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Files.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
187 .text_box => |*tb| tb.options.border_style = if (box.getFocus().child_id == id) .single else .hidden, 188 else => {}, 189 } 190 } 191 // clear the incoming min_size so rows size to their content rather than 192 // stretching to fill the page height; max_size still bounds the scroll 193 // viewport so a long listing clips and scrolls. 194 try self.scroll.build(allocator, .{ 195 .min_size = .{ .width = null, .height = null }, 196 .max_size = constraint.max_size, 197 }, root_focus); 198 } 199 200 pub fn input(self: *View, allocator: std.mem.Allocator, key: inp.Key, root_focus: *Focus) !void { 201 _ = allocator; 202 // up/down move the selection. Enter and clicks on a link row are turned 203 // into navigation by the host (crossPageLink); the ".." row and the 204 // browser Back button (or Escape on the TUI) handle ascending. 205 switch (key) { 206 .arrow_down => try self.moveSelection(root_focus, 1), 207 .arrow_up => try self.moveSelection(root_focus, -1), 208 else => {}, 209 } 210 } 211 212 fn moveSelection(self: *View, root_focus: *Focus, delta: isize) !void { 213 const box = self.innerBox(); 214 const keys = box.children.keys(); 215 const cur_id = box.getFocus().child_id orelse return; 216 const cur = box.children.getIndex(cur_id) orelse return; 217 if (delta < 0 and cur == 0) return; 218 if (delta > 0 and cur + 1 >= keys.len) return; 219 const next = if (delta < 0) cur - 1 else cur + 1; 220 try root_focus.setFocus(keys[next]); 221 if (box.children.values()[next].rect) |rect| self.scroll.scrollToRect(rect); 222 } 223 224 pub fn clearGrid(self: *View) void { 225 self.scroll.clearGrid(); 226 } 227 228 pub fn getGrid(self: View) ?Grid { 229 return self.scroll.getGrid(); 230 } 231 232 pub fn getFocus(self: *View) *Focus { 233 return self.scroll.getFocus(); 234 } 235 236 pub fn getSelectedIndex(self: View) ?usize { 237 const box = &self.scroll.child.box; 238 const child_id = box.focus.child_id orelse return null; 239 return box.children.getIndex(child_id); 240 } 241 }; 242 243 // the "a:" link for the files route at `dir`, pinned to the listing's ref. 244 fn dirLink(page_arena: *std.heap.ArenaAllocator, data: *const Self, dir: []const u8) ![]const u8 { 245 const route = ui.RoutablePage.repoFilesRoute(data.identity, data.ref_or_oid, data.ref_or_oid_value, dir) orelse return error.RouteTooLong; 246 const url = try route.urlAlloc(page_arena); 247 return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{url}); 248 } 249 250 // `dir`/`name`, or just `name` at the root. 251 fn childDir(arena: std.mem.Allocator, dir: []const u8, name: []const u8) ![]const u8 { 252 return if (dir.len == 0) name else std.fmt.allocPrint(arena, "{s}/{s}", .{ dir, name }); 253 } 254 255 // `dir` with its last segment removed ("" when `dir` has a single segment). 256 fn parentDir(dir: []const u8) []const u8 { 257 const slash = std.mem.lastIndexOfScalar(u8, dir, '/') orelse return ""; 258 return dir[0..slash]; 259 } 260