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