##// END OF EJS Templates
Add configurable providers for autosuggestions
Alexander Steppke -
Show More
@@ -1,710 +1,728 b''
1 1 """IPython terminal interface using prompt_toolkit"""
2 2
3 3 import asyncio
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.async_helpers import get_asyncio_loop
10 10 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
11 11 from IPython.utils import io
12 12 from IPython.utils.py3compat import input
13 13 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
14 14 from IPython.utils.process import abbrev_cwd
15 15 from traitlets import (
16 16 Bool,
17 17 Unicode,
18 18 Dict,
19 19 Integer,
20 20 observe,
21 21 Instance,
22 22 Type,
23 23 default,
24 24 Enum,
25 25 Union,
26 26 Any,
27 27 validate,
28 28 Float,
29 29 )
30 30
31 31 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
32 32 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
33 33 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
34 34 from prompt_toolkit.formatted_text import PygmentsTokens
35 35 from prompt_toolkit.history import InMemoryHistory
36 36 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
37 37 from prompt_toolkit.output import ColorDepth
38 38 from prompt_toolkit.patch_stdout import patch_stdout
39 39 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
40 40 from prompt_toolkit.styles import DynamicStyle, merge_styles
41 41 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
42 42 from prompt_toolkit import __version__ as ptk_version
43 43
44 44 from pygments.styles import get_style_by_name
45 45 from pygments.style import Style
46 46 from pygments.token import Token
47 47
48 48 from .debugger import TerminalPdb, Pdb
49 49 from .magics import TerminalMagics
50 50 from .pt_inputhooks import get_inputhook_name_and_func
51 51 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
52 52 from .ptutils import IPythonPTCompleter, IPythonPTLexer
53 53 from .shortcuts import create_ipython_shortcuts
54 54
55 55 PTK3 = ptk_version.startswith('3.')
56 56
57 57
58 58 class _NoStyle(Style): pass
59 59
60 60
61 61
62 62 _style_overrides_light_bg = {
63 63 Token.Prompt: '#ansibrightblue',
64 64 Token.PromptNum: '#ansiblue bold',
65 65 Token.OutPrompt: '#ansibrightred',
66 66 Token.OutPromptNum: '#ansired bold',
67 67 }
68 68
69 69 _style_overrides_linux = {
70 70 Token.Prompt: '#ansibrightgreen',
71 71 Token.PromptNum: '#ansigreen bold',
72 72 Token.OutPrompt: '#ansibrightred',
73 73 Token.OutPromptNum: '#ansired bold',
74 74 }
75 75
76 76 def get_default_editor():
77 77 try:
78 78 return os.environ['EDITOR']
79 79 except KeyError:
80 80 pass
81 81 except UnicodeError:
82 82 warn("$EDITOR environment variable is not pure ASCII. Using platform "
83 83 "default editor.")
84 84
85 85 if os.name == 'posix':
86 86 return 'vi' # the only one guaranteed to be there!
87 87 else:
88 88 return 'notepad' # same in Windows!
89 89
90 90 # conservatively check for tty
91 91 # overridden streams can result in things like:
92 92 # - sys.stdin = None
93 93 # - no isatty method
94 94 for _name in ('stdin', 'stdout', 'stderr'):
95 95 _stream = getattr(sys, _name)
96 96 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
97 97 _is_tty = False
98 98 break
99 99 else:
100 100 _is_tty = True
101 101
102 102
103 103 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
104 104
105 105 def black_reformat_handler(text_before_cursor):
106 106 """
107 107 We do not need to protect against error,
108 108 this is taken care at a higher level where any reformat error is ignored.
109 109 Indeed we may call reformatting on incomplete code.
110 110 """
111 111 import black
112 112
113 113 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
114 114 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
115 115 formatted_text = formatted_text[:-1]
116 116 return formatted_text
117 117
118 118
119 119 class TerminalInteractiveShell(InteractiveShell):
120 120 mime_renderers = Dict().tag(config=True)
121 121
122 122 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
123 123 'to reserve for the tab completion menu, '
124 124 'search history, ...etc, the height of '
125 125 'these menus will at most this value. '
126 126 'Increase it is you prefer long and skinny '
127 127 'menus, decrease for short and wide.'
128 128 ).tag(config=True)
129 129
130 130 pt_app = None
131 131 debugger_history = None
132 132
133 133 debugger_history_file = Unicode(
134 134 "~/.pdbhistory", help="File in which to store and read history"
135 135 ).tag(config=True)
136 136
137 137 simple_prompt = Bool(_use_simple_prompt,
138 138 help="""Use `raw_input` for the REPL, without completion and prompt colors.
139 139
140 140 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
141 141 IPython own testing machinery, and emacs inferior-shell integration through elpy.
142 142
143 143 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
144 144 environment variable is set, or the current terminal is not a tty."""
145 145 ).tag(config=True)
146 146
147 147 @property
148 148 def debugger_cls(self):
149 149 return Pdb if self.simple_prompt else TerminalPdb
150 150
151 151 confirm_exit = Bool(True,
152 152 help="""
153 153 Set to confirm when you try to exit IPython with an EOF (Control-D
154 154 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
155 155 you can force a direct exit without any confirmation.""",
156 156 ).tag(config=True)
157 157
158 158 editing_mode = Unicode('emacs',
159 159 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
160 160 ).tag(config=True)
161 161
162 162 emacs_bindings_in_vi_insert_mode = Bool(
163 163 True,
164 164 help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.",
165 165 ).tag(config=True)
166 166
167 167 modal_cursor = Bool(
168 168 True,
169 169 help="""
170 170 Cursor shape changes depending on vi mode: beam in vi insert mode,
171 171 block in nav mode, underscore in replace mode.""",
172 172 ).tag(config=True)
173 173
174 174 ttimeoutlen = Float(
175 175 0.01,
176 176 help="""The time in milliseconds that is waited for a key code
177 177 to complete.""",
178 178 ).tag(config=True)
179 179
180 180 timeoutlen = Float(
181 181 0.5,
182 182 help="""The time in milliseconds that is waited for a mapped key
183 183 sequence to complete.""",
184 184 ).tag(config=True)
185 185
186 186 autoformatter = Unicode(
187 187 "black",
188 188 help="Autoformatter to reformat Terminal code. Can be `'black'` or `None`",
189 189 allow_none=True
190 190 ).tag(config=True)
191 191
192 192 auto_match = Bool(
193 193 False,
194 194 help="""
195 195 Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted.
196 196 Brackets: (), [], {}
197 197 Quotes: '', \"\"
198 198 """,
199 199 ).tag(config=True)
200 200
201 201 mouse_support = Bool(False,
202 202 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
203 203 ).tag(config=True)
204 204
205 205 # We don't load the list of styles for the help string, because loading
206 206 # Pygments plugins takes time and can cause unexpected errors.
207 207 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
208 208 help="""The name or class of a Pygments style to use for syntax
209 209 highlighting. To see available styles, run `pygmentize -L styles`."""
210 210 ).tag(config=True)
211 211
212 212 @validate('editing_mode')
213 213 def _validate_editing_mode(self, proposal):
214 214 if proposal['value'].lower() == 'vim':
215 215 proposal['value']= 'vi'
216 216 elif proposal['value'].lower() == 'default':
217 217 proposal['value']= 'emacs'
218 218
219 219 if hasattr(EditingMode, proposal['value'].upper()):
220 220 return proposal['value'].lower()
221 221
222 222 return self.editing_mode
223 223
224 224
225 225 @observe('editing_mode')
226 226 def _editing_mode(self, change):
227 227 if self.pt_app:
228 228 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
229 229
230 230 def _set_formatter(self, formatter):
231 231 if formatter is None:
232 232 self.reformat_handler = lambda x:x
233 233 elif formatter == 'black':
234 234 self.reformat_handler = black_reformat_handler
235 235 else:
236 236 raise ValueError
237 237
238 238 @observe("autoformatter")
239 239 def _autoformatter_changed(self, change):
240 240 formatter = change.new
241 241 self._set_formatter(formatter)
242 242
243 243 @observe('highlighting_style')
244 244 @observe('colors')
245 245 def _highlighting_style_changed(self, change):
246 246 self.refresh_style()
247 247
248 248 def refresh_style(self):
249 249 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
250 250
251 251
252 252 highlighting_style_overrides = Dict(
253 253 help="Override highlighting format for specific tokens"
254 254 ).tag(config=True)
255 255
256 256 true_color = Bool(False,
257 257 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
258 258 If your terminal supports true color, the following command should
259 259 print ``TRUECOLOR`` in orange::
260 260
261 261 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
262 262 """,
263 263 ).tag(config=True)
264 264
265 265 editor = Unicode(get_default_editor(),
266 266 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
267 267 ).tag(config=True)
268 268
269 269 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
270 270
271 271 prompts = Instance(Prompts)
272 272
273 273 @default('prompts')
274 274 def _prompts_default(self):
275 275 return self.prompts_class(self)
276 276
277 277 # @observe('prompts')
278 278 # def _(self, change):
279 279 # self._update_layout()
280 280
281 281 @default('displayhook_class')
282 282 def _displayhook_class_default(self):
283 283 return RichPromptDisplayHook
284 284
285 285 term_title = Bool(True,
286 286 help="Automatically set the terminal title"
287 287 ).tag(config=True)
288 288
289 289 term_title_format = Unicode("IPython: {cwd}",
290 290 help="Customize the terminal title format. This is a python format string. " +
291 291 "Available substitutions are: {cwd}."
292 292 ).tag(config=True)
293 293
294 294 display_completions = Enum(('column', 'multicolumn','readlinelike'),
295 295 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
296 296 "'readlinelike'. These options are for `prompt_toolkit`, see "
297 297 "`prompt_toolkit` documentation for more information."
298 298 ),
299 299 default_value='multicolumn').tag(config=True)
300 300
301 301 highlight_matching_brackets = Bool(True,
302 302 help="Highlight matching brackets.",
303 303 ).tag(config=True)
304 304
305 305 extra_open_editor_shortcuts = Bool(False,
306 306 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
307 307 "This is in addition to the F2 binding, which is always enabled."
308 308 ).tag(config=True)
309 309
310 310 handle_return = Any(None,
311 311 help="Provide an alternative handler to be called when the user presses "
312 312 "Return. This is an advanced option intended for debugging, which "
313 313 "may be changed or removed in later releases."
314 314 ).tag(config=True)
315 315
316 316 enable_history_search = Bool(True,
317 317 help="Allows to enable/disable the prompt toolkit history search"
318 318 ).tag(config=True)
319 319
320 enable_autosuggestions = Bool(True,
321 help="Allows to enable/disable the prompt autosuggestions based on "
322 "the prompt toolkit history.",
320 autosuggestions_provider = Unicode(
321 "AutoSuggestFromHistory",
322 help="Specifies from which source automatic suggestions are provided. "
323 "Can be set to `'AutoSuggestFromHistory`' or `None` to disable"
324 "automatic suggestions. Default is `'AutoSuggestFromHistory`'.",
325 allow_none=True,
323 326 ).tag(config=True)
324 327
328 prompt_includes_vi_mode = Bool(
329 True, help="Display the current vi mode (when using vi editing mode)."
330 ).tag(config=True)
331
332 def _set_autosuggestions(self, provider):
333 if provider is None:
334 self.auto_suggest = None
335 elif provider == "AutoSuggestFromHistory":
336 self.auto_suggest = AutoSuggestFromHistory()
337 else:
338 raise ValueError("No valid provider.")
339 if self.pt_app:
340 self.pt_app.auto_suggest = self.auto_suggest
341
342 @observe("autosuggestions_provider")
343 def _autosuggestions_provider_changed(self, change):
344 provider = change.new
345 self._set_autosuggestions(provider)
346
325 347 prompt_includes_vi_mode = Bool(True,
326 348 help="Display the current vi mode (when using vi editing mode)."
327 349 ).tag(config=True)
328 350
329 351 @observe('term_title')
330 352 def init_term_title(self, change=None):
331 353 # Enable or disable the terminal title.
332 354 if self.term_title:
333 355 toggle_set_term_title(True)
334 356 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
335 357 else:
336 358 toggle_set_term_title(False)
337 359
338 360 def restore_term_title(self):
339 361 if self.term_title:
340 362 restore_term_title()
341 363
342 364 def init_display_formatter(self):
343 365 super(TerminalInteractiveShell, self).init_display_formatter()
344 366 # terminal only supports plain text
345 367 self.display_formatter.active_types = ["text/plain"]
346 368
347 369 def init_prompt_toolkit_cli(self):
348 370 if self.simple_prompt:
349 371 # Fall back to plain non-interactive output for tests.
350 372 # This is very limited.
351 373 def prompt():
352 374 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
353 375 lines = [input(prompt_text)]
354 376 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
355 377 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
356 378 lines.append( input(prompt_continuation) )
357 379 return '\n'.join(lines)
358 380 self.prompt_for_code = prompt
359 381 return
360 382
361 383 # Set up keyboard shortcuts
362 384 key_bindings = create_ipython_shortcuts(self)
363 385
364 386 # Pre-populate history from IPython's history database
365 387 history = InMemoryHistory()
366 388 last_cell = u""
367 389 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
368 390 include_latest=True):
369 391 # Ignore blank lines and consecutive duplicates
370 392 cell = cell.rstrip()
371 393 if cell and (cell != last_cell):
372 394 history.append_string(cell)
373 395 last_cell = cell
374 396
375 397 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
376 398 self.style = DynamicStyle(lambda: self._style)
377 399
378 if self.enable_autosuggestions:
379 auto_suggest = AutoSuggestFromHistory()
380 else:
381 auto_suggest = None
382
383 400 editing_mode = getattr(EditingMode, self.editing_mode.upper())
384 401
385 402 self.pt_loop = asyncio.new_event_loop()
386 403 self.pt_app = PromptSession(
387 auto_suggest=auto_suggest,
404 auto_suggest=self.auto_suggest,
388 405 editing_mode=editing_mode,
389 406 key_bindings=key_bindings,
390 407 history=history,
391 408 completer=IPythonPTCompleter(shell=self),
392 409 enable_history_search=self.enable_history_search,
393 410 style=self.style,
394 411 include_default_pygments_style=False,
395 412 mouse_support=self.mouse_support,
396 413 enable_open_in_editor=self.extra_open_editor_shortcuts,
397 414 color_depth=self.color_depth,
398 415 tempfile_suffix=".py",
399 416 **self._extra_prompt_options()
400 417 )
401 418
402 419 def _make_style_from_name_or_cls(self, name_or_cls):
403 420 """
404 421 Small wrapper that make an IPython compatible style from a style name
405 422
406 423 We need that to add style for prompt ... etc.
407 424 """
408 425 style_overrides = {}
409 426 if name_or_cls == 'legacy':
410 427 legacy = self.colors.lower()
411 428 if legacy == 'linux':
412 429 style_cls = get_style_by_name('monokai')
413 430 style_overrides = _style_overrides_linux
414 431 elif legacy == 'lightbg':
415 432 style_overrides = _style_overrides_light_bg
416 433 style_cls = get_style_by_name('pastie')
417 434 elif legacy == 'neutral':
418 435 # The default theme needs to be visible on both a dark background
419 436 # and a light background, because we can't tell what the terminal
420 437 # looks like. These tweaks to the default theme help with that.
421 438 style_cls = get_style_by_name('default')
422 439 style_overrides.update({
423 440 Token.Number: '#ansigreen',
424 441 Token.Operator: 'noinherit',
425 442 Token.String: '#ansiyellow',
426 443 Token.Name.Function: '#ansiblue',
427 444 Token.Name.Class: 'bold #ansiblue',
428 445 Token.Name.Namespace: 'bold #ansiblue',
429 446 Token.Name.Variable.Magic: '#ansiblue',
430 447 Token.Prompt: '#ansigreen',
431 448 Token.PromptNum: '#ansibrightgreen bold',
432 449 Token.OutPrompt: '#ansired',
433 450 Token.OutPromptNum: '#ansibrightred bold',
434 451 })
435 452
436 453 # Hack: Due to limited color support on the Windows console
437 454 # the prompt colors will be wrong without this
438 455 if os.name == 'nt':
439 456 style_overrides.update({
440 457 Token.Prompt: '#ansidarkgreen',
441 458 Token.PromptNum: '#ansigreen bold',
442 459 Token.OutPrompt: '#ansidarkred',
443 460 Token.OutPromptNum: '#ansired bold',
444 461 })
445 462 elif legacy =='nocolor':
446 463 style_cls=_NoStyle
447 464 style_overrides = {}
448 465 else :
449 466 raise ValueError('Got unknown colors: ', legacy)
450 467 else :
451 468 if isinstance(name_or_cls, str):
452 469 style_cls = get_style_by_name(name_or_cls)
453 470 else:
454 471 style_cls = name_or_cls
455 472 style_overrides = {
456 473 Token.Prompt: '#ansigreen',
457 474 Token.PromptNum: '#ansibrightgreen bold',
458 475 Token.OutPrompt: '#ansired',
459 476 Token.OutPromptNum: '#ansibrightred bold',
460 477 }
461 478 style_overrides.update(self.highlighting_style_overrides)
462 479 style = merge_styles([
463 480 style_from_pygments_cls(style_cls),
464 481 style_from_pygments_dict(style_overrides),
465 482 ])
466 483
467 484 return style
468 485
469 486 @property
470 487 def pt_complete_style(self):
471 488 return {
472 489 'multicolumn': CompleteStyle.MULTI_COLUMN,
473 490 'column': CompleteStyle.COLUMN,
474 491 'readlinelike': CompleteStyle.READLINE_LIKE,
475 492 }[self.display_completions]
476 493
477 494 @property
478 495 def color_depth(self):
479 496 return (ColorDepth.TRUE_COLOR if self.true_color else None)
480 497
481 498 def _extra_prompt_options(self):
482 499 """
483 500 Return the current layout option for the current Terminal InteractiveShell
484 501 """
485 502 def get_message():
486 503 return PygmentsTokens(self.prompts.in_prompt_tokens())
487 504
488 505 if self.editing_mode == 'emacs':
489 506 # with emacs mode the prompt is (usually) static, so we call only
490 507 # the function once. With VI mode it can toggle between [ins] and
491 508 # [nor] so we can't precompute.
492 509 # here I'm going to favor the default keybinding which almost
493 510 # everybody uses to decrease CPU usage.
494 511 # if we have issues with users with custom Prompts we can see how to
495 512 # work around this.
496 513 get_message = get_message()
497 514
498 515 options = {
499 516 'complete_in_thread': False,
500 517 'lexer':IPythonPTLexer(),
501 518 'reserve_space_for_menu':self.space_for_menu,
502 519 'message': get_message,
503 520 'prompt_continuation': (
504 521 lambda width, lineno, is_soft_wrap:
505 522 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
506 523 'multiline': True,
507 524 'complete_style': self.pt_complete_style,
508 525
509 526 # Highlight matching brackets, but only when this setting is
510 527 # enabled, and only when the DEFAULT_BUFFER has the focus.
511 528 'input_processors': [ConditionalProcessor(
512 529 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
513 530 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
514 531 Condition(lambda: self.highlight_matching_brackets))],
515 532 }
516 533 if not PTK3:
517 534 options['inputhook'] = self.inputhook
518 535
519 536 return options
520 537
521 538 def prompt_for_code(self):
522 539 if self.rl_next_input:
523 540 default = self.rl_next_input
524 541 self.rl_next_input = None
525 542 else:
526 543 default = ''
527 544
528 545 # In order to make sure that asyncio code written in the
529 546 # interactive shell doesn't interfere with the prompt, we run the
530 547 # prompt in a different event loop.
531 548 # If we don't do this, people could spawn coroutine with a
532 549 # while/true inside which will freeze the prompt.
533 550
534 551 policy = asyncio.get_event_loop_policy()
535 552 old_loop = get_asyncio_loop()
536 553
537 554 # FIXME: prompt_toolkit is using the deprecated `asyncio.get_event_loop`
538 555 # to get the current event loop.
539 556 # This will probably be replaced by an attribute or input argument,
540 557 # at which point we can stop calling the soon-to-be-deprecated `set_event_loop` here.
541 558 if old_loop is not self.pt_loop:
542 559 policy.set_event_loop(self.pt_loop)
543 560 try:
544 561 with patch_stdout(raw=True):
545 562 text = self.pt_app.prompt(
546 563 default=default,
547 564 **self._extra_prompt_options())
548 565 finally:
549 566 # Restore the original event loop.
550 567 if old_loop is not None and old_loop is not self.pt_loop:
551 568 policy.set_event_loop(old_loop)
552 569
553 570 return text
554 571
555 572 def enable_win_unicode_console(self):
556 573 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
557 574 # console by default, so WUC shouldn't be needed.
558 575 from warnings import warn
559 576 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
560 577 DeprecationWarning,
561 578 stacklevel=2)
562 579
563 580 def init_io(self):
564 581 if sys.platform not in {'win32', 'cli'}:
565 582 return
566 583
567 584 import colorama
568 585 colorama.init()
569 586
570 587 def init_magics(self):
571 588 super(TerminalInteractiveShell, self).init_magics()
572 589 self.register_magics(TerminalMagics)
573 590
574 591 def init_alias(self):
575 592 # The parent class defines aliases that can be safely used with any
576 593 # frontend.
577 594 super(TerminalInteractiveShell, self).init_alias()
578 595
579 596 # Now define aliases that only make sense on the terminal, because they
580 597 # need direct access to the console in a way that we can't emulate in
581 598 # GUI or web frontend
582 599 if os.name == 'posix':
583 600 for cmd in ('clear', 'more', 'less', 'man'):
584 601 self.alias_manager.soft_define_alias(cmd, cmd)
585 602
586 603
587 604 def __init__(self, *args, **kwargs):
588 605 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
606 self._set_autosuggestions(self.autosuggestions_provider)
589 607 self.init_prompt_toolkit_cli()
590 608 self.init_term_title()
591 609 self.keep_running = True
592 610 self._set_formatter(self.autoformatter)
593 611
594 612
595 613 def ask_exit(self):
596 614 self.keep_running = False
597 615
598 616 rl_next_input = None
599 617
600 618 def interact(self):
601 619 self.keep_running = True
602 620 while self.keep_running:
603 621 print(self.separate_in, end='')
604 622
605 623 try:
606 624 code = self.prompt_for_code()
607 625 except EOFError:
608 626 if (not self.confirm_exit) \
609 627 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
610 628 self.ask_exit()
611 629
612 630 else:
613 631 if code:
614 632 self.run_cell(code, store_history=True)
615 633
616 634 def mainloop(self):
617 635 # An extra layer of protection in case someone mashing Ctrl-C breaks
618 636 # out of our internal code.
619 637 while True:
620 638 try:
621 639 self.interact()
622 640 break
623 641 except KeyboardInterrupt as e:
624 642 print("\n%s escaped interact()\n" % type(e).__name__)
625 643 finally:
626 644 # An interrupt during the eventloop will mess up the
627 645 # internal state of the prompt_toolkit library.
628 646 # Stopping the eventloop fixes this, see
629 647 # https://github.com/ipython/ipython/pull/9867
630 648 if hasattr(self, '_eventloop'):
631 649 self._eventloop.stop()
632 650
633 651 self.restore_term_title()
634 652
635 653 # try to call some at-exit operation optimistically as some things can't
636 654 # be done during interpreter shutdown. this is technically inaccurate as
637 655 # this make mainlool not re-callable, but that should be a rare if not
638 656 # in existent use case.
639 657
640 658 self._atexit_once()
641 659
642 660
643 661 _inputhook = None
644 662 def inputhook(self, context):
645 663 if self._inputhook is not None:
646 664 self._inputhook(context)
647 665
648 666 active_eventloop = None
649 667 def enable_gui(self, gui=None):
650 668 if gui and (gui != 'inline') :
651 669 self.active_eventloop, self._inputhook =\
652 670 get_inputhook_name_and_func(gui)
653 671 else:
654 672 self.active_eventloop = self._inputhook = None
655 673
656 674 # For prompt_toolkit 3.0. We have to create an asyncio event loop with
657 675 # this inputhook.
658 676 if PTK3:
659 677 import asyncio
660 678 from prompt_toolkit.eventloop import new_eventloop_with_inputhook
661 679
662 680 if gui == 'asyncio':
663 681 # When we integrate the asyncio event loop, run the UI in the
664 682 # same event loop as the rest of the code. don't use an actual
665 683 # input hook. (Asyncio is not made for nesting event loops.)
666 684 self.pt_loop = get_asyncio_loop()
667 685
668 686 elif self._inputhook:
669 687 # If an inputhook was set, create a new asyncio event loop with
670 688 # this inputhook for the prompt.
671 689 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
672 690 else:
673 691 # When there's no inputhook, run the prompt in a separate
674 692 # asyncio event loop.
675 693 self.pt_loop = asyncio.new_event_loop()
676 694
677 695 # Run !system commands directly, not through pipes, so terminal programs
678 696 # work correctly.
679 697 system = InteractiveShell.system_raw
680 698
681 699 def auto_rewrite_input(self, cmd):
682 700 """Overridden from the parent class to use fancy rewriting prompt"""
683 701 if not self.show_rewritten_input:
684 702 return
685 703
686 704 tokens = self.prompts.rewrite_prompt_tokens()
687 705 if self.pt_app:
688 706 print_formatted_text(PygmentsTokens(tokens), end='',
689 707 style=self.pt_app.app.style)
690 708 print(cmd)
691 709 else:
692 710 prompt = ''.join(s for t, s in tokens)
693 711 print(prompt, cmd, sep='')
694 712
695 713 _prompts_before = None
696 714 def switch_doctest_mode(self, mode):
697 715 """Switch prompts to classic for %doctest_mode"""
698 716 if mode:
699 717 self._prompts_before = self.prompts
700 718 self.prompts = ClassicPrompts(self)
701 719 elif self._prompts_before:
702 720 self.prompts = self._prompts_before
703 721 self._prompts_before = None
704 722 # self._update_layout()
705 723
706 724
707 725 InteractiveShellABC.register(TerminalInteractiveShell)
708 726
709 727 if __name__ == '__main__':
710 728 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now