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 ,
@@ -353,6 +355,7 @@ def __init__(
353
355
self .hidden_commands = ['eof' , '_relative_run_script' ]
354
356
355
357
# Initialize history
358
+ self .persistent_history_file = ''
356
359
self ._persistent_history_length = persistent_history_length
357
360
self ._initialize_history (persistent_history_file )
358
361
@@ -3295,6 +3298,12 @@ def _set_up_cmd2_readline(self) -> _SavedReadlineSettings:
3295
3298
"""
3296
3299
readline_settings = _SavedReadlineSettings ()
3297
3300
3301
+ if rl_type == RlType .GNU :
3302
+ # To calculate line count when printing async_alerts, we rely on commands wider than
3303
+ # the terminal to wrap across multiple lines. The default for horizontal-scroll-mode
3304
+ # is "off" but a user may have overridden it in their readline initialization file.
3305
+ readline .parse_and_bind ("set horizontal-scroll-mode off" )
3306
+
3298
3307
if self ._completion_supported ():
3299
3308
# Set up readline for our tab completion needs
3300
3309
if rl_type == RlType .GNU :
@@ -5273,16 +5282,16 @@ class TestMyAppCase(Cmd2TestCase):
5273
5282
def async_alert (self , alert_msg : str , new_prompt : Optional [str ] = None ) -> None : # pragma: no cover
5274
5283
"""
5275
5284
Display an important message to the user while they are at a command line prompt.
5276
- To the user it appears as if an alert message is printed above the prompt and their current input
5277
- text and cursor location is left alone.
5285
+ To the user it appears as if an alert message is printed above the prompt and their
5286
+ current input text and cursor location is left alone.
5278
5287
5279
- 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
5281
- to guarantee the alert prints and to avoid raising a RuntimeError.
5288
+ This function needs to acquire self.terminal_lock to ensure a prompt is on screen.
5289
+ Therefore, it is best to acquire the lock before calling this function to avoid
5290
+ raising a RuntimeError.
5282
5291
5283
- This function is only needed when you need to print an alert while the main thread is blocking
5284
- at the prompt. Therefore, this should never be called from the main thread. Doing so will
5285
- raise a RuntimeError.
5292
+ This function is only needed when you need to print an alert or update the prompt while the
5293
+ main thread is blocking at the prompt. Therefore, this should never be called from the main
5294
+ thread. Doing so will raise a RuntimeError.
5286
5295
5287
5296
:param alert_msg: the message to display to the user
5288
5297
:param new_prompt: If you also want to change the prompt that is displayed, then include it here.
@@ -5309,20 +5318,18 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
5309
5318
if new_prompt is not None :
5310
5319
self .prompt = new_prompt
5311
5320
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 :
5321
+ # Check if the onscreen prompt needs to be refreshed to match self.prompt.
5322
+ if self .need_prompt_refresh ():
5317
5323
update_terminal = True
5324
+ rl_set_prompt (self .prompt )
5318
5325
5319
5326
if update_terminal :
5320
5327
import shutil
5321
5328
5322
- # Generate the string which will replace the current prompt and input lines with the alert
5329
+ # Print a string which replaces the onscreen prompt and input lines with the alert.
5323
5330
terminal_str = ansi .async_alert_str (
5324
5331
terminal_columns = shutil .get_terminal_size ().columns ,
5325
- prompt = cur_onscreen_prompt ,
5332
+ prompt = rl_get_display_prompt () ,
5326
5333
line = readline .get_line_buffer (),
5327
5334
cursor_offset = rl_get_point (),
5328
5335
alert_msg = alert_msg ,
@@ -5333,9 +5340,6 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
5333
5340
elif rl_type == RlType .PYREADLINE :
5334
5341
readline .rl .mode .console .write (terminal_str )
5335
5342
5336
- # Update Readline's prompt before we redraw it
5337
- rl_set_prompt (new_onscreen_prompt )
5338
-
5339
5343
# Redraw the prompt and input lines below the alert
5340
5344
rl_force_redisplay ()
5341
5345
@@ -5346,30 +5350,50 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
5346
5350
5347
5351
def async_update_prompt (self , new_prompt : str ) -> None : # pragma: no cover
5348
5352
"""
5349
- Update the command line prompt while the user is still typing at it. This is good for alerting the user to
5350
- system changes dynamically in between commands. For instance you could alter the color of the prompt to
5351
- indicate a system status or increase a counter to report an event. If you do alter the actual text of the
5352
- prompt, it is best to keep the prompt the same width as what's on screen. Otherwise the user's input text will
5353
- be shifted and the update will not be seamless.
5353
+ Update the command line prompt while the user is still typing at it.
5354
5354
5355
- IMPORTANT: This function will not update the prompt unless it can acquire self.terminal_lock to ensure
5356
- a prompt is onscreen. Therefore, it is best to acquire the lock before calling this function
5357
- to guarantee the prompt changes and to avoid raising a RuntimeError.
5355
+ This is good for alerting the user to system changes dynamically in between commands.
5356
+ For instance you could alter the color of the prompt to indicate a system status or increase a
5357
+ counter to report an event. If you do alter the actual text of the prompt, it is best to keep
5358
+ the prompt the same width as what's on screen. Otherwise the user's input text will be shifted
5359
+ and the update will not be seamless.
5358
5360
5359
- This function is only needed when you need to update the prompt while the main thread is blocking
5360
- at the prompt. Therefore, this should never be called from the main thread. Doing so will
5361
- raise a RuntimeError.
5362
-
5363
- If user is at a continuation prompt while entering a multiline command, the onscreen prompt will
5364
- not change. However, self.prompt will still be updated and display immediately after the multiline
5365
- line command completes.
5361
+ If user is at a continuation prompt while entering a multiline command, the onscreen prompt will
5362
+ not change. However, self.prompt will still be updated and display immediately after the multiline
5363
+ line command completes.
5366
5364
5367
5365
:param new_prompt: what to change the prompt to
5368
5366
:raises RuntimeError: if called from the main thread.
5369
5367
:raises RuntimeError: if called while another thread holds `terminal_lock`
5370
5368
"""
5371
5369
self .async_alert ('' , new_prompt )
5372
5370
5371
+ def async_refresh_prompt (self ) -> None : # pragma: no cover
5372
+ """
5373
+ Refresh the oncreen prompt to match self.prompt.
5374
+
5375
+ One case where the onscreen prompt and self.prompt can get out of sync is
5376
+ when async_alert() is called while a user is in search mode (e.g. Ctrl-r).
5377
+ To prevent overwriting readline's onscreen search prompt, self.prompt is updated
5378
+ but readline's saved prompt isn't.
5379
+
5380
+ Therefore when a user aborts a search, the old prompt is still on screen until they
5381
+ press Enter or this method is called. Call need_prompt_refresh() in an async print
5382
+ thread to know when a refresh is needed.
5383
+
5384
+ :raises RuntimeError: if called from the main thread.
5385
+ :raises RuntimeError: if called while another thread holds `terminal_lock`
5386
+ """
5387
+ self .async_alert ('' )
5388
+
5389
+ def need_prompt_refresh (self ) -> bool : # pragma: no cover
5390
+ """Check whether the onscreen prompt needs to be asynchronously refreshed to match self.prompt."""
5391
+ if not (vt100_support and self .use_rawinput ):
5392
+ return False
5393
+
5394
+ # Don't overwrite a readline search prompt or a continuation prompt.
5395
+ return not rl_in_search_mode () and not self ._at_continuation_prompt and self .prompt != rl_get_prompt ()
5396
+
5373
5397
@staticmethod
5374
5398
def set_window_title (title : str ) -> None : # pragma: no cover
5375
5399
"""
0 commit comments