Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/terminal/interactiveshell.py: 36%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

450 statements  

1"""IPython terminal interface using prompt_toolkit""" 

2 

3import os 

4import sys 

5import inspect 

6from warnings import warn 

7from typing import Union as UnionType, Optional 

8 

9from IPython.core.async_helpers import get_asyncio_loop 

10from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC 

11from IPython.core.kitty import ( 

12 display_formatter_default_active_types, 

13 terminal_default_mime_renderers, 

14) 

15from IPython.utils.py3compat import input 

16from IPython.utils.PyColorize import theme_table 

17from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title 

18from IPython.utils.process import abbrev_cwd 

19from traitlets import ( 

20 Any, 

21 Bool, 

22 Dict, 

23 Enum, 

24 Float, 

25 Instance, 

26 Integer, 

27 List, 

28 Type, 

29 Unicode, 

30 Union, 

31 default, 

32 observe, 

33 validate, 

34 DottedObjectName, 

35) 

36from traitlets.utils.importstring import import_item 

37 

38 

39from prompt_toolkit.auto_suggest import AutoSuggestFromHistory 

40from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode 

41from prompt_toolkit.filters import HasFocus, Condition, IsDone 

42from prompt_toolkit.formatted_text import PygmentsTokens 

43from prompt_toolkit.history import History 

44from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor 

45from prompt_toolkit.output import ColorDepth 

46from prompt_toolkit.patch_stdout import patch_stdout 

47from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text 

48from prompt_toolkit.styles import DynamicStyle, merge_styles 

49from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict 

50from pygments.styles import get_style_by_name 

51from pygments.style import Style 

52 

53from .debugger import TerminalPdb, Pdb 

54from .magics import TerminalMagics 

55from .pt_inputhooks import get_inputhook_name_and_func 

56from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook 

57from .ptutils import IPythonPTCompleter, IPythonPTLexer 

58from .shortcuts import ( 

59 KEY_BINDINGS, 

60 UNASSIGNED_ALLOWED_COMMANDS, 

61 create_ipython_shortcuts, 

62 create_identifier, 

63 RuntimeBinding, 

64 add_binding, 

65) 

66from .shortcuts.filters import KEYBINDING_FILTERS, filter_from_string 

67from .shortcuts.auto_suggest import ( 

68 NavigableAutoSuggestFromHistory, 

69 AppendAutoSuggestionInAnyLine, 

70) 

71 

72 

73class _NoStyle(Style): 

74 pass 

75 

76 

77 

78def _backward_compat_continuation_prompt_tokens( 

79 method, width: int, *, lineno: int, wrap_count: int 

80): 

81 """ 

82 Sagemath use custom prompt and we broke them in 8.19. 

83 

84 make sure to pass only width if method only support width 

85 """ 

86 sig = inspect.signature(method) 

87 extra = {} 

88 params = inspect.signature(method).parameters 

89 if "lineno" in inspect.signature(method).parameters or any( 

90 [p.kind == p.VAR_KEYWORD for p in sig.parameters.values()] 

91 ): 

92 extra["lineno"] = lineno 

93 if "line_number" in inspect.signature(method).parameters or any( 

94 [p.kind == p.VAR_KEYWORD for p in sig.parameters.values()] 

95 ): 

96 extra["line_number"] = lineno 

97 

98 if "wrap_count" in inspect.signature(method).parameters or any( 

99 [p.kind == p.VAR_KEYWORD for p in sig.parameters.values()] 

100 ): 

101 extra["wrap_count"] = wrap_count 

102 return method(width, **extra) 

103 

104 

105def get_default_editor(): 

106 try: 

107 return os.environ['EDITOR'] 

108 except KeyError: 

109 pass 

110 except UnicodeError: 

111 warn("$EDITOR environment variable is not pure ASCII. Using platform " 

112 "default editor.") 

113 

114 if os.name == 'posix': 

115 return 'vi' # the only one guaranteed to be there! 

116 else: 

117 return "notepad" # same in Windows! 

118 

119 

120# conservatively check for tty 

121# overridden streams can result in things like: 

122# - sys.stdin = None 

123# - no isatty method 

124for _name in ('stdin', 'stdout', 'stderr'): 

125 _stream = getattr(sys, _name) 

126 try: 

127 if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty(): 

128 _is_tty = False 

129 break 

130 except ValueError: 

131 # stream is closed 

132 _is_tty = False 

133 break 

134else: 

135 _is_tty = True 

136 

137 

138_use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty) 

139 

140def black_reformat_handler(text_before_cursor): 

141 """ 

142 We do not need to protect against error, 

143 this is taken care at a higher level where any reformat error is ignored. 

144 Indeed we may call reformatting on incomplete code. 

145 """ 

146 import black 

147 

148 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode()) 

149 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"): 

150 formatted_text = formatted_text[:-1] 

151 return formatted_text 

152 

153 

154def yapf_reformat_handler(text_before_cursor): 

155 from yapf.yapflib import file_resources 

