Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/textual/widgets/_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.

Expand Down
30 changes: 30 additions & 0 deletions tests/test_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"],
[
Expand Down
Loading