##// END OF EJS Templates
Format according to black/darker
Yuval -
Show More
@@ -1,538 +1,538 b''
1 1 """
2 2 Module to define and register Terminal IPython shortcuts with
3 3 :mod:`prompt_toolkit`
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import warnings
10 10 import signal
11 11 import sys
12 12 import re
13 13 from typing import Callable
14 14
15 15
16 16 from prompt_toolkit.application.current import get_app
17 17 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
18 18 from prompt_toolkit.filters import (has_focus, has_selection, Condition,
19 19 vi_insert_mode, emacs_insert_mode, has_completions, vi_mode)
20 20 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
21 21 from prompt_toolkit.key_binding import KeyBindings
22 22 from prompt_toolkit.key_binding.bindings import named_commands as nc
23 23 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
24 24
25 25 from IPython.utils.decorators import undoc
26 26
27 27 @undoc
28 28 @Condition
29 29 def cursor_in_leading_ws():
30 30 before = get_app().current_buffer.document.current_line_before_cursor
31 31 return (not before) or before.isspace()
32 32
33 33
34 34 def create_ipython_shortcuts(shell):
35 35 """Set up the prompt_toolkit keyboard shortcuts for IPython"""
36 36
37 37 kb = KeyBindings()
38 38 insert_mode = vi_insert_mode | emacs_insert_mode
39 39
40 40 if getattr(shell, 'handle_return', None):
41 41 return_handler = shell.handle_return(shell)
42 42 else:
43 43 return_handler = newline_or_execute_outer(shell)
44 44
45 45 kb.add('enter', filter=(has_focus(DEFAULT_BUFFER)
46 46 & ~has_selection
47 47 & insert_mode
48 48 ))(return_handler)
49 49
50 50 def reformat_and_execute(event):
51 51 reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell)
52 52 event.current_buffer.validate_and_handle()
53 53
54 54 kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER)
55 55 & ~has_selection
56 56 & insert_mode
57 57 ))(reformat_and_execute)
58 58
59 59 kb.add('c-\\')(force_exit)
60 60
61 61 kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
62 62 )(previous_history_or_previous_completion)
63 63
64 64 kb.add('c-n', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
65 65 )(next_history_or_next_completion)
66 66
67 67 kb.add('c-g', filter=(has_focus(DEFAULT_BUFFER) & has_completions)
68 68 )(dismiss_completion)
69 69
70 70 kb.add('c-c', filter=has_focus(DEFAULT_BUFFER))(reset_buffer)
71 71
72 72 kb.add('c-c', filter=has_focus(SEARCH_BUFFER))(reset_search_buffer)
73 73
74 74 supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP'))
75 75 kb.add('c-z', filter=supports_suspend)(suspend_to_bg)
76 76
77 77 # Ctrl+I == Tab
78 78 kb.add('tab', filter=(has_focus(DEFAULT_BUFFER)
79 79 & ~has_selection
80 80 & insert_mode
81 81 & cursor_in_leading_ws
82 82 ))(indent_buffer)
83 83 kb.add('c-o', filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode)
84 84 )(newline_autoindent_outer(shell.input_transformer_manager))
85 85
86 86 kb.add('f2', filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor)
87 87
88 88 @Condition
89 89 def auto_match():
90 90 return shell.auto_match
91 91
92 92 focused_insert = (vi_insert_mode | emacs_insert_mode) & has_focus(DEFAULT_BUFFER)
93 93 _preceding_text_cache = {}
94 94 _following_text_cache = {}
95 95
96 96 def preceding_text(pattern):
97 97 try:
98 98 return _preceding_text_cache[pattern]
99 99 except KeyError:
100 100 pass
101 101 m = re.compile(pattern)
102 102
103 103 def _preceding_text():
104 104 app = get_app()
105 105 return bool(m.match(app.current_buffer.document.current_line_before_cursor))
106 106
107 107 condition = Condition(_preceding_text)
108 108 _preceding_text_cache[pattern] = condition
109 109 return condition
110 110
111 111 def following_text(pattern):
112 112 try:
113 113 return _following_text_cache[pattern]
114 114 except KeyError:
115 115 pass
116 116 m = re.compile(pattern)
117 117
118 118 def _following_text():
119 119 app = get_app()
120 120 return bool(m.match(app.current_buffer.document.current_line_after_cursor))
121 121
122 122 condition = Condition(_following_text)
123 123 _following_text_cache[pattern] = condition
124 124 return condition
125 125
126 126 # auto match
127 127 @kb.add("(", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
128 128 def _(event):
129 129 event.current_buffer.insert_text("()")
130 130 event.current_buffer.cursor_left()
131 131
132 132 @kb.add("[", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
133 133 def _(event):
134 134 event.current_buffer.insert_text("[]")
135 135 event.current_buffer.cursor_left()
136 136
137 137 @kb.add("{", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
138 138 def _(event):
139 139 event.current_buffer.insert_text("{}")
140 140 event.current_buffer.cursor_left()
141 141
142 142 @kb.add(
143 143 '"',
144 144 filter=focused_insert
145 145 & auto_match
146 146 & preceding_text(r'^([^"]+|"[^"]*")*$')
147 147 & following_text(r"[,)}\]]|$"),
148 148 )
149 149 def _(event):
150 150 event.current_buffer.insert_text('""')
151 151 event.current_buffer.cursor_left()
152 152
153 153 @kb.add(
154 154 "'",
155 155 filter=focused_insert
156 156 & auto_match
157 157 & preceding_text(r"^([^']+|'[^']*')*$")
158 158 & following_text(r"[,)}\]]|$"),
159 159 )
160 160 def _(event):
161 161 event.current_buffer.insert_text("''")
162 162 event.current_buffer.cursor_left()
163 163
164 164 # raw string
165 165 @kb.add(
166 166 "(", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
167 167 )
168 168 def _(event):
169 169 matches = re.match(
170 170 r".*(r|R)[\"'](-*)",
171 171 event.current_buffer.document.current_line_before_cursor,
172 172 )
173 173 dashes = matches.group(2) or ""
174 174 event.current_buffer.insert_text("()" + dashes)
175 175 event.current_buffer.cursor_left(len(dashes) + 1)
176 176
177 177 @kb.add(
178 178 "[", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
179 179 )
180 180 def _(event):
181 181 matches = re.match(
182 182 r".*(r|R)[\"'](-*)",
183 183 event.current_buffer.document.current_line_before_cursor,
184 184 )
185 185 dashes = matches.group(2) or ""
186 186 event.current_buffer.insert_text("[]" + dashes)
187 187 event.current_buffer.cursor_left(len(dashes) + 1)
188 188
189 189 @kb.add(
190 190 "{", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
191 191 )
192 192 def _(event):
193 193 matches = re.match(
194 194 r".*(r|R)[\"'](-*)",
195 195 event.current_buffer.document.current_line_before_cursor,
196 196 )
197 197 dashes = matches.group(2) or ""
198 198 event.current_buffer.insert_text("{}" + dashes)
199 199 event.current_buffer.cursor_left(len(dashes) + 1)
200 200
201 201 # just move cursor
202 202 @kb.add(")", filter=focused_insert & auto_match & following_text(r"^\)"))
203 203 @kb.add("]", filter=focused_insert & auto_match & following_text(r"^\]"))
204 204 @kb.add("}", filter=focused_insert & auto_match & following_text(r"^\}"))
205 205 @kb.add('"', filter=focused_insert & auto_match & following_text('^"'))
206 206 @kb.add("'", filter=focused_insert & auto_match & following_text("^'"))
207 207 def _(event):
208 208 event.current_buffer.cursor_right()
209 209
210 210 @kb.add(
211 211 "backspace",
212 212 filter=focused_insert
213 213 & preceding_text(r".*\($")
214 214 & auto_match
215 215 & following_text(r"^\)"),
216 216 )
217 217 @kb.add(
218 218 "backspace",
219 219 filter=focused_insert
220 220 & preceding_text(r".*\[$")
221 221 & auto_match
222 222 & following_text(r"^\]"),
223 223 )
224 224 @kb.add(
225 225 "backspace",
226 226 filter=focused_insert
227 227 & preceding_text(r".*\{$")
228 228 & auto_match
229 229 & following_text(r"^\}"),
230 230 )
231 231 @kb.add(
232 232 "backspace",
233 233 filter=focused_insert
234 234 & preceding_text('.*"$')
235 235 & auto_match
236 236 & following_text('^"'),
237 237 )
238 238 @kb.add(
239 239 "backspace",
240 240 filter=focused_insert
241 241 & preceding_text(r".*'$")
242 242 & auto_match
243 243 & following_text(r"^'"),
244 244 )
245 245 def _(event):
246 246 event.current_buffer.delete()
247 247 event.current_buffer.delete_before_cursor()
248 248
249 249 if shell.display_completions == "readlinelike":
250 250 kb.add(
251 251 "c-i",
252 252 filter=(
253 253 has_focus(DEFAULT_BUFFER)
254 254 & ~has_selection
255 255 & insert_mode
256 256 & ~cursor_in_leading_ws
257 257 ),
258 258 )(display_completions_like_readline)
259 259
260 260 if sys.platform == "win32":
261 261 kb.add("c-v", filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
262 262
263 263 @Condition
264 264 def ebivim():
265 265 return shell.emacs_bindings_in_vi_insert_mode
266 266
267 267 focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode
268 268
269 269 # Needed for to accept autosuggestions in vi insert mode
270 270 def _apply_autosuggest(event):
271 271 b = event.current_buffer
272 272 suggestion = b.suggestion
273 273 if suggestion:
274 274 b.insert_text(suggestion.text)
275 275 else:
276 276 nc.end_of_line(event)
277 277
278 278 @kb.add("end", filter=has_focus(DEFAULT_BUFFER) & ebivim)
279 279 def _(event):
280 280 _apply_autosuggest(event)
281
281
282 282 @kb.add("c-e", filter=focused_insert_vi & ebivim)
283 283 def _(event):
284 284 _apply_autosuggest(event)
285 285
286 286 @kb.add("c-f", filter=focused_insert_vi)
287 287 def _(event):
288 288 b = event.current_buffer
289 289 suggestion = b.suggestion
290 290 if suggestion:
291 291 b.insert_text(suggestion.text)
292 292 else:
293 293 nc.forward_char(event)
294 294
295 295 @kb.add("escape", "f", filter=focused_insert_vi & ebivim)
296 296 def _(event):
297 297 b = event.current_buffer
298 298 suggestion = b.suggestion
299 299 if suggestion:
300 300 t = re.split(r"(\S+\s+)", suggestion.text)
301 301 b.insert_text(next((x for x in t if x), ""))
302 302 else:
303 303 nc.forward_word(event)
304 304
305 305 # Simple Control keybindings
306 306 key_cmd_dict = {
307 307 "c-a": nc.beginning_of_line,
308 308 "c-b": nc.backward_char,
309 309 "c-k": nc.kill_line,
310 310 "c-w": nc.backward_kill_word,
311 311 "c-y": nc.yank,
312 312 "c-_": nc.undo,
313 313 }
314 314
315 315 for key, cmd in key_cmd_dict.items():
316 316 kb.add(key, filter=focused_insert_vi & ebivim)(cmd)
317 317
318 318 # Alt and Combo Control keybindings
319 319 keys_cmd_dict = {
320 320 # Control Combos
321 321 ("c-x", "c-e"): nc.edit_and_execute,
322 322 ("c-x", "e"): nc.edit_and_execute,
323 323 # Alt
324 324 ("escape", "b"): nc.backward_word,
325 325 ("escape", "c"): nc.capitalize_word,
326 326 ("escape", "d"): nc.kill_word,
327 327 ("escape", "h"): nc.backward_kill_word,
328 328 ("escape", "l"): nc.downcase_word,
329 329 ("escape", "u"): nc.uppercase_word,
330 330 ("escape", "y"): nc.yank_pop,
331 331 ("escape", "."): nc.yank_last_arg,
332 332 }
333 333
334 334 for keys, cmd in keys_cmd_dict.items():
335 335 kb.add(*keys, filter=focused_insert_vi & ebivim)(cmd)
336 336
337 337 def get_input_mode(self):
338 338 app = get_app()
339 339 app.ttimeoutlen = shell.ttimeoutlen
340 340 app.timeoutlen = shell.timeoutlen
341 341
342 342 return self._input_mode
343 343
344 344 def set_input_mode(self, mode):
345 345 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
346 346 cursor = "\x1b[{} q".format(shape)
347 347
348 348 sys.stdout.write(cursor)
349 349 sys.stdout.flush()
350 350
351 351 self._input_mode = mode
352 352
353 353 if shell.editing_mode == "vi" and shell.modal_cursor:
354 354 ViState._input_mode = InputMode.INSERT
355 355 ViState.input_mode = property(get_input_mode, set_input_mode)
356 356
357 357 return kb
358 358
359 359
360 360 def reformat_text_before_cursor(buffer, document, shell):
361 361 text = buffer.delete_before_cursor(len(document.text[:document.cursor_position]))
362 362 try:
363 363 formatted_text = shell.reformat_handler(text)
364 364 buffer.insert_text(formatted_text)
365 365 except Exception as e:
366 366 buffer.insert_text(text)
367 367
368 368
369 369 def newline_or_execute_outer(shell):
370 370
371 371 def newline_or_execute(event):
372 372 """When the user presses return, insert a newline or execute the code."""
373 373 b = event.current_buffer
374 374 d = b.document
375 375
376 376 if b.complete_state:
377 377 cc = b.complete_state.current_completion
378 378 if cc:
379 379 b.apply_completion(cc)
380 380 else:
381 381 b.cancel_completion()
382 382 return
383 383
384 384 # If there's only one line, treat it as if the cursor is at the end.
385 385 # See https://github.com/ipython/ipython/issues/10425
386 386 if d.line_count == 1:
387 387 check_text = d.text
388 388 else:
389 389 check_text = d.text[:d.cursor_position]
390 390 status, indent = shell.check_complete(check_text)
391 391
392 392 # if all we have after the cursor is whitespace: reformat current text
393 393 # before cursor
394 394 after_cursor = d.text[d.cursor_position:]
395 395 reformatted = False
396 396 if not after_cursor.strip():
397 397 reformat_text_before_cursor(b, d, shell)
398 398 reformatted = True
399 399 if not (d.on_last_line or
400 400 d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
401 401 ):
402 402 if shell.autoindent:
403 403 b.insert_text('\n' + indent)
404 404 else:
405 405 b.insert_text('\n')
406 406 return
407 407
408 408 if (status != 'incomplete') and b.accept_handler:
409 409 if not reformatted:
410 410 reformat_text_before_cursor(b, d, shell)
411 411 b.validate_and_handle()
412 412 else:
413 413 if shell.autoindent:
414 414 b.insert_text('\n' + indent)
415 415 else:
416 416 b.insert_text('\n')
417 417 return newline_or_execute
418 418
419 419
420 420 def previous_history_or_previous_completion(event):
421 421 """
422 422 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
423 423
424 424 If completer is open this still select previous completion.
425 425 """
426 426 event.current_buffer.auto_up()
427 427
428 428
429 429 def next_history_or_next_completion(event):
430 430 """
431 431 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
432 432
433 433 If completer is open this still select next completion.
434 434 """
435 435 event.current_buffer.auto_down()
436 436
437 437
438 438 def dismiss_completion(event):
439 439 b = event.current_buffer
440 440 if b.complete_state:
441 441 b.cancel_completion()
442 442
443 443
444 444 def reset_buffer(event):
445 445 b = event.current_buffer
446 446 if b.complete_state:
447 447 b.cancel_completion()
448 448 else:
449 449 b.reset()
450 450
451 451
452 452 def reset_search_buffer(event):
453 453 if event.current_buffer.document.text:
454 454 event.current_buffer.reset()
455 455 else:
456 456 event.app.layout.focus(DEFAULT_BUFFER)
457 457
458 458 def suspend_to_bg(event):
459 459 event.app.suspend_to_background()
460 460
461 461 def force_exit(event):
462 462 """
463 463 Force exit (with a non-zero return value)
464 464 """
465 465 sys.exit("Quit")
466 466
467 467 def indent_buffer(event):
468 468 event.current_buffer.insert_text(' ' * 4)
469 469
470 470 @undoc
471 471 def newline_with_copy_margin(event):
472 472 """
473 473 DEPRECATED since IPython 6.0
474 474
475 475 See :any:`newline_autoindent_outer` for a replacement.
476 476
477 477 Preserve margin and cursor position when using
478 478 Control-O to insert a newline in EMACS mode
479 479 """
480 480 warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
481 481 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
482 482 DeprecationWarning, stacklevel=2)
483 483
484 484 b = event.current_buffer
485 485 cursor_start_pos = b.document.cursor_position_col
486 486 b.newline(copy_margin=True)
487 487 b.cursor_up(count=1)
488 488 cursor_end_pos = b.document.cursor_position_col
489 489 if cursor_start_pos != cursor_end_pos:
490 490 pos_diff = cursor_start_pos - cursor_end_pos
491 491 b.cursor_right(count=pos_diff)
492 492
493 493 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
494 494 """
495 495 Return a function suitable for inserting a indented newline after the cursor.
496 496
497 497 Fancier version of deprecated ``newline_with_copy_margin`` which should
498 498 compute the correct indentation of the inserted line. That is to say, indent
499 499 by 4 extra space after a function definition, class definition, context
500 500 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
501 501 """
502 502
503 503 def newline_autoindent(event):
504 504 """insert a newline after the cursor indented appropriately."""
505 505 b = event.current_buffer
506 506 d = b.document
507 507
508 508 if b.complete_state:
509 509 b.cancel_completion()
510 510 text = d.text[:d.cursor_position] + '\n'
511 511 _, indent = inputsplitter.check_complete(text)
512 512 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
513 513
514 514 return newline_autoindent
515 515
516 516
517 517 def open_input_in_editor(event):
518 518 event.app.current_buffer.open_in_editor()
519 519
520 520
521 521 if sys.platform == 'win32':
522 522 from IPython.core.error import TryNext
523 523 from IPython.lib.clipboard import (ClipboardEmpty,
524 524 win32_clipboard_get,
525 525 tkinter_clipboard_get)
526 526
527 527 @undoc
528 528 def win_paste(event):
529 529 try:
530 530 text = win32_clipboard_get()
531 531 except TryNext:
532 532 try:
533 533 text = tkinter_clipboard_get()
534 534 except (TryNext, ClipboardEmpty):
535 535 return
536 536 except ClipboardEmpty:
537 537 return
538 538 event.current_buffer.insert_text(text.replace("\t", " " * 4))
General Comments 0
You need to be logged in to leave comments. Login now