diff --git a/src/textual/widgets/_markdown.py b/src/textual/widgets/_markdown.py index 5deb964d4a..9dd6f23699 100644 --- a/src/textual/widgets/_markdown.py +++ b/src/textual/widgets/_markdown.py @@ -294,10 +294,11 @@ def build_from_token(self, token: Token) -> None: token: The token from which this block is built. """ self._inline_token = token - content = self._token_to_content(token) + content = self._markdown._get_token_content(token, block=self) self.set_content(content) - def _token_to_content(self, token: Token) -> Content: + @staticmethod + def _token_to_content(token: Token) -> Content: """Convert an inline token to Textual Content. Args: @@ -1282,6 +1283,14 @@ async def load(self, path: Path | MarkdownLocation) -> None: """ await self.sanitize_location(path).load(self) + def _get_token_content(self, token: Token, *, block: MarkdownBlock) -> Content: + """Supply inline content for every block, including headings and tables. + + Prepared documents may return their acquired content here; ordinary + documents use the same native conversion without a separate parser. + """ + return block._token_to_content(token) + def unhandled_token(self, token: Token) -> MarkdownBlock | None: """Process an unhandled token. diff --git a/tests/test_markdown.py b/tests/test_markdown.py index fda3ba7d24..502f3d2098 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -12,6 +12,7 @@ import textual.widgets._markdown as MD from textual import on from textual.app import App, ComposeResult +from textual.content import Content from textual.style import Style from textual.widget import Widget from textual.widgets import Markdown @@ -41,6 +42,35 @@ def compose(self) -> ComposeResult: yield FussyMarkdown(self._markdown) +async def test_document_supplies_inline_content_for_heading_paragraph_and_table(): + class CustomParagraph(MD.MarkdownParagraph): + def _token_to_content(self, token: Token) -> Content: + return super()._token_to_content(token) + Content(" custom") + + class SuppliedMarkdown(Markdown): + BLOCKS = {**Markdown.BLOCKS, "paragraph_open": CustomParagraph} + + def _get_token_content(self, token: Token, *, block: MarkdownBlock) -> Content: + return super()._get_token_content(token, block=block) + Content(" supplied") + + document = SuppliedMarkdown( + "# Heading\n\nParagraph [link](https://example.com)\n\n" + "| Header |\n| --- |\n| Cell |" + ) + async with App().run_test() as pilot: + await pilot.app.mount(document) + await pilot.pause() + assert document.query_one(MD.MarkdownH1)._content.plain == "Heading supplied" + paragraph = document.query_one(MD.MarkdownParagraph)._content + assert paragraph.plain == "Paragraph link custom supplied" + assert paragraph.spans == [ + MD.Span(10, 14, Style.from_meta({"@click": "link('https://example.com')"})) + ] + table = document.query_one(MD.MarkdownTable) + assert [content.plain for content in table._headers] == ["Header supplied"] + assert [[content.plain for content in row] for row in table._rows] == [["Cell supplied"]] + + @pytest.mark.parametrize( ["document", "expected_nodes"], [