diff options
Diffstat (limited to 'docs/pages')
24 files changed, 4900 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. diff --git a/docs/pages/asking_for_input.rst b/docs/pages/asking_for_input.rst new file mode 100644 index 0000000..20619ac --- /dev/null +++ b/docs/pages/asking_for_input.rst @@ -0,0 +1,1034 @@ +.. _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('You said: %s' % 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.prompt` calls. + +- The :func:`~prompt_toolkit.shortcuts.PromptSession` instance and its + :meth:`~prompt_toolkit.shortcuts.PromptSession.prompt` method 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.PromptSession` instance as well, and they + can be overridden by passing values to the + :meth:`~prompt_toolkit.shortcuts.PromptSession.prompt` method. + + +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('You said: %s' % 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('You said: %s' % 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`, `mono` and `border` options are ignored. +- The style has a few additions: ``blink``, ``noblink``, ``reverse`` and ``noreverse``. +- Colors can be in the ``#ff0000`` format, 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 instance +``pygments.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 the +``color_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>', '<body>', '<head>', '<title>']) + text = prompt('Enter HTML: ', completer=html_completer) + print('You said: %s' % 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('You said: %s' % 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 the +``display`` 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('<b>completion</b><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 the +`complete_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('You said: %i' % 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('You said: %i' % 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 the +`fish 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('You said: %s' % 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 <b><style bg="ansired">Toolbar</style></b>!') + + text = prompt('> ', bottom_toolbar=bottom_toolbar) + print('You said: %s' % 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('You said: %s' % 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 the +``key_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('You said: %s' % 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 from +``EditingMode.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 a +``prompt_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='%s' % 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()) + + +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('You said: %s' % 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. diff --git a/docs/pages/dialogs.rst b/docs/pages/dialogs.rst new file mode 100644 index 0000000..e171995 --- /dev/null +++ b/docs/pages/dialogs.rst @@ -0,0 +1,270 @@ +.. _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() diff --git a/docs/pages/full_screen_apps.rst b/docs/pages/full_screen_apps.rst new file mode 100644 index 0000000..805c8c7 --- /dev/null +++ b/docs/pages/full_screen_apps.rst @@ -0,0 +1,422 @@ +.. _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.Input` instance, which is an abstraction + of the input stream (stdin). + - An :class:`~prompt_toolkit.output.Output` instance, 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.Container` and + :class:`~prompt_toolkit.layout.UIControl` objects. + + Examples of :class:`~prompt_toolkit.layout.Container` objects 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.Window` object is a special kind of + container that can contain a :class:`~prompt_toolkit.layout.UIControl` + object. The :class:`~prompt_toolkit.layout.UIControl` object is responsible + for the generation of the actual content. The + :class:`~prompt_toolkit.layout.Window` object acts as an adaptor between the + :class:`~prompt_toolkit.layout.UIControl` and other containers, but it's also + responsible for the scrolling and line wrapping of the content. + + Examples of :class:`~prompt_toolkit.layout.UIControl` objects are + :class:`~prompt_toolkit.layout.BufferControl` for showing the content of an + editable/scrollable buffer, and + :class:`~prompt_toolkit.layout.FormattedTextControl` for displaying + (:ref:`formatted <formatted_text>`) text. + + Normally, it is never needed to create new + :class:`~prompt_toolkit.layout.UIControl` or + :class:`~prompt_toolkit.layout.Container` classes, 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.VerticalLine` and so on. + +- The highest level abstractions can be found in the ``shortcuts`` module. + 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.Screen` instance. + - 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.UIControl` is 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 app + +Notice 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.UIControl` and are only active when + this control is focused. Both + :class:`~prompt_toolkit.layout.BufferControl` + :class:`~prompt_toolkit.layout.FormattedTextControl` take a ``key_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 leafs 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 `cursorline` and `cursorcolumn` options. 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 build-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. diff --git a/docs/pages/gallery.rst b/docs/pages/gallery.rst new file mode 100644 index 0000000..40b6917 --- /dev/null +++ b/docs/pages/gallery.rst @@ -0,0 +1,32 @@ +.. _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 diff --git a/docs/pages/getting_started.rst b/docs/pages/getting_started.rst new file mode 100644 index 0000000..06287a0 --- /dev/null +++ b/docs/pages/getting_started.rst @@ -0,0 +1,84 @@ +.. _getting_started: + +Getting started +=============== + +Installation +------------ + +:: + + pip install prompt_toolkit + +For Conda, do: + +:: + + conda install -c https://conda.anaconda.org/conda-forge prompt_toolkit + + +Several 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('You said: %s' % 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 the +`examples +<https://github.com/prompt-toolkit/python-prompt-toolkit/tree/master/examples>`_ +in the repository. + +- First, :ref:`learn how to print text <printing_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>`. diff --git a/docs/pages/printing_text.rst b/docs/pages/printing_text.rst new file mode 100644 index 0000000..8359d5f --- /dev/null +++ b/docs/pages/printing_text.rst @@ -0,0 +1,274 @@ +.. _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.HTML` object. +- By creating an :class:`~prompt_toolkit.formatted_text.ANSI` object 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: ``<b>``, ``<i>`` and ``<u>``. + +.. code:: python + + from prompt_toolkit import print_formatted_text, HTML + + print_formatted_text(HTML('<b>This is bold</b>')) + print_formatted_text(HTML('<i>This is italic</i>')) + print_formatted_text(HTML('<u>This is underlined</u>')) + +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> <bbb>world</bbb>!'), 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> <bbb>world</bbb>!') + text = to_formatted_text(html, style='class:my_html bg:#00ff00 italic') + + print_formatted_text(text) diff --git a/docs/pages/progress_bars.rst b/docs/pages/progress_bars.rst new file mode 100644 index 0000000..54a8ee1 --- /dev/null +++ b/docs/pages/progress_bars.rst @@ -0,0 +1,248 @@ +.. _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(' <b>[f]</b> Print "f" <b>[x]</b> 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]: + break + +Notice 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 mean 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>` diff --git a/docs/pages/reference.rst b/docs/pages/reference.rst new file mode 100644 index 0000000..d8a705e --- /dev/null +++ b/docs/pages/reference.rst @@ -0,0 +1,393 @@ +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 + +.. 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: + +.. autoclass:: prompt_toolkit.filters.Condition + :members: + +.. 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, get_event_loop + +.. 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 diff --git a/docs/pages/related_projects.rst b/docs/pages/related_projects.rst new file mode 100644 index 0000000..ad0a8af --- /dev/null +++ b/docs/pages/related_projects.rst @@ -0,0 +1,11 @@ +.. _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/>`_ diff --git a/docs/pages/tutorials/index.rst b/docs/pages/tutorials/index.rst new file mode 100644 index 0000000..827b511 --- /dev/null +++ b/docs/pages/tutorials/index.rst @@ -0,0 +1,10 @@ +.. _tutorials: + +Tutorials +========= + +.. toctree:: + :caption: Contents: + :maxdepth: 1 + + repl diff --git a/docs/pages/tutorials/repl.rst b/docs/pages/tutorials/repl.rst new file mode 100644 index 0000000..946786f --- /dev/null +++ b/docs/pages/tutorials/repl.rst @@ -0,0 +1,341 @@ +.. _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_toolkit + + +Read 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 raises +``KeyboardInterrupt`` 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 of +`keywords` 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/ diff --git a/docs/pages/upgrading/2.0.rst b/docs/pages/upgrading/2.0.rst new file mode 100644 index 0000000..6067057 --- /dev/null +++ b/docs/pages/upgrading/2.0.rst @@ -0,0 +1,221 @@ +.. _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 one + ``KeyBindings`` object, and similar, all + :class:`~prompt_toolkit.buffer.Buffer` objects 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 `\\r` into `\\n` during 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 to `Control-M` and `Control-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. + ``KeyBindingsManager`` was too complex and no longer exists. Every set of key + bindings is now a + :class:`~prompt_toolkit.key_binding.KeyBindings` object 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 ``CommandLineInterface`` and + :class:`~prompt_toolkit.application.Application` class was confusing and in + the end, didn't really had an advantage. These two are now merged together in + one :class:`~prompt_toolkit.application.Application` class. + +- 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.Application` became a ``TaskLocal``. + 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.HTML` object. 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.ThreadedValidator` or + :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 the +``Application`` class as well as for the ``prompt`` function. This has been +removed. The recommended way to handle this now is by capturing +``KeyboardInterrupt`` 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.PygmentsTokens` object. 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.Style` with the + :func:`~prompt_toolkit.styles.pygments.style_from_pygments_cls` function + (which used to be called ``style_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` became +`is_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 that +`HasCompletions()` 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 False + diff --git a/docs/pages/upgrading/3.0.rst b/docs/pages/upgrading/3.0.rst new file mode 100644 index 0000000..7a867c5 --- /dev/null +++ b/docs/pages/upgrading/3.0.rst @@ -0,0 +1,118 @@ +.. _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_loop + +Notice that for prompt_toolkit 2.0, ``get_event_loop`` returns a prompt_toolkit +``EventLoop`` 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. The +``async_`` 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) diff --git a/docs/pages/upgrading/index.rst b/docs/pages/upgrading/index.rst new file mode 100644 index 0000000..b790a64 --- /dev/null +++ b/docs/pages/upgrading/index.rst @@ -0,0 +1,11 @@ +.. _upgrading: + +Upgrading +========= + +.. toctree:: + :caption: Contents: + :maxdepth: 1 + + 2.0 + 3.0 |