╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Issues.zig ───────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
497 const inner = self.detailInner(); 498 if (inner.children.count() == 0) return null; 499 return switch (inner.children.values()[tags_child_index].widget) { 500 .tag_flow => |*tf| tf, 501 else => null, 502 }; 503 } 504 505 fn tagsFocused(self: *View) bool { 506 const inner = self.detailInner(); 507 const cid = inner.getFocus().child_id orelse return false; 508 return inner.children.getIndex(cid) == tags_child_index and self.tagFlow() != null; 509 } 510 511 fn focusTag(self: *View, tf: *ui.TagFlow, root_focus: *Focus, index: usize) void { 512 root_focus.setFocus(tf.text_boxes.items[index].getFocus().id); 513 // keep the tag visible on the terminal: its rect offset by the flow's 514 // position in the pane. 515 if (self.session.is_terminal and index < tf.rects.items.len) { 516 if (self.detailInner().children.values()[tags_child_index].rect) |flow_rect| { 517 var rect = tf.rects.items[index]; 518 rect.x += flow_rect.x; 519 rect.y += flow_rect.y; 520 self.detailScroll().scrollToRect(rect); 521 } 522 } 523 } 524 525 fn focusTags(self: *View, root_focus: *Focus) !void { 526 const tf = self.tagFlow() orelse return; 527 if (tf.text_boxes.items.len == 0) return; 528 const cid = tf.focus.child_id orelse tf.text_boxes.items[0].getFocus().id; 529 const index = tf.indexOfFocusId(cid) orelse 0; 530 self.focusTag(tf, root_focus, index); 531 } 532 533 fn focusDescription(self: *View, root_focus: *Focus) !void { 534 const inner = self.detailInner(); 535 const index: usize = if (self.tagFlow() != null) 1 else 0; 536 if (inner.children.count() <= index) return; 537 root_focus.setFocus(inner.children.keys()[index]); 538 } 539 540 // enter the detail pane. an empty pane (no issues) can't be entered. 541 fn focusDetail(self: *View, root_focus: *Focus) !void { 542 if (self.detailInner().children.count() == 0) return; 543 root_focus.setFocus(self.detailOuter().getFocus().id); 544 } 545 546 // return to the list. 547 fn focusList(self: *View, root_focus: *Focus) !void { 548 root_focus.setFocus(self.listScroll().getFocus().id); 549 } 550 551 pub fn clearGrid(self: *View) void { 552 self.box.clearGrid(); 553 } 554 555 pub fn getGrid(self: View) ?Grid { 556 return self.box.getGrid(); 557 } 558 559 pub fn getFocus(self: *View) *Focus { 560 return self.box.getFocus(); 561 } 562 563 // for the parent's "scroll up at the top jumps to the header" check. when 564 // the list holds focus, report its selected row directly. 565 pub fn getSelectedIndex(self: *View) ?usize { 566 if (self.detailActive()) { 567 return if (self.detailScroll().y == 0 and self.paneTopFocused()) 0 else 1; 568 } 569 const lb = self.listBox(); 570 const cid = lb.getFocus().child_id orelse return null; 571 return lb.children.getIndex(cid); 572 } 573 574 // whether the focused element is the detail pane's topmost one, so up has 575 // nowhere further to go within the pane. 576 fn paneTopFocused(self: *View) bool { 577 const tf = self.tagFlow() orelse return true; 578 if (!self.tagsFocused()) return false; 579 const cid = tf.focus.child_id orelse return true; 580 const cur = tf.indexOfFocusId(cid) orelse return true; 581 return tf.rowStep(cur, false) == null; 582 } 583 }; 584 585 // the "a:" navigation link for the issues page filtered to the url-encoded 586 // `tag` and rooted at issue `id` within `identity` ("owner/name"). 587 fn issuesLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, tag: []const u8, id: []const u8) ![]const u8 { 588 const route = ui.RoutablePage.repoIssuesRoute(identity, tag, id) orelse return error.RouteTooLong; 589 const url = try route.urlAlloc(page_arena); 590 return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{url}); 591 } 592 593 // the in-page "ai:" anchor for selecting issue `id` in `identity`'s list; the 594 // href is only followed with js off. 595 fn issueRowLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, id: []const u8) ![]const u8 { 596 const route = ui.RoutablePage.repoIssuesRoute(identity, "", id) orelse return error.RouteTooLong; 597 const url = try route.urlAlloc(page_arena); 598 return std.fmt.allocPrint(page_arena.allocator(), "ai:{s}", .{url}); 599 } 600 601 // the "a:" link to the issues list filtered to `tag` (raw; encoded here). 602 fn tagLink(page_arena: *std.heap.ArenaAllocator, identity: []const u8, tag: []const u8) ![]const u8 { 603 const encoded = try ui.urlEncodeRef(page_arena.allocator(), tag); 604 const route = ui.RoutablePage.repoIssuesRoute(identity, encoded, "") orelse return error.RouteTooLong; 605 const url = try route.urlAlloc(page_arena); 606 return std.fmt.allocPrint(page_arena.allocator(), "a:{s}", .{url}); 607 } 608