11"""Utilities for integrating prompt_toolkit with cmd2."""
22
33import re
4- import weakref
54from collections .abc import (
65 Callable ,
76 Iterable ,
87)
8+ from functools import lru_cache
99from typing import (
1010 TYPE_CHECKING ,
1111 Any ,
@@ -75,6 +75,7 @@ def pt_filter_style(text: str | ANSI) -> str | ANSI:
7575 return text if isinstance (text , ANSI ) else ANSI (text )
7676
7777
78+ @lru_cache (maxsize = 256 )
7879def rich_to_pt_color (color : "Color | None" ) -> str :
7980 """Convert a rich Color object to a prompt_toolkit color string."""
8081 if not color or color .is_default :
@@ -90,6 +91,7 @@ def rich_to_pt_color(color: "Color | None") -> str:
9091 return f"#{ c .red :02x} { c .green :02x} { c .blue :02x} "
9192
9293
94+ @lru_cache (maxsize = 1024 )
9395def rich_to_pt_style (rich_style : StyleType ) -> str :
9496 """Convert a rich Style object to a prompt_toolkit style string."""
9597 if not rich_style :
@@ -115,10 +117,8 @@ def rich_to_pt_style(rich_style: StyleType) -> str:
115117 if rich_style .blink is not None :
116118 parts .append ("blink" if rich_style .blink else "noblink" )
117119 if rich_style .reverse is not None :
118- # prompt-toolkit uses 'reverse'
119120 parts .append ("reverse" if rich_style .reverse else "noreverse" )
120121 if rich_style .conceal is not None :
121- # prompt-toolkit uses 'hidden' for Rich's 'conceal'
122122 parts .append ("hidden" if rich_style .conceal else "nohidden" )
123123 return " " .join (parts )
124124
@@ -263,18 +263,6 @@ def clear(self) -> None:
263263 self ._loaded_strings .clear ()
264264
265265
266- _lexers : "weakref.WeakSet[Cmd2Lexer]" = weakref .WeakSet ()
267-
268-
269- def _update_lexer_colors () -> None :
270- """Update colors for all active lexers."""
271- for lexer in _lexers :
272- lexer .set_colors ()
273-
274-
275- ru .register_theme_update_callback (_update_lexer_colors )
276-
277-
278266class Cmd2Lexer (Lexer ):
279267 """Lexer that highlights cmd2 command names, aliases, and macros."""
280268
@@ -289,18 +277,29 @@ def __init__(
289277 super ().__init__ ()
290278 self .cmd_app = cmd_app
291279
292- _lexers .add (self )
280+ # Cache key used to detect when theme styles have changed
281+ self ._style_key : tuple [Style , ...] = ()
293282 self .set_colors ()
294283
295284 def set_colors (self ) -> None :
296- """Update colors from the current rich theme."""
297- # Retrieve styles dynamically from the current theme
285+ """Synchronize lexer colors with the application theme."""
298286 theme = ru .get_theme ()
299- self .command_color = rich_to_pt_style (theme .styles .get (Cmd2Style .LEXER_COMMAND , "" ))
300- self .alias_color = rich_to_pt_style (theme .styles .get (Cmd2Style .LEXER_ALIAS , "" ))
301- self .macro_color = rich_to_pt_style (theme .styles .get (Cmd2Style .LEXER_MACRO , "" ))
302- self .flag_color = rich_to_pt_style (theme .styles .get (Cmd2Style .LEXER_FLAG , "" ))
303- self .argument_color = rich_to_pt_style (theme .styles .get (Cmd2Style .LEXER_ARGUMENT , "" ))
287+
288+ command_style = theme .styles .get (Cmd2Style .LEXER_COMMAND , Style .null ())
289+ alias_style = theme .styles .get (Cmd2Style .LEXER_ALIAS , Style .null ())
290+ macro_style = theme .styles .get (Cmd2Style .LEXER_MACRO , Style .null ())
291+ flag_style = theme .styles .get (Cmd2Style .LEXER_FLAG , Style .null ())
292+ argument_style = theme .styles .get (Cmd2Style .LEXER_ARGUMENT , Style .null ())
293+
294+ current_key = (command_style , alias_style , macro_style , flag_style , argument_style )
295+
296+ if current_key != self ._style_key :
297+ self ._style_key = current_key
298+ self .command_color = rich_to_pt_style (command_style )
299+ self .alias_color = rich_to_pt_style (alias_style )
300+ self .macro_color = rich_to_pt_style (macro_style )
301+ self .flag_color = rich_to_pt_style (flag_style )
302+ self .argument_color = rich_to_pt_style (argument_style )
304303
305304 def lex_document (self , document : Document ) -> Callable [[int ], Any ]:
306305 """Lex the document."""
@@ -309,6 +308,8 @@ def lex_document(self, document: Document) -> Callable[[int], Any]:
309308 exclude_tokens .update (self .cmd_app .statement_parser .terminators )
310309 arg_pattern = re .compile (r'(\s+)|(--?[^\s\'"]+)|("[^"]*"?|\'[^\']*\'?)|([^\s\'"]+)' )
311310
311+ self .set_colors ()
312+
312313 def highlight_args (text : str , tokens : list [tuple [str , str ]]) -> None :
313314 """Highlight arguments in a string."""
314315 for m in arg_pattern .finditer (text ):
0 commit comments