{"record":{"id":"f60817f6616226c8","repo":"Textualize/textual","slug":"no-line-no-line-in-the-tree","errorCode":null,"errorMessage":"No line no. {line} in the tree","messagePattern":"No line no\\. (.+?) in the tree","errorType":"exception","errorClass":"IndexError","httpStatus":null,"severity":"error","filePath":"src/textual/widgets/_tree.py","lineNumber":994,"sourceCode":"                animate=animate and abs(self.cursor_line - previous_cursor_line) > 1,\n            )\n\n    def move_cursor_to_line(self, line: int, animate=False) -> None:\n        \"\"\"Move the cursor to the given line.\n\n        Args:\n            line: The line number (negative indexes are offsets from the last line).\n            animate: Enable scrolling animation.\n\n        Raises:\n            IndexError: If the line doesn't exist.\n        \"\"\"\n        if self.cursor_line == line:\n            return\n        try:\n            node = self._tree_lines[line].node\n        except IndexError:\n            raise IndexError(f\"No line no. {line} in the tree\")\n        self.move_cursor(node, animate=animate)\n\n    def select_node(self, node: TreeNode[TreeDataType] | None) -> None:\n        \"\"\"Move the cursor to the given node and select it, or reset cursor.\n\n        Args:\n            node: A tree node to move the cursor to and select, or None to reset cursor.\n        \"\"\"\n        self.move_cursor(node)\n        if node is not None:\n            self.post_message(Tree.NodeSelected(node))\n\n    def unselect(self) -> None:\n        \"\"\"Hide and reset the cursor.\"\"\"\n        self.set_reactive(Tree.cursor_line, -1)\n        self._invalidate()\n\n    @on(NodeSelected)","sourceCodeStart":976,"sourceCodeEnd":1012,"githubUrl":"https://github.com/Textualize/textual/blob/06dbeef4bb70fb718236aa418ed658ef4667a126/src/textual/widgets/_tree.py#L976-L1012","documentation":"IndexError from Tree.move_cursor_to_line(): the requested line number doesn't exist in the flattened list of visible tree lines (self._tree_lines). Only expanded, visible nodes occupy lines.","triggerScenarios":"Calling `tree.move_cursor_to_line(n)` where n >= len(tree._tree_lines) or n < 0 after negation — common when nodes were collapsed, removed, or the tree was rebuilt since the line count was captured.","commonSituations":"Using a saved cursor line after `tree.clear()`; computing a target line from total node count instead of visible line count after collapsing branches.","solutions":["Clamp the line: `tree.move_cursor_to_line(min(line, len(tree._tree_lines) - 1))`.","Move by node instead: `tree.move_cursor(tree.get_node_by_id(node_id))` (guard UnknownNodeID).","Re-check line count after structural changes/collapses before moving."],"exampleFix":"# before\ntree.move_cursor_to_line(saved_line)\n# after\nline = min(saved_line, len(tree._tree_lines) - 1)\nif line >= 0:\n    tree.move_cursor_to_line(line)","handlingStrategy":"validation","validationCode":"if 0 <= line < len(tree._tree_lines):\n    tree.move_cursor_to_line(line)","typeGuard":null,"tryCatchPattern":"try:\n    tree.move_cursor_to_line(line)\nexcept IndexError:\n    pass","preventionTips":["Prefer move_cursor(node) over line numbers","Recompute line targets after collapse/remove operations"],"tags":["tree","cursor","index-out-of-range","textual"],"backgroundTag":"index-out-of-bounds","analyzedSha":"06dbeef4bb70fb718236aa418ed658ef4667a126","analyzedAt":"2026-08-27T02:36:57.214Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}