58
58
else :
59
59
from .rl_utils import rl_force_redisplay , readline
60
60
61
+ # Used by rlcompleter in Python console loaded by py command
62
+ orig_rl_delims = readline .get_completer_delims ()
63
+
61
64
if rl_type == RlType .PYREADLINE :
62
65
63
66
# Save the original pyreadline display completion function since we need to override it and restore it
73
76
import ctypes
74
77
from .rl_utils import readline_lib
75
78
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
+
76
82
from .argparse_completer import AutoCompleter , ACArgumentParser
77
83
78
84
# 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
416
422
self .initial_stdout = sys .stdout
417
423
self .history = History ()
418
424
self .pystate = {}
425
+ self .py_history = []
419
426
self .pyscript_name = 'app'
420
427
self .keywords = self .reserved_words + [fname [3 :] for fname in dir (self ) if fname .startswith ('do_' )]
421
428
self .statement_parser = StatementParser (
@@ -476,7 +483,7 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, persistent_histor
476
483
477
484
############################################################################################################
478
485
# 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.
480
487
############################################################################################################
481
488
482
489
# 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):
643
650
644
651
# ----- Methods related to tab completion -----
645
652
646
- def set_completion_defaults (self ):
653
+ def reset_completion_defaults (self ):
647
654
"""
648
655
Resets tab completion settings
649
656
Needs to be called each time readline runs tab completion
@@ -1285,7 +1292,7 @@ def complete(self, text, state):
1285
1292
import functools
1286
1293
if state == 0 and rl_type != RlType .NONE :
1287
1294
unclosed_quote = ''
1288
- self .set_completion_defaults ()
1295
+ self .reset_completion_defaults ()
1289
1296
1290
1297
# lstrip the original line
1291
1298
orig_line = readline .get_line_buffer ()
@@ -2026,12 +2033,10 @@ def _cmdloop(self):
2026
2033
# Set GNU readline's rl_basic_quote_characters to NULL so it won't automatically add a closing quote
2027
2034
# We don't need to worry about setting rl_completion_suppress_quote since we never declared
2028
2035
# 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
2032
2038
2033
2039
old_completer = readline .get_completer ()
2034
- old_delims = readline .get_completer_delims ()
2035
2040
readline .set_completer (self .complete )
2036
2041
2037
2042
# Break words on whitespace and quotes when tab completing
@@ -2041,6 +2046,7 @@ def _cmdloop(self):
2041
2046
# If redirection is allowed, then break words on those characters too
2042
2047
completer_delims += '' .join (constants .REDIRECTION_CHARS )
2043
2048
2049
+ old_delims = readline .get_completer_delims ()
2044
2050
readline .set_completer_delims (completer_delims )
2045
2051
2046
2052
# Enable tab completion
@@ -2077,7 +2083,7 @@ def _cmdloop(self):
2077
2083
2078
2084
if rl_type == RlType .GNU :
2079
2085
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
2081
2087
elif rl_type == RlType .PYREADLINE :
2082
2088
readline .rl .mode ._display_completions = orig_pyreadline_display
2083
2089
@@ -2507,7 +2513,30 @@ def complete_shell(self, text, line, begidx, endidx):
2507
2513
index_dict = {1 : self .shell_cmd_complete }
2508
2514
return self .index_based_complete (text , line , begidx , endidx , index_dict , self .path_complete )
2509
2515
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
+
2511
2540
def do_py (self , arg ):
2512
2541
"""
2513
2542
Invoke python command, shell, or script
@@ -2524,6 +2553,7 @@ def do_py(self, arg):
2524
2553
return
2525
2554
self ._in_py = True
2526
2555
2556
+ # noinspection PyBroadException
2527
2557
try :
2528
2558
arg = arg .strip ()
2529
2559
@@ -2539,6 +2569,7 @@ def run(filename):
2539
2569
except IOError as e :
2540
2570
self .perror (e )
2541
2571
2572
+ # noinspection PyUnusedLocal
2542
2573
def onecmd_plus_hooks (cmd_plus_args ):
2543
2574
"""Run a cmd2.Cmd command from a Python script or the interactive Python console.
2544
2575
@@ -2561,6 +2592,8 @@ def onecmd_plus_hooks(cmd_plus_args):
2561
2592
2562
2593
if arg :
2563
2594
interp .runcode (arg )
2595
+
2596
+ # If there are no args, then we will open an interactive Python console
2564
2597
else :
2565
2598
# noinspection PyShadowingBuiltins
2566
2599
def quit ():
@@ -2570,20 +2603,98 @@ def quit():
2570
2603
self .pystate ['quit' ] = quit
2571
2604
self .pystate ['exit' ] = quit
2572
2605
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
+
2574
2661
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 ))
2583
2664
except EmbeddedConsoleExit :
2584
2665
pass
2585
- if keepstate is not None :
2666
+
2667
+ finally :
2586
2668
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
+
2587
2698
except Exception :
2588
2699
pass
2589
2700
finally :
0 commit comments