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