summaryrefslogtreecommitdiffstats
path: root/docs/pages/advanced_topics/rendering_pipeline.rst
blob: 6b38ba53a706a794ff7e6bb76f072f929f214c5c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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.