Repository: python-cmd2/cmd2
Pull request #597 (2018-11-15 20:13:24)

Added wrapper for wcswidth that removes ansi escape characters


Patch:
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 6f9350c6..9254612d 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -43,7 +43,6 @@ from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, Un
 
 import colorama
 from colorama import Fore
-from wcwidth import wcswidth
 
 from . import constants
 from . import plugin
@@ -1302,7 +1301,7 @@ class Cmd(cmd.Cmd):
                 longest_match_length = 0
 
                 for cur_match in matches_to_display:
-                    cur_length = wcswidth(cur_match)
+                    cur_length = utils.display_width(cur_match)
                     if cur_length > longest_match_length:
                         longest_match_length = cur_length
             else:
@@ -2662,7 +2661,7 @@ class Cmd(cmd.Cmd):
                 widest = 0
                 # measure the commands
                 for command in cmds:
-                    width = len(command)
+                    width = utils.display_width(command)
                     if width > widest:
                         widest = width
                 # add a 4-space pad
@@ -3478,14 +3477,14 @@ a..b, a:b, a:, ..b  items by indices (inclusive)
                     update_terminal = True
 
             if update_terminal:
-                # Remove ansi characters to get the visible width of the prompt
-                prompt_width = wcswidth(utils.strip_ansi(current_prompt))
+                # Get the display width of the prompt
+                prompt_width = utils.display_width(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())
+                total_str_size = prompt_width + utils.display_width(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
diff --git a/cmd2/utils.py b/cmd2/utils.py
index d4a3db2f..909332ae 100644
--- a/cmd2/utils.py
+++ b/cmd2/utils.py
@@ -8,6 +8,8 @@ import re
 import unicodedata
 from typing import Any, Iterable, List, Optional, Union
 
+from wcwidth import wcswidth
+
 from . import constants
 
 
@@ -20,6 +22,15 @@ def strip_ansi(text: str) -> str:
     return constants.ANSI_ESCAPE_RE.sub('', text)
 
 
+def display_width(text: str) -> int:
+    """
+    Return the printable length of a string. This can be different than character count in unicode strings.
+    :param text: the string being measured
+    """
+    # Strip ANSI escape codes since they cause wcswidth to return -1
+    return wcswidth(strip_ansi(text))
+
+
 def is_quoted(arg: str) -> bool:
     """
     Checks if a string is quoted


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 807bc0fd..2f4723e4 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -21,6 +21,11 @@ def test_strip_ansi():
     assert base_str != ansi_str
     assert base_str == cu.strip_ansi(ansi_str)
 
+def test_display_width():
+    base_str = HELLO_WORLD
+    ansi_str = Fore.GREEN + base_str + Fore.RESET
+    assert cu.display_width(ansi_str) != len(ansi_str)
+
 def test_strip_quotes_no_quotes():
     base_str = HELLO_WORLD
     stripped = cu.strip_quotes(base_str)