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
|
#!/usr/bin/env python
"""
Simple example of a full screen application with a vertical split.
This will show a window on the left for user input. When the user types, the
reversed input is shown on the right. Pressing Ctrl-Q will quit the application.
"""
from prompt_toolkit.application import Application
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.layout.containers import HSplit, VSplit, Window, WindowAlign
from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl
from prompt_toolkit.layout.layout import Layout
# 3. Create the buffers
# ------------------
left_buffer = Buffer()
right_buffer = Buffer()
# 1. First we create the layout
# --------------------------
left_window = Window(BufferControl(buffer=left_buffer))
right_window = Window(BufferControl(buffer=right_buffer))
body = VSplit(
[
left_window,
# 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.
Window(width=1, char="|", style="class:line"),
# Display the Result buffer on the right.
right_window,
]
)
# As a demonstration. Let's add a title bar to the top, displaying "Hello world".
# somewhere, because usually the default key bindings include searching. (Press
# Ctrl-R.) It would be really annoying if the search key bindings are handled,
# but the user doesn't see any feedback. We will add the search toolbar to the
# bottom by using an HSplit.
def get_titlebar_text():
return [
("class:title", " Hello world "),
("class:title", " (Press [Ctrl-Q] to quit.)"),
]
root_container = HSplit(
[
# The titlebar.
Window(
height=1,
content=FormattedTextControl(get_titlebar_text),
align=WindowAlign.CENTER,
),
# Horizontal separator.
Window(height=1, char="-", style="class:line"),
# The 'body', like defined above.
body,
]
)
# 2. Adding key bindings
# --------------------
# As a demonstration, we will add just a ControlQ key binding to exit the
# application. Key bindings are registered in a
# `prompt_toolkit.key_bindings.registry.Registry` instance. We use the
# `load_default_key_bindings` utility function to create a registry that
# already contains the default key bindings.
kb = KeyBindings()
# Now add the Ctrl-Q binding. We have to pass `eager=True` here. The reason is
# that there is another key *sequence* that starts with Ctrl-Q as well. Yes, a
# key binding is linked to a sequence of keys, not necessarily one key. So,
# what happens if there is a key binding for the letter 'a' and a key binding
# for 'ab'. When 'a' has been pressed, nothing will happen yet. Because the
# next key could be a 'b', but it could as well be anything else. If it's a 'c'
# for instance, we'll handle the key binding for 'a' and then look for a key
# binding for 'c'. So, when there's a common prefix in a key binding sequence,
# prompt-toolkit will wait calling a handler, until we have enough information.
# Now, There is an Emacs key binding for the [Ctrl-Q Any] sequence by default.
# Pressing Ctrl-Q followed by any other key will do a quoted insert. So to be
# sure that we won't wait for that key binding to match, but instead execute
# Ctrl-Q immediately, we can pass eager=True. (Don't make a habit of adding
# `eager=True` to all key bindings, but do it when it conflicts with another
# existing key binding, and you definitely want to override that behaviour.
@kb.add("c-c", eager=True)
@kb.add("c-q", eager=True)
def _(event):
"""
Pressing Ctrl-Q or Ctrl-C 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.
Note that Ctrl-Q does not work on all terminals. Sometimes it requires
executing `stty -ixon`.
"""
event.app.exit()
# Now we add an event handler that captures change events to the buffer on the
# left. If the text changes over there, we'll update the buffer on the right.
def default_buffer_changed(_):
"""
When the buffer on the left changes, update the buffer on
the right. We just reverse the text.
"""
right_buffer.text = left_buffer.text[::-1]
left_buffer.on_text_changed += default_buffer_changed
# 3. Creating an `Application` instance
# ----------------------------------
# This glues everything together.
application = Application(
layout=Layout(root_container, focused_element=left_window),
key_bindings=kb,
# Let's add mouse support!
mouse_support=True,
# Using an alternate screen buffer means as much as: "run full screen".
# It switches the terminal to an alternate screen.
full_screen=True,
)
# 4. Run the application
# -------------------
def run():
# Run the interface. (This runs the event loop until Ctrl-Q is pressed.)
application.run()
if __name__ == "__main__":
run()
|