summaryrefslogtreecommitdiffstats
path: root/src/prompt_toolkit/shortcuts
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 16:35:31 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 16:35:31 +0000
commit4f1a3b5f9ad05aa7b08715d48909a2b06ee2fcb1 (patch)
treee5dee7be2f0d963da4faad6517278d03783e3adc /src/prompt_toolkit/shortcuts
parentInitial commit. (diff)
downloadprompt-toolkit-4f1a3b5f9ad05aa7b08715d48909a2b06ee2fcb1.tar.xz
prompt-toolkit-4f1a3b5f9ad05aa7b08715d48909a2b06ee2fcb1.zip
Adding upstream version 3.0.43.upstream/3.0.43upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/prompt_toolkit/shortcuts')
-rw-r--r--src/prompt_toolkit/shortcuts/__init__.py46
-rw-r--r--src/prompt_toolkit/shortcuts/dialogs.py330
-rw-r--r--src/prompt_toolkit/shortcuts/progress_bar/__init__.py33
-rw-r--r--src/prompt_toolkit/shortcuts/progress_bar/base.py448
-rw-r--r--src/prompt_toolkit/shortcuts/progress_bar/formatters.py429
-rw-r--r--src/prompt_toolkit/shortcuts/prompt.py1504
-rw-r--r--src/prompt_toolkit/shortcuts/utils.py239
7 files changed, 3029 insertions, 0 deletions
diff --git a/src/prompt_toolkit/shortcuts/__init__.py b/src/prompt_toolkit/shortcuts/__init__.py
new file mode 100644
index 0000000..49e5ac4
--- /dev/null
+++ b/src/prompt_toolkit/shortcuts/__init__.py
@@ -0,0 +1,46 @@
+from __future__ import annotations
+
+from .dialogs import (
+ button_dialog,
+ checkboxlist_dialog,
+ input_dialog,
+ message_dialog,
+ progress_dialog,
+ radiolist_dialog,
+ yes_no_dialog,
+)
+from .progress_bar import ProgressBar, ProgressBarCounter
+from .prompt import (
+ CompleteStyle,
+ PromptSession,
+ confirm,
+ create_confirm_session,
+ prompt,
+)
+from .utils import clear, clear_title, print_container, print_formatted_text, set_title
+
+__all__ = [
+ # Dialogs.
+ "input_dialog",
+ "message_dialog",
+ "progress_dialog",
+ "checkboxlist_dialog",
+ "radiolist_dialog",
+ "yes_no_dialog",
+ "button_dialog",
+ # Prompts.
+ "PromptSession",
+ "prompt",
+ "confirm",
+ "create_confirm_session",
+ "CompleteStyle",
+ # Progress bars.
+ "ProgressBar",
+ "ProgressBarCounter",
+ # Utils.
+ "clear",
+ "clear_title",
+ "print_container",
+ "print_formatted_text",
+ "set_title",
+]
diff --git a/src/prompt_toolkit/shortcuts/dialogs.py b/src/prompt_toolkit/shortcuts/dialogs.py
new file mode 100644
index 0000000..d78e7db
--- /dev/null
+++ b/src/prompt_toolkit/shortcuts/dialogs.py
@@ -0,0 +1,330 @@
+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()
diff --git a/src/prompt_toolkit/shortcuts/progress_bar/__init__.py b/src/prompt_toolkit/shortcuts/progress_bar/__init__.py
new file mode 100644
index 0000000..2261a5b
--- /dev/null
+++ b/src/prompt_toolkit/shortcuts/progress_bar/__init__.py
@@ -0,0 +1,33 @@
+from __future__ import annotations
+
+from .base import ProgressBar, ProgressBarCounter
+from .formatters import (
+ Bar,
+ Formatter,
+ IterationsPerSecond,
+ Label,
+ Percentage,
+ Progress,
+ Rainbow,
+ SpinningWheel,
+ Text,
+ TimeElapsed,
+ TimeLeft,
+)
+
+__all__ = [
+ "ProgressBar",
+ "ProgressBarCounter",
+ # Formatters.
+ "Formatter",
+ "Text",
+ "Label",
+ "Percentage",
+ "Bar",
+ "Progress",
+ "TimeElapsed",
+ "TimeLeft",
+ "IterationsPerSecond",
+ "SpinningWheel",
+ "Rainbow",
+]
diff --git a/src/prompt_toolkit/shortcuts/progress_bar/base.py b/src/prompt_toolkit/shortcuts/progress_bar/base.py
new file mode 100644
index 0000000..21aa1be
--- /dev/null
+++ b/src/prompt_toolkit/shortcuts/progress_bar/base.py
@@ -0,0 +1,448 @@
+"""
+Progress bar implementation on top of prompt_toolkit.
+
+::
+
+ with ProgressBar(...) as pb:
+ for item in pb(data):
+ ...
+"""
+from __future__ import annotations
+
+import contextvars
+import datetime
+import functools
+import os
+import signal
+import threading
+import traceback
+from typing import (
+ Callable,
+ Generic,
+ Iterable,
+ Iterator,
+ Sequence,
+ Sized,
+ TextIO,
+ TypeVar,
+ cast,
+)
+
+from prompt_toolkit.application import Application
+from prompt_toolkit.application.current import get_app_session
+from prompt_toolkit.filters import Condition, is_done, renderer_height_is_known
+from prompt_toolkit.formatted_text import (
+ AnyFormattedText,
+ StyleAndTextTuples,
+ to_formatted_text,
+)
+from prompt_toolkit.input import Input
+from prompt_toolkit.key_binding import KeyBindings
+from prompt_toolkit.key_binding.key_processor import KeyPressEvent
+from prompt_toolkit.layout import (
+ ConditionalContainer,
+ FormattedTextControl,
+ HSplit,
+ Layout,
+ VSplit,
+ Window,
+)
+from prompt_toolkit.layout.controls import UIContent, UIControl
+from prompt_toolkit.layout.dimension import AnyDimension, D
+from prompt_toolkit.output import ColorDepth, Output
+from prompt_toolkit.styles import BaseStyle
+from prompt_toolkit.utils import in_main_thread
+
+from .formatters import Formatter, create_default_formatters
+
+__all__ = ["ProgressBar"]
+
+E = KeyPressEvent
+
+_SIGWINCH = getattr(signal, "SIGWINCH", None)
+
+
+def create_key_bindings(cancel_callback: Callable[[], None] | None) -> KeyBindings:
+ """
+ Key bindings handled by the progress bar.
+ (The main thread is not supposed to handle any key bindings.)
+ """
+ kb = KeyBindings()
+
+ @kb.add("c-l")
+ def _clear(event: E) -> None:
+ event.app.renderer.clear()
+
+ if cancel_callback is not None:
+
+ @kb.add("c-c")
+ def _interrupt(event: E) -> None:
+ "Kill the 'body' of the progress bar, but only if we run from the main thread."
+ assert cancel_callback is not None
+ cancel_callback()
+
+ return kb
+
+
+_T = TypeVar("_T")
+
+
+class ProgressBar:
+ """
+ Progress bar context manager.
+
+ Usage ::
+
+ with ProgressBar(...) as pb:
+ for item in pb(data):
+ ...
+
+ :param title: Text to be displayed above the progress bars. This can be a
+ callable or formatted text as well.
+ :param formatters: List of :class:`.Formatter` instances.
+ :param bottom_toolbar: Text to be displayed in the bottom toolbar. This
+ can be a callable or formatted text.
+ :param style: :class:`prompt_toolkit.styles.BaseStyle` instance.
+ :param key_bindings: :class:`.KeyBindings` instance.
+ :param cancel_callback: Callback function that's called when control-c is
+ pressed by the user. This can be used for instance to start "proper"
+ cancellation if the wrapped code supports it.
+ :param file: The file object used for rendering, by default `sys.stderr` is used.
+
+ :param color_depth: `prompt_toolkit` `ColorDepth` instance.
+ :param output: :class:`~prompt_toolkit.output.Output` instance.
+ :param input: :class:`~prompt_toolkit.input.Input` instance.
+ """
+
+ def __init__(
+ self,
+ title: AnyFormattedText = None,
+ formatters: Sequence[Formatter] | None = None,
+ bottom_toolbar: AnyFormattedText = None,
+ style: BaseStyle | None = None,
+ key_bindings: KeyBindings | None = None,
+ cancel_callback: Callable[[], None] | None = None,
+ file: TextIO | None = None,
+ color_depth: ColorDepth | None = None,
+ output: Output | None = None,
+ input: Input | None = None,
+ ) -> None:
+ self.title = title
+ self.formatters = formatters or create_default_formatters()
+ self.bottom_toolbar = bottom_toolbar
+ self.counters: list[ProgressBarCounter[object]] = []
+ self.style = style
+ self.key_bindings = key_bindings
+ self.cancel_callback = cancel_callback
+
+ # If no `cancel_callback` was given, and we're creating the progress
+ # bar from the main thread. Cancel by sending a `KeyboardInterrupt` to
+ # the main thread.
+ if self.cancel_callback is None and in_main_thread():
+
+ def keyboard_interrupt_to_main_thread() -> None:
+ os.kill(os.getpid(), signal.SIGINT)
+
+ self.cancel_callback = keyboard_interrupt_to_main_thread
+
+ # Note that we use __stderr__ as default error output, because that
+ # works best with `patch_stdout`.
+ self.color_depth = color_depth
+ self.output = output or get_app_session().output
+ self.input = input or get_app_session().input
+
+ self._thread: threading.Thread | None = None
+
+ self._has_sigwinch = False
+ self._app_started = threading.Event()
+
+ def __enter__(self) -> ProgressBar:
+ # Create UI Application.
+ title_toolbar = ConditionalContainer(
+ Window(
+ FormattedTextControl(lambda: self.title),
+ height=1,
+ style="class:progressbar,title",
+ ),
+ filter=Condition(lambda: self.title is not None),
+ )
+
+ bottom_toolbar = ConditionalContainer(
+ Window(
+ FormattedTextControl(
+ lambda: self.bottom_toolbar, style="class:bottom-toolbar.text"
+ ),
+ style="class:bottom-toolbar",
+ height=1,
+ ),
+ filter=~is_done
+ & renderer_height_is_known
+ & Condition(lambda: self.bottom_toolbar is not None),
+ )
+
+ def width_for_formatter(formatter: Formatter) -> AnyDimension:
+ # Needs to be passed as callable (partial) to the 'width'
+ # parameter, because we want to call it on every resize.
+ return formatter.get_width(progress_bar=self)
+
+ progress_controls = [
+ Window(
+ content=_ProgressControl(self, f, self.cancel_callback),
+ width=functools.partial(width_for_formatter, f),
+ )
+ for f in self.formatters
+ ]
+
+ self.app: Application[None] = Application(
+ min_redraw_interval=0.05,
+ layout=Layout(
+ HSplit(
+ [
+ title_toolbar,
+ VSplit(
+ progress_controls,
+ height=lambda: D(
+ preferred=len(self.counters), max=len(self.counters)
+ ),
+ ),
+ Window(),
+ bottom_toolbar,
+ ]
+ )
+ ),
+ style=self.style,
+ key_bindings=self.key_bindings,
+ refresh_interval=0.3,
+ color_depth=self.color_depth,
+ output=self.output,
+ input=self.input,
+ )
+
+ # Run application in different thread.
+ def run() -> None:
+ try:
+ self.app.run(pre_run=self._app_started.set)
+ except BaseException as e:
+ traceback.print_exc()
+ print(e)
+
+ ctx: contextvars.Context = contextvars.copy_context()
+
+ self._thread = threading.Thread(target=ctx.run, args=(run,))
+ self._thread.start()
+
+ return self
+
+ def __exit__(self, *a: object) -> None:
+ # Wait for the app to be started. Make sure we don't quit earlier,
+ # otherwise `self.app.exit` won't terminate the app because
+ # `self.app.future` has not yet been set.
+ self._app_started.wait()
+
+ # Quit UI application.
+ if self.app.is_running and self.app.loop is not None:
+ self.app.loop.call_soon_threadsafe(self.app.exit)
+
+ if self._thread is not None:
+ self._thread.join()
+
+ def __call__(
+ self,
+ data: Iterable[_T] | None = None,
+ label: AnyFormattedText = "",
+ remove_when_done: bool = False,
+ total: int | None = None,
+ ) -> ProgressBarCounter[_T]:
+ """
+ Start a new counter.
+
+ :param label: Title text or description for this progress. (This can be
+ formatted text as well).
+ :param remove_when_done: When `True`, hide this progress bar.
+ :param total: Specify the maximum value if it can't be calculated by
+ calling ``len``.
+ """
+ counter = ProgressBarCounter(
+ self, data, label=label, remove_when_done=remove_when_done, total=total
+ )
+ self.counters.append(counter)
+ return counter
+
+ def invalidate(self) -> None:
+ self.app.invalidate()
+
+
+class _ProgressControl(UIControl):
+ """
+ User control for the progress bar.
+ """
+
+ def __init__(
+ self,
+ progress_bar: ProgressBar,
+ formatter: Formatter,
+ cancel_callback: Callable[[], None] | None,
+ ) -> None:
+ self.progress_bar = progress_bar
+ self.formatter = formatter
+ self._key_bindings = create_key_bindings(cancel_callback)
+
+ def create_content(self, width: int, height: int) -> UIContent:
+ items: list[StyleAndTextTuples] = []
+
+ for pr in self.progress_bar.counters:
+ try:
+ text = self.formatter.format(self.progress_bar, pr, width)
+ except BaseException:
+ traceback.print_exc()
+ text = "ERROR"
+
+ items.append(to_formatted_text(text))
+
+ def get_line(i: int) -> StyleAndTextTuples:
+ return items[i]
+
+ return UIContent(get_line=get_line, line_count=len(items), show_cursor=False)
+
+ def is_focusable(self) -> bool:
+ return True # Make sure that the key bindings work.
+
+ def get_key_bindings(self) -> KeyBindings:
+ return self._key_bindings
+
+
+_CounterItem = TypeVar("_CounterItem", covariant=True)
+
+
+class ProgressBarCounter(Generic[_CounterItem]):
+ """
+ An individual counter (A progress bar can have multiple counters).
+ """
+
+ def __init__(
+ self,
+ progress_bar: ProgressBar,
+ data: Iterable[_CounterItem] | None = None,
+ label: AnyFormattedText = "",
+ remove_when_done: bool = False,
+ total: int | None = None,
+ ) -> None:
+ self.start_time = datetime.datetime.now()
+ self.stop_time: datetime.datetime | None = None
+ self.progress_bar = progress_bar
+ self.data = data
+ self.items_completed = 0
+ self.label = label
+ self.remove_when_done = remove_when_done
+ self._done = False
+ self.total: int | None
+
+ if total is None:
+ try:
+ self.total = len(cast(Sized, data))
+ except TypeError:
+ self.total = None # We don't know the total length.
+ else:
+ self.total = total
+
+ def __iter__(self) -> Iterator[_CounterItem]:
+ if self.data is not None:
+ try:
+ for item in self.data:
+ yield item
+ self.item_completed()
+
+ # Only done if we iterate to the very end.
+ self.done = True
+ finally:
+ # Ensure counter has stopped even if we did not iterate to the
+ # end (e.g. break or exceptions).
+ self.stopped = True
+ else:
+ raise NotImplementedError("No data defined to iterate over.")
+
+ def item_completed(self) -> None:
+ """
+ Start handling the next item.
+
+ (Can be called manually in case we don't have a collection to loop through.)
+ """
+ self.items_completed += 1
+ self.progress_bar.invalidate()
+
+ @property
+ def done(self) -> bool:
+ """Whether a counter has been completed.
+
+ Done counter have been stopped (see stopped) and removed depending on
+ remove_when_done value.
+
+ Contrast this with stopped. A stopped counter may be terminated before
+ 100% completion. A done counter has reached its 100% completion.
+ """
+ return self._done
+
+ @done.setter
+ def done(self, value: bool) -> None:
+ self._done = value
+ self.stopped = value
+
+ if value and self.remove_when_done:
+ self.progress_bar.counters.remove(self)
+
+ @property
+ def stopped(self) -> bool:
+ """Whether a counter has been stopped.
+
+ Stopped counters no longer have increasing time_elapsed. This distinction is
+ also used to prevent the Bar formatter with unknown totals from continuing to run.
+
+ A stopped counter (but not done) can be used to signal that a given counter has
+ encountered an error but allows other counters to continue
+ (e.g. download X of Y failed). Given how only done counters are removed
+ (see remove_when_done) this can help aggregate failures from a large number of
+ successes.
+
+ Contrast this with done. A done counter has reached its 100% completion.
+ A stopped counter may be terminated before 100% completion.
+ """
+ return self.stop_time is not None
+
+ @stopped.setter
+ def stopped(self, value: bool) -> None:
+ if value:
+ # This counter has not already been stopped.
+ if not self.stop_time:
+ self.stop_time = datetime.datetime.now()
+ else:
+ # Clearing any previously set stop_time.
+ self.stop_time = None
+
+ @property
+ def percentage(self) -> float:
+ if self.total is None:
+ return 0
+ else:
+ return self.items_completed * 100 / max(self.total, 1)
+
+ @property
+ def time_elapsed(self) -> datetime.timedelta:
+ """
+ Return how much time has been elapsed since the start.
+ """
+ if self.stop_time is None:
+ return datetime.datetime.now() - self.start_time
+ else:
+ return self.stop_time - self.start_time
+
+ @property
+ def time_left(self) -> datetime.timedelta | None:
+ """
+ Timedelta representing the time left.
+ """
+ if self.total is None or not self.percentage:
+ return None
+ elif self.done or self.stopped:
+ return datetime.timedelta(0)
+ else:
+ return self.time_elapsed * (100 - self.percentage) / self.percentage
diff --git a/src/prompt_toolkit/shortcuts/progress_bar/formatters.py b/src/prompt_toolkit/shortcuts/progress_bar/formatters.py
new file mode 100644
index 0000000..dd0339c
--- /dev/null
+++ b/src/prompt_toolkit/shortcuts/progress_bar/formatters.py
@@ -0,0 +1,429 @@
+"""
+Formatter classes for the progress bar.
+Each progress bar consists of a list of these formatters.
+"""
+from __future__ import annotations
+
+import datetime
+import time
+from abc import ABCMeta, abstractmethod
+from typing import TYPE_CHECKING
+
+from prompt_toolkit.formatted_text import (
+ HTML,
+ AnyFormattedText,
+ StyleAndTextTuples,
+ to_formatted_text,
+)
+from prompt_toolkit.formatted_text.utils import fragment_list_width
+from prompt_toolkit.layout.dimension import AnyDimension, D
+from prompt_toolkit.layout.utils import explode_text_fragments
+from prompt_toolkit.utils import get_cwidth
+
+if TYPE_CHECKING:
+ from .base import ProgressBar, ProgressBarCounter
+
+__all__ = [
+ "Formatter",
+ "Text",
+ "Label",
+ "Percentage",
+ "Bar",
+ "Progress",
+ "TimeElapsed",
+ "TimeLeft",
+ "IterationsPerSecond",
+ "SpinningWheel",
+ "Rainbow",
+ "create_default_formatters",
+]
+
+
+class Formatter(metaclass=ABCMeta):
+ """
+ Base class for any formatter.
+ """
+
+ @abstractmethod
+ def format(
+ self,
+ progress_bar: ProgressBar,
+ progress: ProgressBarCounter[object],
+ width: int,
+ ) -> AnyFormattedText:
+ pass
+
+ def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
+ return D()
+
+
+class Text(Formatter):
+ """
+ Display plain text.
+ """
+
+ def __init__(self, text: AnyFormattedText, style: str = "") -> None:
+ self.text = to_formatted_text(text, style=style)
+
+ def format(
+ self,
+ progress_bar: ProgressBar,
+ progress: ProgressBarCounter[object],
+ width: int,
+ ) -> AnyFormattedText:
+ return self.text
+
+ def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
+ return fragment_list_width(self.text)
+
+
+class Label(Formatter):
+ """
+ Display the name of the current task.
+
+ :param width: If a `width` is given, use this width. Scroll the text if it
+ doesn't fit in this width.
+ :param suffix: String suffix to be added after the task name, e.g. ': '.
+ If no task name was given, no suffix will be added.
+ """
+
+ def __init__(self, width: AnyDimension = None, suffix: str = "") -> None:
+ self.width = width
+ self.suffix = suffix
+
+ def _add_suffix(self, label: AnyFormattedText) -> StyleAndTextTuples:
+ label = to_formatted_text(label, style="class:label")
+ return label + [("", self.suffix)]
+
+ def format(
+ self,
+ progress_bar: ProgressBar,
+ progress: ProgressBarCounter[object],
+ width: int,
+ ) -> AnyFormattedText:
+ label = self._add_suffix(progress.label)
+ cwidth = fragment_list_width(label)
+
+ if cwidth > width:
+ # It doesn't fit -> scroll task name.
+ label = explode_text_fragments(label)
+ max_scroll = cwidth - width
+ current_scroll = int(time.time() * 3 % max_scroll)
+ label = label[current_scroll:]
+
+ return label
+
+ def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
+ if self.width:
+ return self.width
+
+ all_labels = [self._add_suffix(c.label) for c in progress_bar.counters]
+ if all_labels:
+ max_widths = max(fragment_list_width(l) for l in all_labels)
+ return D(preferred=max_widths, max=max_widths)
+ else:
+ return D()
+
+
+class Percentage(Formatter):
+ """
+ Display the progress as a percentage.
+ """
+
+ template = "<percentage>{percentage:>5}%</percentage>"
+
+ def format(
+ self,
+ progress_bar: ProgressBar,
+ progress: ProgressBarCounter[object],
+ width: int,
+ ) -> AnyFormattedText:
+ return HTML(self.template).format(percentage=round(progress.percentage, 1))
+
+ def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
+ return D.exact(6)
+
+
+class Bar(Formatter):
+ """
+ Display the progress bar itself.
+ """
+
+ template = "<bar>{start}<bar-a>{bar_a}</bar-a><bar-b>{bar_b}</bar-b><bar-c>{bar_c}</bar-c>{end}</bar>"
+
+ def __init__(
+ self,
+ start: str = "[",
+ end: str = "]",
+ sym_a: str = "=",
+ sym_b: str = ">",
+ sym_c: str = " ",
+ unknown: str = "#",
+ ) -> None:
+ assert len(sym_a) == 1 and get_cwidth(sym_a) == 1
+ assert len(sym_c) == 1 and get_cwidth(sym_c) == 1
+
+ self.start = start
+ self.end = end
+ self.sym_a = sym_a
+ self.sym_b = sym_b
+ self.sym_c = sym_c
+ self.unknown = unknown
+
+ def format(
+ self,
+ progress_bar: ProgressBar,
+ progress: ProgressBarCounter[object],
+ width: int,
+ ) -> AnyFormattedText:
+ if progress.done or progress.total or progress.stopped:
+ sym_a, sym_b, sym_c = self.sym_a, self.sym_b, self.sym_c
+
+ # Compute pb_a based on done, total, or stopped states.
+ if progress.done:
+ # 100% completed irrelevant of how much was actually marked as completed.
+ percent = 1.0
+ else:
+ # Show percentage completed.
+ percent = progress.percentage / 100
+ else:
+ # Total is unknown and bar is still running.
+ sym_a, sym_b, sym_c = self.sym_c, self.unknown, self.sym_c
+
+ # Compute percent based on the time.
+ percent = time.time() * 20 % 100 / 100
+
+ # Subtract left, sym_b, and right.
+ width -= get_cwidth(self.start + sym_b + self.end)
+
+ # Scale percent by width
+ pb_a = int(percent * width)
+ bar_a = sym_a * pb_a
+ bar_b = sym_b
+ bar_c = sym_c * (width - pb_a)
+
+ return HTML(self.template).format(
+ start=self.start, end=self.end, bar_a=bar_a, bar_b=bar_b, bar_c=bar_c
+ )
+
+ def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
+ return D(min=9)
+
+
+class Progress(Formatter):
+ """
+ Display the progress as text. E.g. "8/20"
+ """
+
+ template = "<current>{current:>3}</current>/<total>{total:>3}</total>"
+
+ def format(
+ self,
+ progress_bar: ProgressBar,
+ progress: ProgressBarCounter[object],
+ width: int,
+ ) -> AnyFormattedText:
+ return HTML(self.template).format(
+ current=progress.items_completed, total=progress.total or "?"
+ )
+
+ def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
+ all_lengths = [
+ len("{:>3}".format(c.total or "?")) for c in progress_bar.counters
+ ]
+ all_lengths.append(1)
+ return D.exact(max(all_lengths) * 2 + 1)
+
+
+def _format_timedelta(timedelta: datetime.timedelta) -> str:
+ """
+ Return hh:mm:ss, or mm:ss if the amount of hours is zero.
+ """
+ result = f"{timedelta}".split(".")[0]
+ if result.startswith("0:"):
+ result = result[2:]
+ return result
+
+
+class TimeElapsed(Formatter):
+ """
+ Display the elapsed time.
+ """
+
+ def format(
+ self,
+ progress_bar: ProgressBar,
+ progress: ProgressBarCounter[object],
+ width: int,
+ ) -> AnyFormattedText:
+ text = _format_timedelta(progress.time_elapsed).rjust(width)
+ return HTML("<time-elapsed>{time_elapsed}</time-elapsed>").format(
+ time_elapsed=text
+ )
+
+ def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
+ all_values = [
+ len(_format_timedelta(c.time_elapsed)) for c in progress_bar.counters
+ ]
+ if all_values:
+ return max(all_values)
+ return 0
+
+
+class TimeLeft(Formatter):
+ """
+ Display the time left.
+ """
+
+ template = "<time-left>{time_left}</time-left>"
+ unknown = "?:??:??"
+
+ def format(
+ self,
+ progress_bar: ProgressBar,
+ progress: ProgressBarCounter[object],
+ width: int,
+ ) -> AnyFormattedText:
+ time_left = progress.time_left
+ if time_left is not None:
+ formatted_time_left = _format_timedelta(time_left)
+ else:
+ formatted_time_left = self.unknown
+
+ return HTML(self.template).format(time_left=formatted_time_left.rjust(width))
+
+ def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
+ all_values = [
+ len(_format_timedelta(c.time_left)) if c.time_left is not None else 7
+ for c in progress_bar.counters
+ ]
+ if all_values:
+ return max(all_values)
+ return 0
+
+
+class IterationsPerSecond(Formatter):
+ """
+ Display the iterations per second.
+ """
+
+ template = (
+ "<iterations-per-second>{iterations_per_second:.2f}</iterations-per-second>"
+ )
+
+ def format(
+ self,
+ progress_bar: ProgressBar,
+ progress: ProgressBarCounter[object],
+ width: int,
+ ) -> AnyFormattedText:
+ value = progress.items_completed / progress.time_elapsed.total_seconds()
+ return HTML(self.template.format(iterations_per_second=value))
+
+ def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
+ all_values = [
+ len(f"{c.items_completed / c.time_elapsed.total_seconds():.2f}")
+ for c in progress_bar.counters
+ ]
+ if all_values:
+ return max(all_values)
+ return 0
+
+
+class SpinningWheel(Formatter):
+ """
+ Display a spinning wheel.
+ """
+
+ characters = r"/-\|"
+
+ def format(
+ self,
+ progress_bar: ProgressBar,
+ progress: ProgressBarCounter[object],
+ width: int,
+ ) -> AnyFormattedText:
+ index = int(time.time() * 3) % len(self.characters)
+ return HTML("<spinning-wheel>{0}</spinning-wheel>").format(
+ self.characters[index]
+ )
+
+ def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
+ return D.exact(1)
+
+
+def _hue_to_rgb(hue: float) -> tuple[int, int, int]:
+ """
+ Take hue between 0 and 1, return (r, g, b).
+ """
+ i = int(hue * 6.0)
+ f = (hue * 6.0) - i
+
+ q = int(255 * (1.0 - f))
+ t = int(255 * (1.0 - (1.0 - f)))
+
+ i %= 6
+
+ return [
+ (255, t, 0),
+ (q, 255, 0),
+ (0, 255, t),
+ (0, q, 255),
+ (t, 0, 255),
+ (255, 0, q),
+ ][i]
+
+
+class Rainbow(Formatter):
+ """
+ For the fun. Add rainbow colors to any of the other formatters.
+ """
+
+ colors = ["#%.2x%.2x%.2x" % _hue_to_rgb(h / 100.0) for h in range(0, 100)]
+
+ def __init__(self, formatter: Formatter) -> None:
+ self.formatter = formatter
+
+ def format(
+ self,
+ progress_bar: ProgressBar,
+ progress: ProgressBarCounter[object],
+ width: int,
+ ) -> AnyFormattedText:
+ # Get formatted text from nested formatter, and explode it in
+ # text/style tuples.
+ result = self.formatter.format(progress_bar, progress, width)
+ result = explode_text_fragments(to_formatted_text(result))
+
+ # Insert colors.
+ result2: StyleAndTextTuples = []
+ shift = int(time.time() * 3) % len(self.colors)
+
+ for i, (style, text, *_) in enumerate(result):
+ result2.append(
+ (style + " " + self.colors[(i + shift) % len(self.colors)], text)
+ )
+ return result2
+
+ def get_width(self, progress_bar: ProgressBar) -> AnyDimension:
+ return self.formatter.get_width(progress_bar)
+
+
+def create_default_formatters() -> list[Formatter]:
+ """
+ Return the list of default formatters.
+ """
+ return [
+ Label(),
+ Text(" "),
+ Percentage(),
+ Text(" "),
+ Bar(),
+ Text(" "),
+ Progress(),
+ Text(" "),
+ Text("eta [", style="class:time-left"),
+ TimeLeft(),
+ Text("]", style="class:time-left"),
+ Text(" "),
+ ]
diff --git a/src/prompt_toolkit/shortcuts/prompt.py b/src/prompt_toolkit/shortcuts/prompt.py
new file mode 100644
index 0000000..7274b5f
--- /dev/null
+++ b/src/prompt_toolkit/shortcuts/prompt.py
@@ -0,0 +1,1504 @@
+"""
+Line editing functionality.
+---------------------------
+
+This provides a UI for a line input, similar to GNU Readline, libedit and
+linenoise.
+
+Either call the `prompt` function for every line input. Or create an instance
+of the :class:`.PromptSession` class and call the `prompt` method from that
+class. In the second case, we'll have a 'session' that keeps all the state like
+the history in between several calls.
+
+There is a lot of overlap between the arguments taken by the `prompt` function
+and the `PromptSession` (like `completer`, `style`, etcetera). There we have
+the freedom to decide which settings we want for the whole 'session', and which
+we want for an individual `prompt`.
+
+Example::
+
+ # Simple `prompt` call.
+ result = prompt('Say something: ')
+
+ # Using a 'session'.
+ s = PromptSession()
+ result = s.prompt('Say something: ')
+"""
+from __future__ import annotations
+
+from asyncio import get_running_loop
+from contextlib import contextmanager
+from enum import Enum
+from functools import partial
+from typing import TYPE_CHECKING, Callable, Generic, Iterator, TypeVar, Union, cast
+
+from prompt_toolkit.application import Application
+from prompt_toolkit.application.current import get_app
+from prompt_toolkit.auto_suggest import AutoSuggest, DynamicAutoSuggest
+from prompt_toolkit.buffer import Buffer
+from prompt_toolkit.clipboard import Clipboard, DynamicClipboard, InMemoryClipboard
+from prompt_toolkit.completion import Completer, DynamicCompleter, ThreadedCompleter
+from prompt_toolkit.cursor_shapes import (
+ AnyCursorShapeConfig,
+ CursorShapeConfig,
+ DynamicCursorShapeConfig,
+)
+from prompt_toolkit.document import Document
+from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode
+from prompt_toolkit.eventloop import InputHook
+from prompt_toolkit.filters import (
+ Condition,
+ FilterOrBool,
+ has_arg,
+ has_focus,
+ is_done,
+ is_true,
+ renderer_height_is_known,
+ to_filter,
+)
+from prompt_toolkit.formatted_text import (
+ AnyFormattedText,
+ StyleAndTextTuples,
+ fragment_list_to_text,
+ merge_formatted_text,
+ to_formatted_text,
+)
+from prompt_toolkit.history import History, InMemoryHistory
+from prompt_toolkit.input.base import Input
+from prompt_toolkit.key_binding.bindings.auto_suggest import load_auto_suggest_bindings
+from prompt_toolkit.key_binding.bindings.completion import (
+ display_completions_like_readline,
+)
+from prompt_toolkit.key_binding.bindings.open_in_editor import (
+ load_open_in_editor_bindings,
+)
+from prompt_toolkit.key_binding.key_bindings import (
+ ConditionalKeyBindings,
+ DynamicKeyBindings,
+ KeyBindings,
+ KeyBindingsBase,
+ merge_key_bindings,
+)
+from prompt_toolkit.key_binding.key_processor import KeyPressEvent
+from prompt_toolkit.keys import Keys
+from prompt_toolkit.layout import Float, FloatContainer, HSplit, Window
+from prompt_toolkit.layout.containers import ConditionalContainer, WindowAlign
+from prompt_toolkit.layout.controls import (
+ BufferControl,
+ FormattedTextControl,
+ SearchBufferControl,
+)
+from prompt_toolkit.layout.dimension import Dimension
+from prompt_toolkit.layout.layout import Layout
+from prompt_toolkit.layout.menus import CompletionsMenu, MultiColumnCompletionsMenu
+from prompt_toolkit.layout.processors import (
+ AfterInput,
+ AppendAutoSuggestion,
+ ConditionalProcessor,
+ DisplayMultipleCursors,
+ DynamicProcessor,
+ HighlightIncrementalSearchProcessor,
+ HighlightSelectionProcessor,
+ PasswordProcessor,
+ Processor,
+ ReverseSearchProcessor,
+ merge_processors,
+)
+from prompt_toolkit.layout.utils import explode_text_fragments
+from prompt_toolkit.lexers import DynamicLexer, Lexer
+from prompt_toolkit.output import ColorDepth, DummyOutput, Output
+from prompt_toolkit.styles import (
+ BaseStyle,
+ ConditionalStyleTransformation,
+ DynamicStyle,
+ DynamicStyleTransformation,
+ StyleTransformation,
+ SwapLightAndDarkStyleTransformation,
+ merge_style_transformations,
+)
+from prompt_toolkit.utils import (
+ get_cwidth,
+ is_dumb_terminal,
+ suspend_to_background_supported,
+ to_str,
+)
+from prompt_toolkit.validation import DynamicValidator, Validator
+from prompt_toolkit.widgets.toolbars import (
+ SearchToolbar,
+ SystemToolbar,
+ ValidationToolbar,
+)
+
+if TYPE_CHECKING:
+ from prompt_toolkit.formatted_text.base import MagicFormattedText
+
+__all__ = [
+ "PromptSession",
+ "prompt",
+ "confirm",
+ "create_confirm_session", # Used by '_display_completions_like_readline'.
+ "CompleteStyle",
+]
+
+_StyleAndTextTuplesCallable = Callable[[], StyleAndTextTuples]
+E = KeyPressEvent
+
+
+def _split_multiline_prompt(
+ get_prompt_text: _StyleAndTextTuplesCallable,
+) -> tuple[
+ Callable[[], bool], _StyleAndTextTuplesCallable, _StyleAndTextTuplesCallable
+]:
+ """
+ Take a `get_prompt_text` function and return three new functions instead.
+ One that tells whether this prompt consists of multiple lines; one that
+ returns the fragments to be shown on the lines above the input; and another
+ one with the fragments to be shown at the first line of the input.
+ """
+
+ def has_before_fragments() -> bool:
+ for fragment, char, *_ in get_prompt_text():
+ if "\n" in char:
+ return True
+ return False
+
+ def before() -> StyleAndTextTuples:
+ result: StyleAndTextTuples = []
+ found_nl = False
+ for fragment, char, *_ in reversed(explode_text_fragments(get_prompt_text())):
+ if found_nl:
+ result.insert(0, (fragment, char))
+ elif char == "\n":
+ found_nl = True
+ return result
+
+ def first_input_line() -> StyleAndTextTuples:
+ result: StyleAndTextTuples = []
+ for fragment, char, *_ in reversed(explode_text_fragments(get_prompt_text())):
+ if char == "\n":
+ break
+ else:
+ result.insert(0, (fragment, char))
+ return result
+
+ return has_before_fragments, before, first_input_line
+
+
+class _RPrompt(Window):
+ """
+ The prompt that is displayed on the right side of the Window.
+ """
+
+ def __init__(self, text: AnyFormattedText) -> None:
+ super().__init__(
+ FormattedTextControl(text=text),
+ align=WindowAlign.RIGHT,
+ style="class:rprompt",
+ )
+
+
+class CompleteStyle(str, Enum):
+ """
+ How to display autocompletions for the prompt.
+ """
+
+ value: str
+
+ COLUMN = "COLUMN"
+ MULTI_COLUMN = "MULTI_COLUMN"
+ READLINE_LIKE = "READLINE_LIKE"
+
+
+# Formatted text for the continuation prompt. It's the same like other
+# formatted text, except that if it's a callable, it takes three arguments.
+PromptContinuationText = Union[
+ str,
+ "MagicFormattedText",
+ StyleAndTextTuples,
+ # (prompt_width, line_number, wrap_count) -> AnyFormattedText.
+ Callable[[int, int, int], AnyFormattedText],
+]
+
+_T = TypeVar("_T")
+
+
+class PromptSession(Generic[_T]):
+ """
+ PromptSession for a prompt application, which can be used as a GNU Readline
+ replacement.
+
+ This is a wrapper around a lot of ``prompt_toolkit`` functionality and can
+ be a replacement for `raw_input`.
+
+ All parameters that expect "formatted text" can take either just plain text
+ (a unicode object), a list of ``(style_str, text)`` tuples or an HTML object.
+
+ Example usage::
+
+ s = PromptSession(message='>')
+ text = s.prompt()
+
+ :param message: Plain text or formatted text to be shown before the prompt.
+ This can also be a callable that returns formatted text.
+ :param multiline: `bool` or :class:`~prompt_toolkit.filters.Filter`.
+ When True, prefer a layout that is more adapted for multiline input.
+ Text after newlines is automatically indented, and search/arg input is
+ shown below the input, instead of replacing the prompt.
+ :param wrap_lines: `bool` or :class:`~prompt_toolkit.filters.Filter`.
+ When True (the default), automatically wrap long lines instead of
+ scrolling horizontally.
+ :param is_password: Show asterisks instead of the actual typed characters.
+ :param editing_mode: ``EditingMode.VI`` or ``EditingMode.EMACS``.
+ :param vi_mode: `bool`, if True, Identical to ``editing_mode=EditingMode.VI``.
+ :param complete_while_typing: `bool` or
+ :class:`~prompt_toolkit.filters.Filter`. Enable autocompletion while
+ typing.
+ :param validate_while_typing: `bool` or
+ :class:`~prompt_toolkit.filters.Filter`. Enable input validation while
+ typing.
+ :param enable_history_search: `bool` or
+ :class:`~prompt_toolkit.filters.Filter`. Enable up-arrow parting
+ string matching.
+ :param search_ignore_case:
+ :class:`~prompt_toolkit.filters.Filter`. Search case insensitive.
+ :param lexer: :class:`~prompt_toolkit.lexers.Lexer` to be used for the
+ syntax highlighting.
+ :param validator: :class:`~prompt_toolkit.validation.Validator` instance
+ for input validation.
+ :param completer: :class:`~prompt_toolkit.completion.Completer` instance
+ for input completion.
+ :param complete_in_thread: `bool` or
+ :class:`~prompt_toolkit.filters.Filter`. Run the completer code in a
+ background thread in order to avoid blocking the user interface.
+ For ``CompleteStyle.READLINE_LIKE``, this setting has no effect. There
+ we always run the completions in the main thread.
+ :param reserve_space_for_menu: Space to be reserved for displaying the menu.
+ (0 means that no space needs to be reserved.)
+ :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest`
+ instance for input suggestions.
+ :param style: :class:`.Style` instance for the color scheme.
+ :param include_default_pygments_style: `bool` or
+ :class:`~prompt_toolkit.filters.Filter`. Tell whether the default
+ styling for Pygments lexers has to be included. By default, this is
+ true, but it is recommended to be disabled if another Pygments style is
+ passed as the `style` argument, otherwise, two Pygments styles will be
+ merged.
+ :param style_transformation:
+ :class:`~prompt_toolkit.style.StyleTransformation` instance.
+ :param swap_light_and_dark_colors: `bool` or
+ :class:`~prompt_toolkit.filters.Filter`. When enabled, apply
+ :class:`~prompt_toolkit.style.SwapLightAndDarkStyleTransformation`.
+ This is useful for switching between dark and light terminal
+ backgrounds.
+ :param enable_system_prompt: `bool` or
+ :class:`~prompt_toolkit.filters.Filter`. Pressing Meta+'!' will show
+ a system prompt.
+ :param enable_suspend: `bool` or :class:`~prompt_toolkit.filters.Filter`.
+ Enable Control-Z style suspension.
+ :param enable_open_in_editor: `bool` or
+ :class:`~prompt_toolkit.filters.Filter`. Pressing 'v' in Vi mode or
+ C-X C-E in emacs mode will open an external editor.
+ :param history: :class:`~prompt_toolkit.history.History` instance.
+ :param clipboard: :class:`~prompt_toolkit.clipboard.Clipboard` instance.
+ (e.g. :class:`~prompt_toolkit.clipboard.InMemoryClipboard`)
+ :param rprompt: Text or formatted text to be displayed on the right side.
+ This can also be a callable that returns (formatted) text.
+ :param bottom_toolbar: Formatted text or callable which is supposed to
+ return formatted text.
+ :param prompt_continuation: Text that needs to be displayed for a multiline
+ prompt continuation. This can either be formatted text or a callable
+ that takes a `prompt_width`, `line_number` and `wrap_count` as input
+ and returns formatted text. When this is `None` (the default), then
+ `prompt_width` spaces will be used.
+ :param complete_style: ``CompleteStyle.COLUMN``,
+ ``CompleteStyle.MULTI_COLUMN`` or ``CompleteStyle.READLINE_LIKE``.
+ :param mouse_support: `bool` or :class:`~prompt_toolkit.filters.Filter`
+ to enable mouse support.
+ :param placeholder: Text to be displayed when no input has been given
+ yet. Unlike the `default` parameter, this won't be returned as part of
+ the output ever. This can be formatted text or a callable that returns
+ formatted text.
+ :param refresh_interval: (number; in seconds) When given, refresh the UI
+ every so many seconds.
+ :param input: `Input` object. (Note that the preferred way to change the
+ input/output is by creating an `AppSession`.)
+ :param output: `Output` object.
+ """
+
+ _fields = (
+ "message",
+ "lexer",
+ "completer",
+ "complete_in_thread",
+ "is_password",
+ "editing_mode",
+ "key_bindings",
+ "is_password",
+ "bottom_toolbar",
+ "style",
+ "style_transformation",
+ "swap_light_and_dark_colors",
+ "color_depth",
+ "cursor",
+ "include_default_pygments_style",
+ "rprompt",
+ "multiline",
+ "prompt_continuation",
+ "wrap_lines",
+ "enable_history_search",
+ "search_ignore_case",
+ "complete_while_typing",
+ "validate_while_typing",
+ "complete_style",
+ "mouse_support",
+ "auto_suggest",
+ "clipboard",
+ "validator",
+ "refresh_interval",
+ "input_processors",
+ "placeholder",
+ "enable_system_prompt",
+ "enable_suspend",
+ "enable_open_in_editor",
+ "reserve_space_for_menu",
+ "tempfile_suffix",
+ "tempfile",
+ )
+
+ def __init__(
+ self,
+ message: AnyFormattedText = "",
+ *,
+ multiline: FilterOrBool = False,
+ wrap_lines: FilterOrBool = True,
+ is_password: FilterOrBool = False,
+ vi_mode: bool = False,
+ editing_mode: EditingMode = EditingMode.EMACS,
+ complete_while_typing: FilterOrBool = True,
+ validate_while_typing: FilterOrBool = True,
+ enable_history_search: FilterOrBool = False,
+ search_ignore_case: FilterOrBool = False,
+ lexer: Lexer | None = None,
+ enable_system_prompt: FilterOrBool = False,
+ enable_suspend: FilterOrBool = False,
+ enable_open_in_editor: FilterOrBool = False,
+ validator: Validator | None = None,
+ completer: Completer | None = None,
+ complete_in_thread: bool = False,
+ reserve_space_for_menu: int = 8,
+ complete_style: CompleteStyle = CompleteStyle.COLUMN,
+ auto_suggest: AutoSuggest | None = None,
+ style: BaseStyle | None = None,
+ style_transformation: StyleTransformation | None = None,
+ swap_light_and_dark_colors: FilterOrBool = False,
+ color_depth: ColorDepth | None = None,
+ cursor: AnyCursorShapeConfig = None,
+ include_default_pygments_style: FilterOrBool = True,
+ history: History | None = None,
+ clipboard: Clipboard | None = None,
+ prompt_continuation: PromptContinuationText | None = None,
+ rprompt: AnyFormattedText = None,
+ bottom_toolbar: AnyFormattedText = None,
+ mouse_support: FilterOrBool = False,
+ input_processors: list[Processor] | None = None,
+ placeholder: AnyFormattedText | None = None,
+ key_bindings: KeyBindingsBase | None = None,
+ erase_when_done: bool = False,
+ tempfile_suffix: str | Callable[[], str] | None = ".txt",
+ tempfile: str | Callable[[], str] | None = None,
+ refresh_interval: float = 0,
+ input: Input | None = None,
+ output: Output | None = None,
+ ) -> None:
+ history = history or InMemoryHistory()
+ clipboard = clipboard or InMemoryClipboard()
+
+ # Ensure backwards-compatibility, when `vi_mode` is passed.
+ if vi_mode:
+ editing_mode = EditingMode.VI
+
+ # Store all settings in this class.
+ self._input = input
+ self._output = output
+
+ # Store attributes.
+ # (All except 'editing_mode'.)
+ self.message = message
+ self.lexer = lexer
+ self.completer = completer
+ self.complete_in_thread = complete_in_thread
+ self.is_password = is_password
+ self.key_bindings = key_bindings
+ self.bottom_toolbar = bottom_toolbar
+ self.style = style
+ self.style_transformation = style_transformation
+ self.swap_light_and_dark_colors = swap_light_and_dark_colors
+ self.color_depth = color_depth
+ self.cursor = cursor
+ self.include_default_pygments_style = include_default_pygments_style
+ self.rprompt = rprompt
+ self.multiline = multiline
+ self.prompt_continuation = prompt_continuation
+ self.wrap_lines = wrap_lines
+ self.enable_history_search = enable_history_search
+ self.search_ignore_case = search_ignore_case
+ self.complete_while_typing = complete_while_typing
+ self.validate_while_typing = validate_while_typing
+ self.complete_style = complete_style
+ self.mouse_support = mouse_support
+ self.auto_suggest = auto_suggest
+ self.clipboard = clipboard
+ self.validator = validator
+ self.refresh_interval = refresh_interval
+ self.input_processors = input_processors
+ self.placeholder = placeholder
+ self.enable_system_prompt = enable_system_prompt
+ self.enable_suspend = enable_suspend
+ self.enable_open_in_editor = enable_open_in_editor
+ self.reserve_space_for_menu = reserve_space_for_menu
+ self.tempfile_suffix = tempfile_suffix
+ self.tempfile = tempfile
+
+ # Create buffers, layout and Application.
+ self.history = history
+ self.default_buffer = self._create_default_buffer()
+ self.search_buffer = self._create_search_buffer()
+ self.layout = self._create_layout()
+ self.app = self._create_application(editing_mode, erase_when_done)
+
+ def _dyncond(self, attr_name: str) -> Condition:
+ """
+ Dynamically take this setting from this 'PromptSession' class.
+ `attr_name` represents an attribute name of this class. Its value
+ can either be a boolean or a `Filter`.
+
+ This returns something that can be used as either a `Filter`
+ or `Filter`.
+ """
+
+ @Condition
+ def dynamic() -> bool:
+ value = cast(FilterOrBool, getattr(self, attr_name))
+ return to_filter(value)()
+
+ return dynamic
+
+ def _create_default_buffer(self) -> Buffer:
+ """
+ Create and return the default input buffer.
+ """
+ dyncond = self._dyncond
+
+ # Create buffers list.
+ def accept(buff: Buffer) -> bool:
+ """Accept the content of the default buffer. This is called when
+ the validation succeeds."""
+ cast(Application[str], get_app()).exit(result=buff.document.text)
+ return True # Keep text, we call 'reset' later on.
+
+ return Buffer(
+ name=DEFAULT_BUFFER,
+ # Make sure that complete_while_typing is disabled when
+ # enable_history_search is enabled. (First convert to Filter,
+ # to avoid doing bitwise operations on bool objects.)
+ complete_while_typing=Condition(
+ lambda: is_true(self.complete_while_typing)
+ and not is_true(self.enable_history_search)
+ and not self.complete_style == CompleteStyle.READLINE_LIKE
+ ),
+ validate_while_typing=dyncond("validate_while_typing"),
+ enable_history_search=dyncond("enable_history_search"),
+ validator=DynamicValidator(lambda: self.validator),
+ completer=DynamicCompleter(
+ lambda: ThreadedCompleter(self.completer)
+ if self.complete_in_thread and self.completer
+ else self.completer
+ ),
+ history=self.history,
+ auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest),
+ accept_handler=accept,
+ tempfile_suffix=lambda: to_str(self.tempfile_suffix or ""),
+ tempfile=lambda: to_str(self.tempfile or ""),
+ )
+
+ def _create_search_buffer(self) -> Buffer:
+ return Buffer(name=SEARCH_BUFFER)
+
+ def _create_layout(self) -> Layout:
+ """
+ Create `Layout` for this prompt.
+ """
+ dyncond = self._dyncond
+
+ # Create functions that will dynamically split the prompt. (If we have
+ # a multiline prompt.)
+ (
+ has_before_fragments,
+ get_prompt_text_1,
+ get_prompt_text_2,
+ ) = _split_multiline_prompt(self._get_prompt)
+
+ default_buffer = self.default_buffer
+ search_buffer = self.search_buffer
+
+ # Create processors list.
+ @Condition
+ def display_placeholder() -> bool:
+ return self.placeholder is not None and self.default_buffer.text == ""
+
+ all_input_processors = [
+ HighlightIncrementalSearchProcessor(),
+ HighlightSelectionProcessor(),
+ ConditionalProcessor(
+ AppendAutoSuggestion(), has_focus(default_buffer) & ~is_done
+ ),
+ ConditionalProcessor(PasswordProcessor(), dyncond("is_password")),
+ DisplayMultipleCursors(),
+ # Users can insert processors here.
+ DynamicProcessor(lambda: merge_processors(self.input_processors or [])),
+ ConditionalProcessor(
+ AfterInput(lambda: self.placeholder),
+ filter=display_placeholder,
+ ),
+ ]
+
+ # Create bottom toolbars.
+ 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=Condition(lambda: self.bottom_toolbar is not None)
+ & ~is_done
+ & renderer_height_is_known,
+ )
+
+ search_toolbar = SearchToolbar(
+ search_buffer, ignore_case=dyncond("search_ignore_case")
+ )
+
+ search_buffer_control = SearchBufferControl(
+ buffer=search_buffer,
+ input_processors=[ReverseSearchProcessor()],
+ ignore_case=dyncond("search_ignore_case"),
+ )
+
+ system_toolbar = SystemToolbar(
+ enable_global_bindings=dyncond("enable_system_prompt")
+ )
+
+ def get_search_buffer_control() -> SearchBufferControl:
+ "Return the UIControl to be focused when searching start."
+ if is_true(self.multiline):
+ return search_toolbar.control
+ else:
+ return search_buffer_control
+
+ default_buffer_control = BufferControl(
+ buffer=default_buffer,
+ search_buffer_control=get_search_buffer_control,
+ input_processors=all_input_processors,
+ include_default_input_processors=False,
+ lexer=DynamicLexer(lambda: self.lexer),
+ preview_search=True,
+ )
+
+ default_buffer_window = Window(
+ default_buffer_control,
+ height=self._get_default_buffer_control_height,
+ get_line_prefix=partial(
+ self._get_line_prefix, get_prompt_text_2=get_prompt_text_2
+ ),
+ wrap_lines=dyncond("wrap_lines"),
+ )
+
+ @Condition
+ def multi_column_complete_style() -> bool:
+ return self.complete_style == CompleteStyle.MULTI_COLUMN
+
+ # Build the layout.
+ layout = HSplit(
+ [
+ # The main input, with completion menus floating on top of it.
+ FloatContainer(
+ HSplit(
+ [
+ ConditionalContainer(
+ Window(
+ FormattedTextControl(get_prompt_text_1),
+ dont_extend_height=True,
+ ),
+ Condition(has_before_fragments),
+ ),
+ ConditionalContainer(
+ default_buffer_window,
+ Condition(
+ lambda: get_app().layout.current_control
+ != search_buffer_control
+ ),
+ ),
+ ConditionalContainer(
+ Window(search_buffer_control),
+ Condition(
+ lambda: get_app().layout.current_control
+ == search_buffer_control
+ ),
+ ),
+ ]
+ ),
+ [
+ # Completion menus.
+ # NOTE: Especially the multi-column menu needs to be
+ # transparent, because the shape is not always
+ # rectangular due to the meta-text below the menu.
+ Float(
+ xcursor=True,
+ ycursor=True,
+ transparent=True,
+ content=CompletionsMenu(
+ max_height=16,
+ scroll_offset=1,
+ extra_filter=has_focus(default_buffer)
+ & ~multi_column_complete_style,
+ ),
+ ),
+ Float(
+ xcursor=True,
+ ycursor=True,
+ transparent=True,
+ content=MultiColumnCompletionsMenu(
+ show_meta=True,
+ extra_filter=has_focus(default_buffer)
+ & multi_column_complete_style,
+ ),
+ ),
+ # The right prompt.
+ Float(
+ right=0,
+ top=0,
+ hide_when_covering_content=True,
+ content=_RPrompt(lambda: self.rprompt),
+ ),
+ ],
+ ),
+ ConditionalContainer(ValidationToolbar(), filter=~is_done),
+ ConditionalContainer(
+ system_toolbar, dyncond("enable_system_prompt") & ~is_done
+ ),
+ # In multiline mode, we use two toolbars for 'arg' and 'search'.
+ ConditionalContainer(
+ Window(FormattedTextControl(self._get_arg_text), height=1),
+ dyncond("multiline") & has_arg,
+ ),
+ ConditionalContainer(search_toolbar, dyncond("multiline") & ~is_done),
+ bottom_toolbar,
+ ]
+ )
+
+ return Layout(layout, default_buffer_window)
+
+ def _create_application(
+ self, editing_mode: EditingMode, erase_when_done: bool
+ ) -> Application[_T]:
+ """
+ Create the `Application` object.
+ """
+ dyncond = self._dyncond
+
+ # Default key bindings.
+ auto_suggest_bindings = load_auto_suggest_bindings()
+ open_in_editor_bindings = load_open_in_editor_bindings()
+ prompt_bindings = self._create_prompt_bindings()
+
+ # Create application
+ application: Application[_T] = Application(
+ layout=self.layout,
+ style=DynamicStyle(lambda: self.style),
+ style_transformation=merge_style_transformations(
+ [
+ DynamicStyleTransformation(lambda: self.style_transformation),
+ ConditionalStyleTransformation(
+ SwapLightAndDarkStyleTransformation(),
+ dyncond("swap_light_and_dark_colors"),
+ ),
+ ]
+ ),
+ include_default_pygments_style=dyncond("include_default_pygments_style"),
+ clipboard=DynamicClipboard(lambda: self.clipboard),
+ key_bindings=merge_key_bindings(
+ [
+ merge_key_bindings(
+ [
+ auto_suggest_bindings,
+ ConditionalKeyBindings(
+ open_in_editor_bindings,
+ dyncond("enable_open_in_editor")
+ & has_focus(DEFAULT_BUFFER),
+ ),
+ prompt_bindings,
+ ]
+ ),
+ DynamicKeyBindings(lambda: self.key_bindings),
+ ]
+ ),
+ mouse_support=dyncond("mouse_support"),
+ editing_mode=editing_mode,
+ erase_when_done=erase_when_done,
+ reverse_vi_search_direction=True,
+ color_depth=lambda: self.color_depth,
+ cursor=DynamicCursorShapeConfig(lambda: self.cursor),
+ refresh_interval=self.refresh_interval,
+ input=self._input,
+ output=self._output,
+ )
+
+ # During render time, make sure that we focus the right search control
+ # (if we are searching). - This could be useful if people make the
+ # 'multiline' property dynamic.
+ """
+ def on_render(app):
+ multiline = is_true(self.multiline)
+ current_control = app.layout.current_control
+
+ if multiline:
+ if current_control == search_buffer_control:
+ app.layout.current_control = search_toolbar.control
+ app.invalidate()
+ else:
+ if current_control == search_toolbar.control:
+ app.layout.current_control = search_buffer_control
+ app.invalidate()
+
+ app.on_render += on_render
+ """
+
+ return application
+
+ def _create_prompt_bindings(self) -> KeyBindings:
+ """
+ Create the KeyBindings for a prompt application.
+ """
+ kb = KeyBindings()
+ handle = kb.add
+ default_focused = has_focus(DEFAULT_BUFFER)
+
+ @Condition
+ def do_accept() -> bool:
+ return not is_true(self.multiline) and self.app.layout.has_focus(
+ DEFAULT_BUFFER
+ )
+
+ @handle("enter", filter=do_accept & default_focused)
+ def _accept_input(event: E) -> None:
+ "Accept input when enter has been pressed."
+ self.default_buffer.validate_and_handle()
+
+ @Condition
+ def readline_complete_style() -> bool:
+ return self.complete_style == CompleteStyle.READLINE_LIKE
+
+ @handle("tab", filter=readline_complete_style & default_focused)
+ def _complete_like_readline(event: E) -> None:
+ "Display completions (like Readline)."
+ display_completions_like_readline(event)
+
+ @handle("c-c", filter=default_focused)
+ @handle("<sigint>")
+ def _keyboard_interrupt(event: E) -> None:
+ "Abort when Control-C has been pressed."
+ event.app.exit(exception=KeyboardInterrupt, style="class:aborting")
+
+ @Condition
+ def ctrl_d_condition() -> bool:
+ """Ctrl-D binding is only active when the default buffer is selected
+ and empty."""
+ app = get_app()
+ return (
+ app.current_buffer.name == DEFAULT_BUFFER
+ and not app.current_buffer.text
+ )
+
+ @handle("c-d", filter=ctrl_d_condition & default_focused)
+ def _eof(event: E) -> None:
+ "Exit when Control-D has been pressed."
+ event.app.exit(exception=EOFError, style="class:exiting")
+
+ suspend_supported = Condition(suspend_to_background_supported)
+
+ @Condition
+ def enable_suspend() -> bool:
+ return to_filter(self.enable_suspend)()
+
+ @handle("c-z", filter=suspend_supported & enable_suspend)
+ def _suspend(event: E) -> None:
+ """
+ Suspend process to background.
+ """
+ event.app.suspend_to_background()
+
+ return kb
+
+ def prompt(
+ self,
+ # When any of these arguments are passed, this value is overwritten
+ # in this PromptSession.
+ message: AnyFormattedText | None = None,
+ # `message` should go first, because people call it as
+ # positional argument.
+ *,
+ editing_mode: EditingMode | None = None,
+ refresh_interval: float | None = None,
+ vi_mode: bool | None = None,
+ lexer: Lexer | None = None,
+ completer: Completer | None = None,
+ complete_in_thread: bool | None = None,
+ is_password: bool | None = None,
+ key_bindings: KeyBindingsBase | None = None,
+ bottom_toolbar: AnyFormattedText | None = None,
+ style: BaseStyle | None = None,
+ color_depth: ColorDepth | None = None,
+ cursor: AnyCursorShapeConfig | None = None,
+ include_default_pygments_style: FilterOrBool | None = None,
+ style_transformation: StyleTransformation | None = None,
+ swap_light_and_dark_colors: FilterOrBool | None = None,
+ rprompt: AnyFormattedText | None = None,
+ multiline: FilterOrBool | None = None,
+ prompt_continuation: PromptContinuationText | None = None,
+ wrap_lines: FilterOrBool | None = None,
+ enable_history_search: FilterOrBool | None = None,
+ search_ignore_case: FilterOrBool | None = None,
+ complete_while_typing: FilterOrBool | None = None,
+ validate_while_typing: FilterOrBool | None = None,
+ complete_style: CompleteStyle | None = None,
+ auto_suggest: AutoSuggest | None = None,
+ validator: Validator | None = None,
+ clipboard: Clipboard | None = None,
+ mouse_support: FilterOrBool | None = None,
+ input_processors: list[Processor] | None = None,
+ placeholder: AnyFormattedText | None = None,
+ reserve_space_for_menu: int | None = None,
+ enable_system_prompt: FilterOrBool | None = None,
+ enable_suspend: FilterOrBool | None = None,
+ enable_open_in_editor: FilterOrBool | None = None,
+ tempfile_suffix: str | Callable[[], str] | None = None,
+ tempfile: str | Callable[[], str] | None = None,
+ # Following arguments are specific to the current `prompt()` call.
+ default: str | Document = "",
+ accept_default: bool = False,
+ pre_run: Callable[[], None] | None = None,
+ set_exception_handler: bool = True,
+ handle_sigint: bool = True,
+ in_thread: bool = False,
+ inputhook: InputHook | None = None,
+ ) -> _T:
+ """
+ Display the prompt.
+
+ The first set of arguments is a subset of the :class:`~.PromptSession`
+ class itself. For these, passing in ``None`` will keep the current
+ values that are active in the session. Passing in a value will set the
+ attribute for the session, which means that it applies to the current,
+ but also to the next prompts.
+
+ Note that in order to erase a ``Completer``, ``Validator`` or
+ ``AutoSuggest``, you can't use ``None``. Instead pass in a
+ ``DummyCompleter``, ``DummyValidator`` or ``DummyAutoSuggest`` instance
+ respectively. For a ``Lexer`` you can pass in an empty ``SimpleLexer``.
+
+ Additional arguments, specific for this prompt:
+
+ :param default: The default input text to be shown. (This can be edited
+ by the user).
+ :param accept_default: When `True`, automatically accept the default
+ value without allowing the user to edit the input.
+ :param pre_run: Callable, called at the start of `Application.run`.
+ :param in_thread: Run the prompt in a background thread; block the
+ current thread. This avoids interference with an event loop in the
+ current thread. Like `Application.run(in_thread=True)`.
+
+ This method will raise ``KeyboardInterrupt`` when control-c has been
+ pressed (for abort) and ``EOFError`` when control-d has been pressed
+ (for exit).
+ """
+ # NOTE: We used to create a backup of the PromptSession attributes and
+ # restore them after exiting the prompt. This code has been
+ # removed, because it was confusing and didn't really serve a use
+ # case. (People were changing `Application.editing_mode`
+ # dynamically and surprised that it was reset after every call.)
+
+ # NOTE 2: YES, this is a lot of repeation below...
+ # However, it is a very convenient for a user to accept all
+ # these parameters in this `prompt` method as well. We could
+ # use `locals()` and `setattr` to avoid the repetition, but
+ # then we loose the advantage of mypy and pyflakes to be able
+ # to verify the code.
+ if message is not None:
+ self.message = message
+ if editing_mode is not None:
+ self.editing_mode = editing_mode
+ if refresh_interval is not None:
+ self.refresh_interval = refresh_interval
+ if vi_mode:
+ self.editing_mode = EditingMode.VI
+ if lexer is not None:
+ self.lexer = lexer
+ if completer is not None:
+ self.completer = completer
+ if complete_in_thread is not None:
+ self.complete_in_thread = complete_in_thread
+ if is_password is not None:
+ self.is_password = is_password
+ if key_bindings is not None:
+ self.key_bindings = key_bindings
+ if bottom_toolbar is not None:
+ self.bottom_toolbar = bottom_toolbar
+ if style is not None:
+ self.style = style
+ if color_depth is not None:
+ self.color_depth = color_depth
+ if cursor is not None:
+ self.cursor = cursor
+ if include_default_pygments_style is not None:
+ self.include_default_pygments_style = include_default_pygments_style
+ if style_transformation is not None:
+ self.style_transformation = style_transformation
+ if swap_light_and_dark_colors is not None:
+ self.swap_light_and_dark_colors = swap_light_and_dark_colors
+ if rprompt is not None:
+ self.rprompt = rprompt
+ if multiline is not None:
+ self.multiline = multiline
+ if prompt_continuation is not None:
+ self.prompt_continuation = prompt_continuation
+ if wrap_lines is not None:
+ self.wrap_lines = wrap_lines
+ if enable_history_search is not None:
+ self.enable_history_search = enable_history_search
+ if search_ignore_case is not None:
+ self.search_ignore_case = search_ignore_case
+ if complete_while_typing is not None:
+ self.complete_while_typing = complete_while_typing
+ if validate_while_typing is not None:
+ self.validate_while_typing = validate_while_typing
+ if complete_style is not None:
+ self.complete_style = complete_style
+ if auto_suggest is not None:
+ self.auto_suggest = auto_suggest
+ if validator is not None:
+ self.validator = validator
+ if clipboard is not None:
+ self.clipboard = clipboard
+ if mouse_support is not None:
+ self.mouse_support = mouse_support
+ if input_processors is not None:
+ self.input_processors = input_processors
+ if placeholder is not None:
+ self.placeholder = placeholder
+ if reserve_space_for_menu is not None:
+ self.reserve_space_for_menu = reserve_space_for_menu
+ if enable_system_prompt is not None:
+ self.enable_system_prompt = enable_system_prompt
+ if enable_suspend is not None:
+ self.enable_suspend = enable_suspend
+ if enable_open_in_editor is not None:
+ self.enable_open_in_editor = enable_open_in_editor
+ if tempfile_suffix is not None:
+ self.tempfile_suffix = tempfile_suffix
+ if tempfile is not None:
+ self.tempfile = tempfile
+
+ self._add_pre_run_callables(pre_run, accept_default)
+ self.default_buffer.reset(
+ default if isinstance(default, Document) else Document(default)
+ )
+ self.app.refresh_interval = self.refresh_interval # This is not reactive.
+
+ # If we are using the default output, and have a dumb terminal. Use the
+ # dumb prompt.
+ if self._output is None and is_dumb_terminal():
+ with self._dumb_prompt(self.message) as dump_app:
+ return dump_app.run(in_thread=in_thread, handle_sigint=handle_sigint)
+
+ return self.app.run(
+ set_exception_handler=set_exception_handler,
+ in_thread=in_thread,
+ handle_sigint=handle_sigint,
+ inputhook=inputhook,
+ )
+
+ @contextmanager
+ def _dumb_prompt(self, message: AnyFormattedText = "") -> Iterator[Application[_T]]:
+ """
+ Create prompt `Application` for prompt function for dumb terminals.
+
+ Dumb terminals have minimum rendering capabilities. We can only print
+ text to the screen. We can't use colors, and we can't do cursor
+ movements. The Emacs inferior shell is an example of a dumb terminal.
+
+ We will show the prompt, and wait for the input. We still handle arrow
+ keys, and all custom key bindings, but we don't really render the
+ cursor movements. Instead we only print the typed character that's
+ right before the cursor.
+ """
+ # Send prompt to output.
+ self.output.write(fragment_list_to_text(to_formatted_text(self.message)))
+ self.output.flush()
+
+ # Key bindings for the dumb prompt: mostly the same as the full prompt.
+ key_bindings: KeyBindingsBase = self._create_prompt_bindings()
+ if self.key_bindings:
+ key_bindings = merge_key_bindings([self.key_bindings, key_bindings])
+
+ # Create and run application.
+ application = cast(
+ Application[_T],
+ Application(
+ input=self.input,
+ output=DummyOutput(),
+ layout=self.layout,
+ key_bindings=key_bindings,
+ ),
+ )
+
+ def on_text_changed(_: object) -> None:
+ self.output.write(self.default_buffer.document.text_before_cursor[-1:])
+ self.output.flush()
+
+ self.default_buffer.on_text_changed += on_text_changed
+
+ try:
+ yield application
+ finally:
+ # Render line ending.
+ self.output.write("\r\n")
+ self.output.flush()
+
+ self.default_buffer.on_text_changed -= on_text_changed
+
+ async def prompt_async(
+ self,
+ # When any of these arguments are passed, this value is overwritten
+ # in this PromptSession.
+ message: AnyFormattedText | None = None,
+ # `message` should go first, because people call it as
+ # positional argument.
+ *,
+ editing_mode: EditingMode | None = None,
+ refresh_interval: float | None = None,
+ vi_mode: bool | None = None,
+ lexer: Lexer | None = None,
+ completer: Completer | None = None,
+ complete_in_thread: bool | None = None,
+ is_password: bool | None = None,
+ key_bindings: KeyBindingsBase | None = None,
+ bottom_toolbar: AnyFormattedText | None = None,
+ style: BaseStyle | None = None,
+ color_depth: ColorDepth | None = None,
+ cursor: CursorShapeConfig | None = None,
+ include_default_pygments_style: FilterOrBool | None = None,
+ style_transformation: StyleTransformation | None = None,
+ swap_light_and_dark_colors: FilterOrBool | None = None,
+ rprompt: AnyFormattedText | None = None,
+ multiline: FilterOrBool | None = None,
+ prompt_continuation: PromptContinuationText | None = None,
+ wrap_lines: FilterOrBool | None = None,
+ enable_history_search: FilterOrBool | None = None,
+ search_ignore_case: FilterOrBool | None = None,
+ complete_while_typing: FilterOrBool | None = None,
+ validate_while_typing: FilterOrBool | None = None,
+ complete_style: CompleteStyle | None = None,
+ auto_suggest: AutoSuggest | None = None,
+ validator: Validator | None = None,
+ clipboard: Clipboard | None = None,
+ mouse_support: FilterOrBool | None = None,
+ input_processors: list[Processor] | None = None,
+ placeholder: AnyFormattedText | None = None,
+ reserve_space_for_menu: int | None = None,
+ enable_system_prompt: FilterOrBool | None = None,
+ enable_suspend: FilterOrBool | None = None,
+ enable_open_in_editor: FilterOrBool | None = None,
+ tempfile_suffix: str | Callable[[], str] | None = None,
+ tempfile: str | Callable[[], str] | None = None,
+ # Following arguments are specific to the current `prompt()` call.
+ default: str | Document = "",
+ accept_default: bool = False,
+ pre_run: Callable[[], None] | None = None,
+ set_exception_handler: bool = True,
+ handle_sigint: bool = True,
+ ) -> _T:
+ if message is not None:
+ self.message = message
+ if editing_mode is not None:
+ self.editing_mode = editing_mode
+ if refresh_interval is not None:
+ self.refresh_interval = refresh_interval
+ if vi_mode:
+ self.editing_mode = EditingMode.VI
+ if lexer is not None:
+ self.lexer = lexer
+ if completer is not None:
+ self.completer = completer
+ if complete_in_thread is not None:
+ self.complete_in_thread = complete_in_thread
+ if is_password is not None:
+ self.is_password = is_password
+ if key_bindings is not None:
+ self.key_bindings = key_bindings
+ if bottom_toolbar is not None:
+ self.bottom_toolbar = bottom_toolbar
+ if style is not None:
+ self.style = style
+ if color_depth is not None:
+ self.color_depth = color_depth
+ if cursor is not None:
+ self.cursor = cursor
+ if include_default_pygments_style is not None:
+ self.include_default_pygments_style = include_default_pygments_style
+ if style_transformation is not None:
+ self.style_transformation = style_transformation
+ if swap_light_and_dark_colors is not None:
+ self.swap_light_and_dark_colors = swap_light_and_dark_colors
+ if rprompt is not None:
+ self.rprompt = rprompt
+ if multiline is not None:
+ self.multiline = multiline
+ if prompt_continuation is not None:
+ self.prompt_continuation = prompt_continuation
+ if wrap_lines is not None:
+ self.wrap_lines = wrap_lines
+ if enable_history_search is not None:
+ self.enable_history_search = enable_history_search
+ if search_ignore_case is not None:
+ self.search_ignore_case = search_ignore_case
+ if complete_while_typing is not None:
+ self.complete_while_typing = complete_while_typing
+ if validate_while_typing is not None:
+ self.validate_while_typing = validate_while_typing
+ if complete_style is not None:
+ self.complete_style = complete_style
+ if auto_suggest is not None:
+ self.auto_suggest = auto_suggest
+ if validator is not None:
+ self.validator = validator
+ if clipboard is not None:
+ self.clipboard = clipboard
+ if mouse_support is not None:
+ self.mouse_support = mouse_support
+ if input_processors is not None:
+ self.input_processors = input_processors
+ if placeholder is not None:
+ self.placeholder = placeholder
+ if reserve_space_for_menu is not None:
+ self.reserve_space_for_menu = reserve_space_for_menu
+ if enable_system_prompt is not None:
+ self.enable_system_prompt = enable_system_prompt
+ if enable_suspend is not None:
+ self.enable_suspend = enable_suspend
+ if enable_open_in_editor is not None:
+ self.enable_open_in_editor = enable_open_in_editor
+ if tempfile_suffix is not None:
+ self.tempfile_suffix = tempfile_suffix
+ if tempfile is not None:
+ self.tempfile = tempfile
+
+ self._add_pre_run_callables(pre_run, accept_default)
+ self.default_buffer.reset(
+ default if isinstance(default, Document) else Document(default)
+ )
+ self.app.refresh_interval = self.refresh_interval # This is not reactive.
+
+ # If we are using the default output, and have a dumb terminal. Use the
+ # dumb prompt.
+ if self._output is None and is_dumb_terminal():
+ with self._dumb_prompt(self.message) as dump_app:
+ return await dump_app.run_async(handle_sigint=handle_sigint)
+
+ return await self.app.run_async(
+ set_exception_handler=set_exception_handler, handle_sigint=handle_sigint
+ )
+
+ def _add_pre_run_callables(
+ self, pre_run: Callable[[], None] | None, accept_default: bool
+ ) -> None:
+ def pre_run2() -> None:
+ if pre_run:
+ pre_run()
+
+ if accept_default:
+ # Validate and handle input. We use `call_from_executor` in
+ # order to run it "soon" (during the next iteration of the
+ # event loop), instead of right now. Otherwise, it won't
+ # display the default value.
+ get_running_loop().call_soon(self.default_buffer.validate_and_handle)
+
+ self.app.pre_run_callables.append(pre_run2)
+
+ @property
+ def editing_mode(self) -> EditingMode:
+ return self.app.editing_mode
+
+ @editing_mode.setter
+ def editing_mode(self, value: EditingMode) -> None:
+ self.app.editing_mode = value
+
+ def _get_default_buffer_control_height(self) -> Dimension:
+ # If there is an autocompletion menu to be shown, make sure that our
+ # layout has at least a minimal height in order to display it.
+ if (
+ self.completer is not None
+ and self.complete_style != CompleteStyle.READLINE_LIKE
+ ):
+ space = self.reserve_space_for_menu
+ else:
+ space = 0
+
+ if space and not get_app().is_done:
+ buff = self.default_buffer
+
+ # Reserve the space, either when there are completions, or when
+ # `complete_while_typing` is true and we expect completions very
+ # soon.
+ if buff.complete_while_typing() or buff.complete_state is not None:
+ return Dimension(min=space)
+
+ return Dimension()
+
+ def _get_prompt(self) -> StyleAndTextTuples:
+ return to_formatted_text(self.message, style="class:prompt")
+
+ def _get_continuation(
+ self, width: int, line_number: int, wrap_count: int
+ ) -> StyleAndTextTuples:
+ """
+ Insert the prompt continuation.
+
+ :param width: The width that was used for the prompt. (more or less can
+ be used.)
+ :param line_number:
+ :param wrap_count: Amount of times that the line has been wrapped.
+ """
+ prompt_continuation = self.prompt_continuation
+
+ if callable(prompt_continuation):
+ continuation: AnyFormattedText = prompt_continuation(
+ width, line_number, wrap_count
+ )
+ else:
+ continuation = prompt_continuation
+
+ # When the continuation prompt is not given, choose the same width as
+ # the actual prompt.
+ if continuation is None and is_true(self.multiline):
+ continuation = " " * width
+
+ return to_formatted_text(continuation, style="class:prompt-continuation")
+
+ def _get_line_prefix(
+ self,
+ line_number: int,
+ wrap_count: int,
+ get_prompt_text_2: _StyleAndTextTuplesCallable,
+ ) -> StyleAndTextTuples:
+ """
+ Return whatever needs to be inserted before every line.
+ (the prompt, or a line continuation.)
+ """
+ # First line: display the "arg" or the prompt.
+ if line_number == 0 and wrap_count == 0:
+ if not is_true(self.multiline) and get_app().key_processor.arg is not None:
+ return self._inline_arg()
+ else:
+ return get_prompt_text_2()
+
+ # For the next lines, display the appropriate continuation.
+ prompt_width = get_cwidth(fragment_list_to_text(get_prompt_text_2()))
+ return self._get_continuation(prompt_width, line_number, wrap_count)
+
+ def _get_arg_text(self) -> StyleAndTextTuples:
+ "'arg' toolbar, for in multiline mode."
+ arg = self.app.key_processor.arg
+ if arg is None:
+ # Should not happen because of the `has_arg` filter in the layout.
+ return []
+
+ if arg == "-":
+ arg = "-1"
+
+ return [("class:arg-toolbar", "Repeat: "), ("class:arg-toolbar.text", arg)]
+
+ def _inline_arg(self) -> StyleAndTextTuples:
+ "'arg' prefix, for in single line mode."
+ app = get_app()
+ if app.key_processor.arg is None:
+ return []
+ else:
+ arg = app.key_processor.arg
+
+ return [
+ ("class:prompt.arg", "(arg: "),
+ ("class:prompt.arg.text", str(arg)),
+ ("class:prompt.arg", ") "),
+ ]
+
+ # Expose the Input and Output objects as attributes, mainly for
+ # backward-compatibility.
+
+ @property
+ def input(self) -> Input:
+ return self.app.input
+
+ @property
+ def output(self) -> Output:
+ return self.app.output
+
+
+def prompt(
+ message: AnyFormattedText | None = None,
+ *,
+ history: History | None = None,
+ editing_mode: EditingMode | None = None,
+ refresh_interval: float | None = None,
+ vi_mode: bool | None = None,
+ lexer: Lexer | None = None,
+ completer: Completer | None = None,
+ complete_in_thread: bool | None = None,
+ is_password: bool | None = None,
+ key_bindings: KeyBindingsBase | None = None,
+ bottom_toolbar: AnyFormattedText | None = None,
+ style: BaseStyle | None = None,
+ color_depth: ColorDepth | None = None,
+ cursor: AnyCursorShapeConfig = None,
+ include_default_pygments_style: FilterOrBool | None = None,
+ style_transformation: StyleTransformation | None = None,
+ swap_light_and_dark_colors: FilterOrBool | None = None,
+ rprompt: AnyFormattedText | None = None,
+ multiline: FilterOrBool | None = None,
+ prompt_continuation: PromptContinuationText | None = None,
+ wrap_lines: FilterOrBool | None = None,
+ enable_history_search: FilterOrBool | None = None,
+ search_ignore_case: FilterOrBool | None = None,
+ complete_while_typing: FilterOrBool | None = None,
+ validate_while_typing: FilterOrBool | None = None,
+ complete_style: CompleteStyle | None = None,
+ auto_suggest: AutoSuggest | None = None,
+ validator: Validator | None = None,
+ clipboard: Clipboard | None = None,
+ mouse_support: FilterOrBool | None = None,
+ input_processors: list[Processor] | None = None,
+ placeholder: AnyFormattedText | None = None,
+ reserve_space_for_menu: int | None = None,
+ enable_system_prompt: FilterOrBool | None = None,
+ enable_suspend: FilterOrBool | None = None,
+ enable_open_in_editor: FilterOrBool | None = None,
+ tempfile_suffix: str | Callable[[], str] | None = None,
+ tempfile: str | Callable[[], str] | None = None,
+ # Following arguments are specific to the current `prompt()` call.
+ default: str = "",
+ accept_default: bool = False,
+ pre_run: Callable[[], None] | None = None,
+ set_exception_handler: bool = True,
+ handle_sigint: bool = True,
+ in_thread: bool = False,
+ inputhook: InputHook | None = None,
+) -> str:
+ """
+ The global `prompt` function. This will create a new `PromptSession`
+ instance for every call.
+ """
+ # The history is the only attribute that has to be passed to the
+ # `PromptSession`, it can't be passed into the `prompt()` method.
+ session: PromptSession[str] = PromptSession(history=history)
+
+ return session.prompt(
+ message,
+ editing_mode=editing_mode,
+ refresh_interval=refresh_interval,
+ vi_mode=vi_mode,
+ lexer=lexer,
+ completer=completer,
+ complete_in_thread=complete_in_thread,
+ is_password=is_password,
+ key_bindings=key_bindings,
+ bottom_toolbar=bottom_toolbar,
+ style=style,
+ color_depth=color_depth,
+ cursor=cursor,
+ include_default_pygments_style=include_default_pygments_style,
+ style_transformation=style_transformation,
+ swap_light_and_dark_colors=swap_light_and_dark_colors,
+ rprompt=rprompt,
+ multiline=multiline,
+ prompt_continuation=prompt_continuation,
+ wrap_lines=wrap_lines,
+ enable_history_search=enable_history_search,
+ search_ignore_case=search_ignore_case,
+ complete_while_typing=complete_while_typing,
+ validate_while_typing=validate_while_typing,
+ complete_style=complete_style,
+ auto_suggest=auto_suggest,
+ validator=validator,
+ clipboard=clipboard,
+ mouse_support=mouse_support,
+ input_processors=input_processors,
+ placeholder=placeholder,
+ reserve_space_for_menu=reserve_space_for_menu,
+ enable_system_prompt=enable_system_prompt,
+ enable_suspend=enable_suspend,
+ enable_open_in_editor=enable_open_in_editor,
+ tempfile_suffix=tempfile_suffix,
+ tempfile=tempfile,
+ default=default,
+ accept_default=accept_default,
+ pre_run=pre_run,
+ set_exception_handler=set_exception_handler,
+ handle_sigint=handle_sigint,
+ in_thread=in_thread,
+ inputhook=inputhook,
+ )
+
+
+prompt.__doc__ = PromptSession.prompt.__doc__
+
+
+def create_confirm_session(
+ message: str, suffix: str = " (y/n) "
+) -> PromptSession[bool]:
+ """
+ Create a `PromptSession` object for the 'confirm' function.
+ """
+ bindings = KeyBindings()
+
+ @bindings.add("y")
+ @bindings.add("Y")
+ def yes(event: E) -> None:
+ session.default_buffer.text = "y"
+ event.app.exit(result=True)
+
+ @bindings.add("n")
+ @bindings.add("N")
+ def no(event: E) -> None:
+ session.default_buffer.text = "n"
+ event.app.exit(result=False)
+
+ @bindings.add(Keys.Any)
+ def _(event: E) -> None:
+ "Disallow inserting other text."
+ pass
+
+ complete_message = merge_formatted_text([message, suffix])
+ session: PromptSession[bool] = PromptSession(
+ complete_message, key_bindings=bindings
+ )
+ return session
+
+
+def confirm(message: str = "Confirm?", suffix: str = " (y/n) ") -> bool:
+ """
+ Display a confirmation prompt that returns True/False.
+ """
+ session = create_confirm_session(message, suffix)
+ return session.prompt()
diff --git a/src/prompt_toolkit/shortcuts/utils.py b/src/prompt_toolkit/shortcuts/utils.py
new file mode 100644
index 0000000..abf4fd2
--- /dev/null
+++ b/src/prompt_toolkit/shortcuts/utils.py
@@ -0,0 +1,239 @@
+from __future__ import annotations
+
+from asyncio.events import AbstractEventLoop
+from typing import TYPE_CHECKING, Any, TextIO
+
+from prompt_toolkit.application import Application
+from prompt_toolkit.application.current import get_app_or_none, get_app_session
+from prompt_toolkit.application.run_in_terminal import run_in_terminal
+from prompt_toolkit.formatted_text import (
+ FormattedText,
+ StyleAndTextTuples,
+ to_formatted_text,
+)
+from prompt_toolkit.input import DummyInput
+from prompt_toolkit.layout import Layout
+from prompt_toolkit.output import ColorDepth, Output
+from prompt_toolkit.output.defaults import create_output
+from prompt_toolkit.renderer import (
+ print_formatted_text as renderer_print_formatted_text,
+)
+from prompt_toolkit.styles import (
+ BaseStyle,
+ StyleTransformation,
+ default_pygments_style,
+ default_ui_style,
+ merge_styles,
+)
+
+if TYPE_CHECKING:
+ from prompt_toolkit.layout.containers import AnyContainer
+
+__all__ = [
+ "print_formatted_text",
+ "print_container",
+ "clear",
+ "set_title",
+ "clear_title",
+]
+
+
+def print_formatted_text(
+ *values: Any,
+ sep: str = " ",
+ end: str = "\n",
+ file: TextIO | None = None,
+ flush: bool = False,
+ style: BaseStyle | None = None,
+ output: Output | None = None,
+ color_depth: ColorDepth | None = None,
+ style_transformation: StyleTransformation | None = None,
+ include_default_pygments_style: bool = True,
+) -> None:
+ """
+ ::
+
+ print_formatted_text(*values, sep=' ', end='\\n', file=None, flush=False, style=None, output=None)
+
+ Print text to stdout. This is supposed to be compatible with Python's print
+ function, but supports printing of formatted text. You can pass a
+ :class:`~prompt_toolkit.formatted_text.FormattedText`,
+ :class:`~prompt_toolkit.formatted_text.HTML` or
+ :class:`~prompt_toolkit.formatted_text.ANSI` object to print formatted
+ text.
+
+ * Print HTML as follows::
+
+ print_formatted_text(HTML('<i>Some italic text</i> <ansired>This is red!</ansired>'))
+
+ style = Style.from_dict({
+ 'hello': '#ff0066',
+ 'world': '#884444 italic',
+ })
+ print_formatted_text(HTML('<hello>Hello</hello> <world>world</world>!'), style=style)
+
+ * Print a list of (style_str, text) tuples in the given style to the
+ output. E.g.::
+
+ style = Style.from_dict({
+ 'hello': '#ff0066',
+ 'world': '#884444 italic',
+ })
+ fragments = FormattedText([
+ ('class:hello', 'Hello'),
+ ('class:world', 'World'),
+ ])
+ print_formatted_text(fragments, style=style)
+
+ If you want to print a list of Pygments tokens, wrap it in
+ :class:`~prompt_toolkit.formatted_text.PygmentsTokens` to do the
+ conversion.
+
+ If a prompt_toolkit `Application` is currently running, this will always
+ print above the application or prompt (similar to `patch_stdout`). So,
+ `print_formatted_text` will erase the current application, print the text,
+ and render the application again.
+
+ :param values: Any kind of printable object, or formatted string.
+ :param sep: String inserted between values, default a space.
+ :param end: String appended after the last value, default a newline.
+ :param style: :class:`.Style` instance for the color scheme.
+ :param include_default_pygments_style: `bool`. Include the default Pygments
+ style when set to `True` (the default).
+ """
+ assert not (output and file)
+
+ # Create Output object.
+ if output is None:
+ if file:
+ output = create_output(stdout=file)
+ else:
+ output = get_app_session().output
+
+ assert isinstance(output, Output)
+
+ # Get color depth.
+ color_depth = color_depth or output.get_default_color_depth()
+
+ # Merges values.
+ def to_text(val: Any) -> StyleAndTextTuples:
+ # Normal lists which are not instances of `FormattedText` are
+ # considered plain text.
+ if isinstance(val, list) and not isinstance(val, FormattedText):
+ return to_formatted_text(f"{val}")
+ return to_formatted_text(val, auto_convert=True)
+
+ fragments = []
+ for i, value in enumerate(values):
+ fragments.extend(to_text(value))
+
+ if sep and i != len(values) - 1:
+ fragments.extend(to_text(sep))
+
+ fragments.extend(to_text(end))
+
+ # Print output.
+ def render() -> None:
+ assert isinstance(output, Output)
+
+ renderer_print_formatted_text(
+ output,
+ fragments,
+ _create_merged_style(
+ style, include_default_pygments_style=include_default_pygments_style
+ ),
+ color_depth=color_depth,
+ style_transformation=style_transformation,
+ )
+
+ # Flush the output stream.
+ if flush:
+ output.flush()
+
+ # If an application is running, print above the app. This does not require
+ # `patch_stdout`.
+ loop: AbstractEventLoop | None = None
+
+ app = get_app_or_none()
+ if app is not None:
+ loop = app.loop
+
+ if loop is not None:
+ loop.call_soon_threadsafe(lambda: run_in_terminal(render))
+ else:
+ render()
+
+
+def print_container(
+ container: AnyContainer,
+ file: TextIO | None = None,
+ style: BaseStyle | None = None,
+ include_default_pygments_style: bool = True,
+) -> None:
+ """
+ Print any layout to the output in a non-interactive way.
+
+ Example usage::
+
+ from prompt_toolkit.widgets import Frame, TextArea
+ print_container(
+ Frame(TextArea(text='Hello world!')))
+ """
+ if file:
+ output = create_output(stdout=file)
+ else:
+ output = get_app_session().output
+
+ app: Application[None] = Application(
+ layout=Layout(container=container),
+ output=output,
+ # `DummyInput` will cause the application to terminate immediately.
+ input=DummyInput(),
+ style=_create_merged_style(
+ style, include_default_pygments_style=include_default_pygments_style
+ ),
+ )
+ try:
+ app.run(in_thread=True)
+ except EOFError:
+ pass
+
+
+def _create_merged_style(
+ style: BaseStyle | None, include_default_pygments_style: bool
+) -> BaseStyle:
+ """
+ Merge user defined style with built-in style.
+ """
+ styles = [default_ui_style()]
+ if include_default_pygments_style:
+ styles.append(default_pygments_style())
+ if style:
+ styles.append(style)
+
+ return merge_styles(styles)
+
+
+def clear() -> None:
+ """
+ Clear the screen.
+ """
+ output = get_app_session().output
+ output.erase_screen()
+ output.cursor_goto(0, 0)
+ output.flush()
+
+
+def set_title(text: str) -> None:
+ """
+ Set the terminal title.
+ """
+ output = get_app_session().output
+ output.set_title(text)
+
+
+def clear_title() -> None:
+ """
+ Erase the current title.
+ """
+ set_title("")