| from __future__ import annotations |
|
|
| from typing import Generic, Sequence, TypeVar |
|
|
| from prompt_toolkit.application import Application |
| from prompt_toolkit.filters import ( |
| Condition, |
| FilterOrBool, |
| is_done, |
| renderer_height_is_known, |
| to_filter, |
| ) |
| from prompt_toolkit.formatted_text import AnyFormattedText |
| from prompt_toolkit.key_binding.key_bindings import ( |
| DynamicKeyBindings, |
| KeyBindings, |
| KeyBindingsBase, |
| merge_key_bindings, |
| ) |
| from prompt_toolkit.key_binding.key_processor import KeyPressEvent |
| from prompt_toolkit.layout import ( |
| AnyContainer, |
| ConditionalContainer, |
| HSplit, |
| Layout, |
| Window, |
| ) |
| from prompt_toolkit.layout.controls import FormattedTextControl |
| from prompt_toolkit.layout.dimension import Dimension |
| from prompt_toolkit.styles import BaseStyle, Style |
| from prompt_toolkit.utils import suspend_to_background_supported |
| from prompt_toolkit.widgets import Box, Frame, Label, RadioList |
|
|
| __all__ = [ |
| "ChoiceInput", |
| "choice", |
| ] |
|
|
| _T = TypeVar("_T") |
| E = KeyPressEvent |
|
|
|
|
| def create_default_choice_input_style() -> BaseStyle: |
| return Style.from_dict( |
| { |
| "frame.border": "#884444", |
| "selected-option": "bold", |
| } |
| ) |
|
|
|
|
| class ChoiceInput(Generic[_T]): |
| """ |
| Input selection prompt. Ask the user to choose among a set of options. |
| |
| Example usage:: |
| |
| input_selection = ChoiceInput( |
| message="Please select a dish:", |
| options=[ |
| ("pizza", "Pizza with mushrooms"), |
| ("salad", "Salad with tomatoes"), |
| ("sushi", "Sushi"), |
| ], |
| default="pizza", |
| ) |
| result = input_selection.prompt() |
| |
| :param message: Plain text or formatted text to be shown before the options. |
| :param options: Sequence of ``(value, label)`` tuples. The labels can be |
| formatted text. |
| :param default: Default value. If none is given, the first option is |
| considered the default. |
| :param mouse_support: Enable mouse support. |
| :param style: :class:`.Style` instance for the color scheme. |
| :param symbol: Symbol to be displayed in front of the selected choice. |
| :param bottom_toolbar: Formatted text or callable that returns formatted |
| text to be displayed at the bottom of the screen. |
| :param show_frame: `bool` or |
| :class:`~prompt_toolkit.filters.Filter`. When True, surround the input |
| with a frame. |
| :param enable_interrupt: `bool` or |
| :class:`~prompt_toolkit.filters.Filter`. When True, raise |
| the ``interrupt_exception`` (``KeyboardInterrupt`` by default) when |
| control-c has been pressed. |
| :param interrupt_exception: The exception type that will be raised when |
| there is a keyboard interrupt (control-c keypress). |
| """ |
|
|
| def __init__( |
| self, |
| *, |
| message: AnyFormattedText, |
| options: Sequence[tuple[_T, AnyFormattedText]], |
| default: _T | None = None, |
| mouse_support: bool = False, |
| style: BaseStyle | None = None, |
| symbol: str = ">", |
| bottom_toolbar: AnyFormattedText = None, |
| show_frame: FilterOrBool = False, |
| enable_suspend: FilterOrBool = False, |
| enable_interrupt: FilterOrBool = True, |
| interrupt_exception: type[BaseException] = KeyboardInterrupt, |
| key_bindings: KeyBindingsBase | None = None, |
| ) -> None: |
| if style is None: |
| style = create_default_choice_input_style() |
|
|
| self.message = message |
| self.default = default |
| self.options = options |
| self.mouse_support = mouse_support |
| self.style = style |
| self.symbol = symbol |
| self.show_frame = show_frame |
| self.enable_suspend = enable_suspend |
| self.interrupt_exception = interrupt_exception |
| self.enable_interrupt = enable_interrupt |
| self.bottom_toolbar = bottom_toolbar |
| self.key_bindings = key_bindings |
|
|
| def _create_application(self) -> Application[_T]: |
| radio_list = RadioList( |
| values=self.options, |
| default=self.default, |
| select_on_focus=True, |
| open_character="", |
| select_character=self.symbol, |
| close_character="", |
| show_cursor=False, |
| show_numbers=True, |
| container_style="class:input-selection", |
| default_style="class:option", |
| selected_style="", |
| checked_style="class:selected-option", |
| number_style="class:number", |
| show_scrollbar=False, |
| ) |
| container: AnyContainer = HSplit( |
| [ |
| Box( |
| Label(text=self.message, dont_extend_height=True), |
| padding_top=0, |
| padding_left=1, |
| padding_right=1, |
| padding_bottom=0, |
| ), |
| Box( |
| radio_list, |
| padding_top=0, |
| padding_left=3, |
| padding_right=1, |
| padding_bottom=0, |
| ), |
| ] |
| ) |
|
|
| @Condition |
| def show_frame_filter() -> bool: |
| return to_filter(self.show_frame)() |
|
|
| show_bottom_toolbar = ( |
| Condition(lambda: self.bottom_toolbar is not None) |
| & ~is_done |
| & renderer_height_is_known |
| ) |
|
|
| container = ConditionalContainer( |
| Frame(container), |
| alternative_content=container, |
| filter=show_frame_filter, |
| ) |
|
|
| bottom_toolbar = ConditionalContainer( |
| Window( |
| FormattedTextControl( |
| lambda: self.bottom_toolbar, style="class:bottom-toolbar.text" |
| ), |
| style="class:bottom-toolbar", |
| dont_extend_height=True, |
| height=Dimension(min=1), |
| ), |
| filter=show_bottom_toolbar, |
| ) |
|
|
| layout = Layout( |
| HSplit( |
| [ |
| container, |
| |
| |
| |
| |
| ConditionalContainer(Window(), filter=show_bottom_toolbar), |
| bottom_toolbar, |
| ] |
| ), |
| focused_element=radio_list, |
| ) |
|
|
| kb = KeyBindings() |
|
|
| @kb.add("enter", eager=True) |
| def _accept_input(event: E) -> None: |
| "Accept input when enter has been pressed." |
| event.app.exit(result=radio_list.current_value, style="class:accepted") |
|
|
| @Condition |
| def enable_interrupt() -> bool: |
| return to_filter(self.enable_interrupt)() |
|
|
| @kb.add("c-c", filter=enable_interrupt) |
| @kb.add("<sigint>", filter=enable_interrupt) |
| def _keyboard_interrupt(event: E) -> None: |
| "Abort when Control-C has been pressed." |
| event.app.exit(exception=self.interrupt_exception(), style="class:aborting") |
|
|
| suspend_supported = Condition(suspend_to_background_supported) |
|
|
| @Condition |
| def enable_suspend() -> bool: |
| return to_filter(self.enable_suspend)() |
|
|
| @kb.add("c-z", filter=suspend_supported & enable_suspend) |
| def _suspend(event: E) -> None: |
| """ |
| Suspend process to background. |
| """ |
| event.app.suspend_to_background() |
|
|
| return Application( |
| layout=layout, |
| full_screen=False, |
| mouse_support=self.mouse_support, |
| key_bindings=merge_key_bindings( |
| [kb, DynamicKeyBindings(lambda: self.key_bindings)] |
| ), |
| style=self.style, |
| ) |
|
|
| def prompt(self) -> _T: |
| return self._create_application().run() |
|
|
| async def prompt_async(self) -> _T: |
| return await self._create_application().run_async() |
|
|
|
|
| def choice( |
| message: AnyFormattedText, |
| *, |
| options: Sequence[tuple[_T, AnyFormattedText]], |
| default: _T | None = None, |
| mouse_support: bool = False, |
| style: BaseStyle | None = None, |
| symbol: str = ">", |
| bottom_toolbar: AnyFormattedText = None, |
| show_frame: bool = False, |
| enable_suspend: FilterOrBool = False, |
| enable_interrupt: FilterOrBool = True, |
| interrupt_exception: type[BaseException] = KeyboardInterrupt, |
| key_bindings: KeyBindingsBase | None = None, |
| ) -> _T: |
| """ |
| Choice selection prompt. Ask the user to choose among a set of options. |
| |
| Example usage:: |
| |
| result = choice( |
| message="Please select a dish:", |
| options=[ |
| ("pizza", "Pizza with mushrooms"), |
| ("salad", "Salad with tomatoes"), |
| ("sushi", "Sushi"), |
| ], |
| default="pizza", |
| ) |
| |
| :param message: Plain text or formatted text to be shown before the options. |
| :param options: Sequence of ``(value, label)`` tuples. The labels can be |
| formatted text. |
| :param default: Default value. If none is given, the first option is |
| considered the default. |
| :param mouse_support: Enable mouse support. |
| :param style: :class:`.Style` instance for the color scheme. |
| :param symbol: Symbol to be displayed in front of the selected choice. |
| :param bottom_toolbar: Formatted text or callable that returns formatted |
| text to be displayed at the bottom of the screen. |
| :param show_frame: `bool` or |
| :class:`~prompt_toolkit.filters.Filter`. When True, surround the input |
| with a frame. |
| :param enable_interrupt: `bool` or |
| :class:`~prompt_toolkit.filters.Filter`. When True, raise |
| the ``interrupt_exception`` (``KeyboardInterrupt`` by default) when |
| control-c has been pressed. |
| :param interrupt_exception: The exception type that will be raised when |
| there is a keyboard interrupt (control-c keypress). |
| """ |
| return ChoiceInput[_T]( |
| message=message, |
| options=options, |
| default=default, |
| mouse_support=mouse_support, |
| style=style, |
| symbol=symbol, |
| bottom_toolbar=bottom_toolbar, |
| show_frame=show_frame, |
| enable_suspend=enable_suspend, |
| enable_interrupt=enable_interrupt, |
| interrupt_exception=interrupt_exception, |
| key_bindings=key_bindings, |
| ).prompt() |
|
|