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