peterbrittain / asciimatics

A cross platform package to do curses-like operations, plus higher level APIs and widgets to create text UIs and ASCII art animations
Apache License 2.0
3.64k stars 238 forks source link

IndexError when reducing window height with big TextBox #291

Closed lykius closed 3 years ago

lykius commented 3 years ago

Describe the bug An IndexError (list index out of range) is raised when displaying a TextBox with many lines and the height of the terminal window is reduced.

To Reproduce

Expected behavior The displayed Frame is expected to be resized according to the new terminal window height. It will not be possible to display the entire TextBox, but just few lines of it.

Screenshots After reducing the terminal height over a certain threshold (I couldn't established what is triggering the error exactly, since it is not raised immediately, but only after that the window has been reduced considerably) an IndexError is raised:

File ".../asciimatics/screen.py", line 83, in clear self._double_buffer[i][x:x + w] = line[:]
IndexError: list index out of range

System details (please complete the following information):

peterbrittain commented 3 years ago

That code is for the Screen double buffering and so should not be affected by use of Widgets like the TextBox. The code already handles off-screen clipping, so my guess is that it isn't detecting or handling the resize correctly. Are you using stop_on_resize when you call play()?

If that's not it can you please show the full stack trace and sample code for the issue?

peterbrittain commented 3 years ago

Though... Suspiciously the only invocation of clear_buffer with non-default dimensions is in TextBox. Could be a clipping bug there.

lykius commented 3 years ago

This is the sample code for the issue (sorry for providing it only now):

from asciimatics.exceptions import ResizeScreenError
from asciimatics.scene import Scene
from asciimatics.screen import Screen
from asciimatics.widgets import Frame, Layout, TextBox

class MyFrame(Frame):
    def __init__(self, screen):
        Frame.__init__(self, screen, int(screen.height), int(screen.width), name="test")
        self.set_theme("bright")

        layout = Layout([100])
        self.add_layout(layout)

        self.text_box = TextBox(30)
        layout.add_widget(self.text_box)

        line = "This is a test"
        text = []
        for _ in range(30):
            text.append(line)

        self.text_box.value = text

        self.fix()

def run(screen, scene):
    scenes = []
    scenes.append(Scene([MyFrame(screen)], duration=-1))
    screen.play(scenes, stop_on_resize=True, start_scene=scene)

def show():
    last_scene = None
    while True:
        try:
            Screen.wrapper(run, arguments=[last_scene])
            break
        except ResizeScreenError as e:
            last_scene = e.scene

if __name__ == "__main__":
    show()

I hope this helps!