##// END OF EJS Templates
Fix interact() and mainloop() for backward compat.
Matthias Bussonnier -
Show More
@@ -1,589 +1,597
1 1 """IPython terminal interface using prompt_toolkit"""
2 2 from __future__ import print_function
3 3
4 4 import os
5 5 import sys
6 6 import signal
7 7 from warnings import warn
8 8
9 9 from IPython.core.error import TryNext
10 10 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
11 11 from IPython.utils.py3compat import PY3, cast_unicode_py2, input
12 12 from IPython.utils.terminal import toggle_set_term_title, set_term_title
13 13 from IPython.utils.process import abbrev_cwd
14 14 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum
15 15
16 16 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode
17 17 from prompt_toolkit.filters import (HasFocus, HasSelection, Condition,
18 18 ViInsertMode, EmacsInsertMode, IsDone, HasCompletions)
19 19 from prompt_toolkit.filters.cli import ViMode
20 20 from prompt_toolkit.history import InMemoryHistory
21 21 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout
22 22 from prompt_toolkit.interface import CommandLineInterface
23 23 from prompt_toolkit.key_binding.manager import KeyBindingManager
24 24 from prompt_toolkit.keys import Keys
25 25 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
26 26 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
27 27 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
28 28
29 29 from pygments.styles import get_style_by_name, get_all_styles
30 30 from pygments.token import Token
31 31
32 32 from .debugger import TerminalPdb, Pdb
33 33 from .magics import TerminalMagics
34 34 from .pt_inputhooks import get_inputhook_func
35 35 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
36 36 from .ptutils import IPythonPTCompleter, IPythonPTLexer
37 37
38 DISPLAY_BANNER_DEPRECATED = object()
39
38 40
39 41 def get_default_editor():
40 42 try:
41 43 ed = os.environ['EDITOR']
42 44 if not PY3:
43 45 ed = ed.decode()
44 46 return ed
45 47 except KeyError:
46 48 pass
47 49 except UnicodeError:
48 50 warn("$EDITOR environment variable is not pure ASCII. Using platform "
49 51 "default editor.")
50 52
51 53 if os.name == 'posix':
52 54 return 'vi' # the only one guaranteed to be there!
53 55 else:
54 56 return 'notepad' # same in Windows!
55 57
56 58
57 59 if sys.stdin and sys.stdout and sys.stderr:
58 60 _is_tty = (sys.stdin.isatty()) and (sys.stdout.isatty()) and (sys.stderr.isatty())
59 61 else:
60 62 _is_tty = False
61 63
62 64
63 65 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
64 66
65 67 class TerminalInteractiveShell(InteractiveShell):
66 68 colors_force = True
67 69
68 70 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
69 71 'to reserve for the completion menu'
70 72 ).tag(config=True)
71 73
72 74 def _space_for_menu_changed(self, old, new):
73 75 self._update_layout()
74 76
75 77 pt_cli = None
76 78 debugger_history = None
77 79
78 80 simple_prompt = Bool(_use_simple_prompt,
79 81 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
80 82
81 83 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
82 84 IPython own testing machinery, and emacs inferior-shell integration through elpy.
83 85
84 86 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
85 87 environment variable is set, or the current terminal is not a tty.
86 88
87 89 """
88 90 ).tag(config=True)
89 91
90 92 @property
91 93 def debugger_cls(self):
92 94 return Pdb if self.simple_prompt else TerminalPdb
93 95
94 96 autoedit_syntax = Bool(False,
95 97 help="auto editing of files with syntax errors.",
96 98 ).tag(config=True)
97 99
98 100
99 101 confirm_exit = Bool(True,
100 102 help="""
101 103 Set to confirm when you try to exit IPython with an EOF (Control-D
102 104 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
103 105 you can force a direct exit without any confirmation.""",
104 106 ).tag(config=True)
105 107
106 108 editing_mode = Unicode('emacs',
107 109 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
108 110 ).tag(config=True)
109 111
110 112 mouse_support = Bool(False,
111 113 help="Enable mouse support in the prompt"
112 114 ).tag(config=True)
113 115
114 116 highlighting_style = Unicode('default',
115 117 help="The name of a Pygments style to use for syntax highlighting: \n %s" % ', '.join(get_all_styles())
116 118 ).tag(config=True)
117 119
118 120
119 121 @observe('highlighting_style')
120 122 def _highlighting_style_changed(self, change):
121 123 self._style = self._make_style_from_name(self.highlighting_style)
122 124
123 125 highlighting_style_overrides = Dict(
124 126 help="Override highlighting format for specific tokens"
125 127 ).tag(config=True)
126 128
127 129 editor = Unicode(get_default_editor(),
128 130 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
129 131 ).tag(config=True)
130 132
131 133 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
132 134
133 135 prompts = Instance(Prompts)
134 136
135 137 @default('prompts')
136 138 def _prompts_default(self):
137 139 return self.prompts_class(self)
138 140
139 141 @observe('prompts')
140 142 def _(self, change):
141 143 self._update_layout()
142 144
143 145 @default('displayhook_class')
144 146 def _displayhook_class_default(self):
145 147 return RichPromptDisplayHook
146 148
147 149 term_title = Bool(True,
148 150 help="Automatically set the terminal title"
149 151 ).tag(config=True)
150 152
151 153 display_completions_in_columns = Bool(None,
152 154 help="Display a multi column completion menu.", allow_none=True
153 155 ).tag(config=True)
154 156
155 157 @observe('display_completions_in_columns')
156 158 def _display_completions_in_columns_changed(self, new):
157 159 raise DeprecationWarning("The `display_completions_in_columns` Boolean has been replaced by the enum `display_completions`"
158 160 "with the following acceptable value: 'column', 'multicolumn','readlinelike'. ")
159 161
160 162 display_completions = Enum(('column', 'multicolumn','readlinelike'), default_value='multicolumn').tag(config=True)
161 163
162 164 highlight_matching_brackets = Bool(True,
163 165 help="Highlight matching brackets .",
164 166 ).tag(config=True)
165 167
166 168 @observe('term_title')
167 169 def init_term_title(self, change=None):
168 170 # Enable or disable the terminal title.
169 171 if self.term_title:
170 172 toggle_set_term_title(True)
171 173 set_term_title('IPython: ' + abbrev_cwd())
172 174 else:
173 175 toggle_set_term_title(False)
174 176
175 177 def init_display_formatter(self):
176 178 super(TerminalInteractiveShell, self).init_display_formatter()
177 179 # terminal only supports plain text
178 180 self.display_formatter.active_types = ['text/plain']
179 181
180 182 def init_prompt_toolkit_cli(self):
181 183 self._app = None
182 184 if self.simple_prompt:
183 185 # Fall back to plain non-interactive output for tests.
184 186 # This is very limited, and only accepts a single line.
185 187 def prompt():
186 188 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
187 189 self.prompt_for_code = prompt
188 190 return
189 191
190 192 kbmanager = KeyBindingManager.for_prompt()
191 193 insert_mode = ViInsertMode() | EmacsInsertMode()
192 194 # Ctrl+J == Enter, seemingly
193 195 @kbmanager.registry.add_binding(Keys.ControlJ,
194 196 filter=(HasFocus(DEFAULT_BUFFER)
195 197 & ~HasSelection()
196 198 & insert_mode
197 199 ))
198 200 def _(event):
199 201 b = event.current_buffer
200 202 d = b.document
201 203
202 204 if b.complete_state:
203 205 cc = b.complete_state.current_completion
204 206 if cc:
205 207 b.apply_completion(cc)
206 208 else:
207 209 b.cancel_completion()
208 210 return
209 211
210 212 if not (d.on_last_line or d.cursor_position_row >= d.line_count
211 213 - d.empty_line_count_at_the_end()):
212 214 b.newline()
213 215 return
214 216
215 217 status, indent = self.input_splitter.check_complete(d.text + '\n')
216 218
217 219 if (status != 'incomplete') and b.accept_action.is_returnable:
218 220 b.accept_action.validate_and_handle(event.cli, b)
219 221 else:
220 222 b.insert_text('\n' + (' ' * (indent or 0)))
221 223
222 224 @kbmanager.registry.add_binding(Keys.ControlP, filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER)))
223 225 def _previous_history_or_previous_completion(event):
224 226 """
225 227 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
226 228
227 229 If completer is open this still select previous completion.
228 230 """
229 231 event.current_buffer.auto_up()
230 232
231 233 @kbmanager.registry.add_binding(Keys.ControlN, filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER)))
232 234 def _next_history_or_next_completion(event):
233 235 """
234 236 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
235 237
236 238 If completer is open this still select next completion.
237 239 """
238 240 event.current_buffer.auto_down()
239 241
240 242 @kbmanager.registry.add_binding(Keys.ControlG, filter=(
241 243 HasFocus(DEFAULT_BUFFER) & HasCompletions()
242 244 ))
243 245 def _dismiss_completion(event):
244 246 b = event.current_buffer
245 247 if b.complete_state:
246 248 b.cancel_completion()
247 249
248 250 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER))
249 251 def _reset_buffer(event):
250 252 b = event.current_buffer
251 253 if b.complete_state:
252 254 b.cancel_completion()
253 255 else:
254 256 b.reset()
255 257
256 258 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER))
257 259 def _reset_search_buffer(event):
258 260 if event.current_buffer.document.text:
259 261 event.current_buffer.reset()
260 262 else:
261 263 event.cli.push_focus(DEFAULT_BUFFER)
262 264
263 265 supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
264 266
265 267 @kbmanager.registry.add_binding(Keys.ControlZ, filter=supports_suspend)
266 268 def _suspend_to_bg(event):
267 269 event.cli.suspend_to_background()
268 270
269 271 @Condition
270 272 def cursor_in_leading_ws(cli):
271 273 before = cli.application.buffer.document.current_line_before_cursor
272 274 return (not before) or before.isspace()
273 275
274 276 # Ctrl+I == Tab
275 277 @kbmanager.registry.add_binding(Keys.ControlI,
276 278 filter=(HasFocus(DEFAULT_BUFFER)
277 279 & ~HasSelection()
278 280 & insert_mode
279 281 & cursor_in_leading_ws
280 282 ))
281 283 def _indent_buffer(event):
282 284 event.current_buffer.insert_text(' ' * 4)
283 285
284 286
285 287 if self.display_completions == 'readlinelike':
286 288 @kbmanager.registry.add_binding(Keys.ControlI,
287 289 filter=(HasFocus(DEFAULT_BUFFER)
288 290 & ~HasSelection()
289 291 & insert_mode
290 292 & ~cursor_in_leading_ws
291 293 ))
292 294 def _disaply_compl(ev):
293 295 display_completions_like_readline(ev)
294 296
295 297
296 298 if sys.platform == 'win32':
297 299 from IPython.lib.clipboard import (ClipboardEmpty,
298 300 win32_clipboard_get, tkinter_clipboard_get)
299 301 @kbmanager.registry.add_binding(Keys.ControlV,
300 302 filter=(HasFocus(DEFAULT_BUFFER) & ~ViMode()))
301 303 def _paste(event):
302 304 try:
303 305 text = win32_clipboard_get()
304 306 except TryNext:
305 307 try:
306 308 text = tkinter_clipboard_get()
307 309 except (TryNext, ClipboardEmpty):
308 310 return
309 311 except ClipboardEmpty:
310 312 return
311 313 event.current_buffer.insert_text(text.replace('\t', ' ' * 4))
312 314
313 315 # Pre-populate history from IPython's history database
314 316 history = InMemoryHistory()
315 317 last_cell = u""
316 318 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
317 319 include_latest=True):
318 320 # Ignore blank lines and consecutive duplicates
319 321 cell = cell.rstrip()
320 322 if cell and (cell != last_cell):
321 323 history.append(cell)
322 324
323 325 self._style = self._make_style_from_name(self.highlighting_style)
324 326 style = DynamicStyle(lambda: self._style)
325 327
326 328 editing_mode = getattr(EditingMode, self.editing_mode.upper())
327 329
328 330 self._app = create_prompt_application(
329 331 editing_mode=editing_mode,
330 332 key_bindings_registry=kbmanager.registry,
331 333 history=history,
332 334 completer=IPythonPTCompleter(self.Completer),
333 335 enable_history_search=True,
334 336 style=style,
335 337 mouse_support=self.mouse_support,
336 338 **self._layout_options()
337 339 )
338 340 self._eventloop = create_eventloop(self.inputhook)
339 341 self.pt_cli = CommandLineInterface(self._app, eventloop=self._eventloop)
340 342
341 343 def _make_style_from_name(self, name):
342 344 """
343 345 Small wrapper that make an IPython compatible style from a style name
344 346
345 347 We need that to add style for prompt ... etc.
346 348 """
347 349 style_cls = get_style_by_name(name)
348 350 style_overrides = {
349 351 Token.Prompt: '#009900',
350 352 Token.PromptNum: '#00ff00 bold',
351 353 Token.OutPrompt: '#990000',
352 354 Token.OutPromptNum: '#ff0000 bold',
353 355 }
354 356 if name == 'default':
355 357 style_cls = get_style_by_name('default')
356 358 # The default theme needs to be visible on both a dark background
357 359 # and a light background, because we can't tell what the terminal
358 360 # looks like. These tweaks to the default theme help with that.
359 361 style_overrides.update({
360 362 Token.Number: '#007700',
361 363 Token.Operator: 'noinherit',
362 364 Token.String: '#BB6622',
363 365 Token.Name.Function: '#2080D0',
364 366 Token.Name.Class: 'bold #2080D0',
365 367 Token.Name.Namespace: 'bold #2080D0',
366 368 })
367 369 style_overrides.update(self.highlighting_style_overrides)
368 370 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
369 371 style_dict=style_overrides)
370 372
371 373 return style
372 374
373 375 def _layout_options(self):
374 376 """
375 377 Return the current layout option for the current Terminal InteractiveShell
376 378 """
377 379 return {
378 380 'lexer':IPythonPTLexer(),
379 381 'reserve_space_for_menu':self.space_for_menu,
380 382 'get_prompt_tokens':self.prompts.in_prompt_tokens,
381 383 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
382 384 'multiline':True,
383 385 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
384 386
385 387 # Highlight matching brackets, but only when this setting is
386 388 # enabled, and only when the DEFAULT_BUFFER has the focus.
387 389 'extra_input_processors': [ConditionalProcessor(
388 390 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
389 391 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
390 392 Condition(lambda cli: self.highlight_matching_brackets))],
391 393 }
392 394
393 395 def _update_layout(self):
394 396 """
395 397 Ask for a re computation of the application layout, if for example ,
396 398 some configuration options have changed.
397 399 """
398 400 if getattr(self, '._app', None):
399 401 self._app.layout = create_prompt_layout(**self._layout_options())
400 402
401 403 def prompt_for_code(self):
402 404 document = self.pt_cli.run(
403 405 pre_run=self.pre_prompt, reset_current_buffer=True)
404 406 return document.text
405 407
406 408 def init_io(self):
407 409 if sys.platform not in {'win32', 'cli'}:
408 410 return
409 411
410 412 import win_unicode_console
411 413 import colorama
412 414
413 415 win_unicode_console.enable()
414 416 colorama.init()
415 417
416 418 # For some reason we make these wrappers around stdout/stderr.
417 419 # For now, we need to reset them so all output gets coloured.
418 420 # https://github.com/ipython/ipython/issues/8669
419 421 from IPython.utils import io
420 422 io.stdout = io.IOStream(sys.stdout)
421 423 io.stderr = io.IOStream(sys.stderr)
422 424
423 425 def init_magics(self):
424 426 super(TerminalInteractiveShell, self).init_magics()
425 427 self.register_magics(TerminalMagics)
426 428
427 429 def init_alias(self):
428 430 # The parent class defines aliases that can be safely used with any
429 431 # frontend.
430 432 super(TerminalInteractiveShell, self).init_alias()
431 433
432 434 # Now define aliases that only make sense on the terminal, because they
433 435 # need direct access to the console in a way that we can't emulate in
434 436 # GUI or web frontend
435 437 if os.name == 'posix':
436 438 for cmd in ['clear', 'more', 'less', 'man']:
437 439 self.alias_manager.soft_define_alias(cmd, cmd)
438 440
439 441
440 442 def __init__(self, *args, **kwargs):
441 443 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
442 444 self.init_prompt_toolkit_cli()
443 445 self.init_term_title()
444 446 self.keep_running = True
445 447
446 448 self.debugger_history = InMemoryHistory()
447 449
448 450 def ask_exit(self):
449 451 self.keep_running = False
450 452
451 453 rl_next_input = None
452 454
453 455 def pre_prompt(self):
454 456 if self.rl_next_input:
455 457 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
456 458 self.rl_next_input = None
457 459
458 def interact(self):
460 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
461
462 if display_banner is not DISPLAY_BANNER_DEPRECATED:
463 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
464
459 465 while self.keep_running:
460 466 print(self.separate_in, end='')
461 467
462 468 try:
463 469 code = self.prompt_for_code()
464 470 except EOFError:
465 471 if (not self.confirm_exit) \
466 472 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
467 473 self.ask_exit()
468 474
469 475 else:
470 476 if code:
471 477 self.run_cell(code, store_history=True)
472 478 if self.autoedit_syntax and self.SyntaxTB.last_syntax_error:
473 479 self.edit_syntax_error()
474 480
475 def mainloop(self):
481 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
476 482 # An extra layer of protection in case someone mashing Ctrl-C breaks
477 483 # out of our internal code.
484 if display_banner is not DISPLAY_BANNER_DEPRECATED:
485 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
478 486 while True:
479 487 try:
480 488 self.interact()
481 489 break
482 490 except KeyboardInterrupt:
483 491 print("\nKeyboardInterrupt escaped interact()\n")
484 492
485 493 if hasattr(self, '_eventloop'):
486 494 self._eventloop.close()
487 495
488 496 _inputhook = None
489 497 def inputhook(self, context):
490 498 if self._inputhook is not None:
491 499 self._inputhook(context)
492 500
493 501 def enable_gui(self, gui=None):
494 502 if gui:
495 503 self._inputhook = get_inputhook_func(gui)
496 504 else:
497 505 self._inputhook = None
498 506
499 507 # Methods to support auto-editing of SyntaxErrors:
500 508
501 509 def edit_syntax_error(self):
502 510 """The bottom half of the syntax error handler called in the main loop.
503 511
504 512 Loop until syntax error is fixed or user cancels.
505 513 """
506 514
507 515 while self.SyntaxTB.last_syntax_error:
508 516 # copy and clear last_syntax_error
509 517 err = self.SyntaxTB.clear_err_state()
510 518 if not self._should_recompile(err):
511 519 return
512 520 try:
513 521 # may set last_syntax_error again if a SyntaxError is raised
514 522 self.safe_execfile(err.filename, self.user_ns)
515 523 except:
516 524 self.showtraceback()
517 525 else:
518 526 try:
519 527 with open(err.filename) as f:
520 528 # This should be inside a display_trap block and I
521 529 # think it is.
522 530 sys.displayhook(f.read())
523 531 except:
524 532 self.showtraceback()
525 533
526 534 def _should_recompile(self, e):
527 535 """Utility routine for edit_syntax_error"""
528 536
529 537 if e.filename in ('<ipython console>', '<input>', '<string>',
530 538 '<console>', '<BackgroundJob compilation>',
531 539 None):
532 540 return False
533 541 try:
534 542 if (self.autoedit_syntax and
535 543 not self.ask_yes_no(
536 544 'Return to editor to correct syntax error? '
537 545 '[Y/n] ', 'y')):
538 546 return False
539 547 except EOFError:
540 548 return False
541 549
542 550 def int0(x):
543 551 try:
544 552 return int(x)
545 553 except TypeError:
546 554 return 0
547 555
548 556 # always pass integer line and offset values to editor hook
549 557 try:
550 558 self.hooks.fix_error_editor(e.filename,
551 559 int0(e.lineno), int0(e.offset),
552 560 e.msg)
553 561 except TryNext:
554 562 warn('Could not open editor')
555 563 return False
556 564 return True
557 565
558 566 # Run !system commands directly, not through pipes, so terminal programs
559 567 # work correctly.
560 568 system = InteractiveShell.system_raw
561 569
562 570 def auto_rewrite_input(self, cmd):
563 571 """Overridden from the parent class to use fancy rewriting prompt"""
564 572 if not self.show_rewritten_input:
565 573 return
566 574
567 575 tokens = self.prompts.rewrite_prompt_tokens()
568 576 if self.pt_cli:
569 577 self.pt_cli.print_tokens(tokens)
570 578 print(cmd)
571 579 else:
572 580 prompt = ''.join(s for t, s in tokens)
573 581 print(prompt, cmd, sep='')
574 582
575 583 _prompts_before = None
576 584 def switch_doctest_mode(self, mode):
577 585 """Switch prompts to classic for %doctest_mode"""
578 586 if mode:
579 587 self._prompts_before = self.prompts
580 588 self.prompts = ClassicPrompts(self)
581 589 elif self._prompts_before:
582 590 self.prompts = self._prompts_before
583 591 self._prompts_before = None
584 592
585 593
586 594 InteractiveShellABC.register(TerminalInteractiveShell)
587 595
588 596 if __name__ == '__main__':
589 597 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now