1from __future__ import annotations 
    2 
    3import functools 
    4from asyncio import get_running_loop 
    5from typing import Any, Callable, Sequence, TypeVar 
    6 
    7from prompt_toolkit.application import Application 
    8from prompt_toolkit.application.current import get_app 
    9from prompt_toolkit.buffer import Buffer 
    10from prompt_toolkit.completion import Completer 
    11from prompt_toolkit.eventloop import run_in_executor_with_context 
    12from prompt_toolkit.filters import FilterOrBool 
    13from prompt_toolkit.formatted_text import AnyFormattedText 
    14from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous 
    15from prompt_toolkit.key_binding.defaults import load_key_bindings 
    16from prompt_toolkit.key_binding.key_bindings import KeyBindings, merge_key_bindings 
    17from prompt_toolkit.layout import Layout 
    18from prompt_toolkit.layout.containers import AnyContainer, HSplit 
    19from prompt_toolkit.layout.dimension import Dimension as D 
    20from prompt_toolkit.styles import BaseStyle 
    21from prompt_toolkit.validation import Validator 
    22from prompt_toolkit.widgets import ( 
    23    Box, 
    24    Button, 
    25    CheckboxList, 
    26    Dialog, 
    27    Label, 
    28    ProgressBar, 
    29    RadioList, 
    30    TextArea, 
    31    ValidationToolbar, 
    32) 
    33 
    34__all__ = [ 
    35    "yes_no_dialog", 
    36    "button_dialog", 
    37    "input_dialog", 
    38    "message_dialog", 
    39    "radiolist_dialog", 
    40    "checkboxlist_dialog", 
    41    "progress_dialog", 
    42] 
    43 
    44 
    45def yes_no_dialog( 
    46    title: AnyFormattedText = "", 
    47    text: AnyFormattedText = "", 
    48    yes_text: str = "Yes", 
    49    no_text: str = "No", 
    50    style: BaseStyle | None = None, 
    51) -> Application[bool]: 
    52    """ 
    53    Display a Yes/No dialog. 
    54    Return a boolean. 
    55    """ 
    56 
    57    def yes_handler() -> None: 
    58        get_app().exit(result=True) 
    59 
    60    def no_handler() -> None: 
    61        get_app().exit(result=False) 
    62 
    63    dialog = Dialog( 
    64        title=title, 
    65        body=Label(text=text, dont_extend_height=True), 
    66        buttons=[ 
    67            Button(text=yes_text, handler=yes_handler), 
    68            Button(text=no_text, handler=no_handler), 
    69        ], 
    70        with_background=True, 
    71    ) 
    72 
    73    return _create_app(dialog, style) 
    74 
    75 
    76_T = TypeVar("_T") 
    77 
    78 
    79def button_dialog( 
    80    title: AnyFormattedText = "", 
    81    text: AnyFormattedText = "", 
    82    buttons: list[tuple[str, _T]] = [], 
    83    style: BaseStyle | None = None, 
    84) -> Application[_T]: 
    85    """ 
    86    Display a dialog with button choices (given as a list of tuples). 
    87    Return the value associated with button. 
    88    """ 
    89 
    90    def button_handler(v: _T) -> None: 
    91        get_app().exit(result=v) 
    92 
    93    dialog = Dialog( 
    94        title=title, 
    95        body=Label(text=text, dont_extend_height=True), 
    96        buttons=[ 
    97            Button(text=t, handler=functools.partial(button_handler, v)) 
    98            for t, v in buttons 
    99        ], 
    100        with_background=True, 
    101    ) 
    102 
    103    return _create_app(dialog, style) 
    104 
    105 
    106def input_dialog( 
    107    title: AnyFormattedText = "", 
    108    text: AnyFormattedText = "", 
    109    ok_text: str = "OK", 
    110    cancel_text: str = "Cancel", 
    111    completer: Completer | None = None, 
    112    validator: Validator | None = None, 
    113    password: FilterOrBool = False, 
    114    style: BaseStyle | None = None, 
    115    default: str = "", 
    116) -> Application[str]: 
    117    """ 
    118    Display a text input box. 
    119    Return the given text, or None when cancelled. 
    120    """ 
    121 
    122    def accept(buf: Buffer) -> bool: 
    123        get_app().layout.focus(ok_button) 
    124        return True  # Keep text. 
    125 
    126    def ok_handler() -> None: 
    127        get_app().exit(result=textfield.text) 
    128 
    129    ok_button = Button(text=ok_text, handler=ok_handler) 
    130    cancel_button = Button(text=cancel_text, handler=_return_none) 
    131 
    132    textfield = TextArea( 
    133        text=default, 
    134        multiline=False, 
    135        password=password, 
    136        completer=completer, 
    137        validator=validator, 
    138        accept_handler=accept, 
    139    ) 
    140 
    141    dialog = Dialog( 
    142        title=title, 
    143        body=HSplit( 
    144            [ 
    145                Label(text=text, dont_extend_height=True), 
    146                textfield, 
    147                ValidationToolbar(), 
    148            ], 
    149            padding=D(preferred=1, max=1), 
    150        ), 
    151        buttons=[ok_button, cancel_button], 
    152        with_background=True, 
    153    ) 
    154 
    155    return _create_app(dialog, style) 
    156 
    157 
    158def message_dialog( 
    159    title: AnyFormattedText = "", 
    160    text: AnyFormattedText = "", 
    161    ok_text: str = "Ok", 
    162    style: BaseStyle | None = None, 
    163) -> Application[None]: 
    164    """ 
    165    Display a simple message box and wait until the user presses enter. 
    166    """ 
    167    dialog = Dialog( 
    168        title=title, 
    169        body=Label(text=text, dont_extend_height=True), 
    170        buttons=[Button(text=ok_text, handler=_return_none)], 
    171        with_background=True, 
    172    ) 
    173 
    174    return _create_app(dialog, style) 
    175 
    176 
    177def radiolist_dialog( 
    178    title: AnyFormattedText = "", 
    179    text: AnyFormattedText = "", 
    180    ok_text: str = "Ok", 
    181    cancel_text: str = "Cancel", 
    182    values: Sequence[tuple[_T, AnyFormattedText]] | None = None, 
    183    default: _T | None = None, 
    184    style: BaseStyle | None = None, 
    185) -> Application[_T]: 
    186    """ 
    187    Display a simple list of element the user can choose amongst. 
    188 
    189    Only one element can be selected at a time using Arrow keys and Enter. 
    190    The focus can be moved between the list and the Ok/Cancel button with tab. 
    191    """ 
    192    if values is None: 
    193        values = [] 
    194 
    195    def ok_handler() -> None: 
    196        get_app().exit(result=radio_list.current_value) 
    197 
    198    radio_list = RadioList(values=values, default=default) 
    199 
    200    dialog = Dialog( 
    201        title=title, 
    202        body=HSplit( 
    203            [Label(text=text, dont_extend_height=True), radio_list], 
    204            padding=1, 
    205        ), 
    206        buttons=[ 
    207            Button(text=ok_text, handler=ok_handler), 
    208            Button(text=cancel_text, handler=_return_none), 
    209        ], 
    210        with_background=True, 
    211    ) 
    212 
    213    return _create_app(dialog, style) 
    214 
    215 
    216def checkboxlist_dialog( 
    217    title: AnyFormattedText = "", 
    218    text: AnyFormattedText = "", 
    219    ok_text: str = "Ok", 
    220    cancel_text: str = "Cancel", 
    221    values: Sequence[tuple[_T, AnyFormattedText]] | None = None, 
    222    default_values: Sequence[_T] | None = None, 
    223    style: BaseStyle | None = None, 
    224) -> Application[list[_T]]: 
    225    """ 
    226    Display a simple list of element the user can choose multiple values amongst. 
    227 
    228    Several elements can be selected at a time using Arrow keys and Enter. 
    229    The focus can be moved between the list and the Ok/Cancel button with tab. 
    230    """ 
    231    if values is None: 
    232        values = [] 
    233 
    234    def ok_handler() -> None: 
    235        get_app().exit(result=cb_list.current_values) 
    236 
    237    cb_list = CheckboxList(values=values, default_values=default_values) 
    238 
    239    dialog = Dialog( 
    240        title=title, 
    241        body=HSplit( 
    242            [Label(text=text, dont_extend_height=True), cb_list], 
    243            padding=1, 
    244        ), 
    245        buttons=[ 
    246            Button(text=ok_text, handler=ok_handler), 
    247            Button(text=cancel_text, handler=_return_none), 
    248        ], 
    249        with_background=True, 
    250    ) 
    251 
    252    return _create_app(dialog, style) 
    253 
    254 
    255def progress_dialog( 
    256    title: AnyFormattedText = "", 
    257    text: AnyFormattedText = "", 
    258    run_callback: Callable[[Callable[[int], None], Callable[[str], None]], None] = ( 
    259        lambda *a: None 
    260    ), 
    261    style: BaseStyle | None = None, 
    262) -> Application[None]: 
    263    """ 
    264    :param run_callback: A function that receives as input a `set_percentage` 
    265        function and it does the work. 
    266    """ 
    267    loop = get_running_loop() 
    268    progressbar = ProgressBar() 
    269    text_area = TextArea( 
    270        focusable=False, 
    271        # Prefer this text area as big as possible, to avoid having a window 
    272        # that keeps resizing when we add text to it. 
    273        height=D(preferred=10**10), 
    274    ) 
    275 
    276    dialog = Dialog( 
    277        body=HSplit( 
    278            [ 
    279                Box(Label(text=text)), 
    280                Box(text_area, padding=D.exact(1)), 
    281                progressbar, 
    282            ] 
    283        ), 
    284        title=title, 
    285        with_background=True, 
    286    ) 
    287    app = _create_app(dialog, style) 
    288 
    289    def set_percentage(value: int) -> None: 
    290        progressbar.percentage = int(value) 
    291        app.invalidate() 
    292 
    293    def log_text(text: str) -> None: 
    294        loop.call_soon_threadsafe(text_area.buffer.insert_text, text) 
    295        app.invalidate() 
    296 
    297    # Run the callback in the executor. When done, set a return value for the 
    298    # UI, so that it quits. 
    299    def start() -> None: 
    300        try: 
    301            run_callback(set_percentage, log_text) 
    302        finally: 
    303            app.exit() 
    304 
    305    def pre_run() -> None: 
    306        run_in_executor_with_context(start) 
    307 
    308    app.pre_run_callables.append(pre_run) 
    309 
    310    return app 
    311 
    312 
    313def _create_app(dialog: AnyContainer, style: BaseStyle | None) -> Application[Any]: 
    314    # Key bindings. 
    315    bindings = KeyBindings() 
    316    bindings.add("tab")(focus_next) 
    317    bindings.add("s-tab")(focus_previous) 
    318 
    319    return Application( 
    320        layout=Layout(dialog), 
    321        key_bindings=merge_key_bindings([load_key_bindings(), bindings]), 
    322        mouse_support=True, 
    323        style=style, 
    324        full_screen=True, 
    325    ) 
    326 
    327 
    328def _return_none() -> None: 
    329    "Button handler that returns None." 
    330    get_app().exit()