##// END OF EJS Templates
format
M Bussonnier -
Show More
@@ -1,1026 +1,1030
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 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 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 allowed_commands.update({
518 allowed_commands.update(
519 {
519 520 create_identifier(command): command
520 521 for command in UNASSIGNED_ALLOWED_COMMANDS
521 })
522 }
523 )
522 524 shortcuts_to_skip = []
523 525 shortcuts_to_add = []
524 526
525 527 for shortcut in user_shortcuts:
526 528 command_id = shortcut["command"]
527 529 if command_id not in allowed_commands:
528 530 allowed_commands = "\n - ".join(allowed_commands)
529 531 raise ValueError(
530 532 f"{command_id} is not a known shortcut command."
531 533 f" Allowed commands are: \n - {allowed_commands}"
532 534 )
533 535 old_keys = shortcut.get("match_keys", None)
534 536 old_filter = (
535 537 filter_from_string(shortcut["match_filter"])
536 538 if "match_filter" in shortcut
537 539 else None
538 540 )
539 541 matching = [
540 542 binding
541 543 for binding in KEY_BINDINGS
542 544 if (
543 545 (old_filter is None or binding.filter == old_filter)
544 546 and (old_keys is None or [k for k in binding.keys] == old_keys)
545 547 and create_identifier(binding.command) == command_id
546 548 )
547 549 ]
548 550
549 551 new_keys = shortcut.get("new_keys", None)
550 552 new_filter = shortcut.get("new_filter", None)
551 553
552 554 command = allowed_commands[command_id]
553 555
554 556 creating_new = shortcut.get("create", False)
555 557 modifying_existing = not creating_new and (
556 558 new_keys is not None or new_filter
557 559 )
558 560
559 561 if creating_new and new_keys == []:
560 562 raise ValueError("Cannot add a shortcut without keys")
561 563
562 564 if modifying_existing:
563 565 specification = {
564 566 key: shortcut[key]
565 567 for key in ["command", "filter"]
566 568 if key in shortcut
567 569 }
568 570 if len(matching) == 0:
569 571 raise ValueError(
570 572 f"No shortcuts matching {specification} found in {KEY_BINDINGS}"
571 573 )
572 574 elif len(matching) > 1:
573 575 raise ValueError(
574 576 f"Multiple shortcuts matching {specification} found,"
575 577 f" please add keys/filter to select one of: {matching}"
576 578 )
577 579
578 580 matched = matching[0]
579 581 old_filter = matched.filter
580 582 old_keys = list(matched.keys)
581 583 shortcuts_to_skip.append(
582 584 RuntimeBinding(
583 585 command,
584 586 keys=old_keys,
585 587 filter=old_filter,
586 588 )
587 589 )
588 590
589 591 if new_keys != []:
590 592 shortcuts_to_add.append(
591 593 RuntimeBinding(
592 594 command,
593 595 keys=new_keys or old_keys,
594 filter=filter_from_string(new_filter)
595 if new_filter is not None
596 else (
597 old_filter
598 if old_filter is not None
599 else filter_from_string("always")
596 filter=(
597 filter_from_string(new_filter)
598 if new_filter is not None
599 else (
600 old_filter
601 if old_filter is not None
602 else filter_from_string("always")
603 )
600 604 ),
601 605 )
602 606 )
603 607
604 608 # rebuild the bindings list from scratch
605 609 key_bindings = create_ipython_shortcuts(self, skip=shortcuts_to_skip)
606 610 for binding in shortcuts_to_add:
607 611 add_binding(key_bindings, binding)
608 612
609 613 return key_bindings
610 614
611 615 prompt_includes_vi_mode = Bool(True,
612 616 help="Display the current vi mode (when using vi editing mode)."
613 617 ).tag(config=True)
614 618
615 619 prompt_line_number_format = Unicode(
616 620 "",
617 621 help="The format for line numbering, will be passed `line` (int, 1 based)"
618 622 " the current line number and `rel_line` the relative line number."
619 623 " for example to display both you can use the following template string :"
620 624 " c.TerminalInteractiveShell.prompt_line_number_format='{line: 4d}/{rel_line:+03d} | '"
621 625 " This will display the current line number, with leading space and a width of at least 4"
622 626 " character, as well as the relative line number 0 padded and always with a + or - sign."
623 627 " Note that when using Emacs mode the prompt of the first line may not update.",
624 628 ).tag(config=True)
625 629
626 630 @observe('term_title')
627 631 def init_term_title(self, change=None):
628 632 # Enable or disable the terminal title.
629 633 if self.term_title and _is_tty:
630 634 toggle_set_term_title(True)
631 635 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
632 636 else:
633 637 toggle_set_term_title(False)
634 638
635 639 def restore_term_title(self):
636 640 if self.term_title and _is_tty:
637 641 restore_term_title()
638 642
639 643 def init_display_formatter(self):
640 644 super(TerminalInteractiveShell, self).init_display_formatter()
641 645 # terminal only supports plain text
642 646 self.display_formatter.active_types = ["text/plain"]
643 647
644 648 def init_prompt_toolkit_cli(self):
645 649 if self.simple_prompt:
646 650 # Fall back to plain non-interactive output for tests.
647 651 # This is very limited.
648 652 def prompt():
649 653 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
650 654 lines = [input(prompt_text)]
651 655 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
652 656 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
653 657 lines.append( input(prompt_continuation) )
654 658 return '\n'.join(lines)
655 659 self.prompt_for_code = prompt
656 660 return
657 661
658 662 # Set up keyboard shortcuts
659 663 key_bindings = self._merge_shortcuts(user_shortcuts=self.shortcuts)
660 664
661 665 # Pre-populate history from IPython's history database
662 666 history = PtkHistoryAdapter(self)
663 667
664 668 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
665 669 self.style = DynamicStyle(lambda: self._style)
666 670
667 671 editing_mode = getattr(EditingMode, self.editing_mode.upper())
668 672
669 673 self._use_asyncio_inputhook = False
670 674 self.pt_app = PromptSession(
671 675 auto_suggest=self.auto_suggest,
672 676 editing_mode=editing_mode,
673 677 key_bindings=key_bindings,
674 678 history=history,
675 679 completer=IPythonPTCompleter(shell=self),
676 680 enable_history_search=self.enable_history_search,
677 681 style=self.style,
678 682 include_default_pygments_style=False,
679 683 mouse_support=self.mouse_support,
680 684 enable_open_in_editor=self.extra_open_editor_shortcuts,
681 685 color_depth=self.color_depth,
682 686 tempfile_suffix=".py",
683 687 **self._extra_prompt_options(),
684 688 )
685 689 if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory):
686 690 self.auto_suggest.connect(self.pt_app)
687 691
688 692 def _make_style_from_name_or_cls(self, name_or_cls):
689 693 """
690 694 Small wrapper that make an IPython compatible style from a style name
691 695
692 696 We need that to add style for prompt ... etc.
693 697 """
694 698 style_overrides = {}
695 699 if name_or_cls == 'legacy':
696 700 legacy = self.colors.lower()
697 701 if legacy == 'linux':
698 702 style_cls = get_style_by_name('monokai')
699 703 style_overrides = _style_overrides_linux
700 704 elif legacy == 'lightbg':
701 705 style_overrides = _style_overrides_light_bg
702 706 style_cls = get_style_by_name('pastie')
703 707 elif legacy == 'neutral':
704 708 # The default theme needs to be visible on both a dark background
705 709 # and a light background, because we can't tell what the terminal
706 710 # looks like. These tweaks to the default theme help with that.
707 711 style_cls = get_style_by_name('default')
708 712 style_overrides.update({
709 713 Token.Number: '#ansigreen',
710 714 Token.Operator: 'noinherit',
711 715 Token.String: '#ansiyellow',
712 716 Token.Name.Function: '#ansiblue',
713 717 Token.Name.Class: 'bold #ansiblue',
714 718 Token.Name.Namespace: 'bold #ansiblue',
715 719 Token.Name.Variable.Magic: '#ansiblue',
716 720 Token.Prompt: '#ansigreen',
717 721 Token.PromptNum: '#ansibrightgreen bold',
718 722 Token.OutPrompt: '#ansired',
719 723 Token.OutPromptNum: '#ansibrightred bold',
720 724 })
721 725
722 726 # Hack: Due to limited color support on the Windows console
723 727 # the prompt colors will be wrong without this
724 728 if os.name == 'nt':
725 729 style_overrides.update({
726 730 Token.Prompt: '#ansidarkgreen',
727 731 Token.PromptNum: '#ansigreen bold',
728 732 Token.OutPrompt: '#ansidarkred',
729 733 Token.OutPromptNum: '#ansired bold',
730 734 })
731 735 elif legacy =='nocolor':
732 736 style_cls=_NoStyle
733 737 style_overrides = {}
734 738 else :
735 739 raise ValueError('Got unknown colors: ', legacy)
736 740 else :
737 741 if isinstance(name_or_cls, str):
738 742 style_cls = get_style_by_name(name_or_cls)
739 743 else:
740 744 style_cls = name_or_cls
741 745 style_overrides = {
742 746 Token.Prompt: '#ansigreen',
743 747 Token.PromptNum: '#ansibrightgreen bold',
744 748 Token.OutPrompt: '#ansired',
745 749 Token.OutPromptNum: '#ansibrightred bold',
746 750 }
747 751 style_overrides.update(self.highlighting_style_overrides)
748 752 style = merge_styles([
749 753 style_from_pygments_cls(style_cls),
750 754 style_from_pygments_dict(style_overrides),
751 755 ])
752 756
753 757 return style
754 758
755 759 @property
756 760 def pt_complete_style(self):
757 761 return {
758 762 'multicolumn': CompleteStyle.MULTI_COLUMN,
759 763 'column': CompleteStyle.COLUMN,
760 764 'readlinelike': CompleteStyle.READLINE_LIKE,
761 765 }[self.display_completions]
762 766
763 767 @property
764 768 def color_depth(self):
765 769 return (ColorDepth.TRUE_COLOR if self.true_color else None)
766 770
767 771 def _extra_prompt_options(self):
768 772 """
769 773 Return the current layout option for the current Terminal InteractiveShell
770 774 """
771 775 def get_message():
772 776 return PygmentsTokens(self.prompts.in_prompt_tokens())
773 777
774 778 if self.editing_mode == "emacs" and self.prompt_line_number_format == "":
775 779 # with emacs mode the prompt is (usually) static, so we call only
776 780 # the function once. With VI mode it can toggle between [ins] and
777 781 # [nor] so we can't precompute.
778 782 # here I'm going to favor the default keybinding which almost
779 783 # everybody uses to decrease CPU usage.
780 784 # if we have issues with users with custom Prompts we can see how to
781 785 # work around this.
782 786 get_message = get_message()
783 787
784 788 options = {
785 789 "complete_in_thread": False,
786 790 "lexer": IPythonPTLexer(),
787 791 "reserve_space_for_menu": self.space_for_menu,
788 792 "message": get_message,
789 793 "prompt_continuation": (
790 794 lambda width, lineno, is_soft_wrap: PygmentsTokens(
791 795 _backward_compat_continuation_prompt_tokens(
792 796 self.prompts.continuation_prompt_tokens, width, lineno=lineno
793 797 )
794 798 )
795 799 ),
796 800 "multiline": True,
797 801 "complete_style": self.pt_complete_style,
798 802 "input_processors": [
799 803 # Highlight matching brackets, but only when this setting is
800 804 # enabled, and only when the DEFAULT_BUFFER has the focus.
801 805 ConditionalProcessor(
802 806 processor=HighlightMatchingBracketProcessor(chars="[](){}"),
803 807 filter=HasFocus(DEFAULT_BUFFER)
804 808 & ~IsDone()
805 809 & Condition(lambda: self.highlight_matching_brackets),
806 810 ),
807 811 # Show auto-suggestion in lines other than the last line.
808 812 ConditionalProcessor(
809 813 processor=AppendAutoSuggestionInAnyLine(),
810 814 filter=HasFocus(DEFAULT_BUFFER)
811 815 & ~IsDone()
812 816 & Condition(
813 817 lambda: isinstance(
814 818 self.auto_suggest, NavigableAutoSuggestFromHistory
815 819 )
816 820 ),
817 821 ),
818 822 ],
819 823 }
820 824 if not PTK3:
821 825 options['inputhook'] = self.inputhook
822 826
823 827 return options
824 828
825 829 def prompt_for_code(self):
826 830 if self.rl_next_input:
827 831 default = self.rl_next_input
828 832 self.rl_next_input = None
829 833 else:
830 834 default = ''
831 835
832 836 # In order to make sure that asyncio code written in the
833 837 # interactive shell doesn't interfere with the prompt, we run the
834 838 # prompt in a different event loop.
835 839 # If we don't do this, people could spawn coroutine with a
836 840 # while/true inside which will freeze the prompt.
837 841
838 842 with patch_stdout(raw=True):
839 843 if self._use_asyncio_inputhook:
840 844 # When we integrate the asyncio event loop, run the UI in the
841 845 # same event loop as the rest of the code. don't use an actual
842 846 # input hook. (Asyncio is not made for nesting event loops.)
843 847 asyncio_loop = get_asyncio_loop()
844 848 text = asyncio_loop.run_until_complete(
845 849 self.pt_app.prompt_async(
846 850 default=default, **self._extra_prompt_options()
847 851 )
848 852 )
849 853 else:
850 854 text = self.pt_app.prompt(
851 855 default=default,
852 856 inputhook=self._inputhook,
853 857 **self._extra_prompt_options(),
854 858 )
855 859
856 860 return text
857 861
858 862 def enable_win_unicode_console(self):
859 863 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
860 864 # console by default, so WUC shouldn't be needed.
861 865 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
862 866 DeprecationWarning,
863 867 stacklevel=2)
864 868
865 869 def init_io(self):
866 870 if sys.platform not in {'win32', 'cli'}:
867 871 return
868 872
869 873 import colorama
870 874 colorama.init()
871 875
872 876 def init_magics(self):
873 877 super(TerminalInteractiveShell, self).init_magics()
874 878 self.register_magics(TerminalMagics)
875 879
876 880 def init_alias(self):
877 881 # The parent class defines aliases that can be safely used with any
878 882 # frontend.
879 883 super(TerminalInteractiveShell, self).init_alias()
880 884
881 885 # Now define aliases that only make sense on the terminal, because they
882 886 # need direct access to the console in a way that we can't emulate in
883 887 # GUI or web frontend
884 888 if os.name == 'posix':
885 889 for cmd in ('clear', 'more', 'less', 'man'):
886 890 self.alias_manager.soft_define_alias(cmd, cmd)
887 891
888 892 def __init__(self, *args, **kwargs) -> None:
889 893 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
890 894 self._set_autosuggestions(self.autosuggestions_provider)
891 895 self.init_prompt_toolkit_cli()
892 896 self.init_term_title()
893 897 self.keep_running = True
894 898 self._set_formatter(self.autoformatter)
895 899
896 900 def ask_exit(self):
897 901 self.keep_running = False
898 902
899 903 rl_next_input = None
900 904
901 905 def interact(self):
902 906 self.keep_running = True
903 907 while self.keep_running:
904 908 print(self.separate_in, end='')
905 909
906 910 try:
907 911 code = self.prompt_for_code()
908 912 except EOFError:
909 913 if (not self.confirm_exit) \
910 914 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
911 915 self.ask_exit()
912 916
913 917 else:
914 918 if code:
915 919 self.run_cell(code, store_history=True)
916 920
917 921 def mainloop(self):
918 922 # An extra layer of protection in case someone mashing Ctrl-C breaks
919 923 # out of our internal code.
920 924 while True:
921 925 try:
922 926 self.interact()
923 927 break
924 928 except KeyboardInterrupt as e:
925 929 print("\n%s escaped interact()\n" % type(e).__name__)
926 930 finally:
927 931 # An interrupt during the eventloop will mess up the
928 932 # internal state of the prompt_toolkit library.
929 933 # Stopping the eventloop fixes this, see
930 934 # https://github.com/ipython/ipython/pull/9867
931 935 if hasattr(self, '_eventloop'):
932 936 self._eventloop.stop()
933 937
934 938 self.restore_term_title()
935 939
936 940 # try to call some at-exit operation optimistically as some things can't
937 941 # be done during interpreter shutdown. this is technically inaccurate as
938 942 # this make mainlool not re-callable, but that should be a rare if not
939 943 # in existent use case.
940 944
941 945 self._atexit_once()
942 946
943 947 _inputhook = None
944 948 def inputhook(self, context):
945 949 if self._inputhook is not None:
946 950 self._inputhook(context)
947 951
948 952 active_eventloop: Optional[str] = None
949 953
950 954 def enable_gui(self, gui: Optional[str] = None) -> None:
951 955 if gui:
952 956 from ..core.pylabtools import _convert_gui_from_matplotlib
953 957
954 958 gui = _convert_gui_from_matplotlib(gui)
955 959
956 960 if self.simple_prompt is True and gui is not None:
957 961 print(
958 962 f'Cannot install event loop hook for "{gui}" when running with `--simple-prompt`.'
959 963 )
960 964 print(
961 965 "NOTE: Tk is supported natively; use Tk apps and Tk backends with `--simple-prompt`."
962 966 )
963 967 return
964 968
965 969 if self._inputhook is None and gui is None:
966 970 print("No event loop hook running.")
967 971 return
968 972
969 973 if self._inputhook is not None and gui is not None:
970 974 newev, newinhook = get_inputhook_name_and_func(gui)
971 975 if self._inputhook == newinhook:
972 976 # same inputhook, do nothing
973 977 self.log.info(
974 978 f"Shell is already running the {self.active_eventloop} eventloop. Doing nothing"
975 979 )
976 980 return
977 981 self.log.warning(
978 982 f"Shell is already running a different gui event loop for {self.active_eventloop}. "
979 983 "Call with no arguments to disable the current loop."
980 984 )
981 985 return
982 986 if self._inputhook is not None and gui is None:
983 987 self.active_eventloop = self._inputhook = None
984 988
985 989 if gui and (gui not in {None, "webagg"}):
986 990 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
987 991 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
988 992 else:
989 993 self.active_eventloop = self._inputhook = None
990 994
991 995 self._use_asyncio_inputhook = gui == "asyncio"
992 996
993 997 # Run !system commands directly, not through pipes, so terminal programs
994 998 # work correctly.
995 999 system = InteractiveShell.system_raw
996 1000
997 1001 def auto_rewrite_input(self, cmd):
998 1002 """Overridden from the parent class to use fancy rewriting prompt"""
999 1003 if not self.show_rewritten_input:
1000 1004 return
1001 1005
1002 1006 tokens = self.prompts.rewrite_prompt_tokens()
1003 1007 if self.pt_app:
1004 1008 print_formatted_text(PygmentsTokens(tokens), end='',
1005 1009 style=self.pt_app.app.style)
1006 1010 print(cmd)
1007 1011 else:
1008 1012 prompt = ''.join(s for t, s in tokens)
1009 1013 print(prompt, cmd, sep='')
1010 1014
1011 1015 _prompts_before = None
1012 1016 def switch_doctest_mode(self, mode):
1013 1017 """Switch prompts to classic for %doctest_mode"""
1014 1018 if mode:
1015 1019 self._prompts_before = self.prompts
1016 1020 self.prompts = ClassicPrompts(self)
1017 1021 elif self._prompts_before:
1018 1022 self.prompts = self._prompts_before
1019 1023 self._prompts_before = None
1020 1024 # self._update_layout()
1021 1025
1022 1026
1023 1027 InteractiveShellABC.register(TerminalInteractiveShell)
1024 1028
1025 1029 if __name__ == '__main__':
1026 1030 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now