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