From d999c692616b8ec31fbe748a8b20e79b0577504d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 18 Mar 2023 06:19:05 -0500 Subject: [PATCH] Allow customizing the markdown parser (#2075) * Allow customizing the markdown parser For instance, code using Markdown might wish to create a markdown parser that does not parse embedded HTML: ```py def parser_factory(): parser = MarkdownIt("gfm-like") parser.options["html"] = False return parser ``` * blacken * Implement requested changes * fix AttributeError --- CHANGELOG.md | 3 +++ src/textual/widgets/_markdown.py | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e068003e..2dfb8feb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Added +- Added `parser_factory` argument to `Markdown` and `MarkdownViewer` constructors https://github.com/Textualize/textual/pull/2075 + ### Changed - Dropped "loading-indicator--dot" component style from LoadingIndicator https://github.com/Textualize/textual/pull/2050 diff --git a/src/textual/widgets/_markdown.py b/src/textual/widgets/_markdown.py index 767f9d6cc..9c7f3d992 100644 --- a/src/textual/widgets/_markdown.py +++ b/src/textual/widgets/_markdown.py @@ -1,7 +1,7 @@ from __future__ import annotations from pathlib import Path, PurePath -from typing import Iterable +from typing import Iterable, Callable from markdown_it import MarkdownIt from rich import box @@ -541,6 +541,7 @@ class Markdown(Widget): name: str | None = None, id: str | None = None, classes: str | None = None, + parser_factory: Callable[[], MarkdownIt] | None = None, ): """A Markdown widget. @@ -549,9 +550,11 @@ class Markdown(Widget): name: The name of the widget. id: The ID of the widget in the DOM. classes: The CSS classes of the widget. + parser_factory: A factory function to return a configured MarkdownIt instance. If `None`, a "gfm-like" parser is used. """ super().__init__(name=name, id=id, classes=classes) self._markdown = markdown + self._parser_factory = parser_factory class TableOfContentsUpdated(Message, bubble=True): """The table of contents was updated.""" @@ -606,7 +609,11 @@ class Markdown(Widget): """ output: list[MarkdownBlock] = [] stack: list[MarkdownBlock] = [] - parser = MarkdownIt("gfm-like") + parser = ( + MarkdownIt("gfm-like") + if self._parser_factory is None + else self._parser_factory() + ) content = Text() block_id: int = 0 @@ -831,6 +838,7 @@ class MarkdownViewer(VerticalScroll, can_focus=True, can_focus_children=True): name: str | None = None, id: str | None = None, classes: str | None = None, + parser_factory: Callable[[], MarkdownIt] | None = None, ): """Create a Markdown Viewer object. @@ -840,10 +848,12 @@ class MarkdownViewer(VerticalScroll, can_focus=True, can_focus_children=True): name: The name of the widget. id: The ID of the widget in the DOM. classes: The CSS classes of the widget. + parser_factory: A factory function to return a configured MarkdownIt instance. If `None`, a "gfm-like" parser is used. """ super().__init__(name=name, id=id, classes=classes) self.show_table_of_contents = show_table_of_contents self._markdown = markdown + self._parser_factory = parser_factory @property def document(self) -> Markdown: @@ -882,7 +892,7 @@ class MarkdownViewer(VerticalScroll, can_focus=True, can_focus_children=True): def compose(self) -> ComposeResult: yield MarkdownTableOfContents() - yield Markdown() + yield Markdown(parser_factory=self._parser_factory) def on_markdown_table_of_contents_updated( self, message: Markdown.TableOfContentsUpdated