Skip to content

Commit 519283d

Browse files
committed
Added matches_sorted member to support custom sorting order of tab-completion matches
Made all sorting alphabetical Fixed case where extra slash was printing when tab completing users on Windows
1 parent bc559df commit 519283d

File tree

4 files changed

+49
-21
lines changed

4 files changed

+49
-21
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
* Bug Fixes
33
* Fixed bug where ``preparse`` wasn't getting called
44
* Enhancements
5-
* Improved implementation of lifecycle hooks to to support a plugin
5+
* Improved implementation of lifecycle hooks to support a plugin
66
framework, see ``docs/hooks.rst`` for details.
77
* New dependency on ``attrs`` third party module
8+
* Added ``matches_sorted`` member to support custom sorting of tab-completion matches
89
* Deprecations
910
* Deprecated the following hook methods, see ``hooks.rst`` for full details:
1011
* ``cmd2.Cmd.preparse()`` - equivilent functionality available
@@ -14,6 +15,10 @@
1415
* ``cmd2.Cmd.postparsing_postcmd()`` - equivilent functionality available
1516
via ``cmd2.Cmd.register_postcmd_hook()``
1617

18+
## 0.8.9 (TBD 2018)
19+
* Bug Fixes
20+
* Fixed extra slash that could print when tab completing users on Windows
21+
1722
## 0.9.3 (July 12, 2018)
1823
* Bug Fixes
1924
* Fixed bug when StatementParser ``__init__()`` was called with ``terminators`` equal to ``None``

