Skip to content

Commit dfedeba

Browse files
committed
Add styles to theme for completion item and meta foreground and background color
This is an attempt to provide higher-contrast color options and to make them configurable as part of a theme.
1 parent 12a7d5e commit dfedeba

6 files changed

Lines changed: 82 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ prompt is displayed.
135135
full type hints and IDE autocompletion for `self._cmd` without needing to override and cast
136136
the property.
137137
- Added `traceback_kwargs` attribute to allow customization of Rich-based tracebacks.
138+
- Added ability to customize `prompt-toolkit` completion menu colors by overriding
139+
`Cmd2Style.COMPLETION_MENU_ITEM` and `Cmd2Style.COMPLETION_MENU_META` in the `cmd2` theme.
138140

139141
## 3.5.1 (April 24, 2026)
140142

cmd2/cmd2.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@
7878
from prompt_toolkit.output import DummyOutput, create_output
7979
from prompt_toolkit.patch_stdout import patch_stdout
8080
from prompt_toolkit.shortcuts import CompleteStyle, PromptSession, choice, set_title
81+
from prompt_toolkit.styles import DynamicStyle
82+
from prompt_toolkit.styles import Style as PtStyle
8183
from rich.console import (
8284
Group,
8385
JustifyMethod,
@@ -714,6 +716,46 @@ def _should_continue_multiline(self) -> bool:
714716
# No macro found or already processed. The statement is complete.
715717
return False
716718

719+
def _get_pt_style(self) -> "PtStyle":
720+
"""Return the prompt_toolkit style for the completion menu."""
721+
722+
def to_pt_style(rich_style: Style | None) -> str:
723+
"""Convert a rich Style object to a prompt_toolkit style string."""
724+
if not rich_style:
725+
return ""
726+
parts = ["noreverse"]
727+
if rich_style.color:
728+
c = rich_style.color.get_truecolor()
729+
parts.append(f"fg:#{c.red:02x}{c.green:02x}{c.blue:02x}")
730+
else:
731+
parts.append("fg:default")
732+
733+
if rich_style.bgcolor:
734+
c = rich_style.bgcolor.get_truecolor()
735+
parts.append(f"bg:#{c.red:02x}{c.green:02x}{c.blue:02x}")
736+
else:
737+
parts.append("bg:default")
738+
739+
if rich_style.bold is not None:
740+
parts.append("bold" if rich_style.bold else "nobold")
741+
if rich_style.italic is not None:
742+
parts.append("italic" if rich_style.italic else "noitalic")
743+
if rich_style.underline is not None:
744+
parts.append("underline" if rich_style.underline else "nounderline")
745+
return " ".join(parts)
746+
747+
theme = ru.get_theme()
748+
item_style = to_pt_style(theme.styles.get(Cmd2Style.COMPLETION_MENU_ITEM))
749+
meta_style = to_pt_style(theme.styles.get(Cmd2Style.COMPLETION_MENU_META))
750+
751+
return PtStyle.from_dict(
752+
{
753+
"completion-menu.completion.current": item_style,
754+
"completion-menu.meta.completion.current": meta_style,
755+
"completion-menu.multi-column-meta": meta_style,
756+
}
757+
)
758+
717759
def _create_main_session(self, auto_suggest: bool, completekey: str) -> PromptSession[str]:
718760
"""Create and return the main PromptSession for the application.
719761
@@ -755,6 +797,7 @@ def _(event: Any) -> None: # pragma: no cover
755797
"multiline": filters.Condition(self._should_continue_multiline),
756798
"prompt_continuation": self.continuation_prompt,
757799
"rprompt": self.get_rprompt,
800+
"style": DynamicStyle(self._get_pt_style),
758801
}
759802

760803
if self.stdin.isatty() and self.stdout.isatty():
@@ -3578,6 +3621,7 @@ def read_input(
35783621
key_bindings=self.main_session.key_bindings,
35793622
input=self.main_session.input,
35803623
output=self.main_session.output,
3624+
style=DynamicStyle(self._get_pt_style),
35813625
)
35823626

35833627
return self._read_raw_input(prompt, temp_session)
@@ -3596,6 +3640,7 @@ def read_secret(
35963640
temp_session: PromptSession[str] = PromptSession(
35973641
input=self.main_session.input,
35983642
output=self.main_session.output,
3643+
style=DynamicStyle(self._get_pt_style),
35993644
)
36003645

36013646
return self._read_raw_input(prompt, temp_session, is_password=True)

cmd2/styles.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class Cmd2Style(StrEnum):
5151
"""
5252

5353
COMMAND_LINE = "cmd2.example" # Command line examples in help text
54+
COMPLETION_MENU_ITEM = "cmd2.completion_menu.item" # Selected completion item
55+
COMPLETION_MENU_META = "cmd2.completion_menu.meta" # Selected completion help/meta text
5456
ERROR = "cmd2.error" # Error text (used by perror())
5557
HELP_HEADER = "cmd2.help.header" # Help table header text
5658
HELP_LEADER = "cmd2.help.leader" # Text right before the help tables are listed
@@ -63,6 +65,8 @@ class Cmd2Style(StrEnum):
6365
# Tightly coupled with the Cmd2Style enum.
6466
DEFAULT_CMD2_STYLES: dict[str, StyleType] = {
6567
Cmd2Style.COMMAND_LINE: Style(color=Color.CYAN, bold=True),
68+
Cmd2Style.COMPLETION_MENU_ITEM: Style(color=Color.BLACK, bgcolor=Color.GREEN),
69+
Cmd2Style.COMPLETION_MENU_META: Style(color=Color.BLACK, bgcolor=Color.LIGHT_GREEN),
6670
Cmd2Style.ERROR: Style(color=Color.BRIGHT_RED),
6771
Cmd2Style.HELP_HEADER: Style(color=Color.BRIGHT_GREEN),
6872
Cmd2Style.HELP_LEADER: Style(color=Color.CYAN),

docs/features/completion.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ demonstration of how this is used.
116116
[read_input](https://github.com/python-cmd2/cmd2/blob/main/examples/read_input.py) example for a
117117
demonstration.
118118

119+
## Custom Completion Menu Colors
120+
121+
`cmd2` provides the ability to customize the foreground and background colors of the completion menu
122+
items and their associated help text. See
123+
[Customizing Completion Menu Colors](./theme.md#customizing-completion-menu-colors) in the Theme
124+
documentation for more details.
125+
119126
## For More Information
120127

121128
See [cmd2's argparse_utils API](../api/argparse_utils.md) for a more detailed discussion of argparse

docs/features/theme.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,19 @@
66
information. You can use this to brand your application and set an overall consistent look and feel
77
that is appealing to your user base.
88

9+
## Customizing Completion Menu Colors
10+
11+
`cmd2` leverages `prompt-toolkit` for its tab completion menu. You can customize the colors of the
12+
completion menu by overriding the following styles in your `cmd2` theme:
13+
14+
- `Cmd2Style.COMPLETION_MENU_ITEM`: The background and foreground color of the selected completion
15+
item.
16+
- `Cmd2Style.COMPLETION_MENU_META`: The background and foreground color of the selected completion
17+
item's help/meta text.
18+
19+
By default, these are styled with black text on a green background to provide contrast.
20+
21+
## Example
22+
923
See [rich_theme.py](https://github.com/python-cmd2/cmd2/blob/main/examples/rich_theme.py) for a
1024
simple example of configuring a custom theme for your `cmd2` application.

docs/upgrades.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ See the
4646
example for a demonstration of how to implement a background thread that refreshes the toolbar
4747
periodically.
4848

49+
### Custom Completion Menu Colors
50+
51+
`cmd2` now leverages `prompt-toolkit` for its tab completion menu and provides the ability to
52+
customize its appearance using the `cmd2` theme.
53+
54+
- **Customization**: Override the `Cmd2Style.COMPLETION_MENU_ITEM` and
55+
`Cmd2Style.COMPLETION_MENU_META` styles using `cmd2.rich_utils.set_theme()`. See
56+
[Customizing Completion Menu Colors](features/theme.md#customizing-completion-menu-colors) for
57+
more details.
58+
4959
### Deleted Modules
5060

5161
Removed `rl_utils.py` and `terminal_utils.py` since `prompt-toolkit` provides this functionality.

0 commit comments

Comments
 (0)