Skip to content

Commit eb851f4

Browse files
committed
All completer routines now receive the entire token being completed in the text variable
1 parent 8082d98 commit eb851f4

1 file changed

Lines changed: 37 additions & 96 deletions

File tree

cmd2.py

Lines changed: 37 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,9 +1393,6 @@ def tokens_for_completion(self, line, begidx, endidx):
13931393
def basic_complete(self, text, line, begidx, endidx, match_against):
13941394
"""
13951395
Performs tab completion against a list
1396-
This is ultimately called by many completer functions like flag_based_complete and index_based_complete.
1397-
It can also be used by custom completer functions and that is the suggested approach since this function
1398-
handles things like tab completions with spaces.
13991396
14001397
:param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
14011398
:param line: str - the current input line with leading whitespace removed
@@ -1404,24 +1401,12 @@ def basic_complete(self, text, line, begidx, endidx, match_against):
14041401
:param match_against: Collection - the list being matched against
14051402
:return: List[str] - a sorted list of possible tab completions
14061403
"""
1407-
# Make sure we were given an Collection with items to match against
1404+
# Make sure we were given a Collection with items to match against
14081405
if not isinstance(match_against, Collection) or len(match_against) == 0:
14091406
return []
14101407

1411-
# Get all tokens through the one being completed
1412-
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
1413-
if tokens is None:
1414-
return []
1415-
14161408
# Perform matching and eliminate duplicates
1417-
completion_token = tokens[-1]
1418-
full_matches = [cur_match for cur_match in set(match_against) if cur_match.startswith(completion_token)]
1419-
if len(full_matches) == 0:
1420-
return []
1421-
1422-
# We will only keep where the text value starts
1423-
starting_index = len(completion_token) - len(text)
1424-
completion_matches = [cur_match[starting_index:] for cur_match in full_matches]
1409+
completion_matches = [cur_match for cur_match in set(match_against) if cur_match.startswith(text)]
14251410

