##// END OF EJS Templates
Remove deprecated `newline_with_copy_margin`
Yann Pellegrini -
Show More
@@ -1,626 +1,599 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 os
10 10 import signal
11 11 import sys
12 12 import warnings
13 13 from dataclasses import dataclass
14 14 from typing import Callable, Any, Optional, List
15 15
16 16 from prompt_toolkit.application.current import get_app
17 17 from prompt_toolkit.key_binding import KeyBindings
18 18 from prompt_toolkit.key_binding.key_processor import KeyPressEvent
19 19 from prompt_toolkit.key_binding.bindings import named_commands as nc
20 20 from prompt_toolkit.key_binding.bindings.completion import (
21 21 display_completions_like_readline,
22 22 )
23 23 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
24 24 from prompt_toolkit.filters import Condition
25 25
26 26 from IPython.core.getipython import get_ipython
27 27 from IPython.terminal.shortcuts import auto_match as match
28 28 from IPython.terminal.shortcuts import auto_suggest
29 29 from IPython.terminal.shortcuts.filters import filter_from_string
30 30 from IPython.utils.decorators import undoc
31 31
32 32 __all__ = ["create_ipython_shortcuts"]
33 33
34 34
35 35 @dataclass
36 36 class BaseBinding:
37 37 command: Callable[[KeyPressEvent], Any]
38 38 keys: List[str]
39 39
40 40
41 41 @dataclass
42 42 class RuntimeBinding(BaseBinding):
43 43 filter: Condition
44 44
45 45
46 46 @dataclass
47 47 class Binding(BaseBinding):
48 48 # while filter could be created by referencing variables directly (rather
49 49 # than created from strings), by using strings we ensure that users will
50 50 # be able to create filters in configuration (e.g. JSON) files too, which
51 51 # also benefits the documentation by enforcing human-readable filter names.
52 52 condition: Optional[str] = None
53 53
54 54 def __post_init__(self):
55 55 if self.condition:
56 56 self.filter = filter_from_string(self.condition)
57 57 else:
58 58 self.filter = None
59 59
60 60
61 61 def create_identifier(handler: Callable):
62 62 parts = handler.__module__.split(".")
63 63 name = handler.__name__
64 64 package = parts[0]
65 65 if len(parts) > 1:
66 66 final_module = parts[-1]
67 67 return f"{package}:{final_module}.{name}"
68 68 else:
69 69 return f"{package}:{name}"
70 70
71 71
72 72 AUTO_MATCH_BINDINGS = [
73 73 *[
74 74 Binding(
75 75 cmd, [key], "focused_insert & auto_match & followed_by_closing_paren_or_end"
76 76 )
77 77 for key, cmd in match.auto_match_parens.items()
78 78 ],
79 79 *[
80 80 # raw string
81 81 Binding(cmd, [key], "focused_insert & auto_match & preceded_by_raw_str_prefix")
82 82 for key, cmd in match.auto_match_parens_raw_string.items()
83 83 ],
84 84 Binding(
85 85 match.double_quote,
86 86 ['"'],
87 87 "focused_insert"
88 88 " & auto_match"
89 89 " & not_inside_unclosed_string"
90 90 " & preceded_by_paired_double_quotes"
91 91 " & followed_by_closing_paren_or_end",
92 92 ),
93 93 Binding(
94 94 match.single_quote,
95 95 ["'"],
96 96 "focused_insert"
97 97 " & auto_match"
98 98 " & not_inside_unclosed_string"
99 99 " & preceded_by_paired_single_quotes"
100 100 " & followed_by_closing_paren_or_end",
101 101 ),
102 102 Binding(
103 103 match.docstring_double_quotes,
104 104 ['"'],
105 105 "focused_insert"
106 106 " & auto_match"
107 107 " & not_inside_unclosed_string"
108 108 " & preceded_by_two_double_quotes",
109 109 ),
110 110 Binding(
111 111 match.docstring_single_quotes,
112 112 ["'"],
113 113 "focused_insert"
114 114 " & auto_match"
115 115 " & not_inside_unclosed_string"
116 116 " & preceded_by_two_single_quotes",
117 117 ),
118 118 Binding(
119 119 match.skip_over,
120 120 [")"],
121 121 "focused_insert & auto_match & followed_by_closing_round_paren",
122 122 ),
123 123 Binding(
124 124 match.skip_over,
125 125 ["]"],
126 126 "focused_insert & auto_match & followed_by_closing_bracket",
127 127 ),
128 128 Binding(
129 129 match.skip_over,
130 130 ["}"],
131 131 "focused_insert & auto_match & followed_by_closing_brace",
132 132 ),
133 133 Binding(
134 134 match.skip_over, ['"'], "focused_insert & auto_match & followed_by_double_quote"
135 135 ),
136 136 Binding(
137 137 match.skip_over, ["'"], "focused_insert & auto_match & followed_by_single_quote"
138 138 ),
139 139 Binding(
140 140 match.delete_pair,
141 141 ["backspace"],
142 142 "focused_insert"
143 143 " & preceded_by_opening_round_paren"
144 144 " & auto_match"
145 145 " & followed_by_closing_round_paren",
146 146 ),
147 147 Binding(
148 148 match.delete_pair,
149 149 ["backspace"],
150 150 "focused_insert"
151 151 " & preceded_by_opening_bracket"
152 152 " & auto_match"
153 153 " & followed_by_closing_bracket",
154 154 ),
155 155 Binding(
156 156 match.delete_pair,
157 157 ["backspace"],
158 158 "focused_insert"
159 159 " & preceded_by_opening_brace"
160 160 " & auto_match"
161 161 " & followed_by_closing_brace",
162 162 ),
163 163 Binding(
164 164 match.delete_pair,
165 165 ["backspace"],
166 166 "focused_insert"
167 167 " & preceded_by_double_quote"
168 168 " & auto_match"
169 169 " & followed_by_double_quote",
170 170 ),
171 171 Binding(
172 172 match.delete_pair,
173 173 ["backspace"],
174 174 "focused_insert"
175 175 " & preceded_by_single_quote"
176 176 " & auto_match"
177 177 " & followed_by_single_quote",
178 178 ),
179 179 ]
180 180
181 181 AUTO_SUGGEST_BINDINGS = [
182 182 Binding(
183 183 auto_suggest.accept_in_vi_insert_mode,
184 184 ["end"],
185 185 "default_buffer_focused & (ebivim | ~vi_insert_mode)",
186 186 ),
187 187 Binding(
188 188 auto_suggest.accept_in_vi_insert_mode,
189 189 ["c-e"],
190 190 "vi_insert_mode & default_buffer_focused & ebivim",
191 191 ),
192 192 Binding(auto_suggest.accept, ["c-f"], "vi_insert_mode & default_buffer_focused"),
193 193 Binding(
194 194 auto_suggest.accept_word,
195 195 ["escape", "f"],
196 196 "vi_insert_mode & default_buffer_focused & ebivim",
197 197 ),
198 198 Binding(
199 199 auto_suggest.accept_token,
200 200 ["c-right"],
201 201 "has_suggestion & default_buffer_focused",
202 202 ),
203 203 Binding(
204 204 auto_suggest.discard,
205 205 ["escape"],
206 206 "has_suggestion & default_buffer_focused & emacs_insert_mode",
207 207 ),
208 208 Binding(
209 209 auto_suggest.swap_autosuggestion_up,
210 210 ["up"],
211 211 "navigable_suggestions"
212 212 " & ~has_line_above"
213 213 " & has_suggestion"
214 214 " & default_buffer_focused",
215 215 ),
216 216 Binding(
217 217 auto_suggest.swap_autosuggestion_down,
218 218 ["down"],
219 219 "navigable_suggestions"
220 220 " & ~has_line_below"
221 221 " & has_suggestion"
222 222 " & default_buffer_focused",
223 223 ),
224 224 Binding(
225 225 auto_suggest.up_and_update_hint,
226 226 ["up"],
227 227 "has_line_above & navigable_suggestions & default_buffer_focused",
228 228 ),
229 229 Binding(
230 230 auto_suggest.down_and_update_hint,
231 231 ["down"],
232 232 "has_line_below & navigable_suggestions & default_buffer_focused",
233 233 ),
234 234 Binding(
235 235 auto_suggest.accept_character,
236 236 ["escape", "right"],
237 237 "has_suggestion & default_buffer_focused",
238 238 ),
239 239 Binding(
240 240 auto_suggest.accept_and_move_cursor_left,
241 241 ["c-left"],
242 242 "has_suggestion & default_buffer_focused",
243 243 ),
244 244 Binding(
245 245 auto_suggest.accept_and_keep_cursor,
246 246 ["c-down"],
247 247 "has_suggestion & default_buffer_focused",
248 248 ),
249 249 Binding(
250 250 auto_suggest.backspace_and_resume_hint,
251 251 ["backspace"],
252 252 "has_suggestion & default_buffer_focused",
253 253 ),
254 254 ]
255 255
256 256
257 257 SIMPLE_CONTROL_BINDINGS = [
258 258 Binding(cmd, [key], "vi_insert_mode & default_buffer_focused & ebivim")
259 259 for key, cmd in {
260 260 "c-a": nc.beginning_of_line,
261 261 "c-b": nc.backward_char,
262 262 "c-k": nc.kill_line,
263 263 "c-w": nc.backward_kill_word,
264 264 "c-y": nc.yank,
265 265 "c-_": nc.undo,
266 266 }.items()
267 267 ]
268 268
269 269
270 270 ALT_AND_COMOBO_CONTROL_BINDINGS = [
271 271 Binding(cmd, list(keys), "vi_insert_mode & default_buffer_focused & ebivim")
272 272 for keys, cmd in {
273 273 # Control Combos
274 274 ("c-x", "c-e"): nc.edit_and_execute,
275 275 ("c-x", "e"): nc.edit_and_execute,
276 276 # Alt
277 277 ("escape", "b"): nc.backward_word,
278 278 ("escape", "c"): nc.capitalize_word,
279 279 ("escape", "d"): nc.kill_word,
280 280 ("escape", "h"): nc.backward_kill_word,
281 281 ("escape", "l"): nc.downcase_word,
282 282 ("escape", "u"): nc.uppercase_word,
283 283 ("escape", "y"): nc.yank_pop,
284 284 ("escape", "."): nc.yank_last_arg,
285 285 }.items()
286 286 ]
287 287
288 288
289 289 def add_binding(bindings: KeyBindings, binding: Binding):
290 290 bindings.add(
291 291 *binding.keys,
292 292 **({"filter": binding.filter} if binding.filter is not None else {}),
293 293 )(binding.command)
294 294
295 295
296 296 def create_ipython_shortcuts(shell, skip=None) -> KeyBindings:
297 297 """Set up the prompt_toolkit keyboard shortcuts for IPython.
298 298
299 299 Parameters
300 300 ----------
301 301 shell: InteractiveShell
302 302 The current IPython shell Instance
303 303 skip: List[Binding]
304 304 Bindings to skip.
305 305
306 306 Returns
307 307 -------
308 308 KeyBindings
309 309 the keybinding instance for prompt toolkit.
310 310
311 311 """
312 312 kb = KeyBindings()
313 313 skip = skip or []
314 314 for binding in KEY_BINDINGS:
315 315 skip_this_one = False
316 316 for to_skip in skip:
317 317 if (
318 318 to_skip.command == binding.command
319 319 and to_skip.filter == binding.filter
320 320 and to_skip.keys == binding.keys
321 321 ):
322 322 skip_this_one = True
323 323 break
324 324 if skip_this_one:
325 325 continue
326 326 add_binding(kb, binding)
327 327
328 328 def get_input_mode(self):
329 329 app = get_app()
330 330 app.ttimeoutlen = shell.ttimeoutlen
331 331 app.timeoutlen = shell.timeoutlen
332 332
333 333 return self._input_mode
334 334
335 335 def set_input_mode(self, mode):
336 336 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
337 337 cursor = "\x1b[{} q".format(shape)
338 338
339 339 sys.stdout.write(cursor)
340 340 sys.stdout.flush()
341 341
342 342 self._input_mode = mode
343 343
344 344 if shell.editing_mode == "vi" and shell.modal_cursor:
345 345 ViState._input_mode = InputMode.INSERT # type: ignore
346 346 ViState.input_mode = property(get_input_mode, set_input_mode) # type: ignore
347 347
348 348 return kb
349 349
350 350
351 351 def reformat_and_execute(event):
352 352 """Reformat code and execute it"""
353 353 shell = get_ipython()
354 354 reformat_text_before_cursor(
355 355 event.current_buffer, event.current_buffer.document, shell
356 356 )
357 357 event.current_buffer.validate_and_handle()
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 handle_return_or_newline_or_execute(event):
370 370 shell = get_ipython()
371 371 if getattr(shell, "handle_return", None):
372 372 return shell.handle_return(shell)(event)
373 373 else:
374 374 return newline_or_execute_outer(shell)(event)
375 375
376 376
377 377 def newline_or_execute_outer(shell):
378 378 def newline_or_execute(event):
379 379 """When the user presses return, insert a newline or execute the code."""
380 380 b = event.current_buffer
381 381 d = b.document
382 382
383 383 if b.complete_state:
384 384 cc = b.complete_state.current_completion
385 385 if cc:
386 386 b.apply_completion(cc)
387 387 else:
388 388 b.cancel_completion()
389 389 return
390 390
391 391 # If there's only one line, treat it as if the cursor is at the end.
392 392 # See https://github.com/ipython/ipython/issues/10425
393 393 if d.line_count == 1:
394 394 check_text = d.text
395 395 else:
396 396 check_text = d.text[: d.cursor_position]
397 397 status, indent = shell.check_complete(check_text)
398 398
399 399 # if all we have after the cursor is whitespace: reformat current text
400 400 # before cursor
401 401 after_cursor = d.text[d.cursor_position :]
402 402 reformatted = False
403 403 if not after_cursor.strip():
404 404 reformat_text_before_cursor(b, d, shell)
405 405 reformatted = True
406 406 if not (
407 407 d.on_last_line
408 408 or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
409 409 ):
410 410 if shell.autoindent:
411 411 b.insert_text("\n" + indent)
412 412 else:
413 413 b.insert_text("\n")
414 414 return
415 415
416 416 if (status != "incomplete") and b.accept_handler:
417 417 if not reformatted:
418 418 reformat_text_before_cursor(b, d, shell)
419 419 b.validate_and_handle()
420 420 else:
421 421 if shell.autoindent:
422 422 b.insert_text("\n" + indent)
423 423 else:
424 424 b.insert_text("\n")
425 425
426 426 return newline_or_execute
427 427
428 428
429 429 def previous_history_or_previous_completion(event):
430 430 """
431 431 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
432 432
433 433 If completer is open this still select previous completion.
434 434 """
435 435 event.current_buffer.auto_up()
436 436
437 437
438 438 def next_history_or_next_completion(event):
439 439 """
440 440 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
441 441
442 442 If completer is open this still select next completion.
443 443 """
444 444 event.current_buffer.auto_down()
445 445
446 446
447 447 def dismiss_completion(event):
448 448 """Dismiss completion"""
449 449 b = event.current_buffer
450 450 if b.complete_state:
451 451 b.cancel_completion()
452 452
453 453
454 454 def reset_buffer(event):
455 455 """Reset buffer"""
456 456 b = event.current_buffer
457 457 if b.complete_state:
458 458 b.cancel_completion()
459 459 else:
460 460 b.reset()
461 461
462 462
463 463 def reset_search_buffer(event):
464 464 """Reset search buffer"""
465 465 if event.current_buffer.document.text:
466 466 event.current_buffer.reset()
467 467 else:
468 468 event.app.layout.focus(DEFAULT_BUFFER)
469 469
470 470
471 471 def suspend_to_bg(event):
472 472 """Suspend to background"""
473 473 event.app.suspend_to_background()
474 474
475 475
476 476 def quit(event):
477 477 """
478 478 Quit application with ``SIGQUIT`` if supported or ``sys.exit`` otherwise.
479 479
480 480 On platforms that support SIGQUIT, send SIGQUIT to the current process.
481 481 On other platforms, just exit the process with a message.
482 482 """
483 483 sigquit = getattr(signal, "SIGQUIT", None)
484 484 if sigquit is not None:
485 485 os.kill(0, signal.SIGQUIT)
486 486 else:
487 487 sys.exit("Quit")
488 488
489 489
490 490 def indent_buffer(event):
491 491 """Indent buffer"""
492 492 event.current_buffer.insert_text(" " * 4)
493 493
494 494
495 @undoc
496 def newline_with_copy_margin(event):
497 """
498 DEPRECATED since IPython 6.0
499
500 See :any:`newline_autoindent_outer` for a replacement.
501
502 Preserve margin and cursor position when using
503 Control-O to insert a newline in EMACS mode
504 """
505 warnings.warn(
506 "`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
507 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
508 DeprecationWarning,
509 stacklevel=2,
510 )
511
512 b = event.current_buffer
513 cursor_start_pos = b.document.cursor_position_col
514 b.newline(copy_margin=True)
515 b.cursor_up(count=1)
516 cursor_end_pos = b.document.cursor_position_col
517 if cursor_start_pos != cursor_end_pos:
518 pos_diff = cursor_start_pos - cursor_end_pos
519 b.cursor_right(count=pos_diff)
520
521
522 495 def newline_autoindent(event):
523 496 """Insert a newline after the cursor indented appropriately.
524 497
525 Fancier version of deprecated ``newline_with_copy_margin`` which should
498 Fancier version of former ``newline_with_copy_margin`` which should
526 499 compute the correct indentation of the inserted line. That is to say, indent
527 500 by 4 extra space after a function definition, class definition, context
528 501 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
529 502 """
530 503 shell = get_ipython()
531 504 inputsplitter = shell.input_transformer_manager
532 505 b = event.current_buffer
533 506 d = b.document
534 507
535 508 if b.complete_state:
536 509 b.cancel_completion()
537 510 text = d.text[: d.cursor_position] + "\n"
538 511 _, indent = inputsplitter.check_complete(text)
539 512 b.insert_text("\n" + (" " * (indent or 0)), move_cursor=False)
540 513
541 514
542 515 def open_input_in_editor(event):
543 516 """Open code from input in external editor"""
544 517 event.app.current_buffer.open_in_editor()
545 518
546 519
547 520 if sys.platform == "win32":
548 521 from IPython.core.error import TryNext
549 522 from IPython.lib.clipboard import (
550 523 ClipboardEmpty,
551 524 tkinter_clipboard_get,
552 525 win32_clipboard_get,
553 526 )
554 527
555 528 @undoc
556 529 def win_paste(event):
557 530 try:
558 531 text = win32_clipboard_get()
559 532 except TryNext:
560 533 try:
561 534 text = tkinter_clipboard_get()
562 535 except (TryNext, ClipboardEmpty):
563 536 return
564 537 except ClipboardEmpty:
565 538 return
566 539 event.current_buffer.insert_text(text.replace("\t", " " * 4))
567 540
568 541 else:
569 542
570 543 @undoc
571 544 def win_paste(event):
572 545 """Stub used on other platforms"""
573 546 pass
574 547
575 548
576 549 KEY_BINDINGS = [
577 550 Binding(
578 551 handle_return_or_newline_or_execute,
579 552 ["enter"],
580 553 "default_buffer_focused & ~has_selection & insert_mode",
581 554 ),
582 555 Binding(
583 556 reformat_and_execute,
584 557 ["escape", "enter"],
585 558 "default_buffer_focused & ~has_selection & insert_mode & ebivim",
586 559 ),
587 560 Binding(quit, ["c-\\"]),
588 561 Binding(
589 562 previous_history_or_previous_completion,
590 563 ["c-p"],
591 564 "vi_insert_mode & default_buffer_focused",
592 565 ),
593 566 Binding(
594 567 next_history_or_next_completion,
595 568 ["c-n"],
596 569 "vi_insert_mode & default_buffer_focused",
597 570 ),
598 571 Binding(dismiss_completion, ["c-g"], "default_buffer_focused & has_completions"),
599 572 Binding(reset_buffer, ["c-c"], "default_buffer_focused"),
600 573 Binding(reset_search_buffer, ["c-c"], "search_buffer_focused"),
601 574 Binding(suspend_to_bg, ["c-z"], "supports_suspend"),
602 575 Binding(
603 576 indent_buffer,
604 577 ["tab"], # Ctrl+I == Tab
605 578 "default_buffer_focused"
606 579 " & ~has_selection"
607 580 " & insert_mode"
608 581 " & cursor_in_leading_ws",
609 582 ),
610 583 Binding(newline_autoindent, ["c-o"], "default_buffer_focused & emacs_insert_mode"),
611 584 Binding(open_input_in_editor, ["f2"], "default_buffer_focused"),
612 585 *AUTO_MATCH_BINDINGS,
613 586 *AUTO_SUGGEST_BINDINGS,
614 587 Binding(
615 588 display_completions_like_readline,
616 589 ["c-i"],
617 590 "readline_like_completions"
618 591 " & default_buffer_focused"
619 592 " & ~has_selection"
620 593 " & insert_mode"
621 594 " & ~cursor_in_leading_ws",
622 595 ),
623 596 Binding(win_paste, ["c-v"], "default_buffer_focused & ~vi_mode & is_windows_os"),
624 597 *SIMPLE_CONTROL_BINDINGS,
625 598 *ALT_AND_COMOBO_CONTROL_BINDINGS,
626 599 ]
General Comments 0
You need to be logged in to leave comments. Login now