@@ -153,6 +153,9 @@ class RlType(Enum):
153153elif 'gnureadline' in sys .modules or 'readline' in sys .modules :
154154 rl_type = RlType .GNU
155155
156+ # We need wcswidth to calculate display width of tab completions
157+ from wcwidth import wcswidth
158+
156159 # Load the readline lib so we can make changes to it
157160 import ctypes
158161 readline_lib = ctypes .CDLL (readline .__file__ )
@@ -1268,55 +1271,6 @@ def set_completion_defaults(self):
12681271 self .allow_closing_quote = True
12691272 self .display_matches = []
12701273
1271- @staticmethod
1272- def display_match_list_gnu_readline (substitution , matches , longest_match_length ):
1273- """
1274- Prints a match list using GNU readline's rl_display_match_list()
1275- :param substitution: str - the substitution written to the command line
1276- :param matches: list[str] - the tab completion matches to display
1277- :param longest_match_length: int - longest printed length of the matches
1278- """
1279- if rl_type == RlType .GNU :
1280- # We will use readline's display function (rl_display_match_list()), so we
1281- # need to encode our string as bytes to place in a C array.
1282- if six .PY3 :
1283- encoded_substitution = bytes (substitution , encoding = 'utf-8' )
1284- encoded_matches = [bytes (cur_match , encoding = 'utf-8' ) for cur_match in matches ]
1285- else :
1286- encoded_substitution = bytes (substitution )
1287- encoded_matches = [bytes (cur_match ) for cur_match in matches ]
1288-
1289- # rl_display_match_list() expects matches to be in argv format where
1290- # substitution is the first element, followed by the matches, and then a NULL.
1291- # noinspection PyCallingNonCallable,PyTypeChecker
1292- strings_array = (ctypes .c_char_p * (1 + len (encoded_matches ) + 1 ))()
1293-
1294- # Copy in the encoded strings and add a NULL to the end
1295- strings_array [0 ] = encoded_substitution
1296- strings_array [1 :- 1 ] = encoded_matches
1297- strings_array [- 1 ] = None
1298-
1299- # Call readline's display function
1300- # rl_display_match_list(strings_array, number of completion matches, longest match length)
1301- readline_lib .rl_display_match_list (strings_array , len (encoded_matches ), longest_match_length )
1302-
1303- # rl_forced_update_display() is the proper way to redraw the prompt and line, but we
1304- # have to use ctypes to do it since Python's readline API does not wrap the function
1305- readline_lib .rl_forced_update_display ()
1306-
1307- # Since we updated the display, readline asks that rl_display_fixed be set for efficiency
1308- display_fixed = ctypes .c_int .in_dll (readline_lib , "rl_display_fixed" )
1309- display_fixed .value = 1
1310-
1311- @staticmethod
1312- def display_match_list_pyreadline (matches ):
1313- """
1314- Prints a match list using pyreadline's _display_completions()
1315- :param matches: list[str] - the tab completion matches to display
1316- """
1317- if rl_type == RlType .PYREADLINE :
1318- orig_pyreadline_display (matches )
1319-
13201274 def tokens_for_completion (self , line , begidx , endidx ):
13211275 """
13221276 Used by tab completion functions to get all tokens through the one being completed
@@ -1817,55 +1771,87 @@ def _redirect_complete(self, text, line, begidx, endidx, compfunc):
18171771
18181772 def _display_matches_gnu_readline (self , substitution , matches , longest_match_length ):
18191773 """
1820- cmd2's default GNU readline function that prints tab-completion matches to the screen
1821- This exists to allow the printing of self.display_matches if it has data. Otherwise matches prints.
1822- The actual printing is done by display_match_list_gnu_readline().
1823-
1824- If you need a custom match display function for a particular completion type, then set it by calling
1825- readline.set_completion_display_matches_hook() during the completer routine.
1826- Your custom display function should ultimately call display_match_list_gnu_readline() to print.
1774+ Prints a match list using GNU readline's rl_display_match_list()
1775+ This exists to print self.display_matches if it has data. Otherwise matches prints.
18271776
18281777 :param substitution: str - the substitution written to the command line
18291778 :param matches: list[str] - the tab completion matches to display
18301779 :param longest_match_length: int - longest printed length of the matches
18311780 """
1832- if len (self .display_matches ) > 0 :
1833- matches_to_display = self .display_matches
1834- else :
1835- matches_to_display = matches
1781+ if rl_type == RlType .GNU :
1782+
1783+ # Check if we should show display_matches
1784+ if len (self .display_matches ) > 0 :
1785+ matches_to_display = self .display_matches
1786+
1787+ # Recalculate longest_match_length for display_matches
1788+ longest_match_length = 0
1789+
1790+ for cur_match in matches_to_display :
1791+ cur_length = wcswidth (cur_match )
1792+ if cur_length > longest_match_length :
1793+ longest_match_length = cur_length
1794+ else :
1795+ matches_to_display = matches
1796+
1797+ # Eliminate duplicates and sort
1798+ matches_to_display_set = set (matches_to_display )
1799+ matches_to_display = list (matches_to_display_set )
1800+ matches_to_display .sort ()
1801+
1802+ # We will use readline's display function (rl_display_match_list()), so we
1803+ # need to encode our string as bytes to place in a C array.
1804+ if six .PY3 :
1805+ encoded_substitution = bytes (substitution , encoding = 'utf-8' )
1806+ encoded_matches = [bytes (cur_match , encoding = 'utf-8' ) for cur_match in matches_to_display ]
1807+ else :
1808+ encoded_substitution = bytes (substitution )
1809+ encoded_matches = [bytes (cur_match ) for cur_match in matches_to_display ]
1810+
1811+ # rl_display_match_list() expects matches to be in argv format where
1812+ # substitution is the first element, followed by the matches, and then a NULL.
1813+ # noinspection PyCallingNonCallable,PyTypeChecker
1814+ strings_array = (ctypes .c_char_p * (1 + len (encoded_matches ) + 1 ))()
18361815
1837- # Eliminate duplicates and sort
1838- matches_to_display_set = set (matches_to_display )
1839- matches_to_display = list (matches_to_display_set )
1840- matches_to_display .sort ()
1816+ # Copy in the encoded strings and add a NULL to the end
1817+ strings_array [0 ] = encoded_substitution
1818+ strings_array [1 :- 1 ] = encoded_matches
1819+ strings_array [- 1 ] = None
1820+
1821+ # Call readline's display function
1822+ # rl_display_match_list(strings_array, number of completion matches, longest match length)
1823+ readline_lib .rl_display_match_list (strings_array , len (encoded_matches ), longest_match_length )
1824+
1825+ # rl_forced_update_display() is the proper way to redraw the prompt and line, but we
1826+ # have to use ctypes to do it since Python's readline API does not wrap the function
1827+ readline_lib .rl_forced_update_display ()
18411828
1842- # Display the matches
1843- self .display_match_list_gnu_readline (substitution , matches_to_display , longest_match_length )
1829+ # Since we updated the display, readline asks that rl_display_fixed be set for efficiency
1830+ display_fixed = ctypes .c_int .in_dll (readline_lib , "rl_display_fixed" )
1831+ display_fixed .value = 1
18441832
18451833 def _display_matches_pyreadline (self , matches ):
18461834 """
1847- cmd2's default pyreadline function that prints tab-completion matches to the screen
1848- This exists to allow the printing of self.display_matches if it has data. Otherwise matches prints.
1849- The actual printing is done by display_match_list_pyreadline().
1850-
1851- If you need a custom match display function for a particular completion type, then set
1852- readline.rl.mode._display_completions to that function during the completer routine.
1853- Your custom display function should ultimately call display_match_list_pyreadline() to print.
1835+ Prints a match list using pyreadline's _display_completions()
1836+ This exists to print self.display_matches if it has data. Otherwise matches prints.
18541837
18551838 :param matches: list[str] - the tab completion matches to display
18561839 """
1857- if len (self .display_matches ) > 0 :
1858- matches_to_display = self .display_matches
1859- else :
1860- matches_to_display = matches
1840+ if rl_type == RlType .PYREADLINE :
18611841
1862- # Eliminate duplicates and sort
1863- matches_to_display_set = set (matches_to_display )
1864- matches_to_display = list (matches_to_display_set )
1865- matches_to_display .sort ()
1842+ # Check if we should show display_matches
1843+ if len (self .display_matches ) > 0 :
1844+ matches_to_display = self .display_matches
1845+ else :
1846+ matches_to_display = matches
1847+
1848+ # Eliminate duplicates and sort
1849+ matches_to_display_set = set (matches_to_display )
1850+ matches_to_display = list (matches_to_display_set )
1851+ matches_to_display .sort ()
18661852
1867- # Display the matches
1868- self . display_match_list_pyreadline (matches_to_display )
1853+ # Display the matches
1854+ orig_pyreadline_display (matches_to_display )
18691855
18701856 def _handle_completion_token_quote (self , raw_completion_token ):
18711857 """
0 commit comments