╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1202 const entry = &self.window(i).issues[sel]; │ │1203 const mirrored = if (self.data.description_page and std.mem.eql(u8, entry.id, self.data.selected_id))│ │1204 ui.RoutablePage.repoIssuesDescriptionRoute(self.data.identity, entry.id) │ │1205 + else if (std.mem.eql(u8, entry.id, self.data.selected_id)) │ │1206 + ui.RoutablePage.repoIssueCommentsRoute(self.data.identity, entry.id, entry.comments.start) │ │1207 else │ │1182 - ui.RoutablePage.repoIssuesRoute(self.data.identity, entry.issue.event.status, "", entry.id); │ │1208 + ui.RoutablePage.repoIssueCommentsRoute(self.data.identity, entry.id, 0); │ │1209 if (mirrored) |route| self.session.data.current_page = route; │ │1210 } │ │1211 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1432 try inner.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │1433 break :blk tb.getFocus().id; │ │1434 }; │ │1435 + │ │1436 + if (!description_page) { │ │1437 + try Comments.appendCount(allocator, inner, entry.comments.count, "comment", "comments", self.session.page_arena.allocator()); │ │1438 + for (entry.comments.comments) |comment| try Comments.appendComment(allocator, inner, self.session, self.data.identity, comment);│ │1439 + try Comments.appendWindowNav(allocator, inner, self.session, self.data.identity, entry.id, null, entry.comments); │ │1440 + } │ │1441 │ │1442 // select the title by default │ │1443 inner.getFocus().child_id = inner.children.keys()[title_child_index]; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────╮ │1538 try self.authorInput(index, key, root_focus); │ │1539 } else if (self.tagsFocused(index)) { │ │1540 try self.tagsInput(index, key, root_focus); │ │1509 - } else { │ │1541 + } else if (self.descriptionFocused(index)) { │ │1542 try self.descriptionInput(index, key, root_focus);│ │1543 + } else { │ │1544 + try self.commentInput(index, key, root_focus); │ │1545 } │ │1546 } │ │1547 │ ╰────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │1606 } │ │1607 return; │ │1608 }, │ │1575 - .arrow_down => sc.y += 1, │ │1609 + .arrow_down => { │ │1610 + if (self.moveDetailFocus(index, root_focus, true)) return; │ │1611 + const before = sc.y; │ │1612 + sc.y += 1; │ │1613 + sc.clampToContent(); │ │1614 + if (sc.y == before) _ = self.moveDetailFocus(index, root_focus, true);│ │1615 + return; │ │1616 + }, │ │1617 .page_up => sc.y -= 10, │ │1618 .page_down => sc.y += 10, │ │1619 .home => sc.y = 0, │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1621 else => return, │ │1622 } │ │1623 sc.clampToContent(); │ │1624 + } │ │1625 + │ │1626 + fn commentInput(self: *View, index: usize, key: Key, root_focus: *Focus) !void { │ │1627 + switch (key) { │ │1628 + .arrow_left => if (!self.moveCommentHorizontal(index, root_focus, false)) return self.focusList(index, root_focus),│ │1629 + .arrow_right => _ = self.moveCommentHorizontal(index, root_focus, true), │ │1630 + .arrow_up => _ = self.moveCommentVertical(index, root_focus, false), │ │1631 + .arrow_down => _ = self.moveCommentVertical(index, root_focus, true), │ │1632 + .page_up => self.detailScroll(index).y -= 10, │ │1633 + .page_down => self.detailScroll(index).y += 10, │ │1634 + .home => self.detailScroll(index).y = 0, │ │1635 + .end => self.detailScroll(index).y = std.math.maxInt(isize), │ │1636 + else => return, │ │1637 + } │ │1638 + self.detailScroll(index).clampToContent(); │ │1639 } │ │1640 │ │1641 + fn focusedDetailChild(self: *View, index: usize, root_focus: *Focus) ?usize { │ │1642 + const inner = self.detailInner(index); │ │1643 + var id = root_focus.grandchild_id orelse return null; │ │1644 + while (inner.getFocus().children.get(id)) |child| { │ │1645 + if (child.parent_id == inner.getFocus().id) return inner.children.getIndex(id); │ │1646 + id = child.parent_id; │ │1647 + } │ │1648 + return null; │ │1649 + } │ │1650 + │ │1651 + fn detailChildFocusable(self: *View, index: usize, child_index: usize) bool { │ │1652 + const child = &self.detailInner(index).children.values()[child_index].widget; │ │1653 + return switch (child.*) { │ │1654 + .repo_comment => true, │ │1655 + .box => |*box| for (box.children.values()) |*item| { │ │1656 + if (item.widget.getFocus().focusable) break true; │ │1657 + } else false, │ │1658 + else => child.getFocus().focusable, │ │1659 + }; │ │1660 + } │ │1661 + │ │1662 + fn focusDetailChild(self: *View, index: usize, child_index: usize, last: bool, root_focus: *Focus) void { │ │1663 + const inner = self.detailInner(index); │ │1664 + const child = &inner.children.values()[child_index]; │ │1665 + var target = inner.children.keys()[child_index]; │ │1666 + var rect = child.rect orelse return; │ │1667 + switch (child.widget) { │ │1668 + .repo_comment => |*comment| { │ │1669 + if (comment.rowRect(last)) |row_rect| { │ │1670 + rect.x += row_rect.x; │ │1671 + rect.y += row_rect.y; │ │1672 + rect.size = row_rect.size; │ │1673 + } │ │1674 + if (last) comment.focusBody(root_focus) else comment.focusMetadata(root_focus); │ │1675 + }, │ │1676 + .box => |*box| if (box.children.count() > 0) { │ │1677 + target = box.children.keys()[if (last) box.children.count() - 1 else 0]; │ │1678 + root_focus.setFocus(target); │ │1679 + }, │ │1680 + else => root_focus.setFocus(target), │ │1681 + } │ │1682 + self.detailScroll(index).scrollToRect(rect); │ │1683 + } │ │1684 + │ │1685 + fn moveCommentVertical(self: *View, index: usize, root_focus: *Focus, down: bool) bool { │ │1686 + const inner = self.detailInner(index); │ │1687 + const current = self.focusedDetailChild(index, root_focus) orelse return false; │ │1688 + if (inner.children.values()[current].widget == .repo_comment) { │ │1689 + const comment = &inner.children.values()[current].widget.repo_comment; │ │1690 + if (down and !comment.bodyFocused()) { │ │1691 + self.focusDetailChild(index, current, true, root_focus); │ │1692 + return true; │ │1693 + } │ │1694 + if (!down and comment.bodyFocused()) { │ │1695 + self.focusDetailChild(index, current, false, root_focus); │ │1696 + return true; │ │1697 + } │ │1698 + } │ │1699 + │ │1700 + var next = current; │ │1701 + while (true) { │ │1702 + if (down) { │ │1703 + next += 1; │ │1704 + if (next >= inner.children.count()) return false; │ │1705 + } else { │ │1706 + if (next == 0) return false; │ │1707 + next -= 1; │ │1708 + } │ │1709 + if (!self.detailChildFocusable(index, next)) continue; │ │1710 + self.focusDetailChild(index, next, !down, root_focus); │ │1711 + return true; │ │1712 + } │ │1713 + } │ │1714 + │ │1715 + fn moveCommentHorizontal(self: *View, index: usize, root_focus: *Focus, right: bool) bool { │ │1716 + const inner = self.detailInner(index); │ │1717 + const current = self.focusedDetailChild(index, root_focus) orelse return false; │ │1718 + const child = &inner.children.values()[current]; │ │1719 + if (child.widget == .repo_comment) { │ │1720 + const moved = child.widget.repo_comment.moveHorizontal(root_focus, right); │ │1721 + if (moved) if (child.rect) |rect| self.detailScroll(index).scrollToRect(rect); │ │1722 + return moved; │ │1723 + } │ │1724 + const row = switch (child.widget) { │ │1725 + .box => |*box| box, │ │1726 + else => return false, │ │1727 + }; │ │1728 + const selected = row.getFocus().child_id orelse return false; │ │1729 + const selected_index = row.children.getIndex(selected) orelse return false; │ │1730 + const target = if (right) selected_index + 1 else selected_index -| 1; │ │1731 + if (target == selected_index or target >= row.children.count()) return false; │ │1732 + root_focus.setFocus(row.children.keys()[target]); │ │1733 + if (child.rect) |rect| self.detailScroll(index).scrollToRect(rect); │ │1734 + return true; │ │1735 + } │ │1736 + │ │1737 + fn moveDetailFocus(self: *View, index: usize, root_focus: *Focus, down: bool) bool { │ │1738 + return self.moveCommentVertical(index, root_focus, down); │ │1739 + } │ │1740 + │ │1741 // up/down (and tab/shift+tab) move between a form's fields; up from the │ │1742 // title crosses into the header tabs. the multiline description keeps │ │1743 // enter and any up/down that has a row to move to. │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │2149 return cid == self.author_id[index]; │ │2150 } │ │2151 │ │2152 + fn descriptionFocused(self: *View, index: usize) bool { │ │2153 + const cid = self.detailInner(index).getFocus().child_id orelse return false;│ │2154 + return cid == self.description_id[index]; │ │2155 + } │ │2156 + │ │2157 // the author sits right under the title at the top of the pane, so │ │2158 // focusing it scrolls there. │ │2159 fn focusAuthor(self: *View, index: usize, root_focus: *Focus) void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭ added ─────────────────╮ │src/ui/Repo/Comments.zig│ ╰────────────────────────╯