diff --git a/CHANGELOG.md b/CHANGELOG.md index a5198ed00..eb453c8d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -151,6 +151,8 @@ prompt is displayed. - Added `Cmd2ArgumentParser.output_to()` context manager to temporarily set the output stream during `argparse` operations. This is helpful for directing output for functions like `parse_args()`, which default to `sys.stdout` and lack a `file` argument. + - Added ability to customize `prompt-toolkit` completion menu colors by overriding + `Cmd2Style.COMPLETION_MENU_ITEM` and `Cmd2Style.COMPLETION_MENU_META` in the `cmd2` theme. ## 3.5.1 (April 24, 2026) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index e271f721c..873d831af 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -80,6 +80,8 @@ from prompt_toolkit.output import DummyOutput, create_output from prompt_toolkit.patch_stdout import patch_stdout from prompt_toolkit.shortcuts import CompleteStyle, PromptSession, choice, set_title +from prompt_toolkit.styles import DynamicStyle +from prompt_toolkit.styles import Style as PtStyle from rich.console import ( Group, JustifyMethod, @@ -192,6 +194,7 @@ def __init__(self, msg: str = "") -> None: Cmd2History, Cmd2Lexer, pt_filter_style, + rich_to_pt_style, ) from .utils import ( Settable, @@ -523,6 +526,10 @@ def __init__( self._persistent_history_length = persistent_history_length self._initialize_history(persistent_history_file) + # Cache for prompt_toolkit completion menu styles + self._cached_pt_style: PtStyle | None = None + self._cached_pt_style_params: tuple[StyleType, StyleType] | None = None + # Create the main PromptSession self.bottom_toolbar = bottom_toolbar self.main_session = self._create_main_session(auto_suggest, completekey) @@ -716,6 +723,30 @@ def _should_continue_multiline(self) -> bool: # No macro found or already processed. The statement is complete. return False + def _get_pt_style(self) -> "PtStyle": + """Return the prompt_toolkit style for the completion menu.""" + theme = ru.get_theme() + rich_item_style = theme.styles.get(Cmd2Style.COMPLETION_MENU_ITEM, "") + rich_meta_style = theme.styles.get(Cmd2Style.COMPLETION_MENU_META, "") + + current_params = (rich_item_style, rich_meta_style) + if self._cached_pt_style is not None and self._cached_pt_style_params == current_params: + return self._cached_pt_style + + item_style = rich_to_pt_style(rich_item_style) + meta_style = rich_to_pt_style(rich_meta_style) + + self._cached_pt_style_params = current_params + self._cached_pt_style = PtStyle.from_dict( + { + "completion-menu.completion.current": item_style, + "completion-menu.meta.completion.current": meta_style, + "completion-menu.multi-column-meta": meta_style, + } + ) + + return self._cached_pt_style + def _create_main_session(self, auto_suggest: bool, completekey: str) -> PromptSession[str]: """Create and return the main PromptSession for the application. @@ -757,6 +788,7 @@ def _(event: Any) -> None: # pragma: no cover "multiline": filters.Condition(self._should_continue_multiline), "prompt_continuation": self.continuation_prompt, "rprompt": self.get_rprompt, + "style": DynamicStyle(self._get_pt_style), } if self.stdin.isatty() and self.stdout.isatty(): @@ -3569,6 +3601,7 @@ def read_input( key_bindings=self.main_session.key_bindings, input=self.main_session.input, output=self.main_session.output, + style=DynamicStyle(self._get_pt_style), ) return self._read_raw_input(prompt, temp_session) @@ -3587,6 +3620,7 @@ def read_secret( temp_session: PromptSession[str] = PromptSession( input=self.main_session.input, output=self.main_session.output, + style=DynamicStyle(self._get_pt_style), ) return self._read_raw_input(prompt, temp_session, is_password=True) diff --git a/cmd2/pt_utils.py b/cmd2/pt_utils.py index 8b7da28da..cc954c08d 100644 --- a/cmd2/pt_utils.py +++ b/cmd2/pt_utils.py @@ -20,6 +20,7 @@ from prompt_toolkit.formatted_text import ANSI from prompt_toolkit.history import History from prompt_toolkit.lexers import Lexer +from rich.style import Style, StyleType from . import ( constants, @@ -27,13 +28,36 @@ ) from . import rich_utils as ru from . import string_utils as su +from .styles import Cmd2Style if TYPE_CHECKING: # pragma: no cover + from rich.color import Color + from .cmd2 import Cmd BASE_DELIMITERS = " \t\n" + "".join(constants.QUOTES) + "".join(constants.REDIRECTION_CHARS) +# prompt_toolkit accepts these standard ANSI color names directly +ANSI_NAMES = ( + "ansiblack", + "ansired", + "ansigreen", + "ansiyellow", + "ansiblue", + "ansimagenta", + "ansicyan", + "ansiwhite", + "ansibrightblack", + "ansibrightred", + "ansibrightgreen", + "ansibrightyellow", + "ansibrightblue", + "ansibrightmagenta", + "ansibrightcyan", + "ansibrightwhite", +) + def pt_filter_style(text: str | ANSI) -> str | ANSI: """Strip styles if disallowed by ru.ALLOW_STYLE. Otherwise return an ANSI object. @@ -50,6 +74,46 @@ def pt_filter_style(text: str | ANSI) -> str | ANSI: return text if isinstance(text, ANSI) else ANSI(text) +def rich_to_pt_color(color: "Color | None") -> str: + """Convert a rich Color object to a prompt_toolkit color string.""" + if not color or color.is_default: + return "default" + + # Use prompt_toolkit's 16 standard ansi color names if applicable. + # This prevents overriding terminal themes with absolute RGB values. + if color.number is not None and 0 <= color.number <= 15: + return ANSI_NAMES[color.number] + + # For 8-bit and truecolor, we fallback to hex RGB strings which prompt-toolkit supports natively + c = color.get_truecolor() + return f"#{c.red:02x}{c.green:02x}{c.blue:02x}" + + +def rich_to_pt_style(rich_style: StyleType) -> str: + """Convert a rich Style object to a prompt_toolkit style string.""" + if not rich_style: + return "" + + if isinstance(rich_style, str): + rich_style = Style.parse(rich_style) + + parts = ["noreverse"] + + fg_color = rich_to_pt_color(rich_style.color) + parts.append(f"fg:{fg_color}") + + bg_color = rich_to_pt_color(rich_style.bgcolor) + parts.append(f"bg:{bg_color}") + + if rich_style.bold is not None: + parts.append("bold" if rich_style.bold else "nobold") + if rich_style.italic is not None: + parts.append("italic" if rich_style.italic else "noitalic") + if rich_style.underline is not None: + parts.append("underline" if rich_style.underline else "nounderline") + return " ".join(parts) + + class Cmd2Completer(Completer): """Completer that delegates to cmd2's completion logic.""" @@ -196,28 +260,21 @@ class Cmd2Lexer(Lexer): def __init__( self, cmd_app: "Cmd", - command_color: str = "ansigreen", - alias_color: str = "ansicyan", - macro_color: str = "ansimagenta", - flag_color: str = "ansired", - argument_color: str = "ansiyellow", ) -> None: """Initialize the Lexer. :param cmd_app: cmd2.Cmd instance - :param command_color: color to use for commands, defaults to 'ansigreen' - :param alias_color: color to use for aliases, defaults to 'ansicyan' - :param macro_color: color to use for macros, defaults to 'ansimagenta' - :param flag_color: color to use for flags, defaults to 'ansired' - :param argument_color: color to use for arguments, defaults to 'ansiyellow' """ super().__init__() self.cmd_app = cmd_app - self.command_color = command_color - self.alias_color = alias_color - self.macro_color = macro_color - self.flag_color = flag_color - self.argument_color = argument_color + + # Retrieve styles dynamically from the current theme + theme = ru.get_theme() + self.command_color = rich_to_pt_style(theme.styles.get(Cmd2Style.LEXER_COMMAND, "")) + self.alias_color = rich_to_pt_style(theme.styles.get(Cmd2Style.LEXER_ALIAS, "")) + self.macro_color = rich_to_pt_style(theme.styles.get(Cmd2Style.LEXER_MACRO, "")) + self.flag_color = rich_to_pt_style(theme.styles.get(Cmd2Style.LEXER_FLAG, "")) + self.argument_color = rich_to_pt_style(theme.styles.get(Cmd2Style.LEXER_ARGUMENT, "")) def lex_document(self, document: Document) -> Callable[[int], Any]: """Lex the document.""" diff --git a/cmd2/styles.py b/cmd2/styles.py index 15489d46e..995979453 100644 --- a/cmd2/styles.py +++ b/cmd2/styles.py @@ -51,9 +51,16 @@ class Cmd2Style(StrEnum): """ COMMAND_LINE = "cmd2.example" # Command line examples in help text + COMPLETION_MENU_ITEM = "cmd2.completion_menu.item" # Selected completion item + COMPLETION_MENU_META = "cmd2.completion_menu.meta" # Selected completion help/meta text ERROR = "cmd2.error" # Error text (used by perror()) HELP_HEADER = "cmd2.help.header" # Help table header text HELP_LEADER = "cmd2.help.leader" # Text right before the help tables are listed + LEXER_COMMAND = "cmd2.lexer.command" # Lexer color for commands + LEXER_ALIAS = "cmd2.lexer.alias" # Lexer color for aliases + LEXER_MACRO = "cmd2.lexer.macro" # Lexer color for macros + LEXER_FLAG = "cmd2.lexer.flag" # Lexer color for flags + LEXER_ARGUMENT = "cmd2.lexer.argument" # Lexer color for arguments SUCCESS = "cmd2.success" # Success text (used by psuccess()) TABLE_BORDER = "cmd2.table_border" # Applied to cmd2's table borders WARNING = "cmd2.warning" # Warning text (used by pwarning()) @@ -63,9 +70,16 @@ class Cmd2Style(StrEnum): # Tightly coupled with the Cmd2Style enum. DEFAULT_CMD2_STYLES: dict[str, StyleType] = { Cmd2Style.COMMAND_LINE: Style(color=Color.CYAN, bold=True), + Cmd2Style.COMPLETION_MENU_ITEM: Style(color=Color.BLACK, bgcolor=Color.GREEN), + Cmd2Style.COMPLETION_MENU_META: Style(color=Color.BLACK, bgcolor=Color.BRIGHT_GREEN), Cmd2Style.ERROR: Style(color=Color.BRIGHT_RED), Cmd2Style.HELP_HEADER: Style(color=Color.BRIGHT_GREEN), Cmd2Style.HELP_LEADER: Style(color=Color.CYAN), + Cmd2Style.LEXER_COMMAND: Style(color=Color.GREEN), + Cmd2Style.LEXER_ALIAS: Style(color=Color.CYAN), + Cmd2Style.LEXER_MACRO: Style(color=Color.MAGENTA), + Cmd2Style.LEXER_FLAG: Style(color=Color.RED), + Cmd2Style.LEXER_ARGUMENT: Style(color=Color.YELLOW), Cmd2Style.SUCCESS: Style(color=Color.GREEN), Cmd2Style.TABLE_BORDER: Style(color=Color.BRIGHT_GREEN), Cmd2Style.WARNING: Style(color=Color.BRIGHT_YELLOW), diff --git a/docs/features/completion.md b/docs/features/completion.md index 85143650b..1def16f23 100644 --- a/docs/features/completion.md +++ b/docs/features/completion.md @@ -116,6 +116,13 @@ demonstration of how this is used. [read_input](https://github.com/python-cmd2/cmd2/blob/main/examples/read_input.py) example for a demonstration. +## Custom Completion Menu Colors + +`cmd2` provides the ability to customize the foreground and background colors of the completion menu +items and their associated help text. See +[Customizing Completion Menu Colors](./theme.md#customizing-completion-menu-colors) in the Theme +documentation for more details. + ## For More Information See [cmd2's argparse_utils API](../api/argparse_utils.md) for a more detailed discussion of argparse diff --git a/docs/features/theme.md b/docs/features/theme.md index 064178fa7..cd348f061 100644 --- a/docs/features/theme.md +++ b/docs/features/theme.md @@ -6,5 +6,19 @@ information. You can use this to brand your application and set an overall consistent look and feel that is appealing to your user base. +## Customizing Completion Menu Colors + +`cmd2` leverages `prompt-toolkit` for its tab completion menu. You can customize the colors of the +completion menu by overriding the following styles in your `cmd2` theme: + +- `Cmd2Style.COMPLETION_MENU_ITEM`: The background and foreground color of the selected completion + item. +- `Cmd2Style.COMPLETION_MENU_META`: The background and foreground color of the selected completion + item's help/meta text. + +By default, these are styled with black text on a green background to provide contrast. + +## Example + See [rich_theme.py](https://github.com/python-cmd2/cmd2/blob/main/examples/rich_theme.py) for a simple example of configuring a custom theme for your `cmd2` application. diff --git a/docs/upgrades.md b/docs/upgrades.md index a89e248f2..2d10a7a44 100644 --- a/docs/upgrades.md +++ b/docs/upgrades.md @@ -46,6 +46,16 @@ See the example for a demonstration of how to implement a background thread that refreshes the toolbar periodically. +### Custom Completion Menu Colors + +`cmd2` now leverages `prompt-toolkit` for its tab completion menu and provides the ability to +customize its appearance using the `cmd2` theme. + +- **Customization**: Override the `Cmd2Style.COMPLETION_MENU_ITEM` and + `Cmd2Style.COMPLETION_MENU_META` styles using `cmd2.rich_utils.set_theme()`. See + [Customizing Completion Menu Colors](features/theme.md#customizing-completion-menu-colors) for + more details. + ### Deleted Modules Removed `rl_utils.py` and `terminal_utils.py` since `prompt-toolkit` provides this functionality. diff --git a/examples/rich_theme.py b/examples/rich_theme.py index 67914e33f..7fb367a2f 100755 --- a/examples/rich_theme.py +++ b/examples/rich_theme.py @@ -29,6 +29,13 @@ def __init__(self, *args, **kwargs): Cmd2Style.HELP_HEADER: Style(color=Color.CYAN, bgcolor="#44475a"), Cmd2Style.HELP_LEADER: Style(color="#f8f8f2", bgcolor="#282a36"), # use RGB hex colors Cmd2Style.TABLE_BORDER: Style(color="turquoise2"), # use a rich standard color + Cmd2Style.LEXER_COMMAND: Style(color=Color.LIGHT_GREEN), + Cmd2Style.LEXER_ALIAS: Style(color=Color.LIGHT_CYAN1), + Cmd2Style.LEXER_MACRO: Style(color=Color.LIGHT_CORAL), + Cmd2Style.LEXER_FLAG: Style(color=Color.LIGHT_PINK3), + Cmd2Style.LEXER_ARGUMENT: Style(color=Color.LIGHT_GOLDENROD1), + Cmd2Style.COMPLETION_MENU_ITEM: Style(color=Color.WHITE, bgcolor=Color.NAVY_BLUE), + Cmd2Style.COMPLETION_MENU_META: Style(color=Color.WHITE, bgcolor=Color.DARK_SLATE_GRAY2), "traceback.exc_type": Style(color=Color.RED, bgcolor=Color.LIGHT_YELLOW3, bold=True), "argparse.args": Style(color=Color.AQUAMARINE3, underline=True), } diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index bcea8c924..1423b2bf2 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1871,6 +1871,54 @@ def do_orate(self, opts, arg) -> None: self.stdout.write(arg + "\n") +def test_get_pt_style_caching(base_app) -> None: + # Get the initial style (populates the cache) + style1 = base_app._get_pt_style() + + # Getting it again should return the exact same object from the cache + style2 = base_app._get_pt_style() + assert style1 is style2 + + # Change the theme which should invalidate the cache + from rich.style import Style + + import cmd2.rich_utils as ru + from cmd2.styles import Cmd2Style + + # Save the original theme to restore later + orig_theme = ru.get_theme() + + try: + ru.set_theme({Cmd2Style.COMPLETION_MENU_ITEM: Style(color="red")}) + + # Getting the style now should return a new object + style3 = base_app._get_pt_style() + assert style3 is not style1 + + # Getting it again should return the new cached object + style4 = base_app._get_pt_style() + assert style4 is style3 + + # Verify the style reflects the change + # In prompt_toolkit 3, styles are accessed differently + attrs = style3.class_names_and_attrs + found = False + for classes, attr in attrs: + if "completion-menu.completion.current" in classes and attr.color in ( + "800000", + "darkred", + "ff0000", + "#800000", + "ansired", + ): + found = True + break + assert found, "Color change not found in cached style" + + finally: + ru._APP_THEME = orig_theme + + @pytest.fixture def multiline_app(): return MultilineApp() diff --git a/tests/test_pt_utils.py b/tests/test_pt_utils.py index a8fcdbacc..2cb52f827 100644 --- a/tests/test_pt_utils.py +++ b/tests/test_pt_utils.py @@ -116,7 +116,11 @@ def test_lex_document_command(self, mock_cmd_app): get_line = lexer.lex_document(document) tokens = get_line(0) - assert tokens == [("ansigreen", "help"), ("", " "), ("ansiyellow", "something")] + assert tokens == [ + ("noreverse fg:ansigreen bg:default", "help"), + ("", " "), + ("noreverse fg:ansiyellow bg:default", "something"), + ] def test_lex_document_alias(self, mock_cmd_app): """Test lexing an alias.""" @@ -128,7 +132,7 @@ def test_lex_document_alias(self, mock_cmd_app): get_line = lexer.lex_document(document) tokens = get_line(0) - assert tokens == [("ansicyan", "ls"), ("", " "), ("ansired", "-l")] + assert tokens == [("noreverse fg:ansicyan bg:default", "ls"), ("", " "), ("noreverse fg:ansired bg:default", "-l")] def test_lex_document_macro(self, mock_cmd_app): """Test lexing a macro.""" @@ -140,7 +144,11 @@ def test_lex_document_macro(self, mock_cmd_app): get_line = lexer.lex_document(document) tokens = get_line(0) - assert tokens == [("ansimagenta", "my_macro"), ("", " "), ("ansiyellow", "arg1")] + assert tokens == [ + ("noreverse fg:ansimagenta bg:default", "my_macro"), + ("", " "), + ("noreverse fg:ansiyellow bg:default", "arg1"), + ] def test_lex_document_leading_whitespace(self, mock_cmd_app): """Test lexing with leading whitespace.""" @@ -152,7 +160,12 @@ def test_lex_document_leading_whitespace(self, mock_cmd_app): get_line = lexer.lex_document(document) tokens = get_line(0) - assert tokens == [("", " "), ("ansigreen", "help"), ("", " "), ("ansiyellow", "something")] + assert tokens == [ + ("", " "), + ("noreverse fg:ansigreen bg:default", "help"), + ("", " "), + ("noreverse fg:ansiyellow bg:default", "something"), + ] def test_lex_document_unknown_command(self, mock_cmd_app): """Test lexing an unknown command.""" @@ -163,7 +176,7 @@ def test_lex_document_unknown_command(self, mock_cmd_app): get_line = lexer.lex_document(document) tokens = get_line(0) - assert tokens == [("", "unknown"), ("", " "), ("ansiyellow", "command")] + assert tokens == [("", "unknown"), ("", " "), ("noreverse fg:ansiyellow bg:default", "command")] def test_lex_document_no_command(self, mock_cmd_app): """Test lexing an empty line or line with only whitespace.""" @@ -200,17 +213,17 @@ def test_lex_document_arguments(self, mock_cmd_app): tokens = get_line(0) assert tokens == [ - ("ansigreen", "help"), + ("noreverse fg:ansigreen bg:default", "help"), ("", " "), - ("ansired", "-v"), + ("noreverse fg:ansired bg:default", "-v"), ("", " "), - ("ansired", "--name"), + ("noreverse fg:ansired bg:default", "--name"), ("", " "), - ("ansiyellow", '"John Doe"'), + ("noreverse fg:ansiyellow bg:default", '"John Doe"'), ("", " "), ("", ">"), ("", " "), - ("ansiyellow", "out.txt"), + ("noreverse fg:ansiyellow bg:default", "out.txt"), ] def test_lex_document_unclosed_quote(self, mock_cmd_app): @@ -223,7 +236,11 @@ def test_lex_document_unclosed_quote(self, mock_cmd_app): get_line = lexer.lex_document(document) tokens = get_line(0) - assert tokens == [("ansigreen", "echo"), ("", " "), ("ansiyellow", '"hello')] + assert tokens == [ + ("noreverse fg:ansigreen bg:default", "echo"), + ("", " "), + ("noreverse fg:ansiyellow bg:default", '"hello'), + ] def test_lex_document_shortcut(self, mock_cmd_app): """Test lexing a shortcut.""" @@ -235,13 +252,13 @@ def test_lex_document_shortcut(self, mock_cmd_app): document = Document(line) get_line = lexer.lex_document(document) tokens = get_line(0) - assert tokens == [("ansigreen", "!"), ("ansiyellow", "ls")] + assert tokens == [("noreverse fg:ansigreen bg:default", "!"), ("noreverse fg:ansiyellow bg:default", "ls")] line = "! ls" document = Document(line) get_line = lexer.lex_document(document) tokens = get_line(0) - assert tokens == [("ansigreen", "!"), ("", " "), ("ansiyellow", "ls")] + assert tokens == [("noreverse fg:ansigreen bg:default", "!"), ("", " "), ("noreverse fg:ansiyellow bg:default", "ls")] def test_lex_document_multiline(self, mock_cmd_app): """Test lexing a multiline command.""" @@ -255,11 +272,11 @@ def test_lex_document_multiline(self, mock_cmd_app): # First line should have command tokens0 = get_line(0) - assert tokens0 == [("ansigreen", "orate")] + assert tokens0 == [("noreverse fg:ansigreen bg:default", "orate")] # Second line should have argument (not command) tokens1 = get_line(1) - assert tokens1 == [("ansiyellow", "help")] + assert tokens1 == [("noreverse fg:ansiyellow bg:default", "help")] class TestCmd2Completer: @@ -600,3 +617,119 @@ def test_clear(self): history.clear() assert not history.get_strings() + + +class TestRichToPtColor: + def test_rich_to_pt_color_none(self): + assert pt_utils.rich_to_pt_color(None) == "default" + + def test_rich_to_pt_color_default(self): + from rich.color import Color + + c = Color.parse("default") + assert pt_utils.rich_to_pt_color(c) == "default" + + def test_rich_to_pt_color_standard(self): + from rich.color import Color + + c = Color.parse("red") + assert pt_utils.rich_to_pt_color(c) == "ansired" + c = Color.parse("bright_red") + assert pt_utils.rich_to_pt_color(c) == "ansibrightred" + # Test a standard color initialized by number + c = Color.from_ansi(2) + assert pt_utils.rich_to_pt_color(c) == "ansigreen" + + def test_rich_to_pt_color_eight_bit(self): + from rich.color import Color + + # 155 is an 8-bit color + c = Color.from_ansi(155) + # Should convert to truecolor hex equivalent #afff5f + assert pt_utils.rich_to_pt_color(c) == "#afff5f" + + def test_rich_to_pt_color_truecolor(self): + from rich.color import Color + + c = Color.parse("#123456") + assert pt_utils.rich_to_pt_color(c) == "#123456" + + +class TestRichToPtStyle: + def test_rich_to_pt_style_none(self): + assert pt_utils.rich_to_pt_style(None) == "" + + def test_rich_to_pt_style_string(self): + pt_style = pt_utils.rich_to_pt_style("bold red on blue") + assert "fg:ansired" in pt_style + assert "bg:ansiblue" in pt_style + assert "bold" in pt_style + assert "nobold" not in pt_style + + def test_rich_to_pt_style_color(self): + from rich.style import Style + + style = Style(color="#123456") + pt_style = pt_utils.rich_to_pt_style(style) + assert "fg:#123456" in pt_style + assert "bg:default" in pt_style + assert "noreverse" in pt_style + + def test_rich_to_pt_style_bgcolor(self): + from rich.style import Style + + style = Style(bgcolor="#654321") + pt_style = pt_utils.rich_to_pt_style(style) + assert "fg:default" in pt_style + assert "bg:#654321" in pt_style + + def test_rich_to_pt_style_default_color(self): + from rich.style import Style + + style = Style(color="default", bgcolor="default") + pt_style = pt_utils.rich_to_pt_style(style) + assert "fg:default" in pt_style + assert "bg:default" in pt_style + + def test_rich_to_pt_style_bold(self): + from rich.style import Style + + style = Style(bold=True) + pt_style = pt_utils.rich_to_pt_style(style) + assert "bold" in pt_style + assert "nobold" not in pt_style + + def test_rich_to_pt_style_nobold(self): + from rich.style import Style + + style = Style(bold=False) + pt_style = pt_utils.rich_to_pt_style(style) + assert "nobold" in pt_style + + def test_rich_to_pt_style_italic(self): + from rich.style import Style + + style = Style(italic=True) + pt_style = pt_utils.rich_to_pt_style(style) + assert "italic" in pt_style + + def test_rich_to_pt_style_noitalic(self): + from rich.style import Style + + style = Style(italic=False) + pt_style = pt_utils.rich_to_pt_style(style) + assert "noitalic" in pt_style + + def test_rich_to_pt_style_underline(self): + from rich.style import Style + + style = Style(underline=True) + pt_style = pt_utils.rich_to_pt_style(style) + assert "underline" in pt_style + + def test_rich_to_pt_style_nounderline(self): + from rich.style import Style + + style = Style(underline=False) + pt_style = pt_utils.rich_to_pt_style(style) + assert "nounderline" in pt_style