summaryrefslogtreecommitdiffstats
path: root/docs/pages/advanced_topics
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 16:35:31 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 16:35:31 +0000
commit4f1a3b5f9ad05aa7b08715d48909a2b06ee2fcb1 (patch)
treee5dee7be2f0d963da4faad6517278d03783e3adc /docs/pages/advanced_topics
parentInitial commit. (diff)
downloadprompt-toolkit-4f1a3b5f9ad05aa7b08715d48909a2b06ee2fcb1.tar.xz
prompt-toolkit-4f1a3b5f9ad05aa7b08715d48909a2b06ee2fcb1.zip
Adding upstream version 3.0.43.upstream/3.0.43upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'docs/pages/advanced_topics')
-rw-r--r--docs/pages/advanced_topics/architecture.rst97
-rw-r--r--docs/pages/advanced_topics/asyncio.rst30
-rw-r--r--docs/pages/advanced_topics/filters.rst169
-rw-r--r--docs/pages/advanced_topics/index.rst18
-rw-r--r--docs/pages/advanced_topics/input_hooks.rst41
-rw-r--r--docs/pages/advanced_topics/key_bindings.rst388
-rw-r--r--docs/pages/advanced_topics/rendering_flow.rst86
-rw-r--r--docs/pages/advanced_topics/rendering_pipeline.rst157
-rw-r--r--docs/pages/advanced_topics/styling.rst320
-rw-r--r--docs/pages/advanced_topics/unit_testing.rst125
10 files changed, 1431 insertions, 0 deletions
diff --git a/docs/pages/advanced_topics/architecture.rst b/docs/pages/advanced_topics/architecture.rst
new file mode 100644
index 0000000..c690104
--- /dev/null
+++ b/docs/pages/advanced_topics/architecture.rst
@@ -0,0 +1,97 @@
+.. _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. |
+ +---------------------------------------------------------------+
diff --git a/docs/pages/advanced_topics/asyncio.rst b/docs/pages/advanced_topics/asyncio.rst
new file mode 100644
index 0000000..a692630
--- /dev/null
+++ b/docs/pages/advanced_topics/asyncio.rst
@@ -0,0 +1,30 @@
+.. _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())
diff --git a/docs/pages/advanced_topics/filters.rst b/docs/pages/advanced_topics/filters.rst
new file mode 100644
index 0000000..4788769
--- /dev/null
+++ b/docs/pages/advanced_topics/filters.rst
@@ -0,0 +1,169 @@
+.. _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_lines`` option 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 to
+``True`` 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_searching
+
+This 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.
+ pass
+
+If 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. "
+ pass
+
+
+to_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)
diff --git a/docs/pages/advanced_topics/index.rst b/docs/pages/advanced_topics/index.rst
new file mode 100644
index 0000000..4c4fcc9
--- /dev/null
+++ b/docs/pages/advanced_topics/index.rst
@@ -0,0 +1,18 @@
+.. _advanced_topics:
+
+Advanced topics
+===============
+
+.. toctree::
+ :caption: Contents:
+ :maxdepth: 1
+
+ key_bindings
+ styling
+ filters
+ rendering_flow
+ asyncio
+ unit_testing
+ input_hooks
+ architecture
+ rendering_pipeline
diff --git a/docs/pages/advanced_topics/input_hooks.rst b/docs/pages/advanced_topics/input_hooks.rst
new file mode 100644
index 0000000..16c1bf5
--- /dev/null
+++ b/docs/pages/advanced_topics/input_hooks.rst
@@ -0,0 +1,41 @@
+.. _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 like
+`IPython <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:
+ :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.
diff --git a/docs/pages/advanced_topics/key_bindings.rst b/docs/pages/advanced_topics/key_bindings.rst
new file mode 100644
index 0000000..8b334fc
--- /dev/null
+++ b/docs/pages/advanced_topics/key_bindings.rst
@@ -0,0 +1,388 @@
+.. _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 -ixon
+
+Key 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 '<any>' key:
+
+.. code:: python
+
+ @bindings.add('a', '<any>')
+ def _(event):
+ ...
+
+This will handle `aa`, `ab`, `ac`, etcetera. The key binding can check the
+`event` 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):
+ # ...
+ pass
+
+The 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's `ttimeoutlen` option.
+ 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 the `ttimeoutlen` option in Vim.
+
+- ``KeyProcessor.timeoutlen``: like Vim's `timeoutlen` option.
+ This can be `None` or 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. Pass `None` to 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):
+ # ...
+ pass
+
+
+Creating 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):
+ # ...
+ pass
+
+This 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 a
+``KeyboardInterrupt`` 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.
diff --git a/docs/pages/advanced_topics/rendering_flow.rst b/docs/pages/advanced_topics/rendering_flow.rst
new file mode 100644
index 0000000..0cd12c7
--- /dev/null
+++ b/docs/pages/advanced_topics/rendering_flow.rst
@@ -0,0 +1,86 @@
+.. _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:
+
+1. The :class:`~prompt_toolkit.renderer.Renderer` calls the
+ :meth:`~prompt_toolkit.layout.Container.write_to_screen` method
+ 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.Window` object then requests
+ the :class:`~prompt_toolkit.layout.UIControl` to create a
+ :class:`~prompt_toolkit.layout.UIContent` instance (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:
+
+ 2. First, the buffer's text is passed to the
+ :meth:`~prompt_toolkit.lexers.Lexer.lex_document` method 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).
+
+ 3. This list is passed through a list of
+ :class:`~prompt_toolkit.layout.processors.Processor` objects.
+ 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...)
+
+ 4. The :class:`~prompt_toolkit.layout.UIControl` returns a
+ :class:`~prompt_toolkit.layout.UIContent` instance which
+ generates such a token lists for each lines.
+
+The :class:`~prompt_toolkit.layout.Window` receives the
+:class:`~prompt_toolkit.layout.UIContent` and then:
+
+5. It calculates the horizontal and vertical scrolling, if applicable
+ (if the content would take more space than what is available).
+
+6. 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.Window` can 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`.
diff --git a/docs/pages/advanced_topics/rendering_pipeline.rst b/docs/pages/advanced_topics/rendering_pipeline.rst
new file mode 100644
index 0000000..6b38ba5
--- /dev/null
+++ b/docs/pages/advanced_topics/rendering_pipeline.rst
@@ -0,0 +1,157 @@
+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.Vt100Parser` state 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 the
+``j`` 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.UIControl` objects.
+- 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 root
+`Container` 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.
diff --git a/docs/pages/advanced_topics/styling.rst b/docs/pages/advanced_topics/styling.rst
new file mode 100644
index 0000000..55cf6ee
--- /dev/null
+++ b/docs/pages/advanced_topics/styling.rst
@@ -0,0 +1,320 @@
+.. _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 specifying
+``style="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, ansiwhite
+
+In 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 is
+``a.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 underline``
+
+- Then 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
+ (like ``class: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`` or
+``eterm-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_BIT
+
+An 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,
+ )
+ # ...
+ )
diff --git a/docs/pages/advanced_topics/unit_testing.rst b/docs/pages/advanced_topics/unit_testing.rst
new file mode 100644
index 0000000..2224bfc
--- /dev/null
+++ b/docs/pages/advanced_topics/unit_testing.rst
@@ -0,0 +1,125 @@
+.. _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.PosixPipeInput` or a
+ :class:`~prompt_toolkit.input.win32_pipe.Win32PipeInput` depending 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 to `sys.stdout` in 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_input
+
+
+Type 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.