Technical Documentation: prompt-toolkit/python-prompt-toolkit
โน๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference
Overview (`examples/tutorial/README.md`)
See http://python-prompt-toolkit.readthedocs.io/en/stable/pages/tutorials/repl.html
Index (`docs/index.rst`)
Python Prompt Toolkit 3.0
prompt_toolkit is a library for building powerful interactive command line
and terminal applications in Python.
It can be a very advanced pure Python replacement for GNU readline <https://tiswww.case.edu/php/chet/readline/rltop.html>_, but it can also be
used for building full screen applications.
.. image:: images/ptpython-2.png
Some features:
- Syntax highlighting of the input while typing. (For instance, with a Pygments lexer.)
- Multi-line input editing.
- Advanced code completion.
- Selecting text for copy/paste. (Both Emacs and Vi style.)
- Mouse support for cursor positioning and scrolling.
- Auto suggestions. (Like
fish shell <http://fishshell.com/>_.) - No global state.
Like readline:
- Both Emacs and Vi key bindings.
- Reverse and forward incremental search.
- Works well with Unicode double width characters. (Chinese input.)
Works everywhere:
- Pure Python. Runs on all Python versions starting at Python 3.6.
(Python 2.6 - 3.x is supported in prompt_toolkit 2.0; not 3.0). - Runs on Linux, OS X, OpenBSD and Windows systems.
- Lightweight, the only dependencies are Pygments and wcwidth.
- No assumptions about I/O are made. Every prompt_toolkit application should
also run in a telnet/ssh server or anasyncio <https://docs.python.org/3/library/asyncio.html>_ process.
Have a look at :ref:the gallery <gallery> to get an idea of what is possible.
Getting started
Go to :ref:getting started <getting_started> and build your first prompt.
Issues are tracked on the Github project <https://github.com/prompt-toolkit/python-prompt-toolkit>_.
Thanks to:
A special thanks to all the contributors <https://github.com/prompt-toolkit/python-prompt-toolkit/graphs/contributors>_
for making prompt_toolkit possible.
Also, a special thanks to the Pygments <http://pygments.org/>_ and wcwidth <https://github.com/jquast/wcwidth>_ libraries.
Table of contents
.. toctree::
:maxdepth: 2
pages/gallery
pages/getting_started
pages/upgrading/index
pages/printing_text
pages/asking_for_input
pages/asking_for_a_choice
pages/dialogs
pages/progress_bars
pages/full_screen_apps
pages/tutorials/index
pages/advanced_topics/index
pages/reference
pages/related_projects
Indices and tables
- :ref:
genindex - :ref:
modindex - :ref:
search
Prompt_toolkit was created by Jonathan Slenders <http://github.com/prompt-toolkit/>_.
Requirements (`docs/requirements.txt`)
Sphinx>=8,<9
wcwidth<1
pyperclip<2
sphinx_copybutton>=0.5.2,<1.0.0
sphinx-nefertiti>=0.8.8
Pages Asking For A Choice (`docs/pages/asking_for_a_choice.rst`)
.. _asking_for_a_choice:
Asking for a choice
Similar to how the :func:~prompt_toolkit.shortcuts.prompt function allows for
text input, prompt_toolkit has a
:func:~prompt_toolkit.shortcuts.choice function to ask for a choice
from a list of options:
.. code:: python
from prompt_toolkit.shortcuts import choice
result = choice(
message="Please choose a dish:",
options=[
("pizza", "Pizza with mushrooms"),
("salad", "Salad with tomatoes"),
("sushi", "Sushi"),
],
default="salad",
)
print(f"You have chosen: {result}").. image:: ../images/choice-input.png
Coloring the options
It is possible to customize the colors and styles. The message parameter
takes any :ref:formatted text <formatted_text>, and the labels (2nd argument
from the options) can be :ref:formatted text <formatted_text> as well.
Further, we can pass a :class:~prompt_toolkit.styles.Style instance using the
:meth:~prompt_toolkit.styles.Style.from_dict function:
.. code:: python
from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.shortcuts import choice
from prompt_toolkit.styles import Style
style = Style.from_dict(
{
"input-selection": "fg:#ff0000",
"number": "fg:#884444 bold",
"selected-option": "underline",
}
)
result = choice(
message=HTML("Please select a dish:"),
options=[
("pizza", "Pizza with mushrooms"),
(
"salad",
HTML("<ansigreen>Salad</ansigreen> with <ansired>tomatoes</ansired>"),
),
("sushi", "Sushi"),
],
style=style,
)
print(f"You have chosen: {result}").. image:: ../images/colored-choice.png
Adding a frame
The :func:~prompt_toolkit.shortcuts.choice function takes ashow_frame argument. When True, the input is displayed within a frame.
It is also possible to pass a :ref:filter <filters>, like ~is_done, so
that the frame is only displayed when asking for input, but hidden once the
input is accepted.
.. code:: python
from prompt_toolkit.shortcuts import choice
from prompt_toolkit.filters import is_done
from prompt_toolkit.styles import Style
style = Style.from_dict(
{
"frame.border": "#884444",
"selected-option": "bold",
}
)
result = choice(
message="Please select a dish:",
options=[
("pizza", "Pizza with mushrooms"),
("salad", "Salad with tomatoes"),
("sushi", "Sushi"),
],
style=style,
show_frame=~is_done,
)
print(f"You have chosen: {result}").. image:: ../images/choice-with-frame.png
Adding a bottom toolbar
Adding a bottom toolbar can be done by passing a bottom_toolbar argument to
:func:~prompt_toolkit.shortcuts.choice. This argument can be plain text,
:ref:formatted text <formatted_text> or a callable that returns plain or
formatted text.
.. code:: python
from prompt_toolkit.filters import is_done
from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.shortcuts import choice
from prompt_toolkit.styles import Style
style = Style.from_dict(
{
"frame.border": "#ff4444",
"selected-option": "bold",
# ('noreverse' because the default toolbar style uses 'reverse')
"bottom-toolbar": "#ffffff bg:#333333 noreverse",
}
)
result = choice(
message=HTML("Please select a dish:"),
options=[
("pizza", "Pizza with mushrooms"),
("salad", "Salad with tomatoes"),
("sushi", "Sushi"),
],
style=style,
bottom_toolbar=HTML(
" Press **[Up]**/**[Down]** to select, **[Enter]** to accept."
),
show_frame=~is_done,
)
print(f"You have chosen: {result}").. image:: ../images/choice-with-frame-and-bottom-toolbar.png
Pages Asking For Input (`docs/pages/asking_for_input.rst`)
.. _asking_for_input:
Asking for input (prompts)
This page is about building prompts. Pieces of code that we can embed in a
program for asking the user for input. Even if you want to use prompt_toolkit
for building full screen terminal applications, it is probably still a good
idea to read this first, before heading to the :ref:building full screen applications <full_screen_applications> page.
In this page, we will cover autocompletion, syntax highlighting, key bindings,
and so on.
Hello world
The following snippet is the most simple example, it uses the
:func:~prompt_toolkit.shortcuts.prompt function to ask the user for input
and returns the text. Just like (raw_)input.
.. code:: python
from prompt_toolkit import prompt
text = prompt("Give me some input: ")
print(f"You said: {text}").. image:: ../images/hello-world-prompt.png
What we get here is a simple prompt that supports the Emacs key bindings like
readline, but further nothing special. However,
:func:~prompt_toolkit.shortcuts.prompt has a lot of configuration options.
In the following sections, we will discover all these parameters.
The `PromptSession` object
Instead of calling the :func:~prompt_toolkit.shortcuts.prompt function, it's
also possible to create a :class:~prompt_toolkit.shortcuts.PromptSession
instance followed by calling its
:meth:~prompt_toolkit.shortcuts.PromptSession.prompt method for every input
call. This creates a kind of an input session.
.. code:: python
from prompt_toolkit import PromptSession
# Create prompt object.
session = PromptSession()
# Do multiple input calls.
text1 = session.prompt()
text2 = session.prompt()This has mainly two advantages:
The input history will be kept between consecutive
:meth:~prompt_toolkit.shortcuts.PromptSession.promptcalls.The :func:
~prompt_toolkit.shortcuts.PromptSessioninstance and its
:meth:~prompt_toolkit.shortcuts.PromptSession.promptmethod take about the
same arguments, like all the options described below (highlighting,
completion, etc...). So if you want to ask for multiple inputs, but each
input call needs about the same arguments, they can be passed to the
:func:~prompt_toolkit.shortcuts.PromptSessioninstance as well, and they
can be overridden by passing values to the
:meth:~prompt_toolkit.shortcuts.PromptSession.promptmethod.
Syntax highlighting
Adding syntax highlighting is as simple as adding a lexer. All of the Pygments <http://pygments.org/>_ lexers can be used after wrapping them in a
:class:~prompt_toolkit.lexers.PygmentsLexer. It is also possible to create a
custom lexer by implementing the :class:~prompt_toolkit.lexers.Lexer abstract
base class.
.. code:: python
from pygments.lexers.html import HtmlLexer
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.lexers import PygmentsLexer
text = prompt("Enter HTML: ", lexer=PygmentsLexer(HtmlLexer))
print(f"You said: {text}").. image:: ../images/html-input.png
The default Pygments colorscheme is included as part of the default style in
prompt_toolkit. If you want to use another Pygments style along with the lexer,
you can do the following:
.. code:: python
from pygments.lexers.html import HtmlLexer
from pygments.styles import get_style_by_name
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.lexers import PygmentsLexer
from prompt_toolkit.styles.pygments import style_from_pygments_cls
style = style_from_pygments_cls(get_style_by_name("monokai"))
text = prompt(
"Enter HTML: ",
lexer=PygmentsLexer(HtmlLexer),
style=style,
include_default_pygments_style=False
)
print(f"You said: {text}")We pass include_default_pygments_style=False, because otherwise, both
styles will be merged, possibly giving slightly different colors in the outcome
for cases where where our custom Pygments style doesn't specify a color.
.. _colors:
Colors
The colors for syntax highlighting are defined by a
:class:~prompt_toolkit.styles.Style instance. By default, a neutral
built-in style is used, but any style instance can be passed to the
:func:~prompt_toolkit.shortcuts.prompt function. A simple way to create a
style, is by using the :meth:~prompt_toolkit.styles.Style.from_dict
function:
.. code:: python
from pygments.lexers.html import HtmlLexer
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.styles import Style
from prompt_toolkit.lexers import PygmentsLexer
our_style = Style.from_dict({
"pygments.comment": "#888888 bold",
"pygments.keyword": "#ff88ff bold",
})
text = prompt(
"Enter HTML: ",
lexer=PygmentsLexer(HtmlLexer),
style=our_style
)The style dictionary is very similar to the Pygments styles dictionary,
with a few differences:
- The
roman,sans,monoandborderoptions are ignored. - The style has a few additions:
blink,noblink,reverseandnoreverse. - Colors can be in the
#ff0000format, but they can be one of the built-in
ANSI color names as well. In that case, they map directly to the 16 color
palette of the terminal.
:ref:Read more about styling <styling>.
Using a Pygments style
^^^^^^^^^^^^^^^^^^^^^^
All Pygments style classes can be used as well, when they are wrapped through
:func:~prompt_toolkit.styles.style_from_pygments_cls.
Suppose we'd like to use a Pygments style, for instancepygments.styles.tango.TangoStyle, that is possible like this:
.. code:: python
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.styles import style_from_pygments_cls
from prompt_toolkit.lexers import PygmentsLexer
from pygments.styles.tango import TangoStyle
from pygments.lexers.html import HtmlLexer
tango_style = style_from_pygments_cls(TangoStyle)
text = prompt(
"Enter HTML: ",
lexer=PygmentsLexer(HtmlLexer),
style=tango_style
)Creating a custom style could be done like this:
.. code:: python
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.styles import Style, style_from_pygments_cls, merge_styles
from prompt_toolkit.lexers import PygmentsLexer
from pygments.styles.tango import TangoStyle
from pygments.lexers.html import HtmlLexer
our_style = merge_styles([
style_from_pygments_cls(TangoStyle),
Style.from_dict({
"pygments.comment": "#888888 bold",
"pygments.keyword": "#ff88ff bold",
})
])
text = prompt(
"Enter HTML: ",
lexer=PygmentsLexer(HtmlLexer),
style=our_style
)Coloring the prompt itself
^^^^^^^^^^^^^^^^^^^^^^^^^^
It is possible to add some colors to the prompt itself. For this, we need to
build some :ref:formatted text <formatted_text>. One way of doing this is by
creating a list of style/text tuples. In the following example, we use class
names to refer to the style.
.. code:: python
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.styles import Style
style = Style.from_dict({
# User input (default text).
"": "#ff0066",
# Prompt.
"username": "#884444",
"at": "#00aa00",
"colon": "#0000aa",
"pound": "#00aa00",
"host": "#00ffff bg:#444400",
"path": "ansicyan underline",
})
message = [
("class:username", "john"),
("class:at", "@"),
("class:host", "localhost"),
("class:colon", ":"),
("class:path", "/user/john"),
("class:pound", "# "),
]
text = prompt(message, style=style).. image:: ../images/colored-prompt.png
The message can be any kind of formatted text, as discussed :ref:here <formatted_text>. It can also be a callable that returns some formatted text.
By default, colors are taken from the 256 color palette. If you want to have
24bit true color, this is possible by adding thecolor_depth=ColorDepth.TRUE_COLOR option to the
:func:~prompt_toolkit.shortcuts.prompt.prompt function.
.. code:: python
from prompt_toolkit.output import ColorDepth
text = prompt(message, style=style, color_depth=ColorDepth.TRUE_COLOR)Autocompletion
Autocompletion can be added by passing a completer parameter. This should
be an instance of the :class:~prompt_toolkit.completion.Completer abstract
base class. :class:~prompt_toolkit.completion.WordCompleter is an example of
a completer that implements that interface.
.. code:: python
from prompt_toolkit import prompt
from prompt_toolkit.completion import WordCompleter
html_completer = WordCompleter(["<html>", "", "<head>", "<title>"])
text = prompt("Enter HTML: ", completer=html_completer)
print(f"You said: {text}"):class:~prompt_toolkit.completion.WordCompleter is a simple completer that
completes the last word before the cursor with any of the given words.
.. image:: ../images/html-completion.png
.. note::
Note that in prompt_toolkit 2.0, the auto completion became synchronous. This
means that if it takes a long time to compute the completions, that this
will block the event loop and the input processing.
For heavy completion algorithms, it is recommended to wrap the completer in
a :class:`~prompt_toolkit.completion.ThreadedCompleter` in order to run it
in a background thread.Nested completion
^^^^^^^^^^^^^^^^^
Sometimes you have a command line interface where the completion depends on the
previous words from the input. Examples are the CLIs from routers and switches.
A simple :class:~prompt_toolkit.completion.WordCompleter is not enough in
that case. We want to to be able to define completions at multiple hierarchical
levels. :class:~prompt_toolkit.completion.NestedCompleter solves this issue:
.. code:: python
from prompt_toolkit import prompt
from prompt_toolkit.completion import NestedCompleter
completer = NestedCompleter.from_nested_dict({
"show": {
"version": None,
"clock": None,
"ip": {
"interface": {"brief"}
}
},
"exit": None,
})
text = prompt("# ", completer=completer)
print(f"You said: {text}")Whenever there is a None value in the dictionary, it means that there is no
further nested completion at that point. When all values of a dictionary would
be None, it can also be replaced with a set.
A custom completer
^^^^^^^^^^^^^^^^^^
For more complex examples, it makes sense to create a custom completer. For
instance:
.. code:: python
from prompt_toolkit import prompt
from prompt_toolkit.completion import Completer, Completion
class MyCustomCompleter(Completer):
def get_completions(self, document, complete_event):
yield Completion("completion", start_position=0)
text = prompt("> ", completer=MyCustomCompleter())A :class:~prompt_toolkit.completion.Completer class has to implement a
generator named :meth:~prompt_toolkit.completion.Completer.get_completions
that takes a :class:~prompt_toolkit.document.Document and yields the current
:class:~prompt_toolkit.completion.Completion instances. Each completion
contains a portion of text, and a position.
The position is used for fixing text before the cursor. Pressing the tab key
could for instance turn parts of the input from lowercase to uppercase. This
makes sense for a case insensitive completer. Or in case of a fuzzy completion,
it could fix typos. When start_position is something negative, this amount
of characters will be deleted and replaced.
Styling individual completions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Each completion can provide a custom style, which is used when it is rendered
in the completion menu or toolbar. This is possible by passing a style to each
:class:~prompt_toolkit.completion.Completion instance.
.. code:: python
from prompt_toolkit.completion import Completer, Completion
class MyCustomCompleter(Completer):
def get_completions(self, document, complete_event):
# Display this completion, black on yellow.
yield Completion(
"completion1",
start_position=0,
style="bg:ansiyellow fg:ansiblack"
)
# Underline completion.
yield Completion(
"completion2",
start_position=0,
style="underline"
)
# Specify class name, which will be looked up in the style sheet.
yield Completion(
"completion3",
start_position=0,
style="class:special-completion"
)The "colorful-prompts.py" example uses completion styling:
.. image:: ../images/colorful-completions.png
Finally, it is possible to pass :ref:formatted text <formatted_text> for thedisplay attribute of a :class:~prompt_toolkit.completion.Completion. This
provides all the freedom you need to display the text in any possible way. It
can also be combined with the style attribute. For instance:
.. code:: python
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit.formatted_text import HTML
class MyCustomCompleter(Completer):
def get_completions(self, document, complete_event):
yield Completion(
"completion1",
start_position=0,
display=HTML("**completion**<ansired>1</ansired>"),
style="bg:ansiyellow"
)Fuzzy completion
^^^^^^^^^^^^^^^^
If one possible completions is "django_migrations", a fuzzy completer would
allow you to get this by typing "djm" only, a subset of characters for this
string.
Prompt_toolkit ships with a :class:~prompt_toolkit.completion.FuzzyCompleter
and :class:~prompt_toolkit.completion.FuzzyWordCompleter class. These provide
the means for doing this kind of "fuzzy completion". The first one can take any
completer instance and wrap it so that it becomes a fuzzy completer. The second
one behaves like a :class:~prompt_toolkit.completion.WordCompleter wrapped
into a :class:~prompt_toolkit.completion.FuzzyCompleter.
Complete while typing
^^^^^^^^^^^^^^^^^^^^^
Autcompletions can be generated automatically while typing or when the user
presses the tab key. This can be configured with the complete_while_typing
option:
.. code:: python
text = prompt(
"Enter HTML: ",
completer=my_completer,
complete_while_typing=True
)Notice that this setting is incompatible with the enable_history_search
option. The reason for this is that the up and down key bindings would conflict
otherwise. So, make sure to disable history search for this.
Asynchronous completion
^^^^^^^^^^^^^^^^^^^^^^^
When generating the completions takes a lot of time, it's better to do this in
a background thread. This is possible by wrapping the completer in a
:class:~prompt_toolkit.completion.ThreadedCompleter, but also by passing thecomplete_in_thread=True argument.
.. code:: python
text = prompt("> ", completer=MyCustomCompleter(), complete_in_thread=True)Input validation
A prompt can have a validator attached. This is some code that will check
whether the given input is acceptable and it will only return it if that's the
case. Otherwise it will show an error message and move the cursor to a given
position.
A validator should implements the :class:~prompt_toolkit.validation.Validator
abstract base class. This requires only one method, named validate that
takes a :class:~prompt_toolkit.document.Document as input and raises
:class:~prompt_toolkit.validation.ValidationError when the validation fails.
.. code:: python
from prompt_toolkit.validation import Validator, ValidationError
from prompt_toolkit import prompt
class NumberValidator(Validator):
def validate(self, document):
text = document.text
if text and not text.isdigit():
i = 0
# Get index of first non numeric character.
# We want to move the cursor here.
for i, c in enumerate(text):
if not c.isdigit():
break
raise ValidationError(
message="This input contains non-numeric characters",
cursor_position=i
)
number = int(prompt("Give a number: ", validator=NumberValidator()))
print(f"You said: {number}").. image:: ../images/number-validator.png
By default, the input is validated in real-time while the user is typing, but
prompt_toolkit can also validate after the user presses the enter key:
.. code:: python
prompt(
"Give a number: ",
validator=NumberValidator(),
validate_while_typing=False
)If the input validation contains some heavy CPU intensive code, but you don't
want to block the event loop, then it's recommended to wrap the validator class
in a :class:~prompt_toolkit.validation.ThreadedValidator.
Validator from a callable
^^^^^^^^^^^^^^^^^^^^^^^^^
Instead of implementing the :class:~prompt_toolkit.validation.Validator
abstract base class, it is also possible to start from a simple function and
use the :meth:~prompt_toolkit.validation.Validator.from_callable classmethod.
This is easier and sufficient for probably 90% of the validators. It looks as
follows:
.. code:: python
from prompt_toolkit.validation import Validator
from prompt_toolkit import prompt
def is_number(text):
return text.isdigit()
validator = Validator.from_callable(
is_number,
error_message="This input contains non-numeric characters",
move_cursor_to_end=True
)
number = int(prompt("Give a number: ", validator=validator))
print(f"You said: {number}")We define a function that takes a string, and tells whether it's valid input or
not by returning a boolean.
:meth:~prompt_toolkit.validation.Validator.from_callable turns that into a
:class:~prompt_toolkit.validation.Validator instance. Notice that setting the
cursor position is not possible this way.
History
A :class:~prompt_toolkit.history.History object keeps track of all the
previously entered strings, so that the up-arrow can reveal previously entered
items.
The recommended way is to use a
:class:~prompt_toolkit.shortcuts.PromptSession, which uses an
:class:~prompt_toolkit.history.InMemoryHistory for the entire session by
default. The following example has a history out of the box:
.. code:: python
from prompt_toolkit import PromptSession
session = PromptSession()
while True:
session.prompt()
To persist a history to disk, use a :class:~prompt_toolkit.history.FileHistory
instead of the default
:class:~prompt_toolkit.history.InMemoryHistory. This history object can be
passed either to a :class:~prompt_toolkit.shortcuts.PromptSession or to the
:meth:~prompt_toolkit.shortcuts.prompt function. For instance:
.. code:: python
from prompt_toolkit import PromptSession
from prompt_toolkit.history import FileHistory
session = PromptSession(history=FileHistory("~/.myhistory"))
while True:
session.prompt()
Auto suggestion
Auto suggestion is a way to propose some input completions to the user like thefish shell <http://fishshell.com/>_.
Usually, the input is compared to the history and when there is another entry
starting with the given text, the completion will be shown as gray text behind
the current input. Pressing the right arrow :kbd:โ or :kbd:c-e will insert
this suggestion, :kbd:alt-f will insert the first word of the suggestion.
.. note::
When suggestions are based on the history, don't forget to share one
:class:`~prompt_toolkit.history.History` object between consecutive
:func:`~prompt_toolkit.shortcuts.prompt` calls. Using a
:class:`~prompt_toolkit.shortcuts.PromptSession` does this for you.Example:
.. code:: python
from prompt_toolkit import PromptSession
from prompt_toolkit.history import InMemoryHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
session = PromptSession()
while True:
text = session.prompt("> ", auto_suggest=AutoSuggestFromHistory())
print(f"You said: {text}").. image:: ../images/auto-suggestion.png
A suggestion does not have to come from the history. Any implementation of the
:class:~prompt_toolkit.auto_suggest.AutoSuggest abstract base class can be
passed as an argument.
Adding a bottom toolbar
Adding a bottom toolbar is as easy as passing a bottom_toolbar argument to
:func:~prompt_toolkit.shortcuts.prompt. This argument be either plain text,
:ref:formatted text <formatted_text> or a callable that returns plain or
formatted text.
When a function is given, it will be called every time the prompt is rendered,
so the bottom toolbar can be used to display dynamic information.
The toolbar is always erased when the prompt returns.
Here we have an example of a callable that returns an
:class:~prompt_toolkit.formatted_text.HTML object. By default, the toolbar
has the reversed style, which is why we are setting the background instead
of the foreground.
.. code:: python
from prompt_toolkit import prompt
from prompt_toolkit.formatted_text import HTML
def bottom_toolbar():
return HTML('This is a **<style bg="ansired">Toolbar</style>**!')
text = prompt("> ", bottom_toolbar=bottom_toolbar)
print(f"You said: {text}").. image:: ../images/bottom-toolbar.png
Similar, we could use a list of style/text tuples.
.. code:: python
from prompt_toolkit import prompt
from prompt_toolkit.styles import Style
def bottom_toolbar():
return [("class:bottom-toolbar", " This is a toolbar. ")]
style = Style.from_dict({
"bottom-toolbar": "#ffffff bg:#333333",
})
text = prompt("> ", bottom_toolbar=bottom_toolbar, style=style)
print(f"You said: {text}")The default class name is bottom-toolbar and that will also be used to fill
the background of the toolbar.
Adding a right prompt
The :func:~prompt_toolkit.shortcuts.prompt function has out of the box
support for right prompts as well. People familiar to ZSH could recognize this
as the RPROMPT option.
So, similar to adding a bottom toolbar, we can pass an rprompt argument.
This can be either plain text, :ref:formatted text <formatted_text> or a
callable which returns either.
.. code:: python
from prompt_toolkit import prompt
from prompt_toolkit.styles import Style
example_style = Style.from_dict({
"rprompt": "bg:#ff0066 #ffffff",
})
def get_rprompt():
return "<rprompt>"
answer = prompt("> ", rprompt=get_rprompt, style=example_style).. image:: ../images/rprompt.png
The get_rprompt function can return any kind of formatted text such as
:class:~prompt_toolkit.formatted_text.HTML. it is also possible to pass text
directly to the rprompt argument of the
:func:~prompt_toolkit.shortcuts.prompt function. It does not have to be a
callable.
Vi input mode
Prompt-toolkit supports both Emacs and Vi key bindings, similar to Readline.
The :func:~prompt_toolkit.shortcuts.prompt function will use Emacs bindings by
default. This is done because on most operating systems, also the Bash shell
uses Emacs bindings by default, and that is more intuitive. If however, Vi
binding are required, just pass vi_mode=True.
.. code:: python
from prompt_toolkit import prompt
prompt("> ", vi_mode=True)Adding custom key bindings
By default, every prompt already has a set of key bindings which implements the
usual Vi or Emacs behavior. We can extend this by passing another
:class:~prompt_toolkit.key_binding.KeyBindings instance to thekey_bindings argument of the :func:~prompt_toolkit.shortcuts.prompt
function or the :class:~prompt_toolkit.shortcuts.PromptSession class.
An example of a prompt that prints 'hello world' when :kbd:Control-T is
pressed.
.. code:: python
from prompt_toolkit import prompt
from prompt_toolkit.application import run_in_terminal
from prompt_toolkit.key_binding import KeyBindings
bindings = KeyBindings()
@bindings.add("c-t")
def _(event):
" Say "hello" when `c-t` is pressed. "
def print_hello():
print("hello world")
run_in_terminal(print_hello)
@bindings.add("c-x")
def _(event):
" Exit when `c-x` is pressed. "
event.app.exit()
text = prompt("> ", key_bindings=bindings)
print(f"You said: {text}")Note that we use
:meth:~prompt_toolkit.application.run_in_terminal for the first key binding.
This ensures that the output of the print-statement and the prompt don't mix
up. If the key bindings doesn't print anything, then it can be handled directly
without nesting functions.
Enable key bindings according to a condition
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Often, some key bindings can be enabled or disabled according to a certain
condition. For instance, the Emacs and Vi bindings will never be active at the
same time, but it is possible to switch between Emacs and Vi bindings at run
time.
In order to enable a key binding according to a certain condition, we have to
pass it a :class:~prompt_toolkit.filters.Filter, usually a
:class:~prompt_toolkit.filters.Condition instance. (:ref:Read more about filters <filters>.)
.. code:: python
from prompt_toolkit import prompt
from prompt_toolkit.filters import Condition
from prompt_toolkit.key_binding import KeyBindings
bindings = KeyBindings()
@Condition
def is_active():
" Only activate key binding on the second half of each minute. "
return datetime.datetime.now().second > 30
@bindings.add("c-t", filter=is_active)
def _(event):
# ...
pass
prompt("> ", key_bindings=bindings)Dynamically switch between Emacs and Vi mode
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :class:~prompt_toolkit.application.Application has an editing_mode
attribute. We can change the key bindings by changing this attribute fromEditingMode.VI to EditingMode.EMACS.
.. code:: python
from prompt_toolkit import prompt
from prompt_toolkit.application.current import get_app
from prompt_toolkit.enums import EditingMode
from prompt_toolkit.key_binding import KeyBindings
def run():
# Create a set of key bindings.
bindings = KeyBindings()
# Add an additional key binding for toggling this flag.
@bindings.add("f4")
def _(event):
" Toggle between Emacs and Vi mode. "
app = event.app
if app.editing_mode == EditingMode.VI:
app.editing_mode = EditingMode.EMACS
else:
app.editing_mode = EditingMode.VI
# Add a toolbar at the bottom to display the current input mode.
def bottom_toolbar():
" Display the current input mode. "
text = "Vi" if get_app().editing_mode == EditingMode.VI else "Emacs"
return [
("class:toolbar", " [F4] %s " % text)
]
prompt("> ", key_bindings=bindings, bottom_toolbar=bottom_toolbar)
run():ref:Read more about key bindings ...<key_bindings>
Using control-space for completion
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
An popular short cut that people sometimes use it to use control-space for
opening the autocompletion menu instead of the tab key. This can be done with
the following key binding.
.. code:: python
kb = KeyBindings()
@kb.add("c-space")
def _(event):
" Initialize autocompletion, or select the next completion. "
buff = event.app.current_buffer
if buff.complete_state:
buff.complete_next()
else:
buff.start_completion(select_first=False)Other prompt options
Multiline input
^^^^^^^^^^^^^^^
Reading multiline input is as easy as passing the multiline=True parameter.
.. code:: python
from prompt_toolkit import prompt
prompt("> ", multiline=True)A side effect of this is that the enter key will now insert a newline instead
of accepting and returning the input. The user will now have to press
:kbd:Meta+Enter in order to accept the input. (Or :kbd:Escape followed by
:kbd:Enter.)
It is possible to specify a continuation prompt. This works by passing aprompt_continuation callable to :func:~prompt_toolkit.shortcuts.prompt.
This function is supposed to return :ref:formatted text <formatted_text>, or
a list of (style, text) tuples. The width of the returned text should not
exceed the given width. (The width of the prompt margin is defined by the
prompt.)
.. code:: python
from prompt_toolkit import prompt
def prompt_continuation(width, line_number, is_soft_wrap):
return "." * width
# Or: return [("", "." * width)]
prompt(
"multiline input> ",
multiline=True,
prompt_continuation=prompt_continuation
).. image:: ../images/multiline-input.png
Passing a default
^^^^^^^^^^^^^^^^^
A default value can be given:
.. code:: python
from prompt_toolkit import prompt
import getpass
prompt("What is your name: ", default=f"{getpass.getuser()}")Mouse support
^^^^^^^^^^^^^
There is limited mouse support for positioning the cursor, for scrolling (in
case of large multiline inputs) and for clicking in the autocompletion menu.
Enabling can be done by passing the mouse_support=True option.
.. code:: python
from prompt_toolkit import prompt
prompt("What is your name: ", mouse_support=True)Line wrapping
^^^^^^^^^^^^^
Line wrapping is enabled by default. This is what most people are used to and
this is what GNU Readline does. When it is disabled, the input string will
scroll horizontally.
.. code:: python
from prompt_toolkit import prompt
prompt("What is your name: ", wrap_lines=False)Password input
^^^^^^^^^^^^^^
When the is_password=True flag has been given, the input is replaced by
asterisks (* characters).
.. code:: python
from prompt_toolkit import prompt
prompt("Enter password: ", is_password=True)Cursor shapes
Many terminals support displaying different types of cursor shapes. The most
common are block, beam or underscore. Either blinking or not. It is possible to
decide which cursor to display while asking for input, or in case of Vi input
mode, have a modal prompt for which its cursor shape changes according to the
input mode.
.. code:: python
from prompt_toolkit import prompt
from prompt_toolkit.cursor_shapes import CursorShape, ModalCursorShapeConfig
# Several possible values for the `cursor_shape_config` parameter:
prompt(">", cursor=CursorShape.BLOCK)
prompt(">", cursor=CursorShape.UNDERLINE)
prompt(">", cursor=CursorShape.BEAM)
prompt(">", cursor=CursorShape.BLINKING_BLOCK)
prompt(">", cursor=CursorShape.BLINKING_UNDERLINE)
prompt(">", cursor=CursorShape.BLINKING_BEAM)
prompt(">", cursor=ModalCursorShapeConfig())Adding a frame
A frame can be displayed around the input by passing show_frame=True as a
parameter. The color of the frame can be chosen by styling the frame.border
element:
.. code:: python
from prompt_toolkit import prompt
from prompt_toolkit.styles import Style
style = Style.from_dict(
{
"frame.border": "#884444",
}
)
answer = prompt("Say something > ", style=style, show_frame=True)
print(f"You said: {answer}").. image:: ../images/prompt-with-frame.png
It is also possible to pass a :ref:filter <filters>, for instanceshow_frame=~is_done, so that the frame is only displayed when asking for
input, but hidden once the input is accepted.
.. code:: python
from prompt_toolkit import prompt
from prompt_toolkit.filters import is_done
answer = prompt("Say something > ", show_frame=~is_done)
print(f"You said: {answer}")Prompt in an `asyncio` application
.. note::
New in prompt_toolkit 3.0. (In prompt_toolkit 2.0 this was possible using a
work-around).For asyncio <https://docs.python.org/3/library/asyncio.html>_ applications,
it's very important to never block the eventloop. However,
:func:~prompt_toolkit.shortcuts.prompt is blocking, and calling this would
freeze the whole application. Asyncio actually won't even allow us to run that
function within a coroutine.
The answer is to call
:meth:~prompt_toolkit.shortcuts.PromptSession.prompt_async instead of
:meth:~prompt_toolkit.shortcuts.PromptSession.prompt. The async variation
returns a coroutines and is awaitable.
.. code:: python
from prompt_toolkit import PromptSession
from prompt_toolkit.patch_stdout import patch_stdout
async def my_coroutine():
session = PromptSession()
while True:
with patch_stdout():
result = await session.prompt_async("Say something: ")
print(f"You said: {result}")The :func:~prompt_toolkit.patch_stdout.patch_stdout context manager is
optional, but it's recommended, because other coroutines could print to stdout.
This ensures that other output won't destroy the prompt.
Reading keys from stdin, one key at a time, but without a prompt
Suppose that you want to use prompt_toolkit to read the keys from stdin, one
key at a time, but not render a prompt to the output, that is also possible:
.. code:: python
import asyncio
from prompt_toolkit.input import create_input
from prompt_toolkit.keys import Keys
async def main() -> None:
done = asyncio.Event()
input = create_input()
def keys_ready():
for key_press in input.read_keys():
print(key_press)
if key_press.key == Keys.ControlC:
done.set()
with input.raw_mode():
with input.attach(keys_ready):
await done.wait()
if __name__ == "__main__":
asyncio.run(main())The above snippet will print the KeyPress object whenever a key is pressed.
This is also cross platform, and should work on Windows.
Pages Dialogs (`docs/pages/dialogs.rst`)
.. _dialogs:
Dialogs
Prompt_toolkit ships with a high level API for displaying dialogs, similar to
the Whiptail program, but in pure Python.
Message box
Use the :func:~prompt_toolkit.shortcuts.message_dialog function to display a
simple message box. For instance:
.. code:: python
from prompt_toolkit.shortcuts import message_dialog
message_dialog(
title='Example dialog window',
text='Do you want to continue?\nPress ENTER to quit.').run().. image:: ../images/dialogs/messagebox.png
Input box
The :func:~prompt_toolkit.shortcuts.input_dialog function can display an
input box. It will return the user input as a string.
.. code:: python
from prompt_toolkit.shortcuts import input_dialog
text = input_dialog(
title='Input dialog example',
text='Please type your name:').run().. image:: ../images/dialogs/inputbox.png
The password=True option can be passed to the
:func:~prompt_toolkit.shortcuts.input_dialog function to turn this into a
password input box.
Yes/No confirmation dialog
The :func:~prompt_toolkit.shortcuts.yes_no_dialog function displays a yes/no
confirmation dialog. It will return a boolean according to the selection.
.. code:: python
from prompt_toolkit.shortcuts import yes_no_dialog
result = yes_no_dialog(
title='Yes/No dialog example',
text='Do you want to confirm?').run().. image:: ../images/dialogs/confirm.png
Button dialog
The :func:~prompt_toolkit.shortcuts.button_dialog function displays a dialog
with choices offered as buttons. Buttons are indicated as a list of tuples,
each providing the label (first) and return value if clicked (second).
.. code:: python
from prompt_toolkit.shortcuts import button_dialog
result = button_dialog(
title='Button dialog example',
text='Do you want to confirm?',
buttons=[
('Yes', True),
('No', False),
('Maybe...', None)
],
).run().. image:: ../images/dialogs/button.png
Radio list dialog
The :func:~prompt_toolkit.shortcuts.radiolist_dialog function displays a dialog
with choices offered as a radio list. The values are provided as a list of tuples,
each providing the return value (first element) and the displayed value (second element).
.. code:: python
from prompt_toolkit.shortcuts import radiolist_dialog
result = radiolist_dialog(
title="RadioList dialog",
text="Which breakfast would you like ?",
values=[
("breakfast1", "Eggs and beacon"),
("breakfast2", "French breakfast"),
("breakfast3", "Equestrian breakfast")
]
).run()Checkbox list dialog
The :func:~prompt_toolkit.shortcuts.checkboxlist_dialog has the same usage and purpose than the Radiolist dialog, but allows several values to be selected and therefore returned.
.. code:: python
from prompt_toolkit.shortcuts import checkboxlist_dialog
results_array = checkboxlist_dialog(
title="CheckboxList dialog",
text="What would you like in your breakfast ?",
values=[
("eggs", "Eggs"),
("bacon", "Bacon"),
("croissants", "20 Croissants"),
("daily", "The breakfast of the day")
]
).run()Styling of dialogs
A custom :class:~prompt_toolkit.styles.Style instance can be passed to all
dialogs to override the default style. Also, text can be styled by passing an
:class:~prompt_toolkit.formatted_text.HTML object.
.. code:: python
from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.shortcuts import message_dialog
from prompt_toolkit.styles import Style
example_style = Style.from_dict({
'dialog': 'bg:#88ff88',
'dialog frame.label': 'bg:#ffffff #000000',
'dialog.body': 'bg:#000000 #00ff00',
'dialog shadow': 'bg:#00aa00',
})
message_dialog(
title=HTML('<style bg="blue" fg="white">Styled</style> '
'<style fg="ansired">dialog</style> window'),
text='Do you want to continue?\nPress ENTER to quit.',
style=example_style).run().. image:: ../images/dialogs/styled.png
Styling reference sheet
In reality, the shortcut commands presented above build a full-screen frame by using a list of components. The two tables below allow you to get the classnames available for each shortcut, therefore you will be able to provide a custom style for every element that is displayed, using the method provided above.
.. note:: All the shortcuts use the Dialog component, therefore it isn't specified explicitly below.
+--------------------------+-------------------------+
| Shortcut | Components used |
+==========================+=========================+
| yes_no_dialog | - Label |
| | - Button (x2) |
+--------------------------+-------------------------+
| button_dialog | - Label |
| | - Button |
+--------------------------+-------------------------+
| input_dialog | - TextArea |
| | - Button (x2) |
+--------------------------+-------------------------+
| message_dialog | - Label |
| | - Button |
+--------------------------+-------------------------+
| radiolist_dialog | - Label |
| | - RadioList |
| | - Button (x2) |
+--------------------------+-------------------------+
| checkboxlist_dialog | - Label |
| | - CheckboxList |
| | - Button (x2) |
+--------------------------+-------------------------+
| progress_dialog | - Label |
| | - TextArea (locked) |
| | - ProgressBar |
+--------------------------+-------------------------+
+----------------+-----------------------------+
| Components | Available classnames |
+================+=============================+
| Dialog | - dialog |
| | - dialog.body |
+----------------+-----------------------------+
| TextArea | - text-area |
| | - text-area.prompt |
+----------------+-----------------------------+
| Label | - label |
+----------------+-----------------------------+
| Button | - button |
| | - button.focused |
| | - button.arrow |
| | - button.text |
+----------------+-----------------------------+
| Frame | - frame |
| | - frame.border |
| | - frame.label |
+----------------+-----------------------------+
| Shadow | - shadow |
+----------------+-----------------------------+
| RadioList | - radio-list |
| | - radio |
| | - radio-checked |
| | - radio-selected |
+----------------+-----------------------------+
| CheckboxList | - checkbox-list |
| | - checkbox |
| | - checkbox-checked |
| | - checkbox-selected |
+----------------+-----------------------------+
| VerticalLine | - line |
| | - vertical-line |
+----------------+-----------------------------+
| HorizontalLine | - line |
| | - horizontal-line |
+----------------+-----------------------------+
| ProgressBar | - progress-bar |
| | - progress-bar.used |
+----------------+-----------------------------+
Example
Let's customize the example of the checkboxlist_dialog.
It uses 2 Button, a CheckboxList and a Label, packed inside a Dialog.
Therefore we can customize each of these elements separately, using for instance:
.. code:: python
from prompt_toolkit.shortcuts import checkboxlist_dialog
from prompt_toolkit.styles import Style
results = checkboxlist_dialog(
title="CheckboxList dialog",
text="What would you like in your breakfast ?",
values=[
("eggs", "Eggs"),
("bacon", "Bacon"),
("croissants", "20 Croissants"),
("daily", "The breakfast of the day")
],
style=Style.from_dict({
'dialog': 'bg:#cdbbb3',
'button': 'bg:#bf99a4',
'checkbox': '#e8612c',
'dialog.body': 'bg:#a9cfd0',
'dialog shadow': 'bg:#c98982',
'frame.label': '#fcaca3',
'dialog.body label': '#fd8bb6',
})
).run()Pages Full Screen Apps (`docs/pages/full_screen_apps.rst`)
.. _full_screen_applications:
Building full screen applications
prompt_toolkit can be used to create complex full screen terminal
applications. Typically, an application consists of a layout (to describe the
graphical part) and a set of key bindings.
The sections below describe the components required for full screen
applications (or custom, non full screen applications), and how to assemble
them together.
Before going through this page, it could be helpful to go through :ref:asking for input <asking_for_input> (prompts) first. Many things that apply to an
input prompt, like styling, key bindings and so on, also apply to full screen
applications.
.. note::
Also remember that the ``examples`` directory of the prompt_toolkit
repository contains plenty of examples. Each example is supposed to explain
one idea. So, this as well should help you get started.
Don't hesitate to open a GitHub issue if you feel that a certain example is
missing.A simple application
Every prompt_toolkit application is an instance of an
:class:~prompt_toolkit.application.Application object. The simplest full
screen example would look like this:
.. code:: python
from prompt_toolkit import Application
app = Application(full_screen=True)
app.run()This will display a dummy application that says "No layout specified. Press
ENTER to quit.".
.. note::
If we wouldn't set the ``full_screen`` option, the application would
not run in the alternate screen buffer, and only consume the least
amount of space required for the layout.An application consists of several components. The most important are:
- I/O objects: the input and output device.
- The layout: this defines the graphical structure of the application. For
instance, a text box on the left side, and a button on the right side.
You can also think of the layout as a collection of 'widgets'. - A style: this defines what colors and underline/bold/italic styles are used
everywhere. - A set of key bindings.
We will discuss all of these in more detail below.
I/O objects
Every :class:~prompt_toolkit.application.Application instance requires an I/O
object for input and output:
- An :class:
~prompt_toolkit.input.Inputinstance, which is an abstraction
of the input stream (stdin). - An :class:
~prompt_toolkit.output.Outputinstance, which is an
abstraction of the output stream, and is called by the renderer.
Both are optional and normally not needed to pass explicitly. Usually, the
default works fine.
There is a third I/O object which is also required by the application, but not
passed inside. This is the event loop, an
:class:~prompt_toolkit.eventloop instance. This is basically a
while-true loop that waits for user input, and when it receives something (like
a key press), it will send that to the the appropriate handler, like for
instance, a key binding.
When :func:~prompt_toolkit.application.Application.run() is called, the event
loop will run until the application is done. An application will quit when
:func:~prompt_toolkit.application.Application.exit() is called.
The layout
A layered layout architecture
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are several ways to create a prompt_toolkit layout, depending on how
customizable you want things to be. In fact, there are several layers of
abstraction.
The most low-level way of creating a layout is by combining
:class:~prompt_toolkit.layout.Containerand
:class:~prompt_toolkit.layout.UIControlobjects.Examples of :class:
~prompt_toolkit.layout.Containerobjects are
:class:~prompt_toolkit.layout.VSplit(vertical split),
:class:~prompt_toolkit.layout.HSplit(horizontal split) and
:class:~prompt_toolkit.layout.FloatContainer. These containers arrange the
layout and can split it in multiple regions. Each container can recursively
contain multiple other containers. They can be combined in any way to define
the "shape" of the layout.The :class:
~prompt_toolkit.layout.Windowobject is a special kind of
container that can contain a :class:~prompt_toolkit.layout.UIControl
object. The :class:~prompt_toolkit.layout.UIControlobject is responsible
for the generation of the actual content. The
:class:~prompt_toolkit.layout.Windowobject acts as an adaptor between the
:class:~prompt_toolkit.layout.UIControland other containers, but it's also
responsible for the scrolling and line wrapping of the content.Examples of :class:
~prompt_toolkit.layout.UIControlobjects are
:class:~prompt_toolkit.layout.BufferControlfor showing the content of an
editable/scrollable buffer, and
:class:~prompt_toolkit.layout.FormattedTextControlfor displaying
(:ref:formatted <formatted_text>) text.Normally, it is never needed to create new
:class:~prompt_toolkit.layout.UIControlor
:class:~prompt_toolkit.layout.Containerclasses, but instead you would
create the layout by composing instances of the existing built-ins.A higher level abstraction of building a layout is by using "widgets". A
widget is a reusable layout component that can contain multiple containers
and controls. Widgets have a__pt_container__function, which returns
the root container for this widget. Prompt_toolkit contains a couple of
widgets like :class:~prompt_toolkit.widgets.TextArea,
:class:~prompt_toolkit.widgets.Button,
:class:~prompt_toolkit.widgets.Frame,
:class:~prompt_toolkit.widgets.VerticalLineand so on.The highest level abstractions can be found in the
shortcutsmodule.
There we don't have to think about the layout, controls and containers at
all. This is the simplest way to use prompt_toolkit, but is only meant for
specific use cases, like a prompt or a simple dialog window.
Containers and controls
^^^^^^^^^^^^^^^^^^^^^^^
The biggest difference between containers and controls is that containers
arrange the layout by splitting the screen in many regions, while controls are
responsible for generating the actual content.
.. note::
Under the hood, the difference is:
- containers use absolute coordinates, and paint on a
:class:~prompt_toolkit.layout.screen.Screeninstance. - user controls create a :class:
~prompt_toolkit.layout.controls.UIContent
instance. This is a collection of lines that represent the actual
content. A :class:~prompt_toolkit.layout.controls.UIControlis not aware
of the screen.
+---------------------------------------------+------------------------------------------------------+
| Abstract base class | Examples |
+=============================================+======================================================+
| :class:~prompt_toolkit.layout.Container | :class:~prompt_toolkit.layout.HSplit |
| | :class:~prompt_toolkit.layout.VSplit |
| | :class:~prompt_toolkit.layout.FloatContainer |
| | :class:~prompt_toolkit.layout.Window |
| | :class:~prompt_toolkit.layout.ScrollablePane |
+---------------------------------------------+------------------------------------------------------+
| :class:~prompt_toolkit.layout.UIControl | :class:~prompt_toolkit.layout.BufferControl |
| | :class:~prompt_toolkit.layout.FormattedTextControl |
+---------------------------------------------+------------------------------------------------------+
The :class:~prompt_toolkit.layout.Window class itself is
particular: it is a :class:~prompt_toolkit.layout.Container that
can contain a :class:~prompt_toolkit.layout.UIControl. Thus, it's the adaptor
between the two. The :class:~prompt_toolkit.layout.Window class also takes
care of scrolling the content and wrapping the lines if needed.
Finally, there is the :class:~prompt_toolkit.layout.Layout class which wraps
the whole layout. This is responsible for keeping track of which window has the
focus.
Here is an example of a layout that displays the content of the default buffer
on the left, and displays "Hello world" on the right. In between it shows a
vertical line:
.. code:: python
from prompt_toolkit import Application
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.layout.containers import VSplit, Window
from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl
from prompt_toolkit.layout.layout import Layout
buffer1 = Buffer() # Editable buffer.
root_container = VSplit([
# One window that holds the BufferControl with the default buffer on
# the left.
Window(content=BufferControl(buffer=buffer1)),
# A vertical line in the middle. We explicitly specify the width, to
# make sure that the layout engine will not try to divide the whole
# width by three for all these windows. The window will simply fill its
# content by repeating this character.
Window(width=1, char='|'),
# Display the text 'Hello world' on the right.
Window(content=FormattedTextControl(text='Hello world')),
])
layout = Layout(root_container)
app = Application(layout=layout, full_screen=True)
app.run() # You won't be able to Exit this appNotice that if you execute this right now, there is no way to quit this
application yet. This is something we explain in the next section below.
More complex layouts can be achieved by nesting multiple
:class:~prompt_toolkit.layout.VSplit,
:class:~prompt_toolkit.layout.HSplit and
:class:~prompt_toolkit.layout.FloatContainer objects.
If you want to make some part of the layout only visible when a certain
condition is satisfied, use a
:class:~prompt_toolkit.layout.ConditionalContainer.
Finally, there is :class:~prompt_toolkit.layout.ScrollablePane, a container
class that can be used to create long forms or nested layouts that are
scrollable as a whole.
Focusing windows
^^^^^^^^^^^^^^^^^
Focusing something can be done by calling the
:meth:~prompt_toolkit.layout.Layout.focus method. This method is very
flexible and accepts a :class:~prompt_toolkit.layout.Window, a
:class:~prompt_toolkit.buffer.Buffer, a
:class:~prompt_toolkit.layout.controls.UIControl and more.
In the following example, we use :func:~prompt_toolkit.application.get_app
for getting the active application.
.. code:: python
from prompt_toolkit.application import get_app
# This window was created earlier.
w = Window()
# ...
# Now focus it.
get_app().layout.focus(w)Changing the focus is something which is typically done in a key binding, so
read on to see how to define key bindings.
Key bindings
In order to react to user actions, we need to create a
:class:~prompt_toolkit.key_binding.KeyBindings object and pass
that to our :class:~prompt_toolkit.application.Application.
There are two kinds of key bindings:
- Global key bindings, which are always active.
- Key bindings that belong to a certain
:class:~prompt_toolkit.layout.controls.UIControland are only active when
this control is focused. Both
:class:~prompt_toolkit.layout.BufferControl
:class:~prompt_toolkit.layout.FormattedTextControltake akey_bindings
argument.
Global key bindings
^^^^^^^^^^^^^^^^^^^
Key bindings can be passed to the application as follows:
.. code:: python
from prompt_toolkit import Application
from prompt_toolkit.key_binding import KeyBindings
kb = KeyBindings()
app = Application(key_bindings=kb)
app.run()To register a new keyboard shortcut, we can use the
:meth:~prompt_toolkit.key_binding.KeyBindings.add method as a decorator of
the key handler:
.. code:: python
from prompt_toolkit import Application
from prompt_toolkit.key_binding import KeyBindings
kb = KeyBindings()
@kb.add('c-q')
def exit_(event):
"""
Pressing Ctrl-Q will exit the user interface.
Setting a return value means: quit the event loop that drives the user
interface and return this value from the `Application.run()` call.
"""
event.app.exit()
app = Application(key_bindings=kb, full_screen=True)
app.run()The callback function is named exit_ for clarity, but it could have been
named _ (underscore) as well, because we won't refer to this name.
:ref:Read more about key bindings ...<key_bindings>
Modal containers
^^^^^^^^^^^^^^^^
The following container objects take a modal argument
:class:~prompt_toolkit.layout.VSplit,
:class:~prompt_toolkit.layout.HSplit, and
:class:~prompt_toolkit.layout.FloatContainer.
Setting modal=True makes what is called a modal container. Normally, a
child container would inherit its parent key bindings. This does not apply to
modal containers.
Consider a modal container (e.g. :class:~prompt_toolkit.layout.VSplit)
is child of another container, its parent. Any key bindings from the parent
are not taken into account if the modal container (child) has the focus.
This is useful in a complex layout, where many controls have their own key
bindings, but you only want to enable the key bindings for a certain region of
the layout.
The global key bindings are always active.
More about the Window class
As said earlier, a :class:~prompt_toolkit.layout.Window is a
:class:~prompt_toolkit.layout.Container that wraps a
:class:~prompt_toolkit.layout.UIControl, like a
:class:~prompt_toolkit.layout.BufferControl or
:class:~prompt_toolkit.layout.FormattedTextControl.
.. note::
Basically, windows are the leaves in the tree structure that represent the UI.A :class:~prompt_toolkit.layout.Window provides a "view" on the
:class:~prompt_toolkit.layout.UIControl, which provides lines of content. The
window is in the first place responsible for the line wrapping and scrolling of
the content, but there are much more options.
- Adding left or right margins. These are used for displaying scroll bars or
line numbers. - There are the
cursorlineandcursorcolumnoptions. These allow
highlighting the line or column of the cursor position. - Alignment of the content. The content can be left aligned, right aligned or
centered. - Finally, the background can be filled with a default character.
More about buffers and `BufferControl`
Input processors
^^^^^^^^^^^^^^^^
A :class:~prompt_toolkit.layout.processors.Processor is used to postprocess
the content of a :class:~prompt_toolkit.layout.BufferControl before it's
displayed. It can for instance highlight matching brackets or change the
visualization of tabs and so on.
A :class:~prompt_toolkit.layout.processors.Processor operates on individual
lines. Basically, it takes a (formatted) line and produces a new (formatted)
line.
Some built-in processors:
+----------------------------------------------------------------------------+-----------------------------------------------------------+
| Processor | Usage: |
+============================================================================+===========================================================+
| :class:~prompt_toolkit.layout.processors.HighlightSearchProcessor | Highlight the current search results. |
+----------------------------------------------------------------------------+-----------------------------------------------------------+
| :class:~prompt_toolkit.layout.processors.HighlightSelectionProcessor | Highlight the selection. |
+----------------------------------------------------------------------------+-----------------------------------------------------------+
| :class:~prompt_toolkit.layout.processors.PasswordProcessor | Display input as asterisks. (* characters). |
+----------------------------------------------------------------------------+-----------------------------------------------------------+
| :class:~prompt_toolkit.layout.processors.BracketsMismatchProcessor | Highlight open/close mismatches for brackets. |
+----------------------------------------------------------------------------+-----------------------------------------------------------+
| :class:~prompt_toolkit.layout.processors.BeforeInput | Insert some text before. |
+----------------------------------------------------------------------------+-----------------------------------------------------------+
| :class:~prompt_toolkit.layout.processors.AfterInput | Insert some text after. |
+----------------------------------------------------------------------------+-----------------------------------------------------------+
| :class:~prompt_toolkit.layout.processors.AppendAutoSuggestion | Append auto suggestion text. |
+----------------------------------------------------------------------------+-----------------------------------------------------------+
| :class:~prompt_toolkit.layout.processors.ShowLeadingWhiteSpaceProcessor | Visualize leading whitespace. |
+----------------------------------------------------------------------------+-----------------------------------------------------------+
| :class:~prompt_toolkit.layout.processors.ShowTrailingWhiteSpaceProcessor | Visualize trailing whitespace. |
+----------------------------------------------------------------------------+-----------------------------------------------------------+
| :class:~prompt_toolkit.layout.processors.TabsProcessor | Visualize tabs as n spaces, or some symbols. |
+----------------------------------------------------------------------------+-----------------------------------------------------------+
A :class:~prompt_toolkit.layout.BufferControl takes only one processor as
input, but it is possible to "merge" multiple processors into one with the
:func:~prompt_toolkit.layout.processors.merge_processors function.
Pages Gallery (`docs/pages/gallery.rst`)
.. _gallery:
Gallery
Showcase, demonstrating the possibilities of prompt_toolkit.
Ptpython, a Python REPL
^^^^^^^^^^^^^^^^^^^^^^^
The prompt:
.. image:: ../images/ptpython.png
The configuration menu of ptpython.
.. image:: ../images/ptpython-menu.png
The history page with its help. (This is a full-screen layout.)
.. image:: ../images/ptpython-history-help.png
Pyvim, a Vim clone
^^^^^^^^^^^^^^^^^^
.. image:: ../images/pyvim.png
Pymux, a terminal multiplexer (like tmux) in Python
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. image:: ../images/pymux.png
Pages Getting Started (`docs/pages/getting_started.rst`)
.. _getting_started:
Getting started
Installation
::
pip install prompt_toolkitFor Conda, do:
::
conda install -c https://conda.anaconda.org/conda-forge prompt_toolkitSeveral use cases: prompts versus full screen terminal applications
prompt_toolkit was in the first place meant to be a replacement for readline.
However, when it became more mature, we realized that all the components for
full screen applications are there and prompt_toolkit is very capable of
handling many use situations. Pyvim <http://github.com/prompt-toolkit/pyvim>_ and pymux <http://github.com/prompt-toolkit/pymux>_ are examples of full screen
applications.
.. image:: ../images/pyvim.png
Basically, at the core, prompt_toolkit has a layout engine, that supports
horizontal and vertical splits as well as floats, where each "window" can
display a user control. The API for user controls is simple yet powerful.
When prompt_toolkit is used as a readline replacement, (to simply read some
input from the user), it uses a rather simple built-in layout. One that
displays the default input buffer and the prompt, a float for the
autocompletions and a toolbar for input validation which is hidden by default.
For full screen applications, usually we build a custom layout ourselves.
Further, there is a very flexible key binding system that can be programmed for
all the needs of full screen applications.
A simple prompt
The following snippet is the most simple example, it uses the
:func:~prompt_toolkit.shortcuts.prompt function to asks the user for input
and returns the text. Just like (raw_)input.
.. code:: python
from prompt_toolkit import prompt
text = prompt("Give me some input: ")
print(f"You said: {text}")Learning `prompt_toolkit`
In order to learn and understand prompt_toolkit, it is best to go through the
all sections in the order below. Also don't forget to have a look at all theexamples <https://github.com/prompt-toolkit/python-prompt-toolkit/tree/master/examples>_
in the repository.
First, :ref:
learn how to print text. This is important,
because it covers how to use "formatted text", which is something you'll use
whenever you want to use colors anywhere.Secondly, go through the :ref:
asking for input <asking_for_input>section.
This is useful for almost any use case, even for full screen applications.
It covers autocompletions, syntax highlighting, key bindings, and so on.Then, learn about :ref:
dialogs, which is easy and fun.Finally, learn about :ref:
full screen applications <full_screen_applications>and read through :ref:the advanced topics <advanced_topics>.
Pages Printing Text (`docs/pages/printing_text.rst`)
.. _printing_text:
Printing (and using) formatted text
Prompt_toolkit ships with a
:func:~prompt_toolkit.shortcuts.print_formatted_text function that's meant to
be (as much as possible) compatible with the built-in print function, but on
top of that, also supports colors and formatting.
On Linux systems, this will output VT100 escape sequences, while on Windows it
will use Win32 API calls or VT100 sequences, depending on what is available.
.. note::
This page is also useful if you'd like to learn how to use formatting
in other places, like in a prompt or a toolbar. Just like
:func:`~prompt_toolkit.shortcuts.print_formatted_text` takes any kind
of "formatted text" as input, prompts and toolbars also accept
"formatted text".Printing plain text
The print function can be imported as follows:
.. code:: python
from prompt_toolkit import print_formatted_text
print_formatted_text('Hello world')You can replace the built in print function as follows, if you want to.
.. code:: python
from prompt_toolkit import print_formatted_text as print
print('Hello world').. note::
If you're using Python 2, make sure to add ``from __future__ import
print_function``. Otherwise, it will not be possible to import a function
named ``print``... _formatted_text:
Formatted text
There are several ways to display colors:
- By creating an :class:
~prompt_toolkit.formatted_text.HTMLobject. - By creating an :class:
~prompt_toolkit.formatted_text.ANSIobject that
contains ANSI escape sequences. - By creating a list of
(style, text)tuples. - By creating a list of
(pygments.Token, text)tuples, and wrapping it in
:class:~prompt_toolkit.formatted_text.PygmentsTokens.
An instance of any of these four kinds of objects is called "formatted text".
There are various places in prompt toolkit, where we accept not just plain text
(as a string), but also formatted text.
HTML
^^^^
:class:~prompt_toolkit.formatted_text.HTML can be used to indicate that a
string contains HTML-like formatting. It recognizes the basic tags for bold,
italic and underline: , and ````.
.. code:: python
from prompt_toolkit import print_formatted_text, HTML
print_formatted_text(HTML('**This is bold**'))
print_formatted_text(HTML('*This is italic*'))
print_formatted_text(HTML('This is underlined'))Further, it's possible to use tags for foreground colors:
.. code:: python
# Colors from the ANSI palette.
print_formatted_text(HTML('<ansired>This is red</ansired>'))
print_formatted_text(HTML('<ansigreen>This is green</ansigreen>'))
# Named colors (256 color palette, or true color, depending on the output).
print_formatted_text(HTML('<skyblue>This is sky blue</skyblue>'))
print_formatted_text(HTML('<seagreen>This is sea green</seagreen>'))
print_formatted_text(HTML('<violet>This is violet</violet>'))Both foreground and background colors can also be specified setting the fg
and bg attributes of any HTML tag:
.. code:: python
# Colors from the ANSI palette.
print_formatted_text(HTML('<aaa fg="ansiwhite" bg="ansigreen">White on green</aaa>'))Underneath, all HTML tags are mapped to classes from a stylesheet, so you can
assign a style for a custom tag.
.. code:: python
from prompt_toolkit import print_formatted_text, HTML
from prompt_toolkit.styles import Style
style = Style.from_dict({
'aaa': '#ff0066',
'bbb': '#44ff00 italic',
})
print_formatted_text(HTML('<aaa>Hello</aaa> world!'), style=style)ANSI
^^^^
Some people like to use the VT100 ANSI escape sequences to generate output.
Natively, this is however only supported on VT100 terminals, but prompt_toolkit
can parse these, and map them to formatted text instances. This means that they
will work on Windows as well. The :class:~prompt_toolkit.formatted_text.ANSI
class takes care of that.
.. code:: python
from prompt_toolkit import print_formatted_text, ANSI
print_formatted_text(ANSI('\x1b[31mhello \x1b[32mworld'))Keep in mind that even on a Linux VT100 terminal, the final output produced by
prompt_toolkit, is not necessarily exactly the same. Depending on the color
depth, it is possible that colors are mapped to different colors, and unknown
tags will be removed.
(style, text) tuples
^^^^^^^^^^^^^^^^^^^^
Internally, both :class:~prompt_toolkit.formatted_text.HTML and
:class:~prompt_toolkit.formatted_text.ANSI objects are mapped to a list of(style, text) tuples. It is however also possible to create such a list
manually with :class:~prompt_toolkit.formatted_text.FormattedText class.
This is a little more verbose, but it's probably the most powerful
way of expressing formatted text.
.. code:: python
from prompt_toolkit import print_formatted_text
from prompt_toolkit.formatted_text import FormattedText
text = FormattedText([
('#ff0066', 'Hello'),
('', ' '),
('#44ff00 italic', 'World'),
])
print_formatted_text(text)Similar to the :class:~prompt_toolkit.formatted_text.HTML example, it is also
possible to use class names, and separate the styling in a style sheet.
.. code:: python
from prompt_toolkit import print_formatted_text
from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit.styles import Style
# The text.
text = FormattedText([
('class:aaa', 'Hello'),
('', ' '),
('class:bbb', 'World'),
])
# The style sheet.
style = Style.from_dict({
'aaa': '#ff0066',
'bbb': '#44ff00 italic',
})
print_formatted_text(text, style=style)Pygments (Token, text) tuples
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When you have a list of Pygments <http://pygments.org/>_ (Token, text)
tuples, then these can be printed by wrapping them in a
:class:~prompt_toolkit.formatted_text.PygmentsTokens object.
.. code:: python
from pygments.token import Token
from prompt_toolkit import print_formatted_text
from prompt_toolkit.formatted_text import PygmentsTokens
text = [
(Token.Keyword, 'print'),
(Token.Punctuation, '('),
(Token.Literal.String.Double, '"'),
(Token.Literal.String.Double, 'hello'),
(Token.Literal.String.Double, '"'),
(Token.Punctuation, ')'),
(Token.Text, '\n'),
]
print_formatted_text(PygmentsTokens(text))Similarly, it is also possible to print the output of a Pygments lexer:
.. code:: python
import pygments
from pygments.token import Token
from pygments.lexers.python import PythonLexer
from prompt_toolkit.formatted_text import PygmentsTokens
from prompt_toolkit import print_formatted_text
# Printing the output of a pygments lexer.
tokens = list(pygments.lex('print("Hello")', lexer=PythonLexer()))
print_formatted_text(PygmentsTokens(tokens))Prompt_toolkit ships with a default colorscheme which styles it just like
Pygments would do, but if you'd like to change the colors, keep in mind that
Pygments tokens map to classnames like this:
+-----------------------------------+---------------------------------------------+
| pygments.Token | prompt_toolkit classname |
+===================================+=============================================+
| - Token.Keyword | - "class:pygments.keyword" |
| - Token.Punctuation | - "class:pygments.punctuation" |
| - Token.Literal.String.Double | - "class:pygments.literal.string.double"|
| - Token.Text | - "class:pygments.text" |
| - Token | - "class:pygments" |
+-----------------------------------+---------------------------------------------+
A classname like pygments.literal.string.double is actually decomposed in
the following four classnames: pygments, pygments.literal,pygments.literal.string and pygments.literal.string.double. The final
style is computed by combining the style for these four classnames. So,
changing the style from these Pygments tokens can be done as follows:
.. code:: python
from prompt_toolkit.styles import Style
style = Style.from_dict({
'pygments.keyword': 'underline',
'pygments.literal.string': 'bg:#00ff00 #ffffff',
})
print_formatted_text(PygmentsTokens(tokens), style=style)to_formatted_text
^^^^^^^^^^^^^^^^^
A useful function to know about is
:func:~prompt_toolkit.formatted_text.to_formatted_text. This ensures that the
given input is valid formatted text. While doing so, an additional style can be
applied as well.
.. code:: python
from prompt_toolkit.formatted_text import to_formatted_text, HTML
from prompt_toolkit import print_formatted_text
html = HTML('<aaa>Hello</aaa> world!')
text = to_formatted_text(html, style='class:my_html bg:#00ff00 italic')
print_formatted_text(text)Pages Progress Bars (`docs/pages/progress_bars.rst`)
.. _progress_bars:
Progress bars
Prompt_toolkit ships with a high level API for displaying progress bars,
inspired by tqdm <https://github.com/tqdm/tqdm>_
.. warning::
The API for the prompt_toolkit progress bars is still very new and can
possibly change in the future. It is usable and tested, but keep this in
mind when upgrading.Remember that the examples directory <https://github.com/prompt-toolkit/python-prompt-toolkit/tree/master/examples>_
of the prompt_toolkit repository ships with many progress bar examples as well.
Simple progress bar
Creating a new progress bar can be done by calling the
:class:~prompt_toolkit.shortcuts.ProgressBar context manager.
The progress can be displayed for any iterable. This works by wrapping the
iterable (like range) with the
:class:~prompt_toolkit.shortcuts.ProgressBar context manager itself. This
way, the progress bar knows when the next item is consumed by the forloop and
when progress happens.
.. code:: python
from prompt_toolkit.shortcuts import ProgressBar
import time
with ProgressBar() as pb:
for i in pb(range(800)):
time.sleep(.01).. image:: ../images/progress-bars/simple-progress-bar.png
Keep in mind that not all iterables can report their total length. This happens
with a typical generator. In that case, you can still pass the total as follows
in order to make displaying the progress possible:
.. code:: python
def some_iterable():
yield ...
with ProgressBar() as pb:
for i in pb(some_iterable(), total=1000):
time.sleep(.01)Multiple parallel tasks
A prompt_toolkit :class:~prompt_toolkit.shortcuts.ProgressBar can display the
progress of multiple tasks running in parallel. Each task can run in a separate
thread and the :class:~prompt_toolkit.shortcuts.ProgressBar user interface
runs in its own thread.
Notice that we set the "daemon" flag for both threads that run the tasks. This
is because control-c will stop the progress and quit our application. We don't
want the application to wait for the background threads to finish. Whether you
want this depends on the application.
.. code:: python
from prompt_toolkit.shortcuts import ProgressBar
import time
import threading
with ProgressBar() as pb:
# Two parallel tasks.
def task_1():
for i in pb(range(100)):
time.sleep(.05)
def task_2():
for i in pb(range(150)):
time.sleep(.08)
# Start threads.
t1 = threading.Thread(target=task_1)
t2 = threading.Thread(target=task_2)
t1.daemon = True
t2.daemon = True
t1.start()
t2.start()
# Wait for the threads to finish. We use a timeout for the join() call,
# because on Windows, join cannot be interrupted by Control-C or any other
# signal.
for t in [t1, t2]:
while t.is_alive():
t.join(timeout=.5).. image:: ../images/progress-bars/two-tasks.png
Adding a title and label
Each progress bar can have one title, and for each task an individual label.
Both the title and the labels can be :ref:formatted text <formatted_text>.
.. code:: python
from prompt_toolkit.shortcuts import ProgressBar
from prompt_toolkit.formatted_text import HTML
import time
title = HTML('Downloading <style bg="yellow" fg="black">4 files...</style>')
label = HTML('<ansired>some file</ansired>: ')
with ProgressBar(title=title) as pb:
for i in pb(range(800), label=label):
time.sleep(.01).. image:: ../images/progress-bars/colored-title-and-label.png
Formatting the progress bar
The visualization of a :class:~prompt_toolkit.shortcuts.ProgressBar can be
customized by using a different sequence of formatters. The default formatting
looks something like this:
.. code:: python
from prompt_toolkit.shortcuts.progress_bar.formatters import *
default_formatting = [
Label(),
Text(' '),
Percentage(),
Text(' '),
Bar(),
Text(' '),
Progress(),
Text(' '),
Text('eta [', style='class:time-left'),
TimeLeft(),
Text(']', style='class:time-left'),
Text(' '),
]That sequence of
:class:~prompt_toolkit.shortcuts.progress_bar.formatters.Formatter can be
passed to the formatter argument of
:class:~prompt_toolkit.shortcuts.ProgressBar. So, we could change this and
modify the progress bar to look like an apt-get style progress bar:
.. code:: python
from prompt_toolkit.shortcuts import ProgressBar
from prompt_toolkit.styles import Style
from prompt_toolkit.shortcuts.progress_bar import formatters
import time
style = Style.from_dict({
'label': 'bg:#ffff00 #000000',
'percentage': 'bg:#ffff00 #000000',
'current': '#448844',
'bar': '',
})
custom_formatters = [
formatters.Label(),
formatters.Text(': [', style='class:percentage'),
formatters.Percentage(),
formatters.Text(']', style='class:percentage'),
formatters.Text(' '),
formatters.Bar(sym_a='#', sym_b='#', sym_c='.'),
formatters.Text(' '),
]
with ProgressBar(style=style, formatters=custom_formatters) as pb:
for i in pb(range(1600), label='Installing'):
time.sleep(.01).. image:: ../images/progress-bars/apt-get.png
Adding key bindings and toolbar
Like other prompt_toolkit applications, we can add custom key bindings, by
passing a :class:~prompt_toolkit.key_binding.KeyBindings object:
.. code:: python
from prompt_toolkit import HTML
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.patch_stdout import patch_stdout
from prompt_toolkit.shortcuts import ProgressBar
import os
import time
import signal
bottom_toolbar = HTML(' **[f]** Print "f" **[x]** Abort.')
# Create custom key bindings first.
kb = KeyBindings()
cancel = [False]
@kb.add('f')
def _(event):
print('You pressed `f`.')
@kb.add('x')
def _(event):
" Send Abort (control-c) signal. "
cancel[0] = True
os.kill(os.getpid(), signal.SIGINT)
# Use `patch_stdout`, to make sure that prints go above the
# application.
with patch_stdout():
with ProgressBar(key_bindings=kb, bottom_toolbar=bottom_toolbar) as pb:
for i in pb(range(800)):
time.sleep(.01)
# Stop when the cancel flag has been set.
if cancel[0]:
breakNotice that we use :func:~prompt_toolkit.patch_stdout.patch_stdout to make
printing text possible while the progress bar is displayed. This ensures that
printing happens above the progress bar.
Further, when "x" is pressed, we set a cancel flag, which stops the progress.
It would also be possible to send SIGINT to the main thread, but that's not
always considered a clean way of cancelling something.
In the example above, we also display a toolbar at the bottom which shows the
key bindings.
.. image:: ../images/progress-bars/custom-key-bindings.png
:ref:Read more about key bindings ...<key_bindings>
Pages Reference (`docs/pages/reference.rst`)
Reference
Application
.. automodule:: prompt_toolkit.application
:members: Application, get_app, get_app_or_none, set_app,
create_app_session, AppSession, get_app_session, DummyApplication,
in_terminal, run_in_terminal,
Formatted text
.. automodule:: prompt_toolkit.formatted_text
:members:
Buffer
.. automodule:: prompt_toolkit.buffer
:members:
Selection
.. automodule:: prompt_toolkit.selection
:members:
Clipboard
.. automodule:: prompt_toolkit.clipboard
:members: Clipboard, ClipboardData, DummyClipboard, DynamicClipboard, InMemoryClipboard
.. automodule:: prompt_toolkit.clipboard.pyperclip
:members:
Auto completion
.. automodule:: prompt_toolkit.completion
:members:
Document
.. automodule:: prompt_toolkit.document
:members:
Enums
.. automodule:: prompt_toolkit.enums
:members:
History
.. automodule:: prompt_toolkit.history
:members:
Keys
.. automodule:: prompt_toolkit.keys
:members:
Style
.. automodule:: prompt_toolkit.styles
:members: Attrs, ANSI_COLOR_NAMES, BaseStyle, DummyStyle, DynamicStyle,
Style, Priority, merge_styles, style_from_pygments_cls,
style_from_pygments_dict, pygments_token_to_classname, NAMED_COLORS,
StyleTransformation, SwapLightAndDarkStyleTransformation,
AdjustBrightnessStyleTransformation, merge_style_transformations,
DummyStyleTransformation, ConditionalStyleTransformation,
DynamicStyleTransformation
Shortcuts
.. automodule:: prompt_toolkit.shortcuts
:members: prompt, PromptSession, confirm, CompleteStyle,
create_confirm_session, clear, clear_title, print_formatted_text,
set_title, ProgressBar, input_dialog, message_dialog, progress_dialog,
radiolist_dialog, yes_no_dialog, button_dialog, choice
.. automodule:: prompt_toolkit.shortcuts.progress_bar.formatters
:members:
Validation
.. automodule:: prompt_toolkit.validation
:members:
Auto suggestion
.. automodule:: prompt_toolkit.auto_suggest
:members:
Renderer
.. automodule:: prompt_toolkit.renderer
:members:
Lexers
.. automodule:: prompt_toolkit.lexers
:members:
Layout
.. automodule:: prompt_toolkit.layout
The layout class itself
^^^^^^^^^^^^^^^^^^^^^^^
.. autoclass:: prompt_toolkit.layout.Layout
:members:
.. autoclass:: prompt_toolkit.layout.InvalidLayoutError
:members:
.. autoclass:: prompt_toolkit.layout.walk
:members:
Containers
^^^^^^^^^^
.. autoclass:: prompt_toolkit.layout.Container
:members:
.. autoclass:: prompt_toolkit.layout.HSplit
:members:
.. autoclass:: prompt_toolkit.layout.VSplit
:members:
.. autoclass:: prompt_toolkit.layout.FloatContainer
:members:
.. autoclass:: prompt_toolkit.layout.Float
:members:
.. autoclass:: prompt_toolkit.layout.Window
:members:
.. autoclass:: prompt_toolkit.layout.WindowAlign
:members:
.. autoclass:: prompt_toolkit.layout.ConditionalContainer
:members:
.. autoclass:: prompt_toolkit.layout.DynamicContainer
:members:
.. autoclass:: prompt_toolkit.layout.ScrollablePane
:members:
.. autoclass:: prompt_toolkit.layout.ScrollOffsets
:members:
.. autoclass:: prompt_toolkit.layout.ColorColumn
:members:
.. autoclass:: prompt_toolkit.layout.to_container
:members:
.. autoclass:: prompt_toolkit.layout.to_window
:members:
.. autoclass:: prompt_toolkit.layout.is_container
:members:
.. autoclass:: prompt_toolkit.layout.HorizontalAlign
:members:
.. autoclass:: prompt_toolkit.layout.VerticalAlign
:members:
Controls
^^^^^^^^
.. autoclass:: prompt_toolkit.layout.BufferControl
:members:
.. autoclass:: prompt_toolkit.layout.SearchBufferControl
:members:
.. autoclass:: prompt_toolkit.layout.DummyControl
:members:
.. autoclass:: prompt_toolkit.layout.FormattedTextControl
:members:
.. autoclass:: prompt_toolkit.layout.UIControl
:members:
.. autoclass:: prompt_toolkit.layout.UIContent
:members:
Other
^^^^^
Sizing
""""""
.. autoclass:: prompt_toolkit.layout.Dimension
:members:
Margins
"""""""
.. autoclass:: prompt_toolkit.layout.Margin
:members:
.. autoclass:: prompt_toolkit.layout.NumberedMargin
:members:
.. autoclass:: prompt_toolkit.layout.ScrollbarMargin
:members:
.. autoclass:: prompt_toolkit.layout.ConditionalMargin
:members:
.. autoclass:: prompt_toolkit.layout.PromptMargin
:members:
Completion Menus
""""""""""""""""
.. autoclass:: prompt_toolkit.layout.CompletionsMenu
:members:
.. autoclass:: prompt_toolkit.layout.MultiColumnCompletionsMenu
:members:
Processors
""""""""""
.. automodule:: prompt_toolkit.layout.processors
:members:
Utils
"""""
.. automodule:: prompt_toolkit.layout.utils
:members:
Screen
""""""
.. automodule:: prompt_toolkit.layout.screen
:members:
Widgets
.. automodule:: prompt_toolkit.widgets
:members: TextArea, Label, Button, Frame, Shadow, Box, VerticalLine,
HorizontalLine, RadioList, Checkbox, ProgressBar, CompletionsToolbar,
FormattedTextToolbar, SearchToolbar, SystemToolbar, ValidationToolbar,
MenuContainer, MenuItem
Filters
.. automodule:: prompt_toolkit.filters
:members:
.. autoclass:: prompt_toolkit.filters.Filter
:members:
:no-index:
.. autoclass:: prompt_toolkit.filters.Condition
:members:
:no-index:
.. automodule:: prompt_toolkit.filters.utils
:members:
.. automodule:: prompt_toolkit.filters.app
:members:
Key binding
.. automodule:: prompt_toolkit.key_binding
:members: KeyBindingsBase, KeyBindings, ConditionalKeyBindings,
merge_key_bindings, DynamicKeyBindings
.. automodule:: prompt_toolkit.key_binding.defaults
:members:
.. automodule:: prompt_toolkit.key_binding.vi_state
:members:
.. automodule:: prompt_toolkit.key_binding.key_processor
:members:
Eventloop
.. automodule:: prompt_toolkit.eventloop
:members: run_in_executor_with_context, call_soon_threadsafe,
get_traceback_from_context
.. automodule:: prompt_toolkit.eventloop.inputhook
:members:
.. automodule:: prompt_toolkit.eventloop.utils
:members:
Input
.. automodule:: prompt_toolkit.input
:members: Input, DummyInput, create_input, create_pipe_input
.. automodule:: prompt_toolkit.input.vt100
:members:
.. automodule:: prompt_toolkit.input.vt100_parser
:members:
.. automodule:: prompt_toolkit.input.ansi_escape_sequences
:members:
.. automodule:: prompt_toolkit.input.win32
:members:
Output
.. automodule:: prompt_toolkit.output
:members: Output, DummyOutput, ColorDepth, create_output
.. automodule:: prompt_toolkit.output.vt100
:members:
.. automodule:: prompt_toolkit.output.win32
:members:
Data structures
.. autoclass:: prompt_toolkit.layout.WindowRenderInfo
:members:
.. autoclass:: prompt_toolkit.data_structures.Point
:members:
.. autoclass:: prompt_toolkit.data_structures.Size
:members:
Patch stdout
.. automodule:: prompt_toolkit.patch_stdout
:members: patch_stdout, StdoutProxy
Pages Related Projects (`docs/pages/related_projects.rst`)
.. _related_projects:
Related projects
There are some other Python libraries that provide similar functionality that
are also worth checking out:
Urwid <http://urwid.org/>_Textual <https://textual.textualize.io/>_Rich <https://rich.readthedocs.io/>_
Pages Advanced Topics Architecture (`docs/pages/advanced_topics/architecture.rst`)
.. _architecture:
Architecture
TODO: this is a little outdated.
::
+---------------------------------------------------------------+
| InputStream |
| =========== |
| - Parses the input stream coming from a VT100 |
| compatible terminal. Translates it into data input |
| and control characters. Calls the corresponding |
| handlers of the `InputStreamHandler` instance. |
| |
| e.g. Translate '\x1b[6~' into "Keys.PageDown", call |
| the `feed_key` method of `InputProcessor`. |
+---------------------------------------------------------------+
|
v
+---------------------------------------------------------------+
| InputStreamHandler |
| ================== |
| - Has a `Registry` of key bindings, it calls the |
| bindings according to the received keys and the |
| input mode. |
| |
| We have Vi and Emacs bindings.
+---------------------------------------------------------------+
|
v
+---------------------------------------------------------------+
| Key bindings |
| ============ |
| - Every key binding consists of a function that |
| receives an `Event` and usually it operates on |
| the `Buffer` object. (It could insert data or |
| move the cursor for example.) |
+---------------------------------------------------------------+
|
| Most of the key bindings operate on a `Buffer` object, but
| they don't have to. They could also change the visibility
| of a menu for instance, or change the color scheme.
|
v
+---------------------------------------------------------------+
| Buffer |
| ====== |
| - Contains a data structure to hold the current |
| input (text and cursor position). This class |
| implements all text manipulations and cursor |
| movements (Like e.g. cursor_forward, insert_char |
| or delete_word.) |
| |
| +-----------------------------------------------+ |
| | Document (text, cursor_position) | |
| | ================================ | |
| | Accessed as the `document` property of the | |
| | `Buffer` class. This is a wrapper around the | |
| | text and cursor position, and contains | |
| | methods for querying this data , e.g. to give | |
| | the text before the cursor. | |
| +-----------------------------------------------+ |
+---------------------------------------------------------------+
|
| Normally after every key press, the output will be
| rendered again. This happens in the event loop of
| the `Application` where `Renderer.render` is called.
v
+---------------------------------------------------------------+
| Layout |
| ====== |
| - When the renderer should redraw, the renderer |
| asks the layout what the output should look like. |
| - The layout operates on a `Screen` object that he |
| received from the `Renderer` and will put the |
| toolbars, menus, highlighted content and prompt |
| in place. |
| |
| +-----------------------------------------------+ |
| | Menus, toolbars, prompt | |
| | ======================= | |
| | | |
| +-----------------------------------------------+ |
+---------------------------------------------------------------+
|
v
+---------------------------------------------------------------+
| Renderer |
| ======== |
| - Calculates the difference between the last output |
| and the new one and writes it to the terminal |
| output. |
+---------------------------------------------------------------+Pages Advanced Topics Asyncio (`docs/pages/advanced_topics/asyncio.rst`)
.. _asyncio:
Running on top of the `asyncio` event loop
.. note::
New in prompt_toolkit 3.0. (In prompt_toolkit 2.0 this was possible using a
work-around).Prompt_toolkit 3.0 uses asyncio natively. Calling Application.run() will
automatically run the asyncio event loop.
If however you want to run a prompt_toolkit Application within an asyncio
environment, you have to call the run_async method, like this:
.. code:: python
from prompt_toolkit.application import Application
async def main():
# Define application.
application = Application(
...
)
result = await application.run_async()
print(result)
asyncio.get_event_loop().run_until_complete(main())Pages Advanced Topics Filters (`docs/pages/advanced_topics/filters.rst`)
.. _filters:
Filters
Many places in prompt_toolkit require a boolean value that can change over
time. For instance:
- to specify whether a part of the layout needs to be visible or not;
- or to decide whether a certain key binding needs to be active or not;
- or the
wrap_linesoption of
:class:~prompt_toolkit.layout.BufferControl; - etcetera.
These booleans are often dynamic and can change at runtime. For instance, the
search toolbar should only be visible when the user is actually searching (when
the search buffer has the focus). The wrap_lines option could be changed
with a certain key binding. And that key binding could only work when the
default buffer got the focus.
In prompt_toolkit, we decided to reduce the amount of state in the whole
framework, and apply a simple kind of reactive programming to describe the flow
of these booleans as expressions. (It's one-way only: if a key binding needs to
know whether it's active or not, it can follow this flow by evaluating an
expression.)
The (abstract) base class is :class:~prompt_toolkit.filters.Filter, which
wraps an expression that takes no input and evaluates to a boolean. Getting the
state of a filter is done by simply calling it.
An example
The most obvious way to create such a :class:~prompt_toolkit.filters.Filter
instance is by creating a :class:~prompt_toolkit.filters.Condition instance
from a function. For instance, the following condition will evaluate toTrue when the user is searching:
.. code:: python
from prompt_toolkit.application.current import get_app
from prompt_toolkit.filters import Condition
is_searching = Condition(lambda: get_app().is_searching)A different way of writing this, is by using the decorator syntax:
.. code:: python
from prompt_toolkit.application.current import get_app
from prompt_toolkit.filters import Condition
@Condition
def is_searching():
return get_app().is_searchingThis filter can then be used in a key binding, like in the following snippet:
.. code:: python
from prompt_toolkit.key_binding import KeyBindings
kb = KeyBindings()
@kb.add('c-t', filter=is_searching)
def _(event):
# Do, something, but only when searching.
passIf we want to know the boolean value of this filter, we have to call it like a
function:
.. code:: python
print(is_searching())Built-in filters
There are many built-in filters, ready to use. All of them have a lowercase
name, because they represent the wrapped function underneath, and can be called
as a function.
:class:
~prompt_toolkit.filters.app.has_arg:class:
~prompt_toolkit.filters.app.has_completions:class:
~prompt_toolkit.filters.app.has_focus:class:
~prompt_toolkit.filters.app.buffer_has_focus:class:
~prompt_toolkit.filters.app.has_selection:class:
~prompt_toolkit.filters.app.has_validation_error:class:
~prompt_toolkit.filters.app.is_aborting:class:
~prompt_toolkit.filters.app.is_done:class:
~prompt_toolkit.filters.app.is_read_only:class:
~prompt_toolkit.filters.app.is_multiline:class:
~prompt_toolkit.filters.app.renderer_height_is_known:class:
~prompt_toolkit.filters.app.in_editing_mode:class:
~prompt_toolkit.filters.app.in_paste_mode:class:
~prompt_toolkit.filters.app.vi_mode:class:
~prompt_toolkit.filters.app.vi_navigation_mode:class:
~prompt_toolkit.filters.app.vi_insert_mode:class:
~prompt_toolkit.filters.app.vi_insert_multiple_mode:class:
~prompt_toolkit.filters.app.vi_replace_mode:class:
~prompt_toolkit.filters.app.vi_selection_mode:class:
~prompt_toolkit.filters.app.vi_waiting_for_text_object_mode:class:
~prompt_toolkit.filters.app.vi_digraph_mode:class:
~prompt_toolkit.filters.app.emacs_mode:class:
~prompt_toolkit.filters.app.emacs_insert_mode:class:
~prompt_toolkit.filters.app.emacs_selection_mode:class:
~prompt_toolkit.filters.app.is_searching:class:
~prompt_toolkit.filters.app.control_is_searchable:class:
~prompt_toolkit.filters.app.vi_search_direction_reversed
Combining filters
Filters can be chained with the & (AND) and | (OR) operators and
negated with the ~ (negation) operator.
Some examples:
.. code:: python
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.filters import has_selection, has_selection
kb = KeyBindings()
@kb.add('c-t', filter=~is_searching)
def _(event):
" Do something, but not while searching. "
pass
@kb.add('c-t', filter=has_search | has_selection)
def _(event):
" Do something, but only when searching or when there is a selection. "
passto_filter
Finally, in many situations you want your code to expose an API that is able to
deal with both booleans as well as filters. For instance, when for most users a
boolean works fine because they don't need to change the value over time, while
some advanced users want to be able this value to a certain setting or event
that does changes over time.
In order to handle both use cases, there is a utility called
:func:~prompt_toolkit.filters.utils.to_filter.
This is a function that takes
either a boolean or an actual :class:~prompt_toolkit.filters.Filter
instance, and always returns a :class:~prompt_toolkit.filters.Filter.
.. code:: python
from prompt_toolkit.filters.utils import to_filter
# In each of the following three examples, 'f' will be a `Filter`
# instance.
f = to_filter(True)
f = to_filter(False)
f = to_filter(Condition(lambda: True))
f = to_filter(has_search | has_selection)Pages Advanced Topics Index (`docs/pages/advanced_topics/index.rst`)
.. _advanced_topics:
Advanced topics
.. toctree::
:caption: Contents:
:maxdepth: 1
key_bindings
styling
filters
rendering_flow
asyncio
unit_testing
input_hooks
architecture
rendering_pipelinePages Advanced Topics Input Hooks (`docs/pages/advanced_topics/input_hooks.rst`)
.. _input_hooks:
Input hooks
Input hooks are a tool for inserting an external event loop into the
prompt_toolkit event loop, so that the other loop can run as long as
prompt_toolkit (actually asyncio) is idle. This is used in applications likeIPython <https://ipython.org/>_, so that GUI toolkits can display their
windows while we wait at the prompt for user input.
As a consequence, we will "trampoline" back and forth between two event loops.
.. note::
This will use a :class:`~asyncio.SelectorEventLoop`, not the
:class:`~asyncio.ProactorEventLoop` (on Windows) due to the way the
implementation works (contributions are welcome to make that work)... code:: python
from prompt_toolkit.eventloop.inputhook import set_eventloop_with_inputhook
def inputhook(inputhook_context):
# At this point, we run the other loop. This loop is supposed to run
# until either `inputhook_context.fileno` becomes ready for reading or
# `inputhook_context.input_is_ready()` returns True.
# A good way is to register this file descriptor in this other event
# loop with a callback that stops this loop when this FD becomes ready.
# There is no need to actually read anything from the FD.
while True:
...
set_eventloop_with_inputhook(inputhook)
# Any asyncio code at this point will now use this new loop, with input
# hook installed.Pages Advanced Topics Key Bindings (`docs/pages/advanced_topics/key_bindings.rst`)
.. _key_bindings:
More about key bindings
This page contains a few additional notes about key bindings.
Key bindings can be defined as follows by creating a
:class:~prompt_toolkit.key_binding.KeyBindings instance:
.. code:: python
from prompt_toolkit.key_binding import KeyBindings
bindings = KeyBindings()
@bindings.add('a')
def _(event):
" Do something if 'a' has been pressed. "
...
@bindings.add('c-t')
def _(event):
" Do something if Control-T has been pressed. "
..... note::
:kbd:`c-q` (control-q) and :kbd:`c-s` (control-s) are often captured by the
terminal, because they were used traditionally for software flow control.
When this is enabled, the application will automatically freeze when
:kbd:`c-s` is pressed, until :kbd:`c-q` is pressed. It won't be possible to
bind these keys.
In order to disable this, execute the following command in your shell, or even
add it to your `.bashrc`.
.. code::
stty -ixonKey bindings can even consist of a sequence of multiple keys. The binding is
only triggered when all the keys in this sequence are pressed.
.. code:: python
@bindings.add('a', 'b')
def _(event):
" Do something if 'a' is pressed and then 'b' is pressed. "
...If the user presses only a, then nothing will happen until either a second
key (like b) has been pressed or until the timeout expires (see later).
List of special keys
Besides literal characters, any of the following keys can be used in a key
binding:
+-------------------+-----------------------------------------+
| Name + Possible keys |
+===================+=========================================+
| Escape | :kbd:escape |
| Shift + escape | :kbd:s-escape |
+-------------------+-----------------------------------------+
| Arrows | :kbd:left, |
| | :kbd:right, |
| | :kbd:up, |
| | :kbd:down |
+-------------------+-----------------------------------------+
| Navigation | :kbd:home, |
| | :kbd:end, |
| | :kbd:delete, |
| | :kbd:pageup, |
| | :kbd:pagedown, |
| | :kbd:insert |
+-------------------+-----------------------------------------+
| Control+letter | :kbd:c-a, :kbd:c-b, :kbd:c-c, |
| | :kbd:c-d, :kbd:c-e, :kbd:c-f, |
| | :kbd:c-g, :kbd:c-h, :kbd:c-i, |
| | :kbd:c-j, :kbd:c-k, :kbd:c-l, |
| | |
| | :kbd:c-m, :kbd:c-n, :kbd:c-o, |
| | :kbd:c-p, :kbd:c-q, :kbd:c-r, |
| | :kbd:c-s, :kbd:c-t, :kbd:c-u, |
| | :kbd:c-v, :kbd:c-w, :kbd:c-x, |
| | |
| | :kbd:c-y, :kbd:c-z |
+-------------------+-----------------------------------------+
| Control + number | :kbd:c-1, :kbd:c-2, :kbd:c-3, |
| | :kbd:c-4, :kbd:c-5, :kbd:c-6, |
| | :kbd:c-7, :kbd:c-8, :kbd:c-9, |
| | :kbd:c-0 |
+-------------------+-----------------------------------------+
| Control + arrow | :kbd:c-left, |
| | :kbd:c-right, |
| | :kbd:c-up, |
| | :kbd:c-down |
+-------------------+-----------------------------------------+
| Other control | :kbd:c-@, |
| keys | :kbd:c-\\, |
| | :kbd:c-], |
| | :kbd:c-^, |
| | :kbd:c-_, |
| | :kbd:c-delete |
+-------------------+-----------------------------------------+
| Shift + arrow | :kbd:s-left, |
| | :kbd:s-right, |
| | :kbd:s-up, |
| | :kbd:s-down |
+-------------------+-----------------------------------------+
| Control + Shift + | :kbd:c-s-left, |
| arrow | :kbd:c-s-right, |
| | :kbd:c-s-up, |
| | :kbd:c-s-down |
+-------------------+-----------------------------------------+
| Other shift | :kbd:s-delete, |
| keys | :kbd:s-tab |
+-------------------+-----------------------------------------+
| F-keys | :kbd:f1, :kbd:f2, :kbd:f3, |
| | :kbd:f4, :kbd:f5, :kbd:f6, |
| | :kbd:f7, :kbd:f8, :kbd:f9, |
| | :kbd:f10, :kbd:f11, :kbd:f12, |
| | |
| | :kbd:f13, :kbd:f14, :kbd:f15, |
| | :kbd:f16, :kbd:f17, :kbd:f18, |
| | :kbd:f19, :kbd:f20, :kbd:f21, |
| | :kbd:f22, :kbd:f23, :kbd:f24 |
+-------------------+-----------------------------------------+
There are a couple of useful aliases as well:
+-------------------+-------------------+
| :kbd:c-h | :kbd:backspace |
+-------------------+-------------------+
| :kbd:c-@ | :kbd:c-space |
+-------------------+-------------------+
| :kbd:c-m | :kbd:enter |
+-------------------+-------------------+
| :kbd:c-i | :kbd:tab |
+-------------------+-------------------+
.. note::
Note that the supported keys are limited to what typical VT100 terminals
offer. Binding :kbd:`c-7` (control + number 7) for instance is not
supported.Binding alt+something, option+something or meta+something
Vt100 terminals translate the alt key into a leading :kbd:escape key.
For instance, in order to handle :kbd:alt-f, we have to handle
:kbd:escape + :kbd:f. Notice that we receive this as two individual keys.
This means that it's exactly the same as first typing :kbd:escape and then
typing :kbd:f. Something this alt-key is also known as option or meta.
In code that looks as follows:
.. code:: python
@bindings.add('escape', 'f')
def _(event):
" Do something if alt-f or meta-f have been pressed. "Wildcards
Sometimes you want to catch any key that follows after a certain key stroke.
This is possible by binding the '
.. code:: python
@bindings.add('a', '<any>')
def _(event):
...This will handle aa, ab, ac, etcetera. The key binding can check theevent object for which keys exactly have been pressed.
Attaching a filter (condition)
In order to enable a key binding according to a certain condition, we have to
pass it a :class:~prompt_toolkit.filters.Filter, usually a
:class:~prompt_toolkit.filters.Condition instance. (:ref:Read more about filters <filters>.)
.. code:: python
from prompt_toolkit.filters import Condition
@Condition
def is_active():
" Only activate key binding on the second half of each minute. "
return datetime.datetime.now().second > 30
@bindings.add('c-t', filter=is_active)
def _(event):
# ...
passThe key binding will be ignored when this condition is not satisfied.
ConditionalKeyBindings: Disabling a set of key bindings
Sometimes you want to enable or disable a whole set of key bindings according
to a certain condition. This is possible by wrapping it in a
:class:~prompt_toolkit.key_binding.ConditionalKeyBindings object.
.. code:: python
from prompt_toolkit.key_binding import ConditionalKeyBindings
@Condition
def is_active():
" Only activate key binding on the second half of each minute. "
return datetime.datetime.now().second > 30
bindings = ConditionalKeyBindings(
key_bindings=my_bindings,
filter=is_active)If the condition is not satisfied, all the key bindings in my_bindings above
will be ignored.
Merging key bindings
Sometimes you have different parts of your application generate a collection of
key bindings. It is possible to merge them together through the
:func:~prompt_toolkit.key_binding.merge_key_bindings function. This is
preferred above passing a :class:~prompt_toolkit.key_binding.KeyBindings
object around and having everyone populate it.
.. code:: python
from prompt_toolkit.key_binding import merge_key_bindings
bindings = merge_key_bindings([
bindings1,
bindings2,
])Eager
Usually not required, but if ever you have to override an existing key binding,
the eager flag can be useful.
Suppose that there is already an active binding for ab and you'd like to add
a second binding that only handles a. When the user presses only a,
prompt_toolkit has to wait for the next key press in order to know which
handler to call.
By passing the eager flag to this second binding, we are actually saying that
prompt_toolkit shouldn't wait for longer matches when all the keys in this key
binding are matched. So, if a has been pressed, this second binding will be
called, even if there's an active ab binding.
.. code:: python
@bindings.add('a', 'b')
def binding_1(event):
...
@bindings.add('a', eager=True)
def binding_2(event):
...This is mainly useful in order to conditionally override another binding.
Asyncio coroutines
Key binding handlers can be asyncio coroutines.
.. code:: python
from prompt_toolkit.application import in_terminal
@bindings.add('x')
async def print_hello(event):
"""
Pressing 'x' will print 5 times "hello" in the background above the
prompt.
"""
for i in range(5):
# Print hello above the current prompt.
async with in_terminal():
print('hello')
# Sleep, but allow further input editing in the meantime.
await asyncio.sleep(1)If the user accepts the input on the prompt, while this coroutine is not yet
finished , an asyncio.CancelledError exception will be thrown in this
coroutine.
Timeouts
There are two timeout settings that effect the handling of keys.
Application.ttimeoutlen: Like Vim'sttimeoutlenoption.
When to flush the input (For flushing escape keys.) This is important on
terminals that use vt100 input. We can't distinguish the escape key from for
instance the left-arrow key, if we don't know what follows after "\x1b". This
little timer will consider "\x1b" to be escape if nothing did follow in this
time span. This seems to work like thettimeoutlenoption in Vim.KeyProcessor.timeoutlen: like Vim'stimeoutlenoption.
This can beNoneor a float. For instance, suppose that we have a key
binding AB and a second key binding A. If the uses presses A and then waits,
we don't handle this binding yet (unless it was marked 'eager'), because we
don't know what will follow. This timeout is the maximum amount of time that
we wait until we call the handlers anyway. PassNoneto disable this
timeout.
Recording macros
Both Emacs and Vi mode allow macro recording. By default, all key presses are
recorded during a macro, but it is possible to exclude certain keys by setting
the record_in_macro parameter to False:
.. code:: python
@bindings.add('c-t', record_in_macro=False)
def _(event):
# ...
passCreating new Vi text objects and operators
We tried very hard to ship prompt_toolkit with as many as possible Vi text
objects and operators, so that text editing feels as natural as possible to Vi
users.
If you wish to create a new text object or key binding, that is actually
possible. Check the custom-vi-operator-and-text-object.py example for more
information.
Handling SIGINT
The SIGINT Unix signal can be handled by binding <sigint>. For instance:
.. code:: python
@bindings.add('<sigint>')
def _(event):
# ...
passThis will handle a SIGINT that was sent by an external application into the
process. Handling control-c should be done by binding c-c. (The terminal
input is set to raw mode, which means that a c-c won't be translated into a
SIGINT.)
For a PromptSession, there is a default binding for <sigint> that
corresponds to c-c: it will exit the prompt, raising aKeyboardInterrupt exception.
Processing `.inputrc`
GNU readline can be configured using an .inputrc configuration file. This file
contains key bindings as well as certain settings. Right now, prompt_toolkit
doesn't support .inputrc, but it should be possible in the future.
Pages Advanced Topics Rendering Flow (`docs/pages/advanced_topics/rendering_flow.rst`)
.. _rendering_flow:
The rendering flow
Understanding the rendering flow is important for understanding how
:class:~prompt_toolkit.layout.Container and
:class:~prompt_toolkit.layout.UIControl objects interact. We will demonstrate
it by explaining the flow around a
:class:~prompt_toolkit.layout.BufferControl.
.. note::
A :class:`~prompt_toolkit.layout.BufferControl` is a
:class:`~prompt_toolkit.layout.UIControl` for displaying the content of a
:class:`~prompt_toolkit.buffer.Buffer`. A buffer is the object that holds
any editable region of text. Like all controls, it has to be wrapped into a
:class:`~prompt_toolkit.layout.Window`.Let's take the following code:
.. code:: python
from prompt_toolkit.enums import DEFAULT_BUFFER
from prompt_toolkit.layout.containers import Window
from prompt_toolkit.layout.controls import BufferControl
from prompt_toolkit.buffer import Buffer
b = Buffer(name=DEFAULT_BUFFER)
Window(content=BufferControl(buffer=b))What happens when a :class:~prompt_toolkit.renderer.Renderer objects wants a
:class:~prompt_toolkit.layout.Container to be rendered on a certain
:class:~prompt_toolkit.layout.screen.Screen?
The visualization happens in several steps:
The :class:
~prompt_toolkit.renderer.Renderercalls the
:meth:~prompt_toolkit.layout.Container.write_to_screenmethod
of a :class:~prompt_toolkit.layout.Container.
This is a request to paint the layout in a rectangle of a certain size.The :class:
~prompt_toolkit.layout.Windowobject then requests
the :class:~prompt_toolkit.layout.UIControlto create a
:class:~prompt_toolkit.layout.UIContentinstance (by calling
:meth:~prompt_toolkit.layout.UIControl.create_content).
The user control receives the dimensions of the window, but can still
decide to create more or less content.Inside the :meth:
~prompt_toolkit.layout.UIControl.create_content
method of :class:~prompt_toolkit.layout.UIControl, there are several
steps:First, the buffer's text is passed to the
:meth:~prompt_toolkit.lexers.Lexer.lex_documentmethod of a
:class:~prompt_toolkit.lexers.Lexer. This returns a function which
for a given line number, returns a "formatted text list" for that line
(that's a list of(style_string, text)tuples).This list is passed through a list of
:class:~prompt_toolkit.layout.processors.Processorobjects.
Each processor can do a transformation for each line.
(For instance, they can insert or replace some text, highlight the
selection or search string, etc...)The :class:
~prompt_toolkit.layout.UIControlreturns a
:class:~prompt_toolkit.layout.UIContentinstance which
generates such a token lists for each lines.
The :class:~prompt_toolkit.layout.Window receives the
:class:~prompt_toolkit.layout.UIContent and then:
It calculates the horizontal and vertical scrolling, if applicable
(if the content would take more space than what is available).The content is copied to the correct absolute position
:class:~prompt_toolkit.layout.screen.Screen, as requested by the
:class:~prompt_toolkit.renderer.Renderer. While doing this, the
:class:~prompt_toolkit.layout.Windowcan possible wrap the
lines, if line wrapping was configured.
Note that this process is lazy: if a certain line is not displayed in the
:class:~prompt_toolkit.layout.Window, then it is not requested
from the :class:~prompt_toolkit.layout.UIContent. And from there, the line is
not passed through the processors or even asked from the
:class:~prompt_toolkit.lexers.Lexer.
Pages Advanced Topics Rendering Pipeline (`docs/pages/advanced_topics/rendering_pipeline.rst`)
The rendering pipeline
This document is an attempt to describe how prompt_toolkit applications are
rendered. It's a complex but logical process that happens more or less after
every key stroke. We'll go through all the steps from the point where the user
hits a key, until the character appears on the screen.
Waiting for user input
Most of the time when a prompt_toolkit application is running, it is idle. It's
sitting in the event loop, waiting for some I/O to happen. The most important
kind of I/O we're waiting for is user input. So, within the event loop, we have
one file descriptor that represents the input device from where we receive key
presses. The details are a little different between operating systems, but it
comes down to a selector (like select or epoll) which waits for one or more
file descriptor. The event loop is then responsible for calling the appropriate
feedback when one of the file descriptors becomes ready.
It is like that when the user presses a key: the input device becomes ready for
reading, and the appropriate callback is called. This is the read_from_input
function somewhere in application.py. It will read the input from the
:class:~prompt_toolkit.input.Input object, by calling
:meth:~prompt_toolkit.input.Input.read_keys.
Reading the user input
The actual reading is also operating system dependent. For instance, on a Linux
machine with a vt100 terminal, we read the input from the pseudo terminal
device, by calling os.read. This however returns a sequence of bytes. There
are two difficulties:
- The input could be UTF-8 encoded, and there is always the possibility that we
receive only a portion of a multi-byte character. - vt100 key presses consist of multiple characters. For instance the "left
arrow" would generate something like\x1b[D. It could be that when we
read this input stream, that at some point we only get the first part of such
a key press, and we have to wait for the rest to arrive.
Both problems are implemented using state machines.
- The UTF-8 problem is solved using
codecs.getincrementaldecoder, which is an
object in which we can feed the incoming bytes, and it will only return the
complete UTF-8 characters that we have so far. The rest is buffered for the
next read operation. - Vt100 parsing is solved by the
:class:~prompt_toolkit.input.vt100_parser.Vt100Parserstate machine. The
state machine itself is implemented using a generator. We feed the incoming
characters to the generator, and it will call the appropriate callback for
key presses once they arrive. One thing here to keep in mind is that the
characters for some key presses are a prefix of other key presses, like for
instance, escape (\x1b) is a prefix of the left arrow key (\x1b[D).
So for those, we don't know what key is pressed until more data arrives or
when the input is flushed because of a timeout.
For Windows systems, it's a little different. Here we use Win32 syscalls for
reading the console input.
Processing the key presses
The Key objects that we receive are then passed to the
:class:~prompt_toolkit.key_binding.key_processor.KeyProcessor for matching
against the currently registered and active key bindings.
This is another state machine, because key bindings are linked to a sequence of
key presses. We cannot call the handler until all of these key presses arrive
and until we're sure that this combination is not a prefix of another
combination. For instance, sometimes people bind jj (a double j key
press) to esc in Vi mode. This is convenient, but we want to make sure that
pressing j once only, followed by a different key will still insert thej character as usual.
Now, there are hundreds of key bindings in prompt_toolkit (in ptpython, right
now we have 585 bindings). This is mainly caused by the way that Vi key
bindings are generated. In order to make this efficient, we keep a cache of
handlers which match certain sequences of keys.
Of course, key bindings also have filters attached for enabling/disabling them.
So, if at some point, we get a list of handlers from that cache, we still have
to discard the inactive bindings. Luckily, many bindings share exactly the same
filter, and we have to check every filter only once.
:ref:Read more about key bindings ...<key_bindings>
The key handlers
Once a key sequence is matched, the handler is called. This can do things like
text manipulation, changing the focus or anything else.
After the handler is called, the user interface is invalidated and rendered
again.
Rendering the user interface
The rendering is pretty complex for several reasons:
- We have to compute the dimensions of all user interface elements. Sometimes
they are given, but sometimes this requires calculating the size of
:class:~prompt_toolkit.layout.UIControlobjects. - It needs to be very efficient, because it's something that happens on every
single key stroke. - We should output as little as possible on stdout in order to reduce latency
on slow network connections and older terminals.
Calculating the total UI height
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Unless the application is a full screen application, we have to know how much
vertical space is going to be consumed. The total available width is given, but
the vertical space is more dynamic. We do this by asking the root
:class:~prompt_toolkit.layout.Container object to calculate its preferred
height. If this is a :class:~prompt_toolkit.layout.VSplit or
:class:~prompt_toolkit.layout.HSplit then this involves recursively querying
the child objects for their preferred widths and heights and either summing it
up, or taking maximum values depending on the actual layout.
In the end, we get the preferred height, for which we make sure it's at least
the distance from the cursor position to the bottom of the screen.
Painting to the screen
^^^^^^^^^^^^^^^^^^^^^^
Then we create a :class:~prompt_toolkit.layout.screen.Screen object. This is
like a canvas on which user controls can paint their content. The
:meth:~prompt_toolkit.layout.Container.write_to_screen method of the rootContainer is called with the screen dimensions. This will call recursively
:meth:~prompt_toolkit.layout.Container.write_to_screen methods of nested
child containers, each time passing smaller dimensions while we traverse what
is a tree of Container objects.
The most inner containers are :class:~prompt_toolkit.layout.Window objects,
they will do the actual painting of the
:class:~prompt_toolkit.layout.UIControl to the screen. This involves line
wrapping the UIControl's text and maybe scrolling the content horizontally or
vertically.
Rendering to stdout
^^^^^^^^^^^^^^^^^^^
Finally, when we have painted the screen, this needs to be rendered to stdout.
This is done by taking the difference of the previously rendered screen and the
new one. The algorithm that we have is heavily optimized to compute this
difference as quickly as possible, and call the appropriate output functions of
the :class:~prompt_toolkit.output.Output back-end. At the end, it will
position the cursor in the right place.
Pages Advanced Topics Styling (`docs/pages/advanced_topics/styling.rst`)
.. _styling:
More about styling
This page will attempt to explain in more detail how to use styling in
prompt_toolkit.
To some extent, it is very similar to how Pygments <http://pygments.org/>_
styling works.
Style strings
Many user interface controls, like :class:~prompt_toolkit.layout.Window
accept a style argument which can be used to pass the formatting as a
string. For instance, we can select a foreground color:
"fg:ansired"(ANSI color palette)"fg:ansiblue"(ANSI color palette)"fg:#ffaa33"(hexadecimal notation)"fg:darkred"(named color)
Or a background color:
"bg:ansired"(ANSI color palette)"bg:#ffaa33"(hexadecimal notation)
Or we can add one of the following flags:
"bold""italic""underline""blink""reverse"(reverse foreground and background on the terminal.)"hidden"
Or their negative variants:
"nobold""noitalic""nounderline""noblink""noreverse""nohidden"
All of these formatting options can be combined as well:
"fg:ansiyellow bg:black bold underline"
The style string can be given to any user control directly, or to a
:class:~prompt_toolkit.layout.Container object from where it will propagate
to all its children. A style defined by a parent user control can be overridden
by any of its children. The parent can for instance say style="bold underline" where a child overrides this style partly by specifyingstyle="nobold bg:ansired".
.. note::
These styles are actually compatible with
`Pygments <http://pygments.org/>`_ styles, with additional support for
`reverse` and `blink`. Further, we ignore flags like `roman`, `sans`,
`mono` and `border`.The following ANSI colors are available (both for foreground and background):
.. code::
# Low intensity, dark. (One or two components 0x80, the other 0x00.)
ansiblack, ansired, ansigreen, ansiyellow, ansiblue
ansimagenta, ansicyan, ansigray
# High intensity, bright.
ansibrightblack, ansibrightred, ansibrightgreen, ansibrightyellow
ansibrightblue, ansibrightmagenta, ansibrightcyan, ansiwhiteIn order to know which styles are actually used in an application, it is
possible to call :meth:~Application.get_used_style_strings, when the
application is done.
Class names
Like we do for web design, it is not a good habit to specify all styling
inline. Instead, we can attach class names to UI controls and have a style
sheet that refers to these class names. The
:class:~prompt_toolkit.styles.Style can be passed as an argument to the
:class:~prompt_toolkit.application.Application.
.. code:: python
from prompt_toolkit.layout import VSplit, Window
from prompt_toolkit.styles import Style
layout = VSplit([
Window(BufferControl(...), style='class:left'),
HSplit([
Window(BufferControl(...), style='class:top'),
Window(BufferControl(...), style='class:bottom'),
], style='class:right')
])
style = Style([
('left', 'bg:ansired'),
('top', 'fg:#00aaaa'),
('bottom', 'underline bold'),
])It is possible to add multiple class names to an element. That way we'll
combine the styling for these class names. Multiple classes can be passed by
using a comma separated list, or by using the class: prefix twice.
.. code:: python
Window(BufferControl(...), style='class:left,bottom'),
Window(BufferControl(...), style='class:left class:bottom'),
It is possible to combine class names and inline styling. The order in which
the class names and inline styling is specified determines the order of
priority. In the following example for instance, we'll take first the style of
the "header" class, and then override that with a red background color.
.. code:: python
Window(BufferControl(...), style='class:header bg:red'),Dot notation in class names
The dot operator has a special meaning in a class name. If we write:style="class:a.b.c", then this will actually expand to the following:style="class:a class:a.b class:a.b.c".
This is mainly added for Pygments <http://pygments.org/>_ lexers, which
specify "Tokens" like this, but it's useful in other situations as well.
Multiple classes in a style sheet
A style sheet can be more complex as well. We can for instance specify two
class names. The following will underline the left part within the header, or
whatever has both the class "left" and the class "header" (the order doesn't
matter).
.. code:: python
style = Style([
('header left', 'underline'),
])If you have a dotted class, then it's required to specify the whole path in the
style sheet (just typing c or b.c doesn't work if the class isa.b.c):
.. code:: python
style = Style([
('a.b.c', 'underline'),
])It is possible to combine this:
.. code:: python
style = Style([
('header body left.text', 'underline'),
])Evaluation order of rules in a style sheet
The style is determined as follows:
First, we concatenate all the style strings from the root control through all
the parents to the child in one big string. (Things at the right take
precedence anyway.)E.g:
class:body bg:#aaaaaa #000000 class:header.focused class:left.text.highlighted underlineThen we go through this style from left to right, starting from the default
style. Inline styling is applied directly.If we come across a class name, then we generate all combinations of the
class names that we collected so far (this one and all class names to the
left), and for each combination which includes the new class name, we look
for matching rules in our style sheet. All these rules are then applied
(later rules have higher priority).If we find a dotted class name, this will be expanded in the individual names
(likeclass:left class:left.text class:left.text.highlighted), and all
these are applied like any class names.Then this final style is applied to this user interface element.
Using a dictionary as a style sheet
The order of the rules in a style sheet is meaningful, so typically, we use a
list of tuples to specify the style. But is also possible to use a dictionary
as a style sheet. This makes sense for Python 3.6, where dictionaries remember
their ordering. An OrderedDict works as well.
.. code:: python
from prompt_toolkit.styles import Style
style = Style.from_dict({
'header body left.text': 'underline',
})Loading a style from Pygments
Pygments <http://pygments.org/>_ has a slightly different notation for
specifying styles, because it maps styling to Pygments "Tokens". A Pygments
style can however be loaded and used as follows:
.. code:: python
from prompt_toolkit.styles.pygments import style_from_pygments_cls
from pygments.styles import get_style_by_name
style = style_from_pygments_cls(get_style_by_name('monokai'))Merging styles together
Multiple :class:~prompt_toolkit.styles.Style objects can be merged together as
follows:
.. code:: python
from prompt_toolkit.styles import merge_styles
style = merge_styles([
style1,
style2,
style3
])Color depths
There are four different levels of color depths available:
+--------+-----------------+-----------------------------+---------------------------------+
| 1 bit | Black and white | ColorDepth.DEPTH_1_BIT | ColorDepth.MONOCHROME |
+--------+-----------------+-----------------------------+---------------------------------+
| 4 bit | ANSI colors | ColorDepth.DEPTH_4_BIT | ColorDepth.ANSI_COLORS_ONLY |
+--------+-----------------+-----------------------------+---------------------------------+
| 8 bit | 256 colors | ColorDepth.DEPTH_8_BIT | ColorDepth.DEFAULT |
+--------+-----------------+-----------------------------+---------------------------------+
| 24 bit | True colors | ColorDepth.DEPTH_24_BIT | ColorDepth.TRUE_COLOR |
+--------+-----------------+-----------------------------+---------------------------------+
By default, 256 colors are used, because this is what most terminals support
these days. If the TERM environment variable is set to linux oreterm-color, then only ANSI colors are used, because of these terminals. The 24
bit true color output needs to be enabled explicitly. When 4 bit color output
is chosen, all colors will be mapped to the closest ANSI color.
Setting the default color depth for any prompt_toolkit application can be done
by setting the PROMPT_TOOLKIT_COLOR_DEPTH environment variable. You could
for instance copy the following into your .bashrc file.
.. code:: shell
# export PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_1_BIT
export PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_4_BIT
# export PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_8_BIT
# export PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_24_BITAn application can also decide to set the color depth manually by passing a
:class:~prompt_toolkit.output.ColorDepth value to the
:class:~prompt_toolkit.application.Application object:
.. code:: python
from prompt_toolkit.output.color_depth import ColorDepth
app = Application(
color_depth=ColorDepth.ANSI_COLORS_ONLY,
# ...
)Style transformations
Prompt_toolkit supports a way to apply certain transformations to the styles
near the end of the rendering pipeline. This can be used for instance to change
certain colors to improve the rendering in some terminals.
One useful example is the
:class:~prompt_toolkit.styles.AdjustBrightnessStyleTransformation class,
which takes min_brightness and max_brightness as arguments which by default
have 0.0 and 1.0 as values. In the following code snippet, we increase the
minimum brightness to improve rendering on terminals with a dark background.
.. code:: python
from prompt_toolkit.styles import AdjustBrightnessStyleTransformation
app = Application(
style_transformation=AdjustBrightnessStyleTransformation(
min_brightness=0.5, # Increase the minimum brightness.
max_brightness=1.0,
)
# ...
)Pages Advanced Topics Unit Testing (`docs/pages/advanced_topics/unit_testing.rst`)
.. _unit_testing:
Unit testing
Testing user interfaces is not always obvious. Here are a few tricks for
testing prompt_toolkit applications.
`PosixPipeInput` and `DummyOutput`
During the creation of a prompt_toolkit
:class:~prompt_toolkit.application.Application, we can specify what input and
output device to be used. By default, these are output objects that correspond
with sys.stdin and sys.stdout. In unit tests however, we want to replace
these.
- For the input, we want a "pipe input". This is an input device, in which we
can programmatically send some input. It can be created with
:func:~prompt_toolkit.input.create_pipe_input, and that return either a
:class:~prompt_toolkit.input.posix_pipe.PosixPipeInputor a
:class:~prompt_toolkit.input.win32_pipe.Win32PipeInputdepending on the
platform. - For the output, we want a :class:
~prompt_toolkit.output.DummyOutput. This is
an output device that doesn't render anything. We don't want to render
anything tosys.stdoutin the unit tests.
.. note::
Typically, we don't want to test the bytes that are written to
`sys.stdout`, because these can change any time when the rendering
algorithm changes, and are not so meaningful anyway. Instead, we want to
test the return value from the
:class:`~prompt_toolkit.application.Application` or test how data
structures (like text buffers) change over time.So we programmatically feed some input to the input pipe, have the key
bindings process the input and then test what comes out of it.
In the following example we use a
:class:~prompt_toolkit.shortcuts.PromptSession, but the same works for any
:class:~prompt_toolkit.application.Application.
.. code:: python
from prompt_toolkit.shortcuts import PromptSession
from prompt_toolkit.input import create_pipe_input
from prompt_toolkit.output import DummyOutput
def test_prompt_session():
with create_pipe_input() as inp:
inp.send_text("hello\n")
session = PromptSession(
input=inp,
output=DummyOutput(),
)
result = session.prompt()
assert result == "hello"In the above example, don't forget to send the \\n character to accept the
prompt, otherwise the :class:~prompt_toolkit.application.Application will
wait forever for some more input to receive.
Using an :class:`~prompt_toolkit.application.current.AppSession`
Sometimes it's not convenient to pass input or output objects to the
:class:~prompt_toolkit.application.Application, and in some situations it's
not even possible at all.
This happens when these parameters are not passed down the call stack, through
all function calls.
An easy way to specify which input/output to use for all applications, is by
creating an :class:~prompt_toolkit.application.current.AppSession with this
input/output and running all code in that
:class:~prompt_toolkit.application.current.AppSession. This way, we don't
need to inject it into every :class:~prompt_toolkit.application.Application
or :func:~prompt_toolkit.shortcuts.print_formatted_text call.
Here is an example where we use
:func:~prompt_toolkit.application.create_app_session:
.. code:: python
from prompt_toolkit.application import create_app_session
from prompt_toolkit.shortcuts import print_formatted_text
from prompt_toolkit.output import DummyOutput
def test_something():
with create_app_session(output=DummyOutput()):
...
print_formatted_text('Hello world')
...Pytest fixtures
In order to get rid of the boilerplate of creating the input, the
:class:~prompt_toolkit.output.DummyOutput, and the
:class:~prompt_toolkit.application.current.AppSession, we create a
single fixture that does it for every test. Something like this:
.. code:: python
import pytest
from prompt_toolkit.application import create_app_session
from prompt_toolkit.input import create_pipe_input
from prompt_toolkit.output import DummyOutput
@pytest.fixture(autouse=True, scope="function")
def mock_input():
with create_pipe_input() as pipe_input:
with create_app_session(input=pipe_input, output=DummyOutput()):
yield pipe_inputFor compatibility with pytest's capsys fixture, we have to create a new
:class:~prompt_toolkit.application.current.AppSession for every test. This
can be done in an autouse fixture. Pytest replaces sys.stdout with a new
object in every test that uses capsys and the following will ensure that
the new :class:~prompt_toolkit.application.current.AppSession will each time
refer to the latest output.
.. code:: python
from prompt_toolkit.application import create_app_session
@fixture(autouse=True, scope="function")
def _pt_app_session()
with create_app_session():
yieldType checking
Prompt_toolkit 3.0 is fully type annotated. This means that if a
prompt_toolkit application is typed too, it can be verified with mypy. This is
complementary to unit tests, but also great for testing for correctness.
Pages Tutorials Index (`docs/pages/tutorials/index.rst`)
.. _tutorials:
Tutorials
.. toctree::
:caption: Contents:
:maxdepth: 1
replPages Tutorials Repl (`docs/pages/tutorials/repl.rst`)
.. _tutorial_repl:
Tutorial: Build an SQLite REPL
The aim of this tutorial is to build an interactive command line interface for
an SQLite database using prompt_toolkit_.
First, install the library using pip, if you haven't done this already.
.. code::
pip install prompt_toolkitRead User Input
Let's start accepting input using the
:func:~prompt_toolkit.shortcuts.prompt() function. This will ask the user for
input, and echo back whatever the user typed. We wrap it in a main()
function as a good practice.
.. code:: python
from prompt_toolkit import prompt
def main():
text = prompt('> ')
print('You entered:', text)
if __name__ == '__main__':
main().. image:: ../../images/repl/sqlite-1.png
Loop The REPL
Now we want to call the :meth:~prompt_toolkit.shortcuts.PromptSession.prompt
method in a loop. In order to keep the history, the easiest way to do it is to
use a :class:~prompt_toolkit.shortcuts.PromptSession. This uses an
:class:~prompt_toolkit.history.InMemoryHistory underneath that keeps track of
the history, so that if the user presses the up-arrow, they'll see the previous
entries.
The :meth:~prompt_toolkit.shortcuts.PromptSession.prompt method raisesKeyboardInterrupt when ControlC has been pressed and EOFError when
ControlD has been pressed. This is what people use for cancelling commands and
exiting in a REPL. The try/except below handles these error conditions and make
sure that we go to the next iteration of the loop or quit the loop
respectively.
.. code:: python
from prompt_toolkit import PromptSession
def main():
session = PromptSession()
while True:
try:
text = session.prompt('> ')
except KeyboardInterrupt:
continue
except EOFError:
break
else:
print('You entered:', text)
print('GoodBye!')
if __name__ == '__main__':
main().. image:: ../../images/repl/sqlite-2.png
Syntax Highlighting
This is where things get really interesting. Let's step it up a notch by adding
syntax highlighting to the user input. We know that users will be entering SQL
statements, so we can leverage the Pygments_ library for coloring the input.
The lexer parameter allows us to set the syntax lexer. We're going to use
the SqlLexer from the Pygments_ library for highlighting.
Notice that in order to pass a Pygments lexer to prompt_toolkit, it needs to be
wrapped into a :class:~prompt_toolkit.lexers.PygmentsLexer.
.. code:: python
from prompt_toolkit import PromptSession
from prompt_toolkit.lexers import PygmentsLexer
from pygments.lexers.sql import SqlLexer
def main():
session = PromptSession(lexer=PygmentsLexer(SqlLexer))
while True:
try:
text = session.prompt('> ')
except KeyboardInterrupt:
continue
except EOFError:
break
else:
print('You entered:', text)
print('GoodBye!')
if __name__ == '__main__':
main().. image:: ../../images/repl/sqlite-3.png
Auto-completion
Now we are going to add auto completion. We'd like to display a drop down menu
of possible keywords <https://www.sqlite.org/lang_keywords.html>_ when the
user starts typing.
We can do this by creating an sql_completer object from the
:class:~prompt_toolkit.completion.WordCompleter class, defining a set ofkeywords for the auto-completion.
Like the lexer, this sql_completer instance can be passed to either the
:class:~prompt_toolkit.shortcuts.PromptSession class or the
:meth:~prompt_toolkit.shortcuts.PromptSession.prompt method.
.. code:: python
from prompt_toolkit import PromptSession
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit.lexers import PygmentsLexer
from pygments.lexers.sql import SqlLexer
sql_completer = WordCompleter([
'abort', 'action', 'add', 'after', 'all', 'alter', 'analyze', 'and',
'as', 'asc', 'attach', 'autoincrement', 'before', 'begin', 'between',
'by', 'cascade', 'case', 'cast', 'check', 'collate', 'column',
'commit', 'conflict', 'constraint', 'create', 'cross', 'current_date',
'current_time', 'current_timestamp', 'database', 'default',
'deferrable', 'deferred', 'delete', 'desc', 'detach', 'distinct',
'drop', 'each', 'else', 'end', 'escape', 'except', 'exclusive',
'exists', 'explain', 'fail', 'for', 'foreign', 'from', 'full', 'glob',
'group', 'having', 'if', 'ignore', 'immediate', 'in', 'index',
'indexed', 'initially', 'inner', 'insert', 'instead', 'intersect',
'into', 'is', 'isnull', 'join', 'key', 'left', 'like', 'limit',
'match', 'natural', 'no', 'not', 'notnull', 'null', 'of', 'offset',
'on', 'or', 'order', 'outer', 'plan', 'pragma', 'primary', 'query',
'raise', 'recursive', 'references', 'regexp', 'reindex', 'release',
'rename', 'replace', 'restrict', 'right', 'rollback', 'row',
'savepoint', 'select', 'set', 'table', 'temp', 'temporary', 'then',
'to', 'transaction', 'trigger', 'union', 'unique', 'update', 'using',
'vacuum', 'values', 'view', 'virtual', 'when', 'where', 'with',
'without'], ignore_case=True)
def main():
session = PromptSession(
lexer=PygmentsLexer(SqlLexer), completer=sql_completer)
while True:
try:
text = session.prompt('> ')
except KeyboardInterrupt:
continue
except EOFError:
break
else:
print('You entered:', text)
print('GoodBye!')
if __name__ == '__main__':
main().. image:: ../../images/repl/sqlite-4.png
In about 30 lines of code we got ourselves an auto completing, syntax
highlighting REPL. Let's make it even better.
Styling the menus
If we want, we can now change the colors of the completion menu. This is
possible by creating a :class:~prompt_toolkit.styles.Style instance and
passing it to the :meth:~prompt_toolkit.shortcuts.PromptSession.prompt
function.
.. code:: python
from prompt_toolkit import PromptSession
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit.lexers import PygmentsLexer
from prompt_toolkit.styles import Style
from pygments.lexers.sql import SqlLexer
sql_completer = WordCompleter([
'abort', 'action', 'add', 'after', 'all', 'alter', 'analyze', 'and',
'as', 'asc', 'attach', 'autoincrement', 'before', 'begin', 'between',
'by', 'cascade', 'case', 'cast', 'check', 'collate', 'column',
'commit', 'conflict', 'constraint', 'create', 'cross', 'current_date',
'current_time', 'current_timestamp', 'database', 'default',
'deferrable', 'deferred', 'delete', 'desc', 'detach', 'distinct',
'drop', 'each', 'else', 'end', 'escape', 'except', 'exclusive',
'exists', 'explain', 'fail', 'for', 'foreign', 'from', 'full', 'glob',
'group', 'having', 'if', 'ignore', 'immediate', 'in', 'index',
'indexed', 'initially', 'inner', 'insert', 'instead', 'intersect',
'into', 'is', 'isnull', 'join', 'key', 'left', 'like', 'limit',
'match', 'natural', 'no', 'not', 'notnull', 'null', 'of', 'offset',
'on', 'or', 'order', 'outer', 'plan', 'pragma', 'primary', 'query',
'raise', 'recursive', 'references', 'regexp', 'reindex', 'release',
'rename', 'replace', 'restrict', 'right', 'rollback', 'row',
'savepoint', 'select', 'set', 'table', 'temp', 'temporary', 'then',
'to', 'transaction', 'trigger', 'union', 'unique', 'update', 'using',
'vacuum', 'values', 'view', 'virtual', 'when', 'where', 'with',
'without'], ignore_case=True)
style = Style.from_dict({
'completion-menu.completion': 'bg:#008888 #ffffff',
'completion-menu.completion.current': 'bg:#00aaaa #000000',
'scrollbar.background': 'bg:#88aaaa',
'scrollbar.button': 'bg:#222222',
})
def main():
session = PromptSession(
lexer=PygmentsLexer(SqlLexer), completer=sql_completer, style=style)
while True:
try:
text = session.prompt('> ')
except KeyboardInterrupt:
continue
except EOFError:
break
else:
print('You entered:', text)
print('GoodBye!')
if __name__ == '__main__':
main().. image:: ../../images/repl/sqlite-5.png
All that's left is hooking up the sqlite backend, which is left as an exercise
for the reader. Just kidding... Keep reading.
Hook up Sqlite
This step is the final step to make the SQLite REPL actually work. It's time
to relay the input to SQLite.
Obviously I haven't done the due diligence to deal with the errors. But it
gives a good idea of how to get started.
.. code:: python
#!/usr/bin/env python
import sys
import sqlite3
from prompt_toolkit import PromptSession
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit.lexers import PygmentsLexer
from prompt_toolkit.styles import Style
from pygments.lexers.sql import SqlLexer
sql_completer = WordCompleter([
'abort', 'action', 'add', 'after', 'all', 'alter', 'analyze', 'and',
'as', 'asc', 'attach', 'autoincrement', 'before', 'begin', 'between',
'by', 'cascade', 'case', 'cast', 'check', 'collate', 'column',
'commit', 'conflict', 'constraint', 'create', 'cross', 'current_date',
'current_time', 'current_timestamp', 'database', 'default',
'deferrable', 'deferred', 'delete', 'desc', 'detach', 'distinct',
'drop', 'each', 'else', 'end', 'escape', 'except', 'exclusive',
'exists', 'explain', 'fail', 'for', 'foreign', 'from', 'full', 'glob',
'group', 'having', 'if', 'ignore', 'immediate', 'in', 'index',
'indexed', 'initially', 'inner', 'insert', 'instead', 'intersect',
'into', 'is', 'isnull', 'join', 'key', 'left', 'like', 'limit',
'match', 'natural', 'no', 'not', 'notnull', 'null', 'of', 'offset',
'on', 'or', 'order', 'outer', 'plan', 'pragma', 'primary', 'query',
'raise', 'recursive', 'references', 'regexp', 'reindex', 'release',
'rename', 'replace', 'restrict', 'right', 'rollback', 'row',
'savepoint', 'select', 'set', 'table', 'temp', 'temporary', 'then',
'to', 'transaction', 'trigger', 'union', 'unique', 'update', 'using',
'vacuum', 'values', 'view', 'virtual', 'when', 'where', 'with',
'without'], ignore_case=True)
style = Style.from_dict({
'completion-menu.completion': 'bg:#008888 #ffffff',
'completion-menu.completion.current': 'bg:#00aaaa #000000',
'scrollbar.background': 'bg:#88aaaa',
'scrollbar.button': 'bg:#222222',
})
def main(database):
connection = sqlite3.connect(database)
session = PromptSession(
lexer=PygmentsLexer(SqlLexer), completer=sql_completer, style=style)
while True:
try:
text = session.prompt('> ')
except KeyboardInterrupt:
continue # Control-C pressed. Try again.
except EOFError:
break # Control-D pressed.
with connection:
try:
messages = connection.execute(text)
except Exception as e:
print(repr(e))
else:
for message in messages:
print(message)
print('GoodBye!')
if __name__ == '__main__':
if len(sys.argv) < 2:
db = ':memory:'
else:
db = sys.argv[1]
main(db).. image:: ../../images/repl/sqlite-6.png
I hope that gives an idea of how to get started on building command line
interfaces.
The End.
.. _prompt_toolkit: https://github.com/prompt-toolkit/python-prompt-toolkit
.. _Pygments: http://pygments.org/
Pages Upgrading 2.0 (`docs/pages/upgrading/2.0.rst`)
.. _upgrading_2_0:
Upgrading to prompt_toolkit 2.0
Prompt_toolkit 2.0 is not compatible with 1.0, however you probably want to
upgrade your applications. This page explains why we have these differences and
how to upgrade.
If you experience some difficulties or you feel that some information is
missing from this page, don't hesitate to open a GitHub issue for help.
Why all these breaking changes?
After more and more custom prompt_toolkit applications were developed, it
became clear that prompt_toolkit 1.0 was not flexible enough for certain use
cases. Mostly, the development of full screen applications was not really
natural. All the important components, like the rendering, key bindings, input
and output handling were present, but the API was in the first place designed
for simple command line prompts. This was mostly notably in the following two
places:
- First, there was the focus which was always pointing to a
:class:~prompt_toolkit.buffer.Buffer(or text input widget), but in full
screen applications there are other widgets, like menus and buttons which
can be focused. - And secondly, it was impossible to make reusable UI components. All the key
bindings for the entire applications were stored together in oneKeyBindingsobject, and similar, all
:class:~prompt_toolkit.buffer.Bufferobjects were stored together in one
dictionary. This didn't work well. You want reusable components to define
their own key bindings and everything. It's the idea of encapsulation.
For simple prompts, the changes wouldn't be that invasive, but given that there
would be some, I took the opportunity to fix a couple of other things. For
instance:
- In prompt_toolkit 1.0, we translated
\\rinto\\nduring the input
processing. This was not a good idea, because some people wanted to handle
these keys individually. This makes sense if you keep in mind that they
correspond toControl-MandControl-J. However, we couldn't fix this
without breaking everyone's enter key, which happens to be the most important
key in prompts.
Given that we were going to break compatibility anyway, we changed a couple of
other important things that effect both simple prompt applications and
full screen applications. These are the most important:
We no longer depend on Pygments for styling. While we like Pygments, it was
not flexible enough to provide all the styling options that we need, and the
Pygments tokens were not ideal for styling anything besides tokenized text.Instead we created something similar to CSS. All UI components can attach
classnames to themselves, as well as define an inline style. The final style is
then computed by combining the inline styles, the classnames and the style
sheet.There are still adaptors available for using Pygments lexers as well as for
Pygments styles.The way that key bindings were defined was too complex.
KeyBindingsManagerwas too complex and no longer exists. Every set of key
bindings is now a
:class:~prompt_toolkit.key_binding.KeyBindingsobject and multiple of these
can be merged together at any time. The runtime performance remains the same,
but it's now easier for users.The separation between the
CommandLineInterfaceand
:class:~prompt_toolkit.application.Applicationclass was confusing and in
the end, didn't really had an advantage. These two are now merged together in
one :class:~prompt_toolkit.application.Applicationclass.We no longer pass around the active
CommandLineInterface. This was one of
the most annoying things. Key bindings need it in order to change anything
and filters need it in order to evaluate their state. It was pretty annoying,
especially because there was usually only one application active at a time.
So, :class:~prompt_toolkit.application.Applicationbecame aTaskLocal.
That is like a global variable, but scoped in the current coroutine or
context. The way this works is still not 100% correct, but good enough for
the projects that need it (like Pymux), and hopefully Python will get support
for this in the future thanks to PEP521, PEP550 or PEP555.
All of these changes have been tested for many months, and I can say with
confidence that prompt_toolkit 2.0 is a better prompt_toolkit.
Some new features
Apart from the breaking changes above, there are also some exciting new
features.
We now support vt100 escape codes for Windows consoles on Windows 10. This
means much faster rendering, and full color support.We have a concept of formatted text. This is an object that evaluates to
styled text. Every input that expects some text, like the message in a
prompt, or the text in a toolbar, can take any kind of formatted text as input.
This means you can pass in a plain string, but also a list of(style, text)tuples (similar to a Pygments tokenized string), or an
:class:~prompt_toolkit.formatted_text.HTMLobject. This simplifies many
APIs.New utilities were added. We now have function for printing formatted text
and an experimental module for displaying progress bars.Autocompletion, input validation, and auto suggestion can now either be
asynchronous or synchronous. By default they are synchronous, but by wrapping
them in :class:~prompt_toolkit.completion.ThreadedCompleter,
:class:~prompt_toolkit.validation.ThreadedValidatoror
:class:~prompt_toolkit.auto_suggest.ThreadedAutoSuggest, they will become
asynchronous by running in a background thread.Further, if the autocompletion code runs in a background thread, we will show
the completions as soon as they arrive. This means that the autocompletion
algorithm could for instance first yield the most trivial completions and then
take time to produce the completions that take more time.
Upgrading
More guidelines on how to upgrade will follow.
AbortAction has been removed
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Prompt_toolkit 1.0 had an argument abort_action for both theApplication class as well as for the prompt function. This has been
removed. The recommended way to handle this now is by capturingKeyboardInterrupt and EOFError manually.
Calling create_eventloop usually not required anymore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Prompt_toolkit 2.0 will automatically create the appropriate event loop when
it's needed for the first time. There is no need to create one and pass it
around. If you want to run an application on top of asyncio (without using an
executor), it still needs to be activated by calling
:func:~prompt_toolkit.eventloop.use_asyncio_event_loop at the beginning.
Pygments styles and tokens
^^^^^^^^^^^^^^^^^^^^^^^^^^
prompt_toolkit 2.0 no longer depends on Pygments <http://pygments.org/>_, but
that definitely doesn't mean that you can't use any Pygments functionality
anymore. The only difference is that Pygments stuff needs to be wrapped in an
adaptor to make it compatible with the native prompt_toolkit objects.
For instance, if you have a list of
(pygments.Token, text)tuples for
formatting, then this needs to be wrapped in a
:class:~prompt_toolkit.formatted_text.PygmentsTokensobject. This is an
adaptor that turns it into prompt_toolkit "formatted text". Feel free to keep
using this.Pygments lexers need to be wrapped in a
:class:~prompt_toolkit.lexers.PygmentsLexer. This will convert the list of
Pygments tokens into prompt_toolkit formatted text.If you have a Pygments style, then this needs to be converted as well. A
Pygments style class can be converted in a prompt_toolkit
:class:~prompt_toolkit.styles.Stylewith the
:func:~prompt_toolkit.styles.pygments.style_from_pygments_clsfunction
(which used to be calledstyle_from_pygments). A Pygments style
dictionary can be converted using
:func:~prompt_toolkit.styles.pygments.style_from_pygments_dict.Multiple styles can be merged together using
:func:~prompt_toolkit.styles.merge_styles.
Wordcompleter
^^^^^^^^^^^^^
WordCompleter was moved from
:class:prompt_toolkit.contrib.completers.base.WordCompleter to
:class:prompt_toolkit.completion.word_completer.WordCompleter.
Asynchronous autocompletion
^^^^^^^^^^^^^^^^^^^^^^^^^^^
By default, prompt_toolkit 2.0 completion is now synchronous. If you still want
asynchronous auto completion (which is often good thing), then you have to wrap
the completer in a :class:~prompt_toolkit.completion.ThreadedCompleter.
Filters
^^^^^^^
We don't distinguish anymore between CLIFilter and SimpleFilter, because the
application object is no longer passed around. This means that all filters are
a Filter from now on.
All filters have been turned into functions. For instance, IsDone becameis_done and HasCompletions became has_completions.
This was done because almost all classes were called without any arguments in
the __init__ causing additional braces everywhere. This means thatHasCompletions() has to be replaced by has_completions (without
parenthesis).
The few filters that took arguments as input, became functions, but still have
to be called with the given arguments.
For new filters, it is recommended to use the @Condition decorator,
rather then inheriting from Filter. For instance:
.. code:: python
from prompt_toolkit.filters import Condition
@Condition
def my_filter();
return True # Or FalsePages Upgrading 3.0 (`docs/pages/upgrading/3.0.rst`)
.. _upgrading_3_0:
Upgrading to prompt_toolkit 3.0
There are two major changes in 3.0 to be aware of:
First, prompt_toolkit uses the asyncio event loop natively, rather then using
its own implementations of event loops. This means that all coroutines are
now asyncio coroutines, and all Futures are asyncio futures. Asynchronous
generators became real asynchronous generators as well.Prompt_toolkit uses type annotations (almost) everywhere. This should not
break any code, but its very helpful in many ways.
There are some minor breaking changes:
- The dialogs API had to change (see below).
Detecting the prompt_toolkit version
Detecting whether version 3 is being used can be done as follows:
.. code:: python
from prompt_toolkit import __version__ as ptk_version
PTK3 = ptk_version.startswith('3.')Fixing calls to `get_event_loop`
Every usage of get_event_loop has to be fixed. An easy way to do this is by
changing the imports like this:
.. code:: python
if PTK3:
from asyncio import get_event_loop
else:
from prompt_toolkit.eventloop import get_event_loopNotice that for prompt_toolkit 2.0, get_event_loop returns a prompt_toolkitEventLoop object. This is not an asyncio eventloop, but the API is
similar.
There are some changes to the eventloop API:
+-----------------------------------+--------------------------------------+
| version 2.0 | version 3.0 (asyncio) |
+===================================+======================================+
| loop.run_in_executor(callback) | loop.run_in_executor(None, callback) |
+-----------------------------------+--------------------------------------+
| loop.call_from_executor(callback) | loop.call_soon_threadsafe(callback) |
+-----------------------------------+--------------------------------------+
Running on top of asyncio
For 2.0, you had tell prompt_toolkit to run on top of the asyncio event loop.
Now it's the default. So, you can simply remove the following two lines:
.. code::
from prompt_toolkit.eventloop.defaults import use_asyncio_event_loop
use_asyncio_event_loop()There is a few little breaking changes though. The following:
.. code::
# For 2.0
result = await PromptSession().prompt('Say something: ', async_=True)has to be changed into:
.. code::
# For 3.0
result = await PromptSession().prompt_async('Say something: ')Further, it's impossible to call the prompt() function within an asyncio
application (within a coroutine), because it will try to run the event loop
again. In that case, always use prompt_async().
Changes to the dialog functions
The original way of using dialog boxes looked like this:
.. code:: python
from prompt_toolkit.shortcuts import input_dialog
result = input_dialog(title='...', text='...')Now, the dialog functions return a prompt_toolkit Application object. You have
to call either its run or run_async method to display the dialog. Theasync_ parameter has been removed everywhere.
.. code:: python
if PTK3:
result = input_dialog(title='...', text='...').run()
else:
result = input_dialog(title='...', text='...')
# Or
if PTK3:
result = await input_dialog(title='...', text='...').run_async()
else:
result = await input_dialog(title='...', text='...', async_=True)Pages Upgrading Index (`docs/pages/upgrading/index.rst`)
.. _upgrading:
Upgrading
.. toctree::
:caption: Contents:
:maxdepth: 1
2.0
3.0--- METRICS ---
- Files Extracted: 28
- Estimated Token Budget: ~44973 tokens
- Canonical Reference: https://codewiki.google/github.com/prompt-toolkit/python-prompt-toolkit