Skip to content

Commit b17b1bd

Browse files
committed
Added UI refresh interval setting.
1 parent 8a644dc commit b17b1bd

2 files changed

Lines changed: 39 additions & 54 deletions

File tree

cmd2/cmd2.py

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ def __init__(
376376
multiline_commands: Iterable[str] | None = None,
377377
persistent_history_file: str = "",
378378
persistent_history_length: int = 1000,
379+
refresh_interval: float = 0,
379380
shortcuts: Mapping[str, str] | None = None,
380381
silence_startup_script: bool = False,
381382
startup_script: str = "",
@@ -417,6 +418,11 @@ def __init__(
417418
:param persistent_history_file: file path to load a persistent cmd2 command history from
418419
:param persistent_history_length: max number of history items to write
419420
to the persistent history file
421+
:param refresh_interval: How often, in seconds, to refresh the UI. Defaults to 0.
422+
prompt-toolkit already refreshes the UI every time a key is pressed.
423+
Set this value if you need the UI to update automatically without
424+
user input (e.g., for displaying a clock or background status
425+
updates in the bottom toolbar).
420426
:param shortcuts: Mapping containing shortcuts for commands. If not supplied,
421427
then defaults to constants.DEFAULT_SHORTCUTS. If you do not want
422428
any shortcuts, pass None and an empty dictionary will be created.
@@ -531,6 +537,7 @@ def __init__(
531537
# Create the main PromptSession
532538
self.bottom_toolbar = bottom_toolbar
533539
self.complete_in_thread = complete_in_thread
540+
self.refresh_interval = refresh_interval
534541
self.main_session = self._create_main_session(auto_suggest, completekey)
535542

536543
# The session currently holding focus (either the main REPL or a command's
@@ -763,6 +770,7 @@ def _(event: Any) -> None: # pragma: no cover
763770
"lexer": Cmd2Lexer(self),
764771
"multiline": filters.Condition(self._should_continue_multiline),
765772
"prompt_continuation": self.continuation_prompt,
773+
"refresh_interval": self.refresh_interval,
766774
"rprompt": self.get_rprompt,
767775
"style": DynamicStyle(get_pt_theme),
768776
}
@@ -1978,36 +1986,37 @@ def ppretty(
19781986
def get_bottom_toolbar(self) -> list[str | tuple[str, str]] | None:
19791987
"""Get the bottom toolbar content.
19801988
1981-
If self.bottom_toolbar is False, returns None.
1989+
Returns None if `self.bottom_toolbar` is False. Otherwise, returns a
1990+
list of tokens to populate the toolbar (which can span multiple lines).
19821991
1983-
Otherwise returns tokens for prompt-toolkit to populate in the bottom toolbar.
1984-
1985-
NOTE: This content can extend over multiple lines. However we would recommend
1986-
keeping it to a single line or two lines maximum.
1992+
NOTE: prompt-toolkit calls this method on every UI refresh (e.g., on every keypress
1993+
and at scheduled refresh intervals). To ensure the CLI remains responsive, keep
1994+
this function highly optimized.
19871995
"""
1988-
if self.bottom_toolbar:
1989-
import datetime
1990-
import shutil
1991-
1992-
# Get the current time in ISO format with 0.01s precision
1993-
dt = datetime.datetime.now(datetime.timezone.utc).astimezone()
1994-
now = dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-4] + dt.strftime("%z")
1995-
left_text = sys.argv[0]
1996-
1997-
# Get terminal width to calculate padding for right-alignment
1998-
cols, _ = shutil.get_terminal_size()
1999-
padding_size = cols - len(left_text) - len(now) - 1
2000-
if padding_size < 1:
2001-
padding_size = 1
2002-
padding = " " * padding_size
2003-
2004-
# Return formatted text for prompt-toolkit
2005-
return [
2006-
("ansigreen", left_text),
2007-
("", padding),
2008-
("ansicyan", now),
2009-
]
2010-
return None
1996+
if not self.bottom_toolbar:
1997+
return None
1998+
1999+
import datetime
2000+
import shutil
2001+
2002+
# Get the current time in ISO format with 0.01s precision
2003+
dt = datetime.datetime.now(datetime.timezone.utc).astimezone()
2004+
now = dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-4] + dt.strftime("%z")
2005+
left_text = sys.argv[0]
2006+
2007+
# Get terminal width to calculate padding for right-alignment
2008+
cols, _ = shutil.get_terminal_size()
2009+
padding_size = cols - len(left_text) - len(now) - 1
2010+
if padding_size < 1:
2011+
padding_size = 1
2012+
padding = " " * padding_size
2013+
2014+
# Return formatted text for prompt-toolkit
2015+
return [
2016+
("ansigreen", left_text),
2017+
("", padding),
2018+
("ansicyan", now),
2019+
]
20112020

20122021
def get_rprompt(self) -> str | FormattedText | None:
20132022
"""Provide text to populate prompt-toolkit right prompt with.

examples/getting_started.py

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
"""
1818

1919
import pathlib
20-
import threading
21-
import time
2220

2321
from prompt_toolkit.formatted_text import FormattedText
2422
from rich.style import Style
@@ -43,22 +41,18 @@ def __init__(self) -> None:
4341
# Create a shortcut for one of our commands
4442
shortcuts = cmd2.DEFAULT_SHORTCUTS
4543
shortcuts.update({"&": "intro"})
44+
4645
super().__init__(
4746
auto_suggest=True,
4847
bottom_toolbar=True,
4948
include_ipy=True,
5049
multiline_commands=["echo"],
5150
persistent_history_file="cmd2_history.dat",
51+
refresh_interval=0.5, # refresh the UI twice a second to keep the bottom toolbar timestamp current
5252
shortcuts=shortcuts,
5353
startup_script=str(alias_script),
5454
)
5555

56-
# Spawn a background thread to refresh the bottom toolbar twice a second.
57-
# This is necessary because the toolbar contains a timestamp that we want to keep current.
58-
self._stop_refresh = False
59-
self._refresh_thread = threading.Thread(target=self._refresh_bottom_toolbar, daemon=True)
60-
self._refresh_thread.start()
61-
6256
# Prints an intro banner once upon application startup
6357
self.intro = (
6458
stylize(
@@ -99,24 +93,6 @@ def get_rprompt(self) -> str | FormattedText | None:
9993
text = f"cwd={current_working_directory}"
10094
return FormattedText([(style, text)])
10195

102-
def _refresh_bottom_toolbar(self) -> None:
103-
"""Background thread target to refresh the bottom toolbar.
104-
105-
This is a toy example to show how the bottom toolbar can be used to display
106-
realtime status updates in an otherwise line-oriented command interpreter.
107-
"""
108-
import contextlib
109-
110-
from prompt_toolkit.application.current import get_app
111-
112-
while not self._stop_refresh:
113-
with contextlib.suppress(Exception):
114-
# get_app() will return the currently running prompt-toolkit application
115-
app = get_app()
116-
if app:
117-
app.invalidate()
118-
time.sleep(0.5)
119-
12096
def do_intro(self, _: cmd2.Statement) -> None:
12197
"""Display the intro banner."""
12298
self.poutput(self.intro)

0 commit comments

Comments
 (0)