|
7 | 7 | from markdown_it.token import Token |
8 | 8 | from rich import box |
9 | 9 | from rich.style import Style |
| 10 | +from rich.syntax import Syntax |
10 | 11 | from rich.table import Table |
11 | 12 | from rich.text import Text |
12 | 13 | from typing_extensions import TypeAlias |
@@ -498,22 +499,41 @@ class MarkdownFence(MarkdownBlock): |
498 | 499 | """ |
499 | 500 |
|
500 | 501 | def __init__(self, markdown: Markdown, code: str, lexer: str) -> None: |
| 502 | + super().__init__(markdown) |
501 | 503 | self.code = code |
502 | 504 | self.lexer = lexer |
503 | | - super().__init__(markdown) |
| 505 | + self.theme = ( |
| 506 | + self._markdown.code_dark_theme |
| 507 | + if self.app.dark |
| 508 | + else self._markdown.code_light_theme |
| 509 | + ) |
504 | 510 |
|
505 | | - def compose(self) -> ComposeResult: |
506 | | - from rich.syntax import Syntax |
| 511 | + def _block(self) -> Syntax: |
| 512 | + return Syntax( |
| 513 | + self.code, |
| 514 | + lexer=self.lexer, |
| 515 | + word_wrap=False, |
| 516 | + indent_guides=True, |
| 517 | + padding=(1, 2), |
| 518 | + theme=self.theme, |
| 519 | + ) |
| 520 | + |
| 521 | + def _on_mount(self, _: Mount) -> None: |
| 522 | + """Watch app theme switching.""" |
| 523 | + self.watch(self.app, "dark", self._retheme) |
| 524 | + |
| 525 | + def _retheme(self) -> None: |
| 526 | + """Rerender when the theme changes.""" |
| 527 | + self.theme = ( |
| 528 | + self._markdown.code_dark_theme |
| 529 | + if self.app.dark |
| 530 | + else self._markdown.code_light_theme |
| 531 | + ) |
| 532 | + self.get_child_by_type(Static).update(self._block()) |
507 | 533 |
|
| 534 | + def compose(self) -> ComposeResult: |
508 | 535 | yield Static( |
509 | | - Syntax( |
510 | | - self.code, |
511 | | - lexer=self.lexer, |
512 | | - word_wrap=False, |
513 | | - indent_guides=True, |
514 | | - padding=(1, 2), |
515 | | - theme="material", |
516 | | - ), |
| 536 | + self._block(), |
517 | 537 | expand=True, |
518 | 538 | shrink=False, |
519 | 539 | ) |
@@ -567,6 +587,12 @@ class Markdown(Widget): |
567 | 587 |
|
568 | 588 | BULLETS = ["\u25CF ", "▪ ", "‣ ", "• ", "⭑ "] |
569 | 589 |
|
| 590 | + code_dark_theme: reactive[str] = reactive("material") |
| 591 | + """The theme to use for code blocks when in [dark mode][textual.app.App.dark].""" |
| 592 | + |
| 593 | + code_light_theme: reactive[str] = reactive("material-light") |
| 594 | + """The theme to use for code blocks when in [light mode][textual.app.App.dark].""" |
| 595 | + |
570 | 596 | def __init__( |
571 | 597 | self, |
572 | 598 | markdown: str | None = None, |
@@ -653,6 +679,18 @@ def _on_mount(self, _: Mount) -> None: |
653 | 679 | if self._markdown is not None: |
654 | 680 | self.update(self._markdown) |
655 | 681 |
|
| 682 | + def _watch_code_dark_theme(self) -> None: |
| 683 | + """React to the dark theme being changed.""" |
| 684 | + if self.app.dark: |
| 685 | + for block in self.query(MarkdownFence): |
| 686 | + block._retheme() |
| 687 | + |
| 688 | + def _watch_code_light_theme(self) -> None: |
| 689 | + """React to the light theme being changed.""" |
| 690 | + if not self.app.dark: |
| 691 | + for block in self.query(MarkdownFence): |
| 692 | + block._retheme() |
| 693 | + |
656 | 694 | @staticmethod |
657 | 695 | def sanitize_location(location: str) -> tuple[Path, str]: |
658 | 696 | """Given a location, break out the path and any anchor. |
@@ -858,11 +896,7 @@ def update(self, markdown: str) -> AwaitComplete: |
858 | 896 | stack[-1].set_content(content) |
859 | 897 | elif token.type in ("fence", "code_block"): |
860 | 898 | (stack[-1]._blocks if stack else output).append( |
861 | | - MarkdownFence( |
862 | | - self, |
863 | | - token.content.rstrip(), |
864 | | - token.info, |
865 | | - ) |
| 899 | + MarkdownFence(self, token.content.rstrip(), token.info) |
866 | 900 | ) |
867 | 901 | else: |
868 | 902 | external = self.unhandled_token(token) |
|
0 commit comments