|
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 |
|
|
|
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, |
|
|
|
|
|
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() |
|
|
|
|
|
|
|
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]: |
|
|
|
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() |
|
|