diff --git a/CHANGELOG.md b/CHANGELOG.md index ca172cb486..846f70b2f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fixed infinite loop in `Widget.anchor` https://github.com/Textualize/textual/pull/5290 +- Restores the ability to supply console markup to command list https://github.com/Textualize/textual/pull/5294 - Fixed delayed App Resize event https://github.com/Textualize/textual/pull/5296 ## [0.87.1] - 2024-11-24 diff --git a/src/textual/color.py b/src/textual/color.py index c5f5abcf3f..e0ddf3b944 100644 --- a/src/textual/color.py +++ b/src/textual/color.py @@ -40,6 +40,7 @@ from rich.color import Color as RichColor from rich.color import ColorType from rich.color_triplet import ColorTriplet +from rich.terminal_theme import TerminalTheme from typing_extensions import Final from textual._color_constants import ANSI_COLORS, COLOR_NAME_TO_RGB @@ -176,18 +177,21 @@ def automatic(cls, alpha_percentage: float = 100.0) -> Color: return cls(0, 0, 0, alpha_percentage / 100.0, auto=True) @classmethod - def from_rich_color(cls, rich_color: RichColor | None) -> Color: + def from_rich_color( + cls, rich_color: RichColor | None, theme: TerminalTheme | None = None + ) -> Color: """Create a new color from Rich's Color class. Args: rich_color: An instance of [Rich color][rich.color.Color]. + theme: Optional Rich [terminal theme][rich.terminal_theme.TerminalTheme]. Returns: A new Color instance. """ if rich_color is None: return TRANSPARENT - r, g, b = rich_color.get_truecolor() + r, g, b = rich_color.get_truecolor(theme) return cls(r, g, b) @classmethod diff --git a/src/textual/command.py b/src/textual/command.py index 68677ba001..9e28eddb31 100644 --- a/src/textual/command.py +++ b/src/textual/command.py @@ -1120,7 +1120,7 @@ def build_prompt() -> Iterable[Content]: help_style = VisualStyle.from_styles( self.get_component_styles("command-palette--help-text") ) - yield Content.styled(hit.help, help_style) + yield Content.from_rich_text(hit.help).stylize_before(help_style) prompt = Content("\n").join(build_prompt()) diff --git a/src/textual/content.py b/src/textual/content.py index 6df51e99ce..7b402de45e 100644 --- a/src/textual/content.py +++ b/src/textual/content.py @@ -19,9 +19,11 @@ from rich.cells import set_cell_size from rich.console import OverflowMethod from rich.segment import Segment, Segments +from rich.terminal_theme import TerminalTheme from rich.text import Text from textual._cells import cell_len +from textual._context import active_app from textual._loop import loop_last from textual.color import Color from textual.css.types import TextAlign @@ -180,15 +182,28 @@ def from_rich_text( New Content. """ if isinstance(text, str): - return cls(text, align=align, no_wrap=no_wrap, ellipsis=ellipsis) - spans = [ - Span( - start, - end, - style if isinstance(style, str) else Style.from_rich_style(style), - ) - for start, end, style in text._spans - ] + text = Text.from_markup(text) + if text._spans: + ansi_theme: TerminalTheme | None + try: + ansi_theme = active_app.get().ansi_theme + except LookupError: + ansi_theme = None + spans = [ + Span( + start, + end, + ( + style + if isinstance(style, str) + else Style.from_rich_style(style, ansi_theme) + ), + ) + for start, end, style in text._spans + ] + else: + spans = [] + return cls( text.plain, spans, @@ -681,9 +696,15 @@ def render( return if parse_style is None: + app = active_app.get() + # TODO: Update when we add Content.from_markup def get_style(style: str, /) -> Style: - return TRANSPARENT_STYLE if isinstance(style, str) else style + return ( + Style.from_rich_style(app.console.get_style(style), app.ansi_theme) + if isinstance(style, str) + else style + ) else: get_style = parse_style diff --git a/src/textual/visual.py b/src/textual/visual.py index ed21c26fe1..29303b8d3a 100644 --- a/src/textual/visual.py +++ b/src/textual/visual.py @@ -13,6 +13,7 @@ from rich.protocol import is_renderable, rich_cast from rich.segment import Segment from rich.style import Style as RichStyle +from rich.terminal_theme import TerminalTheme from rich.text import Text from textual._context import active_app @@ -141,18 +142,21 @@ def __add__(self, other: object) -> Style: return new_style @classmethod - def from_rich_style(cls, rich_style: RichStyle) -> Style: + def from_rich_style( + cls, rich_style: RichStyle, theme: TerminalTheme | None = None + ) -> Style: """Build a Style from a (Rich) Style. Args: rich_style: A Rich Style object. + theme: Optional Rich [terminal theme][rich.terminal_theme.TerminalTheme]. Returns: New Style. """ return Style( - Color.from_rich_color(rich_style.bgcolor), - Color.from_rich_color(rich_style.color), + Color.from_rich_color(rich_style.bgcolor, theme), + Color.from_rich_color(rich_style.color, theme), bold=rich_style.bold, dim=rich_style.dim, italic=rich_style.italic, diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_markup_command_list.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_markup_command_list.svg new file mode 100644 index 0000000000..3e2a84d61c --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_markup_command_list.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MyApp + + + + + + + + + + + + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +🔎Search for commands… + + +  Hello World +Help text +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index d87c730697..b2fcd41a1d 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -10,6 +10,7 @@ from textual import events, on from textual.app import App, ComposeResult from textual.binding import Binding, Keymap +from textual.command import SimpleCommand from textual.containers import Center, Container, Grid, Middle, Vertical, VerticalScroll from textual.pilot import Pilot from textual.renderables.gradient import LinearGradient @@ -2751,6 +2752,23 @@ async def run_before(pilot: Pilot) -> None: snap_compare(TallSelectApp(), run_before=run_before) + +def test_markup_command_list(snap_compare): + """Regression test for https://github.com/Textualize/textual/issues/5276 + You should see a command list, with console markup applied to the action name and help text.""" + + class MyApp(App): + def on_mount(self) -> None: + self.search_commands( + [ + SimpleCommand( + "Hello [u green]World", lambda: None, "Help [u red]text" + ) + ] + ) + + snap_compare(MyApp()) + def test_app_resize_order(snap_compare): """Regression test for https://github.com/Textualize/textual/issues/5284 You should see a placeholder with text "BAR", focused and scrolled down so it fills the screen. @@ -2791,3 +2809,4 @@ def on_resize(self) -> None: self.add_class("narrow") snap_compare(SCApp()) +