From 6980c470b075c05900a5bf91b8bf04d84051215a Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 20 Jun 2025 12:56:13 +0100 Subject: [PATCH] Expose code intend guides --- CHANGELOG.md | 1 + Makefile | 4 +- docs/examples/widgets/markdown.py | 57 +++++++++++++++++++----- docs/examples/widgets/markdown_viewer.py | 16 ++++++- docs/widget_gallery.md | 4 +- src/textual/widgets/_markdown.py | 14 ++++-- 6 files changed, 74 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3969b496..f7f02340c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Optimized startup https://github.com/Textualize/textual/pull/5869 - New blank visual which makes background faster to render (note this will break snapshots tests this version) https://github.com/Textualize/textual/pull/5869 +- Exposed `code_indent_guides` boolean on Markdown widget ## [3.4.0] - 2025-06-14 diff --git a/Makefile b/Makefile index c7e2e74fd..fd35774d2 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ docs-online-nav: .PHONY: docs-serve docs-serve: clean-screenshot-cache docs-online-nav - $(run) mkdocs serve --config-file mkdocs-nav-online.yml + TEXTUAL_THEME=dracula $(run) mkdocs serve --config-file mkdocs-nav-online.yml rm -f mkdocs-nav-online.yml .PHONY: docs-serve-offline @@ -76,7 +76,7 @@ clean-offline-docs: .PHONY: docs-deploy docs-deploy: clean-screenshot-cache docs-online-nav - $(run) mkdocs gh-deploy --config-file mkdocs-nav-online.yml + TEXTUAL_THEME=dracula $(run) mkdocs gh-deploy --config-file mkdocs-nav-online.yml rm -f mkdocs-nav-online.yml .PHONY: build diff --git a/docs/examples/widgets/markdown.py b/docs/examples/widgets/markdown.py index 1294b53f6..c51101d85 100644 --- a/docs/examples/widgets/markdown.py +++ b/docs/examples/widgets/markdown.py @@ -2,25 +2,58 @@ from textual.app import App, ComposeResult from textual.widgets import Markdown EXAMPLE_MARKDOWN = """\ -# Markdown Document +## Markdown -This is an example of Textual's `Markdown` widget. - -## Features - -Markdown syntax and extensions are supported. - -- Typography *emphasis*, **strong**, `inline code` etc. -- Headers -- Lists (bullet and ordered) +- Typography *emphasis*, **strong**, `inline code` etc. +- Headers +- Lists - Syntax highlighted code blocks -- Tables! +- Tables and more + +## Quotes + +> I must not fear. +> > Fear is the mind-killer. +> > Fear is the little-death that brings total obliteration. +> > I will face my fear. +> > > I will permit it to pass over me and through me. +> > > And when it has gone past, I will turn the inner eye to see its path. +> > > Where the fear has gone there will be nothing. Only I will remain. + +## Tables + +| Name | Type | Default | Description | +| --------------- | ------ | ------- | ---------------------------------- | +| `show_header` | `bool` | `True` | Show the table header | +| `fixed_rows` | `int` | `0` | Number of fixed rows | +| `fixed_columns` | `int` | `0` | Number of fixed columns | + +## Code blocks + +```python +def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + \"\"\"Iterate and generate a tuple with a flag for last value.\"\"\" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + for value in iter_values: + yield False, previous_value + previous_value = value + yield True, previous_value +``` + + """ class MarkdownExampleApp(App): + def compose(self) -> ComposeResult: - yield Markdown(EXAMPLE_MARKDOWN) + markdown = Markdown(EXAMPLE_MARKDOWN) + markdown.code_indent_guides = False + yield markdown if __name__ == "__main__": diff --git a/docs/examples/widgets/markdown_viewer.py b/docs/examples/widgets/markdown_viewer.py index 13843905a..e1bc99604 100644 --- a/docs/examples/widgets/markdown_viewer.py +++ b/docs/examples/widgets/markdown_viewer.py @@ -33,7 +33,7 @@ Tables are displayed in a DataTable widget. ## Code Blocks -Code blocks are syntax highlighted, with guidelines. +Code blocks are syntax highlighted. ```python class ListViewExample(App): @@ -45,12 +45,24 @@ class ListViewExample(App): ) yield Footer() ``` + +## Litany Against Fear + +I must not fear. +Fear is the mind-killer. +Fear is the little-death that brings total obliteration. +I will face my fear. +I will permit it to pass over me and through me. +And when it has gone past, I will turn the inner eye to see its path. +Where the fear has gone there will be nothing. Only I will remain. """ class MarkdownExampleApp(App): def compose(self) -> ComposeResult: - yield MarkdownViewer(EXAMPLE_MARKDOWN, show_table_of_contents=True) + markdown_viewer = MarkdownViewer(EXAMPLE_MARKDOWN, show_table_of_contents=True) + markdown_viewer.code_indent_guides = False + yield markdown_viewer if __name__ == "__main__": diff --git a/docs/widget_gallery.md b/docs/widget_gallery.md index f1d26d69f..4b0d89b9f 100644 --- a/docs/widget_gallery.md +++ b/docs/widget_gallery.md @@ -162,7 +162,7 @@ Display and interact with a Markdown document (adds a table of contents and brow [MarkdownViewer reference](./widgets/markdown_viewer.md){ .md-button .md-button--primary } -```{.textual path="docs/examples/widgets/markdown_viewer.py" columns="100" lines="42"} +```{.textual path="docs/examples/widgets/markdown_viewer.py" columns="140" lines="50" press="tab,down"} ``` ## Markdown @@ -172,7 +172,7 @@ Display a markdown document. [Markdown reference](./widgets/markdown.md){ .md-button .md-button--primary } -```{.textual path="docs/examples/widgets/markdown.py"} +```{.textual path="docs/examples/widgets/markdown.py" columns="140" lines="51"} ``` ## MaskedInput diff --git a/src/textual/widgets/_markdown.py b/src/textual/widgets/_markdown.py index ad796b173..2456e7120 100644 --- a/src/textual/widgets/_markdown.py +++ b/src/textual/widgets/_markdown.py @@ -478,7 +478,7 @@ class MarkdownTableContent(Widget): def render(self) -> Table: table = Table( expand=True, - box=box.SIMPLE_HEAVY, + box=box.SIMPLE_HEAD, style=self.rich_style, header_style=self.get_component_rich_style("markdown-table--header"), border_style=self.get_component_rich_style("markdown-table--lines"), @@ -504,7 +504,7 @@ class MarkdownTable(MarkdownBlock): DEFAULT_CSS = """ MarkdownTable { width: 100%; - background: $surface; + background: $background 80%; } """ @@ -635,7 +635,7 @@ class MarkdownFence(MarkdownBlock): self.code, lexer=self.lexer, word_wrap=False, - indent_guides=True, + indent_guides=self._markdown.code_indent_guides, padding=(1, 2), theme=self.theme, ) @@ -722,6 +722,9 @@ class Markdown(Widget): code_light_theme: reactive[str] = reactive("material-light") """The theme to use for code blocks when the App theme is light.""" + code_indent_guides: reactive[bool] = reactive(True) + """Should code fences display indent guides?""" + def __init__( self, markdown: str | None = None, @@ -1157,6 +1160,9 @@ class MarkdownViewer(VerticalScroll, can_focus=False, can_focus_children=True): """ show_table_of_contents = reactive(True) + """Show the table of contents?""" + code_indent_guides: reactive[bool] = reactive(True) + """Should code fences display indent guides?""" top_block = reactive("") navigator: var[Navigator] = var(Navigator) @@ -1241,7 +1247,7 @@ class MarkdownViewer(VerticalScroll, can_focus=False, can_focus_children=True): parser_factory=self._parser_factory, open_links=self._open_links ) markdown.can_focus = True - yield markdown + yield markdown.data_bind(MarkdownViewer.code_indent_guides) yield MarkdownTableOfContents(markdown) def _on_markdown_table_of_contents_updated(