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