Skip to content

Commit cf99bc3

Browse files
committed
Fixed cmd2 bypassing NO_COLOR and allow_style when setting prompt-toolkit's color depth.
1 parent d35f7d7 commit cf99bc3

6 files changed

Lines changed: 51 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- Bug Fixes
1313
- Fixed type hinting so that methods decorated with `with_annotated` no longer trigger spurious
1414
mypy errors and preserve their original signature.
15+
- Fixed cmd2 bypassing NO_COLOR and allow_style when setting prompt-toolkit's color depth.
1516

1617
- Experimental features
1718
- `@with_annotated` now supports `frozenset[T]` collection parameters, alongside the existing

cmd2/cmd2.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@
7979
from prompt_toolkit.input import DummyInput, create_input
8080
from prompt_toolkit.key_binding import KeyBindings
8181
from prompt_toolkit.output import DummyOutput, create_output
82-
from prompt_toolkit.output.color_depth import ColorDepth
8382
from prompt_toolkit.patch_stdout import patch_stdout
8483
from prompt_toolkit.shortcuts import CompleteStyle, PromptSession, choice, set_title
8584
from prompt_toolkit.styles import DynamicStyle
@@ -202,6 +201,7 @@ def __init__(self, msg: str = "") -> None:
202201
Cmd2History,
203202
Cmd2Lexer,
204203
pt_filter_style,
204+
pt_resolve_color_depth,
205205
)
206206
from .utils import (
207207
Settable,
@@ -778,7 +778,7 @@ def _(event: Any) -> None: # pragma: no cover
778778
kwargs: dict[str, Any] = {
779779
"auto_suggest": AutoSuggestFromHistory() if auto_suggest else None,
780780
"bottom_toolbar": self.get_bottom_toolbar if enable_bottom_toolbar else None,
781-
"color_depth": ColorDepth.TRUE_COLOR,
781+
"color_depth": pt_resolve_color_depth(),
782782
"complete_style": CompleteStyle.MULTI_COLUMN,
783783
"complete_in_thread": complete_in_thread,
784784
"complete_while_typing": False,
@@ -1443,6 +1443,7 @@ def allow_style(self) -> ru.AllowStyle:
14431443
def allow_style(self, value: ru.AllowStyle) -> None:
14441444
"""Setter property needed to support do_set when it updates allow_style."""
14451445
ru.ALLOW_STYLE = value
1446+
self.main_session.color_depth = pt_resolve_color_depth()
14461447

14471448
@property
14481449
def traceback_show_locals(self) -> bool:

cmd2/pt_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Utilities for integrating prompt_toolkit with cmd2."""
22

3+
import os
34
import re
45
from collections.abc import (
56
Callable,
@@ -21,6 +22,7 @@
2122
from prompt_toolkit.formatted_text import ANSI
2223
from prompt_toolkit.history import History
2324
from prompt_toolkit.lexers import Lexer
25+
from prompt_toolkit.output.color_depth import ColorDepth
2426
from rich.style import Style, StyleType
2527

2628
from . import (
@@ -75,6 +77,13 @@ def pt_filter_style(text: str | ANSI) -> str | ANSI:
7577
return text if isinstance(text, ANSI) else ANSI(text)
7678

7779

80+
def pt_resolve_color_depth() -> ColorDepth:
81+
"""Determine the prompt-toolkit ColorDepth based on NO_COLOR and ru.ALLOW_STYLE."""
82+
if "NO_COLOR" in os.environ or ru.ALLOW_STYLE == ru.AllowStyle.NEVER:
83+
return ColorDepth.DEPTH_1_BIT
84+
return ColorDepth.TRUE_COLOR
85+
86+
7887
@lru_cache(maxsize=256)
7988
def rich_to_pt_color(color: "Color | None") -> str:
8089
"""Convert a rich Color object to a prompt_toolkit color string."""

cmd2/styles.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ class Cmd2Style(StrEnum):
7272
Cmd2Style.COMMAND_LINE: Style(color=Color.CYAN, bold=True),
7373
Cmd2Style.COMPLETION_MENU: Style(color="#000000", bgcolor="#bbbbbb"), # prompt-toolkit default
7474
Cmd2Style.COMPLETION_MENU_COMPLETION: Style(), # prompt-toolkit default
75-
Cmd2Style.COMPLETION_MENU_CURRENT: Style(color=Color.GREEN, bgcolor=Color.BLACK), # This style swaps FG and BG colors
75+
# Defined with swapped FG/BG colors because prompt-toolkit applies 'reverse' by default.
76+
# This yields a black-on-green highlight in color mode, while maintaining a fallback
77+
# reverse-video highlight when colors are disabled (e.g. NO_COLOR=1 or allow_style=never).
78+
Cmd2Style.COMPLETION_MENU_CURRENT: Style(color=Color.GREEN, bgcolor=Color.BLACK),
7679
Cmd2Style.COMPLETION_MENU_META: Style(color="#000000", bgcolor="#bbbbbb"), # prompt-toolkit default
7780
Cmd2Style.COMPLETION_MENU_META_CURRENT: Style(color=Color.BLACK, bgcolor=Color.BRIGHT_GREEN),
7881
Cmd2Style.ERROR: Style(color=Color.BRIGHT_RED),

tests/test_cmd2.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,9 @@ def test_set_no_settables(base_app) -> None:
245245
],
246246
)
247247
@with_ansi_style(ru.AllowStyle.TERMINAL)
248-
def test_set_allow_style(base_app, new_val, is_valid, expected) -> None:
248+
def test_set_allow_style(base_app: cmd2.Cmd, new_val: str, is_valid: bool, expected: ru.AllowStyle) -> None:
249+
from cmd2.pt_utils import pt_resolve_color_depth
250+
249251
# Use the set command to alter allow_style
250252
out, err = run_cmd(base_app, f"set allow_style {new_val}")
251253
assert base_app.last_result is is_valid
@@ -255,6 +257,7 @@ def test_set_allow_style(base_app, new_val, is_valid, expected) -> None:
255257
if is_valid:
256258
assert out
257259
assert not err
260+
assert base_app.main_session.color_depth == pt_resolve_color_depth()
258261

259262

260263
def test_set_traceback_show_locals(base_app: cmd2.Cmd) -> None:

tests/test_pt_utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
ANSI,
1212
to_formatted_text,
1313
)
14+
from prompt_toolkit.output.color_depth import ColorDepth
1415
from rich.table import Table
1516

1617
import cmd2
@@ -25,6 +26,7 @@
2526
from cmd2.pt_utils import (
2627
Cmd2Lexer,
2728
pt_filter_style,
29+
pt_resolve_color_depth,
2830
)
2931

3032
from .conftest import with_ansi_style
@@ -96,6 +98,34 @@ def test_pt_filter_style_never() -> None:
9698
assert result == su.strip_style(styled)
9799

98100

101+
@with_ansi_style(ru.AllowStyle.ALWAYS)
102+
def test_pt_resolve_color_depth_always(monkeypatch) -> None:
103+
# Ensure any host NO_COLOR environment variable doesn't affect the test
104+
monkeypatch.delenv("NO_COLOR", raising=False)
105+
assert pt_resolve_color_depth() == ColorDepth.TRUE_COLOR
106+
107+
108+
@with_ansi_style(ru.AllowStyle.TERMINAL)
109+
def test_pt_resolve_color_depth_terminal(monkeypatch) -> None:
110+
# Ensure any host NO_COLOR environment variable doesn't affect the test
111+
monkeypatch.delenv("NO_COLOR", raising=False)
112+
assert pt_resolve_color_depth() == ColorDepth.TRUE_COLOR
113+
114+
115+
@with_ansi_style(ru.AllowStyle.NEVER)
116+
def test_pt_resolve_color_depth_never(monkeypatch) -> None:
117+
# Under NEVER, colors are suppressed regardless of NO_COLOR
118+
monkeypatch.delenv("NO_COLOR", raising=False)
119+
assert pt_resolve_color_depth() == ColorDepth.DEPTH_1_BIT
120+
121+
122+
@with_ansi_style(ru.AllowStyle.ALWAYS)
123+
def test_pt_resolve_color_depth_no_color(monkeypatch) -> None:
124+
# Mock NO_COLOR=1 to ensure colors are suppressed
125+
monkeypatch.setenv("NO_COLOR", "1")
126+
assert pt_resolve_color_depth() == ColorDepth.DEPTH_1_BIT
127+
128+
99129
class TestCmd2Lexer:
100130
@with_ansi_style(ru.AllowStyle.NEVER)
101131
def test_lex_document_no_style(self, mock_cmd_app):

0 commit comments

Comments
 (0)