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.
This commit is contained in:
Rodrigo Girão Serrão
2023-05-04 18:59:05 +01:00
parent 2187a1d4a3
commit a10d2d9f98
2 changed files with 52 additions and 23 deletions

View File

@@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added ### 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 - Added the `control` property to messages
- `OptionList.OptionHighlighted` and `OptionList.OptionSelected` - `OptionList.OptionHighlighted` and `OptionList.OptionSelected`
- `Tree.NodeSelected`, `Tree.NodeHighlighted`, `Tree.NodeExpanded`, and `Tree.NodeCollapsed` - `Tree.NodeSelected`, `Tree.NodeHighlighted`, `Tree.NodeExpanded`, and `Tree.NodeCollapsed`

View File

@@ -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._text = Text()
self._blocks: list[MarkdownBlock] = [] self._blocks: list[MarkdownBlock] = []
super().__init__(*args, **kwargs) 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 self.bullet = bullet
super().__init__() super().__init__(markdown)
class MarkdownOrderedListItem(MarkdownListItem): 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.code = code
self.lexer = lexer self.lexer = lexer
super().__init__() super().__init__(markdown)
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Static( yield Static(
@@ -629,20 +631,20 @@ class Markdown(Widget):
for token in parser.parse(markdown): for token in parser.parse(markdown):
if token.type == "heading_open": if token.type == "heading_open":
block_id += 1 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": elif token.type == "hr":
output.append(MarkdownHorizontalRule()) output.append(MarkdownHorizontalRule(self))
elif token.type == "paragraph_open": elif token.type == "paragraph_open":
stack.append(MarkdownParagraph()) stack.append(MarkdownParagraph(self))
elif token.type == "blockquote_open": elif token.type == "blockquote_open":
stack.append(MarkdownBlockQuote()) stack.append(MarkdownBlockQuote(self))
elif token.type == "bullet_list_open": elif token.type == "bullet_list_open":
stack.append(MarkdownBulletList()) stack.append(MarkdownBulletList(self))
elif token.type == "ordered_list_open": elif token.type == "ordered_list_open":
stack.append(MarkdownOrderedList()) stack.append(MarkdownOrderedList(self))
elif token.type == "list_item_open": elif token.type == "list_item_open":
if token.info: if token.info:
stack.append(MarkdownOrderedListItem(token.info)) stack.append(MarkdownOrderedListItem(self, token.info))
else: else:
item_count = sum( item_count = sum(
1 1
@@ -651,22 +653,23 @@ class Markdown(Widget):
) )
stack.append( stack.append(
MarkdownUnorderedListItem( MarkdownUnorderedListItem(
self.BULLETS[item_count % len(self.BULLETS)] self,
self.BULLETS[item_count % len(self.BULLETS)],
) )
) )
elif token.type == "table_open": elif token.type == "table_open":
stack.append(MarkdownTable()) stack.append(MarkdownTable(self))
elif token.type == "tbody_open": elif token.type == "tbody_open":
stack.append(MarkdownTBody()) stack.append(MarkdownTBody(self))
elif token.type == "thead_open": elif token.type == "thead_open":
stack.append(MarkdownTHead()) stack.append(MarkdownTHead(self))
elif token.type == "tr_open": elif token.type == "tr_open":
stack.append(MarkdownTR()) stack.append(MarkdownTR(self))
elif token.type == "th_open": elif token.type == "th_open":
stack.append(MarkdownTH()) stack.append(MarkdownTH(self))
elif token.type == "td_open": elif token.type == "td_open":
stack.append(MarkdownTD()) stack.append(MarkdownTD(self))
elif token.type.endswith("_close"): elif token.type.endswith("_close"):
block = stack.pop() block = stack.pop()
if token.type == "heading_close": if token.type == "heading_close":
@@ -742,12 +745,13 @@ class Markdown(Widget):
elif token.type == "fence": elif token.type == "fence":
output.append( output.append(
MarkdownFence( MarkdownFence(
self,
token.content.rstrip(), token.content.rstrip(),
token.info, token.info,
) )
) )
self.post_message(Markdown.TableOfContentsUpdated(table_of_contents)) self.post_message(Markdown.TableOfContentsUpdated(self, table_of_contents))
with self.app.batch_update(): with self.app.batch_update():
self.query("MarkdownBlock").remove() self.query("MarkdownBlock").remove()
self.mount_all(output) self.mount_all(output)
@@ -768,6 +772,27 @@ class MarkdownTableOfContents(Widget, can_focus_children=True):
table_of_contents = reactive["TableOfContentsType | None"](None, init=False) 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: def compose(self) -> ComposeResult:
tree: Tree = Tree("TOC") tree: Tree = Tree("TOC")
tree.show_root = False tree.show_root = False
@@ -804,8 +829,9 @@ class MarkdownTableOfContents(Widget, can_focus_children=True):
node_data = message.node.data node_data = message.node.data
if node_data is not None: if node_data is not None:
await self._post_message( 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): 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") self.set_class(show_table_of_contents, "-show-table-of-contents")
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield MarkdownTableOfContents() markdown = Markdown(parser_factory=self._parser_factory)
yield Markdown(parser_factory=self._parser_factory) yield MarkdownTableOfContents(markdown)
yield markdown
def _on_markdown_table_of_contents_updated( def _on_markdown_table_of_contents_updated(
self, message: Markdown.TableOfContentsUpdated self, message: Markdown.TableOfContentsUpdated