1# pylint: disable=function-redefined
2from __future__ import annotations
3
4from prompt_toolkit.application.current import get_app
5from prompt_toolkit.filters import (
6 Condition,
7 emacs_insert_mode,
8 has_selection,
9 in_paste_mode,
10 is_multiline,
11 vi_insert_mode,
12)
13from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent
14from prompt_toolkit.keys import Keys
15
16from ..key_bindings import KeyBindings
17from .named_commands import get_by_name
18
19__all__ = [
20 "load_basic_bindings",
21]
22
23E = KeyPressEvent
24
25
26def if_no_repeat(event: E) -> bool:
27 """Callable that returns True when the previous event was delivered to
28 another handler."""
29 return not event.is_repeat
30
31
32@Condition
33def has_text_before_cursor() -> bool:
34 return bool(get_app().current_buffer.text)
35
36
37@Condition
38def in_quoted_insert() -> bool:
39 return get_app().quoted_insert
40
41
42def load_basic_bindings() -> KeyBindings:
43 key_bindings = KeyBindings()
44 insert_mode = vi_insert_mode | emacs_insert_mode
45 handle = key_bindings.add
46
47 @handle("c-a")
48 @handle("c-b")
49 @handle("c-c")
50 @handle("c-d")
51 @handle("c-e")
52 @handle("c-f")
53 @handle("c-g")
54 @handle("c-h")
55 @handle("c-i")
56 @handle("c-j")
57 @handle("c-k")
58 @handle("c-l")
59 @handle("c-m")
60 @handle("c-n")
61 @handle("c-o")
62 @handle("c-p")
63 @handle("c-q")
64 @handle("c-r")
65 @handle("c-s")
66 @handle("c-t")
67 @handle("c-u")
68 @handle("c-v")
69 @handle("c-w")
70 @handle("c-x")
71 @handle("c-y")
72 @handle("c-z")
73 @handle("f1")
74 @handle("f2")
75 @handle("f3")
76 @handle("f4")
77 @handle("f5")
78 @handle("f6")
79 @handle("f7")
80 @handle("f8")
81 @handle("f9")
82 @handle("f10")
83 @handle("f11")
84 @handle("f12")
85 @handle("f13")
86 @handle("f14")
87 @handle("f15")
88 @handle("f16")
89 @handle("f17")
90 @handle("f18")
91 @handle("f19")
92 @handle("f20")
93 @handle("f21")
94 @handle("f22")
95 @handle("f23")
96 @handle("f24")
97 @handle("c-@") # Also c-space.
98 @handle("c-\\")
99 @handle("c-]")
100 @handle("c-^")
101 @handle("c-_")
102 @handle("backspace")
103 @handle("up")
104 @handle("down")
105 @handle("right")
106 @handle("left")
107 @handle("s-up")
108 @handle("s-down")
109 @handle("s-right")
110 @handle("s-left")
111 @handle("home")
112 @handle("end")
113 @handle("s-home")
114 @handle("s-end")
115 @handle("delete")
116 @handle("s-delete")
117 @handle("c-delete")
118 @handle("pageup")
119 @handle("pagedown")
120 @handle("s-tab")
121 @handle("tab")
122 @handle("c-s-left")
123 @handle("c-s-right")
124 @handle("c-s-home")
125 @handle("c-s-end")
126 @handle("c-left")
127 @handle("c-right")
128 @handle("c-up")
129 @handle("c-down")
130 @handle("c-home")
131 @handle("c-end")
132 @handle("insert")
133 @handle("s-insert")
134 @handle("c-insert")
135 @handle("<sigint>")
136 @handle(Keys.Ignore)
137 def _ignore(event: E) -> None:
138 """
139 First, for any of these keys, Don't do anything by default. Also don't
140 catch them in the 'Any' handler which will insert them as data.
141
142 If people want to insert these characters as a literal, they can always
143 do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi
144 mode.)
145 """
146 pass
147
148 # Readline-style bindings.
149 handle("home")(get_by_name("beginning-of-line"))
150 handle("end")(get_by_name("end-of-line"))
151 handle("left")(get_by_name("backward-char"))
152 handle("right")(get_by_name("forward-char"))
153 handle("c-up")(get_by_name("previous-history"))
154 handle("c-down")(get_by_name("next-history"))
155 handle("c-l")(get_by_name("clear-screen"))
156
157 handle("c-k", filter=insert_mode)(get_by_name("kill-line"))
158 handle("c-u", filter=insert_mode)(get_by_name("unix-line-discard"))
159 handle("backspace", filter=insert_mode, save_before=if_no_repeat)(
160 get_by_name("backward-delete-char")
161 )
162 handle("delete", filter=insert_mode, save_before=if_no_repeat)(
163 get_by_name("delete-char")
164 )
165 handle("c-delete", filter=insert_mode, save_before=if_no_repeat)(
166 get_by_name("delete-char")
167 )
168 handle(Keys.Any, filter=insert_mode, save_before=if_no_repeat)(
169 get_by_name("self-insert")
170 )
171 handle("c-t", filter=insert_mode)(get_by_name("transpose-chars"))
172 handle("c-i", filter=insert_mode)(get_by_name("menu-complete"))
173 handle("s-tab", filter=insert_mode)(get_by_name("menu-complete-backward"))
174
175 # Control-W should delete, using whitespace as separator, while M-Del
176 # should delete using [^a-zA-Z0-9] as a boundary.
177 handle("c-w", filter=insert_mode)(get_by_name("unix-word-rubout"))
178
179 handle("pageup", filter=~has_selection)(get_by_name("previous-history"))
180 handle("pagedown", filter=~has_selection)(get_by_name("next-history"))
181
182 # CTRL keys.
183
184 handle("c-d", filter=has_text_before_cursor & insert_mode)(
185 get_by_name("delete-char")
186 )
187
188 @handle("enter", filter=insert_mode & is_multiline)
189 def _newline(event: E) -> None:
190 """
191 Newline (in case of multiline input.
192 """
193 event.current_buffer.newline(copy_margin=not in_paste_mode())
194
195 @handle("c-j")
196 def _newline2(event: E) -> None:
197 r"""
198 By default, handle \n as if it were a \r (enter).
199 (It appears that some terminals send \n instead of \r when pressing
200 enter. - at least the Linux subsystem for Windows.)
201 """
202 event.key_processor.feed(KeyPress(Keys.ControlM, "\r"), first=True)
203
204 # Delete the word before the cursor.
205
206 @handle("up")
207 def _go_up(event: E) -> None:
208 event.current_buffer.auto_up(count=event.arg)
209
210 @handle("down")
211 def _go_down(event: E) -> None:
212 event.current_buffer.auto_down(count=event.arg)
213
214 @handle("delete", filter=has_selection)
215 def _cut(event: E) -> None:
216 data = event.current_buffer.cut_selection()
217 event.app.clipboard.set_data(data)
218
219 # Global bindings.
220
221 @handle("c-z")
222 def _insert_ctrl_z(event: E) -> None:
223 """
224 By default, control-Z should literally insert Ctrl-Z.
225 (Ansi Ctrl-Z, code 26 in MSDOS means End-Of-File.
226 In a Python REPL for instance, it's possible to type
227 Control-Z followed by enter to quit.)
228
229 When the system bindings are loaded and suspend-to-background is
230 supported, that will override this binding.
231 """
232 event.current_buffer.insert_text(event.data)
233
234 @handle(Keys.BracketedPaste)
235 def _paste(event: E) -> None:
236 """
237 Pasting from clipboard.
238 """
239 data = event.data
240
241 # Be sure to use \n as line ending.
242 # Some terminals (Like iTerm2) seem to paste \r\n line endings in a
243 # bracketed paste. See: https://github.com/ipython/ipython/issues/9737
244 data = data.replace("\r\n", "\n")
245 data = data.replace("\r", "\n")
246
247 event.current_buffer.insert_text(data)
248
249 @handle(Keys.Any, filter=in_quoted_insert, eager=True)
250 def _insert_text(event: E) -> None:
251 """
252 Handle quoted insert.
253 """
254 event.current_buffer.insert_text(event.data, overwrite=False)
255 event.app.quoted_insert = False
256
257 return key_bindings