Spaces:
Sleeping
Sleeping
"""Contains the base class :class:`.BaseListPrompt` which can be used to create a prompt involving choices.""" | |
from abc import abstractmethod | |
from typing import Any, Callable, List, Optional | |
from prompt_toolkit.filters.base import Condition | |
from prompt_toolkit.keys import Keys | |
from InquirerPy.base.complex import BaseComplexPrompt | |
from InquirerPy.base.control import InquirerPyUIListControl | |
from InquirerPy.separator import Separator | |
from InquirerPy.utils import ( | |
InquirerPyKeybindings, | |
InquirerPyMessage, | |
InquirerPySessionResult, | |
InquirerPyStyle, | |
InquirerPyValidate, | |
) | |
class BaseListPrompt(BaseComplexPrompt): | |
"""A base class to create a complex prompt involving choice selections (i.e. list) using `prompt_toolkit` Application. | |
Note: | |
This class does not create :class:`~prompt_toolkit.layout.Layout` nor :class:`~prompt_toolkit.application.Application`, | |
it only contains the necessary attributes and helper functions to be consumed. | |
See Also: | |
:class:`~InquirerPy.prompts.list.ListPrompt` | |
:class:`~InquirerPy.prompts.fuzzy.FuzzyPrompt` | |
""" | |
def __init__( | |
self, | |
message: InquirerPyMessage, | |
style: Optional[InquirerPyStyle] = None, | |
vi_mode: bool = False, | |
qmark: str = "?", | |
amark: str = "?", | |
instruction: str = "", | |
long_instruction: str = "", | |
border: bool = False, | |
transformer: Optional[Callable[[Any], Any]] = None, | |
filter: Optional[Callable[[Any], Any]] = None, | |
validate: Optional[InquirerPyValidate] = None, | |
invalid_message: str = "Invalid input", | |
multiselect: bool = False, | |
keybindings: Optional[InquirerPyKeybindings] = None, | |
cycle: bool = True, | |
wrap_lines: bool = True, | |
raise_keyboard_interrupt: bool = True, | |
mandatory: bool = True, | |
mandatory_message: str = "Mandatory prompt", | |
session_result: Optional[InquirerPySessionResult] = None, | |
) -> None: | |
super().__init__( | |
message=message, | |
style=style, | |
border=border, | |
vi_mode=vi_mode, | |
qmark=qmark, | |
amark=amark, | |
transformer=transformer, | |
filter=filter, | |
invalid_message=invalid_message, | |
validate=validate, | |
instruction=instruction, | |
long_instruction=long_instruction, | |
wrap_lines=wrap_lines, | |
raise_keyboard_interrupt=raise_keyboard_interrupt, | |
mandatory=mandatory, | |
mandatory_message=mandatory_message, | |
session_result=session_result, | |
) | |
self._content_control: InquirerPyUIListControl | |
self._multiselect = multiselect | |
self._is_multiselect = Condition(lambda: self._multiselect) | |
self._cycle = cycle | |
if not keybindings: | |
keybindings = {} | |
self.kb_maps = { | |
"down": [ | |
{"key": "down"}, | |
{"key": "c-n", "filter": ~self._is_vim_edit}, | |
{"key": "j", "filter": self._is_vim_edit}, | |
], | |
"up": [ | |
{"key": "up"}, | |
{"key": "c-p", "filter": ~self._is_vim_edit}, | |
{"key": "k", "filter": self._is_vim_edit}, | |
], | |
"toggle": [ | |
{"key": "space"}, | |
], | |
"toggle-down": [ | |
{"key": Keys.Tab}, | |
], | |
"toggle-up": [ | |
{"key": Keys.BackTab}, | |
], | |
"toggle-all": [ | |
{"key": "alt-r"}, | |
{"key": "c-r"}, | |
], | |
"toggle-all-true": [ | |
{"key": "alt-a"}, | |
{"key": "c-a"}, | |
], | |
"toggle-all-false": [], | |
**keybindings, | |
} | |
self.kb_func_lookup = { | |
"down": [{"func": self._handle_down}], | |
"up": [{"func": self._handle_up}], | |
"toggle": [{"func": self._handle_toggle_choice}], | |
"toggle-down": [ | |
{"func": self._handle_toggle_choice}, | |
{"func": self._handle_down}, | |
], | |
"toggle-up": [ | |
{"func": self._handle_toggle_choice}, | |
{"func": self._handle_up}, | |
], | |
"toggle-all": [{"func": self._handle_toggle_all}], | |
"toggle-all-true": [{"func": self._handle_toggle_all, "args": [True]}], | |
"toggle-all-false": [{"func": self._handle_toggle_all, "args": [False]}], | |
} | |
def content_control(self) -> InquirerPyUIListControl: | |
"""Get the content controller object. | |
Needs to be an instance of :class:`~InquirerPy.base.control.InquirerPyUIListControl`. | |
Each :class:`.BaseComplexPrompt` requires a `content_control` to display custom | |
contents for the prompt. | |
Raises: | |
NotImplementedError: When `self._content_control` is not found. | |
""" | |
if not self._content_control: | |
raise NotImplementedError | |
return self._content_control | |
def content_control(self, value: InquirerPyUIListControl) -> None: | |
self._content_control = value | |
def result_name(self) -> Any: | |
"""Get the result value that should be printed to the terminal. | |
In multiselect scenario, return result as a list. | |
""" | |
if self._multiselect: | |
return [choice["name"] for choice in self.selected_choices] | |
else: | |
try: | |
return self.content_control.selection["name"] | |
except IndexError: | |
return "" | |
def result_value(self) -> Any: | |
"""Get the result value that should return to the user. | |
In multiselect scenario, return result as a list. | |
""" | |
if self._multiselect: | |
return [choice["value"] for choice in self.selected_choices] | |
else: | |
try: | |
return self.content_control.selection["value"] | |
except IndexError: | |
return "" | |
def selected_choices(self) -> List[Any]: | |
"""List[Any]: Get all user selected choices.""" | |
def filter_choice(choice): | |
return not isinstance(choice, Separator) and choice["enabled"] | |
return list(filter(filter_choice, self.content_control.choices)) | |
def _handle_down(self, _) -> bool: | |
"""Handle event when user attempts to move down. | |
Returns: | |
Boolean indicating if the action hits the cap. | |
""" | |
if self._cycle: | |
self.content_control.selected_choice_index = ( | |
self.content_control.selected_choice_index + 1 | |
) % self.content_control.choice_count | |
return False | |
else: | |
self.content_control.selected_choice_index += 1 | |
if ( | |
self.content_control.selected_choice_index | |
>= self.content_control.choice_count | |
): | |
self.content_control.selected_choice_index = ( | |
self.content_control.choice_count - 1 | |
) | |
return True | |
return False | |
def _handle_up(self, _) -> bool: | |
"""Handle event when user attempts to move up. | |
Returns: | |
Boolean indicating if the action hits the cap. | |
""" | |
if self._cycle: | |
self.content_control.selected_choice_index = ( | |
self.content_control.selected_choice_index - 1 | |
) % self.content_control.choice_count | |
return False | |
else: | |
self.content_control.selected_choice_index -= 1 | |
if self.content_control.selected_choice_index < 0: | |
self.content_control.selected_choice_index = 0 | |
return True | |
return False | |
def _handle_toggle_choice(self, event) -> None: | |
"""Handle event when user attempting to toggle the state of the chocie.""" | |
pass | |
def _handle_toggle_all(self, event, value: bool) -> None: | |
"""Handle event when user attempting to alter the state of all choices.""" | |
pass | |