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 :
@@ -5277,7 +5285,7 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
5277
5285
text and cursor location is left alone.
5278
5286
5279
5287
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
5288
+ a prompt is on screen . Therefore, it is best to acquire the lock before calling this function
5281
5289
to guarantee the alert prints and to avoid raising a RuntimeError.
5282
5290
5283
5291
This function is only needed when you need to print an alert while the main thread is blocking
@@ -5309,20 +5317,18 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
5309
5317
if new_prompt is not None :
5310
5318
self .prompt = new_prompt
5311
5319
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 :
5320
+ # Check if the onscreen prompt needs to be refreshed to match self.prompt.
5321
+ if self .need_prompt_refresh ():
5317
5322
update_terminal = True
5323
+ rl_set_prompt (self .prompt )
5318
5324
5319
5325
if update_terminal :
5320
5326
import shutil
5321
5327
5322
- # Generate the string which will replace the current prompt and input lines with the alert
5328
+ # Print a string which replaces the onscreen prompt and input lines with the alert.
5323
5329
terminal_str = ansi .async_alert_str (
5324
5330
terminal_columns = shutil .get_terminal_size ().columns ,
5325
- prompt = cur_onscreen_prompt ,
5331
+ prompt = rl_get_display_prompt () ,
5326
5332
line = readline .get_line_buffer (),
5327
5333
cursor_offset = rl_get_point (),
5328
5334
alert_msg = alert_msg ,
@@ -5333,9 +5339,6 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
5333
5339
elif rl_type == RlType .PYREADLINE :
5334
5340
readline .rl .mode .console .write (terminal_str )
5335
5341
5336
- # Update Readline's prompt before we redraw it
5337
- rl_set_prompt (new_onscreen_prompt )
5338
-
5339
5342
# Redraw the prompt and input lines below the alert
5340
5343
rl_force_redisplay ()
5341
5344
@@ -5370,6 +5373,26 @@ def async_update_prompt(self, new_prompt: str) -> None: # pragma: no cover
5370
5373
"""
5371
5374
self .async_alert ('' , new_prompt )
5372
5375
5376
+ def need_prompt_refresh (self ) -> bool : # pragma: no cover
5377
+ """
5378
+ Used by async print threads to check whether the onscreen prompt needs to be
5379
+ refreshed to match self.prompt.
5380
+
5381
+ One case where the onscreen prompt and self.prompt can get out of sync is
5382
+ when async_alert() is called while a user is in search mode (e.g. Ctrl-r).
5383
+ To prevent overwriting readline's onscreen search prompt, self.prompt is updated
5384
+ but readline's saved prompt isn't.
5385
+
5386
+ Therefore when a user aborts a search, the old prompt is still on screen until they
5387
+ press Enter or async_alert() is called again. Use this function in an async print
5388
+ thread to know when to make that extra call to async_alert().
5389
+ """
5390
+ if not (vt100_support and self .use_rawinput ):
5391
+ return False
5392
+
5393
+ # Don't overwrite a readline search prompt or a continuation prompt.
5394
+ return not rl_in_search_mode () and not self ._at_continuation_prompt and self .prompt != rl_get_prompt ()
5395
+
5373
5396
@staticmethod
5374
5397
def set_window_title (title : str ) -> None : # pragma: no cover
5375
5398
"""
0 commit comments