You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			331 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			331 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Python
		
	
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()
 |