2323from typing import cast
2424
2525from prompt_toolkit .styles import Style as PtStyle
26- from rich .style import StyleType
26+ from rich .style import (
27+ Style ,
28+ StyleType ,
29+ )
2730from rich .theme import Theme
2831
2932from .pt_utils import rich_to_pt_style
3538)
3639
3740# The application-wide theme, defined using Rich's styling system.
38- # Use get_theme() to access it and set_theme() to modify it.
41+ # Use get_theme() to access it.
42+ # Use reset_theme() and update_theme() to modify it.
3943_THEME : Theme | None = None
4044
4145# The prompt-toolkit version of the theme, synchronized from the Rich theme.
42- # Use get_pt_theme() to access it. This object is automatically updated whenever
43- # set_theme() is called.
46+ # Use get_pt_theme() to access it.
4447_PT_THEME : PtStyle | None = None
4548
4649# Maps style names to internal UI component names used by prompt-toolkit.
47- # This allows developers to use application-specific style names in set_theme ()
50+ # This allows developers to use application-specific style names in update_theme ()
4851# while ensuring the underlying prompt-toolkit UI is styled correctly.
4952# Use register_pt_mapping() and unregister_pt_mapping() to manage these mappings.
5053#
7275def get_theme () -> Theme :
7376 """Get the application-wide Rich theme. Initializes it on the first call."""
7477 if _THEME is None :
75- set_theme ()
78+ reset_theme ()
7679 return cast (Theme , _THEME )
7780
7881
7982def get_pt_theme () -> PtStyle :
8083 """Get the application-wide prompt-toolkit style. Initializes it on the first call."""
8184 if _PT_THEME is None :
82- set_theme ()
85+ reset_theme ()
8386 return cast (PtStyle , _PT_THEME )
8487
8588
86- def set_theme ( styles : Mapping [ str , StyleType ] | None = None ) -> None :
87- """Set the application-wide theme.
89+ def reset_theme ( ) -> None :
90+ """Reset the application-wide theme to its initial state .
8891
89- This function performs an in-place update of the existing Rich theme's
92+ This function performs an in-place reset of the existing Rich theme's
9093 styles. This ensures that any Console objects already using the theme
9194 will reflect the changes immediately without needing to be recreated.
9295
93- It also automatically synchronizes the prompt-toolkit theme for any
94- styles with registered prefixes or mapped UI components.
95-
96- Call set_theme() with no arguments to reset to the default theme.
97- This will clear any custom styles that were previously applied.
98-
99- :param styles: optional mapping of style names to styles
96+ Changes are automatically propagated to all synchronized components.
10097 """
10198 global _THEME # noqa: PLW0603
99+
100+ # Include default styles from cmd2, rich-argparse, and Rich.
101+ styles = DEFAULT_CMD2_STYLES .copy ()
102+ styles .update (DEFAULT_ARGPARSE_STYLES )
103+ default_theme = Theme (styles , inherit = True )
104+
102105 if _THEME is None :
103- _THEME = Theme ()
106+ # Initial assignment
107+ _THEME = default_theme
108+ else :
109+ # Perform in-place reset to preserve existing references
110+ _THEME .styles .clear ()
111+ _THEME .styles .update (default_theme .styles )
112+
113+ _sync_all ()
114+
115+
116+ def update_theme (styles : Mapping [str , StyleType ]) -> None :
117+ """Update the existing theme.
118+
119+ This function performs an in-place update of the existing Rich theme's
120+ styles. This ensures that any Console objects already using the theme
121+ will reflect the changes immediately without needing to be recreated.
122+
123+ Changes are automatically propagated to all synchronized components.
104124
105- # Start with a fresh copy of the default styles.
106- unparsed_styles : dict [str , StyleType ] = {}
107- unparsed_styles .update (_create_default_theme ().styles )
125+ :param styles: mapping of style names to styles
126+ """
127+ # Convert any string styles to Style objects
128+ parsed_styles = {name : style if isinstance (style , Style ) else Style .parse (style ) for name , style in styles .items ()}
129+
130+ # Perform in-place update to preserve existing references
131+ get_theme ().styles .update (parsed_styles )
108132
109- # Add the custom styles, which may contain unparsed strings
110- if styles is not None :
111- unparsed_styles .update (styles )
133+ _sync_all ()
112134
113- # Use Rich's Theme class to perform the parsing
114- parsed_styles = Theme (unparsed_styles ).styles
115135
116- # Perform the in-place update with the results
117- _THEME .styles .clear ()
118- _THEME .styles .update (parsed_styles )
136+ def _sync_all () -> None :
137+ """Propagate the global theme to rich-argparse and prompt-toolkit.
138+
139+ If the theme hasn't been initialized yet, this is a no-op.
140+ """
141+ if _THEME is None :
142+ return
119143
120- # Synchronize rich-argparse styles with the main application theme.
144+ # Synchronize rich-argparse styles
121145 for name in Cmd2HelpFormatter .styles .keys () & _THEME .styles .keys ():
122146 Cmd2HelpFormatter .styles [name ] = _THEME .styles [name ]
123147
@@ -126,11 +150,16 @@ def set_theme(styles: Mapping[str, StyleType] | None = None) -> None:
126150
127151
128152def _sync_pt_theme () -> None :
129- """Build a new global PT style object based on the current Rich theme."""
130- theme = get_theme ()
153+ """Build a new global prompt-toolkit style object based on the current Rich theme.
154+
155+ If the theme hasn't been initialized yet, this is a no-op.
156+ """
157+ if _THEME is None :
158+ return
159+
131160 style_rules : list [tuple [str , str ]] = []
132161
133- for name , rich_style in theme .styles .items ():
162+ for name , rich_style in _THEME .styles .items ():
134163 # Only synchronize if it has a registered prefix or mapped UI component.
135164 is_framework_style = any (name .startswith (p ) for p in _SYNCHRONIZED_PREFIXES )
136165 is_mapped_style = name in _PT_UI_MAP
@@ -149,16 +178,6 @@ def _sync_pt_theme() -> None:
149178 _PT_THEME = PtStyle (style_rules )
150179
151180
152- def _create_default_theme () -> Theme :
153- """Create a default theme for the application.
154-
155- This theme combines the default styles from cmd2, rich-argparse, and Rich.
156- """
157- app_styles = DEFAULT_CMD2_STYLES .copy ()
158- app_styles .update (DEFAULT_ARGPARSE_STYLES )
159- return Theme (app_styles , inherit = True )
160-
161-
162181def register_pt_mapping (style_name : str , pt_ui_names : str | Iterable [str ]) -> None :
163182 """Map a Rich theme style name to one or more prompt-toolkit UI components.
164183
0 commit comments