3232import argparse
3333import cmd
3434import collections
35- import colorama
36- from colorama import Fore
3735import glob
3836import inspect
3937import os
4341import threading
4442from typing import Any , Callable , Dict , List , Mapping , Optional , Tuple , Type , Union , IO
4543
44+ import colorama
45+ from colorama import Fore
46+ from wcwidth import wcswidth
47+
4648from . import constants
47- from . import utils
4849from . import plugin
50+ from . import utils
4951from .argparse_completer import AutoCompleter , ACArgumentParser , ACTION_ARG_CHOICES
5052from .clipboard import can_clip , get_paste_buffer , write_to_paste_buffer
5153from .parsing import StatementParser , Statement , Macro , MacroArg
5254
5355# Set up readline
5456from .rl_utils import rl_type , RlType , rl_get_point , rl_set_prompt , vt100_support , rl_make_safe_prompt
57+
5558if rl_type == RlType .NONE : # pragma: no cover
5659 rl_warning = "Readline features including tab completion have been disabled since no \n " \
5760 "supported version of readline was found. To resolve this, install \n " \
7174
7275 elif rl_type == RlType .GNU :
7376
74- # We need wcswidth to calculate display width of tab completions
75- from wcwidth import wcswidth
76-
7777 # Get the readline lib so we can make changes to it
7878 import ctypes
7979 from .rl_utils import readline_lib
@@ -3436,11 +3436,12 @@ class TestMyAppCase(Cmd2TestCase):
34363436 runner = unittest .TextTestRunner ()
34373437 runner .run (testcase )
34383438
3439- def _clear_input_lines_str (self ) -> str : # pragma: no cover
3439+ def _clear_input_lines_str (self , current_prompt : str ) -> str : # pragma: no cover
34403440 """
34413441 Returns a string that if printed will clear the prompt and input lines in the terminal,
34423442 leaving the cursor at the beginning of the first input line
3443- :return: the string to print
3443+
3444+ :param current_prompt: the currently displayed prompt
34443445 """
34453446 if not (vt100_support and self .use_rawinput ):
34463447 return ''
@@ -3449,17 +3450,18 @@ def _clear_input_lines_str(self) -> str: # pragma: no cover
34493450 import colorama .ansi as ansi
34503451 from colorama import Cursor
34513452
3452- visible_prompt = self .visible_prompt
3453+ # Remove ansi characters to get the visible width of the prompt
3454+ prompt_width = wcswidth (utils .strip_ansi (current_prompt ))
34533455
34543456 # Get the size of the terminal
34553457 terminal_size = shutil .get_terminal_size ()
34563458
34573459 # Figure out how many lines the prompt and user input take up
3458- total_str_size = len ( visible_prompt ) + len (readline .get_line_buffer ())
3460+ total_str_size = prompt_width + wcswidth (readline .get_line_buffer ())
34593461 num_input_lines = int (total_str_size / terminal_size .columns ) + 1
34603462
34613463 # Get the cursor's offset from the beginning of the first input line
3462- cursor_input_offset = len ( visible_prompt ) + rl_get_point ()
3464+ cursor_input_offset = prompt_width + rl_get_point ()
34633465
34643466 # Calculate what input line the cursor is on
34653467 cursor_input_line = int (cursor_input_offset / terminal_size .columns ) + 1
@@ -3505,16 +3507,20 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
35053507 do_update = False
35063508
35073509 # Generate a string to clear the prompt and input lines and replace with the alert
3508- terminal_str = self ._clear_input_lines_str ()
3510+ current_prompt = self .continuation_prompt if self .at_continuation_prompt else self .prompt
3511+ terminal_str = self ._clear_input_lines_str (current_prompt )
3512+
35093513 if alert_msg :
35103514 terminal_str += alert_msg + '\n '
35113515 do_update = True
35123516
3513- # Set the new prompt now that _clear_input_lines_str is done using the old prompt
3514- if new_prompt is not None and not self .at_continuation_prompt :
3517+ # Set the prompt if its changed
3518+ if new_prompt is not None and new_prompt != self .prompt :
35153519 self .prompt = new_prompt
3516- rl_set_prompt (self .prompt )
3517- do_update = True
3520+
3521+ if not self .at_continuation_prompt :
3522+ rl_set_prompt (self .prompt )
3523+ do_update = True
35183524
35193525 if do_update :
35203526 # Print terminal_str to erase the lines
@@ -3543,8 +3549,9 @@ def async_update_prompt(self, new_prompt: str) -> None: # pragma: no cover
35433549 a prompt is onscreen. Therefore it is best to acquire the lock before calling this function
35443550 to guarantee the prompt changes.
35453551
3546- The prompt will not update if a continuation prompt is being displayed
3547- while entering a multiline command.
3552+ If a continuation prompt is currently being displayed while entering a multiline
3553+ command, the onscreen prompt will not change. However self.prompt will still be updated
3554+ and display immediately after the multiline line command completes.
35483555
35493556 :param new_prompt: what to change the prompt to
35503557 :raises RuntimeError if called while another thread holds terminal_lock
0 commit comments