130
130
from .rl_utils import (
131
131
RlType ,
132
132
rl_escape_prompt ,
133
+ rl_get_display_prompt ,
133
134
rl_get_point ,
134
135
rl_get_prompt ,
136
+ rl_in_search_mode ,
135
137
rl_set_prompt ,
136
138
rl_type ,
137
139
rl_warning ,
@@ -3295,6 +3297,12 @@ def _set_up_cmd2_readline(self) -> _SavedReadlineSettings:
3295
3297
"""
3296
3298
readline_settings = _SavedReadlineSettings ()
3297
3299
3300
+ if rl_type == RlType .GNU :
3301
+ # To calculate line count when printing async_alerts, we rely on commands wider than
3302
+ # the terminal to wrap across multiple lines. The default for horizontal-scroll-mode
3303
+ # is "off" but a user may have overridden it in their readline initialization file.
3304
+ readline .parse_and_bind ("set horizontal-scroll-mode off" )
3305
+
3298
3306
if self ._completion_supported ():
3299
3307
# Set up readline for our tab completion needs
3300
3308
if rl_type == RlType .GNU :
@@ -5270,14 +5278,34 @@ class TestMyAppCase(Cmd2TestCase):
5270
5278
# Return a failure error code to support automated transcript-based testing
5271
5279
self .exit_code = 1
5272
5280
5281
+ def need_prompt_refresh (self , new_prompt : str ) -> bool : # pragma: no cover
5282
+ """
5283
+ Check if the onscreen prompt needs to be refreshed.
5284
+
5285
+ There are two cases when the onscreen prompt needs to be refreshed.
5286
+ 1. self.prompt differs from the new prompt.
5287
+ 2. readline's prompt differs from the new prompt.
5288
+
5289
+ This case occurs when async_alert() is called while a user is in search mode (e.g. Ctrl-r).
5290
+ To prevent overwriting readline's onscreen search prompt, async_alert() does not update
5291
+ readline's saved prompt value. Therefore when a user aborts a search, the old prompt
5292
+ is still on screen until they press Enter or another call to async_alert() is made.
5293
+
5294
+ This function is a convenient way for an async alert thread to check both cases.
5295
+
5296
+ :param new_prompt: the new prompt string
5297
+ :return: True if the onscreen prompt needs to be refreshed, otherwise False.
5298
+ """
5299
+ return new_prompt != self .prompt or new_prompt != rl_get_prompt ()
5300
+
5273
5301
def async_alert (self , alert_msg : str , new_prompt : Optional [str ] = None ) -> None : # pragma: no cover
5274
5302
"""
5275
5303
Display an important message to the user while they are at a command line prompt.
5276
5304
To the user it appears as if an alert message is printed above the prompt and their current input
5277
5305
text and cursor location is left alone.
5278
5306
5279
5307
IMPORTANT: This function will not print an alert unless it can acquire self.terminal_lock to ensure
5280
- a prompt is onscreen . Therefore, it is best to acquire the lock before calling this function
5308
+ a prompt is on screen . Therefore, it is best to acquire the lock before calling this function
5281
5309
to guarantee the alert prints and to avoid raising a RuntimeError.
5282
5310
5283
5311
This function is only needed when you need to print an alert while the main thread is blocking
@@ -5309,20 +5337,21 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
5309
5337
if new_prompt is not None :
5310
5338
self .prompt = new_prompt
5311
5339
5312
- # Check if the prompt to display has changed from what's currently displayed
5313
- cur_onscreen_prompt = rl_get_prompt ()
5314
- new_onscreen_prompt = self .continuation_prompt if self ._at_continuation_prompt else self .prompt
5315
-
5316
- if new_onscreen_prompt != cur_onscreen_prompt :
5317
- update_terminal = True
5340
+ # We won't change readline's prompt while it is in search mode (e.g. Ctrl-r).
5341
+ # Otherwise the new prompt will overwrite the onscreen search prompt.
5342
+ if not rl_in_search_mode ():
5343
+ new_rl_prompt = self .continuation_prompt if self ._at_continuation_prompt else self .prompt
5344
+ if new_rl_prompt != rl_get_prompt ():
5345
+ update_terminal = True
5346
+ rl_set_prompt (new_rl_prompt )
5318
5347
5319
5348
if update_terminal :
5320
5349
import shutil
5321
5350
5322
- # Generate the string which will replace the current prompt and input lines with the alert
5351
+ # Print a string which replaces the current prompt and input lines with the alert.
5323
5352
terminal_str = ansi .async_alert_str (
5324
5353
terminal_columns = shutil .get_terminal_size ().columns ,
5325
- prompt = cur_onscreen_prompt ,
5354
+ prompt = rl_get_display_prompt () ,
5326
5355
line = readline .get_line_buffer (),
5327
5356
cursor_offset = rl_get_point (),
5328
5357
alert_msg = alert_msg ,
@@ -5333,9 +5362,6 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
5333
5362
elif rl_type == RlType .PYREADLINE :
5334
5363
readline .rl .mode .console .write (terminal_str )
5335
5364
5336
- # Update Readline's prompt before we redraw it
5337
- rl_set_prompt (new_onscreen_prompt )
5338
-
5339
5365
# Redraw the prompt and input lines below the alert
5340
5366
rl_force_redisplay ()
5341
5367
0 commit comments