Skip to content

Commit 24aa66f

Browse files
committed
Added tab completion of Python identifiers when running the Python console
1 parent 4ce81a7 commit 24aa66f

File tree

2 files changed

+62
-8
lines changed

2 files changed

+62
-8
lines changed

cmd2/cmd2.py

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@
6464
else:
6565
from .rl_utils import rl_force_redisplay, readline
6666

67+
# Used by rlcompleter in Python console loaded by py command
68+
orig_rl_delims = readline.get_completer_delims()
69+
6770
if rl_type == RlType.PYREADLINE:
6871

6972
# Save the original pyreadline display completion function since we need to override it and restore it
@@ -79,6 +82,9 @@
7982
import ctypes
8083
from .rl_utils import readline_lib
8184

85+
rl_basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters")
86+
orig_rl_basic_quotes = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value
87+
8288
from .argparse_completer import AutoCompleter, ACArgumentParser
8389

8490
# Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure
@@ -2025,7 +2031,6 @@ def _cmdloop(self):
20252031
# Set GNU readline's rl_basic_quote_characters to NULL so it won't automatically add a closing quote
20262032
# We don't need to worry about setting rl_completion_suppress_quote since we never declared
20272033
# rl_completer_quote_characters.
2028-
rl_basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters")
20292034
old_basic_quotes = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value
20302035
rl_basic_quote_characters.value = None
20312036

@@ -2586,8 +2591,36 @@ def quit():
25862591
readline.add_history(item)
25872592

25882593
if self.use_rawinput and self.completekey:
2589-
# Disable tab completion while in interactive Python shell
2590-
readline.parse_and_bind(self.completekey + ": ")
2594+
# Set up tab completion for the Python console
2595+
# rlcompleter relies on the default settings of the Python readline module
2596+
if rl_type == RlType.GNU:
2597+
old_basic_quotes = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value
2598+
rl_basic_quote_characters.value = orig_rl_basic_quotes
2599+
2600+
if 'gnureadline' in sys.modules:
2601+
# rlcompleter imports readline by name, so it won't use gnureadline
2602+
# Force rlcompleter to use gnureadline instead so it has our settings and history
2603+
saved_readline = None
2604+
if 'readline' in sys.modules:
2605+
saved_readline = sys.modules['readline']
2606+
2607+
sys.modules['readline'] = sys.modules['gnureadline']
2608+
2609+
old_delims = readline.get_completer_delims()
2610+
readline.set_completer_delims(orig_rl_delims)
2611+
2612+
# rlcompleter will not need cmd2's custom display function
2613+
# This will be restored by cmd2 the next time complete() is called
2614+
if rl_type == RlType.GNU:
2615+
readline.set_completion_display_matches_hook(None)
2616+
elif rl_type == RlType.PYREADLINE:
2617+
readline.rl.mode._display_completions = self._display_matches_pyreadline
2618+
2619+
# Load rlcompleter so it sets its completer function
2620+
old_completer = readline.get_completer()
2621+
import rlcompleter
2622+
import importlib
2623+
importlib.reload(rlcompleter)
25912624

25922625
cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
25932626
keepstate = Statekeeper(sys, ('stdin', 'stdout'))
@@ -2609,14 +2642,26 @@ def quit():
26092642
for i in range(1, readline.get_current_history_length() + 1):
26102643
self.py_history.append(readline.get_history_item(i))
26112644

2612-
# Restore cmd2 history
2645+
# Restore cmd2's history
26132646
readline.clear_history()
26142647
for item in saved_cmd2_history:
26152648
readline.add_history(item)
26162649

26172650
if self.use_rawinput and self.completekey:
2618-
# Enable tab completion since we are returning to cmd2
2619-
readline.parse_and_bind(self.completekey + ": complete")
2651+
# Restore cmd2's tab completion settings
2652+
readline.set_completer(old_completer)
2653+
readline.set_completer_delims(old_delims)
2654+
2655+
if rl_type == RlType.GNU:
2656+
rl_basic_quote_characters.value = old_basic_quotes
2657+
2658+
if 'gnureadline' in sys.modules:
2659+
# Restore what the readline module pointed to
2660+
if saved_readline is None:
2661+
del(sys.modules['readline'])
2662+
else:
2663+
sys.modules['readline'] = saved_readline
2664+
26202665
except Exception:
26212666
pass
26222667
finally:

cmd2/rl_utils.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,17 @@ class RlType(Enum):
3333
if 'pyreadline' in sys.modules:
3434
rl_type = RlType.PYREADLINE
3535

36-
# pyreadline is incomplete in terms of the Python readline API
37-
# Add the missing functions we need
36+
############################################################################################################
37+
# pyreadline is incomplete in terms of the Python readline API. Add the missing functions we need.
38+
############################################################################################################
39+
# readline.redisplay()
40+
try:
41+
getattr(readline, 'redisplay')
42+
except AttributeError:
43+
# noinspection PyProtectedMember
44+
readline.redisplay = readline.rl.mode._update_line
45+
46+
# readline.remove_history_item()
3847
try:
3948
getattr(readline, 'remove_history_item')
4049
except AttributeError:

0 commit comments

Comments
 (0)