##// END OF EJS Templates
simple-prompt: use prompts from self.prompts...
Ximin Luo -
Show More
@@ -1,537 +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 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 230 isp = self.input_splitter
231 prompt_text = 'In [%d]: ' % self.execution_count
232 prompt_continuation = ("%%%ds" % len(prompt_text)) % '...: '
231 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
232 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
233 233 while isp.push_accepts_more():
234 234 line = cast_unicode_py2(input(prompt_text))
235 235 isp.push(line)
236 236 prompt_text = prompt_continuation
237 237 return isp.source_reset()
238 238 self.prompt_for_code = prompt
239 239 return
240 240
241 241 # Set up keyboard shortcuts
242 242 kbmanager = KeyBindingManager.for_prompt(
243 243 enable_open_in_editor=self.extra_open_editor_shortcuts,
244 244 )
245 245 register_ipython_shortcuts(kbmanager.registry, self)
246 246
247 247 # Pre-populate history from IPython's history database
248 248 history = InMemoryHistory()
249 249 last_cell = u""
250 250 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
251 251 include_latest=True):
252 252 # Ignore blank lines and consecutive duplicates
253 253 cell = cell.rstrip()
254 254 if cell and (cell != last_cell):
255 255 history.append(cell)
256 256 last_cell = cell
257 257
258 258 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
259 259 self.style = DynamicStyle(lambda: self._style)
260 260
261 261 editing_mode = getattr(EditingMode, self.editing_mode.upper())
262 262
263 263 def patch_stdout(**kwargs):
264 264 return self.pt_cli.patch_stdout_context(**kwargs)
265 265
266 266 self._pt_app = create_prompt_application(
267 267 editing_mode=editing_mode,
268 268 key_bindings_registry=kbmanager.registry,
269 269 history=history,
270 270 completer=IPythonPTCompleter(shell=self,
271 271 patch_stdout=patch_stdout),
272 272 enable_history_search=True,
273 273 style=self.style,
274 274 mouse_support=self.mouse_support,
275 275 **self._layout_options()
276 276 )
277 277 self._eventloop = create_eventloop(self.inputhook)
278 278 self.pt_cli = CommandLineInterface(
279 279 self._pt_app, eventloop=self._eventloop,
280 280 output=create_output(true_color=self.true_color))
281 281
282 282 def _make_style_from_name_or_cls(self, name_or_cls):
283 283 """
284 284 Small wrapper that make an IPython compatible style from a style name
285 285
286 286 We need that to add style for prompt ... etc.
287 287 """
288 288 style_overrides = {}
289 289 if name_or_cls == 'legacy':
290 290 legacy = self.colors.lower()
291 291 if legacy == 'linux':
292 292 style_cls = get_style_by_name('monokai')
293 293 style_overrides = _style_overrides_linux
294 294 elif legacy == 'lightbg':
295 295 style_overrides = _style_overrides_light_bg
296 296 style_cls = get_style_by_name('pastie')
297 297 elif legacy == 'neutral':
298 298 # The default theme needs to be visible on both a dark background
299 299 # and a light background, because we can't tell what the terminal
300 300 # looks like. These tweaks to the default theme help with that.
301 301 style_cls = get_style_by_name('default')
302 302 style_overrides.update({
303 303 Token.Number: '#007700',
304 304 Token.Operator: 'noinherit',
305 305 Token.String: '#BB6622',
306 306 Token.Name.Function: '#2080D0',
307 307 Token.Name.Class: 'bold #2080D0',
308 308 Token.Name.Namespace: 'bold #2080D0',
309 309 Token.Prompt: '#009900',
310 310 Token.PromptNum: '#00ff00 bold',
311 311 Token.OutPrompt: '#990000',
312 312 Token.OutPromptNum: '#ff0000 bold',
313 313 })
314 314
315 315 # Hack: Due to limited color support on the Windows console
316 316 # the prompt colors will be wrong without this
317 317 if os.name == 'nt':
318 318 style_overrides.update({
319 319 Token.Prompt: '#ansidarkgreen',
320 320 Token.PromptNum: '#ansigreen bold',
321 321 Token.OutPrompt: '#ansidarkred',
322 322 Token.OutPromptNum: '#ansired bold',
323 323 })
324 324 elif legacy =='nocolor':
325 325 style_cls=_NoStyle
326 326 style_overrides = {}
327 327 else :
328 328 raise ValueError('Got unknown colors: ', legacy)
329 329 else :
330 330 if isinstance(name_or_cls, str):
331 331 style_cls = get_style_by_name(name_or_cls)
332 332 else:
333 333 style_cls = name_or_cls
334 334 style_overrides = {
335 335 Token.Prompt: '#009900',
336 336 Token.PromptNum: '#00ff00 bold',
337 337 Token.OutPrompt: '#990000',
338 338 Token.OutPromptNum: '#ff0000 bold',
339 339 }
340 340 style_overrides.update(self.highlighting_style_overrides)
341 341 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
342 342 style_dict=style_overrides)
343 343
344 344 return style
345 345
346 346 def _layout_options(self):
347 347 """
348 348 Return the current layout option for the current Terminal InteractiveShell
349 349 """
350 350 return {
351 351 'lexer':IPythonPTLexer(),
352 352 'reserve_space_for_menu':self.space_for_menu,
353 353 'get_prompt_tokens':self.prompts.in_prompt_tokens,
354 354 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
355 355 'multiline':True,
356 356 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
357 357
358 358 # Highlight matching brackets, but only when this setting is
359 359 # enabled, and only when the DEFAULT_BUFFER has the focus.
360 360 'extra_input_processors': [ConditionalProcessor(
361 361 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
362 362 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
363 363 Condition(lambda cli: self.highlight_matching_brackets))],
364 364 }
365 365
366 366 def _update_layout(self):
367 367 """
368 368 Ask for a re computation of the application layout, if for example ,
369 369 some configuration options have changed.
370 370 """
371 371 if self._pt_app:
372 372 self._pt_app.layout = create_prompt_layout(**self._layout_options())
373 373
374 374 def prompt_for_code(self):
375 375 document = self.pt_cli.run(
376 376 pre_run=self.pre_prompt, reset_current_buffer=True)
377 377 return document.text
378 378
379 379 def enable_win_unicode_console(self):
380 380 if sys.version_info >= (3, 6):
381 381 # Since PEP 528, Python uses the unicode APIs for the Windows
382 382 # console by default, so WUC shouldn't be needed.
383 383 return
384 384
385 385 import win_unicode_console
386 386 win_unicode_console.enable()
387 387
388 388 def init_io(self):
389 389 if sys.platform not in {'win32', 'cli'}:
390 390 return
391 391
392 392 self.enable_win_unicode_console()
393 393
394 394 import colorama
395 395 colorama.init()
396 396
397 397 # For some reason we make these wrappers around stdout/stderr.
398 398 # For now, we need to reset them so all output gets coloured.
399 399 # https://github.com/ipython/ipython/issues/8669
400 400 # io.std* are deprecated, but don't show our own deprecation warnings
401 401 # during initialization of the deprecated API.
402 402 with warnings.catch_warnings():
403 403 warnings.simplefilter('ignore', DeprecationWarning)
404 404 io.stdout = io.IOStream(sys.stdout)
405 405 io.stderr = io.IOStream(sys.stderr)
406 406
407 407 def init_magics(self):
408 408 super(TerminalInteractiveShell, self).init_magics()
409 409 self.register_magics(TerminalMagics)
410 410
411 411 def init_alias(self):
412 412 # The parent class defines aliases that can be safely used with any
413 413 # frontend.
414 414 super(TerminalInteractiveShell, self).init_alias()
415 415
416 416 # Now define aliases that only make sense on the terminal, because they
417 417 # need direct access to the console in a way that we can't emulate in
418 418 # GUI or web frontend
419 419 if os.name == 'posix':
420 420 for cmd in ['clear', 'more', 'less', 'man']:
421 421 self.alias_manager.soft_define_alias(cmd, cmd)
422 422
423 423
424 424 def __init__(self, *args, **kwargs):
425 425 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
426 426 self.init_prompt_toolkit_cli()
427 427 self.init_term_title()
428 428 self.keep_running = True
429 429
430 430 self.debugger_history = InMemoryHistory()
431 431
432 432 def ask_exit(self):
433 433 self.keep_running = False
434 434
435 435 rl_next_input = None
436 436
437 437 def pre_prompt(self):
438 438 if self.rl_next_input:
439 439 # We can't set the buffer here, because it will be reset just after
440 440 # this. Adding a callable to pre_run_callables does what we need
441 441 # after the buffer is reset.
442 442 s = self.rl_next_input
443 443 def set_doc():
444 444 self.pt_cli.application.buffer.document = Document(s)
445 445 if hasattr(self.pt_cli, 'pre_run_callables'):
446 446 self.pt_cli.pre_run_callables.append(set_doc)
447 447 else:
448 448 # Older version of prompt_toolkit; it's OK to set the document
449 449 # directly here.
450 450 set_doc()
451 451 self.rl_next_input = None
452 452
453 453 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
454 454
455 455 if display_banner is not DISPLAY_BANNER_DEPRECATED:
456 456 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
457 457
458 458 self.keep_running = True
459 459 while self.keep_running:
460 460 print(self.separate_in, end='')
461 461
462 462 try:
463 463 code = self.prompt_for_code()
464 464 except EOFError:
465 465 if (not self.confirm_exit) \
466 466 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
467 467 self.ask_exit()
468 468
469 469 else:
470 470 if code:
471 471 self.run_cell(code, store_history=True)
472 472
473 473 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
474 474 # An extra layer of protection in case someone mashing Ctrl-C breaks
475 475 # out of our internal code.
476 476 if display_banner is not DISPLAY_BANNER_DEPRECATED:
477 477 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
478 478 while True:
479 479 try:
480 480 self.interact()
481 481 break
482 482 except KeyboardInterrupt as e:
483 483 print("\n%s escaped interact()\n" % type(e).__name__)
484 484 finally:
485 485 # An interrupt during the eventloop will mess up the
486 486 # internal state of the prompt_toolkit library.
487 487 # Stopping the eventloop fixes this, see
488 488 # https://github.com/ipython/ipython/pull/9867
489 489 if hasattr(self, '_eventloop'):
490 490 self._eventloop.stop()
491 491
492 492 _inputhook = None
493 493 def inputhook(self, context):
494 494 if self._inputhook is not None:
495 495 self._inputhook(context)
496 496
497 497 active_eventloop = None
498 498 def enable_gui(self, gui=None):
499 499 if gui:
500 500 self.active_eventloop, self._inputhook =\
501 501 get_inputhook_name_and_func(gui)
502 502 else:
503 503 self.active_eventloop = self._inputhook = None
504 504
505 505 # Run !system commands directly, not through pipes, so terminal programs
506 506 # work correctly.
507 507 system = InteractiveShell.system_raw
508 508
509 509 def auto_rewrite_input(self, cmd):
510 510 """Overridden from the parent class to use fancy rewriting prompt"""
511 511 if not self.show_rewritten_input:
512 512 return
513 513
514 514 tokens = self.prompts.rewrite_prompt_tokens()
515 515 if self.pt_cli:
516 516 self.pt_cli.print_tokens(tokens)
517 517 print(cmd)
518 518 else:
519 519 prompt = ''.join(s for t, s in tokens)
520 520 print(prompt, cmd, sep='')
521 521
522 522 _prompts_before = None
523 523 def switch_doctest_mode(self, mode):
524 524 """Switch prompts to classic for %doctest_mode"""
525 525 if mode:
526 526 self._prompts_before = self.prompts
527 527 self.prompts = ClassicPrompts(self)
528 528 elif self._prompts_before:
529 529 self.prompts = self._prompts_before
530 530 self._prompts_before = None
531 531 self._update_layout()
532 532
533 533
534 534 InteractiveShellABC.register(TerminalInteractiveShell)
535 535
536 536 if __name__ == '__main__':
537 537 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now