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