#6708·textual

`Selection.extract` IndexError via ordinary mouse selection on built-in widgets (MRE for #6428)

Author: jchimpCreated Sep 1, 2026Updated Sep 4, 2026

Have you checked closed issues? (https://github.com/Textualize/textual/issues?q=is%3Aissue+is%3Aclosed)

Yes, this is the crash reported in #6428, which was closed for lack of an MRE. Below is a reproduction that uses only built-in widgets and Textual's own mouse-selection machinery.

This could be some AI hallucination stuff, but it has come up a few times for myself and the MRE below crashes every time.

Have you checked against the most recent version of Textual? (https://pypi.org/search/?q=textual)

Yes, reproduced on 8.2.8.

The bug

Selection.extract() clamps end_line but never start_line_index:

python
end_line = min(len(lines), end_line)

if start_line_index == end_line:
    return lines[start_line_index][start_offset:end_offset]   # <- unclamped

A selection whose start row equals the extracted text's line count takes that path and raises IndexError: list index out of range from inside Screen.get_selected_text().

textual_mre.py

The MRE example is attached, and its output is below:

Output:

Traceback (most recent call last):
  ...
  File "...\textual\screen.py", line 977, in get_selected_text
    and (selected_text_in_widget := widget.get_selection(selection))
  File "...\textual\widget.py", line 4232, in get_selection
    return selection.extract(text), "\n"
  File "...\textual\selection.py", line 60, in extract
    return lines[start_line_index][start_offset:end_offset]
IndexError: list index out of range

Here is an interactive variant (drag across the blank line under "hello" with the mouse, then press c):

python
from textual.app import App, ComposeResult
from textual.widgets import Static

class MREApp(App):
    BINDINGS = [("c", "show_selected", "Show selected text")]

    def compose(self) -> ComposeResult:
        yield Static("hello\n")

    def action_show_selected(self) -> None:
        self.notify(repr(self.screen.get_selected_text()))

MREApp().run()

Suggested fix

This was Claude's suggested fix - figured it was worth submitting to see if it would help out.

Guard the fast path the same way the multi-line path below it already guards (except IndexError: pass), or clamp the start like the end:

python
if start_line_index == end_line:
    try:
        return lines[start_line_index][start_offset:end_offset]
    except IndexError:
        return ""

Textual Diagnostics

Versions

Name Value
Textual 8.2.8
Rich 15.0.0

Python

Name Value
Version 3.14.7
Implementation CPython
Compiler MSC v.1944 64 bit (AMD64)
Executable C:\Users\jchampion\AppData\Local\Python\pythoncore-3.14-64\python.exe

Operating System

Name Value
System Windows
Release 11
Version 10.0.26200

Terminal

Name Value
Terminal Application Windows Terminal
TERM Not set
COLORTERM Not set
FORCE_COLOR Not set
NO_COLOR Not set

Rich Console options

Name Value
size width=148, height=34
legacy_windows False
min_width 1
max_width 148
is_terminal True
encoding utf-8
max_height 34
justify None
overflow None
no_wrap False
highlight None
markup None
height None