mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Expose code intend guides
This commit is contained in:
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
- Optimized startup https://github.com/Textualize/textual/pull/5869
|
- 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
|
- 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
|
## [3.4.0] - 2025-06-14
|
||||||
|
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -52,7 +52,7 @@ docs-online-nav:
|
|||||||
|
|
||||||
.PHONY: docs-serve
|
.PHONY: docs-serve
|
||||||
docs-serve: clean-screenshot-cache docs-online-nav
|
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
|
rm -f mkdocs-nav-online.yml
|
||||||
|
|
||||||
.PHONY: docs-serve-offline
|
.PHONY: docs-serve-offline
|
||||||
@@ -76,7 +76,7 @@ clean-offline-docs:
|
|||||||
|
|
||||||
.PHONY: docs-deploy
|
.PHONY: docs-deploy
|
||||||
docs-deploy: clean-screenshot-cache docs-online-nav
|
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
|
rm -f mkdocs-nav-online.yml
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
|
|||||||
@@ -2,25 +2,58 @@ from textual.app import App, ComposeResult
|
|||||||
from textual.widgets import Markdown
|
from textual.widgets import Markdown
|
||||||
|
|
||||||
EXAMPLE_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.
|
- Typography *emphasis*, **strong**, `inline code` etc.
|
||||||
- Headers
|
- Headers
|
||||||
- Lists (bullet and ordered)
|
- Lists
|
||||||
- Syntax highlighted code blocks
|
- 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):
|
class MarkdownExampleApp(App):
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Markdown(EXAMPLE_MARKDOWN)
|
markdown = Markdown(EXAMPLE_MARKDOWN)
|
||||||
|
markdown.code_indent_guides = False
|
||||||
|
yield markdown
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Tables are displayed in a DataTable widget.
|
|||||||
|
|
||||||
## Code Blocks
|
## Code Blocks
|
||||||
|
|
||||||
Code blocks are syntax highlighted, with guidelines.
|
Code blocks are syntax highlighted.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class ListViewExample(App):
|
class ListViewExample(App):
|
||||||
@@ -45,12 +45,24 @@ class ListViewExample(App):
|
|||||||
)
|
)
|
||||||
yield Footer()
|
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):
|
class MarkdownExampleApp(App):
|
||||||
def compose(self) -> ComposeResult:
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -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 }
|
[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
|
## Markdown
|
||||||
@@ -172,7 +172,7 @@ Display a markdown document.
|
|||||||
[Markdown reference](./widgets/markdown.md){ .md-button .md-button--primary }
|
[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
|
## MaskedInput
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ class MarkdownTableContent(Widget):
|
|||||||
def render(self) -> Table:
|
def render(self) -> Table:
|
||||||
table = Table(
|
table = Table(
|
||||||
expand=True,
|
expand=True,
|
||||||
box=box.SIMPLE_HEAVY,
|
box=box.SIMPLE_HEAD,
|
||||||
style=self.rich_style,
|
style=self.rich_style,
|
||||||
header_style=self.get_component_rich_style("markdown-table--header"),
|
header_style=self.get_component_rich_style("markdown-table--header"),
|
||||||
border_style=self.get_component_rich_style("markdown-table--lines"),
|
border_style=self.get_component_rich_style("markdown-table--lines"),
|
||||||
@@ -504,7 +504,7 @@ class MarkdownTable(MarkdownBlock):
|
|||||||
DEFAULT_CSS = """
|
DEFAULT_CSS = """
|
||||||
MarkdownTable {
|
MarkdownTable {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: $surface;
|
background: $background 80%;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -635,7 +635,7 @@ class MarkdownFence(MarkdownBlock):
|
|||||||
self.code,
|
self.code,
|
||||||
lexer=self.lexer,
|
lexer=self.lexer,
|
||||||
word_wrap=False,
|
word_wrap=False,
|
||||||
indent_guides=True,
|
indent_guides=self._markdown.code_indent_guides,
|
||||||
padding=(1, 2),
|
padding=(1, 2),
|
||||||
theme=self.theme,
|
theme=self.theme,
|
||||||
)
|
)
|
||||||
@@ -722,6 +722,9 @@ class Markdown(Widget):
|
|||||||
code_light_theme: reactive[str] = reactive("material-light")
|
code_light_theme: reactive[str] = reactive("material-light")
|
||||||
"""The theme to use for code blocks when the App theme is 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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
markdown: str | None = None,
|
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_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("")
|
top_block = reactive("")
|
||||||
|
|
||||||
navigator: var[Navigator] = var(Navigator)
|
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
|
parser_factory=self._parser_factory, open_links=self._open_links
|
||||||
)
|
)
|
||||||
markdown.can_focus = True
|
markdown.can_focus = True
|
||||||
yield markdown
|
yield markdown.data_bind(MarkdownViewer.code_indent_guides)
|
||||||
yield MarkdownTableOfContents(markdown)
|
yield MarkdownTableOfContents(markdown)
|
||||||
|
|
||||||
def _on_markdown_table_of_contents_updated(
|
def _on_markdown_table_of_contents_updated(
|
||||||
|
|||||||
Reference in New Issue
Block a user