Skip to content

Commit cba3dd1

Browse files
committed
Removed unnecessary sorting and duplicate removal from the completers since all
results from these functions are returned to complete() which already does these things. These changes also provide better examples of what is required to write a completer and what isn't.
1 parent ad463db commit cba3dd1

File tree

2 files changed

+53
-67
lines changed

2 files changed

+53
-67
lines changed

cmd2.py

Lines changed: 35 additions & 57 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

@@ -2121,16 +2098,20 @@ def complete(self, text, state):
21212098

21222099
else:
21232100
# 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)
2101+
alias_names = list(self.aliases.keys())
2102+
visible_commands = self.get_visible_commands()
2103+
strs_to_match = alias_names + visible_commands
21272104
self.completion_matches = self.basic_complete(text, line, begidx, endidx, strs_to_match)
21282105

21292106
# Eliminate duplicates and sort
21302107
matches_set = set(self.completion_matches)
21312108
self.completion_matches = list(matches_set)
21322109
self.completion_matches.sort()
21332110

2111+
display_matches_set = set(self.display_matches)
2112+
self.display_matches = list(display_matches_set)
2113+
self.display_matches.sort()
2114+
21342115
# Handle single result
21352116
if len(self.completion_matches) == 1:
21362117
str_to_append = ''
@@ -2152,19 +2133,14 @@ def complete(self, text, state):
21522133

21532134
def get_all_commands(self):
21542135
"""
2155-
Returns a sorted list of all commands
2156-
Any duplicates have been removed as well
2136+
Returns a list of all commands
21572137
"""
2158-
commands = [cur_name[3:] for cur_name in set(self.get_names()) if cur_name.startswith('do_')]
2159-
commands.sort()
2160-
return commands
2138+
return [cur_name[3:] for cur_name in self.get_names() if cur_name.startswith('do_')]
21612139

21622140
def get_visible_commands(self):
21632141
"""
2164-
Returns a sorted list of commands that have not been hidden
2165-
Any duplicates have been removed as well
2142+
Returns a list of commands that have not been hidden
21662143
"""
2167-
# This list is already sorted and has no duplicates
21682144
commands = self.get_all_commands()
21692145

21702146
# Remove the hidden commands
@@ -2175,13 +2151,13 @@ def get_visible_commands(self):
21752151
return commands
21762152

21772153
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_')]
2154+
""" Returns a list of help topics """
2155+
return [name[5:] for name in self.get_names() if name.startswith('help_')]
21802156

21812157
def complete_help(self, text, line, begidx, endidx):
21822158
"""
21832159
Override of parent class method to handle tab completing subcommands and not showing hidden commands
2184-
Returns a sorted list of possible tab completions
2160+
Returns a list of possible tab completions
21852161
"""
21862162

21872163
# The command is the token at index 1 in the command line
@@ -2204,9 +2180,9 @@ def complete_help(self, text, line, begidx, endidx):
22042180
if index == cmd_index:
22052181

22062182
# Complete token against topics and visible commands
2207-
topics = set(self.get_help_topics())
2208-
visible_commands = set(self.get_visible_commands())
2209-
strs_to_match = list(topics | visible_commands)
2183+
topics = self.get_help_topics()
2184+
visible_commands = self.get_visible_commands()
2185+
strs_to_match = topics + visible_commands
22102186
matches = self.basic_complete(text, line, begidx, endidx, strs_to_match)
22112187

22122188
# Check if we are completing a subcommand
@@ -2871,12 +2847,14 @@ def _help_menu(self):
28712847
"""
28722848
# Get a sorted list of help topics
28732849
help_topics = self.get_help_topics()
2874-
2875-
cmds_doc = []
2876-
cmds_undoc = []
2850+
help_topics.sort()
28772851

28782852
# Get a sorted list of visible command names
28792853
visible_commands = self.get_visible_commands()
2854+
visible_commands.sort()
2855+
2856+
cmds_doc = []
2857+
cmds_undoc = []
28802858

28812859
for command in visible_commands:
28822860
if command in help_topics:
@@ -3067,7 +3045,7 @@ def complete_shell(self, text, line, begidx, endidx):
30673045
:param line: str - the current input line with leading whitespace removed
30683046
:param begidx: int - the beginning index of the prefix text
30693047
:param endidx: int - the ending index of the prefix text
3070-
:return: List[str] - a sorted list of possible tab completions
3048+
:return: List[str] - a list of possible tab completions
30713049
"""
30723050
index_dict = {1: self.shell_cmd_complete}
30733051
return self.index_based_complete(text, line, begidx, endidx, index_dict, self.path_complete)
@@ -3102,7 +3080,7 @@ def cmd_with_subs_completer(self, text, line, begidx, endidx):
31023080
:param line: str - the current input line with leading whitespace removed
31033081
:param begidx: int - the beginning index of the prefix text
31043082
:param endidx: int - the ending index of the prefix text
3105-
:return: List[str] - a sorted list of possible tab completions
3083+
:return: List[str] - a list of possible tab completions
31063084
"""
31073085
# The command is the token at index 0 in the command line
31083086
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)