Skip to content

Commit cc3d5cd

Browse files
authored
Merge pull request #754 from python-cmd2/no_tab_select
Disabled tab completion during a select call
2 parents 89a5a64 + a14c63d commit cc3d5cd

File tree

2 files changed

+56
-5
lines changed

2 files changed

+56
-5
lines changed

cmd2/cmd2.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ def pexcept(self, msg: Any, *, end: str = '\n', apply_style: bool = True) -> Non
684684
final_msg = ansi.style_error(final_msg)
685685

686686
if not self.debug:
687-
warning = "\nTo enable full traceback, run the following command: 'set debug true'"
687+
warning = "\nTo enable full traceback, run the following command: 'set debug true'"
688688
final_msg += ansi.style_warning(warning)
689689

690690
# Set apply_style to False since style has already been applied
@@ -2890,6 +2890,28 @@ def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]],
28902890
| a list of tuples -> interpreted as (value, text), so
28912891
that the return value can differ from
28922892
the text advertised to the user """
2893+
2894+
completion_disabled = False
2895+
orig_completer = None
2896+
2897+
def disable_completion():
2898+
"""Turn off completion during the select input line"""
2899+
nonlocal orig_completer
2900+
nonlocal completion_disabled
2901+
2902+
if rl_type != RlType.NONE and not completion_disabled:
2903+
orig_completer = readline.get_completer()
2904+
readline.set_completer(lambda *args, **kwargs: None)
2905+
completion_disabled = True
2906+
2907+
def enable_completion():
2908+
"""Restore tab completion when select is done reading input"""
2909+
nonlocal completion_disabled
2910+
2911+
if rl_type != RlType.NONE and completion_disabled:
2912+
readline.set_completer(orig_completer)
2913+
completion_disabled = False
2914+
28932915
local_opts = opts
28942916
if isinstance(opts, str):
28952917
local_opts = list(zip(opts.split(), opts.split()))
@@ -2904,15 +2926,28 @@ def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]],
29042926
fulloptions.append((opt[0], opt[0]))
29052927
for (idx, (_, text)) in enumerate(fulloptions):
29062928
self.poutput(' %2d. %s' % (idx + 1, text))
2929+
29072930
while True:
29082931
safe_prompt = rl_make_safe_prompt(prompt)
2909-
response = input(safe_prompt)
2932+
2933+
try:
2934+
with self.sigint_protection:
2935+
disable_completion()
2936+
response = input(safe_prompt)
2937+
except EOFError:
2938+
response = ''
2939+
self.poutput('\n', end='')
2940+
finally:
2941+
with self.sigint_protection:
2942+
enable_completion()
2943+
2944+
if not response:
2945+
continue
29102946

29112947
if rl_type != RlType.NONE:
29122948
hlen = readline.get_current_history_length()
2913-
if hlen >= 1 and response != '':
2949+
if hlen >= 1:
29142950
readline.remove_history_item(hlen - 1)
2915-
29162951
try:
29172952
choice = int(response)
29182953
if choice < 1:
@@ -2922,6 +2957,7 @@ def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]],
29222957
except (ValueError, IndexError):
29232958
self.poutput("{!r} isn't a valid choice. Pick a number between 1 and {}:".format(
29242959
response, len(fulloptions)))
2960+
29252961
return result
29262962

29272963
def _get_read_only_settings(self) -> str:

tests/test_cmd2.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ def _expected_no_editor_error():
639639

640640
expected_text = normalize("""
641641
EXCEPTION of type '{}' occurred with message: 'Please use 'set editor' to specify your text editing program of choice.'
642-
To enable full traceback, run the following command: 'set debug true'
642+
To enable full traceback, run the following command: 'set debug true'
643643
""".format(expected_exception))
644644

645645
return expected_text
@@ -1098,6 +1098,7 @@ def test_select_invalid_option_too_big(select_app):
10981098
arg = 'Sauce? '
10991099
calls = [mock.call(arg), mock.call(arg)]
11001100
m.assert_has_calls(calls)
1101+
assert m.call_count == 2
11011102

11021103
# And verify the expected output to stdout
11031104
assert out == expected
@@ -1122,6 +1123,7 @@ def test_select_invalid_option_too_small(select_app):
11221123
arg = 'Sauce? '
11231124
calls = [mock.call(arg), mock.call(arg)]
11241125
m.assert_has_calls(calls)
1126+
assert m.call_count == 2
11251127

11261128
# And verify the expected output to stdout
11271129
assert out == expected
@@ -1181,6 +1183,19 @@ def test_select_uneven_list_of_tuples(select_app):
11811183
# And verify the expected output to stdout
11821184
assert out == expected
11831185

1186+
def test_select_eof(select_app):
1187+
# Ctrl-D during select causes an EOFError that just reprompts the user
1188+
m = mock.MagicMock(name='input', side_effect=[EOFError, 2])
1189+
builtins.input = m
1190+
1191+
food = 'fish'
1192+
out, err = run_cmd(select_app, "eat {}".format(food))
1193+
1194+
# Make sure our mock was called exactly twice with the expected arguments
1195+
arg = 'Sauce? '
1196+
calls = [mock.call(arg), mock.call(arg)]
1197+
m.assert_has_calls(calls)
1198+
assert m.call_count == 2
11841199

11851200
class HelpNoDocstringApp(cmd2.Cmd):
11861201
greet_parser = argparse.ArgumentParser()

0 commit comments

Comments
 (0)