diff options
author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2018-03-30 17:58:51 -0400 |
---|---|---|
committer | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2018-03-30 17:58:51 -0400 |
commit | 2f4198ef94262dc0c747c34541a72128164dc340 (patch) | |
tree | 89a6640793b0c7a6184255318dccd5e739a458b2 | |
parent | 4c2f4074616bc7b2a31cdaa940ac24a0ef426448 (diff) | |
download | cmd2-git-2f4198ef94262dc0c747c34541a72128164dc340.tar.gz |
Fixed bug where display width was not being calculated for display_matches
-rwxr-xr-x | cmd2.py | 152 | ||||
-rwxr-xr-x | setup.py | 2 | ||||
-rw-r--r-- | tox.ini | 5 |
3 files changed, 76 insertions, 83 deletions
@@ -153,6 +153,9 @@ if 'pyreadline' in sys.modules: elif 'gnureadline' in sys.modules or 'readline' in sys.modules: rl_type = RlType.GNU + # We need wcswidth to calculate display width of tab completions + from wcwidth import wcswidth + # Load the readline lib so we can make changes to it import ctypes readline_lib = ctypes.CDLL(readline.__file__) @@ -1268,55 +1271,6 @@ class Cmd(cmd.Cmd): self.allow_closing_quote = True self.display_matches = [] - @staticmethod - def display_match_list_gnu_readline(substitution, matches, longest_match_length): - """ - Prints a match list using GNU readline's rl_display_match_list() - :param substitution: str - the substitution written to the command line - :param matches: list[str] - the tab completion matches to display - :param longest_match_length: int - longest printed length of the matches - """ - if rl_type == RlType.GNU: - # We will use readline's display function (rl_display_match_list()), so we - # need to encode our string as bytes to place in a C array. - if six.PY3: - encoded_substitution = bytes(substitution, encoding='utf-8') - encoded_matches = [bytes(cur_match, encoding='utf-8') for cur_match in matches] - else: - encoded_substitution = bytes(substitution) - encoded_matches = [bytes(cur_match) for cur_match in matches] - - # rl_display_match_list() expects matches to be in argv format where - # substitution is the first element, followed by the matches, and then a NULL. - # noinspection PyCallingNonCallable,PyTypeChecker - strings_array = (ctypes.c_char_p * (1 + len(encoded_matches) + 1))() - - # Copy in the encoded strings and add a NULL to the end - strings_array[0] = encoded_substitution - strings_array[1:-1] = encoded_matches - strings_array[-1] = None - - # Call readline's display function - # rl_display_match_list(strings_array, number of completion matches, longest match length) - readline_lib.rl_display_match_list(strings_array, len(encoded_matches), longest_match_length) - - # rl_forced_update_display() is the proper way to redraw the prompt and line, but we - # have to use ctypes to do it since Python's readline API does not wrap the function - readline_lib.rl_forced_update_display() - - # Since we updated the display, readline asks that rl_display_fixed be set for efficiency - display_fixed = ctypes.c_int.in_dll(readline_lib, "rl_display_fixed") - display_fixed.value = 1 - - @staticmethod - def display_match_list_pyreadline(matches): - """ - Prints a match list using pyreadline's _display_completions() - :param matches: list[str] - the tab completion matches to display - """ - if rl_type == RlType.PYREADLINE: - orig_pyreadline_display(matches) - def tokens_for_completion(self, line, begidx, endidx): """ Used by tab completion functions to get all tokens through the one being completed @@ -1817,55 +1771,87 @@ class Cmd(cmd.Cmd): def _display_matches_gnu_readline(self, substitution, matches, longest_match_length): """ - cmd2's default GNU readline function that prints tab-completion matches to the screen - This exists to allow the printing of self.display_matches if it has data. Otherwise matches prints. - The actual printing is done by display_match_list_gnu_readline(). - - If you need a custom match display function for a particular completion type, then set it by calling - readline.set_completion_display_matches_hook() during the completer routine. - Your custom display function should ultimately call display_match_list_gnu_readline() to print. + Prints a match list using GNU readline's rl_display_match_list() + This exists to print self.display_matches if it has data. Otherwise matches prints. :param substitution: str - the substitution written to the command line :param matches: list[str] - the tab completion matches to display :param longest_match_length: int - longest printed length of the matches """ - if len(self.display_matches) > 0: - matches_to_display = self.display_matches - else: - matches_to_display = matches + if rl_type == RlType.GNU: + + # Check if we should show display_matches + if len(self.display_matches) > 0: + matches_to_display = self.display_matches + + # Recalculate longest_match_length for display_matches + longest_match_length = 0 + + for cur_match in matches_to_display: + cur_length = wcswidth(cur_match) + if cur_length > longest_match_length: + longest_match_length = cur_length + else: + matches_to_display = matches + + # Eliminate duplicates and sort + matches_to_display_set = set(matches_to_display) + matches_to_display = list(matches_to_display_set) + matches_to_display.sort() + + # We will use readline's display function (rl_display_match_list()), so we + # need to encode our string as bytes to place in a C array. + if six.PY3: + encoded_substitution = bytes(substitution, encoding='utf-8') + encoded_matches = [bytes(cur_match, encoding='utf-8') for cur_match in matches_to_display] + else: + encoded_substitution = bytes(substitution) + encoded_matches = [bytes(cur_match) for cur_match in matches_to_display] + + # rl_display_match_list() expects matches to be in argv format where + # substitution is the first element, followed by the matches, and then a NULL. + # noinspection PyCallingNonCallable,PyTypeChecker + strings_array = (ctypes.c_char_p * (1 + len(encoded_matches) + 1))() - # Eliminate duplicates and sort - matches_to_display_set = set(matches_to_display) - matches_to_display = list(matches_to_display_set) - matches_to_display.sort() + # Copy in the encoded strings and add a NULL to the end + strings_array[0] = encoded_substitution + strings_array[1:-1] = encoded_matches + strings_array[-1] = None + + # Call readline's display function + # rl_display_match_list(strings_array, number of completion matches, longest match length) + readline_lib.rl_display_match_list(strings_array, len(encoded_matches), longest_match_length) + + # rl_forced_update_display() is the proper way to redraw the prompt and line, but we + # have to use ctypes to do it since Python's readline API does not wrap the function + readline_lib.rl_forced_update_display() - # Display the matches - self.display_match_list_gnu_readline(substitution, matches_to_display, longest_match_length) + # Since we updated the display, readline asks that rl_display_fixed be set for efficiency + display_fixed = ctypes.c_int.in_dll(readline_lib, "rl_display_fixed") + display_fixed.value = 1 def _display_matches_pyreadline(self, matches): """ - cmd2's default pyreadline function that prints tab-completion matches to the screen - This exists to allow the printing of self.display_matches if it has data. Otherwise matches prints. - The actual printing is done by display_match_list_pyreadline(). - - If you need a custom match display function for a particular completion type, then set - readline.rl.mode._display_completions to that function during the completer routine. - Your custom display function should ultimately call display_match_list_pyreadline() to print. + Prints a match list using pyreadline's _display_completions() + This exists to print self.display_matches if it has data. Otherwise matches prints. :param matches: list[str] - the tab completion matches to display """ - if len(self.display_matches) > 0: - matches_to_display = self.display_matches - else: - matches_to_display = matches + if rl_type == RlType.PYREADLINE: - # Eliminate duplicates and sort - matches_to_display_set = set(matches_to_display) - matches_to_display = list(matches_to_display_set) - matches_to_display.sort() + # Check if we should show display_matches + if len(self.display_matches) > 0: + matches_to_display = self.display_matches + else: + matches_to_display = matches + + # Eliminate duplicates and sort + matches_to_display_set = set(matches_to_display) + matches_to_display = list(matches_to_display_set) + matches_to_display.sort() - # Display the matches - self.display_match_list_pyreadline(matches_to_display) + # Display the matches + orig_pyreadline_display(matches_to_display) def _handle_completion_token_quote(self, raw_completion_token): """ @@ -81,6 +81,8 @@ if int(setuptools.__version__.split('.')[0]) < 18: EXTRAS_REQUIRE = {} if sys.platform.startswith('win'): INSTALL_REQUIRES.append('pyreadline') + else: + INSTALL_REQUIRES.append('wcwidth') if sys.version_info < (3, 5): INSTALL_REQUIRES.append('contextlib2') if sys.version_info < (3, 4): @@ -22,6 +22,7 @@ deps = pytest-xdist six subprocess32 + wcwidth commands = py.test {posargs: -n 2} --cov=cmd2 --cov-report=term-missing --forked codecov @@ -52,6 +53,7 @@ deps = pytest-forked pytest-xdist six + wcwidth commands = py.test -v -n2 --forked [testenv:py35] @@ -63,6 +65,7 @@ deps = pytest-forked pytest-xdist six + wcwidth commands = py.test -v -n2 --forked [testenv:py35-win] @@ -87,6 +90,7 @@ deps = pytest-forked pytest-xdist six + wcwidth commands = py.test {posargs: -n 2} --cov=cmd2 --cov-report=term-missing --forked codecov @@ -111,5 +115,6 @@ deps = pytest-forked pytest-xdist six + wcwidth commands = py.test -v -n2 --forked |