Skip to content

Commit ad76cec

Browse files
authored
Merge pull request #333 from python-cmd2/remove_extra_sorting
Removed unnecessary sorting and duplicate removal from the completers…
2 parents ad463db + a6ed2bc commit ad76cec

File tree

2 files changed

+58
-69
lines changed

2 files changed

+58
-69
lines changed

cmd2.py

Lines changed: 40 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,17 +1401,9 @@ def basic_complete(text, line, begidx, endidx, match_against):
14011401
:param begidx: int - the beginning index of the prefix text
14021402
:param endidx: int - the ending index of the prefix text
14031403
:param match_against: Collection - the list being matched against
1404-
:return: List[str] - a sorted list of possible tab completions
1404+
:return: List[str] - a list of possible tab completions
14051405
"""
1406-
# Make sure we were given a Collection with items to match against
1407-
if not isinstance(match_against, Collection) or len(match_against) == 0:
1408-
return []
1409-
1410-
# Perform matching and eliminate duplicates
1411-
matches = [cur_match for cur_match in set(match_against) if cur_match.startswith(text)]
1412-
1413-
matches.sort()
1414-
return matches
1406+
return [cur_match for cur_match in match_against if cur_match.startswith(text)]
14151407

14161408
def delimiter_complete(self, text, line, begidx, endidx, match_against, delimiter):
14171409
"""
@@ -1444,7 +1436,7 @@ def delimiter_complete(self, text, line, begidx, endidx, match_against, delimite
14441436
:param endidx: int - the ending index of the prefix text
14451437
:param match_against: Collection - the list being matched against
14461438
:param delimiter: str - what delimits each portion of the matches (ex: paths are delimited by a slash)
1447-
:return: List[str] - a sorted list of possible tab completions
1439+
:return: List[str] - a list of possible tab completions
14481440
"""
14491441
matches = self.basic_complete(text, line, begidx, endidx, match_against)
14501442

@@ -1486,7 +1478,7 @@ def flag_based_complete(self, text, line, begidx, endidx, flag_dict, all_else=No
14861478
2. function that performs tab completion (ex: path_complete)
14871479
:param all_else: Collection or function - an optional parameter for tab completing any token that isn't preceded
14881480
by a flag in flag_dict
1489-
:return: List[str] - a sorted list of possible tab completions
1481+
:return: List[str] - a list of possible tab completions
14901482
"""
14911483
# Get all tokens through the one being completed
14921484
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
@@ -1502,14 +1494,13 @@ def flag_based_complete(self, text, line, begidx, endidx, flag_dict, all_else=No
15021494
if flag in flag_dict:
15031495
match_against = flag_dict[flag]
15041496

1505-
# Perform tab completion using a Collection. These matches are already sorted.
1497+
# Perform tab completion using a Collection
15061498
if isinstance(match_against, Collection):
15071499
completions_matches = self.basic_complete(text, line, begidx, endidx, match_against)
15081500

15091501
# Perform tab completion using a function
15101502
elif callable(match_against):
15111503
completions_matches = match_against(text, line, begidx, endidx)
1512-
completions_matches.sort()
15131504

15141505
return completions_matches
15151506

@@ -1528,7 +1519,7 @@ def index_based_complete(self, text, line, begidx, endidx, index_dict, all_else=
15281519
2. function that performs tab completion (ex: path_complete)
15291520
:param all_else: Collection or function - an optional parameter for tab completing any token that isn't at an
15301521
index in index_dict
1531-
:return: List[str] - a sorted list of possible tab completions
1522+
:return: List[str] - a list of possible tab completions
15321523
"""
15331524
# Get all tokens through the one being completed
15341525
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
@@ -1546,14 +1537,13 @@ def index_based_complete(self, text, line, begidx, endidx, index_dict, all_else=
15461537
else:
15471538
match_against = all_else
15481539

1549-
# Perform tab completion using a Collection. These matches are already sorted.
1540+
# Perform tab completion using a Collection
15501541
if isinstance(match_against, Collection):
15511542
matches = self.basic_complete(text, line, begidx, endidx, match_against)
15521543

15531544
# Perform tab completion using a function
15541545
elif callable(match_against):
15551546
matches = match_against(text, line, begidx, endidx)
1556-
matches.sort()
15571547

15581548
return matches
15591549

@@ -1567,7 +1557,7 @@ def path_complete(self, text, line, begidx, endidx, dir_exe_only=False, dir_only
15671557
:param endidx: int - the ending index of the prefix text
15681558
:param dir_exe_only: bool - only return directories and executables, not non-executable files
15691559
:param dir_only: bool - only return directories
1570-
:return: List[str] - a sorted list of possible tab completions
1560+
:return: List[str] - a list of possible tab completions
15711561
"""
15721562
# Determine if a trailing separator should be appended to directory completions
15731563
add_trailing_sep_if_dir = False
@@ -1655,15 +1645,14 @@ def path_complete(self, text, line, begidx, endidx, dir_exe_only=False, dir_only
16551645
if tilde_expanded:
16561646
matches = [cur_path.replace(user_path, '~', 1) for cur_path in matches]
16571647

1658-
matches.sort()
16591648
return matches
16601649

16611650
@staticmethod
16621651
def get_exes_in_path(starts_with):
16631652
"""
16641653
Returns names of executables in a user's path
16651654
:param starts_with: str - what the exes should start with. leave blank for all exes in path.
1666-
:return: List[str] - a sorted list of matching exe names
1655+
:return: List[str] - a list of matching exe names
16671656
"""
16681657
# Purposely don't match any executable containing wildcards
16691658
wildcards = ['*', '?']
@@ -1685,9 +1674,7 @@ def get_exes_in_path(starts_with):
16851674
for match in matches:
16861675
exes_set.add(os.path.basename(match))
16871676

1688-
exes_list = list(exes_set)
1689-
exes_list.sort()
1690-
return exes_list
1677+
return list(exes_set)
16911678

16921679
def shell_cmd_complete(self, text, line, begidx, endidx, complete_blank=False):
16931680
"""Performs completion of executables either in a user's path or a given path
@@ -1698,7 +1685,7 @@ def shell_cmd_complete(self, text, line, begidx, endidx, complete_blank=False):
16981685
:param complete_blank: bool - If True, then a blank will complete all shell commands in a user's path
16991686
If False, then no completion is performed
17001687
Defaults to False to match Bash shell behavior
1701-
:return: List[str] - a sorted list of possible tab completions
1688+
:return: List[str] - a list of possible tab completions
17021689
"""
17031690
# Don't tab complete anything if no shell command has been started
17041691
if not complete_blank and len(text) == 0:
@@ -1724,7 +1711,7 @@ def _redirect_complete(self, text, line, begidx, endidx, compfunc):
17241711
:param endidx: int - the ending index of the prefix text
17251712
:param compfunc: Callable - the completer function for the current command
17261713
this will be called if we aren't completing for redirection
1727-
:return: List[str] - a sorted list of possible tab completions
1714+
:return: List[str] - a list of possible tab completions
17281715
"""
17291716
if self.allow_redirection:
17301717

@@ -1794,11 +1781,6 @@ def _display_matches_gnu_readline(self, substitution, matches, longest_match_len
17941781
else:
17951782
matches_to_display = matches
17961783

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-
18021784
# We will use readline's display function (rl_display_match_list()), so we
18031785
# need to encode our string as bytes to place in a C array.
18041786
if six.PY3:
@@ -1845,11 +1827,6 @@ def _display_matches_pyreadline(self, matches):
18451827
else:
18461828
matches_to_display = matches
18471829

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()
1852-
18531830
# Display the matches
18541831
orig_pyreadline_display(matches_to_display)
18551832

@@ -2088,6 +2065,13 @@ def complete(self, text, state):
20882065

20892066
if len(self.completion_matches) > 0:
20902067

2068+
# Eliminate duplicates
2069+
matches_set = set(self.completion_matches)
2070+
self.completion_matches = list(matches_set)
2071+
2072+
display_matches_set = set(self.display_matches)
2073+
self.display_matches = list(display_matches_set)
2074+
20912075
# Get the token being completed as it appears on the command line
20922076
raw_completion_token = raw_tokens[-1]
20932077

@@ -2121,16 +2105,11 @@ def complete(self, text, state):
21212105

21222106
else:
21232107
# Complete token against aliases and command names
2124-
alias_names = set(self.aliases.keys())
2125-
visible_commands = set(self.get_visible_commands())
2126-
strs_to_match = list(alias_names | visible_commands)
2108+
alias_names = list(self.aliases.keys())
2109+
visible_commands = self.get_visible_commands()
2110+
strs_to_match = alias_names + visible_commands
21272111
self.completion_matches = self.basic_complete(text, line, begidx, endidx, strs_to_match)
21282112

2129-
# Eliminate duplicates and sort
2130-
matches_set = set(self.completion_matches)
2131-
self.completion_matches = list(matches_set)
2132-
self.completion_matches.sort()
2133-
21342113
# Handle single result
21352114
if len(self.completion_matches) == 1:
21362115
str_to_append = ''
@@ -2145,26 +2124,26 @@ def complete(self, text, state):
21452124

21462125
self.completion_matches[0] += str_to_append
21472126

2127+
# Otherwise sort matches
2128+
elif len(self.completion_matches) > 0:
2129+
self.completion_matches.sort()
2130+
self.display_matches.sort()
2131+
21482132
try:
21492133
return self.completion_matches[state]
21502134
except IndexError:
21512135
return None
21522136

21532137
def get_all_commands(self):
21542138
"""
2155-
Returns a sorted list of all commands
2156-
Any duplicates have been removed as well
2139+
Returns a list of all commands
21572140
"""
2158-
commands = [cur_name[3:] for cur_name in set(self.get_names()) if cur_name.startswith('do_')]
2159-
commands.sort()
2160-
return commands
2141+
return [cur_name[3:] for cur_name in self.get_names() if cur_name.startswith('do_')]
21612142

21622143
def get_visible_commands(self):
21632144
"""
2164-
Returns a sorted list of commands that have not been hidden
2165-
Any duplicates have been removed as well
2145+
Returns a list of commands that have not been hidden
21662146
"""
2167-
# This list is already sorted and has no duplicates
21682147
commands = self.get_all_commands()
21692148

21702149
# Remove the hidden commands
@@ -2175,13 +2154,13 @@ def get_visible_commands(self):
21752154
return commands
21762155

21772156
def get_help_topics(self):
2178-
""" Returns a sorted list of help topics with all duplicates removed """
2179-
return [name[5:] for name in set(self.get_names()) if name.startswith('help_')]
2157+
""" Returns a list of help topics """
2158+
return [name[5:] for name in self.get_names() if name.startswith('help_')]
21802159

21812160
def complete_help(self, text, line, begidx, endidx):
21822161
"""
21832162
Override of parent class method to handle tab completing subcommands and not showing hidden commands
2184-
Returns a sorted list of possible tab completions
2163+
Returns a list of possible tab completions
21852164
"""
21862165

21872166
# The command is the token at index 1 in the command line
@@ -2871,12 +2850,14 @@ def _help_menu(self):
28712850
"""
28722851
# Get a sorted list of help topics
28732852
help_topics = self.get_help_topics()
2874-
2875-
cmds_doc = []
2876-
cmds_undoc = []
2853+
help_topics.sort()
28772854

28782855
# Get a sorted list of visible command names
28792856
visible_commands = self.get_visible_commands()
2857+
visible_commands.sort()
2858+
2859+
cmds_doc = []
2860+
cmds_undoc = []
28802861

28812862
for command in visible_commands:
28822863
if command in help_topics:
@@ -3067,7 +3048,7 @@ def complete_shell(self, text, line, begidx, endidx):
30673048
:param line: str - the current input line with leading whitespace removed
30683049
:param begidx: int - the beginning index of the prefix text
30693050
:param endidx: int - the ending index of the prefix text
3070-
:return: List[str] - a sorted list of possible tab completions
3051+
:return: List[str] - a list of possible tab completions
30713052
"""
30723053
index_dict = {1: self.shell_cmd_complete}
30733054
return self.index_based_complete(text, line, begidx, endidx, index_dict, self.path_complete)
@@ -3102,7 +3083,7 @@ def cmd_with_subs_completer(self, text, line, begidx, endidx):
31023083
:param line: str - the current input line with leading whitespace removed
31033084
:param begidx: int - the beginning index of the prefix text
31043085
:param endidx: int - the ending index of the prefix text
3105-
:return: List[str] - a sorted list of possible tab completions
3086+
:return: List[str] - a list of possible tab completions
31063087
"""
31073088
# The command is the token at index 0 in the command line
31083089
cmd_index = 0

tests/test_completion.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def complete_tester(text, line, begidx, endidx, app):
7373
:param app: the cmd2 app that will run completions
7474
:return: The first matched string or None if there are no matches
7575
Matches are stored in app.completion_matches
76-
These matches have been sorted by complete()
76+
These matches also have been sorted by complete()
7777
"""
7878
def get_line():
7979
return line
@@ -116,7 +116,7 @@ def test_complete_empty_arg(cmd2_app):
116116
endidx = len(line)
117117
begidx = endidx - len(text)
118118

119-
expected = cmd2_app.complete_help(text, line, begidx, endidx)
119+
expected = sorted(cmd2_app.complete_help(text, line, begidx, endidx))
120120
first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
121121

122122
assert first_match is not None and \
@@ -167,7 +167,8 @@ def test_cmd2_help_completion_multiple(cmd2_app):
167167
endidx = len(line)
168168
begidx = endidx - len(text)
169169

170-
assert cmd2_app.complete_help(text, line, begidx, endidx) == ['help', 'history']
170+
matches = sorted(cmd2_app.complete_help(text, line, begidx, endidx))
171+
assert matches == ['help', 'history']
171172

172173
def test_cmd2_help_completion_nomatch(cmd2_app):
173174
text = 'fakecommand'
@@ -269,8 +270,9 @@ def test_path_completion_multiple(cmd2_app, request):
269270
endidx = len(line)
270271
begidx = endidx - len(text)
271272

273+
matches = sorted(cmd2_app.path_complete(text, line, begidx, endidx))
272274
expected = [text + 'cript.py', text + 'cript.txt', text + 'cripts' + os.path.sep]
273-
assert expected == cmd2_app.path_complete(text, line, begidx, endidx)
275+
assert matches == expected
274276

275277
def test_path_completion_nomatch(cmd2_app, request):
276278
test_dir = os.path.dirname(request.module.__file__)
@@ -410,7 +412,8 @@ def test_basic_completion_multiple(cmd2_app):
410412
endidx = len(line)
411413
begidx = endidx - len(text)
412414

413-
assert cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs) == sorted(food_item_strs)
415+
matches = sorted(cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs))
416+
assert matches == sorted(food_item_strs)
414417

415418
def test_basic_completion_nomatch(cmd2_app):
416419
text = 'q'
@@ -449,7 +452,8 @@ def test_flag_based_completion_multiple(cmd2_app):
449452
endidx = len(line)
450453
begidx = endidx - len(text)
451454

452-
assert cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict) == sorted(food_item_strs)
455+
matches = sorted(cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict))
456+
assert matches == sorted(food_item_strs)
453457

454458
def test_flag_based_completion_nomatch(cmd2_app):
455459
text = 'q'
@@ -499,7 +503,8 @@ def test_index_based_completion_multiple(cmd2_app):
499503
endidx = len(line)
500504
begidx = endidx - len(text)
501505

502-
assert cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict) == sorted(sport_item_strs)
506+
matches = sorted(cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict))
507+
assert matches == sorted(sport_item_strs)
503508

504509
def test_index_based_completion_nomatch(cmd2_app):
505510
text = 'q'
@@ -744,7 +749,8 @@ def test_cmd2_help_subcommand_completion_multiple(sc_app):
744749
endidx = len(line)
745750
begidx = endidx - len(text)
746751

747-
assert sc_app.complete_help(text, line, begidx, endidx) == ['bar', 'foo', 'sport']
752+
matches = sorted(sc_app.complete_help(text, line, begidx, endidx))
753+
assert matches == ['bar', 'foo', 'sport']
748754

749755

750756
def test_cmd2_help_subcommand_completion_nomatch(sc_app):
@@ -899,7 +905,8 @@ def test_cmd2_help_submenu_completion_multiple(sb_app):
899905
endidx = len(line)
900906
begidx = endidx - len(text)
901907

902-
assert sb_app.complete_help(text, line, begidx, endidx) == ['py', 'pyscript']
908+
matches = sorted(sb_app.complete_help(text, line, begidx, endidx))
909+
assert matches == ['py', 'pyscript']
903910

904911

905912
def test_cmd2_help_submenu_completion_nomatch(sb_app):
@@ -916,4 +923,5 @@ def test_cmd2_help_submenu_completion_subcommands(sb_app):
916923
endidx = len(line)
917924
begidx = endidx - len(text)
918925

919-
assert sb_app.complete_help(text, line, begidx, endidx) == ['py', 'pyscript']
926+
matches = sorted(sb_app.complete_help(text, line, begidx, endidx))
927+
assert matches == ['py', 'pyscript']

0 commit comments

Comments
 (0)