156 from yapf.yapflib import yapf_api 

157 

158 style_config = file_resources.GetDefaultStyleForDir(os.getcwd()) 

159 formatted_text, was_formatted = yapf_api.FormatCode( 

160 text_before_cursor, style_config=style_config 

161 ) 

162 if was_formatted: 

163 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"): 

164 formatted_text = formatted_text[:-1] 

165 return formatted_text 

166 else: 

167 return text_before_cursor 

168 

169 

170class PtkHistoryAdapter(History): 

171 """ 

172 Prompt toolkit has it's own way of handling history, Where it assumes it can 

173 Push/pull from history. 

174 

175 """ 

176 

177 def __init__(self, shell): 

178 super().__init__() 

179 self.shell = shell 

180 self._refresh() 

181 

182 def append_string(self, string): 

183 # we rely on sql for that. 

184 self._loaded = False 

185 self._refresh() 

186 

187 def _refresh(self): 

188 if not self._loaded: 

189 self._loaded_strings = list(self.load_history_strings()) 

190 

191 def load_history_strings(self): 

192 last_cell = "" 

193 res = [] 

194 for __, ___, cell in self.shell.history_manager.get_tail( 

195 self.shell.history_load_length, include_latest=True 

196 ): 

197 # Ignore blank lines and consecutive duplicates 

198 cell = cell.rstrip() 

199 if cell and (cell != last_cell): 

200 res.append(cell) 

201 last_cell = cell 

202 yield from res[::-1] 

203 

204 def store_string(self, string: str) -> None: 

205 pass 

206 

207class TerminalInteractiveShell(InteractiveShell): 

208 mime_renderers = Dict().tag(config=True) 

209 

210 min_elide = Integer( 

211 30, help="minimum characters for filling with ellipsis in file completions" 

212 ).tag(config=True) 

213 space_for_menu = Integer( 

214 6, 

215 help="Number of line at the bottom of the screen " 

216 "to reserve for the tab completion menu, " 

217 "search history, ...etc, the height of " 

218 "these menus will at most this value. " 

219 "Increase it is you prefer long and skinny " 

220 "menus, decrease for short and wide.", 

221 ).tag(config=True) 

222 

223 pt_app: UnionType[PromptSession, None] = None 

224 auto_suggest: UnionType[ 

225 AutoSuggestFromHistory, 

226 NavigableAutoSuggestFromHistory, 

227 None, 

228 ] = None 

229 debugger_history = None 

230 

231 debugger_history_file = Unicode( 

232 "~/.pdbhistory", help="File in which to store and read history" 

233 ).tag(config=True) 

234 

235 simple_prompt = Bool(_use_simple_prompt, 

236 help="""Use `raw_input` for the REPL, without completion and prompt colors. 

237 

238 Useful when controlling IPython as a subprocess, and piping 

239 STDIN/OUT/ERR. Known usage are: IPython's own testing machinery, 

240 and emacs' inferior-python subprocess (assuming you have set 

241 `python-shell-interpreter` to "ipython") available through the 

242 built-in `M-x run-python` and third party packages such as elpy. 

243 

244 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT` 

245 environment variable is set, or the current terminal is not a tty. 

246 Thus the Default value reported in --help-all, or config will often 

247 be incorrectly reported. 

248 """, 

249 ).tag(config=True) 

250 

251 @property 

252 def debugger_cls(self): 

253 return Pdb if self.simple_prompt else TerminalPdb 

254 

255 confirm_exit = Bool(True, 

256 help=""" 

257 Set to confirm when you try to exit IPython with an EOF (Control-D 

258 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', 

259 you can force a direct exit without any confirmation.""", 

260 ).tag(config=True) 

261 

262 editing_mode = Unicode('emacs', 

263 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", 

264 ).tag(config=True) 

265 

266 emacs_bindings_in_vi_insert_mode = Bool( 

267 True, 

268 help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.", 

269 ).tag(config=True) 

270 

271 modal_cursor = Bool( 

272 True, 

273 help=""" 

274 Cursor shape changes depending on vi mode: beam in vi insert mode, 

275 block in nav mode, underscore in replace mode.""", 

276 ).tag(config=True) 

277 

278 ttimeoutlen = Float( 

279 0.01, 

280 help="""The time in milliseconds that is waited for a key code 

281 to complete.""", 

282 ).tag(config=True) 

283 

284 timeoutlen = Float( 

285 0.5, 

286 help="""The time in milliseconds that is waited for a mapped key 

287 sequence to complete.""", 

288 ).tag(config=True) 

289 

290 autoformatter = Unicode( 

291 None, 

292 help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`", 

293 allow_none=True 

294 ).tag(config=True) 

295 

296 auto_match = Bool( 

297 False, 

298 help=""" 

299 Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted. 

300 Brackets: (), [], {} 

301 Quotes: '', \"\" 

302 """, 

303 ).tag(config=True) 

304 

305 mouse_support = Bool(False, 

306 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)" 

307 ).tag(config=True) 

308 

309 # We don't load the list of styles for the help string, because loading 

310 # Pygments plugins takes time and can cause unexpected errors. 

311 highlighting_style = Union( 

312 [Unicode("legacy"), Type(klass=Style)], 

313 help="""Deprecated, and has not effect, use IPython themes 

314 

315 The name or class of a Pygments style to use for syntax 

316 highlighting. To see available styles, run `pygmentize -L styles`.""", 

317 ).tag(config=True) 

318 

319 @validate('editing_mode') 

320 def _validate_editing_mode(self, proposal): 

321 if proposal['value'].lower() == 'vim': 

322 proposal['value']= 'vi' 

323 elif proposal['value'].lower() == 'default': 

324 proposal['value']= 'emacs' 

325 

326 if hasattr(EditingMode, proposal['value'].upper()): 

327 return proposal['value'].lower() 

328 

329 return self.editing_mode 

330 

331 @observe('editing_mode') 

332 def _editing_mode(self, change): 

333 if self.pt_app: 

334 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper()) 

335 

336 def _set_formatter(self, formatter): 

337 if formatter is None: 

338 self.reformat_handler = lambda x:x 

339 elif formatter == 'black': 

340 self.reformat_handler = black_reformat_handler 

341 elif formatter == "yapf": 

342 self.reformat_handler = yapf_reformat_handler 

343 else: 

344 raise ValueError 

345 

346 @observe("autoformatter") 

347 def _autoformatter_changed(self, change): 

348 formatter = change.new 

349 self._set_formatter(formatter) 

350 

351 @observe('highlighting_style') 

352 @observe('colors') 

353 def _highlighting_style_changed(self, change): 

354 assert change.new == change.new.lower() 

355 if change.new != "legacy": 

356 warn( 

357 "highlighting_style is deprecated since 9.0 and have no effect, use themeing." 

358 ) 

359 return 

360 

361 def refresh_style(self): 

362 self._style = self._make_style_from_name_or_cls("legacy") 

363 

364 # TODO: deprecate this 

365 highlighting_style_overrides = Dict( 

366 help="Override highlighting format for specific tokens" 

367 ).tag(config=True) 

368 

369 true_color = Bool(False, 

370 help="""Use 24bit colors instead of 256 colors in prompt highlighting. 

371 If your terminal supports true color, the following command should 

372 print ``TRUECOLOR`` in orange:: 

373 

374 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\" 

375 """, 

376 ).tag(config=True) 

377 

378 editor = Unicode(get_default_editor(), 

379 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)." 

380 ).tag(config=True) 

381 

382 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True) 

383 

384 prompts = Instance(Prompts) 

385 

386 @default('prompts') 

387 def _prompts_default(self): 

388 return self.prompts_class(self) 

389 

390# @observe('prompts') 

391# def _(self, change): 

392# self._update_layout() 

393 

394 @default('displayhook_class') 

395 def _displayhook_class_default(self): 

396 return RichPromptDisplayHook 

397 

398 term_title = Bool(True, 

399 help="Automatically set the terminal title" 

400 ).tag(config=True) 

401 

402 term_title_format = Unicode("IPython: {cwd}", 

403 help="Customize the terminal title format. This is a python format string. " + 

404 "Available substitutions are: {cwd}." 

405 ).tag(config=True) 

406 

407 display_completions = Enum(('column', 'multicolumn','readlinelike'), 

408 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and " 

409 "'readlinelike'. These options are for `prompt_toolkit`, see " 

410 "`prompt_toolkit` documentation for more information." 

411 ), 

412 default_value='multicolumn').tag(config=True) 

413 

414 highlight_matching_brackets = Bool(True, 

415 help="Highlight matching brackets.", 

416 ).tag(config=True) 

417 

418 extra_open_editor_shortcuts = Bool(False, 

419 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. " 

420 "This is in addition to the F2 binding, which is always enabled." 

421 ).tag(config=True) 

422 

423 handle_return = Any(None, 

424 help="Provide an alternative handler to be called when the user presses " 

425 "Return. This is an advanced option intended for debugging, which " 

426 "may be changed or removed in later releases." 

427 ).tag(config=True) 

428 

429 enable_history_search = Bool(True, 

430 help="Allows to enable/disable the prompt toolkit history search" 

431 ).tag(config=True) 

432 

433 autosuggestions_provider = Unicode( 

434 "NavigableAutoSuggestFromHistory", 

435 help="Specifies from which source automatic suggestions are provided. " 

436 "Can be set to ``'NavigableAutoSuggestFromHistory'`` (:kbd:`up` and " 

437 ":kbd:`down` swap suggestions), ``'AutoSuggestFromHistory'``, " 

438 " or ``None`` to disable automatic suggestions. " 

439 "Default is `'NavigableAutoSuggestFromHistory`'.", 

440 allow_none=True, 

441 ).tag(config=True) 

442 _autosuggestions_provider: Any 

443 

444 llm_constructor_kwargs = Dict( 

445 {}, 

446 help=""" 

447 Extra arguments to pass to `llm_provider_class` constructor. 

448 

449 This is used to – for example – set the `model_id`""", 

450 ).tag(config=True) 

451 

452 llm_prefix_from_history = DottedObjectName( 

453 "input_history", 

454 help="""\ 

455 Fully Qualifed name of a function that takes an IPython history manager and 

456 return a prefix to pass the llm provider in addition to the current buffer 

457 text. 

458 

459 You can use: 

460 

461 - no_prefix 

462 - input_history 

463 

464 As default value. `input_history` (default), will use all the input history 

465 of current IPython session 

466 

467 """, 

468 ).tag(config=True) 

469 _llm_prefix_from_history: Any 

470 

471 @observe("llm_prefix_from_history") 

472 def _llm_prefix_from_history_changed(self, change): 

473 name = change.new 

474 self._llm_prefix_from_history = name 

475 self._set_autosuggestions() 

476 

477 llm_provider_class = DottedObjectName( 

478 None, 

479 allow_none=True, 

480 help="""\ 

481 Provisional: 

482 This is a provisional API in IPython 8.32, before stabilisation 

483 in 9.0, it may change without warnings. 

484 

485 class to use for the `NavigableAutoSuggestFromHistory` to request 

486 completions from a LLM, this should inherit from 

487 `jupyter_ai_magics:BaseProvider` and implement 

488 `stream_inline_completions` 

489 """, 

490 ).tag(config=True) 

491 _llm_provider_class: Any = None 

492 

493 @observe("llm_provider_class") 

494 def _llm_provider_class_changed(self, change): 

495 provider_class = change.new 

496 self._llm_provider_class = provider_class 

497 self._set_autosuggestions() 

498 

499 def _set_autosuggestions(self, provider=None): 

500 if provider is None: 

501 provider = self.autosuggestions_provider 

502 # disconnect old handler 

503 if self.auto_suggest and isinstance( 

504 self.auto_suggest, NavigableAutoSuggestFromHistory 

505 ): 

506 self.auto_suggest.disconnect() 

507 if provider is None: 

508 self.auto_suggest = None 

509 elif provider == "AutoSuggestFromHistory": 

510 self.auto_suggest = AutoSuggestFromHistory() 

511 elif provider == "NavigableAutoSuggestFromHistory": 

512 # LLM stuff are all Provisional in 8.32 

513 if self._llm_provider_class: 

514 

515 def init_llm_provider(): 

516 llm_provider_constructor = import_item(self._llm_provider_class) 

517 return llm_provider_constructor(**self.llm_constructor_kwargs) 

518 

519 else: 

520 init_llm_provider = None 

521 self.auto_suggest = NavigableAutoSuggestFromHistory() 

522 # Provisinal in 8.32 

523 self.auto_suggest._init_llm_provider = init_llm_provider 

524 

525 name = self.llm_prefix_from_history 

526 

527 if name == "no_prefix": 

528 

529 def no_prefix(history_manager): 

530 return "" 

531 

532 fun = no_prefix 

533 

534 elif name == "input_history": 

535 

536 def input_history(history_manager): 

537 return "\n".join([s[2] for s in history_manager.get_range()]) + "\n" 

538 

539 fun = input_history 

540 

541 else: 

542 fun = import_item(name) 

543 self.auto_suggest._llm_prefixer = fun 

544 else: 

545 raise ValueError("No valid provider.") 

546 if self.pt_app: 

547 self.pt_app.auto_suggest = self.auto_suggest 

548 

549 @observe("autosuggestions_provider") 

550 def _autosuggestions_provider_changed(self, change): 

551 provider = change.new 

552 self._set_autosuggestions(provider) 

553 

554 shortcuts = List( 

555 trait=Dict( 

556 key_trait=Enum( 

557 [ 

558 "command", 

559 "match_keys", 

560 "match_filter", 

561 "new_keys", 

562 "new_filter", 

563 "create", 

564 ] 

565 ), 

566 per_key_traits={ 

567 "command": Unicode(), 

568 "match_keys": List(Unicode()), 

569 "match_filter": Unicode(), 

570 "new_keys": List(Unicode()), 

571 "new_filter": Unicode(), 

572 "create": Bool(False), 

573 }, 

574 ), 

575 help=""" 

576 Add, disable or modifying shortcuts. 

577 

578 Each entry on the list should be a dictionary with ``command`` key 

579 identifying the target function executed by the shortcut and at least 

580 one of the following: 

581 

582 - ``match_keys``: list of keys used to match an existing shortcut, 

583 - ``match_filter``: shortcut filter used to match an existing shortcut, 

584 - ``new_keys``: list of keys to set, 

585 - ``new_filter``: a new shortcut filter to set 

586 

587 The filters have to be composed of pre-defined verbs and joined by one 

588 of the following conjunctions: ``&`` (and), ``|`` (or), ``~`` (not). 

589 The pre-defined verbs are: 

590 

591 {filters} 

592 

593 To disable a shortcut set ``new_keys`` to an empty list. 

594 To add a shortcut add key ``create`` with value ``True``. 

595 

596 When modifying/disabling shortcuts, ``match_keys``/``match_filter`` can 

597 be omitted if the provided specification uniquely identifies a shortcut 

598 to be modified/disabled. When modifying a shortcut ``new_filter`` or 

599 ``new_keys`` can be omitted which will result in reuse of the existing 

600 filter/keys. 

601 

602 Only shortcuts defined in IPython (and not default prompt-toolkit 

603 shortcuts) can be modified or disabled. The full list of shortcuts, 

604 command identifiers and filters is available under 

605 :ref:`terminal-shortcuts-list`. 

606 

607 Here is an example: 

608 

609 .. code:: 

610 

611 c.TerminalInteractiveShell.shortcuts = [ 

612 {{ 

613 "new_keys": ["c-q"], 

614 "command": "prompt_toolkit:named_commands.capitalize_word", 

615 "create": True, 

616 }}, 

617 {{ 

618 "new_keys": ["c-j"], 

619 "command": "prompt_toolkit:named_commands.beginning_of_line", 

620 "create": True, 

621 }}, 

622 ] 

623 

624 

625 """.format( 

626 filters="\n ".join([f" - ``{k}``" for k in KEYBINDING_FILTERS]) 

627 ), 

628 ).tag(config=True) 

629 

630 @observe("shortcuts") 

631 def _shortcuts_changed(self, change): 

632 if self.pt_app: 

633 self.pt_app.key_bindings = self._merge_shortcuts(user_shortcuts=change.new) 

634 

635 def _merge_shortcuts(self, user_shortcuts): 

636 # rebuild the bindings list from scratch 

637 key_bindings = create_ipython_shortcuts(self) 

638 

639 # for now we only allow adding shortcuts for a specific set of 

640 # commands; this is a security precution. 

641 allowed_commands = { 

642 create_identifier(binding.command): binding.command 

643 for binding in KEY_BINDINGS 

644 } 

645 allowed_commands.update( 

646 { 

647 create_identifier(command): command 

648 for command in UNASSIGNED_ALLOWED_COMMANDS 

649 } 

650 ) 

651 shortcuts_to_skip = [] 

652 shortcuts_to_add = [] 

653 

654 for shortcut in user_shortcuts: 

655 command_id = shortcut["command"] 

656 if command_id not in allowed_commands: 

657 allowed_commands = "\n - ".join(allowed_commands) 

658 raise ValueError( 

659 f"{command_id} is not a known shortcut command." 

660 f" Allowed commands are: \n - {allowed_commands}" 

661 ) 

662 old_keys = shortcut.get("match_keys", None) 

663 old_filter = ( 

664 filter_from_string(shortcut["match_filter"]) 

665 if "match_filter" in shortcut 

666 else None 

667 ) 

668 matching = [ 

669 binding 

670 for binding in KEY_BINDINGS 

671 if ( 

672 (old_filter is None or binding.filter == old_filter) 

673 and (old_keys is None or [k for k in binding.keys] == old_keys) 

674 and create_identifier(binding.command) == command_id 

675 ) 

676 ] 

677 

678 new_keys = shortcut.get("new_keys", None) 

679 new_filter = shortcut.get("new_filter", None) 

680 

681 command = allowed_commands[command_id] 

682 

683 creating_new = shortcut.get("create", False) 

684 modifying_existing = not creating_new and ( 

685 new_keys is not None or new_filter 

686 ) 

687 

688 if creating_new and new_keys == []: 

689 raise ValueError("Cannot add a shortcut without keys") 

690 

691 if modifying_existing: 

692 specification = { 

693 key: shortcut[key] 

694 for key in ["command", "filter"] 

695 if key in shortcut 

696 } 

697 if len(matching) == 0: 

698 raise ValueError( 

699 f"No shortcuts matching {specification} found in {KEY_BINDINGS}" 

700 ) 

701 elif len(matching) > 1: 

702 raise ValueError( 

703 f"Multiple shortcuts matching {specification} found," 

704 f" please add keys/filter to select one of: {matching}" 

705 ) 

706 

707 matched = matching[0] 

708 old_filter = matched.filter 

709 old_keys = list(matched.keys) 

710 shortcuts_to_skip.append( 

711 RuntimeBinding( 

712 command, 

713 keys=old_keys, 

714 filter=old_filter, 

715 ) 

716 ) 

717 

718 if new_keys != []: 

719 shortcuts_to_add.append( 

720 RuntimeBinding( 

721 command, 

722 keys=new_keys or old_keys, 

723 filter=( 

724 filter_from_string(new_filter) 

725 if new_filter is not None 

726 else ( 

727 old_filter 

728 if old_filter is not None 

729 else filter_from_string("always") 

730 ) 

731 ), 

732 ) 

733 ) 

734 

735 # rebuild the bindings list from scratch 

736 key_bindings = create_ipython_shortcuts(self, skip=shortcuts_to_skip) 

737 for binding in shortcuts_to_add: 

738 add_binding(key_bindings, binding) 

739 

740 return key_bindings 

741 

742 prompt_includes_vi_mode = Bool(True, 

743 help="Display the current vi mode (when using vi editing mode)." 

744 ).tag(config=True) 

745 

746 prompt_line_number_format = Unicode( 

747 "", 

748 help="The format for line numbering, will be passed `line` (int, 1 based)" 

749 " the current line number and `rel_line` the relative line number." 

750 " for example to display both you can use the following template string :" 

751 " c.TerminalInteractiveShell.prompt_line_number_format='{line: 4d}/{rel_line:+03d} | '" 

752 " This will display the current line number, with leading space and a width of at least 4" 

753 " character, as well as the relative line number 0 padded and always with a + or - sign." 

754 " Note that when using Emacs mode the prompt of the first line may not update.", 

755 ).tag(config=True) 

756 

757 @observe('term_title') 

758 def init_term_title(self, change=None): 

759 # Enable or disable the terminal title. 

760 if self.term_title and _is_tty: 

761 toggle_set_term_title(True) 

762 set_term_title(self.term_title_format.format(cwd=abbrev_cwd())) 

763 else: 

764 toggle_set_term_title(False) 

765 

766 def restore_term_title(self): 

767 if self.term_title and _is_tty: 

768 restore_term_title() 

769 

770 def init_display_formatter(self): 

771 super(TerminalInteractiveShell, self).init_display_formatter() 

772 # terminal only supports plain text if not explicitly configured 

773 config = self.display_formatter._trait_values["config"] 

774 if not ( 

775 "DisplayFormatter" in config 

776 and "active_types" in config["DisplayFormatter"] 

777 ): 

778 self.display_formatter.active_types = display_formatter_default_active_types 

779 if not ( 

780 "TerminalInteractiveShell" in config 

781 and "mime_renderers" in config["TerminalInteractiveShell"] 

782 ): 

783 self.mime_renderers = terminal_default_mime_renderers 

784 

785 def init_prompt_toolkit_cli(self): 

786 if self.simple_prompt: 

787 # Fall back to plain non-interactive output for tests. 

788 # This is very limited. 

789 def prompt(): 

790 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens()) 

791 lines = [input(prompt_text)] 

792 prompt_continuation = "".join( 

793 x[1] for x in self.prompts.continuation_prompt_tokens() 

794 ) 

795 while self.check_complete("\n".join(lines))[0] == "incomplete": 

796 lines.append(input(prompt_continuation)) 

797 return "\n".join(lines) 

798 

799 self.prompt_for_code = prompt 

800 return 

801 

802 # Set up keyboard shortcuts 

803 key_bindings = self._merge_shortcuts(user_shortcuts=self.shortcuts) 

804 

805 # Pre-populate history from IPython's history database 

806 history = PtkHistoryAdapter(self) 

807 

808 self.refresh_style() 

809 ptk_s = DynamicStyle(lambda: self._style) 

810 

811 editing_mode = getattr(EditingMode, self.editing_mode.upper()) 

812 

813 self._use_asyncio_inputhook = False 

814 self.pt_app = PromptSession( 

815 auto_suggest=self.auto_suggest, 

816 editing_mode=editing_mode, 

817 key_bindings=key_bindings, 

818 history=history, 

819 completer=IPythonPTCompleter(shell=self), 

820 enable_history_search=self.enable_history_search, 

821 style=ptk_s, 

822 include_default_pygments_style=False, 

823 mouse_support=self.mouse_support, 

824 enable_open_in_editor=self.extra_open_editor_shortcuts, 

825 color_depth=self.color_depth, 

826 tempfile_suffix=".py", 

827 **self._extra_prompt_options(), 

828 ) 

829 if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory): 

830 self.auto_suggest.connect(self.pt_app) 

831 

832 def _make_style_from_name_or_cls(self, name_or_cls): 

833 """ 

834 Small wrapper that make an IPython compatible style from a style name 

835 

836 We need that to add style for prompt ... etc. 

837 """ 

838 assert name_or_cls == "legacy" 

839 legacy = self.colors.lower() 

840 

841 theme = theme_table.get(legacy, None) 

842 assert theme is not None, legacy 

843 

844 if legacy == "nocolor": 

845 style_overrides = {} 

846 style_cls = _NoStyle 

847 else: 

848 style_overrides = {**theme.extra_style, **self.highlighting_style_overrides} 

849 if theme.base is not None: 

850 style_cls = get_style_by_name(theme.base) 

851 else: 

852 style_cls = _NoStyle 

853 

854 style = merge_styles( 

855 [ 

856 style_from_pygments_cls(style_cls), 

857 style_from_pygments_dict(style_overrides), 

858 ] 

859 ) 

860 

861 return style 

862 

863 @property 

864 def pt_complete_style(self): 

865 return { 

866 'multicolumn': CompleteStyle.MULTI_COLUMN, 

867 'column': CompleteStyle.COLUMN, 

868 'readlinelike': CompleteStyle.READLINE_LIKE, 

869 }[self.display_completions] 

870 

871 @property 

872 def color_depth(self): 

873 return (ColorDepth.TRUE_COLOR if self.true_color else None) 

874 

875 def _ptk_prompt_cont(self, width: int, line_number: int, wrap_count: int): 

876 return PygmentsTokens( 

877 _backward_compat_continuation_prompt_tokens( 

878 self.prompts.continuation_prompt_tokens, 

879 width, 

880 lineno=line_number, 

881 wrap_count=wrap_count, 

882 ) 

883 ) 

884 

885 def _extra_prompt_options(self): 

886 """ 

887 Return the current layout option for the current Terminal InteractiveShell 

888 """ 

889 def get_message(): 

890 return PygmentsTokens(self.prompts.in_prompt_tokens()) 

891 

892 if self.editing_mode == "emacs" and self.prompt_line_number_format == "": 

893 # with emacs mode the prompt is (usually) static, so we call only 

894 # the function once. With VI mode it can toggle between [ins] and 

895 # [nor] so we can't precompute. 

896 # here I'm going to favor the default keybinding which almost 

897 # everybody uses to decrease CPU usage. 

898 # if we have issues with users with custom Prompts we can see how to 

899 # work around this. 

900 get_message = get_message() 

901 

902 options = { 

903 "complete_in_thread": False, 

904 "lexer": IPythonPTLexer(), 

905 "reserve_space_for_menu": self.space_for_menu, 

906 "message": get_message, 

907 "prompt_continuation": self._ptk_prompt_cont, 

908 "multiline": True, 

909 "complete_style": self.pt_complete_style, 

910 "input_processors": [ 

911 # Highlight matching brackets, but only when this setting is 

912 # enabled, and only when the DEFAULT_BUFFER has the focus. 

913 ConditionalProcessor( 

914 processor=HighlightMatchingBracketProcessor(chars="[](){}"), 

915 filter=HasFocus(DEFAULT_BUFFER) 

916 & ~IsDone() 

917 & Condition(lambda: self.highlight_matching_brackets), 

918 ), 

919 # Show auto-suggestion in lines other than the last line. 

920 ConditionalProcessor( 

921 processor=AppendAutoSuggestionInAnyLine(), 

922 filter=HasFocus(DEFAULT_BUFFER) 

923 & ~IsDone() 

924 & Condition( 

925 lambda: isinstance( 

926 self.auto_suggest, 

927 NavigableAutoSuggestFromHistory, 

928 ) 

929 ), 

930 ), 

931 ], 

932 } 

933 

934 return options 

935 

936 def prompt_for_code(self): 

937 if self.rl_next_input: 

938 default = self.rl_next_input 

939 self.rl_next_input = None 

940 else: 

941 default = '' 

942 

943 # In order to make sure that asyncio code written in the 

944 # interactive shell doesn't interfere with the prompt, we run the 

945 # prompt in a different event loop. 

946 # If we don't do this, people could spawn coroutine with a 

947 # while/true inside which will freeze the prompt. 

948 

949 with patch_stdout(raw=True): 

950 if self._use_asyncio_inputhook: 

951 # When we integrate the asyncio event loop, run the UI in the 

952 # same event loop as the rest of the code. don't use an actual 

953 # input hook. (Asyncio is not made for nesting event loops.) 

954 asyncio_loop = get_asyncio_loop() 

955 text = asyncio_loop.run_until_complete( 

956 self.pt_app.prompt_async( 

957 default=default, **self._extra_prompt_options() 

958 ) 

959 ) 

960 else: 

961 text = self.pt_app.prompt( 

962 default=default, 

963 inputhook=self._inputhook, 

964 **self._extra_prompt_options(), 

965 ) 

966 

967 return text 

968 

969 def init_io(self): 

970 if sys.platform not in {'win32', 'cli'}: 

971 return 

972 

973 import colorama 

974 colorama.init() 

975 

976 def init_magics(self): 

977 super(TerminalInteractiveShell, self).init_magics() 

978 self.register_magics(TerminalMagics) 

979 

980 def init_alias(self): 

981 # The parent class defines aliases that can be safely used with any 

982 # frontend. 

983 super(TerminalInteractiveShell, self).init_alias() 

984 

985 # Now define aliases that only make sense on the terminal, because they 

986 # need direct access to the console in a way that we can't emulate in 

987 # GUI or web frontend 

988 if os.name == 'posix': 

989 for cmd in ('clear', 'more', 'less', 'man'): 

990 self.alias_manager.soft_define_alias(cmd, cmd) 

991 

992 def __init__(self, *args, **kwargs) -> None: 

993 super().__init__(*args, **kwargs) 

994 self._set_autosuggestions(self.autosuggestions_provider) 

995 self.init_prompt_toolkit_cli() 

996 self.init_term_title() 

997 self.keep_running = True 

998 self._set_formatter(self.autoformatter) 

999 

1000 def ask_exit(self): 

1001 self.keep_running = False 

1002 

1003 rl_next_input = None 

1004 

