130130from .rl_utils import (
131131 RlType ,
132132 rl_escape_prompt ,
133+ rl_get_display_prompt ,
133134 rl_get_point ,
134135 rl_get_prompt ,
136+ rl_in_search_mode ,
135137 rl_set_prompt ,
136138 rl_type ,
137139 rl_warning ,
@@ -3295,6 +3297,12 @@ def _set_up_cmd2_readline(self) -> _SavedReadlineSettings:
32953297 """
32963298 readline_settings = _SavedReadlineSettings ()
32973299
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+
32983306 if self ._completion_supported ():
32993307 # Set up readline for our tab completion needs
33003308 if rl_type == RlType .GNU :
@@ -5270,14 +5278,34 @@ class TestMyAppCase(Cmd2TestCase):
52705278 # Return a failure error code to support automated transcript-based testing
52715279 self .exit_code = 1
52725280
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+
52735301 def async_alert (self , alert_msg : str , new_prompt : Optional [str ] = None ) -> None : # pragma: no cover
52745302 """
52755303 Display an important message to the user while they are at a command line prompt.
52765304 To the user it appears as if an alert message is printed above the prompt and their current input
52775305 text and cursor location is left alone.
52785306
52795307 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
52815309 to guarantee the alert prints and to avoid raising a RuntimeError.
52825310
52835311 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:
53095337 if new_prompt is not None :
53105338 self .prompt = new_prompt
53115339
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 )
53185347
53195348 if update_terminal :
53205349 import shutil
53215350
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.
53235352 terminal_str = ansi .async_alert_str (
53245353 terminal_columns = shutil .get_terminal_size ().columns ,
5325- prompt = cur_onscreen_prompt ,
5354+ prompt = rl_get_display_prompt () ,
53265355 line = readline .get_line_buffer (),
53275356 cursor_offset = rl_get_point (),
53285357 alert_msg = alert_msg ,
@@ -5333,9 +5362,6 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
53335362 elif rl_type == RlType .PYREADLINE :
53345363 readline .rl .mode .console .write (terminal_str )
53355364
5336- # Update Readline's prompt before we redraw it
5337- rl_set_prompt (new_onscreen_prompt )
5338-
53395365 # Redraw the prompt and input lines below the alert
53405366 rl_force_redisplay ()
53415367
0 commit comments