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