diff --git a/questionary/prompts/common.py b/questionary/prompts/common.py index 827629db..ad0fca21 100644 --- a/questionary/prompts/common.py +++ b/questionary/prompts/common.py @@ -69,8 +69,7 @@ class Choice: checked: Optional[bool] """Whether the choice is initially selected""" - shortcut_key: Optional[str] - """A shortcut key for the choice""" + __shortcut_key: Optional[Union[str, bool]] description: Optional[str] """Choice description""" @@ -86,6 +85,8 @@ def __init__( ) -> None: self.disabled = disabled self.title = title + self.shortcut_key = shortcut_key + # self.auto_shortcut is set by the self.shortcut_key setter self.checked = checked if checked is not None else False self.description = description @@ -96,17 +97,6 @@ def __init__( else: self.value = title - if shortcut_key is not None: - if isinstance(shortcut_key, bool): - self.auto_shortcut = shortcut_key - self.shortcut_key = None - else: - self.shortcut_key = str(shortcut_key) - self.auto_shortcut = False - else: - self.shortcut_key = None - self.auto_shortcut = True - @staticmethod def build(c: Union[str, "Choice", Dict[str, Any]]) -> "Choice": """Create a choice object from different representations. @@ -134,12 +124,54 @@ def build(c: Union[str, "Choice", Dict[str, Any]]) -> "Choice": c.get("description", None), ) + @property + def shortcut_key(self) -> Optional[Union[str, bool]]: + """A shortcut key for the choice""" + return self.__shortcut_key + + @shortcut_key.setter + def shortcut_key(self, key: Optional[Union[str, bool]]): + if key is not None: + if isinstance(key, bool): + self.__auto_shortcut = key + self.__shortcut_key = None + else: + self.__shortcut_key = str(key) + self.__auto_shortcut = False + else: + self.__shortcut_key = None + self.__auto_shortcut = True + + @shortcut_key.deleter + def shortcut_key(self): + self.__shortcut_key = None + self.__auto_shortcut = True + def get_shortcut_title(self): if self.shortcut_key is None: return "-) " else: return "{}) ".format(self.shortcut_key) + @property + def auto_shortcut(self) -> bool: + """Whether to assign a shortcut key to the choice + + Keys are assigned starting with numbers and proceeding + through the ASCII alphabet. + """ + return self.__auto_shortcut + + @auto_shortcut.setter + def auto_shortcut(self, should_assign: bool): + self.__auto_shortcut = should_assign + if self.__auto_shortcut: + self.__shortcut_key = None + + @auto_shortcut.deleter + def auto_shortcut(self): + self.__auto_shortcut = False + class Separator(Choice): """Used to space/separate choices group.""" diff --git a/tests/prompts/test_select.py b/tests/prompts/test_select.py index 1d3eaa57..c4b9221d 100644 --- a/tests/prompts/test_select.py +++ b/tests/prompts/test_select.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from copy import copy + import pytest from questionary import Choice @@ -199,6 +201,29 @@ def test_allow_shortcut_key_with_True(): assert result == "bazz" +def test_auto_shortcut_key_stable_in_loop(): + message = "Foo message" + choices = [ + Choice("foo"), + Choice("bar"), + ] + kwargs = { + "choices": choices, + "use_shortcuts": True, + } + text = "\r" + + result, cli = feed_cli_with_input("select", message, text, **kwargs) + assert result == "foo" + result_shortcut_keys = [copy(c.shortcut_key) for c in choices] + result2, cli = feed_cli_with_input("select", message, text, **kwargs) + assert result2 == "foo" + result2_shortcut_keys = [copy(c.shortcut_key) for c in choices] + assert ( + result_shortcut_keys == result2_shortcut_keys + ), "Shortcut keys changed across two runs of 'select'" + + def test_select_initial_choice_with_value(): message = "Foo message" choice = Choice(title="bazz", value="bar")