5858else :
5959 from .rl_utils import rl_force_redisplay , readline
6060
61+ # Used by rlcompleter in Python console loaded by py command
62+ orig_rl_delims = readline .get_completer_delims ()
63+
6164 if rl_type == RlType .PYREADLINE :
6265
6366 # Save the original pyreadline display completion function since we need to override it and restore it
7376 import ctypes
7477 from .rl_utils import readline_lib
7578
79+ rl_basic_quote_characters = ctypes .c_char_p .in_dll (readline_lib , "rl_basic_quote_characters" )
80+ orig_rl_basic_quotes = ctypes .cast (rl_basic_quote_characters , ctypes .c_void_p ).value
81+
7682from .argparse_completer import AutoCompleter , ACArgumentParser
7783
7884# Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure
@@ -416,6 +422,7 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, persistent_histor
416422 self .initial_stdout = sys .stdout
417423 self .history = History ()
418424 self .pystate = {}
425+ self .py_history = []
419426 self .pyscript_name = 'app'
420427 self .keywords = self .reserved_words + [fname [3 :] for fname in dir (self ) if fname .startswith ('do_' )]
421428 self .statement_parser = StatementParser (
@@ -476,7 +483,7 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, persistent_histor
476483
477484 ############################################################################################################
478485 # The following variables are used by tab-completion functions. They are reset each time complete() is run
479- # using set_completion_defaults () and it is up to completer functions to set them before returning results.
486+ # in reset_completion_defaults () and it is up to completer functions to set them before returning results.
480487 ############################################################################################################
481488
482489 # If true and a single match is returned to complete(), then a space will be appended
@@ -643,7 +650,7 @@ def colorize(self, val, color):
643650
644651 # ----- Methods related to tab completion -----
645652
646- def set_completion_defaults (self ):
653+ def reset_completion_defaults (self ):
647654 """
648655 Resets tab completion settings
649656 Needs to be called each time readline runs tab completion
@@ -1285,7 +1292,7 @@ def complete(self, text, state):
12851292 import functools
12861293 if state == 0 and rl_type != RlType .NONE :
12871294 unclosed_quote = ''
1288- self .set_completion_defaults ()
1295+ self .reset_completion_defaults ()
12891296
12901297 # lstrip the original line
12911298 orig_line = readline .get_line_buffer ()
@@ -2026,12 +2033,10 @@ def _cmdloop(self):
20262033 # Set GNU readline's rl_basic_quote_characters to NULL so it won't automatically add a closing quote
20272034 # We don't need to worry about setting rl_completion_suppress_quote since we never declared
20282035 # rl_completer_quote_characters.
2029- basic_quote_characters = ctypes .c_char_p .in_dll (readline_lib , "rl_basic_quote_characters" )
2030- old_basic_quote_characters = ctypes .cast (basic_quote_characters , ctypes .c_void_p ).value
2031- basic_quote_characters .value = None
2036+ old_basic_quotes = ctypes .cast (rl_basic_quote_characters , ctypes .c_void_p ).value
2037+ rl_basic_quote_characters .value = None
20322038
20332039 old_completer = readline .get_completer ()
2034- old_delims = readline .get_completer_delims ()
20352040 readline .set_completer (self .complete )
20362041
20372042 # Break words on whitespace and quotes when tab completing
@@ -2041,6 +2046,7 @@ def _cmdloop(self):
20412046 # If redirection is allowed, then break words on those characters too
20422047 completer_delims += '' .join (constants .REDIRECTION_CHARS )
20432048
2049+ old_delims = readline .get_completer_delims ()
20442050 readline .set_completer_delims (completer_delims )
20452051
20462052 # Enable tab completion
@@ -2077,7 +2083,7 @@ def _cmdloop(self):
20772083
20782084 if rl_type == RlType .GNU :
20792085 readline .set_completion_display_matches_hook (None )
2080- basic_quote_characters .value = old_basic_quote_characters
2086+ rl_basic_quote_characters .value = old_basic_quotes
20812087 elif rl_type == RlType .PYREADLINE :
20822088 readline .rl .mode ._display_completions = orig_pyreadline_display
20832089
@@ -2507,7 +2513,30 @@ def complete_shell(self, text, line, begidx, endidx):
25072513 index_dict = {1 : self .shell_cmd_complete }
25082514 return self .index_based_complete (text , line , begidx , endidx , index_dict , self .path_complete )
25092515
2510- # noinspection PyBroadException
2516+ @staticmethod
2517+ def _reset_py_display () -> None :
2518+ """
2519+ Resets the dynamic objects in the sys module that the py and ipy consoles fight over.
2520+ When a Python console starts it adopts certain display settings if they've already been set.
2521+ If an ipy console has previously been run, then py uses its settings and ends up looking
2522+ like an ipy console in terms of prompt and exception text. This method forces the Python
2523+ console to create its own display settings since they won't exist.
2524+
2525+ IPython does not have this problem since it always overwrites the display settings when it
2526+ is run. Therefore this method only needs to be called before creating a Python console.
2527+ """
2528+ # Delete any prompts that have been set
2529+ attributes = ['ps1' , 'ps2' , 'ps3' ]
2530+ for cur_attr in attributes :
2531+ try :
2532+ del sys .__dict__ [cur_attr ]
2533+ except KeyError :
2534+ pass
2535+
2536+ # Reset functions
2537+ sys .displayhook = sys .__displayhook__
2538+ sys .excepthook = sys .__excepthook__
2539+
25112540 def do_py (self , arg ):
25122541 """
25132542 Invoke python command, shell, or script
@@ -2524,6 +2553,7 @@ def do_py(self, arg):
25242553 return
25252554 self ._in_py = True
25262555
2556+ # noinspection PyBroadException
25272557 try :
25282558 arg = arg .strip ()
25292559
@@ -2539,6 +2569,7 @@ def run(filename):
25392569 except IOError as e :
25402570 self .perror (e )
25412571
2572+ # noinspection PyUnusedLocal
25422573 def onecmd_plus_hooks (cmd_plus_args ):
25432574 """Run a cmd2.Cmd command from a Python script or the interactive Python console.
25442575
@@ -2561,6 +2592,8 @@ def onecmd_plus_hooks(cmd_plus_args):
25612592
25622593 if arg :
25632594 interp .runcode (arg )
2595+
2596+ # If there are no args, then we will open an interactive Python console
25642597 else :
25652598 # noinspection PyShadowingBuiltins
25662599 def quit ():
@@ -2570,20 +2603,98 @@ def quit():
25702603 self .pystate ['quit' ] = quit
25712604 self .pystate ['exit' ] = quit
25722605
2573- keepstate = None
2606+ # Set up readline for Python console
2607+ if rl_type != RlType .NONE :
2608+ # Save cmd2 history
2609+ saved_cmd2_history = []
2610+ for i in range (1 , readline .get_current_history_length () + 1 ):
2611+ saved_cmd2_history .append (readline .get_history_item (i ))
2612+
2613+ readline .clear_history ()
2614+
2615+ # Restore py's history
2616+ for item in self .py_history :
2617+ readline .add_history (item )
2618+
2619+ if self .use_rawinput and self .completekey :
2620+ # Set up tab completion for the Python console
2621+ # rlcompleter relies on the default settings of the Python readline module
2622+ if rl_type == RlType .GNU :
2623+ old_basic_quotes = ctypes .cast (rl_basic_quote_characters , ctypes .c_void_p ).value
2624+ rl_basic_quote_characters .value = orig_rl_basic_quotes
2625+
2626+ if 'gnureadline' in sys .modules :
2627+ # rlcompleter imports readline by name, so it won't use gnureadline
2628+ # Force rlcompleter to use gnureadline instead so it has our settings and history
2629+ saved_readline = None
2630+ if 'readline' in sys .modules :
2631+ saved_readline = sys .modules ['readline' ]
2632+
2633+ sys .modules ['readline' ] = sys .modules ['gnureadline' ]
2634+
2635+ old_delims = readline .get_completer_delims ()
2636+ readline .set_completer_delims (orig_rl_delims )
2637+
2638+ # rlcompleter will not need cmd2's custom display function
2639+ # This will be restored by cmd2 the next time complete() is called
2640+ if rl_type == RlType .GNU :
2641+ readline .set_completion_display_matches_hook (None )
2642+ elif rl_type == RlType .PYREADLINE :
2643+ readline .rl .mode ._display_completions = self ._display_matches_pyreadline
2644+
2645+ # Save off the current completer and set a new one in the Python console
2646+ # Make sure it tab completes from its locals() dictionary
2647+ old_completer = readline .get_completer ()
2648+ interp .runcode ("from rlcompleter import Completer" )
2649+ interp .runcode ("import readline" )
2650+ interp .runcode ("readline.set_completer(Completer(locals()).complete)" )
2651+
2652+ # Set up sys module for the Python console
2653+ self ._reset_py_display ()
2654+ keepstate = Statekeeper (sys , ('stdin' , 'stdout' ))
2655+ sys .stdout = self .stdout
2656+ sys .stdin = self .stdin
2657+
2658+ cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
2659+ docstr = self .do_py .__doc__ .replace ('pyscript_name' , self .pyscript_name )
2660+
25742661 try :
2575- cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
2576- keepstate = Statekeeper (sys , ('stdin' , 'stdout' ))
2577- sys .stdout = self .stdout
2578- sys .stdin = self .stdin
2579- docstr = self .do_py .__doc__ .replace ('pyscript_name' , self .pyscript_name )
2580- interp .interact (banner = "Python %s on %s\n %s\n (%s)\n %s" %
2581- (sys .version , sys .platform , cprt , self .__class__ .__name__ ,
2582- docstr ))
2662+ interp .interact (banner = "Python {} on {}\n {}\n ({})\n {}" .
2663+ format (sys .version , sys .platform , cprt , self .__class__ .__name__ , docstr ))
25832664 except EmbeddedConsoleExit :
25842665 pass
2585- if keepstate is not None :
2666+
2667+ finally :
25862668 keepstate .restore ()
2669+
2670+ # Set up readline for cmd2
2671+ if rl_type != RlType .NONE :
2672+ # Save py's history
2673+ self .py_history .clear ()
2674+ for i in range (1 , readline .get_current_history_length () + 1 ):
2675+ self .py_history .append (readline .get_history_item (i ))
2676+
2677+ readline .clear_history ()
2678+
2679+ # Restore cmd2's history
2680+ for item in saved_cmd2_history :
2681+ readline .add_history (item )
2682+
2683+ if self .use_rawinput and self .completekey :
2684+ # Restore cmd2's tab completion settings
2685+ readline .set_completer (old_completer )
2686+ readline .set_completer_delims (old_delims )
2687+
2688+ if rl_type == RlType .GNU :
2689+ rl_basic_quote_characters .value = old_basic_quotes
2690+
2691+ if 'gnureadline' in sys .modules :
2692+ # Restore what the readline module pointed to
2693+ if saved_readline is None :
2694+ del (sys .modules ['readline' ])
2695+ else :
2696+ sys .modules ['readline' ] = saved_readline
2697+
25872698 except Exception :
25882699 pass
25892700 finally :
0 commit comments