Skip to content

Commit 01e4e02

Browse files
authored
Merge pull request #376 from python-cmd2/submenu_completion
Fixed tab completion issues in submenus
2 parents 1306eeb + 4447a8b commit 01e4e02

File tree

1 file changed

+36
-34
lines changed

1 file changed

+36
-34
lines changed

cmd2/cmd2.py

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,6 @@
7171
import ctypes
7272
from .rl_utils import readline_lib
7373

74-
# Save address that rl_basic_quote_characters is pointing to since we need to override and restore it
75-
rl_basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters")
76-
orig_rl_basic_quote_characters_addr = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value
77-
7874
# Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure
7975
try:
8076
from pyperclip.exceptions import PyperclipException
@@ -595,11 +591,22 @@ def complete_submenu(_self, text, line, begidx, endidx):
595591
try:
596592
# copy over any shared attributes
597593
self._copy_in_shared_attrs(_self)
594+
595+
# Reset the submenu's tab completion parameters
596+
submenu.allow_appended_space = True
597+
submenu.allow_closing_quote = True
598+
submenu.display_matches = []
599+
598600
return _complete_from_cmd(submenu, text, line, begidx, endidx)
599601
finally:
600602
# copy back original attributes
601603
self._copy_out_shared_attrs(_self, original_attributes)
602604

605+
# Pass the submenu's tab completion parameters back up to the menu that called complete()
606+
_self.allow_appended_space = submenu.allow_appended_space
607+
_self.allow_closing_quote = submenu.allow_closing_quote
608+
_self.display_matches = copy.copy(submenu.display_matches)
609+
603610
original_do_help = cmd_obj.do_help
604611
original_complete_help = cmd_obj.complete_help
605612

@@ -987,6 +994,11 @@ def set_completion_defaults(self):
987994
self.allow_closing_quote = True
988995
self.display_matches = []
989996

997+
if rl_type == RlType.GNU:
998+
readline.set_completion_display_matches_hook(self._display_matches_gnu_readline)
999+
elif rl_type == RlType.PYREADLINE:
1000+
readline.rl.mode._display_completions = self._display_matches_pyreadline
1001+
9901002
def tokens_for_completion(self, line, begidx, endidx):
9911003
"""
9921004
Used by tab completion functions to get all tokens through the one being completed
@@ -2357,38 +2369,33 @@ def _cmdloop(self):
23572369
"""
23582370
# An almost perfect copy from Cmd; however, the pseudo_raw_input portion
23592371
# has been split out so that it can be called separately
2360-
if self.use_rawinput and self.completekey:
2372+
if self.use_rawinput and self.completekey and rl_type != RlType.NONE:
23612373

23622374
# Set up readline for our tab completion needs
23632375
if rl_type == RlType.GNU:
2364-
readline.set_completion_display_matches_hook(self._display_matches_gnu_readline)
2365-
23662376
# Set GNU readline's rl_basic_quote_characters to NULL so it won't automatically add a closing quote
23672377
# We don't need to worry about setting rl_completion_suppress_quote since we never declared
23682378
# rl_completer_quote_characters.
2369-
rl_basic_quote_characters.value = None
2379+
basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters")
2380+
old_basic_quote_characters = ctypes.cast(basic_quote_characters, ctypes.c_void_p).value
2381+
basic_quote_characters.value = None
23702382

2371-
elif rl_type == RlType.PYREADLINE:
2372-
readline.rl.mode._display_completions = self._display_matches_pyreadline
2383+
old_completer = readline.get_completer()
2384+
old_delims = readline.get_completer_delims()
2385+
readline.set_completer(self.complete)
23732386

2374-
try:
2375-
self.old_completer = readline.get_completer()
2376-
self.old_delims = readline.get_completer_delims()
2377-
readline.set_completer(self.complete)
2387+
# Break words on whitespace and quotes when tab completing
2388+
completer_delims = " \t\n" + ''.join(constants.QUOTES)
23782389

2379-
# Break words on whitespace and quotes when tab completing
2380-
completer_delims = " \t\n" + ''.join(constants.QUOTES)
2390+
if self.allow_redirection:
2391+
# If redirection is allowed, then break words on those characters too
2392+
completer_delims += ''.join(constants.REDIRECTION_CHARS)
23812393

2382-
if self.allow_redirection:
2383-
# If redirection is allowed, then break words on those characters too
2384-
completer_delims += ''.join(constants.REDIRECTION_CHARS)
2394+
readline.set_completer_delims(completer_delims)
23852395

2386-
readline.set_completer_delims(completer_delims)
2396+
# Enable tab completion
2397+
readline.parse_and_bind(self.completekey + ": complete")
23872398

2388-
# Enable tab completion
2389-
readline.parse_and_bind(self.completekey + ": complete")
2390-
except NameError:
2391-
pass
23922399
stop = None
23932400
try:
23942401
while not stop:
@@ -2412,19 +2419,15 @@ def _cmdloop(self):
24122419
# Run the command along with all associated pre and post hooks
24132420
stop = self.onecmd_plus_hooks(line)
24142421
finally:
2415-
if self.use_rawinput and self.completekey:
2422+
if self.use_rawinput and self.completekey and rl_type != RlType.NONE:
24162423

24172424
# Restore what we changed in readline
2418-
try:
2419-
readline.set_completer(self.old_completer)
2420-
readline.set_completer_delims(self.old_delims)
2421-
except NameError:
2422-
pass
2425+
readline.set_completer(old_completer)
2426+
readline.set_completer_delims(old_delims)
24232427

24242428
if rl_type == RlType.GNU:
24252429
readline.set_completion_display_matches_hook(None)
2426-
rl_basic_quote_characters.value = orig_rl_basic_quote_characters_addr
2427-
2430+
basic_quote_characters.value = old_basic_quote_characters
24282431
elif rl_type == RlType.PYREADLINE:
24292432
readline.rl.mode._display_completions = orig_pyreadline_display
24302433

@@ -2770,7 +2773,7 @@ def show(self, args, parameter):
27702773
set_parser = ACArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
27712774
set_parser.add_argument('-a', '--all', action='store_true', help='display read-only settings as well')
27722775
set_parser.add_argument('-l', '--long', action='store_true', help='describe function of parameter')
2773-
set_parser.add_argument('settable', nargs=(0,2), help='[param_name] [value]')
2776+
set_parser.add_argument('settable', nargs=(0, 2), help='[param_name] [value]')
27742777

27752778
@with_argparser(set_parser)
27762779
def do_set(self, args):
@@ -2851,7 +2854,6 @@ def complete_shell(self, text, line, begidx, endidx):
28512854
index_dict = {1: self.shell_cmd_complete}
28522855
return self.index_based_complete(text, line, begidx, endidx, index_dict, self.path_complete)
28532856

2854-
28552857
# noinspection PyBroadException
28562858
def do_py(self, arg):
28572859
"""

0 commit comments

Comments
 (0)