Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SelectionList -> OptionList.render_line Unhandled IndexError #5568

Closed
ddkasa opened this issue Feb 21, 2025 · 6 comments · Fixed by #5574
Closed

SelectionList -> OptionList.render_line Unhandled IndexError #5568

ddkasa opened this issue Feb 21, 2025 · 6 comments · Fixed by #5574

Comments

@ddkasa
Copy link
Contributor

ddkasa commented Feb 21, 2025

The Bug

When using a SelectionList the render_line method of the new option list is throwing an IndexError exception when the strips are empty.

Traceback

│                                                                                                                                       │
│ /home/dk/dev/tmp/textual-sandbox/.venv/lib/python3.12/site-packages/textual/widgets/_selection_list.py:514 in render_line             │
│                                                                                                                                       │
│   511 │   │   # TODO: This is rather crufty and hard to fathom. Candidate for a rewrite.       ╭──────── locals ────────╮             │
│   512 │   │                                                                                    │ self = SelectionList() │             │
│   513 │   │   # First off, get the underlying prompt from OptionList.                          │    y = 0               │             │
│ ❱ 514 │   │   line = super().render_line(y)                                                    ╰────────────────────────╯             │
│   515 │   │                                                                                                                           │
│   516 │   │   # We know the prompt we're going to display, what we're going to do                                                     │
│   517 │   │   # is place a CheckBox-a-like button next to it. So to start with                                                        │
│                                                                                                                                       │
│ /home/dk/dev/tmp/textual-sandbox/.venv/lib/python3.12/site-packages/textual/widgets/_option_list.py:860 in render_line                │
│                                                                                                                                       │
│   857 │   │                                                                                                                           │
│   858 │   │   strips = self._get_option_render(option, style)                                                                         │
│   859 │   │                                                                                                                           │
│ ❱ 860 │   │   return strips[line_offset]                                                                                              │
│   861 │                                                                                                                               │
│   862 │                                                                                                                               │
│   863 │   def validate_highlighted(self, highlighted: int | None) -> int | None:                                                      │
│                                                                                                                                       │
│ ╭──────────────────────────────────────── locals ─────────────────────────────────────────╮                                           │
│ │ component_class = 'option-list--option-highlighted'                                     │                                           │
│ │     line_number = 0                                                                     │                                           │
│ │     line_offset = 0                                                                     │                                           │
│ │      mouse_over = False                                                                 │                                           │
│ │          option = Selection(<text 'Professional nor' [] ''>)                            │                                           │
│ │    option_index = 0                                                                     │                                           │
│ │            self = SelectionList()                                                       │                                           │
│ │          strips = []                                                                    │                                           │
│ │           style = <Style background=Color(81, 73, 115) foreground=Color(169, 177, 214)> │                                           │
│ │               y = 0                                                                     │                                           │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────╯                                           │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
IndexError: list index out of range

MRE

from textual.app import App
from textual.widgets import SelectionList


class BugReportApp(App):
    def compose(self):
        yield SelectionList(("Professional nor", 1))


if __name__ == "__main__":
    BugReportApp().run()

I found this error extremely flakey at times, as sometimes it would only pop up in test runs, so let me know if there is additional info I can provide.

Looking through the previous OptionList, I found that it had an IndexError exception handler at this point, so maybe that should be reverted?

try:
strip = strips[y_offset]
except IndexError:
return Strip([])

Sidenote is that the strips are being first nulled at the RichVisual.render_strips method here:

strips = [
Strip(line)
for line in islice(
Segment.split_and_crop_lines(
segments, width, include_new_lines=False, pad=False
),
None,
height,
)
]

Textual Diagnostics

Versions

Name Value
Textual 2.1.0
Rich 13.9.4

Python

Name Value
Version 3.12.5
Implementation CPython
Compiler GCC 14.2.1 20240801 (Red Hat 14.2.1-1)
Executable /home/dk/dev/tmp/textual-sandbox/.venv/bin/python

Operating System

Name Value
System Linux
Release 6.12.13-100.fc40.x86_64
Version #1 SMP PREEMPT_DYNAMIC Sat Feb 8 17:10:01 UTC 2025

Terminal

Name Value
Terminal Application Kitty
TERM xterm-kitty
COLORTERM truecolor
FORCE_COLOR Not set
NO_COLOR Not set

Rich Console options

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

We found the following entry in the FAQ which you may find helpful:

Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.

This is an automated reply, generated by FAQtory

@TomJGooding
Copy link
Contributor

Funnily enough I just hit a similar IndexError, but with the OptionList itself while trying to create a MRE for another possible bug.

In my case, the exception occurred after trying to resize my terminal to full screen. I can't reproduce it every time but fairly reliably after resizing the terminal for a while.

@ddkasa
Copy link
Contributor Author

ddkasa commented Feb 22, 2025

I was trying to the reproduce it the way you described, but it doesn't want to error. I am curious what terminal emulator are you using?

@TomJGooding
Copy link
Contributor

I don't think this is related to specific emulators. Try messing around with your terminal size with this app:

from textual.app import App, ComposeResult
from textual.widgets import OptionList


class ExampleApp(App):
    def compose(self) -> ComposeResult:
        yield OptionList(
            *[
                f"This is option {i} which is long and may wrap when resized"
                for i in range(100)
            ]
        )


if __name__ == "__main__":
    app = ExampleApp()
    app.run()

@ddkasa
Copy link
Contributor Author

ddkasa commented Feb 22, 2025

Yeah, I managed to reproduce the same error with a lot resizing with the example you gave.

Copy link

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants