interactiveshell.py 37 KB

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