diff --git a/examples/list_search.py b/examples/list_search.py new file mode 100644 index 00000000..d88c24b2 --- /dev/null +++ b/examples/list_search.py @@ -0,0 +1,33 @@ +import os +import sys +from pprint import pprint + + +sys.path.append(os.path.realpath(".")) +import inquirer # noqa +from readchar import key + + +# To make the search case-insensitive +def matcher(choices, pressedKey, searchString): + if pressedKey == key.BACKSPACE: + searchString = searchString[:-1] + elif pressedKey.isprintable(): + searchString += pressedKey + for i in range(len(choices)): + if choices[i].lower().startswith(searchString.lower()): + return (i, searchString) + return (0, searchString) + + +questions = [ + inquirer.List( + "size", message="What size do you need?", + choices=["Jumbo", "Large", "Standard"], + carousel=True, matcher=matcher + ), +] + +answers = inquirer.prompt(questions) + +pprint(answers) diff --git a/src/inquirer/questions.py b/src/inquirer/questions.py index e1fb427e..a93c8537 100644 --- a/src/inquirer/questions.py +++ b/src/inquirer/questions.py @@ -159,10 +159,12 @@ def __init__( carousel=False, other=False, autocomplete=None, + matcher=None ): super().__init__(name, message, choices, default, ignore, validate, hints=hints, other=other) self.carousel = carousel self.autocomplete = autocomplete + self.matcher = matcher class Checkbox(Question): diff --git a/src/inquirer/render/console/_list.py b/src/inquirer/render/console/_list.py index 5cb48af3..c1ae947c 100644 --- a/src/inquirer/render/console/_list.py +++ b/src/inquirer/render/console/_list.py @@ -11,12 +11,16 @@ class List(BaseConsoleRender): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.current = self._current_index() + self.search = "" @property def is_long(self): choices = self.question.choices or [] return len(choices) >= MAX_OPTIONS_DISPLAYED_AT_ONCE + def get_current_value(self): + return self.search + def get_hint(self): try: choice = self.question.choices[self.current] @@ -90,6 +94,11 @@ def process_input(self, pressed): raise errors.EndOfInput(getattr(value, "value", value)) + if self.question.matcher is not None: + (index, search) = self.question.matcher(self.question.choices, pressed, self.search) + self.search = search + self.current = index + if pressed == key.CTRL_C: raise KeyboardInterrupt() diff --git a/tests/integration/console_render/test_list.py b/tests/integration/console_render/test_list.py index 7cd3dea7..7f1ea692 100644 --- a/tests/integration/console_render/test_list.py +++ b/tests/integration/console_render/test_list.py @@ -132,6 +132,30 @@ def test_ctrl_c_breaks_execution(self): with pytest.raises(KeyboardInterrupt): sut.render(question) + def test_type_char(self): + stdin = helper.event_factory('b', 'A', 'z', key.ENTER) + message = "Foo message" + variable = "Bar variable" + choices = ["foo", "bar", "bazz"] + + # To make the search case-insensitive + def matcher(choices, pressedKey, searchString): + if pressedKey == key.BACKSPACE: + searchString = searchString[:-1] + elif pressedKey.isprintable(): + searchString += pressedKey + for i in range(len(choices)): + if choices[i].lower().startswith(searchString.lower()): + return (i, searchString) + return (0, searchString) + + question = questions.List(variable, message, choices=choices, carousel=True, matcher=matcher) + + sut = ConsoleRender(event_generator=stdin) + result = sut.render(question) + + assert result == "bazz" + def test_first_hint_is_shown(self): stdin = helper.event_factory(key.ENTER) message = "Foo message"