Skip to content

How to implement a terminal/log with widgets? #2026

@tillfalko

Description

@tillfalko

One downside of turning my script into a prompt_toolkit Application is that I can no longer just write to stdout. How would I implement something like a scrolling log with line-wrapping using widgets?
I would like an area that I can print to, with simple line-wrapping, and the ability to scroll while text is being printed.

What I've tried:

Label

I first tried to use a label since I don't need the text to be user-editable. I thought I could simply append the text I wanted to print at the end of the current text, but I couldn't figure out how to update the label text and have it update visually.

TextArea or Buffer + BufferControl + Window

From what I can tell a buffer wants to be used to insert text at a cursor position which must be visible on the screen. TextArea works the same way. That means that whenever I print text, I need to store the current scroll position, move to the bottom and insert text, then restore the previous position. I could not get scrolling to work with wrap_lines = True for the life of me. #686 may be related. I had to manually wrap lines by checking the terminal width and inserting \n.

    async def write(self, text: str):
        ri = self.output.render_info
        at_bottom = True
        if ri:
            at_bottom = (ri.vertical_scroll + ri.window_height >= ri.ui_content.line_count)
        old_cursor = self.output_buffer.cursor_position

        try:
            self.output_buffer.cursor_position = len(self.output_buffer.text)
            free_space = ri.window_width - ri.cursor_position.x - 1
            to_print = ''
            while text != '':
                to_print += text[0]
                free_space = ri.window_width if text[0] == '\n' else free_space - 1
                text = text[1:]

                if free_space < 1:
                    text = '\n' + text

            self.output_buffer.insert_text(to_print) 
        finally:
            pass

        if not at_bottom:
            self.output_buffer.cursor_position = old_cursor

        self.app.invalidate()

This works, but it's fragile, ugly, and breaks when the user resizes their terminal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions