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