Skip to content

Commit 7287681

Browse files
committed
Merge branch 'main' into 1627-silence-settable
2 parents 9bcb78c + a3890a7 commit 7287681

18 files changed

Lines changed: 110 additions & 168 deletions

CHANGELOG.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ prompt is displayed.
9797
`set_theme()` functions to support lazy initialization and safer in-place updates of the
9898
theme.
9999
- Renamed `Cmd._command_parsers` to `Cmd.command_parsers`.
100+
- Removed `RichPrintKwargs` `TypedDict` in favor of using `Mapping[str, Any]`, allowing for
101+
greater flexibility in passing keyword arguments to `console.print()` calls.
102+
- Removed `always_show_hint` settable as it provided a poor user experience with
103+
`prompt-toolkit`
100104
- Enhancements
101105
- New `cmd2.Cmd` parameters
102106
- **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These
@@ -114,8 +118,9 @@ prompt is displayed.
114118
- **read_secret**: read secrets like passwords without displaying them to the terminal
115119
- **ppretty**: a cmd2-compatible replacement for `rich.pretty.pprint()`
116120
- New settables:
117-
- **max_column_completion_results**: (int) the maximum number of completion results to
118-
display in a single column
121+
- **max_column_completion_results**: (int) Maximum number of completion results to display
122+
in a single column
123+
- **traceback_show_locals**: (bool) Display local variables in tracebacks
119124
- `cmd2.Cmd.select` has been revamped to use the
120125
[choice](https://python-prompt-toolkit.readthedocs.io/en/3.0.52/pages/asking_for_a_choice.html)
121126
function from `prompt-toolkit` when both **stdin** and **stdout** are TTYs
@@ -131,10 +136,17 @@ prompt is displayed.
131136
specific `cmd2.Cmd` subclass (e.g.,`class MyCommandSet(CommandSet[MyApp]):`). This provides
132137
full type hints and IDE autocompletion for `self._cmd` without needing to override and cast
133138
the property.
139+
- Added `traceback_kwargs` attribute to allow customization of Rich-based tracebacks
134140
- Updated `set` command to consolidate its confirmation output into a single, colorized line.
135141
The confirmation now uses `pfeedback()`, allowing it to be silenced when the `quiet` settable
136142
is enabled.
137143

144+
## 3.5.1 (April 24, 2026)
145+
146+
- Bug Fixes
147+
- Fixed `ArgparseCompleter.print_help()` not passing file stream to recursive call.
148+
- Fixed issue where `constants.REDIRECTION_TOKENS` was being mutated.
149+
138150
## 3.5.0 (April 13, 2026)
139151

140152
- Bug Fixes

cmd2/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
MetavarTypeCmd2HelpFormatter,
5151
RawDescriptionCmd2HelpFormatter,
5252
RawTextCmd2HelpFormatter,
53-
RichPrintKwargs,
5453
TextGroup,
5554
get_theme,
5655
set_theme,
@@ -105,7 +104,6 @@
105104
"MetavarTypeCmd2HelpFormatter",
106105
"RawDescriptionCmd2HelpFormatter",
107106
"RawTextCmd2HelpFormatter",
108-
"RichPrintKwargs",
109107
"set_theme",
110108
"TextGroup",
111109
# String Utils

cmd2/argparse_completer.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -491,13 +491,6 @@ def _handle_last_token(
491491

492492
# If we have results, then return them
493493
if completions:
494-
if not completions.hint:
495-
# Add a hint even though there are results in case Cmd.always_show_hint is True.
496-
completions = dataclasses.replace(
497-
completions,
498-
hint=_build_hint(self._parser, flag_arg_state.action),
499-
)
500-
501494
return completions
502495

503496
# Otherwise, print a hint if the flag isn't finished or text isn't possibly the start of a flag
@@ -519,12 +512,6 @@ def _handle_last_token(
519512

520513
# If we have results, then return them
521514
if completions:
522-
if not completions.hint:
523-
# Add a hint even though there are results in case Cmd.always_show_hint is True.
524-
completions = dataclasses.replace(
525-
completions,
526-
hint=_build_hint(self._parser, pos_arg_state.action),
527-
)
528515
return completions
529516

530517
# Otherwise, print a hint if text isn't possibly the start of a flag
@@ -705,7 +692,7 @@ def print_help(self, tokens: Sequence[str], file: IO[str] | None = None) -> None
705692
if parser is not None:
706693
completer_type = self._cmd2_app._determine_ap_completer_type(parser)
707694
completer = completer_type(parser, self._cmd2_app)
708-
completer.print_help(tokens[1:])
695+
completer.print_help(tokens[1:], file=file)
709696
return
710697
self._parser.print_help(file=file)
711698

cmd2/cmd2.py

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@
154154
Cmd2ExceptionConsole,
155155
Cmd2GeneralConsole,
156156
Cmd2SimpleTable,
157-
RichPrintKwargs,
158157
TextGroup,
159158
)
160159
from .styles import Cmd2Style
@@ -396,7 +395,8 @@ def __init__(
396395
instantiate and register all commands. If False, CommandSets
397396
must be manually installed with `register_command_set`.
398397
:param auto_suggest: If True, cmd2 will provide fish shell style auto-suggestions
399-
based on history. If False, these will not be provided.
398+
based on history. User can press right-arrow key to accept the
399+
provided suggestion.
400400
:param bottom_toolbar: if ``True``, then a bottom toolbar will be displayed.
401401
:param command_sets: Provide CommandSet instances to load during cmd2 initialization.
402402
This allows CommandSets with custom constructor parameters to be
@@ -465,7 +465,6 @@ def __init__(
465465
self.interactive_pipe = False
466466

467467
# Attributes which ARE dynamically settable via the set command at runtime
468-
self.always_show_hint = False
469468
self.debug = False
470469
self.echo = False
471470
self.editor = self.DEFAULT_EDITOR
@@ -474,6 +473,19 @@ def __init__(
474473
self.scripts_add_to_history = True # Scripts and pyscripts add commands to history
475474
self.timing = False # Prints elapsed time for each command
476475

476+
# Default settings for Rich tracebacks created by format_exception().
477+
# This dictionary can contain any parameter accepted by the
478+
# rich.traceback.Traceback class. You can modify it to adjust
479+
# the detail and layout of tracebacks.
480+
self.traceback_kwargs: dict[str, Any] = {
481+
"width": 100,
482+
"code_width": None, # Show all code characters
483+
"show_locals": False,
484+
"max_frames": 100,
485+
"word_wrap": True, # Wrap long lines of code instead of truncate
486+
"indent_guides": True,
487+
}
488+
477489
# Cached Rich consoles used by core print methods.
478490
self._console_cache = _ConsoleCache()
479491

@@ -1312,10 +1324,6 @@ def allow_style_type(value: str) -> ru.AllowStyle:
13121324
choices_provider=get_allow_style_choices,
13131325
)
13141326
)
1315-
1316-
self.add_settable(
1317-
Settable("always_show_hint", bool, "Display completion hint even when completion suggestions print", self)
1318-
)
13191327
self.add_settable(Settable("debug", bool, "Show full traceback on exception", self))
13201328
self.add_settable(Settable("echo", bool, "Echo command issued into output", self))
13211329
self.add_settable(Settable("editor", str, "Program used by 'edit'", self))
@@ -1339,19 +1347,28 @@ def allow_style_type(value: str) -> ru.AllowStyle:
13391347
self.add_settable(Settable("quiet", bool, "Don't print nonessential feedback", self))
13401348
self.add_settable(Settable("scripts_add_to_history", bool, "Scripts and pyscripts add commands to history", self))
13411349
self.add_settable(Settable("timing", bool, "Report execution times", self))
1342-
1343-
# ----- Methods related to presenting output to the user -----
1350+
self.add_settable(Settable("traceback_show_locals", bool, "Display local variables in tracebacks", self))
13441351

13451352
@property
13461353
def allow_style(self) -> ru.AllowStyle:
1347-
"""Read-only property needed to support do_set when it reads allow_style."""
1354+
"""Property needed to support do_set when it reads allow_style."""
13481355
return ru.ALLOW_STYLE
13491356

13501357
@allow_style.setter
13511358
def allow_style(self, new_val: ru.AllowStyle) -> None:
13521359
"""Setter property needed to support do_set when it updates allow_style."""
13531360
ru.ALLOW_STYLE = new_val
13541361

1362+
@property
1363+
def traceback_show_locals(self) -> bool:
1364+
"""Property needed to support do_set when it reads traceback_show_locals."""
1365+
return cast(bool, self.traceback_kwargs.get("show_locals", False))
1366+
1367+
@traceback_show_locals.setter
1368+
def traceback_show_locals(self, value: bool) -> None:
1369+
"""Setter property needed to support do_set when it updates traceback_show_locals."""
1370+
self.traceback_kwargs["show_locals"] = value
1371+
13551372
@property
13561373
def visible_prompt(self) -> str:
13571374
"""Read-only property to get the visible prompt with any ANSI style sequences stripped.
@@ -1426,7 +1443,7 @@ def print_to(
14261443
emoji: bool = False,
14271444
markup: bool = False,
14281445
highlight: bool = False,
1429-
rich_print_kwargs: RichPrintKwargs | None = None,
1446+
rich_print_kwargs: Mapping[str, Any] | None = None,
14301447
**kwargs: Any, # noqa: ARG002
14311448
) -> None:
14321449
"""Print objects to a given file stream.
@@ -1442,7 +1459,8 @@ def print_to(
14421459
:param style: optional style to apply to output
14431460
:param soft_wrap: Enable soft wrap mode. Defaults to True.
14441461
If True, text that doesn't fit will run on to the following line,
1445-
just like with print(). This is useful for raw text and logs.
1462+
just like the built-in print() function. This is useful for raw text
1463+
and logs.
14461464
If False, Rich wraps text to fit the terminal width.
14471465
Set this to False when printing structured Renderables like
14481466
Tables, Panels, or Columns to ensure they render as expected.
@@ -1457,10 +1475,10 @@ def print_to(
14571475
strings, such as common Python data types like numbers, booleans, or None.
14581476
This is particularly useful when pretty printing objects like lists and
14591477
dictionaries to display them in color. Defaults to False.
1460-
:param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
1478+
:param rich_print_kwargs: optional additional keyword arguments to pass to console.print().
14611479
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
14621480
method and still call `super()` without encountering unexpected keyword argument errors.
1463-
These arguments are not passed to Rich's Console.print().
1481+
These arguments are not passed to console.print().
14641482
14651483
See the Rich documentation for more details on emoji codes, markup tags, and highlighting.
14661484
"""
@@ -1499,7 +1517,7 @@ def poutput(
14991517
emoji: bool = False,
15001518
markup: bool = False,
15011519
highlight: bool = False,
1502-
rich_print_kwargs: RichPrintKwargs | None = None,
1520+
rich_print_kwargs: Mapping[str, Any] | None = None,
15031521
**kwargs: Any, # noqa: ARG002
15041522
) -> None:
15051523
"""Print objects to self.stdout.
@@ -1531,7 +1549,7 @@ def perror(
15311549
emoji: bool = False,
15321550
markup: bool = False,
15331551
highlight: bool = False,
1534-
rich_print_kwargs: RichPrintKwargs | None = None,
1552+
rich_print_kwargs: Mapping[str, Any] | None = None,
15351553
**kwargs: Any, # noqa: ARG002
15361554
) -> None:
15371555
"""Print objects to sys.stderr.
@@ -1564,7 +1582,7 @@ def psuccess(
15641582
emoji: bool = False,
15651583
markup: bool = False,
15661584
highlight: bool = False,
1567-
rich_print_kwargs: RichPrintKwargs | None = None,
1585+
rich_print_kwargs: Mapping[str, Any] | None = None,
15681586
**kwargs: Any, # noqa: ARG002
15691587
) -> None:
15701588
"""Wrap poutput, but apply Cmd2Style.SUCCESS.
@@ -1594,7 +1612,7 @@ def pwarning(
15941612
emoji: bool = False,
15951613
markup: bool = False,
15961614
highlight: bool = False,
1597-
rich_print_kwargs: RichPrintKwargs | None = None,
1615+
rich_print_kwargs: Mapping[str, Any] | None = None,
15981616
**kwargs: Any, # noqa: ARG002
15991617
) -> None:
16001618
"""Wrap perror, but apply Cmd2Style.WARNING.
@@ -1626,13 +1644,7 @@ def format_exception(self, exception: BaseException) -> str:
16261644
with console.capture() as capture:
16271645
# Only print a traceback if we're in debug mode and one exists.
16281646
if self.debug and sys.exc_info() != (None, None, None):
1629-
traceback = Traceback(
1630-
width=None, # Use all available width
1631-
code_width=None, # Use all available width
1632-
show_locals=True,
1633-
max_frames=0, # 0 means full traceback.
1634-
word_wrap=True, # Wrap long lines of code instead of truncate
1635-
)
1647+
traceback = Traceback(**self.traceback_kwargs)
16361648
console.print(traceback, end="")
16371649

16381650
else:
@@ -1690,7 +1702,7 @@ def pfeedback(
16901702
emoji: bool = False,
16911703
markup: bool = False,
16921704
highlight: bool = False,
1693-
rich_print_kwargs: RichPrintKwargs | None = None,
1705+
rich_print_kwargs: Mapping[str, Any] | None = None,
16941706
**kwargs: Any, # noqa: ARG002
16951707
) -> None:
16961708
"""Print nonessential feedback.
@@ -1740,7 +1752,7 @@ def ppaged(
17401752
emoji: bool = False,
17411753
markup: bool = False,
17421754
highlight: bool = False,
1743-
rich_print_kwargs: RichPrintKwargs | None = None,
1755+
rich_print_kwargs: Mapping[str, Any] | None = None,
17441756
**kwargs: Any, # noqa: ARG002
17451757
) -> None:
17461758
"""Print output using a pager.
@@ -1960,10 +1972,8 @@ def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> tuple[li
19601972
**On Failure**
19611973
- Two empty lists
19621974
"""
1963-
import copy
1964-
19651975
unclosed_quote = ""
1966-
quotes_to_try = copy.copy(constants.QUOTES)
1976+
quotes_to_try = [*constants.QUOTES]
19671977

19681978
tmp_line = line[:endidx]
19691979
tmp_endidx = endidx
@@ -3802,8 +3812,7 @@ def _alias_create(self, args: argparse.Namespace) -> None:
38023812
return
38033813

38043814
# Unquote redirection and terminator tokens
3805-
tokens_to_unquote = constants.REDIRECTION_TOKENS
3806-
tokens_to_unquote.extend(self.statement_parser.terminators)
3815+
tokens_to_unquote = (*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators)
38073816
utils.unquote_specific_tokens(args.command_args, tokens_to_unquote)
38083817

38093818
# Build the alias value string
@@ -3882,8 +3891,7 @@ def _alias_list(self, args: argparse.Namespace) -> None:
38823891
"""List some or all aliases as 'alias create' commands."""
38833892
self.last_result = {} # dict[alias_name, alias_value]
38843893

3885-
tokens_to_quote = constants.REDIRECTION_TOKENS
3886-
tokens_to_quote.extend(self.statement_parser.terminators)
3894+
tokens_to_quote = (*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators)
38873895

38883896
to_list = (
38893897
utils.remove_duplicates(args.names)
@@ -4049,8 +4057,7 @@ def _macro_create(self, args: argparse.Namespace) -> None:
40494057
return
40504058

40514059
# Unquote redirection and terminator tokens
4052-
tokens_to_unquote = constants.REDIRECTION_TOKENS
4053-
tokens_to_unquote.extend(self.statement_parser.terminators)
4060+
tokens_to_unquote = (*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators)
40544061
utils.unquote_specific_tokens(args.command_args, tokens_to_unquote)
40554062

40564063
# Build the macro value string
@@ -4172,8 +4179,7 @@ def _macro_list(self, args: argparse.Namespace) -> None:
41724179
"""List macros."""
41734180
self.last_result = {} # dict[macro_name, macro_value]
41744181

4175-
tokens_to_quote = constants.REDIRECTION_TOKENS
4176-
tokens_to_quote.extend(self.statement_parser.terminators)
4182+
tokens_to_quote = (*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators)
41774183

41784184
to_list = (
41794185
utils.remove_duplicates(args.names)
@@ -4910,9 +4916,7 @@ def py_quit() -> None:
49104916
"""Exit an interactive Python environment, callable from the interactive Python console."""
49114917
raise EmbeddedConsoleExit
49124918

4913-
from .py_bridge import (
4914-
PyBridge,
4915-
)
4919+
from .py_bridge import PyBridge
49164920

49174921
add_to_history = self.scripts_add_to_history if pyscript else True
49184922
py_bridge = PyBridge(self, add_to_history=add_to_history)

cmd2/constants.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
INFINITY = float("inf")
77

88
# Used for command parsing, output redirection, completion, and word breaks. Do not change.
9-
QUOTES = ['"', "'"]
9+
QUOTES = ('"', "'")
1010
REDIRECTION_PIPE = "|"
1111
REDIRECTION_OVERWRITE = ">"
1212
REDIRECTION_APPEND = ">>"
13-
REDIRECTION_CHARS = [REDIRECTION_PIPE, REDIRECTION_OVERWRITE]
14-
REDIRECTION_TOKENS = [REDIRECTION_PIPE, REDIRECTION_OVERWRITE, REDIRECTION_APPEND]
13+
REDIRECTION_CHARS = (REDIRECTION_PIPE, REDIRECTION_OVERWRITE)
14+
REDIRECTION_TOKENS = (REDIRECTION_PIPE, REDIRECTION_OVERWRITE, REDIRECTION_APPEND)
1515
COMMENT_CHAR = "#"
1616
MULTILINE_TERMINATOR = ";"
1717

0 commit comments

Comments
 (0)