14261411
completion_matches.sort()
14271412
return completion_matches
@@ -1443,7 +1428,6 @@ def flag_based_complete(self, text, line, begidx, endidx, flag_dict, all_else=No
14431428
by a flag in flag_dict
14441429
:return: List[str] - a sorted list of possible tab completions
14451430
"""
1446-
14471431
# Get all tokens through the one being completed
14481432
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
14491433
if tokens is None:
@@ -1486,7 +1470,6 @@ def index_based_complete(self, text, line, begidx, endidx, index_dict, all_else=
14861470
index in index_dict
14871471
:return: List[str] - a sorted list of possible tab completions
14881472
"""
1489-
14901473
# Get all tokens through the one being completed
14911474
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
14921475
if tokens is None:
@@ -1525,19 +1508,11 @@ def path_complete(self, text, line, begidx, endidx, dir_exe_only=False, dir_only
15251508
:param dir_only: bool - only return directories
15261509
:return: List[str] - a sorted list of possible tab completions
15271510
"""
1528-
1529-
# Get all tokens through the one being completed
1530-
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
1531-
if tokens is None:
1532-
return []
1533-
15341511
# Determine if a trailing separator should be appended to directory completions
15351512
add_trailing_sep_if_dir = False
15361513
if endidx == len(line) or (endidx < len(line) and line[endidx] != os.path.sep):
15371514
add_trailing_sep_if_dir = True
15381515

1539-
completion_token = tokens[-1]
1540-
15411516
# Used to replace cwd in the final results
15421517
cwd = os.getcwd()
15431518
cwd_added = False
@@ -1546,75 +1521,62 @@ def path_complete(self, text, line, begidx, endidx, dir_exe_only=False, dir_only
15461521
user_path = os.path.expanduser('~')
15471522
tilde_expanded = False
15481523

1549-
# If the token being completed is blank, then search in the CWD for *
1550-
if not completion_token:
1524+
# If the search text is blank, then search in the CWD for *
1525+
if not text:
15511526
search_str = os.path.join(os.getcwd(), '*')
15521527
cwd_added = True
15531528
else:
15541529
# Purposely don't match any path containing wildcards - what we are doing is complicated enough!
15551530
wildcards = ['*', '?']
15561531
for wildcard in wildcards:
1557-
if wildcard in completion_token:
1532+
if wildcard in text:
15581533
return []
15591534

15601535
# Used if we need to prepend a directory to the search string
15611536
dirname = ''
15621537

15631538
# If the user only entered a '~', then complete it with a slash
1564-
if completion_token == '~':
1539+
if text == '~':
15651540
# This is a directory, so don't add a space or quote
15661541
self.allow_appended_space = False
15671542
self.allow_closing_quote = False
1568-
return [completion_token + os.path.sep]
1543+
return [text + os.path.sep]
15691544

1570-
elif completion_token.startswith('~'):
1545+
elif text.startswith('~'):
15711546
# Tilde without separator between path is invalid
1572-
if not completion_token.startswith('~' + os.path.sep):
1547+
if not text.startswith('~' + os.path.sep):
15731548
return []
15741549

15751550
# Mark that we are expanding a tilde
15761551
tilde_expanded = True
15771552

1578-
# If the token does not have a directory, then use the cwd
1579-
elif not os.path.dirname(completion_token):
1553+
# If the search text does not have a directory, then use the cwd
1554+
elif not os.path.dirname(text):
15801555
dirname = os.getcwd()
15811556
cwd_added = True
15821557

15831558
# Build the search string
1584-
search_str = os.path.join(dirname, completion_token + '*')
1559+
search_str = os.path.join(dirname, text + '*')
15851560

15861561
# Expand "~" to the real user directory
15871562
search_str = os.path.expanduser(search_str)
15881563

1589-
# If the text being completed does not appear at the beginning of the token being completed,
1590-
# which can happen if there are spaces, save off the index where our search text begins in the
1591-
# search string so we can return only that portion of the completed paths to readline
1592-
if len(completion_token) - len(text) > 0:
1593-
starting_index = search_str.rfind(text + '*')
1594-
else:
1595-
starting_index = 0
1596-
15971564
# Find all matching path completions
1598-
full_matches = glob.glob(search_str)
1565+
completion_matches = glob.glob(search_str)
15991566

1600-
# If we only want directories and executables, filter everything else out first
1567+
# Filter based on type
16011568
if dir_exe_only:
1602-
full_matches = [c for c in full_matches if os.path.isdir(c) or os.access(c, os.X_OK)]
1569+
completion_matches = [c for c in completion_matches if os.path.isdir(c) or os.access(c, os.X_OK)]
16031570
elif dir_only:
1604-
full_matches = [c for c in full_matches if os.path.isdir(c)]
1571+
completion_matches = [c for c in completion_matches if os.path.isdir(c)]
16051572

16061573
# Don't append a space or closing quote to directory
1607-
if len(full_matches) == 1 and not os.path.isfile(full_matches[0]):
1574+
if len(completion_matches) == 1 and not os.path.isfile(completion_matches[0]):
16081575
self.allow_appended_space = False
16091576
self.allow_closing_quote = False
16101577

1611-
# Build the completion lists
1612-
completion_matches = []
1613-
1614-
for cur_match in full_matches:
1615-
1616-
# Only keep where text started for the tab completion
1617-
completion_matches.append(cur_match[starting_index:])
1578+
# Build display_matches and add a slash to directories
1579+
for cur_match in completion_matches:
16181580

16191581
# Display only the basename of this path in the tab-completion suggestions
16201582
self.display_matches.append(os.path.basename(cur_match))
@@ -1642,7 +1604,6 @@ def get_exes_in_path(starts_with):
16421604
:param starts_with: str - what the exes should start with. leave blank for all exes in path.
16431605
:return: List[str] - a sorted list of matching exe names
16441606
"""
1645-
16461607
# Purposely don't match any executable containing wildcards
16471608
wildcards = ['*', '?']
16481609
for wildcard in wildcards:
@@ -1678,31 +1639,13 @@ def shell_cmd_complete(self, text, line, begidx, endidx, complete_blank=False):
16781639
Defaults to False to match Bash shell behavior
16791640
:return: List[str] - a sorted list of possible tab completions
16801641
"""
1681-
1682-
# Get all tokens through the one being completed
1683-
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
1684-
if tokens is None:
1685-
return []
1686-
1687-
completion_token = tokens[-1]
1688-
16891642
# Don't tab complete anything if no shell command has been started
1690-
if not complete_blank and len(completion_token) == 0:
1643+
if not complete_blank and len(text) == 0:
16911644
return []
16921645

1693-
# If there are no path characters in this token, then do shell command completion in the user's path
1694-
if os.path.sep not in completion_token:
1695-
# These matches are already sorted
1696-
full_matches = self.get_exes_in_path(completion_token)
1697-
1698-
# We will only keep where the text value starts for the tab completions
1699-
starting_index = len(completion_token) - len(text)
1700-
completion_matches = [cur_exe[starting_index:] for cur_exe in full_matches]
1701-
1702-
# Use the full name of the executables for the completions that are displayed
1703-
self.display_matches = full_matches
1704-
1705-
return completion_matches
1646+
# If there are no path characters in the search text, then do shell command completion in the user's path
1647+
if os.path.sep not in text:
1648+
return self.get_exes_in_path(text)
17061649

17071650
# Otherwise look for executables in the given path
17081651
else:
@@ -1722,7 +1665,6 @@ def _redirect_complete(self, text, line, begidx, endidx, compfunc):
17221665
this will be called if we aren't completing for redirection
17231666
:return: List[str] - a sorted list of possible tab completions
17241667
"""
1725-
17261668
if self.allow_redirection:
17271669

17281670
# Get all tokens through the one being completed. We want the raw tokens
@@ -1941,7 +1883,6 @@ def complete(self, text, state):
19411883
:param text: str - the current word that user is typing
19421884
:param state: int - non-negative integer
19431885
"""
1944-
19451886
if state == 0:
19461887
unclosed_quote = ''
19471888
self.set_completion_defaults()
@@ -2011,23 +1952,23 @@ def complete(self, text, state):
20111952
self.completion_matches = []
20121953
return None
20131954

2014-
# We may add to the beginning of our search text. Save this text because we
2015-
# need to remove it from tab completions later since readline isn't expecting it
2016-
text_to_remove = ''
1955+
# readline still performs word breaks in quotes. Therefore quoted search text with
1956+
# a space would have resulted in begidx pointing to the middle of the token we want
1957+
# to complete. Figure out where that token actually begins.
1958+
actual_begidx = line[:endidx].rfind(tokens[-1])
20171959

2018-
if self.allow_redirection:
2019-
# Readline may have split a string with an opening quote because of a redirect
2020-
# character like <. Therefore the original begidx would be wrong since a redirect
2021-
# character in a quoted string should not be a word break. Update begidx to where
2022-
# the token being completed actually starts.
2023-
actual_begidx = line[:endidx].rfind(tokens[-1])
1960+
# If actual_begidx is different than what readline gave us, save the beginning portion
1961+
# of the completion token that does not belong in text. We will remove it from the
1962+
# completions later since readline expects our completions to start with the original text.
1963+
text_to_remove = ''
20241964

2025-
if actual_begidx != begidx:
2026-
text_to_remove = line[actual_begidx:begidx]
1965+
if actual_begidx != begidx:
1966+
text_to_remove = line[actual_begidx:begidx]
20271967

2028-
# Adjust text and where it begins
2029-
text = text_to_remove + text
2030-
begidx = actual_begidx
1968+
# Adjust text and where it begins so the completer routines
1969+
# get unbroken search text to complete on.
1970+
text = text_to_remove + text
1971+
begidx = actual_begidx
20311972

20321973
# Get the tokens with preserved quotes
20331974
raw_completion_token = raw_tokens[-1]

0 commit comments

Comments
 (0)