summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--[-rwxr-xr-x]cmd2/cmd2.py109
-rw-r--r--cmd2/rl_utils.py32
2 files changed, 128 insertions, 13 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index a2d67def..a534f2a1 100755..100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -64,6 +64,9 @@ if rl_type == RlType.NONE:
else:
from .rl_utils import rl_force_redisplay, readline
+ # Used by rlcompleter in Python console loaded by py command
+ orig_rl_delims = readline.get_completer_delims()
+
if rl_type == RlType.PYREADLINE:
# Save the original pyreadline display completion function since we need to override it and restore it
@@ -79,6 +82,9 @@ else:
import ctypes
from .rl_utils import readline_lib
+ rl_basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters")
+ orig_rl_basic_quotes = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value
+
from .argparse_completer import AutoCompleter, ACArgumentParser
# Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure
@@ -486,7 +492,7 @@ class Cmd(cmd.Cmd):
############################################################################################################
# The following variables are used by tab-completion functions. They are reset each time complete() is run
- # using set_completion_defaults() and it is up to completer functions to set them before returning results.
+ # in reset_completion_defaults() and it is up to completer functions to set them before returning results.
############################################################################################################
# If true and a single match is returned to complete(), then a space will be appended
@@ -651,7 +657,7 @@ class Cmd(cmd.Cmd):
# ----- Methods related to tab completion -----
- def set_completion_defaults(self):
+ def reset_completion_defaults(self):
"""
Resets tab completion settings
Needs to be called each time readline runs tab completion
@@ -1291,7 +1297,7 @@ class Cmd(cmd.Cmd):
"""
if state == 0 and rl_type != RlType.NONE:
unclosed_quote = ''
- self.set_completion_defaults()
+ self.reset_completion_defaults()
# lstrip the original line
orig_line = readline.get_line_buffer()
@@ -2025,12 +2031,10 @@ class Cmd(cmd.Cmd):
# Set GNU readline's rl_basic_quote_characters to NULL so it won't automatically add a closing quote
# We don't need to worry about setting rl_completion_suppress_quote since we never declared
# rl_completer_quote_characters.
- basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters")
- old_basic_quote_characters = ctypes.cast(basic_quote_characters, ctypes.c_void_p).value
- basic_quote_characters.value = None
+ old_basic_quotes = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value
+ rl_basic_quote_characters.value = None
old_completer = readline.get_completer()
- old_delims = readline.get_completer_delims()
readline.set_completer(self.complete)
# Break words on whitespace and quotes when tab completing
@@ -2040,6 +2044,7 @@ class Cmd(cmd.Cmd):
# If redirection is allowed, then break words on those characters too
completer_delims += ''.join(constants.REDIRECTION_CHARS)
+ old_delims = readline.get_completer_delims()
readline.set_completer_delims(completer_delims)
# Enable tab completion
@@ -2076,7 +2081,7 @@ class Cmd(cmd.Cmd):
if rl_type == RlType.GNU:
readline.set_completion_display_matches_hook(None)
- basic_quote_characters.value = old_basic_quote_characters
+ rl_basic_quote_characters.value = old_basic_quotes
elif rl_type == RlType.PYREADLINE:
readline.rl.mode._display_completions = orig_pyreadline_display
@@ -2503,7 +2508,6 @@ Usage: Usage: unalias [-a] name [name ...]
index_dict = {1: self.shell_cmd_complete}
return self.index_based_complete(text, line, begidx, endidx, index_dict, self.path_complete)
- # noinspection PyBroadException
def do_py(self, arg):
"""
Invoke python command, shell, or script
@@ -2520,6 +2524,7 @@ Usage: Usage: unalias [-a] name [name ...]
return
self._in_py = True
+ # noinspection PyBroadException
try:
arg = arg.strip()
@@ -2535,6 +2540,7 @@ Usage: Usage: unalias [-a] name [name ...]
except IOError as e:
self.perror(e)
+ # noinspection PyUnusedLocal
def onecmd_plus_hooks(cmd_plus_args):
"""Run a cmd2.Cmd command from a Python script or the interactive Python console.
@@ -2556,6 +2562,8 @@ Usage: Usage: unalias [-a] name [name ...]
if arg:
interp.runcode(arg)
+
+ # If there are no args, then we will open an interactive Python console
else:
# noinspection PyShadowingBuiltins
def quit():
@@ -2567,6 +2575,52 @@ Usage: Usage: unalias [-a] name [name ...]
keepstate = None
try:
+ if rl_type != RlType.NONE:
+ # Save cmd2 history
+ saved_cmd2_history = []
+ for i in range(1, readline.get_current_history_length() + 1):
+ saved_cmd2_history.append(readline.get_history_item(i))
+
+ readline.clear_history()
+
+ # Restore py's history
+ # noinspection PyAttributeOutsideInit
+ self.py_history = getattr(self, 'py_history', [])
+ for item in self.py_history:
+ readline.add_history(item)
+
+ if self.use_rawinput and self.completekey:
+ # Set up tab completion for the Python console
+ # rlcompleter relies on the default settings of the Python readline module
+ if rl_type == RlType.GNU:
+ old_basic_quotes = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value
+ rl_basic_quote_characters.value = orig_rl_basic_quotes
+
+ if 'gnureadline' in sys.modules:
+ # rlcompleter imports readline by name, so it won't use gnureadline
+ # Force rlcompleter to use gnureadline instead so it has our settings and history
+ saved_readline = None
+ if 'readline' in sys.modules:
+ saved_readline = sys.modules['readline']
+
+ sys.modules['readline'] = sys.modules['gnureadline']
+
+ old_delims = readline.get_completer_delims()
+ readline.set_completer_delims(orig_rl_delims)
+
+ # rlcompleter will not need cmd2's custom display function
+ # This will be restored by cmd2 the next time complete() is called
+ if rl_type == RlType.GNU:
+ readline.set_completion_display_matches_hook(None)
+ elif rl_type == RlType.PYREADLINE:
+ readline.rl.mode._display_completions = self._display_matches_pyreadline
+
+ # Load rlcompleter so it sets its completer function
+ old_completer = readline.get_completer()
+ import rlcompleter
+ import importlib
+ importlib.reload(rlcompleter)
+
cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
keepstate = Statekeeper(sys, ('stdin', 'stdout'))
sys.stdout = self.stdout
@@ -2577,8 +2631,39 @@ Usage: Usage: unalias [-a] name [name ...]
docstr))
except EmbeddedConsoleExit:
pass
- if keepstate is not None:
- keepstate.restore()
+
+ finally:
+ if keepstate is not None:
+ keepstate.restore()
+
+ if rl_type != RlType.NONE:
+ # Save py's history
+ # noinspection PyAttributeOutsideInit
+ self.py_history = []
+ for i in range(1, readline.get_current_history_length() + 1):
+ self.py_history.append(readline.get_history_item(i))
+
+ readline.clear_history()
+
+ # Restore cmd2's history
+ for item in saved_cmd2_history:
+ readline.add_history(item)
+
+ if self.use_rawinput and self.completekey:
+ # Restore cmd2's tab completion settings
+ readline.set_completer(old_completer)
+ readline.set_completer_delims(old_delims)
+
+ if rl_type == RlType.GNU:
+ rl_basic_quote_characters.value = old_basic_quotes
+
+ if 'gnureadline' in sys.modules:
+ # Restore what the readline module pointed to
+ if saved_readline is None:
+ del(sys.modules['readline'])
+ else:
+ sys.modules['readline'] = saved_readline
+
except Exception:
pass
finally:
@@ -3430,5 +3515,3 @@ class CmdResult(namedtuple_with_two_defaults('CmdResult', ['out', 'err', 'war'])
def __bool__(self):
"""If err is an empty string, treat the result as a success; otherwise treat it as a failure."""
return not self.err
-
-
diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py
index 8ef65d28..55ca4a12 100644
--- a/cmd2/rl_utils.py
+++ b/cmd2/rl_utils.py
@@ -33,6 +33,38 @@ rl_type = RlType.NONE
if 'pyreadline' in sys.modules:
rl_type = RlType.PYREADLINE
+ ############################################################################################################
+ # pyreadline is incomplete in terms of the Python readline API. Add the missing functions we need.
+ ############################################################################################################
+ # readline.redisplay()
+ try:
+ getattr(readline, 'redisplay')
+ except AttributeError:
+ # noinspection PyProtectedMember
+ readline.redisplay = readline.rl.mode._update_line
+
+ # readline.remove_history_item()
+ try:
+ getattr(readline, 'remove_history_item')
+ except AttributeError:
+ # noinspection PyProtectedMember
+ def pyreadline_remove_history_item(pos: int) -> None:
+ """
+ An implementation of remove_history_item() for pyreadline
+ :param pos: The 0-based position in history to remove
+ """
+ # Save of the current location of the history cursor
+ saved_cursor = readline.rl.mode._history.history_cursor
+
+ # Delete the history item
+ del(readline.rl.mode._history.history[pos])
+
+ # Update the cursor if needed
+ if saved_cursor > pos:
+ readline.rl.mode._history.history_cursor -= 1
+
+ readline.remove_history_item = pyreadline_remove_history_item
+
elif 'gnureadline' in sys.modules or 'readline' in sys.modules:
# We don't support libedit
if 'libedit' not in readline.__doc__: