From a10d2d9f988eb75d29c750a56da162db0330cbc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 4 May 2023 18:59:05 +0100 Subject: [PATCH] Reference container markdown document. Adding these references to the sub-widgets that make up a markdown document is necessary in order for the blocks to be able to post messages with a reference to the original document, which in turn is needed for the Message.control property to work. --- CHANGELOG.md | 2 + src/textual/widgets/_markdown.py | 73 ++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13880432a..7488e339f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Markdown document sub-widgets now reference the container document +- Table of contents of a markdown document now references the document - Added the `control` property to messages - `OptionList.OptionHighlighted` and `OptionList.OptionSelected` - `Tree.NodeSelected`, `Tree.NodeHighlighted`, `Tree.NodeExpanded`, and `Tree.NodeCollapsed` diff --git a/src/textual/widgets/_markdown.py b/src/textual/widgets/_markdown.py index 654f838d9..966584228 100644 --- a/src/textual/widgets/_markdown.py +++ b/src/textual/widgets/_markdown.py @@ -88,7 +88,9 @@ class MarkdownBlock(Static): } """ - def __init__(self, *args, **kwargs) -> None: + def __init__(self, markdown: Markdown, *args, **kwargs) -> None: + self._markdown: Markdown = markdown + """A reference to the Markdown document that contains this block.""" self._text = Text() self._blocks: list[MarkdownBlock] = [] super().__init__(*args, **kwargs) @@ -453,9 +455,9 @@ class MarkdownListItem(MarkdownBlock): } """ - def __init__(self, bullet: str) -> None: + def __init__(self, markdown: Markdown, bullet: str) -> None: self.bullet = bullet - super().__init__() + super().__init__(markdown) class MarkdownOrderedListItem(MarkdownListItem): @@ -484,10 +486,10 @@ class MarkdownFence(MarkdownBlock): } """ - def __init__(self, code: str, lexer: str) -> None: + def __init__(self, markdown: Markdown, code: str, lexer: str) -> None: self.code = code self.lexer = lexer - super().__init__() + super().__init__(markdown) def compose(self) -> ComposeResult: yield Static( @@ -629,20 +631,20 @@ class Markdown(Widget): for token in parser.parse(markdown): if token.type == "heading_open": block_id += 1 - stack.append(HEADINGS[token.tag](id=f"block{block_id}")) + stack.append(HEADINGS[token.tag](self, id=f"block{block_id}")) elif token.type == "hr": - output.append(MarkdownHorizontalRule()) + output.append(MarkdownHorizontalRule(self)) elif token.type == "paragraph_open": - stack.append(MarkdownParagraph()) + stack.append(MarkdownParagraph(self)) elif token.type == "blockquote_open": - stack.append(MarkdownBlockQuote()) + stack.append(MarkdownBlockQuote(self)) elif token.type == "bullet_list_open": - stack.append(MarkdownBulletList()) + stack.append(MarkdownBulletList(self)) elif token.type == "ordered_list_open": - stack.append(MarkdownOrderedList()) + stack.append(MarkdownOrderedList(self)) elif token.type == "list_item_open": if token.info: - stack.append(MarkdownOrderedListItem(token.info)) + stack.append(MarkdownOrderedListItem(self, token.info)) else: item_count = sum( 1 @@ -651,22 +653,23 @@ class Markdown(Widget): ) stack.append( MarkdownUnorderedListItem( - self.BULLETS[item_count % len(self.BULLETS)] + self, + self.BULLETS[item_count % len(self.BULLETS)], ) ) elif token.type == "table_open": - stack.append(MarkdownTable()) + stack.append(MarkdownTable(self)) elif token.type == "tbody_open": - stack.append(MarkdownTBody()) + stack.append(MarkdownTBody(self)) elif token.type == "thead_open": - stack.append(MarkdownTHead()) + stack.append(MarkdownTHead(self)) elif token.type == "tr_open": - stack.append(MarkdownTR()) + stack.append(MarkdownTR(self)) elif token.type == "th_open": - stack.append(MarkdownTH()) + stack.append(MarkdownTH(self)) elif token.type == "td_open": - stack.append(MarkdownTD()) + stack.append(MarkdownTD(self)) elif token.type.endswith("_close"): block = stack.pop() if token.type == "heading_close": @@ -742,12 +745,13 @@ class Markdown(Widget): elif token.type == "fence": output.append( MarkdownFence( + self, token.content.rstrip(), token.info, ) ) - self.post_message(Markdown.TableOfContentsUpdated(table_of_contents)) + self.post_message(Markdown.TableOfContentsUpdated(self, table_of_contents)) with self.app.batch_update(): self.query("MarkdownBlock").remove() self.mount_all(output) @@ -768,6 +772,27 @@ class MarkdownTableOfContents(Widget, can_focus_children=True): table_of_contents = reactive["TableOfContentsType | None"](None, init=False) + def __init__( + self, + markdown: Markdown, + name: str | None = None, + id: str | None = None, + classes: str | None = None, + disabled: bool = False, + ) -> None: + """Initialize a table of contents. + + Args: + markdown: The Markdown document associated with this table of contents. + name: The name of the widget. + id: The ID of the widget in the DOM. + classes: The CSS classes for the widget. + disabled: Whether the widget is disabled or not. + """ + self.markdown = markdown + """The Markdown document associated with this table of contents.""" + super().__init__(name=name, id=id, classes=classes, disabled=disabled) + def compose(self) -> ComposeResult: tree: Tree = Tree("TOC") tree.show_root = False @@ -804,8 +829,9 @@ class MarkdownTableOfContents(Widget, can_focus_children=True): node_data = message.node.data if node_data is not None: await self._post_message( - Markdown.TableOfContentsSelected(node_data["block_id"]) + Markdown.TableOfContentsSelected(self.markdown, node_data["block_id"]) ) + message.stop() class MarkdownViewer(VerticalScroll, can_focus=True, can_focus_children=True): @@ -896,8 +922,9 @@ class MarkdownViewer(VerticalScroll, can_focus=True, can_focus_children=True): self.set_class(show_table_of_contents, "-show-table-of-contents") def compose(self) -> ComposeResult: - yield MarkdownTableOfContents() - yield Markdown(parser_factory=self._parser_factory) + markdown = Markdown(parser_factory=self._parser_factory) + yield MarkdownTableOfContents(markdown) + yield markdown def _on_markdown_table_of_contents_updated( self, message: Markdown.TableOfContentsUpdated