Textualize / textual

The lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.
https://textual.textualize.io/
MIT License
24.09k stars 742 forks source link

Markdown Widget Lagging when Opening Large Files #4586

Closed VexiaOnline closed 1 month ago

VexiaOnline commented 1 month ago

Description:

I've encountered an issue where the Markdown Widget in Textual takes an excessively long time to load "large" markdown files, causing the application to freeze and become unresponsive. I put large in quotations because I'm not sure what the lower end of such a filesize is, I know it does it with a few hundred KB file, and davep has tested it with a much larger 7MB+ file.

Steps to Reproduce:

Run a large markdown file through the Markdown Widget, I put a LoadingIndicator on the Markdown widget where I would expect it to show the loading indicator while the file is being read and parsed.

Observe the app freeze completely while the markdown file is parsed, then LoadingIndicator is displaying briefly before the markdown is displayed within the widget.

Expected Behavior: I expect the LoadingIndicator to display for a reasonable amount of time while parsing and rendering the markdown content.

davep helped test what was going on and the following are his observations and comments if they help:

Code Used:

from dataclasses import dataclass
from pathlib import Path

from textual import on, work
from textual.app import App, ComposeResult
from textual.message import Message
from textual.widgets import Button, Markdown

class LoadMarkdownApp(App[None]):

    CSS = """
    Markdown {
        border: solid red;
        height: 1fr;
    }
    """

    def compose(self) -> ComposeResult:
        yield Button()
        yield Markdown()

    @on(Button.Pressed)
    def start_load(self) -> None:
        self.query_one(Markdown).loading = True
        self.load_markdown()

    @dataclass
    class NewDocument(Message):
        markdown: str

    @work(thread=True)
    def load_markdown(self) -> None:
        self.post_message(
            self.NewDocument(Path("test.md").read_text())
        )

    @on(NewDocument)
    async def display_deocument(self, event: NewDocument) -> None:
        await self.query_one(Markdown).update(event.markdown)
        self.query_one(Markdown).loading = False

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

davep:

When I run the above, the LoadingIndicator shows fine but then freezes, the app itself can't actually be stopped with ctrl+c either. Taking out the update in the NewDocument event handler has the loading indicator appear and disappear so fast I never see it.

This, for me, would indicate that the loading of the markdown file from storage happens pretty much instantly (no surprise on my setup here, M2Pro Mac Mini, SSD, etc...).

It looks like the issue is with Markdown.update; a large document is going to take some work to parse, I would suspect, so I imagine Markdown.update is bogged down in the main parsing loop. Off the top of my head I can't think of an easy workaround for handling that without that particular method being modified such that parsing isn't a blocking operation.

Indeed, unless I'm missing something obvious (not unlikely, one coffee into the work day and attention mostly on work things, not Textual), support for loading huge Markdown documents into the Markdown widget would seem to be something that is best baked into the Markdown widget itself (much like how DirectoryTree got an overhaul with similar motivations a year back).

Textual Diagnostics

Versions

Name Value
Textual 0.63.6
Rich 13.7.1

Python

Name Value
Version 3.10.12
Implementation CPython
Compiler GCC 11.4.0
Executable /usr/bin/python3

Operating System

Name Value
System Linux
Release 5.15.146.1-microsoft-standard-WSL2
Version #1 SMP Thu Jan 11 04:09:03 UTC 2024

Terminal

Name Value
Terminal Application Windows Terminal
TERM xterm-256color
COLORTERM Not set
FORCE_COLOR Not set
NO_COLOR Not set

Rich Console options

Name Value
size width=188, height=49
legacy_windows False
min_width 1
max_width 188
is_terminal True
encoding utf-8
max_height 49
justify None
overflow None
no_wrap False
highlight None
markup None
height None
github-actions[bot] commented 1 month ago

Thank you for your issue. Give us a little time to review it.

PS. You might want to check the FAQ if you haven't done so already.

This is an automated reply, generated by FAQtory

github-actions[bot] commented 1 month ago

Don't forget to star the repository!

Follow @textualizeio for Textual updates.