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