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