summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2018-03-19 21:50:31 -0400
committerKevin Van Brunt <kmvanbrunt@gmail.com>2018-03-19 21:50:31 -0400
commit42fbd780d2393f4a2a6e25909a0a6f3f40b596a4 (patch)
tree8059627a0d00c636a46cdb4db7e3176348cbdb9d
parent0310ec70ddccfc3ca3563aaa8e1f62114462c228 (diff)
downloadcmd2-git-42fbd780d2393f4a2a6e25909a0a6f3f40b596a4.tar.gz
First steps toward automatically added opening quote
-rw-r--r--[-rwxr-xr-x]cmd2.py165
1 files changed, 67 insertions, 98 deletions
diff --git a/cmd2.py b/cmd2.py
index 52028798..5a03be7d 100755..100644
--- a/cmd2.py
+++ b/cmd2.py
@@ -95,7 +95,7 @@ try:
except ImportError:
pass
-# Make some changes to GNU readline
+# Load the GNU readline lib so we can make changes to it
readline_lib = None
if not sys.platform.startswith('win'):
import ctypes
@@ -105,10 +105,56 @@ if not sys.platform.startswith('win'):
if readline_lib_name is not None and readline_lib_name:
readline_lib = ctypes.CDLL(readline_lib_name)
+############################################################################################################
+# The following variables are used by tab-completion functions. They are reset each time complete()
+# is run, and it is up to the completer functions to set them on a case-by-case basis
+############################################################################################################
+
+# If true and a single match is returned to complete(), then a space will be appended
+# if the match appears at the end of the line
+allow_appended_space = True
+
+# If true and a single match is returned to complete(), then a closing quote
+# will be added if there is an umatched opening quote
+allow_closing_quote = True
+
+
+def set_allow_appended_space(allow):
+ """
+ Sets allow_appended_space flag
+ :param allow: bool - the new value for allow_appended_space
+ """
+ global allow_appended_space
+ allow_appended_space = allow
+
+
+def set_allow_closing_quote(allow):
+ """
+ Sets allow_closing_quote flag
+ :param allow: bool - the new value for allow_closing_quote
+ """
+ global allow_closing_quote
+ allow_closing_quote = allow
+
+
+def set_completion_defaults():
+ """
+ Resets tab completion settings
+ Called each time complete() is called
+ """
+ set_allow_appended_space(True)
+ set_allow_closing_quote(True)
+
+ if readline_lib is not None:
# Set GNU readline's rl_basic_quote_characters to NULL so it won't automatically add a closing quote
rl_basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters")
rl_basic_quote_characters.value = None
+ # Set GNU readline's rl_completion_suppress_quote to 1 so it won't automatically add a closing quote
+ suppress_quote = ctypes.c_int.in_dll(readline_lib, "rl_completion_suppress_quote")
+ suppress_quote.value = 1
+
+
# BrokenPipeError and FileNotFoundError exist only in Python 3. Use IOError for Python 2.
if six.PY3:
BROKEN_PIPE_ERROR = BrokenPipeError
@@ -257,17 +303,6 @@ def basic_complete(text, line, begidx, endidx, match_against):
starting_index = len(completion_token) - len(text)
completions = [cur_str[starting_index:] for cur_str in match_against if cur_str.startswith(completion_token)]
- # Handle a single completion
- if len(completions) == 1:
-
- # Close the quote
- if unclosed_quote:
- completions[0] += unclosed_quote
-
- # If we are at the end of the line, then add a space
- if endidx == len(line):
- completions[0] += ' '
-
completions.sort()
return completions
@@ -312,17 +347,6 @@ def flag_based_complete(text, line, begidx, endidx, flag_dict, all_else=None):
starting_index = len(completion_token) - len(text)
completions = [cur_str[starting_index:] for cur_str in match_against if cur_str.startswith(completion_token)]
- # Handle a single completion
- if len(completions) == 1:
-
- # Close the quote
- if unclosed_quote:
- completions[0] += unclosed_quote
-
- # If we are at the end of the line, then add a space
- if endidx == len(line):
- completions[0] += ' '
-
# Perform tab completion using a function
elif callable(match_against):
completions = match_against(text, line, begidx, endidx)
@@ -373,17 +397,6 @@ def index_based_complete(text, line, begidx, endidx, index_dict, all_else=None):
starting_index = len(completion_token) - len(text)
completions = [cur_str[starting_index:] for cur_str in match_against if cur_str.startswith(completion_token)]
- # Handle a single completion
- if len(completions) == 1:
-
- # Close the quote
- if unclosed_quote:
- completions[0] += unclosed_quote
-
- # If we are at the end of the line, then add a space
- if endidx == len(line):
- completions[0] += ' '
-
# Perform tab completion using a function
elif callable(match_against):
completions = match_against(text, line, begidx, endidx)
@@ -471,16 +484,10 @@ def path_complete(text, line, begidx, endidx, dir_exe_only=False, dir_only=False
completions.append(return_str)
- # Handle a single file completion
- if len(completions) == 1 and os.path.isfile(path_completions[0]):
-
- # Close the quote
- if unclosed_quote:
- completions[0] += unclosed_quote
-
- # If we are at the end of the line, then add a space
- if endidx == len(line):
- completions[0] += ' '
+ # Don't append a space or closing quote to directory
+ if len(completions) == 1 and not os.path.isfile(path_completions[0]):
+ set_allow_appended_space(False)
+ set_allow_closing_quote(False)
completions.sort()
return completions
@@ -1422,20 +1429,6 @@ class Cmd(cmd.Cmd):
return self._colorcodes[color][True] + val + self._colorcodes[color][False]
return val
- # ----- Methods which override stuff in cmd -----
-
- # noinspection PyMethodOverriding
- def completenames(self, text, line, begidx, endidx):
- """Override of cmd method which completes command names both for command completion and help."""
- # Call super class method. Need to do it this way for Python 2 and 3 compatibility
- cmd_completion = cmd.Cmd.completenames(self, text)
-
- # If we are completing the initial command name and get exactly 1 result and are at end of line, add a space
- if begidx == 0 and len(cmd_completion) == 1 and endidx == len(line):
- cmd_completion[0] += ' '
-
- return cmd_completion
-
def get_subcommands(self, command):
"""
Returns a list of a command's subcommands if they exist
@@ -1455,6 +1448,8 @@ class Cmd(cmd.Cmd):
return subcommand_names
+ # ----- Methods which override stuff in cmd -----
+
def complete(self, text, state):
"""Override of command method which returns the next possible completion for 'text'.
@@ -1471,12 +1466,7 @@ class Cmd(cmd.Cmd):
"""
if state == 0:
-
- if readline_lib is not None:
- # Set GNU readline's rl_completion_suppress_quote to 1 so it won't automatically add a closing quote
- # This gets reset before complete() is called, so we have to set it each time
- suppress_quote = ctypes.c_int.in_dll(readline_lib, "rl_completion_suppress_quote")
- suppress_quote.value = 1
+ set_completion_defaults()
origline = readline.get_line_buffer()
line = origline.lstrip()
@@ -1487,13 +1477,23 @@ class Cmd(cmd.Cmd):
# If begidx is greater than 0, then the cursor is past the command
if begidx > 0:
+ # Get all tokens through the one being completed
+ tokens, unclosed_quote = tokenize_line(line, begidx, endidx)
+
+ # If the cursor is right after a closed quote, then insert a space
+ quotes = ['"', "'"]
+ prior_char = line[begidx - 1]
+ if not unclosed_quote and prior_char in quotes:
+ self.completion_matches = [' ']
+ return self.completion_matches[state]
+
# Parse the command line
command, args, expanded_line = self.parseline(line)
# We overwrote line with a properly formatted but fully stripped version
- # Restore the end spaces from the original since line is only supposed to be
- # lstripped when passed to completer functions according to Python docs
- rstripped_len = len(origline) - len(origline.rstrip())
+ # Restore the end spaces since line is only supposed to be lstripped when
+ # passed to completer functions according to Python docs
+ rstripped_len = len(line) - len(line.rstrip())
expanded_line += ' ' * rstripped_len
# Fix the index values if expanded_line has a different size than line
@@ -1505,15 +1505,6 @@ class Cmd(cmd.Cmd):
# Overwrite line to pass into completers
line = expanded_line
- # If the cursor is right after a closed quote, then insert a space
- tokens, unclosed_quote = tokenize_line(line, begidx, endidx)
- prior_char = line[begidx - 1]
- quotes = ['"', "'"]
-
- if not unclosed_quote and prior_char in quotes:
- self.completion_matches = [' ']
- return self.completion_matches[state]
-
# Otherwise select a completer function
if command == '':
compfunc = self.completedefault
@@ -1599,17 +1590,6 @@ class Cmd(cmd.Cmd):
if subcommands is not None:
completions = [cur_sub for cur_sub in subcommands if cur_sub.startswith(text)]
- # Handle a single completion
- if len(completions) == 1:
-
- # Close the quote
- if unclosed_quote:
- completions[0] += unclosed_quote
-
- # If we are at the end of the line, then add a space
- if endidx == len(line):
- completions[0] += ' '
-
completions.sort()
return completions
@@ -2457,17 +2437,6 @@ Usage: Usage: unalias [-a] name [name ...]
completions = [cur_str[starting_index:] for cur_str in self._get_exes_in_path(completion_token)]
if completions:
- # Handle a single completion
- if len(completions) == 1:
-
- # Close the quote
- if unclosed_quote:
- completions[0] += unclosed_quote
-
- # If we are at the end of the line, then add a space
- if endidx == len(line):
- completions[0] += ' '
-
return completions
# If we have no results, try path completion to find the shell commands