radubulimac's picture
fix import issue
2d876d1
"""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]}],
}
@property
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
@content_control.setter
def content_control(self, value: InquirerPyUIListControl) -> None:
self._content_control = value
@property
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 ""
@property
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 ""
@property
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
@abstractmethod
def _handle_toggle_choice(self, event) -> None:
"""Handle event when user attempting to toggle the state of the chocie."""
pass
@abstractmethod
def _handle_toggle_all(self, event, value: bool) -> None:
"""Handle event when user attempting to alter the state of all choices."""
pass