"""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