{"record":{"id":"c34f21531406ea76","repo":"Textualize/textual","slug":"the-document-line-index-line-index-r-is-out-of-b","errorCode":null,"errorMessage":"The document line index {line_index!r} is out of bounds. The document contains {len(wrap_offsets)!r} lines.","messagePattern":"The document line index (.+?) is out of bounds\\. The document contains (.+?) lines\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/textual/document/_wrapped_document.py","lineNumber":439,"sourceCode":"        return [line.plain for line in wrapped_lines]\n\n    def get_offsets(self, line_index: int) -> list[int]:\n        \"\"\"Given a line index, get the offsets within that line where wrapping\n        should occur for the current document.\n\n        Args:\n            line_index: The index of the line within the document.\n\n        Raises:\n            ValueError: When `line_index` is out of bounds.\n\n        Returns:\n            The offsets within the line where wrapping should occur.\n        \"\"\"\n        wrap_offsets = self._wrap_offsets\n        out_of_bounds = line_index < 0 or line_index >= len(wrap_offsets)\n        if out_of_bounds:\n            raise ValueError(\n                f\"The document line index {line_index!r} is out of bounds. \"\n                f\"The document contains {len(wrap_offsets)!r} lines.\"\n            )\n        return wrap_offsets[line_index]\n\n    def get_tab_widths(self, line_index: int) -> list[int]:\n        \"\"\"Return a list of the tab widths for the given line index.\n\n        Args:\n            line_index: The index of the line in the document.\n\n        Returns:\n            An ordered list of the expanded width of the tabs in the line.\n        \"\"\"\n        return self._tab_width_cache[line_index]\n","sourceCodeStart":421,"sourceCodeEnd":455,"githubUrl":"https://github.com/Textualize/textual/blob/06dbeef4bb70fb718236aa418ed658ef4667a126/src/textual/document/_wrapped_document.py#L421-L455","documentation":"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.","triggerScenarios":"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.","commonSituations":"Custom TextArea renderers/navigation code caching line indices across edits; widgets computing 'line above/below' at the first/last line without bounds checks.","solutions":["Clamp: max(0, min(line_index, wrapped_doc.line_count - 1))","Recompute line indices from the wrapped document after any text change","Guard navigation helpers to no-op at the first/last line"],"exampleFix":"# before\noffsets = wrapped.get_offsets(line_index)  # may be == line_count\n# after\nif 0 <= line_index < wrapped.line_count:\n    offsets = wrapped.get_offsets(line_index)","handlingStrategy":"validation","validationCode":"def safe_offsets(wrapped, line_index: int):\n    if not 0 <= line_index < wrapped.line_count:\n        return []\n    return wrapped.get_offsets(line_index)","typeGuard":"def valid_line(wrapped, line_index: int) -> bool:\n    return 0 <= line_index < wrapped.line_count","tryCatchPattern":"try:\n    offsets = wrapped.get_offsets(line_index)\nexcept ValueError:\n    offsets = []  # treat as no wrap points","preventionTips":["Recompute line indices after any document edit","Clamp line_index to [0, line_count - 1]","Guard first/last-line navigation helpers"],"tags":["textual","document","wrap","bounds","valueerror"],"backgroundTag":"index-out-of-bounds","analyzedSha":"06dbeef4bb70fb718236aa418ed658ef4667a126","analyzedAt":"2026-08-27T02:36:57.214Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}