PageRenderTime 23ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/IPython/terminal/interactiveshell.py

https://github.com/ipython/ipython
Python | 769 lines | 709 code | 31 blank | 29 comment | 33 complexity | 159a63b18374e2d39970908fcfc7899d MD5 | raw file
Possible License(s): BSD-3-Clause
  1. """IPython terminal interface using prompt_toolkit"""
  2. import asyncio
  3. import os
  4. import sys
  5. from warnings import warn
  6. from IPython.core.async_helpers import get_asyncio_loop
  7. from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
  8. from IPython.utils.py3compat import input
  9. from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
  10. from IPython.utils.process import abbrev_cwd
  11. from traitlets import (
  12. Bool,
  13. Unicode,
  14. Dict,
  15. Integer,
  16. observe,
  17. Instance,
  18. Type,
  19. default,
  20. Enum,
  21. Union,
  22. Any,
  23. validate,
  24. Float,
  25. )
  26. from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
  27. from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
  28. from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
  29. from prompt_toolkit.formatted_text import PygmentsTokens
  30. from prompt_toolkit.history import History
  31. from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
  32. from prompt_toolkit.output import ColorDepth
  33. from prompt_toolkit.patch_stdout import patch_stdout
  34. from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
  35. from prompt_toolkit.styles import DynamicStyle, merge_styles
  36. from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
  37. from prompt_toolkit import __version__ as ptk_version
  38. from pygments.styles import get_style_by_name
  39. from pygments.style import Style
  40. from pygments.token import Token
  41. from .debugger import TerminalPdb, Pdb
  42. from .magics import TerminalMagics
  43. from .pt_inputhooks import get_inputhook_name_and_func
  44. from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
  45. from .ptutils import IPythonPTCompleter, IPythonPTLexer
  46. from .shortcuts import create_ipython_shortcuts
  47. PTK3 = ptk_version.startswith('3.')
  48. class _NoStyle(Style): pass
  49. _style_overrides_light_bg = {
  50. Token.Prompt: '#ansibrightblue',
  51. Token.PromptNum: '#ansiblue bold',
  52. Token.OutPrompt: '#ansibrightred',
  53. Token.OutPromptNum: '#ansired bold',
  54. }
  55. _style_overrides_linux = {
  56. Token.Prompt: '#ansibrightgreen',
  57. Token.PromptNum: '#ansigreen bold',
  58. Token.OutPrompt: '#ansibrightred',
  59. Token.OutPromptNum: '#ansired bold',
  60. }
  61. def get_default_editor():
  62. try:
  63. return os.environ['EDITOR']
  64. except KeyError:
  65. pass
  66. except UnicodeError:
  67. warn("$EDITOR environment variable is not pure ASCII. Using platform "
  68. "default editor.")
  69. if os.name == 'posix':
  70. return 'vi' # the only one guaranteed to be there!
  71. else:
  72. return 'notepad' # same in Windows!
  73. # conservatively check for tty
  74. # overridden streams can result in things like:
  75. # - sys.stdin = None
  76. # - no isatty method
  77. for _name in ('stdin', 'stdout', 'stderr'):
  78. _stream = getattr(sys, _name)
  79. if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
  80. _is_tty = False
  81. break
  82. else:
  83. _is_tty = True
  84. _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
  85. def black_reformat_handler(text_before_cursor):
  86. """
  87. We do not need to protect against error,
  88. this is taken care at a higher level where any reformat error is ignored.
  89. Indeed we may call reformatting on incomplete code.
  90. """
  91. import black
  92. formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
  93. if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
  94. formatted_text = formatted_text[:-1]
  95. return formatted_text
  96. def yapf_reformat_handler(text_before_cursor):
  97. from yapf.yapflib import file_resources
  98. from yapf.yapflib import yapf_api
  99. style_config = file_resources.GetDefaultStyleForDir(os.getcwd())
  100. formatted_text, was_formatted = yapf_api.FormatCode(
  101. text_before_cursor, style_config=style_config
  102. )
  103. if was_formatted:
  104. if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
  105. formatted_text = formatted_text[:-1]
  106. return formatted_text
  107. else:
  108. return text_before_cursor
  109. class PtkHistoryAdapter(History):
  110. """
  111. Prompt toolkit has it's own way of handling history, Where it assumes it can
  112. Push/pull from history.
  113. """
  114. def __init__(self, shell):
  115. super().__init__()
  116. self.shell = shell
  117. self._refresh()
  118. def append_string(self, string):
  119. # we rely on sql for that.
  120. self._loaded = False
  121. self._refresh()
  122. def _refresh(self):
  123. if not self._loaded:
  124. self._loaded_strings = list(self.load_history_strings())
  125. def load_history_strings(self):
  126. last_cell = ""
  127. res = []
  128. for __, ___, cell in self.shell.history_manager.get_tail(
  129. self.shell.history_load_length, include_latest=True
  130. ):
  131. # Ignore blank lines and consecutive duplicates
  132. cell = cell.rstrip()
  133. if cell and (cell != last_cell):
  134. res.append(cell)
  135. last_cell = cell
  136. yield from res[::-1]
  137. def store_string(self, string: str) -> None:
  138. pass
  139. class TerminalInteractiveShell(InteractiveShell):
  140. mime_renderers = Dict().tag(config=True)
  141. space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
  142. 'to reserve for the tab completion menu, '
  143. 'search history, ...etc, the height of '
  144. 'these menus will at most this value. '
  145. 'Increase it is you prefer long and skinny '
  146. 'menus, decrease for short and wide.'
  147. ).tag(config=True)
  148. pt_app = None
  149. debugger_history = None
  150. debugger_history_file = Unicode(
  151. "~/.pdbhistory", help="File in which to store and read history"
  152. ).tag(config=True)
  153. simple_prompt = Bool(_use_simple_prompt,
  154. help="""Use `raw_input` for the REPL, without completion and prompt colors.
  155. Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
  156. IPython own testing machinery, and emacs inferior-shell integration through elpy.
  157. This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
  158. environment variable is set, or the current terminal is not a tty."""
  159. ).tag(config=True)
  160. @property
  161. def debugger_cls(self):
  162. return Pdb if self.simple_prompt else TerminalPdb
  163. confirm_exit = Bool(True,
  164. help="""
  165. Set to confirm when you try to exit IPython with an EOF (Control-D
  166. in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
  167. you can force a direct exit without any confirmation.""",
  168. ).tag(config=True)
  169. editing_mode = Unicode('emacs',
  170. help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
  171. ).tag(config=True)
  172. emacs_bindings_in_vi_insert_mode = Bool(
  173. True,
  174. help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.",
  175. ).tag(config=True)
  176. modal_cursor = Bool(
  177. True,
  178. help="""
  179. Cursor shape changes depending on vi mode: beam in vi insert mode,
  180. block in nav mode, underscore in replace mode.""",
  181. ).tag(config=True)
  182. ttimeoutlen = Float(
  183. 0.01,
  184. help="""The time in milliseconds that is waited for a key code
  185. to complete.""",
  186. ).tag(config=True)
  187. timeoutlen = Float(
  188. 0.5,
  189. help="""The time in milliseconds that is waited for a mapped key
  190. sequence to complete.""",
  191. ).tag(config=True)
  192. autoformatter = Unicode(
  193. None,
  194. help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`",
  195. allow_none=True
  196. ).tag(config=True)
  197. auto_match = Bool(
  198. False,
  199. help="""
  200. Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted.
  201. Brackets: (), [], {}
  202. Quotes: '', \"\"
  203. """,
  204. ).tag(config=True)
  205. mouse_support = Bool(False,
  206. help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
  207. ).tag(config=True)
  208. # We don't load the list of styles for the help string, because loading
  209. # Pygments plugins takes time and can cause unexpected errors.
  210. highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
  211. help="""The name or class of a Pygments style to use for syntax
  212. highlighting. To see available styles, run `pygmentize -L styles`."""
  213. ).tag(config=True)
  214. @validate('editing_mode')
  215. def _validate_editing_mode(self, proposal):
  216. if proposal['value'].lower() == 'vim':
  217. proposal['value']= 'vi'
  218. elif proposal['value'].lower() == 'default':
  219. proposal['value']= 'emacs'
  220. if hasattr(EditingMode, proposal['value'].upper()):
  221. return proposal['value'].lower()
  222. return self.editing_mode
  223. @observe('editing_mode')
  224. def _editing_mode(self, change):
  225. if self.pt_app:
  226. self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
  227. def _set_formatter(self, formatter):
  228. if formatter is None:
  229. self.reformat_handler = lambda x:x
  230. elif formatter == 'black':
  231. self.reformat_handler = black_reformat_handler
  232. elif formatter == "yapf":
  233. self.reformat_handler = yapf_reformat_handler
  234. else:
  235. raise ValueError
  236. @observe("autoformatter")
  237. def _autoformatter_changed(self, change):
  238. formatter = change.new
  239. self._set_formatter(formatter)
  240. @observe('highlighting_style')
  241. @observe('colors')
  242. def _highlighting_style_changed(self, change):
  243. self.refresh_style()
  244. def refresh_style(self):
  245. self._style = self._make_style_from_name_or_cls(self.highlighting_style)
  246. highlighting_style_overrides = Dict(
  247. help="Override highlighting format for specific tokens"
  248. ).tag(config=True)
  249. true_color = Bool(False,
  250. help="""Use 24bit colors instead of 256 colors in prompt highlighting.
  251. If your terminal supports true color, the following command should
  252. print ``TRUECOLOR`` in orange::
  253. printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
  254. """,
  255. ).tag(config=True)
  256. editor = Unicode(get_default_editor(),
  257. help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
  258. ).tag(config=True)
  259. prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
  260. prompts = Instance(Prompts)
  261. @default('prompts')
  262. def _prompts_default(self):
  263. return self.prompts_class(self)
  264. # @observe('prompts')
  265. # def _(self, change):
  266. # self._update_layout()
  267. @default('displayhook_class')
  268. def _displayhook_class_default(self):
  269. return RichPromptDisplayHook
  270. term_title = Bool(True,
  271. help="Automatically set the terminal title"
  272. ).tag(config=True)
  273. term_title_format = Unicode("IPython: {cwd}",
  274. help="Customize the terminal title format. This is a python format string. " +
  275. "Available substitutions are: {cwd}."
  276. ).tag(config=True)
  277. display_completions = Enum(('column', 'multicolumn','readlinelike'),
  278. help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
  279. "'readlinelike'. These options are for `prompt_toolkit`, see "
  280. "`prompt_toolkit` documentation for more information."
  281. ),
  282. default_value='multicolumn').tag(config=True)
  283. highlight_matching_brackets = Bool(True,
  284. help="Highlight matching brackets.",
  285. ).tag(config=True)
  286. extra_open_editor_shortcuts = Bool(False,
  287. help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
  288. "This is in addition to the F2 binding, which is always enabled."
  289. ).tag(config=True)
  290. handle_return = Any(None,
  291. help="Provide an alternative handler to be called when the user presses "
  292. "Return. This is an advanced option intended for debugging, which "
  293. "may be changed or removed in later releases."
  294. ).tag(config=True)
  295. enable_history_search = Bool(True,
  296. help="Allows to enable/disable the prompt toolkit history search"
  297. ).tag(config=True)
  298. autosuggestions_provider = Unicode(
  299. "AutoSuggestFromHistory",
  300. help="Specifies from which source automatic suggestions are provided. "
  301. "Can be set to `'AutoSuggestFromHistory`' or `None` to disable"
  302. "automatic suggestions. Default is `'AutoSuggestFromHistory`'.",
  303. allow_none=True,
  304. ).tag(config=True)
  305. def _set_autosuggestions(self, provider):
  306. if provider is None:
  307. self.auto_suggest = None
  308. elif provider == "AutoSuggestFromHistory":
  309. self.auto_suggest = AutoSuggestFromHistory()
  310. else:
  311. raise ValueError("No valid provider.")
  312. if self.pt_app:
  313. self.pt_app.auto_suggest = self.auto_suggest
  314. @observe("autosuggestions_provider")
  315. def _autosuggestions_provider_changed(self, change):
  316. provider = change.new
  317. self._set_autosuggestions(provider)
  318. prompt_includes_vi_mode = Bool(True,
  319. help="Display the current vi mode (when using vi editing mode)."
  320. ).tag(config=True)
  321. @observe('term_title')
  322. def init_term_title(self, change=None):
  323. # Enable or disable the terminal title.
  324. if self.term_title:
  325. toggle_set_term_title(True)
  326. set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
  327. else:
  328. toggle_set_term_title(False)
  329. def restore_term_title(self):
  330. if self.term_title:
  331. restore_term_title()
  332. def init_display_formatter(self):
  333. super(TerminalInteractiveShell, self).init_display_formatter()
  334. # terminal only supports plain text
  335. self.display_formatter.active_types = ["text/plain"]
  336. def init_prompt_toolkit_cli(self):
  337. if self.simple_prompt:
  338. # Fall back to plain non-interactive output for tests.
  339. # This is very limited.
  340. def prompt():
  341. prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
  342. lines = [input(prompt_text)]
  343. prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
  344. while self.check_complete('\n'.join(lines))[0] == 'incomplete':
  345. lines.append( input(prompt_continuation) )
  346. return '\n'.join(lines)
  347. self.prompt_for_code = prompt
  348. return
  349. # Set up keyboard shortcuts
  350. key_bindings = create_ipython_shortcuts(self)
  351. # Pre-populate history from IPython's history database
  352. history = PtkHistoryAdapter(self)
  353. self._style = self._make_style_from_name_or_cls(self.highlighting_style)
  354. self.style = DynamicStyle(lambda: self._style)
  355. editing_mode = getattr(EditingMode, self.editing_mode.upper())
  356. self.pt_loop = asyncio.new_event_loop()
  357. self.pt_app = PromptSession(
  358. auto_suggest=self.auto_suggest,
  359. editing_mode=editing_mode,
  360. key_bindings=key_bindings,
  361. history=history,
  362. completer=IPythonPTCompleter(shell=self),
  363. enable_history_search=self.enable_history_search,
  364. style=self.style,
  365. include_default_pygments_style=False,
  366. mouse_support=self.mouse_support,
  367. enable_open_in_editor=self.extra_open_editor_shortcuts,
  368. color_depth=self.color_depth,
  369. tempfile_suffix=".py",
  370. **self._extra_prompt_options()
  371. )
  372. def _make_style_from_name_or_cls(self, name_or_cls):
  373. """
  374. Small wrapper that make an IPython compatible style from a style name
  375. We need that to add style for prompt ... etc.
  376. """
  377. style_overrides = {}
  378. if name_or_cls == 'legacy':
  379. legacy = self.colors.lower()
  380. if legacy == 'linux':
  381. style_cls = get_style_by_name('monokai')
  382. style_overrides = _style_overrides_linux
  383. elif legacy == 'lightbg':
  384. style_overrides = _style_overrides_light_bg
  385. style_cls = get_style_by_name('pastie')
  386. elif legacy == 'neutral':
  387. # The default theme needs to be visible on both a dark background
  388. # and a light background, because we can't tell what the terminal
  389. # looks like. These tweaks to the default theme help with that.
  390. style_cls = get_style_by_name('default')
  391. style_overrides.update({
  392. Token.Number: '#ansigreen',
  393. Token.Operator: 'noinherit',
  394. Token.String: '#ansiyellow',
  395. Token.Name.Function: '#ansiblue',
  396. Token.Name.Class: 'bold #ansiblue',
  397. Token.Name.Namespace: 'bold #ansiblue',
  398. Token.Name.Variable.Magic: '#ansiblue',
  399. Token.Prompt: '#ansigreen',
  400. Token.PromptNum: '#ansibrightgreen bold',
  401. Token.OutPrompt: '#ansired',
  402. Token.OutPromptNum: '#ansibrightred bold',
  403. })
  404. # Hack: Due to limited color support on the Windows console
  405. # the prompt colors will be wrong without this
  406. if os.name == 'nt':
  407. style_overrides.update({
  408. Token.Prompt: '#ansidarkgreen',
  409. Token.PromptNum: '#ansigreen bold',
  410. Token.OutPrompt: '#ansidarkred',
  411. Token.OutPromptNum: '#ansired bold',
  412. })
  413. elif legacy =='nocolor':
  414. style_cls=_NoStyle
  415. style_overrides = {}
  416. else :
  417. raise ValueError('Got unknown colors: ', legacy)
  418. else :
  419. if isinstance(name_or_cls, str):
  420. style_cls = get_style_by_name(name_or_cls)
  421. else:
  422. style_cls = name_or_cls
  423. style_overrides = {
  424. Token.Prompt: '#ansigreen',
  425. Token.PromptNum: '#ansibrightgreen bold',
  426. Token.OutPrompt: '#ansired',
  427. Token.OutPromptNum: '#ansibrightred bold',
  428. }
  429. style_overrides.update(self.highlighting_style_overrides)
  430. style = merge_styles([
  431. style_from_pygments_cls(style_cls),
  432. style_from_pygments_dict(style_overrides),
  433. ])
  434. return style
  435. @property
  436. def pt_complete_style(self):
  437. return {
  438. 'multicolumn': CompleteStyle.MULTI_COLUMN,
  439. 'column': CompleteStyle.COLUMN,
  440. 'readlinelike': CompleteStyle.READLINE_LIKE,
  441. }[self.display_completions]
  442. @property
  443. def color_depth(self):
  444. return (ColorDepth.TRUE_COLOR if self.true_color else None)
  445. def _extra_prompt_options(self):
  446. """
  447. Return the current layout option for the current Terminal InteractiveShell
  448. """
  449. def get_message():
  450. return PygmentsTokens(self.prompts.in_prompt_tokens())
  451. if self.editing_mode == 'emacs':
  452. # with emacs mode the prompt is (usually) static, so we call only
  453. # the function once. With VI mode it can toggle between [ins] and
  454. # [nor] so we can't precompute.
  455. # here I'm going to favor the default keybinding which almost
  456. # everybody uses to decrease CPU usage.
  457. # if we have issues with users with custom Prompts we can see how to
  458. # work around this.
  459. get_message = get_message()
  460. options = {
  461. 'complete_in_thread': False,
  462. 'lexer':IPythonPTLexer(),
  463. 'reserve_space_for_menu':self.space_for_menu,
  464. 'message': get_message,
  465. 'prompt_continuation': (
  466. lambda width, lineno, is_soft_wrap:
  467. PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
  468. 'multiline': True,
  469. 'complete_style': self.pt_complete_style,
  470. # Highlight matching brackets, but only when this setting is
  471. # enabled, and only when the DEFAULT_BUFFER has the focus.
  472. 'input_processors': [ConditionalProcessor(
  473. processor=HighlightMatchingBracketProcessor(chars='[](){}'),
  474. filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
  475. Condition(lambda: self.highlight_matching_brackets))],
  476. }
  477. if not PTK3:
  478. options['inputhook'] = self.inputhook
  479. return options
  480. def prompt_for_code(self):
  481. if self.rl_next_input:
  482. default = self.rl_next_input
  483. self.rl_next_input = None
  484. else:
  485. default = ''
  486. # In order to make sure that asyncio code written in the
  487. # interactive shell doesn't interfere with the prompt, we run the
  488. # prompt in a different event loop.
  489. # If we don't do this, people could spawn coroutine with a
  490. # while/true inside which will freeze the prompt.
  491. policy = asyncio.get_event_loop_policy()
  492. old_loop = get_asyncio_loop()
  493. # FIXME: prompt_toolkit is using the deprecated `asyncio.get_event_loop`
  494. # to get the current event loop.
  495. # This will probably be replaced by an attribute or input argument,
  496. # at which point we can stop calling the soon-to-be-deprecated `set_event_loop` here.
  497. if old_loop is not self.pt_loop:
  498. policy.set_event_loop(self.pt_loop)
  499. try:
  500. with patch_stdout(raw=True):
  501. text = self.pt_app.prompt(
  502. default=default,
  503. **self._extra_prompt_options())
  504. finally:
  505. # Restore the original event loop.
  506. if old_loop is not None and old_loop is not self.pt_loop:
  507. policy.set_event_loop(old_loop)
  508. return text
  509. def enable_win_unicode_console(self):
  510. # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
  511. # console by default, so WUC shouldn't be needed.
  512. warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
  513. DeprecationWarning,
  514. stacklevel=2)
  515. def init_io(self):
  516. if sys.platform not in {'win32', 'cli'}:
  517. return
  518. import colorama
  519. colorama.init()
  520. def init_magics(self):
  521. super(TerminalInteractiveShell, self).init_magics()
  522. self.register_magics(TerminalMagics)
  523. def init_alias(self):
  524. # The parent class defines aliases that can be safely used with any
  525. # frontend.
  526. super(TerminalInteractiveShell, self).init_alias()
  527. # Now define aliases that only make sense on the terminal, because they
  528. # need direct access to the console in a way that we can't emulate in
  529. # GUI or web frontend
  530. if os.name == 'posix':
  531. for cmd in ('clear', 'more', 'less', 'man'):
  532. self.alias_manager.soft_define_alias(cmd, cmd)
  533. def __init__(self, *args, **kwargs):
  534. super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
  535. self._set_autosuggestions(self.autosuggestions_provider)
  536. self.init_prompt_toolkit_cli()
  537. self.init_term_title()
  538. self.keep_running = True
  539. self._set_formatter(self.autoformatter)
  540. def ask_exit(self):
  541. self.keep_running = False
  542. rl_next_input = None
  543. def interact(self):
  544. self.keep_running = True
  545. while self.keep_running:
  546. print(self.separate_in, end='')
  547. try:
  548. code = self.prompt_for_code()
  549. except EOFError:
  550. if (not self.confirm_exit) \
  551. or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
  552. self.ask_exit()
  553. else:
  554. if code:
  555. self.run_cell(code, store_history=True)
  556. def mainloop(self):
  557. # An extra layer of protection in case someone mashing Ctrl-C breaks
  558. # out of our internal code.
  559. while True:
  560. try:
  561. self.interact()
  562. break
  563. except KeyboardInterrupt as e:
  564. print("\n%s escaped interact()\n" % type(e).__name__)
  565. finally:
  566. # An interrupt during the eventloop will mess up the
  567. # internal state of the prompt_toolkit library.
  568. # Stopping the eventloop fixes this, see
  569. # https://github.com/ipython/ipython/pull/9867
  570. if hasattr(self, '_eventloop'):
  571. self._eventloop.stop()
  572. self.restore_term_title()
  573. # try to call some at-exit operation optimistically as some things can't
  574. # be done during interpreter shutdown. this is technically inaccurate as
  575. # this make mainlool not re-callable, but that should be a rare if not
  576. # in existent use case.
  577. self._atexit_once()
  578. _inputhook = None
  579. def inputhook(self, context):
  580. if self._inputhook is not None:
  581. self._inputhook(context)
  582. active_eventloop = None
  583. def enable_gui(self, gui=None):
  584. if gui and (gui != 'inline') :
  585. self.active_eventloop, self._inputhook =\
  586. get_inputhook_name_and_func(gui)
  587. else:
  588. self.active_eventloop = self._inputhook = None
  589. # For prompt_toolkit 3.0. We have to create an asyncio event loop with
  590. # this inputhook.
  591. if PTK3:
  592. import asyncio
  593. from prompt_toolkit.eventloop import new_eventloop_with_inputhook
  594. if gui == 'asyncio':
  595. # When we integrate the asyncio event loop, run the UI in the
  596. # same event loop as the rest of the code. don't use an actual
  597. # input hook. (Asyncio is not made for nesting event loops.)
  598. self.pt_loop = get_asyncio_loop()
  599. elif self._inputhook:
  600. # If an inputhook was set, create a new asyncio event loop with
  601. # this inputhook for the prompt.
  602. self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
  603. else:
  604. # When there's no inputhook, run the prompt in a separate
  605. # asyncio event loop.
  606. self.pt_loop = asyncio.new_event_loop()
  607. # Run !system commands directly, not through pipes, so terminal programs
  608. # work correctly.
  609. system = InteractiveShell.system_raw
  610. def auto_rewrite_input(self, cmd):
  611. """Overridden from the parent class to use fancy rewriting prompt"""
  612. if not self.show_rewritten_input:
  613. return
  614. tokens = self.prompts.rewrite_prompt_tokens()
  615. if self.pt_app:
  616. print_formatted_text(PygmentsTokens(tokens), end='',
  617. style=self.pt_app.app.style)
  618. print(cmd)
  619. else:
  620. prompt = ''.join(s for t, s in tokens)
  621. print(prompt, cmd, sep='')
  622. _prompts_before = None
  623. def switch_doctest_mode(self, mode):
  624. """Switch prompts to classic for %doctest_mode"""
  625. if mode:
  626. self._prompts_before = self.prompts
  627. self.prompts = ClassicPrompts(self)
  628. elif self._prompts_before:
  629. self.prompts = self._prompts_before
  630. self._prompts_before = None
  631. # self._update_layout()
  632. InteractiveShellABC.register(TerminalInteractiveShell)
  633. if __name__ == '__main__':
  634. TerminalInteractiveShell.instance().interact()