1005 def interact(self): 

1006 self.keep_running = True 

1007 while self.keep_running: 

1008 print(self.separate_in, end='') 

1009 

1010 try: 

1011 code = self.prompt_for_code() 

1012 except EOFError: 

1013 if (not self.confirm_exit) \ 

1014 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'): 

1015 self.ask_exit() 

1016 

1017 else: 

1018 if code: 

1019 self.run_cell(code, store_history=True) 

1020 

1021 def mainloop(self): 

1022 # An extra layer of protection in case someone mashing Ctrl-C breaks 

1023 # out of our internal code. 

1024 while True: 

1025 try: 

1026 self.interact() 

1027 break 

1028 except KeyboardInterrupt as e: 

1029 print("\n%s escaped interact()\n" % type(e).__name__) 

1030 finally: 

1031 # An interrupt during the eventloop will mess up the 

1032 # internal state of the prompt_toolkit library. 

1033 # Stopping the eventloop fixes this, see 

1034 # https://github.com/ipython/ipython/pull/9867 

1035 if hasattr(self, '_eventloop'): 

1036 self._eventloop.stop() 

1037 

1038 self.restore_term_title() 

1039 

1040 # try to call some at-exit operation optimistically as some things can't 

1041 # be done during interpreter shutdown. this is technically inaccurate as 

1042 # this make mainlool not re-callable, but that should be a rare if not 

1043 # in existent use case. 

1044 

1045 self._atexit_once() 

1046 

1047 _inputhook = None 

1048 def inputhook(self, context): 

1049 warn( 

1050 "inputkook seem unused, and marked for deprecation/Removal as of IPython 9.0. " 

1051 "Please open an issue if you are using it.", 

1052 category=DeprecationWarning, 

1053 stacklevel=2, 

1054 ) 

1055 if self._inputhook is not None: 

1056 self._inputhook(context) 

1057 

1058 active_eventloop: Optional[str] = None 

1059 

1060 def enable_gui(self, gui: Optional[str] = None) -> None: 

1061 if gui: 

1062 from ..core.pylabtools import _convert_gui_from_matplotlib 

1063 

1064 gui = _convert_gui_from_matplotlib(gui) 

1065 

1066 if self.simple_prompt is True and gui is not None: 

1067 print( 

1068 f'Cannot install event loop hook for "{gui}" when running with `--simple-prompt`.' 

1069 ) 

1070 print( 

1071 "NOTE: Tk is supported natively; use Tk apps and Tk backends with `--simple-prompt`." 

1072 ) 

1073 return 

1074 

1075 if self._inputhook is None and gui is None: 

1076 print("No event loop hook running.") 

1077 return 

1078 

1079 if self._inputhook is not None and gui is not None: 

1080 newev, newinhook = get_inputhook_name_and_func(gui) 

1081 if self._inputhook == newinhook: 

1082 # same inputhook, do nothing 

1083 self.log.info( 

1084 f"Shell is already running the {self.active_eventloop} eventloop. Doing nothing" 

1085 ) 

1086 return 

1087 self.log.warning( 

1088 f"Shell is already running a different gui event loop for {self.active_eventloop}. " 

1089 "Call with no arguments to disable the current loop." 

1090 ) 

1091 return 

1092 if self._inputhook is not None and gui is None: 

1093 self.active_eventloop = self._inputhook = None 

1094 

1095 if gui and (gui not in {None, "webagg"}): 

1096 # This hook runs with each cycle of the `prompt_toolkit`'s event loop. 

1097 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui) 

1098 else: 

1099 self.active_eventloop = self._inputhook = None 

1100 

1101 self._use_asyncio_inputhook = gui == "asyncio" 

1102 

1103 # Run !system commands directly, not through pipes, so terminal programs 

1104 # work correctly. 

1105 system = InteractiveShell.system_raw 

1106 

1107 def auto_rewrite_input(self, cmd): 

1108 """Overridden from the parent class to use fancy rewriting prompt""" 

1109 if not self.show_rewritten_input: 

1110 return 

1111 

1112 tokens = self.prompts.rewrite_prompt_tokens() 

1113 if self.pt_app: 

1114 print_formatted_text(PygmentsTokens(tokens), end='', 

1115 style=self.pt_app.app.style) 

1116 print(cmd) 

1117 else: 

1118 prompt = ''.join(s for t, s in tokens) 

1119 print(prompt, cmd, sep='') 

1120 

1121 _prompts_before = None 

1122 def switch_doctest_mode(self, mode): 

1123 """Switch prompts to classic for %doctest_mode""" 

1124 if mode: 

1125 self._prompts_before = self.prompts 

1126 self.prompts = ClassicPrompts(self) 

1127 elif self._prompts_before: 

1128 self.prompts = self._prompts_before 

1129 self._prompts_before = None 

1130# self._update_layout() 

1131 

1132 

1133InteractiveShellABC.register(TerminalInteractiveShell) 

1134 

1135if __name__ == '__main__': 

1136 TerminalInteractiveShell.instance().interact()