diff options
author | kmvanbrunt <kmvanbrunt@gmail.com> | 2018-05-26 01:17:48 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-05-26 01:17:48 -0400 |
commit | 6b6a36c41bd4d1ce3c7b8fbe46d935e599ccf744 (patch) | |
tree | bf57a15570248a2e3fbfb058a817d6c743cc9167 /cmd2/cmd2.py | |
parent | cad21a60fa92ebe4a7c177142d273f9f7497967b (diff) | |
parent | 4c67d3372db924e87be920d89aadecb946584b31 (diff) | |
download | cmd2-git-6b6a36c41bd4d1ce3c7b8fbe46d935e599ccf744.tar.gz |
Merge pull request #416 from python-cmd2/pyshell_readline
Pyshell readline
Diffstat (limited to 'cmd2/cmd2.py')
-rw-r--r--[-rwxr-xr-x] | cmd2/cmd2.py | 149 |
1 files changed, 130 insertions, 19 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index f480b3ae..817a1f21 100755..100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -58,6 +58,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 @@ -73,6 +76,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 @@ -416,6 +422,7 @@ class Cmd(cmd.Cmd): self.initial_stdout = sys.stdout self.history = History() self.pystate = {} + self.py_history = [] self.pyscript_name = 'app' self.keywords = self.reserved_words + [fname[3:] for fname in dir(self) if fname.startswith('do_')] self.statement_parser = StatementParser( @@ -476,7 +483,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 @@ -643,7 +650,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 @@ -1285,7 +1292,7 @@ class Cmd(cmd.Cmd): import functools 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() @@ -2026,12 +2033,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 @@ -2041,6 +2046,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 @@ -2077,7 +2083,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 @@ -2507,7 +2513,30 @@ 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 + @staticmethod + def _reset_py_display() -> None: + """ + Resets the dynamic objects in the sys module that the py and ipy consoles fight over. + When a Python console starts it adopts certain display settings if they've already been set. + If an ipy console has previously been run, then py uses its settings and ends up looking + like an ipy console in terms of prompt and exception text. This method forces the Python + console to create its own display settings since they won't exist. + + IPython does not have this problem since it always overwrites the display settings when it + is run. Therefore this method only needs to be called before creating a Python console. + """ + # Delete any prompts that have been set + attributes = ['ps1', 'ps2', 'ps3'] + for cur_attr in attributes: + try: + del sys.__dict__[cur_attr] + except KeyError: + pass + + # Reset functions + sys.displayhook = sys.__displayhook__ + sys.excepthook = sys.__excepthook__ + def do_py(self, arg): """ Invoke python command, shell, or script @@ -2524,6 +2553,7 @@ Usage: Usage: unalias [-a] name [name ...] return self._in_py = True + # noinspection PyBroadException try: arg = arg.strip() @@ -2539,6 +2569,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. @@ -2561,6 +2592,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(): @@ -2570,20 +2603,98 @@ Usage: Usage: unalias [-a] name [name ...] self.pystate['quit'] = quit self.pystate['exit'] = quit - keepstate = None + # Set up readline for Python console + 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 + 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 + + # Save off the current completer and set a new one in the Python console + # Make sure it tab completes from its locals() dictionary + old_completer = readline.get_completer() + interp.runcode("from rlcompleter import Completer") + interp.runcode("import readline") + interp.runcode("readline.set_completer(Completer(locals()).complete)") + + # Set up sys module for the Python console + self._reset_py_display() + keepstate = Statekeeper(sys, ('stdin', 'stdout')) + sys.stdout = self.stdout + sys.stdin = self.stdin + + cprt = 'Type "help", "copyright", "credits" or "license" for more information.' + docstr = self.do_py.__doc__.replace('pyscript_name', self.pyscript_name) + try: - cprt = 'Type "help", "copyright", "credits" or "license" for more information.' - keepstate = Statekeeper(sys, ('stdin', 'stdout')) - sys.stdout = self.stdout - sys.stdin = self.stdin - docstr = self.do_py.__doc__.replace('pyscript_name', self.pyscript_name) - interp.interact(banner="Python %s on %s\n%s\n(%s)\n%s" % - (sys.version, sys.platform, cprt, self.__class__.__name__, - docstr)) + interp.interact(banner="Python {} on {}\n{}\n({})\n{}". + format(sys.version, sys.platform, cprt, self.__class__.__name__, docstr)) except EmbeddedConsoleExit: pass - if keepstate is not None: + + finally: keepstate.restore() + + # Set up readline for cmd2 + if rl_type != RlType.NONE: + # Save py's history + self.py_history.clear() + 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: |