summaryrefslogtreecommitdiff
path: root/cmd2/cmd2.py
diff options
context:
space:
mode:
authorkmvanbrunt <kmvanbrunt@gmail.com>2018-10-12 12:26:04 -0400
committerGitHub <noreply@github.com>2018-10-12 12:26:04 -0400
commitb216987165d7783903e02db006cf5055f2615796 (patch)
tree0851e964a9d715ebf5ad321f0212b83d6b8f7712 /cmd2/cmd2.py
parent11bc029a20168312eaa999e62b45989b6b1d11ef (diff)
parentfbf2a224fcf0e47102c4fcd3ce29c51228b7d931 (diff)
downloadcmd2-git-b216987165d7783903e02db006cf5055f2615796.tar.gz
Merge pull request #575 from python-cmd2/prompt_update
Do not asynchronously redraw continuation prompts
Diffstat (limited to 'cmd2/cmd2.py')
-rw-r--r--cmd2/cmd2.py146
1 files changed, 75 insertions, 71 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 02803b06..e09f428d 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -32,8 +32,6 @@ Git repository on GitHub at https://github.com/python-cmd2/cmd2
import argparse
import cmd
import collections
-import colorama
-from colorama import Fore
import glob
import inspect
import os
@@ -43,15 +41,20 @@ import sys
import threading
from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, Union, IO
+import colorama
+from colorama import Fore
+from wcwidth import wcswidth
+
from . import constants
-from . import utils
from . import plugin
+from . import utils
from .argparse_completer import AutoCompleter, ACArgumentParser, ACTION_ARG_CHOICES
from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer
from .parsing import StatementParser, Statement, Macro, MacroArg
# Set up readline
from .rl_utils import rl_type, RlType, rl_get_point, rl_set_prompt, vt100_support, rl_make_safe_prompt
+
if rl_type == RlType.NONE: # pragma: no cover
rl_warning = "Readline features including tab completion have been disabled since no \n" \
"supported version of readline was found. To resolve this, install \n" \
@@ -71,9 +74,6 @@ else:
elif rl_type == RlType.GNU:
- # We need wcswidth to calculate display width of tab completions
- from wcwidth import wcswidth
-
# Get the readline lib so we can make changes to it
import ctypes
from .rl_utils import readline_lib
@@ -457,6 +457,9 @@ class Cmd(cmd.Cmd):
# Used to keep track of whether we are redirecting or piping output
self.redirecting = False
+ # Used to keep track of whether a continuation prompt is being displayed
+ self.at_continuation_prompt = False
+
# If this string is non-empty, then this warning message will print if a broken pipe error occurs while printing
self.broken_pipe_warning = ''
@@ -1845,6 +1848,7 @@ class Cmd(cmd.Cmd):
# - a multiline command with unclosed quotation marks
if not self.quit_on_sigint:
try:
+ self.at_continuation_prompt = True
newline = self.pseudo_raw_input(self.continuation_prompt)
if newline == 'eof':
# they entered either a blank line, or we hit an EOF
@@ -1858,8 +1862,13 @@ class Cmd(cmd.Cmd):
self.poutput('^C')
statement = self.statement_parser.parse('')
break
+ finally:
+ self.at_continuation_prompt = False
else:
+ self.at_continuation_prompt = True
newline = self.pseudo_raw_input(self.continuation_prompt)
+ self.at_continuation_prompt = False
+
if newline == 'eof':
# they entered either a blank line, or we hit an EOF
# for some other reason. Turn the literal 'eof'
@@ -2074,11 +2083,6 @@ class Cmd(cmd.Cmd):
- if input is a pipe (instead of a tty), look at self.echo
to decide whether to print the prompt and the input
"""
-
- # Temporarily save over self.prompt to reflect what will be on screen
- orig_prompt = self.prompt
- self.prompt = prompt
-
if self.use_rawinput:
try:
if sys.stdin.isatty():
@@ -2122,9 +2126,6 @@ class Cmd(cmd.Cmd):
else:
line = 'eof'
- # Restore prompt
- self.prompt = orig_prompt
-
return line.strip()
def _cmdloop(self) -> bool:
@@ -3435,50 +3436,6 @@ a..b, a:b, a:, ..b items by indices (inclusive)
runner = unittest.TextTestRunner()
runner.run(testcase)
- def _clear_input_lines_str(self) -> str: # pragma: no cover
- """
- Returns a string that if printed will clear the prompt and input lines in the terminal,
- leaving the cursor at the beginning of the first input line
- :return: the string to print
- """
- if not (vt100_support and self.use_rawinput):
- return ''
-
- import shutil
- import colorama.ansi as ansi
- from colorama import Cursor
-
- visible_prompt = self.visible_prompt
-
- # Get the size of the terminal
- terminal_size = shutil.get_terminal_size()
-
- # Figure out how many lines the prompt and user input take up
- total_str_size = len(visible_prompt) + len(readline.get_line_buffer())
- num_input_lines = int(total_str_size / terminal_size.columns) + 1
-
- # Get the cursor's offset from the beginning of the first input line
- cursor_input_offset = len(visible_prompt) + rl_get_point()
-
- # Calculate what input line the cursor is on
- cursor_input_line = int(cursor_input_offset / terminal_size.columns) + 1
-
- # Create a string that will clear all input lines and print the alert
- terminal_str = ''
-
- # Move the cursor down to the last input line
- if cursor_input_line != num_input_lines:
- terminal_str += Cursor.DOWN(num_input_lines - cursor_input_line)
-
- # Clear each input line from the bottom up so that the cursor ends up on the original first input line
- terminal_str += (ansi.clear_line() + Cursor.UP(1)) * (num_input_lines - 1)
- terminal_str += ansi.clear_line()
-
- # Move the cursor to the beginning of the first input line
- terminal_str += '\r'
-
- return terminal_str
-
def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None: # pragma: no cover
"""
Display an important message to the user while they are at the prompt in between commands.
@@ -3497,27 +3454,70 @@ a..b, a:b, a:, ..b items by indices (inclusive)
if not (vt100_support and self.use_rawinput):
return
+ import shutil
+ import colorama.ansi as ansi
+ from colorama import Cursor
+
# Sanity check that can't fail if self.terminal_lock was acquired before calling this function
if self.terminal_lock.acquire(blocking=False):
- # Generate a string to clear the prompt and input lines and replace with the alert
- terminal_str = self._clear_input_lines_str()
+ # Figure out what prompt is displaying
+ current_prompt = self.continuation_prompt if self.at_continuation_prompt else self.prompt
+
+ # Only update terminal if there are changes
+ update_terminal = False
+
if alert_msg:
- terminal_str += alert_msg + '\n'
+ alert_msg += '\n'
+ update_terminal = True
- # Set the new prompt now that _clear_input_lines_str is done using the old prompt
- if new_prompt is not None:
+ # Set the prompt if its changed
+ if new_prompt is not None and new_prompt != self.prompt:
self.prompt = new_prompt
- rl_set_prompt(self.prompt)
- # Print terminal_str to erase the lines
- if rl_type == RlType.GNU:
- sys.stderr.write(terminal_str)
- elif rl_type == RlType.PYREADLINE:
- readline.rl.mode.console.write(terminal_str)
+ # If we aren't at a continuation prompt, then redraw the prompt now
+ if not self.at_continuation_prompt:
+ rl_set_prompt(self.prompt)
+ update_terminal = True
- # Redraw the prompt and input lines
- rl_force_redisplay()
+ if update_terminal:
+ # Remove ansi characters to get the visible width of the prompt
+ prompt_width = wcswidth(utils.strip_ansi(current_prompt))
+
+ # Get the size of the terminal
+ terminal_size = shutil.get_terminal_size()
+
+ # Figure out how many lines the prompt and user input take up
+ total_str_size = prompt_width + wcswidth(readline.get_line_buffer())
+ num_input_lines = int(total_str_size / terminal_size.columns) + 1
+
+ # Get the cursor's offset from the beginning of the first input line
+ cursor_input_offset = prompt_width + rl_get_point()
+
+ # Calculate what input line the cursor is on
+ cursor_input_line = int(cursor_input_offset / terminal_size.columns) + 1
+
+ # Create a string that when printed will clear all input lines and display the alert
+ terminal_str = ''
+
+ # Move the cursor down to the last input line
+ if cursor_input_line != num_input_lines:
+ terminal_str += Cursor.DOWN(num_input_lines - cursor_input_line)
+
+ # Clear each input line from the bottom up so that the cursor ends up on the original first input line
+ terminal_str += (ansi.clear_line() + Cursor.UP(1)) * (num_input_lines - 1)
+ terminal_str += ansi.clear_line()
+
+ # Move the cursor to the beginning of the first input line and print the alert
+ terminal_str += '\r' + alert_msg
+
+ if rl_type == RlType.GNU:
+ sys.stderr.write(terminal_str)
+ elif rl_type == RlType.PYREADLINE:
+ readline.rl.mode.console.write(terminal_str)
+
+ # Redraw the prompt and input lines
+ rl_force_redisplay()
self.terminal_lock.release()
@@ -3536,6 +3536,10 @@ a..b, a:b, a:, ..b items by indices (inclusive)
a prompt is onscreen. Therefore it is best to acquire the lock before calling this function
to guarantee the prompt changes.
+ If a continuation prompt is currently being displayed while entering a multiline
+ command, the onscreen prompt will not change. However self.prompt will still be updated
+ and display immediately after the multiline line command completes.
+
:param new_prompt: what to change the prompt to
:raises RuntimeError if called while another thread holds terminal_lock
"""