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