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