Skip to content

Commit 1486c09

Browse files
committed
Renamed bottom_toolbar flag to enable_bottom_toolbar.
Added enable_rprompt flag.
1 parent 5dbf068 commit 1486c09

7 files changed

Lines changed: 112 additions & 40 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
thread. If `False` then completion runs in the main thread and causes it to block if slow.
77
Defaults to `True`.
88
- **refresh_interval**: (float) How often, in seconds, to automatically refresh the UI.
9-
Defaults to 0.0.
9+
Defaults to 0.0. This is used for bottom toolbars and right prompts which have dynamic
10+
content needing to be refreshed at regular intervals and not just when a key is pressed.
1011

1112
- Bug Fixes
1213
- Fixed type hinting so that methods decorated with `with_annotated` no longer trigger spurious
@@ -48,9 +49,10 @@
4849
`preprocess=str.lower` on an `Enum`). The two are mutually exclusive on one parameter and
4950
neither may be combined with a value-less action.
5051

51-
- Breaking Changes
52-
- Removed `Cmd.bottom_toolbar` boolean. Just return `None` from `Cmd.get_bottom_toolbar()`
53-
when you don't want to display one. This is exactly how `Cmd.get_rprompt()` works.
52+
- Breaking Changes
53+
- Renamed the `bottom_toolbar` argument in `Cmd.__init__()` to `enable_bottom_toolbar`.
54+
- `get_rprompt()` is now only called if the `enable_rprompt` argument in `Cmd.__init__()` is set
55+
to `True`.
5456

5557
## 4.0.0 (June 5, 2026)
5658

