Skip to content

Commit 467be57

Browse files
authored
Merge pull request #570 from python-cmd2/title_lock
set_window_title() will no longer write to stderr unless self._terminal_lock can be acquired
2 parents ad5614e + 35efc5d commit 467be57

File tree

1 file changed

+32
-17
lines changed

1 file changed

+32
-17
lines changed

cmd2/cmd2.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -548,8 +548,8 @@ def __init__(self, completekey: str='tab', stdin=None, stdout=None, persistent_h
548548
self.exit_code = None
549549

550550
# This lock should be acquired before doing any asynchronous changes to the terminal to
551-
# ensure the updates to the terminal don't interfere with the input being typed. It can be
552-
# acquired any time there is a readline prompt on screen.
551+
# ensure the updates to the terminal don't interfere with the input being typed or output
552+
# being printed by a command.
553553
self.terminal_lock = threading.RLock()
554554

555555
# ----- Methods related to presenting output to the user -----
@@ -3481,12 +3481,13 @@ def _clear_input_lines_str(self) -> str: # pragma: no cover
34813481

34823482
def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None: # pragma: no cover
34833483
"""
3484-
Used to display an important message to the user while they are at the prompt in between commands.
3484+
Display an important message to the user while they are at the prompt in between commands.
34853485
To the user it appears as if an alert message is printed above the prompt and their current input
34863486
text and cursor location is left alone.
34873487
3488-
IMPORTANT: Do not call this unless you have acquired self.terminal_lock
3489-
first, which ensures a prompt is onscreen
3488+
IMPORTANT: This function will not print an alert unless it can acquire self.terminal_lock to ensure
3489+
a prompt is onscreen. Therefore it is best to acquire the lock before calling this function
3490+
to guarantee the alert prints.
34903491
34913492
:param alert_msg: the message to display to the user
34923493
:param new_prompt: if you also want to change the prompt that is displayed, then include it here
@@ -3525,34 +3526,48 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
35253526

35263527
def async_update_prompt(self, new_prompt: str) -> None: # pragma: no cover
35273528
"""
3528-
Updates the prompt while the user is still typing at it. This is good for alerting the user to system
3529+
Update the prompt while the user is still typing at it. This is good for alerting the user to system
35293530
changes dynamically in between commands. For instance you could alter the color of the prompt to indicate
35303531
a system status or increase a counter to report an event. If you do alter the actual text of the prompt,
35313532
it is best to keep the prompt the same width as what's on screen. Otherwise the user's input text will
35323533
be shifted and the update will not be seamless.
35333534
3534-
IMPORTANT: Do not call this unless you have acquired self.terminal_lock
3535-
first, which ensures a prompt is onscreen
3535+
IMPORTANT: This function will not update the prompt unless it can acquire self.terminal_lock to ensure
3536+
a prompt is onscreen. Therefore it is best to acquire the lock before calling this function
3537+
to guarantee the prompt changes.
35363538
35373539
:param new_prompt: what to change the prompt to
3540+
:raises RuntimeError if called while another thread holds terminal_lock
35383541
"""
35393542
self.async_alert('', new_prompt)
35403543

3541-
@staticmethod
3542-
def set_window_title(title: str) -> None: # pragma: no cover
3544+
def set_window_title(self, title: str) -> None: # pragma: no cover
35433545
"""
3544-
Sets the terminal window title
3546+
Set the terminal window title
3547+
3548+
IMPORTANT: This function will not set the title unless it can acquire self.terminal_lock to avoid
3549+
writing to stderr while a command is running. Therefore it is best to acquire the lock
3550+
before calling this function to guarantee the title changes.
3551+
35453552
:param title: the new window title
3553+
:raises RuntimeError if called while another thread holds terminal_lock
35463554
"""
35473555
if not vt100_support:
35483556
return
35493557

3550-
import colorama.ansi as ansi
3551-
try:
3552-
sys.stderr.write(ansi.set_title(title))
3553-
except AttributeError:
3554-
# Debugging in Pycharm has issues with setting terminal title
3555-
pass
3558+
# Sanity check that can't fail if self.terminal_lock was acquired before calling this function
3559+
if self.terminal_lock.acquire(blocking=False):
3560+
try:
3561+
import colorama.ansi as ansi
3562+
sys.stderr.write(ansi.set_title(title))
3563+
except AttributeError:
3564+
# Debugging in Pycharm has issues with setting terminal title
3565+
pass
3566+
3567+
self.terminal_lock.release()
3568+
3569+
else:
3570+
raise RuntimeError("another thread holds terminal_lock")
35563571

35573572
def cmdloop(self, intro: Optional[str]=None) -> None:
35583573
"""This is an outer wrapper around _cmdloop() which deals with extra features provided by cmd2.

0 commit comments

Comments
 (0)