Textualize/textual · error · IndexError

No line no. {line} in the tree

Error message

No line no. {line} in the tree

What it means

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.

Source

Thrown at src/textual/widgets/_tree.py:994

                animate=animate and abs(self.cursor_line - previous_cursor_line) > 1,
            )

    def move_cursor_to_line(self, line: int, animate=False) -> None:
        """Move the cursor to the given line.

        Args:
            line: The line number (negative indexes are offsets from the last line).
            animate: Enable scrolling animation.

        Raises:
            IndexError: If the line doesn't exist.
        """
        if self.cursor_line == line:
            return
        try:
            node = self._tree_lines[line].node
        except IndexError:
            raise IndexError(f"No line no. {line} in the tree")
        self.move_cursor(node, animate=animate)

    def select_node(self, node: TreeNode[TreeDataType] | None) -> None:
        """Move the cursor to the given node and select it, or reset cursor.

        Args:
            node: A tree node to move the cursor to and select, or None to reset cursor.
        """
        self.move_cursor(node)
        if node is not None:
            self.post_message(Tree.NodeSelected(node))

    def unselect(self) -> None:
        """Hide and reset the cursor."""
        self.set_reactive(Tree.cursor_line, -1)
        self._invalidate()

    @on(NodeSelected)

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Clamp the line: `tree.move_cursor_to_line(min(line, len(tree._tree_lines) - 1))`.
  2. Move by node instead: `tree.move_cursor(tree.get_node_by_id(node_id))` (guard UnknownNodeID).
  3. Re-check line count after structural changes/collapses before moving.

Example fix

# before
tree.move_cursor_to_line(saved_line)
# after
line = min(saved_line, len(tree._tree_lines) - 1)
if line >= 0:
    tree.move_cursor_to_line(line)
Defensive patterns

Strategy: validation

Validate before calling

if 0 <= line < len(tree._tree_lines):
    tree.move_cursor_to_line(line)

Try / catch

try:
    tree.move_cursor_to_line(line)
except IndexError:
    pass

Prevention

When it happens

Trigger: 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.

Common situations: Using a saved cursor line after `tree.clear()`; computing a target line from total node count instead of visible line count after collapsing branches.

Related errors


AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27). Data as JSON: /api/errors/f60817f6616226c8. Report an issue: GitHub.