cmd2/cmd2.py

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,8 @@ def __init__(
370370
auto_suggest: bool = True,
371371
complete_in_thread: bool = True,
372372
command_sets: Iterable[CommandSet[Any]] | None = None,
373+
enable_bottom_toolbar: bool = False,
374+
enable_rprompt: bool = False,
373375
include_ipy: bool = False,
374376
include_py: bool = False,
375377
intro: RenderableType = "",
@@ -410,6 +412,10 @@ def __init__(
410412
This allows CommandSets with custom constructor parameters to be
411413
loaded. This also allows the a set of CommandSets to be provided
412414
when `auto_load_commands` is set to False
415+
:param enable_bottom_toolbar: if ``True``, enables a bottom toolbar while at the main prompt.
416+
Override ``get_bottom_toolbar()`` to define its content.
417+
:param enable_rprompt: if ``True``, enables a right prompt while at the main prompt.
418+
Override ``get_rprompt()`` to define its content.
413419
:param include_ipy: should the "ipy" command be included for an embedded IPython shell
414420
:param include_py: should the "py" command be included for an embedded Python shell
415421
:param intro: introduction to display at startup
@@ -534,9 +540,14 @@ def __init__(
534540
self._initialize_history(persistent_history_file)
535541

536542
# Create the main PromptSession
537-
self.complete_in_thread = complete_in_thread
538-
self.refresh_interval = refresh_interval
539-
self.main_session = self._create_main_session(auto_suggest, completekey)
543+
self.main_session = self._create_main_session(
544+
auto_suggest=auto_suggest,
545+
complete_in_thread=complete_in_thread,
546+
completekey=completekey,
547+
enable_bottom_toolbar=enable_bottom_toolbar,
548+
enable_rprompt=enable_rprompt,
549+
refresh_interval=refresh_interval,
550+
)
540551

541552
# The session currently holding focus (either the main REPL or a command's
542553
# custom prompt). Completion and UI logic should reference this variable
@@ -727,7 +738,16 @@ def _should_continue_multiline(self) -> bool:
727738
# No macro found or already processed. The statement is complete.
728739
return False
729740

730-
def _create_main_session(self, auto_suggest: bool, completekey: str) -> PromptSession[str]:
741+
def _create_main_session(
742+
self,
743+
*,
744+
auto_suggest: bool,
745+
complete_in_thread: bool,
746+
completekey: str,
747+
enable_bottom_toolbar: bool,
748+
enable_rprompt: bool,
749+
refresh_interval: float,
750+
) -> PromptSession[str]:
731751
"""Create and return the main PromptSession for the application.
732752
733753
Builds an interactive session if self.stdin and self.stdout are TTYs.
@@ -757,19 +777,19 @@ def _(event: Any) -> None: # pragma: no cover
757777
# Base configuration
758778
kwargs: dict[str, Any] = {
759779
"auto_suggest": AutoSuggestFromHistory() if auto_suggest else None,
760-
"bottom_toolbar": self.get_bottom_toolbar,
780+
"bottom_toolbar": self.get_bottom_toolbar if enable_bottom_toolbar else None,
761781
"color_depth": ColorDepth.TRUE_COLOR,
762782
"complete_style": CompleteStyle.MULTI_COLUMN,
763-
"complete_in_thread": self.complete_in_thread,
783+
"complete_in_thread": complete_in_thread,
764784
"complete_while_typing": False,
765785
"completer": Cmd2Completer(self),
766786
"history": Cmd2History(item.raw for item in self.history),
767787
"key_bindings": key_bindings,
768788
"lexer": Cmd2Lexer(self),
769789
"multiline": filters.Condition(self._should_continue_multiline),
770790
"prompt_continuation": self.continuation_prompt,
771-
"refresh_interval": self.refresh_interval,
772-
"rprompt": self.get_rprompt,
791+
"refresh_interval": refresh_interval,
792+
"rprompt": self.get_rprompt if enable_rprompt else None,
773793
"style": DynamicStyle(get_pt_theme),
774794
}
775795

@@ -1984,32 +2004,32 @@ def ppretty(
19842004
def get_bottom_toolbar(self) -> AnyFormattedText:
19852005
"""Get the bottom toolbar content.
19862006
1987-
This method is intended to be called by prompt-toolkit as a UI lifecycle callback.
1988-
Because prompt-toolkit triggers this callback on every UI refresh (e.g., on every
1989-
keypress and at scheduled refresh intervals), keeping this function highly optimized
1990-
is critical to ensuring the CLI remains responsive.
2007+
This method is called by prompt-toolkit while at the main prompt if ``enable_bottom_toolbar``
2008+
was set to ``True`` during initialization. Because prompt-toolkit executes this callback
2009+
on every UI refresh (such as on every keypress or timed interval), keeping this function
2010+
highly optimized is critical to ensuring the CLI remains responsive.
19912011
19922012
Override this if you want a bottom toolbar displaying contextual information useful for
19932013
your application. This could be information like the application name, current state,
19942014
or even a real-time clock.
19952015
1996-
:return: Content to populate the bottom toolbar, or None to hide it.
2016+
:return: Content to populate the bottom toolbar.
19972017
"""
19982018
return None
19992019

20002020
def get_rprompt(self) -> AnyFormattedText:
20012021
"""Provide text to populate the prompt-toolkit right prompt.
20022022
2003-
This method is intended to be called by prompt-toolkit as a UI lifecycle callback.
2004-
Because prompt-toolkit triggers this callback on every UI refresh (e.g., on every
2005-
keypress and at scheduled refresh intervals), keeping this function highly optimized
2006-
is critical to ensuring the CLI remains responsive.
2023+
This method is called by prompt-toolkit while at the main prompt if ``enable_rprompt``
2024+
was set to ``True`` during initialization. Because prompt-toolkit executes this callback
2025+
on every UI refresh (such as on every keypress or timed interval), keeping this function
2026+
highly optimized is critical to ensuring the CLI remains responsive.
20072027
2008-
Override this if you want a right-prompt displaying contextual information useful for
2028+
Override this if you want a right prompt displaying contextual information useful for
20092029
your application. This could be information like the current Git branch, time, or current
20102030
working directory that is displayed without cluttering the main input area.
20112031
2012-
:return: Content to populate the right prompt, or None to hide it.
2032+
:return: Content to populate the right prompt.
20132033
"""
20142034
return None
20152035

docs/features/initialization.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ The `cmd2.Cmd` class provides a large number of public instance attributes which
3333

3434
Here are instance attributes of `cmd2.Cmd` which developers might wish to override:
3535

36-
- **bottom_toolbar**: if `True`, then a bottom toolbar will be displayed (Default: `False`)
3736
- **broken_pipe_warning**: if non-empty, this string will be displayed if a broken pipe error occurs
3837
- **complete_in_thread**: if `True`, then completion will run in a separate thread (Default: `True`)
3938
- **continuation_prompt**: used for multiline commands on 2nd+ line of input
@@ -42,6 +41,8 @@ Here are instance attributes of `cmd2.Cmd` which developers might wish to overri
4241
- **disabled_commands**: commands that have been disabled from use. This is to support commands that are only available during specific states of the application. This dictionary's keys are the command names and its values are DisabledCommand objects.
4342
- **echo**: if `True`, each command the user issues will be repeated to the screen before it is executed. This is particularly useful when running scripts. This behavior does not occur when running a command at the prompt. (Default: `False`)
4443
- **editor**: text editor program to use with _edit_ command (e.g. `vim`)
44+
- **enable_bottom_toolbar**: if `True`, enables a bottom toolbar while at the main prompt. (Default: `False`)
45+
- **enable_rprompt**: if `True`, enables a right prompt while at the main prompt. (Default: `False`)
4546
- **exclude_from_history**: commands to exclude from the _history_ command
4647
- **exit_code**: this determines the value returned by `cmdloop()` when exiting the application
4748
- **help_error**: the error that prints when no help information can be found
@@ -55,6 +56,7 @@ Here are instance attributes of `cmd2.Cmd` which developers might wish to overri
5556
- **py_bridge_name**: name by which embedded Python environments and scripts refer to the `cmd2` application by in order to call commands (Default: `app`)
5657
- **py_locals**: dictionary that defines specific variables/functions available in Python shells and scripts (provides more fine-grained control than making everything available with **self_in_py**)
5758
- **quiet**: if `True`, then completely suppress nonessential output (Default: `False`)
59+
- **refresh_interval**: how often, in seconds, to automatically refresh the UI. (Default: 0.0)
5860
- **scripts_add_to_history**: if `True`, scripts and pyscripts add commands to history (Default: `True`)
5961
- **self_in_py**: if `True`, allow access to your application in _py_ command via `self` (Default: `False`)
6062
- **settable**: dictionary that controls which of these instance attributes are settable at runtime using the _set_ command

docs/features/prompt.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,22 @@ terminal window while the application is idle and waiting for input.
6565

6666
### Enabling the Toolbar
6767

68-
To enable the toolbar, override the [cmd2.Cmd.get_bottom_toolbar][] method to return the content you
69-
wish to display.
68+
To enable the toolbar, set `enable_bottom_toolbar=True` in the [cmd2.Cmd.__init__][] constructor:
7069

7170
```py
71+
class App(cmd2.Cmd):
72+
def __init__(self):
73+
super().__init__(enable_bottom_toolbar=True)
74+
```
75+
76+
### Customizing Toolbar Content
77+
78+
You can customize the content of the toolbar by overriding the [cmd2.Cmd.get_bottom_toolbar][]
79+
method.
80+
81+
```py
82+
from prompt_toolkit.formatted_text import AnyFormattedText
83+
7284
def get_bottom_toolbar(self) -> AnyFormattedText:
7385
return [
7486
('ansigreen', 'My Application Name'),

docs/upgrades.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ While we have strived to maintain compatibility, there are some differences:
3636
`cmd2` now supports an optional, persistent bottom toolbar. This can be used to display information
3737
such as the application name, current state, or even a real-time clock.
3838

39-
- **Enablement**: Override the [cmd2.Cmd.get_bottom_toolbar][] method to return the content you wish
40-
to display.
39+
- **Enablement**: Set `enable_bottom_toolbar=True` in the [cmd2.Cmd.__init__][] constructor.
40+
- **Customization**: Override the [cmd2.Cmd.get_bottom_toolbar][] method to return the content you
41+
wish to display.
4142

4243
See the
4344
[getting_started.py](https://github.com/python-cmd2/cmd2/blob/main/examples/getting_started.py)

examples/getting_started.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ def __init__(self) -> None:
4646

4747
super().__init__(
4848
auto_suggest=True,
49+
enable_bottom_toolbar=True,
50+
enable_rprompt=True,
4951
include_ipy=True,
5052
multiline_commands=["echo"],
5153
persistent_history_file="cmd2_history.dat",

tests/test_cmd2.py

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,14 @@ def test_version(base_app) -> None:
6767
def test_complete_in_thread() -> None:
6868
# Test default
6969
app_default = cmd2.Cmd()
70-
assert app_default.complete_in_thread is True
7170
assert app_default.main_session.complete_in_thread is True
7271

7372
# Test True
7473
app_true = cmd2.Cmd(complete_in_thread=True)
75-
assert app_true.complete_in_thread is True
7674
assert app_true.main_session.complete_in_thread is True
7775

7876
# Test False
7977
app_false = cmd2.Cmd(complete_in_thread=False)
80-
assert app_false.complete_in_thread is False
8178
assert app_false.main_session.complete_in_thread is False
8279

8380

@@ -4229,13 +4226,14 @@ def test_custom_completekey_ctrl_k():
42294226

42304227
def test_completekey_empty_string() -> None:
42314228
# Test that an empty string for completekey defaults to DEFAULT_COMPLETEKEY
4229+
42324230
with mock.patch("cmd2.Cmd._create_main_session", autospec=True) as create_session_mock:
42334231
create_session_mock.return_value = mock.MagicMock(spec=PromptSession)
4234-
app = cmd2.Cmd(completekey="")
42354232

4236-
# Verify it was called with DEFAULT_COMPLETEKEY
4237-
# auto_suggest is the second arg and it defaults to True
4238-
create_session_mock.assert_called_once_with(app, True, app.DEFAULT_COMPLETEKEY)
4233+
app = cmd2.Cmd(completekey="")
4234+
create_session_mock.assert_called_once()
4235+
_, kwargs = create_session_mock.call_args
4236+
assert kwargs["completekey"] == app.DEFAULT_COMPLETEKEY
42394237

42404238

42414239
def test_create_main_session_exception(monkeypatch):
@@ -4290,12 +4288,40 @@ def test_path_complete_users_windows(monkeypatch, base_app):
42904288

42914289
def test_refresh_interval() -> None:
42924290
# Test default value
4293-
default_refresh_app = cmd2.Cmd()
4294-
assert default_refresh_app.main_session.refresh_interval == 0.0
4291+
default_app = cmd2.Cmd()
4292+
assert default_app.main_session.refresh_interval == 0.0
42954293

42964294
# Test custom value
4297-
custom_refresh_app = cmd2.Cmd(refresh_interval=5.0)
4298-
assert custom_refresh_app.main_session.refresh_interval == 5.0
4295+
custom_app = cmd2.Cmd(refresh_interval=5.0)
4296+
assert custom_app.main_session.refresh_interval == 5.0
4297+
4298+
4299+
def test_enable_bottom_toolbar() -> None:
4300+
# Test default
4301+
default_app = cmd2.Cmd()
4302+
assert default_app.main_session.bottom_toolbar is None
4303+
4304+
# Test True
4305+
custom_app = cmd2.Cmd(enable_bottom_toolbar=True)
4306+
assert custom_app.main_session.bottom_toolbar == custom_app.get_bottom_toolbar
4307+
4308+
# Test False
4309+
custom_app = cmd2.Cmd(enable_bottom_toolbar=False)
4310+
assert custom_app.main_session.bottom_toolbar is None
4311+
4312+
4313+
def test_enable_rprompt() -> None:
4314+
# Test default
4315+
default_app = cmd2.Cmd()
4316+
assert default_app.main_session.rprompt is None
4317+
4318+
# Test True
4319+
custom_app = cmd2.Cmd(enable_rprompt=True)
4320+
assert custom_app.main_session.rprompt == custom_app.get_rprompt
4321+
4322+
# Test False
4323+
custom_app = cmd2.Cmd(enable_rprompt=False)
4324+
assert custom_app.main_session.rprompt is None
42994325

43004326

43014327
def test_get_bottom_toolbar(base_app, monkeypatch):
@@ -4377,7 +4403,14 @@ def test_create_main_session_with_custom_tty() -> None:
43774403
app = cmd2.Cmd()
43784404
app.stdin = custom_stdin
43794405
app.stdout = custom_stdout
4380-
app._create_main_session(auto_suggest=True, completekey=app.DEFAULT_COMPLETEKEY)
4406+
app._create_main_session(
4407+
auto_suggest=True,
4408+
completekey=app.DEFAULT_COMPLETEKEY,
4409+
enable_bottom_toolbar=False,
4410+
enable_rprompt=False,
4411+
complete_in_thread=False,
4412+
refresh_interval=0.0,
4413+
)
43814414

43824415
mock_create_input.assert_called_once_with(stdin=custom_stdin)
43834416
mock_create_output.assert_called_once_with(stdout=custom_stdout)

0 commit comments

Comments
 (0)