diff options
-rwxr-xr-x | cmd2/cmd2.py | 44 | ||||
-rwxr-xr-x | tests/test_cmd2.py | 17 |
2 files changed, 56 insertions, 5 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index d82e58c9..a7cd8fac 100755 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -684,7 +684,7 @@ class Cmd(cmd.Cmd): final_msg = ansi.style_error(final_msg) if not self.debug: - warning = "\nTo enable full traceback, run the following command: 'set debug true'" + warning = "\nTo enable full traceback, run the following command: 'set debug true'" final_msg += ansi.style_warning(warning) # Set apply_style to False since style has already been applied @@ -2890,6 +2890,28 @@ class Cmd(cmd.Cmd): | a list of tuples -> interpreted as (value, text), so that the return value can differ from the text advertised to the user """ + + completion_disabled = False + orig_completer = None + + def disable_completion(): + """Turn off completion during the select input line""" + nonlocal orig_completer + nonlocal completion_disabled + + if rl_type != RlType.NONE and not completion_disabled: + orig_completer = readline.get_completer() + readline.set_completer(lambda *args, **kwargs: None) + completion_disabled = True + + def enable_completion(): + """Restore tab completion when select is done reading input""" + nonlocal completion_disabled + + if rl_type != RlType.NONE and completion_disabled: + readline.set_completer(orig_completer) + completion_disabled = False + local_opts = opts if isinstance(opts, str): local_opts = list(zip(opts.split(), opts.split())) @@ -2904,15 +2926,28 @@ class Cmd(cmd.Cmd): fulloptions.append((opt[0], opt[0])) for (idx, (_, text)) in enumerate(fulloptions): self.poutput(' %2d. %s' % (idx + 1, text)) + while True: safe_prompt = rl_make_safe_prompt(prompt) - response = input(safe_prompt) + + try: + with self.sigint_protection: + disable_completion() + response = input(safe_prompt) + except EOFError: + response = '' + self.poutput('\n', end='') + finally: + with self.sigint_protection: + enable_completion() + + if not response: + continue if rl_type != RlType.NONE: hlen = readline.get_current_history_length() - if hlen >= 1 and response != '': + if hlen >= 1: readline.remove_history_item(hlen - 1) - try: choice = int(response) if choice < 1: @@ -2922,6 +2957,7 @@ class Cmd(cmd.Cmd): except (ValueError, IndexError): self.poutput("{!r} isn't a valid choice. Pick a number between 1 and {}:".format( response, len(fulloptions))) + return result def _get_read_only_settings(self) -> str: diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 1ba10f6b..ae02f16b 100755 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -639,7 +639,7 @@ def _expected_no_editor_error(): expected_text = normalize(""" EXCEPTION of type '{}' occurred with message: 'Please use 'set editor' to specify your text editing program of choice.' -To enable full traceback, run the following command: 'set debug true' +To enable full traceback, run the following command: 'set debug true' """.format(expected_exception)) return expected_text @@ -1098,6 +1098,7 @@ def test_select_invalid_option_too_big(select_app): arg = 'Sauce? ' calls = [mock.call(arg), mock.call(arg)] m.assert_has_calls(calls) + assert m.call_count == 2 # And verify the expected output to stdout assert out == expected @@ -1122,6 +1123,7 @@ def test_select_invalid_option_too_small(select_app): arg = 'Sauce? ' calls = [mock.call(arg), mock.call(arg)] m.assert_has_calls(calls) + assert m.call_count == 2 # And verify the expected output to stdout assert out == expected @@ -1181,6 +1183,19 @@ Charm us with the {}... # And verify the expected output to stdout assert out == expected +def test_select_eof(select_app): + # Ctrl-D during select causes an EOFError that just reprompts the user + m = mock.MagicMock(name='input', side_effect=[EOFError, 2]) + builtins.input = m + + food = 'fish' + out, err = run_cmd(select_app, "eat {}".format(food)) + + # Make sure our mock was called exactly twice with the expected arguments + arg = 'Sauce? ' + calls = [mock.call(arg), mock.call(arg)] + m.assert_has_calls(calls) + assert m.call_count == 2 class HelpNoDocstringApp(cmd2.Cmd): greet_parser = argparse.ArgumentParser() |