123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- from __future__ import annotations
- import functools
- from asyncio import get_running_loop
- from typing import Any, Callable, Sequence, TypeVar
- from prompt_toolkit.application import Application
- from prompt_toolkit.application.current import get_app
- from prompt_toolkit.buffer import Buffer
- from prompt_toolkit.completion import Completer
- from prompt_toolkit.eventloop import run_in_executor_with_context
- from prompt_toolkit.filters import FilterOrBool
- from prompt_toolkit.formatted_text import AnyFormattedText
- from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous
- from prompt_toolkit.key_binding.defaults import load_key_bindings
- from prompt_toolkit.key_binding.key_bindings import KeyBindings, merge_key_bindings
- from prompt_toolkit.layout import Layout
- from prompt_toolkit.layout.containers import AnyContainer, HSplit
- from prompt_toolkit.layout.dimension import Dimension as D
- from prompt_toolkit.styles import BaseStyle
- from prompt_toolkit.validation import Validator
- from prompt_toolkit.widgets import (
- Box,
- Button,
- CheckboxList,
- Dialog,
- Label,
- ProgressBar,
- RadioList,
- TextArea,
- ValidationToolbar,
- )
- __all__ = [
- "yes_no_dialog",
- "button_dialog",
- "input_dialog",
- "message_dialog",
- "radiolist_dialog",
- "checkboxlist_dialog",
- "progress_dialog",
- ]
- def yes_no_dialog(
- title: AnyFormattedText = "",
- text: AnyFormattedText = "",
- yes_text: str = "Yes",
- no_text: str = "No",
- style: BaseStyle | None = None,
- ) -> Application[bool]:
- """
- Display a Yes/No dialog.
- Return a boolean.
- """
- def yes_handler() -> None:
- get_app().exit(result=True)
- def no_handler() -> None:
- get_app().exit(result=False)
- dialog = Dialog(
- title=title,
- body=Label(text=text, dont_extend_height=True),
- buttons=[
- Button(text=yes_text, handler=yes_handler),
- Button(text=no_text, handler=no_handler),
- ],
- with_background=True,
- )
- return _create_app(dialog, style)
- _T = TypeVar("_T")
- def button_dialog(
- title: AnyFormattedText = "",
- text: AnyFormattedText = "",
- buttons: list[tuple[str, _T]] = [],
- style: BaseStyle | None = None,
- ) -> Application[_T]:
- """
- Display a dialog with button choices (given as a list of tuples).
- Return the value associated with button.
- """
- def button_handler(v: _T) -> None:
- get_app().exit(result=v)
- dialog = Dialog(
- title=title,
- body=Label(text=text, dont_extend_height=True),
- buttons=[
- Button(text=t, handler=functools.partial(button_handler, v))
- for t, v in buttons
- ],
- with_background=True,
- )
- return _create_app(dialog, style)
- def input_dialog(
- title: AnyFormattedText = "",
- text: AnyFormattedText = "",
- ok_text: str = "OK",
- cancel_text: str = "Cancel",
- completer: Completer | None = None,
- validator: Validator | None = None,
- password: FilterOrBool = False,
- style: BaseStyle | None = None,
- default: str = "",
- ) -> Application[str]:
- """
- Display a text input box.
- Return the given text, or None when cancelled.
- """
- def accept(buf: Buffer) -> bool:
- get_app().layout.focus(ok_button)
- return True # Keep text.
- def ok_handler() -> None:
- get_app().exit(result=textfield.text)
- ok_button = Button(text=ok_text, handler=ok_handler)
- cancel_button = Button(text=cancel_text, handler=_return_none)
- textfield = TextArea(
- text=default,
- multiline=False,
- password=password,
- completer=completer,
- validator=validator,
- accept_handler=accept,
- )
- dialog = Dialog(
- title=title,
- body=HSplit(
- [
- Label(text=text, dont_extend_height=True),
- textfield,
- ValidationToolbar(),
- ],
- padding=D(preferred=1, max=1),
- ),
- buttons=[ok_button, cancel_button],
- with_background=True,
- )
- return _create_app(dialog, style)
- def message_dialog(
- title: AnyFormattedText = "",
- text: AnyFormattedText = "",
- ok_text: str = "Ok",
- style: BaseStyle | None = None,
- ) -> Application[None]:
- """
- Display a simple message box and wait until the user presses enter.
- """
- dialog = Dialog(
- title=title,
- body=Label(text=text, dont_extend_height=True),
- buttons=[Button(text=ok_text, handler=_return_none)],
- with_background=True,
- )
- return _create_app(dialog, style)
- def radiolist_dialog(
- title: AnyFormattedText = "",
- text: AnyFormattedText = "",
- ok_text: str = "Ok",
- cancel_text: str = "Cancel",
- values: Sequence[tuple[_T, AnyFormattedText]] | None = None,
- default: _T | None = None,
- style: BaseStyle | None = None,
- ) -> Application[_T]:
- """
- Display a simple list of element the user can choose amongst.
- Only one element can be selected at a time using Arrow keys and Enter.
- The focus can be moved between the list and the Ok/Cancel button with tab.
- """
- if values is None:
- values = []
- def ok_handler() -> None:
- get_app().exit(result=radio_list.current_value)
- radio_list = RadioList(values=values, default=default)
- dialog = Dialog(
- title=title,
- body=HSplit(
- [Label(text=text, dont_extend_height=True), radio_list],
- padding=1,
- ),
- buttons=[
- Button(text=ok_text, handler=ok_handler),
- Button(text=cancel_text, handler=_return_none),
- ],
- with_background=True,
- )
- return _create_app(dialog, style)
- def checkboxlist_dialog(
- title: AnyFormattedText = "",
- text: AnyFormattedText = "",
- ok_text: str = "Ok",
- cancel_text: str = "Cancel",
- values: Sequence[tuple[_T, AnyFormattedText]] | None = None,
- default_values: Sequence[_T] | None = None,
- style: BaseStyle | None = None,
- ) -> Application[list[_T]]:
- """
- Display a simple list of element the user can choose multiple values amongst.
- Several elements can be selected at a time using Arrow keys and Enter.
- The focus can be moved between the list and the Ok/Cancel button with tab.
- """
- if values is None:
- values = []
- def ok_handler() -> None:
- get_app().exit(result=cb_list.current_values)
- cb_list = CheckboxList(values=values, default_values=default_values)
- dialog = Dialog(
- title=title,
- body=HSplit(
- [Label(text=text, dont_extend_height=True), cb_list],
- padding=1,
- ),
- buttons=[
- Button(text=ok_text, handler=ok_handler),
- Button(text=cancel_text, handler=_return_none),
- ],
- with_background=True,
- )
- return _create_app(dialog, style)
- def progress_dialog(
- title: AnyFormattedText = "",
- text: AnyFormattedText = "",
- run_callback: Callable[[Callable[[int], None], Callable[[str], None]], None] = (
- lambda *a: None
- ),
- style: BaseStyle | None = None,
- ) -> Application[None]:
- """
- :param run_callback: A function that receives as input a `set_percentage`
- function and it does the work.
- """
- loop = get_running_loop()
- progressbar = ProgressBar()
- text_area = TextArea(
- focusable=False,
- # Prefer this text area as big as possible, to avoid having a window
- # that keeps resizing when we add text to it.
- height=D(preferred=10**10),
- )
- dialog = Dialog(
- body=HSplit(
- [
- Box(Label(text=text)),
- Box(text_area, padding=D.exact(1)),
- progressbar,
- ]
- ),
- title=title,
- with_background=True,
- )
- app = _create_app(dialog, style)
- def set_percentage(value: int) -> None:
- progressbar.percentage = int(value)
- app.invalidate()
- def log_text(text: str) -> None:
- loop.call_soon_threadsafe(text_area.buffer.insert_text, text)
- app.invalidate()
- # Run the callback in the executor. When done, set a return value for the
- # UI, so that it quits.
- def start() -> None:
- try:
- run_callback(set_percentage, log_text)
- finally:
- app.exit()
- def pre_run() -> None:
- run_in_executor_with_context(start)
- app.pre_run_callables.append(pre_run)
- return app
- def _create_app(dialog: AnyContainer, style: BaseStyle | None) -> Application[Any]:
- # Key bindings.
- bindings = KeyBindings()
- bindings.add("tab")(focus_next)
- bindings.add("s-tab")(focus_previous)
- return Application(
- layout=Layout(dialog),
- key_bindings=merge_key_bindings([load_key_bindings(), bindings]),
- mouse_support=True,
- style=style,
- full_screen=True,
- )
- def _return_none() -> None:
- "Button handler that returns None."
- get_app().exit()
|