from __future__ import annotations from dataclasses import dataclass, field from functools import partial from typing import Callable from textual.command import DiscoveryHit, Hit, Hits, Provider from textual.design import ColorSystem @dataclass class Theme: """Defines a theme for the application.""" name: str """The name of the theme. After registering a theme with `App.register_theme`, you can set the theme with `App.theme = theme_name`. This will immediately apply the theme's colors to your application. """ primary: str secondary: str | None = None warning: str | None = None error: str | None = None success: str | None = None accent: str | None = None foreground: str | None = None background: str | None = None surface: str | None = None panel: str | None = None boost: str | None = None dark: bool = True luminosity_spread: float = 0.15 text_alpha: float = 0.95 variables: dict[str, str] = field(default_factory=dict) def to_color_system(self) -> ColorSystem: """ Create a ColorSystem instance from this Theme. Returns: A ColorSystem instance with attributes copied from this Theme. """ return ColorSystem( primary=self.primary, secondary=self.secondary, warning=self.warning, error=self.error, success=self.success, accent=self.accent, foreground=self.foreground, background=self.background, surface=self.surface, panel=self.panel, boost=self.boost, dark=self.dark, luminosity_spread=self.luminosity_spread, text_alpha=self.text_alpha, variables=self.variables, ) BUILTIN_THEMES: dict[str, Theme] = { "textual-dark": Theme( name="textual-dark", primary="#0178D4", secondary="#004578", accent="#ffa62b", warning="#ffa62b", error="#ba3c5b", success="#4EBF71", foreground="#e0e0e0", ), "textual-light": Theme( name="textual-light", primary="#004578", secondary="#0178D4", accent="#ffa62b", warning="#ffa62b", error="#ba3c5b", success="#4EBF71", surface="#D8D8D8", panel="#D0D0D0", background="#E0E0E0", dark=False, variables={ "footer-key-foreground": "#0178D4", }, ), "nord": Theme( name="nord", primary="#88C0D0", secondary="#81A1C1", accent="#B48EAD", foreground="#D8DEE9", background="#2E3440", success="#A3BE8C", warning="#EBCB8B", error="#BF616A", surface="#3B4252", panel="#434C5E", variables={ "block-cursor-background": "#88C0D0", "block-cursor-foreground": "#2E3440", "block-cursor-text-style": "none", "footer-key-foreground": "#88C0D0", "input-selection-background": "#81a1c1 35%", "button-color-foreground": "#2E3440", "button-focus-text-style": "reverse", }, ), "gruvbox": Theme( name="gruvbox", primary="#85A598", secondary="#A89A85", warning="#fe8019", error="#fb4934", success="#b8bb26", accent="#fabd2f", foreground="#fbf1c7", background="#282828", surface="#3c3836", panel="#504945", variables={ "block-cursor-foreground": "#fbf1c7", "input-selection-background": "#689d6a40", "button-color-foreground": "#282828", }, ), "catppuccin-mocha": Theme( name="catppuccin-mocha", primary="#F5C2E7", secondary="#cba6f7", warning="#FAE3B0", error="#F28FAD", success="#ABE9B3", accent="#fab387", foreground="#cdd6f4", background="#181825", surface="#313244", panel="#45475a", variables={ "input-cursor-foreground": "#11111b", "input-cursor-background": "#f5e0dc", "input-selection-background": "#9399b2 30%", "border": "#b4befe", "border-blurred": "#585b70", "footer-background": "#45475a", "block-cursor-foreground": "#1e1e2e", "block-cursor-text-style": "none", "button-color-foreground": "#181825", }, ), "textual-ansi": Theme( name="textual-ansi", primary="ansi_blue", secondary="ansi_cyan", warning="ansi_yellow", error="ansi_red", success="ansi_green", accent="ansi_bright_blue", foreground="ansi_default", background="ansi_default", surface="ansi_default", panel="ansi_default", boost="ansi_default", dark=False, variables={ "block-cursor-text-style": "b", "block-cursor-blurred-text-style": "i", "input-selection-background": "ansi_blue", "input-cursor-text-style": "reverse", "scrollbar": "ansi_blue", "border-blurred": "ansi_blue", "border": "ansi_bright_blue", }, ), "dracula": Theme( name="dracula", primary="#BD93F9", secondary="#6272A4", warning="#FFB86C", error="#FF5555", success="#50FA7B", accent="#FF79C6", background="#282A36", surface="#2B2E3B", panel="#313442", foreground="#F8F8F2", variables={ "button-color-foreground": "#282A36", }, ), "tokyo-night": Theme( name="tokyo-night", primary="#BB9AF7", secondary="#7AA2F7", warning="#E0AF68", # Yellow error="#F7768E", # Red success="#9ECE6A", # Green accent="#FF9E64", # Orange foreground="#a9b1d6", background="#1A1B26", # Background surface="#24283B", # Surface panel="#414868", # Panel variables={ "button-color-foreground": "#24283B", }, ), "monokai": Theme( name="monokai", primary="#AE81FF", secondary="#F92672", accent="#66D9EF", warning="#FD971F", error="#F92672", success="#A6E22E", foreground="#d6d6d6", background="#272822", surface="#2e2e2e", panel="#3E3D32", variables={ "foreground-muted": "#797979", "input-selection-background": "#575b6190", "button-color-foreground": "#272822", }, ), "flexoki": Theme( name="flexoki", primary="#205EA6", # blue secondary="#24837B", # cyan warning="#AD8301", # yellow error="#AF3029", # red success="#66800B", # green accent="#9B76C8", # purple light background="#100F0F", # base.black surface="#1C1B1A", # base.950 panel="#282726", # base.900 foreground="#FFFCF0", # base.paper variables={ "input-cursor-foreground": "#5E409D", "input-cursor-background": "#FFFCF0", "input-selection-background": "#6F6E69 35%", # base.600 with opacity "button-color-foreground": "#FFFCF0", }, ), "catppuccin-latte": Theme( name="catppuccin-latte", secondary="#DC8A78", primary="#8839EF", warning="#DF8E1D", error="#D20F39", success="#40A02B", accent="#FE640B", foreground="#4C4F69", background="#EFF1F5", surface="#E6E9EF", panel="#CCD0DA", dark=False, variables={ "button-color-foreground": "#EFF1F5", }, ), "solarized-light": Theme( name="solarized-light", primary="#268bd2", secondary="#2aa198", warning="#cb4b16", error="#dc322f", success="#859900", accent="#6c71c4", foreground="#586e75", background="#fdf6e3", surface="#eee8d5", panel="#eee8d5", dark=False, variables={ "button-color-foreground": "#fdf6e3", "footer-background": "#268bd2", "footer-key-foreground": "#fdf6e3", "footer-description-foreground": "#fdf6e3", }, ), "solarized-dark": Theme( name="solarized-dark", primary="#268bd2", secondary="#2aa198", warning="#cb4b16", error="#dc322f", success="#859900", accent="#6c71c4", background="#002b36", surface="#073642", panel="#073642", foreground="#839496", dark=True, variables={ "button-color-foreground": "#fdf6e3", "footer-background": "#268bd2", "footer-key-foreground": "#fdf6e3", "footer-description-foreground": "#fdf6e3", "input-selection-background": "#073642", # Base02 }, ), "rose-pine": Theme( name="rose-pine", primary="#c4a7e7", secondary="#31748f", warning="#f6c177", error="#eb6f92", success="#9ccfd8", accent="#ebbcba", foreground="#e0def4", background="#191724", surface="#1f1d2e", panel="#26233a", dark=True, variables={ "input-cursor-background": "#f4ede8", "input-selection-background": "#403d52", "border": "#524f67", "border-blurred": "#6e6a86", "footer-background": "#26233a", "block-cursor-foreground": "#191724", "block-cursor-text-style": "none", "block-cursor-background": "#c4a7e7", }, ), "rose-pine-moon": Theme( name="rose-pine-moon", primary="#c4a7e7", secondary="#3e8fb0", warning="#f6c177", error="#eb6f92", success="#9ccfd8", accent="#ea9a97", foreground="#e0def4", background="#232136", surface="#2a273f", panel="#393552", dark=True, variables={ "input-cursor-background": "#f4ede8", "input-selection-background": "#44415a", "border": "#56526e", "border-blurred": "#6e6a86", "footer-background": "#393552", "block-cursor-foreground": "#232136", "block-cursor-text-style": "none", "block-cursor-background": "#c4a7e7", }, ), "rose-pine-dawn": Theme( name="rose-pine-dawn", primary="#907aa9", secondary="#286983", warning="#ea9d34", error="#b4637a", success="#56949f", accent="#d7827e", foreground="#575279", background="#faf4ed", surface="#fffaf3", panel="#f2e9e1", dark=False, variables={ "input-cursor-background": "#575279", "input-selection-background": "#dfdad9", "border": "#cecacd", "border-blurred": "#9893a5", "footer-background": "#f2e9e1", "block-cursor-foreground": "#faf4ed", "block-cursor-text-style": "none", "block-cursor-background": "#575279", }, ), } class ThemeProvider(Provider): """A provider for themes.""" @property def commands(self) -> list[tuple[str, Callable[[], None]]]: themes = self.app.available_themes def set_app_theme(name: str) -> None: self.app.theme = name return [ (theme.name, partial(set_app_theme, theme.name)) for theme in themes.values() if theme.name != "textual-ansi" ] async def discover(self) -> Hits: for command in self.commands: yield DiscoveryHit(*command) async def search(self, query: str) -> Hits: matcher = self.matcher(query) for name, callback in self.commands: if (match := matcher.match(name)) > 0: yield Hit( match, matcher.highlight(name), callback, )