Textualize/textual · error · ValueError

The document line index {line_index!r} is out of bounds. The

Error message

The document line index {line_index!r} is out of bounds. The document contains {len(wrap_offsets)!r} lines.

What it means

WrappedDocument.get_offsets raises ValueError when line_index is negative or >= the number of wrapped lines. Each entry in _wrap_offsets corresponds to one document line, so the index space is document line indices.

Source

Thrown at src/textual/document/_wrapped_document.py:439

        return [line.plain for line in wrapped_lines]

    def get_offsets(self, line_index: int) -> list[int]:
        """Given a line index, get the offsets within that line where wrapping
        should occur for the current document.

        Args:
            line_index: The index of the line within the document.

        Raises:
            ValueError: When `line_index` is out of bounds.

        Returns:
            The offsets within the line where wrapping should occur.
        """
        wrap_offsets = self._wrap_offsets
        out_of_bounds = line_index < 0 or line_index >= len(wrap_offsets)
        if out_of_bounds:
            raise ValueError(
                f"The document line index {line_index!r} is out of bounds. "
                f"The document contains {len(wrap_offsets)!r} lines."
            )
        return wrap_offsets[line_index]

    def get_tab_widths(self, line_index: int) -> list[int]:
        """Return a list of the tab widths for the given line index.

        Args:
            line_index: The index of the line in the document.

        Returns:
            An ordered list of the expanded width of the tabs in the line.
        """
        return self._tab_width_cache[line_index]

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Clamp: max(0, min(line_index, wrapped_doc.line_count - 1))
  2. Recompute line indices from the wrapped document after any text change
  3. Guard navigation helpers to no-op at the first/last line

Example fix

# before
offsets = wrapped.get_offsets(line_index)  # may be == line_count
# after
if 0 <= line_index < wrapped.line_count:
    offsets = wrapped.get_offsets(line_index)
Defensive patterns

Strategy: validation

Validate before calling

def safe_offsets(wrapped, line_index: int):
    if not 0 <= line_index < wrapped.line_count:
        return []
    return wrapped.get_offsets(line_index)

Type guard

def valid_line(wrapped, line_index: int) -> bool:
    return 0 <= line_index < wrapped.line_count

Try / catch

try:
    offsets = wrapped.get_offsets(line_index)
except ValueError:
    offsets = []  # treat as no wrap points

Prevention

When it happens

Trigger: Calling get_offsets(n) after the document shrank (line deleted) while holding a stale line index; or off-by-one logic like get_offsets(line_count) instead of line_count - 1. Called transitively via is_start_of_wrapped_line, get_location_above/below, etc.

Common situations: Custom TextArea renderers/navigation code caching line indices across edits; widgets computing 'line above/below' at the first/last line without bounds checks.

Related errors


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