##// END OF EJS Templates
Support multiline input in --simple-prompt (fixes: #9816)
Ximin Luo -
Show More
@@ -1,530 +1,537 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,
16 16 )
17 17
18 18 from prompt_toolkit.document import Document
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.history import InMemoryHistory
22 22 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output
23 23 from prompt_toolkit.interface import CommandLineInterface
24 24 from prompt_toolkit.key_binding.manager import KeyBindingManager
25 25 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
26 26 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
27 27
28 28 from pygments.styles import get_style_by_name, get_all_styles
29 29 from pygments.style import Style
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_name_and_func
35 35 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
36 36 from .ptutils import IPythonPTCompleter, IPythonPTLexer
37 37 from .shortcuts import register_ipython_shortcuts
38 38
39 39 DISPLAY_BANNER_DEPRECATED = object()
40 40
41 41
42 42 class _NoStyle(Style): pass
43 43
44 44
45 45
46 46 _style_overrides_light_bg = {
47 47 Token.Prompt: '#0000ff',
48 48 Token.PromptNum: '#0000ee bold',
49 49 Token.OutPrompt: '#cc0000',
50 50 Token.OutPromptNum: '#bb0000 bold',
51 51 }
52 52
53 53 _style_overrides_linux = {
54 54 Token.Prompt: '#00cc00',
55 55 Token.PromptNum: '#00bb00 bold',
56 56 Token.OutPrompt: '#cc0000',
57 57 Token.OutPromptNum: '#bb0000 bold',
58 58 }
59 59
60 60
61 61
62 62 def get_default_editor():
63 63 try:
64 64 return os.environ['EDITOR']
65 65 except KeyError:
66 66 pass
67 67 except UnicodeError:
68 68 warn("$EDITOR environment variable is not pure ASCII. Using platform "
69 69 "default editor.")
70 70
71 71 if os.name == 'posix':
72 72 return 'vi' # the only one guaranteed to be there!
73 73 else:
74 74 return 'notepad' # same in Windows!
75 75
76 76 # conservatively check for tty
77 77 # overridden streams can result in things like:
78 78 # - sys.stdin = None
79 79 # - no isatty method
80 80 for _name in ('stdin', 'stdout', 'stderr'):
81 81 _stream = getattr(sys, _name)
82 82 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
83 83 _is_tty = False
84 84 break
85 85 else:
86 86 _is_tty = True
87 87
88 88
89 89 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
90 90
91 91 class TerminalInteractiveShell(InteractiveShell):
92 92 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
93 93 'to reserve for the completion menu'
94 94 ).tag(config=True)
95 95
96 96 def _space_for_menu_changed(self, old, new):
97 97 self._update_layout()
98 98
99 99 pt_cli = None
100 100 debugger_history = None
101 101 _pt_app = None
102 102
103 103 simple_prompt = Bool(_use_simple_prompt,
104 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
104 help="""Use `raw_input` for the REPL, without completion and prompt colors.
105 105
106 106 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
107 107 IPython own testing machinery, and emacs inferior-shell integration through elpy.
108 108
109 109 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
110 110 environment variable is set, or the current terminal is not a tty."""
111 111 ).tag(config=True)
112 112
113 113 @property
114 114 def debugger_cls(self):
115 115 return Pdb if self.simple_prompt else TerminalPdb
116 116
117 117 confirm_exit = Bool(True,
118 118 help="""
119 119 Set to confirm when you try to exit IPython with an EOF (Control-D
120 120 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
121 121 you can force a direct exit without any confirmation.""",
122 122 ).tag(config=True)
123 123
124 124 editing_mode = Unicode('emacs',
125 125 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
126 126 ).tag(config=True)
127 127
128 128 mouse_support = Bool(False,
129 129 help="Enable mouse support in the prompt"
130 130 ).tag(config=True)
131 131
132 132 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
133 133 help="""The name or class of a Pygments style to use for syntax
134 134 highlighting: \n %s""" % ', '.join(get_all_styles())
135 135 ).tag(config=True)
136 136
137 137
138 138 @observe('highlighting_style')
139 139 @observe('colors')
140 140 def _highlighting_style_changed(self, change):
141 141 self.refresh_style()
142 142
143 143 def refresh_style(self):
144 144 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
145 145
146 146
147 147 highlighting_style_overrides = Dict(
148 148 help="Override highlighting format for specific tokens"
149 149 ).tag(config=True)
150 150
151 151 true_color = Bool(False,
152 152 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
153 153 "If your terminal supports true color, the following command "
154 154 "should print 'TRUECOLOR' in orange: "
155 155 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
156 156 ).tag(config=True)
157 157
158 158 editor = Unicode(get_default_editor(),
159 159 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
160 160 ).tag(config=True)
161 161
162 162 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
163 163
164 164 prompts = Instance(Prompts)
165 165
166 166 @default('prompts')
167 167 def _prompts_default(self):
168 168 return self.prompts_class(self)
169 169
170 170 @observe('prompts')
171 171 def _(self, change):
172 172 self._update_layout()
173 173
174 174 @default('displayhook_class')
175 175 def _displayhook_class_default(self):
176 176 return RichPromptDisplayHook
177 177
178 178 term_title = Bool(True,
179 179 help="Automatically set the terminal title"
180 180 ).tag(config=True)
181 181
182 182 term_title_format = Unicode("IPython: {cwd}",
183 183 help="Customize the terminal title format. This is a python format string. " +
184 184 "Available substitutions are: {cwd}."
185 185 ).tag(config=True)
186 186
187 187 display_completions = Enum(('column', 'multicolumn','readlinelike'),
188 188 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
189 189 "'readlinelike'. These options are for `prompt_toolkit`, see "
190 190 "`prompt_toolkit` documentation for more information."
191 191 ),
192 192 default_value='multicolumn').tag(config=True)
193 193
194 194 highlight_matching_brackets = Bool(True,
195 195 help="Highlight matching brackets.",
196 196 ).tag(config=True)
197 197
198 198 extra_open_editor_shortcuts = Bool(False,
199 199 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
200 200 "This is in addition to the F2 binding, which is always enabled."
201 201 ).tag(config=True)
202 202
203 203 handle_return = Any(None,
204 204 help="Provide an alternative handler to be called when the user presses "
205 205 "Return. This is an advanced option intended for debugging, which "
206 206 "may be changed or removed in later releases."
207 207 ).tag(config=True)
208 208
209 209 @observe('term_title')
210 210 def init_term_title(self, change=None):
211 211 # Enable or disable the terminal title.
212 212 if self.term_title:
213 213 toggle_set_term_title(True)
214 214 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
215 215 else:
216 216 toggle_set_term_title(False)
217 217
218 218 def init_display_formatter(self):
219 219 super(TerminalInteractiveShell, self).init_display_formatter()
220 220 # terminal only supports plain text
221 221 self.display_formatter.active_types = ['text/plain']
222 222 # disable `_ipython_display_`
223 223 self.display_formatter.ipython_display_formatter.enabled = False
224 224
225 225 def init_prompt_toolkit_cli(self):
226 226 if self.simple_prompt:
227 227 # Fall back to plain non-interactive output for tests.
228 228 # This is very limited, and only accepts a single line.
229 229 def prompt():
230 return input('In [%d]: ' % self.execution_count)
230 isp = self.input_splitter
231 prompt_text = 'In [%d]: ' % self.execution_count
232 prompt_continuation = ("%%%ds" % len(prompt_text)) % '...: '
233 while isp.push_accepts_more():
234 line = cast_unicode_py2(input(prompt_text))
235 isp.push(line)
236 prompt_text = prompt_continuation
237 return isp.source_reset()
231 238 self.prompt_for_code = prompt
232 239 return
233 240
234 241 # Set up keyboard shortcuts
235 242 kbmanager = KeyBindingManager.for_prompt(
236 243 enable_open_in_editor=self.extra_open_editor_shortcuts,
237 244 )
238 245 register_ipython_shortcuts(kbmanager.registry, self)
239 246
240 247 # Pre-populate history from IPython's history database
241 248 history = InMemoryHistory()
242 249 last_cell = u""
243 250 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
244 251 include_latest=True):
245 252 # Ignore blank lines and consecutive duplicates
246 253 cell = cell.rstrip()
247 254 if cell and (cell != last_cell):
248 255 history.append(cell)
249 256 last_cell = cell
250 257
251 258 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
252 259 self.style = DynamicStyle(lambda: self._style)
253 260
254 261 editing_mode = getattr(EditingMode, self.editing_mode.upper())
255 262
256 263 def patch_stdout(**kwargs):
257 264 return self.pt_cli.patch_stdout_context(**kwargs)
258 265
259 266 self._pt_app = create_prompt_application(
260 267 editing_mode=editing_mode,
261 268 key_bindings_registry=kbmanager.registry,
262 269 history=history,
263 270 completer=IPythonPTCompleter(shell=self,
264 271 patch_stdout=patch_stdout),
265 272 enable_history_search=True,
266 273 style=self.style,
267 274 mouse_support=self.mouse_support,
268 275 **self._layout_options()
269 276 )
270 277 self._eventloop = create_eventloop(self.inputhook)
271 278 self.pt_cli = CommandLineInterface(
272 279 self._pt_app, eventloop=self._eventloop,
273 280 output=create_output(true_color=self.true_color))
274 281
275 282 def _make_style_from_name_or_cls(self, name_or_cls):
276 283 """
277 284 Small wrapper that make an IPython compatible style from a style name
278 285
279 286 We need that to add style for prompt ... etc.
280 287 """
281 288 style_overrides = {}
282 289 if name_or_cls == 'legacy':
283 290 legacy = self.colors.lower()
284 291 if legacy == 'linux':
285 292 style_cls = get_style_by_name('monokai')
286 293 style_overrides = _style_overrides_linux
287 294 elif legacy == 'lightbg':
288 295 style_overrides = _style_overrides_light_bg
289 296 style_cls = get_style_by_name('pastie')
290 297 elif legacy == 'neutral':
291 298 # The default theme needs to be visible on both a dark background
292 299 # and a light background, because we can't tell what the terminal
293 300 # looks like. These tweaks to the default theme help with that.
294 301 style_cls = get_style_by_name('default')
295 302 style_overrides.update({
296 303 Token.Number: '#007700',
297 304 Token.Operator: 'noinherit',
298 305 Token.String: '#BB6622',
299 306 Token.Name.Function: '#2080D0',
300 307 Token.Name.Class: 'bold #2080D0',
301 308 Token.Name.Namespace: 'bold #2080D0',
302 309 Token.Prompt: '#009900',
303 310 Token.PromptNum: '#00ff00 bold',
304 311 Token.OutPrompt: '#990000',
305 312 Token.OutPromptNum: '#ff0000 bold',
306 313 })
307 314
308 315 # Hack: Due to limited color support on the Windows console
309 316 # the prompt colors will be wrong without this
310 317 if os.name == 'nt':
311 318 style_overrides.update({
312 319 Token.Prompt: '#ansidarkgreen',
313 320 Token.PromptNum: '#ansigreen bold',
314 321 Token.OutPrompt: '#ansidarkred',
315 322 Token.OutPromptNum: '#ansired bold',
316 323 })
317 324 elif legacy =='nocolor':
318 325 style_cls=_NoStyle
319 326 style_overrides = {}
320 327 else :
321 328 raise ValueError('Got unknown colors: ', legacy)
322 329 else :
323 330 if isinstance(name_or_cls, str):
324 331 style_cls = get_style_by_name(name_or_cls)
325 332 else:
326 333 style_cls = name_or_cls
327 334 style_overrides = {
328 335 Token.Prompt: '#009900',
329 336 Token.PromptNum: '#00ff00 bold',
330 337 Token.OutPrompt: '#990000',
331 338 Token.OutPromptNum: '#ff0000 bold',
332 339 }
333 340 style_overrides.update(self.highlighting_style_overrides)
334 341 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
335 342 style_dict=style_overrides)
336 343
337 344 return style
338 345
339 346 def _layout_options(self):
340 347 """
341 348 Return the current layout option for the current Terminal InteractiveShell
342 349 """
343 350 return {
344 351 'lexer':IPythonPTLexer(),
345 352 'reserve_space_for_menu':self.space_for_menu,
346 353 'get_prompt_tokens':self.prompts.in_prompt_tokens,
347 354 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
348 355 'multiline':True,
349 356 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
350 357
351 358 # Highlight matching brackets, but only when this setting is
352 359 # enabled, and only when the DEFAULT_BUFFER has the focus.
353 360 'extra_input_processors': [ConditionalProcessor(
354 361 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
355 362 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
356 363 Condition(lambda cli: self.highlight_matching_brackets))],
357 364 }
358 365
359 366 def _update_layout(self):
360 367 """
361 368 Ask for a re computation of the application layout, if for example ,
362 369 some configuration options have changed.
363 370 """
364 371 if self._pt_app:
365 372 self._pt_app.layout = create_prompt_layout(**self._layout_options())
366 373
367 374 def prompt_for_code(self):
368 375 document = self.pt_cli.run(
369 376 pre_run=self.pre_prompt, reset_current_buffer=True)
370 377 return document.text
371 378
372 379 def enable_win_unicode_console(self):
373 380 if sys.version_info >= (3, 6):
374 381 # Since PEP 528, Python uses the unicode APIs for the Windows
375 382 # console by default, so WUC shouldn't be needed.
376 383 return
377 384
378 385 import win_unicode_console
379 386 win_unicode_console.enable()
380 387
381 388 def init_io(self):
382 389 if sys.platform not in {'win32', 'cli'}:
383 390 return
384 391
385 392 self.enable_win_unicode_console()
386 393
387 394 import colorama
388 395 colorama.init()
389 396
390 397 # For some reason we make these wrappers around stdout/stderr.
391 398 # For now, we need to reset them so all output gets coloured.
392 399 # https://github.com/ipython/ipython/issues/8669
393 400 # io.std* are deprecated, but don't show our own deprecation warnings
394 401 # during initialization of the deprecated API.
395 402 with warnings.catch_warnings():
396 403 warnings.simplefilter('ignore', DeprecationWarning)
397 404 io.stdout = io.IOStream(sys.stdout)
398 405 io.stderr = io.IOStream(sys.stderr)
399 406
400 407 def init_magics(self):
401 408 super(TerminalInteractiveShell, self).init_magics()
402 409 self.register_magics(TerminalMagics)
403 410
404 411 def init_alias(self):
405 412 # The parent class defines aliases that can be safely used with any
406 413 # frontend.
407 414 super(TerminalInteractiveShell, self).init_alias()
408 415
409 416 # Now define aliases that only make sense on the terminal, because they
410 417 # need direct access to the console in a way that we can't emulate in
411 418 # GUI or web frontend
412 419 if os.name == 'posix':
413 420 for cmd in ['clear', 'more', 'less', 'man']:
414 421 self.alias_manager.soft_define_alias(cmd, cmd)
415 422
416 423
417 424 def __init__(self, *args, **kwargs):
418 425 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
419 426 self.init_prompt_toolkit_cli()
420 427 self.init_term_title()
421 428 self.keep_running = True
422 429
423 430 self.debugger_history = InMemoryHistory()
424 431
425 432 def ask_exit(self):
426 433 self.keep_running = False
427 434
428 435 rl_next_input = None
429 436
430 437 def pre_prompt(self):
431 438 if self.rl_next_input:
432 439 # We can't set the buffer here, because it will be reset just after
433 440 # this. Adding a callable to pre_run_callables does what we need
434 441 # after the buffer is reset.
435 442 s = self.rl_next_input
436 443 def set_doc():
437 444 self.pt_cli.application.buffer.document = Document(s)
438 445 if hasattr(self.pt_cli, 'pre_run_callables'):
439 446 self.pt_cli.pre_run_callables.append(set_doc)
440 447 else:
441 448 # Older version of prompt_toolkit; it's OK to set the document
442 449 # directly here.
443 450 set_doc()
444 451 self.rl_next_input = None
445 452
446 453 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
447 454
448 455 if display_banner is not DISPLAY_BANNER_DEPRECATED:
449 456 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
450 457
451 458 self.keep_running = True
452 459 while self.keep_running:
453 460 print(self.separate_in, end='')
454 461
455 462 try:
456 463 code = self.prompt_for_code()
457 464 except EOFError:
458 465 if (not self.confirm_exit) \
459 466 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
460 467 self.ask_exit()
461 468
462 469 else:
463 470 if code:
464 471 self.run_cell(code, store_history=True)
465 472
466 473 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
467 474 # An extra layer of protection in case someone mashing Ctrl-C breaks
468 475 # out of our internal code.
469 476 if display_banner is not DISPLAY_BANNER_DEPRECATED:
470 477 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
471 478 while True:
472 479 try:
473 480 self.interact()
474 481 break
475 482 except KeyboardInterrupt as e:
476 483 print("\n%s escaped interact()\n" % type(e).__name__)
477 484 finally:
478 485 # An interrupt during the eventloop will mess up the
479 486 # internal state of the prompt_toolkit library.
480 487 # Stopping the eventloop fixes this, see
481 488 # https://github.com/ipython/ipython/pull/9867
482 489 if hasattr(self, '_eventloop'):
483 490 self._eventloop.stop()
484 491
485 492 _inputhook = None
486 493 def inputhook(self, context):
487 494 if self._inputhook is not None:
488 495 self._inputhook(context)
489 496
490 497 active_eventloop = None
491 498 def enable_gui(self, gui=None):
492 499 if gui:
493 500 self.active_eventloop, self._inputhook =\
494 501 get_inputhook_name_and_func(gui)
495 502 else:
496 503 self.active_eventloop = self._inputhook = None
497 504
498 505 # Run !system commands directly, not through pipes, so terminal programs
499 506 # work correctly.
500 507 system = InteractiveShell.system_raw
501 508
502 509 def auto_rewrite_input(self, cmd):
503 510 """Overridden from the parent class to use fancy rewriting prompt"""
504 511 if not self.show_rewritten_input:
505 512 return
506 513
507 514 tokens = self.prompts.rewrite_prompt_tokens()
508 515 if self.pt_cli:
509 516 self.pt_cli.print_tokens(tokens)
510 517 print(cmd)
511 518 else:
512 519 prompt = ''.join(s for t, s in tokens)
513 520 print(prompt, cmd, sep='')
514 521
515 522 _prompts_before = None
516 523 def switch_doctest_mode(self, mode):
517 524 """Switch prompts to classic for %doctest_mode"""
518 525 if mode:
519 526 self._prompts_before = self.prompts
520 527 self.prompts = ClassicPrompts(self)
521 528 elif self._prompts_before:
522 529 self.prompts = self._prompts_before
523 530 self._prompts_before = None
524 531 self._update_layout()
525 532
526 533
527 534 InteractiveShellABC.register(TerminalInteractiveShell)
528 535
529 536 if __name__ == '__main__':
530 537 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now