##// END OF EJS Templates
Add missing auto_match flag in shortcuts.py...
Gal B -
Show More
@@ -1,528 +1,534 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('"', filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
143 143 def _(event):
144 144 event.current_buffer.insert_text('""')
145 145 event.current_buffer.cursor_left()
146 146
147 147 @kb.add("'", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
148 148 def _(event):
149 149 event.current_buffer.insert_text("''")
150 150 event.current_buffer.cursor_left()
151 151
152 152 # raw string
153 @kb.add("(", filter=focused_insert & preceding_text(r".*(r|R)[\"'](-*)$"))
153 @kb.add(
154 "(", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
155 )
154 156 def _(event):
155 157 matches = re.match(
156 158 r".*(r|R)[\"'](-*)",
157 159 event.current_buffer.document.current_line_before_cursor,
158 160 )
159 161 dashes = matches.group(2) or ""
160 162 event.current_buffer.insert_text("()" + dashes)
161 163 event.current_buffer.cursor_left(len(dashes) + 1)
162 164
163 @kb.add("[", filter=focused_insert & preceding_text(r".*(r|R)[\"'](-*)$"))
165 @kb.add(
166 "[", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
167 )
164 168 def _(event):
165 169 matches = re.match(
166 170 r".*(r|R)[\"'](-*)",
167 171 event.current_buffer.document.current_line_before_cursor,
168 172 )
169 173 dashes = matches.group(2) or ""
170 174 event.current_buffer.insert_text("[]" + dashes)
171 175 event.current_buffer.cursor_left(len(dashes) + 1)
172 176
173 @kb.add("{", filter=focused_insert & preceding_text(r".*(r|R)[\"'](-*)$"))
177 @kb.add(
178 "{", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
179 )
174 180 def _(event):
175 181 matches = re.match(
176 182 r".*(r|R)[\"'](-*)",
177 183 event.current_buffer.document.current_line_before_cursor,
178 184 )
179 185 dashes = matches.group(2) or ""
180 186 event.current_buffer.insert_text("{}" + dashes)
181 187 event.current_buffer.cursor_left(len(dashes) + 1)
182 188
183 @kb.add('"', filter=focused_insert & preceding_text(r".*(r|R)$"))
189 @kb.add('"', filter=focused_insert & auto_match & preceding_text(r".*(r|R)$"))
184 190 def _(event):
185 191 event.current_buffer.insert_text('""')
186 192 event.current_buffer.cursor_left()
187 193
188 @kb.add("'", filter=focused_insert & preceding_text(r".*(r|R)$"))
194 @kb.add("'", filter=focused_insert & auto_match & preceding_text(r".*(r|R)$"))
189 195 def _(event):
190 196 event.current_buffer.insert_text("''")
191 197 event.current_buffer.cursor_left()
192 198
193 199 # just move cursor
194 200 @kb.add(")", filter=focused_insert & auto_match & following_text(r"^\)"))
195 201 @kb.add("]", filter=focused_insert & auto_match & following_text(r"^\]"))
196 202 @kb.add("}", filter=focused_insert & auto_match & following_text(r"^\}"))
197 203 @kb.add('"', filter=focused_insert & auto_match & following_text('^"'))
198 204 @kb.add("'", filter=focused_insert & auto_match & following_text("^'"))
199 205 def _(event):
200 206 event.current_buffer.cursor_right()
201 207
202 208 @kb.add(
203 209 "backspace",
204 210 filter=focused_insert
205 211 & preceding_text(r".*\($")
206 212 & auto_match
207 213 & following_text(r"^\)"),
208 214 )
209 215 @kb.add(
210 216 "backspace",
211 217 filter=focused_insert
212 218 & preceding_text(r".*\[$")
213 219 & auto_match
214 220 & following_text(r"^\]"),
215 221 )
216 222 @kb.add(
217 223 "backspace",
218 224 filter=focused_insert
219 225 & preceding_text(r".*\{$")
220 226 & auto_match
221 227 & following_text(r"^\}"),
222 228 )
223 229 @kb.add(
224 230 "backspace",
225 231 filter=focused_insert
226 232 & preceding_text('.*"$')
227 233 & auto_match
228 234 & following_text('^"'),
229 235 )
230 236 @kb.add(
231 237 "backspace",
232 238 filter=focused_insert
233 239 & preceding_text(r".*'$")
234 240 & auto_match
235 241 & following_text(r"^'"),
236 242 )
237 243 def _(event):
238 244 event.current_buffer.delete()
239 245 event.current_buffer.delete_before_cursor()
240 246
241 247 if shell.display_completions == "readlinelike":
242 248 kb.add(
243 249 "c-i",
244 250 filter=(
245 251 has_focus(DEFAULT_BUFFER)
246 252 & ~has_selection
247 253 & insert_mode
248 254 & ~cursor_in_leading_ws
249 255 ),
250 256 )(display_completions_like_readline)
251 257
252 258 if sys.platform == "win32":
253 259 kb.add("c-v", filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
254 260
255 261 @Condition
256 262 def ebivim():
257 263 return shell.emacs_bindings_in_vi_insert_mode
258 264
259 265 focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode
260 266
261 267 # Needed for to accept autosuggestions in vi insert mode
262 268 @kb.add("c-e", filter=focused_insert_vi & ebivim)
263 269 def _(event):
264 270 b = event.current_buffer
265 271 suggestion = b.suggestion
266 272 if suggestion:
267 273 b.insert_text(suggestion.text)
268 274 else:
269 275 nc.end_of_line(event)
270 276
271 277 @kb.add("c-f", filter=focused_insert_vi)
272 278 def _(event):
273 279 b = event.current_buffer
274 280 suggestion = b.suggestion
275 281 if suggestion:
276 282 b.insert_text(suggestion.text)
277 283 else:
278 284 nc.forward_char(event)
279 285
280 286 @kb.add("escape", "f", filter=focused_insert_vi & ebivim)
281 287 def _(event):
282 288 b = event.current_buffer
283 289 suggestion = b.suggestion
284 290 if suggestion:
285 291 t = re.split(r"(\S+\s+)", suggestion.text)
286 292 b.insert_text(next((x for x in t if x), ""))
287 293 else:
288 294 nc.forward_word(event)
289 295
290 296 # Simple Control keybindings
291 297 key_cmd_dict = {
292 298 "c-a": nc.beginning_of_line,
293 299 "c-b": nc.backward_char,
294 300 "c-k": nc.kill_line,
295 301 "c-w": nc.backward_kill_word,
296 302 "c-y": nc.yank,
297 303 "c-_": nc.undo,
298 304 }
299 305
300 306 for key, cmd in key_cmd_dict.items():
301 307 kb.add(key, filter=focused_insert_vi & ebivim)(cmd)
302 308
303 309 # Alt and Combo Control keybindings
304 310 keys_cmd_dict = {
305 311 # Control Combos
306 312 ("c-x", "c-e"): nc.edit_and_execute,
307 313 ("c-x", "e"): nc.edit_and_execute,
308 314 # Alt
309 315 ("escape", "b"): nc.backward_word,
310 316 ("escape", "c"): nc.capitalize_word,
311 317 ("escape", "d"): nc.kill_word,
312 318 ("escape", "h"): nc.backward_kill_word,
313 319 ("escape", "l"): nc.downcase_word,
314 320 ("escape", "u"): nc.uppercase_word,
315 321 ("escape", "y"): nc.yank_pop,
316 322 ("escape", "."): nc.yank_last_arg,
317 323 }
318 324
319 325 for keys, cmd in keys_cmd_dict.items():
320 326 kb.add(*keys, filter=focused_insert_vi & ebivim)(cmd)
321 327
322 328 def get_input_mode(self):
323 329 app = get_app()
324 330 app.ttimeoutlen = shell.ttimeoutlen
325 331 app.timeoutlen = shell.timeoutlen
326 332
327 333 return self._input_mode
328 334
329 335 def set_input_mode(self, mode):
330 336 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
331 337 cursor = "\x1b[{} q".format(shape)
332 338
333 339 if hasattr(sys.stdout, "_cli"):
334 340 write = sys.stdout._cli.output.write_raw
335 341 else:
336 342 write = sys.stdout.write
337 343
338 344 write(cursor)
339 345 sys.stdout.flush()
340 346
341 347 self._input_mode = mode
342 348
343 349 if shell.editing_mode == "vi" and shell.modal_cursor:
344 350 ViState._input_mode = InputMode.INSERT
345 351 ViState.input_mode = property(get_input_mode, set_input_mode)
346 352
347 353 return kb
348 354
349 355
350 356 def reformat_text_before_cursor(buffer, document, shell):
351 357 text = buffer.delete_before_cursor(len(document.text[:document.cursor_position]))
352 358 try:
353 359 formatted_text = shell.reformat_handler(text)
354 360 buffer.insert_text(formatted_text)
355 361 except Exception as e:
356 362 buffer.insert_text(text)
357 363
358 364
359 365 def newline_or_execute_outer(shell):
360 366
361 367 def newline_or_execute(event):
362 368 """When the user presses return, insert a newline or execute the code."""
363 369 b = event.current_buffer
364 370 d = b.document
365 371
366 372 if b.complete_state:
367 373 cc = b.complete_state.current_completion
368 374 if cc:
369 375 b.apply_completion(cc)
370 376 else:
371 377 b.cancel_completion()
372 378 return
373 379
374 380 # If there's only one line, treat it as if the cursor is at the end.
375 381 # See https://github.com/ipython/ipython/issues/10425
376 382 if d.line_count == 1:
377 383 check_text = d.text
378 384 else:
379 385 check_text = d.text[:d.cursor_position]
380 386 status, indent = shell.check_complete(check_text)
381 387
382 388 # if all we have after the cursor is whitespace: reformat current text
383 389 # before cursor
384 390 after_cursor = d.text[d.cursor_position:]
385 391 reformatted = False
386 392 if not after_cursor.strip():
387 393 reformat_text_before_cursor(b, d, shell)
388 394 reformatted = True
389 395 if not (d.on_last_line or
390 396 d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
391 397 ):
392 398 if shell.autoindent:
393 399 b.insert_text('\n' + indent)
394 400 else:
395 401 b.insert_text('\n')
396 402 return
397 403
398 404 if (status != 'incomplete') and b.accept_handler:
399 405 if not reformatted:
400 406 reformat_text_before_cursor(b, d, shell)
401 407 b.validate_and_handle()
402 408 else:
403 409 if shell.autoindent:
404 410 b.insert_text('\n' + indent)
405 411 else:
406 412 b.insert_text('\n')
407 413 return newline_or_execute
408 414
409 415
410 416 def previous_history_or_previous_completion(event):
411 417 """
412 418 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
413 419
414 420 If completer is open this still select previous completion.
415 421 """
416 422 event.current_buffer.auto_up()
417 423
418 424
419 425 def next_history_or_next_completion(event):
420 426 """
421 427 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
422 428
423 429 If completer is open this still select next completion.
424 430 """
425 431 event.current_buffer.auto_down()
426 432
427 433
428 434 def dismiss_completion(event):
429 435 b = event.current_buffer
430 436 if b.complete_state:
431 437 b.cancel_completion()
432 438
433 439
434 440 def reset_buffer(event):
435 441 b = event.current_buffer
436 442 if b.complete_state:
437 443 b.cancel_completion()
438 444 else:
439 445 b.reset()
440 446
441 447
442 448 def reset_search_buffer(event):
443 449 if event.current_buffer.document.text:
444 450 event.current_buffer.reset()
445 451 else:
446 452 event.app.layout.focus(DEFAULT_BUFFER)
447 453
448 454 def suspend_to_bg(event):
449 455 event.app.suspend_to_background()
450 456
451 457 def force_exit(event):
452 458 """
453 459 Force exit (with a non-zero return value)
454 460 """
455 461 sys.exit("Quit")
456 462
457 463 def indent_buffer(event):
458 464 event.current_buffer.insert_text(' ' * 4)
459 465
460 466 @undoc
461 467 def newline_with_copy_margin(event):
462 468 """
463 469 DEPRECATED since IPython 6.0
464 470
465 471 See :any:`newline_autoindent_outer` for a replacement.
466 472
467 473 Preserve margin and cursor position when using
468 474 Control-O to insert a newline in EMACS mode
469 475 """
470 476 warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
471 477 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
472 478 DeprecationWarning, stacklevel=2)
473 479
474 480 b = event.current_buffer
475 481 cursor_start_pos = b.document.cursor_position_col
476 482 b.newline(copy_margin=True)
477 483 b.cursor_up(count=1)
478 484 cursor_end_pos = b.document.cursor_position_col
479 485 if cursor_start_pos != cursor_end_pos:
480 486 pos_diff = cursor_start_pos - cursor_end_pos
481 487 b.cursor_right(count=pos_diff)
482 488
483 489 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
484 490 """
485 491 Return a function suitable for inserting a indented newline after the cursor.
486 492
487 493 Fancier version of deprecated ``newline_with_copy_margin`` which should
488 494 compute the correct indentation of the inserted line. That is to say, indent
489 495 by 4 extra space after a function definition, class definition, context
490 496 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
491 497 """
492 498
493 499 def newline_autoindent(event):
494 500 """insert a newline after the cursor indented appropriately."""
495 501 b = event.current_buffer
496 502 d = b.document
497 503
498 504 if b.complete_state:
499 505 b.cancel_completion()
500 506 text = d.text[:d.cursor_position] + '\n'
501 507 _, indent = inputsplitter.check_complete(text)
502 508 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
503 509
504 510 return newline_autoindent
505 511
506 512
507 513 def open_input_in_editor(event):
508 514 event.app.current_buffer.open_in_editor()
509 515
510 516
511 517 if sys.platform == 'win32':
512 518 from IPython.core.error import TryNext
513 519 from IPython.lib.clipboard import (ClipboardEmpty,
514 520 win32_clipboard_get,
515 521 tkinter_clipboard_get)
516 522
517 523 @undoc
518 524 def win_paste(event):
519 525 try:
520 526 text = win32_clipboard_get()
521 527 except TryNext:
522 528 try:
523 529 text = tkinter_clipboard_get()
524 530 except (TryNext, ClipboardEmpty):
525 531 return
526 532 except ClipboardEmpty:
527 533 return
528 534 event.current_buffer.insert_text(text.replace("\t", " " * 4))
General Comments 0
You need to be logged in to leave comments. Login now