##// END OF EJS Templates
update code to work with black more recent versions
Matthias Bussonnier -
Show More
@@ -1,580 +1,579 b''
1 1 """IPython terminal interface using prompt_toolkit"""
2 2
3 3 import os
4 4 import sys
5 5 import warnings
6 6 from warnings import warn
7 7
8 8 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
9 9 from IPython.utils import io
10 10 from IPython.utils.py3compat import input
11 11 from IPython.utils.terminal import toggle_set_term_title, set_term_title
12 12 from IPython.utils.process import abbrev_cwd
13 13 from traitlets import (
14 14 Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union,
15 15 Any, validate
16 16 )
17 17
18 18 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
19 19 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
20 20 from prompt_toolkit.formatted_text import PygmentsTokens
21 21 from prompt_toolkit.history import InMemoryHistory
22 22 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
23 23 from prompt_toolkit.output import ColorDepth
24 24 from prompt_toolkit.patch_stdout import patch_stdout
25 25 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
26 26 from prompt_toolkit.styles import DynamicStyle, merge_styles
27 27 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
28 28
29 29 from pygments.styles import get_style_by_name
30 30 from pygments.style import Style
31 31 from pygments.token import Token
32 32
33 33 from .debugger import TerminalPdb, Pdb
34 34 from .magics import TerminalMagics
35 35 from .pt_inputhooks import get_inputhook_name_and_func
36 36 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
37 37 from .ptutils import IPythonPTCompleter, IPythonPTLexer
38 38 from .shortcuts import create_ipython_shortcuts
39 39
40 40 DISPLAY_BANNER_DEPRECATED = object()
41 41
42 42
43 43 class _NoStyle(Style): pass
44 44
45 45
46 46
47 47 _style_overrides_light_bg = {
48 48 Token.Prompt: '#0000ff',
49 49 Token.PromptNum: '#0000ee bold',
50 50 Token.OutPrompt: '#cc0000',
51 51 Token.OutPromptNum: '#bb0000 bold',
52 52 }
53 53
54 54 _style_overrides_linux = {
55 55 Token.Prompt: '#00cc00',
56 56 Token.PromptNum: '#00bb00 bold',
57 57 Token.OutPrompt: '#cc0000',
58 58 Token.OutPromptNum: '#bb0000 bold',
59 59 }
60 60
61 61 def get_default_editor():
62 62 try:
63 63 return os.environ['EDITOR']
64 64 except KeyError:
65 65 pass
66 66 except UnicodeError:
67 67 warn("$EDITOR environment variable is not pure ASCII. Using platform "
68 68 "default editor.")
69 69
70 70 if os.name == 'posix':
71 71 return 'vi' # the only one guaranteed to be there!
72 72 else:
73 73 return 'notepad' # same in Windows!
74 74
75 75 # conservatively check for tty
76 76 # overridden streams can result in things like:
77 77 # - sys.stdin = None
78 78 # - no isatty method
79 79 for _name in ('stdin', 'stdout', 'stderr'):
80 80 _stream = getattr(sys, _name)
81 81 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
82 82 _is_tty = False
83 83 break
84 84 else:
85 85 _is_tty = True
86 86
87 87
88 88 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
89 89
90 90 def black_reformat_handler(text_before_cursor):
91 91 import black
92 92 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
93 93 if not text_before_cursor.endswith('\n') and formatted_text.endswith('\n'):
94 94 formatted_text = formatted_text[:-1]
95 95 return formatted_text
96 96
97 97
98 98 class TerminalInteractiveShell(InteractiveShell):
99 99 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
100 100 'to reserve for the completion menu'
101 101 ).tag(config=True)
102 102
103 103 pt_app = None
104 104 debugger_history = None
105 105
106 106 simple_prompt = Bool(_use_simple_prompt,
107 107 help="""Use `raw_input` for the REPL, without completion and prompt colors.
108 108
109 109 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
110 110 IPython own testing machinery, and emacs inferior-shell integration through elpy.
111 111
112 112 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
113 113 environment variable is set, or the current terminal is not a tty."""
114 114 ).tag(config=True)
115 115
116 116 @property
117 117 def debugger_cls(self):
118 118 return Pdb if self.simple_prompt else TerminalPdb
119 119
120 120 confirm_exit = Bool(True,
121 121 help="""
122 122 Set to confirm when you try to exit IPython with an EOF (Control-D
123 123 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
124 124 you can force a direct exit without any confirmation.""",
125 125 ).tag(config=True)
126 126
127 127 editing_mode = Unicode('emacs',
128 128 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
129 129 ).tag(config=True)
130 130
131 131 autoformatter = Unicode(None,
132 132 help="Autoformatter to reformat Terminal code. Can be `'black'` or `None`",
133 133 allow_none=True
134 134 ).tag(config=True)
135 135
136 136 mouse_support = Bool(False,
137 137 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
138 138 ).tag(config=True)
139 139
140 140 # We don't load the list of styles for the help string, because loading
141 141 # Pygments plugins takes time and can cause unexpected errors.
142 142 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
143 143 help="""The name or class of a Pygments style to use for syntax
144 144 highlighting. To see available styles, run `pygmentize -L styles`."""
145 145 ).tag(config=True)
146 146
147 147 @validate('editing_mode')
148 148 def _validate_editing_mode(self, proposal):
149 149 if proposal['value'].lower() == 'vim':
150 150 proposal['value']= 'vi'
151 151 elif proposal['value'].lower() == 'default':
152 152 proposal['value']= 'emacs'
153 153
154 154 if hasattr(EditingMode, proposal['value'].upper()):
155 155 return proposal['value'].lower()
156 156
157 157 return self.editing_mode
158 158
159 159
160 160 @observe('editing_mode')
161 161 def _editing_mode(self, change):
162 162 u_mode = change.new.upper()
163 163 if self.pt_app:
164 164 self.pt_app.editing_mode = u_mode
165 165
166 166 @observe('autoformatter')
167 167 def _autoformatter_changed(self, change):
168 168 formatter = change.new
169 169 if formatter is None:
170 170 self.reformat_handler = lambda x:x
171 171 elif formatter == 'black':
172 172 self.reformat_handler = black_reformat_handler
173 173 else:
174 174 raise ValueError
175 175
176 176 @observe('highlighting_style')
177 177 @observe('colors')
178 178 def _highlighting_style_changed(self, change):
179 179 self.refresh_style()
180 180
181 181 def refresh_style(self):
182 182 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
183 183
184 184
185 185 highlighting_style_overrides = Dict(
186 186 help="Override highlighting format for specific tokens"
187 187 ).tag(config=True)
188 188
189 189 true_color = Bool(False,
190 190 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
191 191 "If your terminal supports true color, the following command "
192 192 "should print 'TRUECOLOR' in orange: "
193 193 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
194 194 ).tag(config=True)
195 195
196 196 editor = Unicode(get_default_editor(),
197 197 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
198 198 ).tag(config=True)
199 199
200 200 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
201 201
202 202 prompts = Instance(Prompts)
203 203
204 204 @default('prompts')
205 205 def _prompts_default(self):
206 206 return self.prompts_class(self)
207 207
208 208 # @observe('prompts')
209 209 # def _(self, change):
210 210 # self._update_layout()
211 211
212 212 @default('displayhook_class')
213 213 def _displayhook_class_default(self):
214 214 return RichPromptDisplayHook
215 215
216 216 term_title = Bool(True,
217 217 help="Automatically set the terminal title"
218 218 ).tag(config=True)
219 219
220 220 term_title_format = Unicode("IPython: {cwd}",
221 221 help="Customize the terminal title format. This is a python format string. " +
222 222 "Available substitutions are: {cwd}."
223 223 ).tag(config=True)
224 224
225 225 display_completions = Enum(('column', 'multicolumn','readlinelike'),
226 226 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
227 227 "'readlinelike'. These options are for `prompt_toolkit`, see "
228 228 "`prompt_toolkit` documentation for more information."
229 229 ),
230 230 default_value='multicolumn').tag(config=True)
231 231
232 232 highlight_matching_brackets = Bool(True,
233 233 help="Highlight matching brackets.",
234 234 ).tag(config=True)
235 235
236 236 extra_open_editor_shortcuts = Bool(False,
237 237 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
238 238 "This is in addition to the F2 binding, which is always enabled."
239 239 ).tag(config=True)
240 240
241 241 handle_return = Any(None,
242 242 help="Provide an alternative handler to be called when the user presses "
243 243 "Return. This is an advanced option intended for debugging, which "
244 244 "may be changed or removed in later releases."
245 245 ).tag(config=True)
246 246
247 247 enable_history_search = Bool(True,
248 248 help="Allows to enable/disable the prompt toolkit history search"
249 249 ).tag(config=True)
250 250
251 251 prompt_includes_vi_mode = Bool(True,
252 252 help="Display the current vi mode (when using vi editing mode)."
253 253 ).tag(config=True)
254 254
255 255 @observe('term_title')
256 256 def init_term_title(self, change=None):
257 257 # Enable or disable the terminal title.
258 258 if self.term_title:
259 259 toggle_set_term_title(True)
260 260 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
261 261 else:
262 262 toggle_set_term_title(False)
263 263
264 264 def init_display_formatter(self):
265 265 super(TerminalInteractiveShell, self).init_display_formatter()
266 266 # terminal only supports plain text
267 267 self.display_formatter.active_types = ['text/plain']
268 268 # disable `_ipython_display_`
269 269 self.display_formatter.ipython_display_formatter.enabled = False
270 270
271 271 def init_prompt_toolkit_cli(self):
272 self.reformat_handler = lambda x:x
273 272 if self.simple_prompt:
274 273 # Fall back to plain non-interactive output for tests.
275 274 # This is very limited.
276 275 def prompt():
277 276 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
278 277 lines = [input(prompt_text)]
279 278 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
280 279 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
281 280 lines.append( input(prompt_continuation) )
282 281 return '\n'.join(lines)
283 282 self.prompt_for_code = prompt
284 283 return
285 284
286 285 # Set up keyboard shortcuts
287 286 key_bindings = create_ipython_shortcuts(self)
288 287
289 288 # Pre-populate history from IPython's history database
290 289 history = InMemoryHistory()
291 290 last_cell = u""
292 291 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
293 292 include_latest=True):
294 293 # Ignore blank lines and consecutive duplicates
295 294 cell = cell.rstrip()
296 295 if cell and (cell != last_cell):
297 296 history.append_string(cell)
298 297 last_cell = cell
299 298
300 299 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
301 300 self.style = DynamicStyle(lambda: self._style)
302 301
303 302 editing_mode = getattr(EditingMode, self.editing_mode.upper())
304 303
305 304 self.pt_app = PromptSession(
306 305 editing_mode=editing_mode,
307 306 key_bindings=key_bindings,
308 307 history=history,
309 308 completer=IPythonPTCompleter(shell=self),
310 309 enable_history_search = self.enable_history_search,
311 310 style=self.style,
312 311 include_default_pygments_style=False,
313 312 mouse_support=self.mouse_support,
314 313 enable_open_in_editor=self.extra_open_editor_shortcuts,
315 314 color_depth=self.color_depth,
316 315 **self._extra_prompt_options())
317 316
318 317 def _make_style_from_name_or_cls(self, name_or_cls):
319 318 """
320 319 Small wrapper that make an IPython compatible style from a style name
321 320
322 321 We need that to add style for prompt ... etc.
323 322 """
324 323 style_overrides = {}
325 324 if name_or_cls == 'legacy':
326 325 legacy = self.colors.lower()
327 326 if legacy == 'linux':
328 327 style_cls = get_style_by_name('monokai')
329 328 style_overrides = _style_overrides_linux
330 329 elif legacy == 'lightbg':
331 330 style_overrides = _style_overrides_light_bg
332 331 style_cls = get_style_by_name('pastie')
333 332 elif legacy == 'neutral':
334 333 # The default theme needs to be visible on both a dark background
335 334 # and a light background, because we can't tell what the terminal
336 335 # looks like. These tweaks to the default theme help with that.
337 336 style_cls = get_style_by_name('default')
338 337 style_overrides.update({
339 338 Token.Number: '#007700',
340 339 Token.Operator: 'noinherit',
341 340 Token.String: '#BB6622',
342 341 Token.Name.Function: '#2080D0',
343 342 Token.Name.Class: 'bold #2080D0',
344 343 Token.Name.Namespace: 'bold #2080D0',
345 344 Token.Prompt: '#009900',
346 345 Token.PromptNum: '#ansibrightgreen bold',
347 346 Token.OutPrompt: '#990000',
348 347 Token.OutPromptNum: '#ansibrightred bold',
349 348 })
350 349
351 350 # Hack: Due to limited color support on the Windows console
352 351 # the prompt colors will be wrong without this
353 352 if os.name == 'nt':
354 353 style_overrides.update({
355 354 Token.Prompt: '#ansidarkgreen',
356 355 Token.PromptNum: '#ansigreen bold',
357 356 Token.OutPrompt: '#ansidarkred',
358 357 Token.OutPromptNum: '#ansired bold',
359 358 })
360 359 elif legacy =='nocolor':
361 360 style_cls=_NoStyle
362 361 style_overrides = {}
363 362 else :
364 363 raise ValueError('Got unknown colors: ', legacy)
365 364 else :
366 365 if isinstance(name_or_cls, str):
367 366 style_cls = get_style_by_name(name_or_cls)
368 367 else:
369 368 style_cls = name_or_cls
370 369 style_overrides = {
371 370 Token.Prompt: '#009900',
372 371 Token.PromptNum: '#ansibrightgreen bold',
373 372 Token.OutPrompt: '#990000',
374 373 Token.OutPromptNum: '#ansibrightred bold',
375 374 }
376 375 style_overrides.update(self.highlighting_style_overrides)
377 376 style = merge_styles([
378 377 style_from_pygments_cls(style_cls),
379 378 style_from_pygments_dict(style_overrides),
380 379 ])
381 380
382 381 return style
383 382
384 383 @property
385 384 def pt_complete_style(self):
386 385 return {
387 386 'multicolumn': CompleteStyle.MULTI_COLUMN,
388 387 'column': CompleteStyle.COLUMN,
389 388 'readlinelike': CompleteStyle.READLINE_LIKE,
390 389 }[self.display_completions]
391 390
392 391 @property
393 392 def color_depth(self):
394 393 return (ColorDepth.TRUE_COLOR if self.true_color else None)
395 394
396 395 def _extra_prompt_options(self):
397 396 """
398 397 Return the current layout option for the current Terminal InteractiveShell
399 398 """
400 399 def get_message():
401 400 return PygmentsTokens(self.prompts.in_prompt_tokens())
402 401
403 402 return {
404 403 'complete_in_thread': False,
405 404 'lexer':IPythonPTLexer(),
406 405 'reserve_space_for_menu':self.space_for_menu,
407 406 'message': get_message,
408 407 'prompt_continuation': (
409 408 lambda width, lineno, is_soft_wrap:
410 409 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
411 410 'multiline': True,
412 411 'complete_style': self.pt_complete_style,
413 412
414 413 # Highlight matching brackets, but only when this setting is
415 414 # enabled, and only when the DEFAULT_BUFFER has the focus.
416 415 'input_processors': [ConditionalProcessor(
417 416 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
418 417 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
419 418 Condition(lambda: self.highlight_matching_brackets))],
420 419 'inputhook': self.inputhook,
421 420 }
422 421
423 422 def prompt_for_code(self):
424 423 if self.rl_next_input:
425 424 default = self.rl_next_input
426 425 self.rl_next_input = None
427 426 else:
428 427 default = ''
429 428
430 429 with patch_stdout(raw=True):
431 430 text = self.pt_app.prompt(
432 431 default=default,
433 432 # pre_run=self.pre_prompt,# reset_current_buffer=True,
434 433 **self._extra_prompt_options())
435 434 return text
436 435
437 436 def enable_win_unicode_console(self):
438 437 if sys.version_info >= (3, 6):
439 438 # Since PEP 528, Python uses the unicode APIs for the Windows
440 439 # console by default, so WUC shouldn't be needed.
441 440 return
442 441
443 442 import win_unicode_console
444 443 win_unicode_console.enable()
445 444
446 445 def init_io(self):
447 446 if sys.platform not in {'win32', 'cli'}:
448 447 return
449 448
450 449 self.enable_win_unicode_console()
451 450
452 451 import colorama
453 452 colorama.init()
454 453
455 454 # For some reason we make these wrappers around stdout/stderr.
456 455 # For now, we need to reset them so all output gets coloured.
457 456 # https://github.com/ipython/ipython/issues/8669
458 457 # io.std* are deprecated, but don't show our own deprecation warnings
459 458 # during initialization of the deprecated API.
460 459 with warnings.catch_warnings():
461 460 warnings.simplefilter('ignore', DeprecationWarning)
462 461 io.stdout = io.IOStream(sys.stdout)
463 462 io.stderr = io.IOStream(sys.stderr)
464 463
465 464 def init_magics(self):
466 465 super(TerminalInteractiveShell, self).init_magics()
467 466 self.register_magics(TerminalMagics)
468 467
469 468 def init_alias(self):
470 469 # The parent class defines aliases that can be safely used with any
471 470 # frontend.
472 471 super(TerminalInteractiveShell, self).init_alias()
473 472
474 473 # Now define aliases that only make sense on the terminal, because they
475 474 # need direct access to the console in a way that we can't emulate in
476 475 # GUI or web frontend
477 476 if os.name == 'posix':
478 477 for cmd in ('clear', 'more', 'less', 'man'):
479 478 self.alias_manager.soft_define_alias(cmd, cmd)
480 479
481 480
482 481 def __init__(self, *args, **kwargs):
483 482 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
484 483 self.init_prompt_toolkit_cli()
485 484 self.init_term_title()
486 485 self.keep_running = True
487 486
488 487 self.debugger_history = InMemoryHistory()
489 488
490 489 def ask_exit(self):
491 490 self.keep_running = False
492 491
493 492 rl_next_input = None
494 493
495 494 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
496 495
497 496 if display_banner is not DISPLAY_BANNER_DEPRECATED:
498 497 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
499 498
500 499 self.keep_running = True
501 500 while self.keep_running:
502 501 print(self.separate_in, end='')
503 502
504 503 try:
505 504 code = self.prompt_for_code()
506 505 except EOFError:
507 506 if (not self.confirm_exit) \
508 507 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
509 508 self.ask_exit()
510 509
511 510 else:
512 511 if code:
513 512 self.run_cell(code, store_history=True)
514 513
515 514 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
516 515 # An extra layer of protection in case someone mashing Ctrl-C breaks
517 516 # out of our internal code.
518 517 if display_banner is not DISPLAY_BANNER_DEPRECATED:
519 518 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
520 519 while True:
521 520 try:
522 521 self.interact()
523 522 break
524 523 except KeyboardInterrupt as e:
525 524 print("\n%s escaped interact()\n" % type(e).__name__)
526 525 finally:
527 526 # An interrupt during the eventloop will mess up the
528 527 # internal state of the prompt_toolkit library.
529 528 # Stopping the eventloop fixes this, see
530 529 # https://github.com/ipython/ipython/pull/9867
531 530 if hasattr(self, '_eventloop'):
532 531 self._eventloop.stop()
533 532
534 533 _inputhook = None
535 534 def inputhook(self, context):
536 535 if self._inputhook is not None:
537 536 self._inputhook(context)
538 537
539 538 active_eventloop = None
540 539 def enable_gui(self, gui=None):
541 540 if gui:
542 541 self.active_eventloop, self._inputhook =\
543 542 get_inputhook_name_and_func(gui)
544 543 else:
545 544 self.active_eventloop = self._inputhook = None
546 545
547 546 # Run !system commands directly, not through pipes, so terminal programs
548 547 # work correctly.
549 548 system = InteractiveShell.system_raw
550 549
551 550 def auto_rewrite_input(self, cmd):
552 551 """Overridden from the parent class to use fancy rewriting prompt"""
553 552 if not self.show_rewritten_input:
554 553 return
555 554
556 555 tokens = self.prompts.rewrite_prompt_tokens()
557 556 if self.pt_app:
558 557 print_formatted_text(PygmentsTokens(tokens), end='',
559 558 style=self.pt_app.app.style)
560 559 print(cmd)
561 560 else:
562 561 prompt = ''.join(s for t, s in tokens)
563 562 print(prompt, cmd, sep='')
564 563
565 564 _prompts_before = None
566 565 def switch_doctest_mode(self, mode):
567 566 """Switch prompts to classic for %doctest_mode"""
568 567 if mode:
569 568 self._prompts_before = self.prompts
570 569 self.prompts = ClassicPrompts(self)
571 570 elif self._prompts_before:
572 571 self.prompts = self._prompts_before
573 572 self._prompts_before = None
574 573 # self._update_layout()
575 574
576 575
577 576 InteractiveShellABC.register(TerminalInteractiveShell)
578 577
579 578 if __name__ == '__main__':
580 579 TerminalInteractiveShell.instance().interact()
@@ -1,273 +1,274 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 from typing import Callable
13 13
14 14
15 15 from prompt_toolkit.application.current import get_app
16 16 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
17 17 from prompt_toolkit.filters import (has_focus, has_selection, Condition,
18 18 vi_insert_mode, emacs_insert_mode, has_completions, vi_mode)
19 19 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
20 20 from prompt_toolkit.key_binding import KeyBindings
21 21
22 22 from IPython.utils.decorators import undoc
23 23
24 24 @undoc
25 25 @Condition
26 26 def cursor_in_leading_ws():
27 27 before = get_app().current_buffer.document.current_line_before_cursor
28 28 return (not before) or before.isspace()
29 29
30 30
31 31 def create_ipython_shortcuts(shell):
32 32 """Set up the prompt_toolkit keyboard shortcuts for IPython"""
33 33
34 34 kb = KeyBindings()
35 35 insert_mode = vi_insert_mode | emacs_insert_mode
36 36
37 37 if getattr(shell, 'handle_return', None):
38 38 return_handler = shell.handle_return(shell)
39 39 else:
40 40 return_handler = newline_or_execute_outer(shell)
41 41
42 42 kb.add('enter', filter=(has_focus(DEFAULT_BUFFER)
43 43 & ~has_selection
44 44 & insert_mode
45 45 ))(return_handler)
46 46
47 47 def reformat_and_execute(event):
48 48 reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell)
49 49 event.current_buffer.validate_and_handle()
50 50
51 51 kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER)
52 52 & ~has_selection
53 53 & insert_mode
54 54 ))(reformat_and_execute)
55 55
56 56 kb.add('c-\\')(force_exit)
57 57
58 58 kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
59 59 )(previous_history_or_previous_completion)
60 60
61 61 kb.add('c-n', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
62 62 )(next_history_or_next_completion)
63 63
64 64 kb.add('c-g', filter=(has_focus(DEFAULT_BUFFER) & has_completions)
65 65 )(dismiss_completion)
66 66
67 67 kb.add('c-c', filter=has_focus(DEFAULT_BUFFER))(reset_buffer)
68 68
69 69 kb.add('c-c', filter=has_focus(SEARCH_BUFFER))(reset_search_buffer)
70 70
71 71 supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP'))
72 72 kb.add('c-z', filter=supports_suspend)(suspend_to_bg)
73 73
74 74 # Ctrl+I == Tab
75 75 kb.add('tab', filter=(has_focus(DEFAULT_BUFFER)
76 76 & ~has_selection
77 77 & insert_mode
78 78 & cursor_in_leading_ws
79 79 ))(indent_buffer)
80 80 kb.add('c-o', filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode)
81 81 )(newline_autoindent_outer(shell.input_transformer_manager))
82 82
83 83 kb.add('f2', filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor)
84 84
85 85 if shell.display_completions == 'readlinelike':
86 86 kb.add('c-i', filter=(has_focus(DEFAULT_BUFFER)
87 87 & ~has_selection
88 88 & insert_mode
89 89 & ~cursor_in_leading_ws
90 90 ))(display_completions_like_readline)
91 91
92 92 if sys.platform == 'win32':
93 93 kb.add('c-v', filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
94 94
95 95 return kb
96 96
97 97
98 98 def reformat_text_before_cursor(buffer, document, shell):
99 99 text = buffer.delete_before_cursor(len(document.text[:document.cursor_position]))
100 100 try:
101 101 formatted_text = shell.reformat_handler(text)
102 102 buffer.insert_text(formatted_text)
103 103 except Exception as e:
104 104 buffer.insert_text(text)
105 105
106 106
107 107 def newline_or_execute_outer(shell):
108 108
109 109 def newline_or_execute(event):
110 110 """When the user presses return, insert a newline or execute the code."""
111 111 b = event.current_buffer
112 112 d = b.document
113 113
114 114 if b.complete_state:
115 115 cc = b.complete_state.current_completion
116 116 if cc:
117 117 b.apply_completion(cc)
118 118 else:
119 119 b.cancel_completion()
120 120 return
121 121
122 122 # If there's only one line, treat it as if the cursor is at the end.
123 123 # See https://github.com/ipython/ipython/issues/10425
124 124 if d.line_count == 1:
125 125 check_text = d.text
126 126 else:
127 127 check_text = d.text[:d.cursor_position]
128 128 status, indent = shell.check_complete(check_text)
129 129
130 130 # if all we have after the cursor is whitespace: reformat current text
131 131 # before cursor
132 if d.text[d.cursor_position:].isspace():
132 after_cursor = d.text[d.cursor_position:]
133 if not after_cursor.strip():
133 134 reformat_text_before_cursor(b, d, shell)
134 135 if not (d.on_last_line or
135 136 d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
136 137 ):
137 138 if shell.autoindent:
138 139 b.insert_text('\n' + indent)
139 140 else:
140 141 b.insert_text('\n')
141 142 return
142 143
143 144 if (status != 'incomplete') and b.accept_handler:
144 145 reformat_text_before_cursor(b, d, shell)
145 146 b.validate_and_handle()
146 147 else:
147 148 if shell.autoindent:
148 149 b.insert_text('\n' + indent)
149 150 else:
150 151 b.insert_text('\n')
151 152 return newline_or_execute
152 153
153 154
154 155 def previous_history_or_previous_completion(event):
155 156 """
156 157 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
157 158
158 159 If completer is open this still select previous completion.
159 160 """
160 161 event.current_buffer.auto_up()
161 162
162 163
163 164 def next_history_or_next_completion(event):
164 165 """
165 166 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
166 167
167 168 If completer is open this still select next completion.
168 169 """
169 170 event.current_buffer.auto_down()
170 171
171 172
172 173 def dismiss_completion(event):
173 174 b = event.current_buffer
174 175 if b.complete_state:
175 176 b.cancel_completion()
176 177
177 178
178 179 def reset_buffer(event):
179 180 b = event.current_buffer
180 181 if b.complete_state:
181 182 b.cancel_completion()
182 183 else:
183 184 b.reset()
184 185
185 186
186 187 def reset_search_buffer(event):
187 188 if event.current_buffer.document.text:
188 189 event.current_buffer.reset()
189 190 else:
190 191 event.app.layout.focus(DEFAULT_BUFFER)
191 192
192 193 def suspend_to_bg(event):
193 194 event.app.suspend_to_background()
194 195
195 196 def force_exit(event):
196 197 """
197 198 Force exit (with a non-zero return value)
198 199 """
199 200 sys.exit("Quit")
200 201
201 202 def indent_buffer(event):
202 203 event.current_buffer.insert_text(' ' * 4)
203 204
204 205 @undoc
205 206 def newline_with_copy_margin(event):
206 207 """
207 208 DEPRECATED since IPython 6.0
208 209
209 210 See :any:`newline_autoindent_outer` for a replacement.
210 211
211 212 Preserve margin and cursor position when using
212 213 Control-O to insert a newline in EMACS mode
213 214 """
214 215 warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
215 216 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
216 217 DeprecationWarning, stacklevel=2)
217 218
218 219 b = event.current_buffer
219 220 cursor_start_pos = b.document.cursor_position_col
220 221 b.newline(copy_margin=True)
221 222 b.cursor_up(count=1)
222 223 cursor_end_pos = b.document.cursor_position_col
223 224 if cursor_start_pos != cursor_end_pos:
224 225 pos_diff = cursor_start_pos - cursor_end_pos
225 226 b.cursor_right(count=pos_diff)
226 227
227 228 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
228 229 """
229 230 Return a function suitable for inserting a indented newline after the cursor.
230 231
231 232 Fancier version of deprecated ``newline_with_copy_margin`` which should
232 233 compute the correct indentation of the inserted line. That is to say, indent
233 234 by 4 extra space after a function definition, class definition, context
234 235 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
235 236 """
236 237
237 238 def newline_autoindent(event):
238 239 """insert a newline after the cursor indented appropriately."""
239 240 b = event.current_buffer
240 241 d = b.document
241 242
242 243 if b.complete_state:
243 244 b.cancel_completion()
244 245 text = d.text[:d.cursor_position] + '\n'
245 246 _, indent = inputsplitter.check_complete(text)
246 247 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
247 248
248 249 return newline_autoindent
249 250
250 251
251 252 def open_input_in_editor(event):
252 253 event.app.current_buffer.tempfile_suffix = ".py"
253 254 event.app.current_buffer.open_in_editor()
254 255
255 256
256 257 if sys.platform == 'win32':
257 258 from IPython.core.error import TryNext
258 259 from IPython.lib.clipboard import (ClipboardEmpty,
259 260 win32_clipboard_get,
260 261 tkinter_clipboard_get)
261 262
262 263 @undoc
263 264 def win_paste(event):
264 265 try:
265 266 text = win32_clipboard_get()
266 267 except TryNext:
267 268 try:
268 269 text = tkinter_clipboard_get()
269 270 except (TryNext, ClipboardEmpty):
270 271 return
271 272 except ClipboardEmpty:
272 273 return
273 274 event.current_buffer.insert_text(text.replace('\t', ' ' * 4))
General Comments 0
You need to be logged in to leave comments. Login now