cmd2/cmd2.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ def __init__(self, completekey: str='tab', stdin=None, stdout=None, persistent_h
369369
except AttributeError:
370370
pass
371371

372-
# initialize plugin system
372+
# initialize plugin system
373373
# needs to be done before we call __init__(0)
374374
self._initialize_plugin_system()
375375

@@ -482,11 +482,11 @@ def __init__(self, completekey: str='tab', stdin=None, stdout=None, persistent_h
482482
# in reset_completion_defaults() and it is up to completer functions to set them before returning results.
483483
############################################################################################################
484484

485-
# If true and a single match is returned to complete(), then a space will be appended
485+
# If True and a single match is returned to complete(), then a space will be appended
486486
# if the match appears at the end of the line
487487
self.allow_appended_space = True
488488

489-
# If true and a single match is returned to complete(), then a closing quote
489+
# If True and a single match is returned to complete(), then a closing quote
490490
# will be added if there is an unmatched opening quote
491491
self.allow_closing_quote = True
492492

@@ -504,6 +504,10 @@ def __init__(self, completekey: str='tab', stdin=None, stdout=None, persistent_h
504504
# quote matches that are completed in a delimited fashion
505505
self.matches_delimited = False
506506

507+
# Set to True before returning matches to complete() in cases where matches are sorted with custom ordering.
508+
# If False, then complete() will sort the matches alphabetically before they are displayed.
509+
self.matches_sorted = False
510+
507511
# Set the pager(s) for use with the ppaged() method for displaying output using a pager
508512
if sys.platform.startswith('win'):
509513
self.pager = self.pager_chop = 'more'
@@ -678,6 +682,7 @@ def reset_completion_defaults(self) -> None:
678682
self.completion_header = ''
679683
self.display_matches = []
680684
self.matches_delimited = False
685+
self.matches_sorted = False
681686

682687
if rl_type == RlType.GNU:
683688
readline.set_completion_display_matches_hook(self._display_matches_gnu_readline)
@@ -994,12 +999,15 @@ def complete_users():
994999
users = []
9951000

9961001
# Windows lacks the pwd module so we can't get a list of users.
997-
# Instead we will add a slash once the user enters text that
1002+
# Instead we will return a result once the user enters text that
9981003
# resolves to an existing home directory.
9991004
if sys.platform.startswith('win'):
10001005
expanded_path = os.path.expanduser(text)
10011006
if os.path.isdir(expanded_path):
1002-
users.append(text + os.path.sep)
1007+
user = text
1008+
if add_trailing_sep_if_dir:
1009+
user += os.path.sep
1010+
users.append(user)
10031011
else:
10041012
import pwd
10051013

@@ -1083,6 +1091,10 @@ def complete_users():
10831091
self.allow_appended_space = False
10841092
self.allow_closing_quote = False
10851093

1094+
# Sort the matches before any trailing slashes are added
1095+
matches.sort(key=str.lower)
1096+
self.matches_sorted = True
1097+
10861098
# Build display_matches and add a slash to directories
10871099
for index, cur_match in enumerate(matches):
10881100

@@ -1446,11 +1458,8 @@ def complete(self, text: str, state: int) -> Optional[str]:
14461458
if self.completion_matches:
14471459

14481460
# Eliminate duplicates
1449-
matches_set = set(self.completion_matches)
1450-
self.completion_matches = list(matches_set)
1451-
1452-
display_matches_set = set(self.display_matches)
1453-
self.display_matches = list(display_matches_set)
1461+
self.completion_matches = utils.remove_duplicates(self.completion_matches)
1462+
self.display_matches = utils.remove_duplicates(self.display_matches)
14541463

14551464
if not self.display_matches:
14561465
# Since self.display_matches is empty, set it to self.completion_matches
@@ -1521,10 +1530,11 @@ def complete(self, text: str, state: int) -> Optional[str]:
15211530

15221531
self.completion_matches[0] += str_to_append
15231532

1524-
# Otherwise sort matches
1525-
elif self.completion_matches:
1526-
self.completion_matches.sort()
1527-
self.display_matches.sort()
1533+
# Sort matches alphabetically if they haven't already been sorted
1534+
if not self.matches_sorted:
1535+
self.completion_matches.sort(key=str.lower)
1536+
self.display_matches.sort(key=str.lower)
1537+
self.matches_sorted = True
15281538

15291539
try:
15301540
return self.completion_matches[state]
@@ -2270,7 +2280,7 @@ def do_unalias(self, arglist: List[str]) -> None:
22702280

22712281
else:
22722282
# Get rid of duplicates
2273-
arglist = list(set(arglist))
2283+
arglist = utils.remove_duplicates(arglist)
22742284

22752285
for cur_arg in arglist:
22762286
if cur_arg in self.aliases:
@@ -2316,11 +2326,11 @@ def _help_menu(self, verbose: bool=False) -> None:
23162326
"""
23172327
# Get a sorted list of help topics
23182328
help_topics = self.get_help_topics()
2319-
help_topics.sort()
2329+
help_topics.sort(key=str.lower)
23202330

23212331
# Get a sorted list of visible command names
23222332
visible_commands = self.get_visible_commands()
2323-
visible_commands.sort()
2333+
visible_commands.sort(key=str.lower)
23242334

23252335
cmds_doc = []
23262336
cmds_undoc = []

cmd2/utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,16 @@ def is_text_file(file_path: str) -> bool:
144144
pass
145145

146146
return valid_text_file
147+
148+
149+
def remove_duplicates(list_to_prune: List) -> List:
150+
"""
151+
Removes duplicates from a list while preserving order of the items
152+
:param list_to_prune: the list being pruned of duplicates
153+
:return: The pruned list
154+
"""
155+
temp_dict = collections.OrderedDict()
156+
for item in list_to_prune:
157+
temp_dict[item] = None
158+
159+
return list(temp_dict.keys())

tests/test_completion.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import pytest
1616
import cmd2
17+
from cmd2 import utils
1718
from .conftest import complete_tester, StdOut
1819
from examples.subcommands import SubcommandsExample
1920

@@ -408,9 +409,8 @@ def test_delimiter_completion(cmd2_app):
408409
cmd2_app.delimiter_complete(text, line, begidx, endidx, delimited_strs, '/')
409410

410411
# Remove duplicates from display_matches and sort it. This is typically done in complete().
411-
display_set = set(cmd2_app.display_matches)
412-
display_list = list(display_set)
413-
display_list.sort()
412+
display_list = utils.remove_duplicates(cmd2_app.display_matches)
413+
display_list.sort(key=str.lower)
414414

415415
assert display_list == ['other user', 'user']
416416

0 commit comments

Comments
 (0)