interactiveshell.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. """IPython terminal interface using prompt_toolkit"""
  2. from __future__ import print_function
  3. import os
  4. import sys
  5. import warnings
  6. from warnings import warn
  7. from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
  8. from IPython.utils import io
  9. from IPython.utils.py3compat import PY3, cast_unicode_py2, input
  10. from IPython.utils.terminal import toggle_set_term_title, set_term_title
  11. from IPython.utils.process import abbrev_cwd
  12. from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum
  13. from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
  14. from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
  15. from prompt_toolkit.history import InMemoryHistory
  16. from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output
  17. from prompt_toolkit.interface import CommandLineInterface
  18. from prompt_toolkit.key_binding.manager import KeyBindingManager
  19. from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
  20. from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
  21. from pygments.styles import get_style_by_name, get_all_styles
  22. from pygments.token import Token
  23. from .debugger import TerminalPdb, Pdb
  24. from .magics import TerminalMagics
  25. from .pt_inputhooks import get_inputhook_func
  26. from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
  27. from .ptutils import IPythonPTCompleter, IPythonPTLexer
  28. from .shortcuts import register_ipython_shortcuts
  29. DISPLAY_BANNER_DEPRECATED = object()
  30. from pygments.style import Style
  31. class _NoStyle(Style): pass
  32. _style_overrides_light_bg = {
  33. Token.Prompt: '#0000ff',
  34. Token.PromptNum: '#0000ee bold',
  35. Token.OutPrompt: '#cc0000',
  36. Token.OutPromptNum: '#bb0000 bold',
  37. }
  38. _style_overrides_linux = {
  39. Token.Prompt: '#00cc00',
  40. Token.PromptNum: '#00bb00 bold',
  41. Token.OutPrompt: '#cc0000',
  42. Token.OutPromptNum: '#bb0000 bold',
  43. }
  44. def get_default_editor():
  45. try:
  46. ed = os.environ['EDITOR']
  47. if not PY3:
  48. ed = ed.decode()
  49. return ed
  50. except KeyError:
  51. pass
  52. except UnicodeError:
  53. warn("$EDITOR environment variable is not pure ASCII. Using platform "
  54. "default editor.")
  55. if os.name == 'posix':
  56. return 'vi' # the only one guaranteed to be there!
  57. else:
  58. return 'notepad' # same in Windows!
  59. # conservatively check for tty
  60. # overridden streams can result in things like:
  61. # - sys.stdin = None
  62. # - no isatty method
  63. for _name in ('stdin', 'stdout', 'stderr'):
  64. _stream = getattr(sys, _name)
  65. if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
  66. _is_tty = False
  67. break
  68. else:
  69. _is_tty = True
  70. _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
  71. class TerminalInteractiveShell(InteractiveShell):
  72. space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
  73. 'to reserve for the completion menu'
  74. ).tag(config=True)
  75. def _space_for_menu_changed(self, old, new):
  76. self._update_layout()
  77. pt_cli = None
  78. debugger_history = None
  79. _pt_app = None
  80. simple_prompt = Bool(_use_simple_prompt,
  81. help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
  82. Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
  83. IPython own testing machinery, and emacs inferior-shell integration through elpy.
  84. This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
  85. environment variable is set, or the current terminal is not a tty.
  86. """
  87. ).tag(config=True)
  88. @property
  89. def debugger_cls(self):
  90. return Pdb if self.simple_prompt else TerminalPdb
  91. confirm_exit = Bool(True,
  92. help="""
  93. Set to confirm when you try to exit IPython with an EOF (Control-D
  94. in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
  95. you can force a direct exit without any confirmation.""",
  96. ).tag(config=True)
  97. editing_mode = Unicode('emacs',
  98. help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
  99. ).tag(config=True)
  100. mouse_support = Bool(False,
  101. help="Enable mouse support in the prompt"
  102. ).tag(config=True)
  103. highlighting_style = Unicode('legacy',
  104. help="The name of a Pygments style to use for syntax highlighting: \n %s" % ', '.join(get_all_styles())
  105. ).tag(config=True)
  106. @observe('highlighting_style')
  107. @observe('colors')
  108. def _highlighting_style_changed(self, change):
  109. self.refresh_style()
  110. def refresh_style(self):
  111. self._style = self._make_style_from_name(self.highlighting_style)
  112. highlighting_style_overrides = Dict(
  113. help="Override highlighting format for specific tokens"
  114. ).tag(config=True)
  115. true_color = Bool(False,
  116. help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
  117. "If your terminal supports true color, the following command "
  118. "should print 'TRUECOLOR' in orange: "
  119. "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
  120. ).tag(config=True)
  121. editor = Unicode(get_default_editor(),
  122. help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
  123. ).tag(config=True)
  124. prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
  125. prompts = Instance(Prompts)
  126. @default('prompts')
  127. def _prompts_default(self):
  128. return self.prompts_class(self)
  129. @observe('prompts')
  130. def _(self, change):
  131. self._update_layout()
  132. @default('displayhook_class')
  133. def _displayhook_class_default(self):
  134. return RichPromptDisplayHook
  135. term_title = Bool(True,
  136. help="Automatically set the terminal title"
  137. ).tag(config=True)
  138. display_completions = Enum(('column', 'multicolumn','readlinelike'),
  139. help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
  140. "'readlinelike'. These options are for `prompt_toolkit`, see "
  141. "`prompt_toolkit` documentation for more information."
  142. ),
  143. default_value='multicolumn').tag(config=True)
  144. highlight_matching_brackets = Bool(True,
  145. help="Highlight matching brackets .",
  146. ).tag(config=True)
  147. @observe('term_title')
  148. def init_term_title(self, change=None):
  149. # Enable or disable the terminal title.
  150. if self.term_title:
  151. toggle_set_term_title(True)
  152. set_term_title('IPython: ' + abbrev_cwd())
  153. else:
  154. toggle_set_term_title(False)
  155. def init_display_formatter(self):
  156. super(TerminalInteractiveShell, self).init_display_formatter()
  157. # terminal only supports plain text
  158. self.display_formatter.active_types = ['text/plain']
  159. def init_prompt_toolkit_cli(self):
  160. if self.simple_prompt:
  161. # Fall back to plain non-interactive output for tests.
  162. # This is very limited, and only accepts a single line.
  163. def prompt():
  164. return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
  165. self.prompt_for_code = prompt
  166. return
  167. # Set up keyboard shortcuts
  168. kbmanager = KeyBindingManager.for_prompt()
  169. register_ipython_shortcuts(kbmanager.registry, self)
  170. # Pre-populate history from IPython's history database
  171. history = InMemoryHistory()
  172. last_cell = u""
  173. for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
  174. include_latest=True):
  175. # Ignore blank lines and consecutive duplicates
  176. cell = cell.rstrip()
  177. if cell and (cell != last_cell):
  178. history.append(cell)
  179. self._style = self._make_style_from_name(self.highlighting_style)
  180. style = DynamicStyle(lambda: self._style)
  181. editing_mode = getattr(EditingMode, self.editing_mode.upper())
  182. self._pt_app = create_prompt_application(
  183. editing_mode=editing_mode,
  184. key_bindings_registry=kbmanager.registry,
  185. history=history,
  186. completer=IPythonPTCompleter(shell=self),
  187. enable_history_search=True,
  188. style=style,
  189. mouse_support=self.mouse_support,
  190. **self._layout_options()
  191. )
  192. self._eventloop = create_eventloop(self.inputhook)
  193. self.pt_cli = CommandLineInterface(
  194. self._pt_app, eventloop=self._eventloop,
  195. output=create_output(true_color=self.true_color))
  196. def _make_style_from_name(self, name):
  197. """
  198. Small wrapper that make an IPython compatible style from a style name
  199. We need that to add style for prompt ... etc.
  200. """
  201. style_overrides = {}
  202. if name == 'legacy':
  203. legacy = self.colors.lower()
  204. if legacy == 'linux':
  205. style_cls = get_style_by_name('monokai')
  206. style_overrides = _style_overrides_linux
  207. elif legacy == 'lightbg':
  208. style_overrides = _style_overrides_light_bg
  209. style_cls = get_style_by_name('pastie')
  210. elif legacy == 'neutral':
  211. # The default theme needs to be visible on both a dark background
  212. # and a light background, because we can't tell what the terminal
  213. # looks like. These tweaks to the default theme help with that.
  214. style_cls = get_style_by_name('default')
  215. style_overrides.update({
  216. Token.Number: '#007700',
  217. Token.Operator: 'noinherit',
  218. Token.String: '#BB6622',
  219. Token.Name.Function: '#2080D0',
  220. Token.Name.Class: 'bold #2080D0',
  221. Token.Name.Namespace: 'bold #2080D0',
  222. Token.Prompt: '#009900',
  223. Token.PromptNum: '#00ff00 bold',
  224. Token.OutPrompt: '#990000',
  225. Token.OutPromptNum: '#ff0000 bold',
  226. })
  227. elif legacy =='nocolor':
  228. style_cls=_NoStyle
  229. style_overrides = {}
  230. else :
  231. raise ValueError('Got unknown colors: ', legacy)
  232. else :
  233. style_cls = get_style_by_name(name)
  234. style_overrides = {
  235. Token.Prompt: '#009900',
  236. Token.PromptNum: '#00ff00 bold',
  237. Token.OutPrompt: '#990000',
  238. Token.OutPromptNum: '#ff0000 bold',
  239. }
  240. style_overrides.update(self.highlighting_style_overrides)
  241. style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
  242. style_dict=style_overrides)
  243. return style
  244. def _layout_options(self):
  245. """
  246. Return the current layout option for the current Terminal InteractiveShell
  247. """
  248. return {
  249. 'lexer':IPythonPTLexer(),
  250. 'reserve_space_for_menu':self.space_for_menu,
  251. 'get_prompt_tokens':self.prompts.in_prompt_tokens,
  252. 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
  253. 'multiline':True,
  254. 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
  255. # Highlight matching brackets, but only when this setting is
  256. # enabled, and only when the DEFAULT_BUFFER has the focus.
  257. 'extra_input_processors': [ConditionalProcessor(
  258. processor=HighlightMatchingBracketProcessor(chars='[](){}'),
  259. filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
  260. Condition(lambda cli: self.highlight_matching_brackets))],
  261. }
  262. def _update_layout(self):
  263. """
  264. Ask for a re computation of the application layout, if for example ,
  265. some configuration options have changed.
  266. """
  267. if self._pt_app:
  268. self._pt_app.layout = create_prompt_layout(**self._layout_options())
  269. def prompt_for_code(self):
  270. document = self.pt_cli.run(
  271. pre_run=self.pre_prompt, reset_current_buffer=True)
  272. return document.text
  273. def enable_win_unicode_console(self):
  274. import win_unicode_console
  275. if PY3:
  276. win_unicode_console.enable()
  277. else:
  278. # https://github.com/ipython/ipython/issues/9768
  279. from win_unicode_console.streams import (TextStreamWrapper,
  280. stdout_text_transcoded, stderr_text_transcoded)
  281. class LenientStrStreamWrapper(TextStreamWrapper):
  282. def write(self, s):
  283. if isinstance(s, bytes):
  284. s = s.decode(self.encoding, 'replace')
  285. self.base.write(s)
  286. stdout_text_str = LenientStrStreamWrapper(stdout_text_transcoded)
  287. stderr_text_str = LenientStrStreamWrapper(stderr_text_transcoded)
  288. win_unicode_console.enable(stdout=stdout_text_str,
  289. stderr=stderr_text_str)
  290. def init_io(self):
  291. if sys.platform not in {'win32', 'cli'}:
  292. return
  293. self.enable_win_unicode_console()
  294. import colorama
  295. colorama.init()
  296. # For some reason we make these wrappers around stdout/stderr.
  297. # For now, we need to reset them so all output gets coloured.
  298. # https://github.com/ipython/ipython/issues/8669
  299. # io.std* are deprecated, but don't show our own deprecation warnings
  300. # during initialization of the deprecated API.
  301. with warnings.catch_warnings():
  302. warnings.simplefilter('ignore', DeprecationWarning)
  303. io.stdout = io.IOStream(sys.stdout)
  304. io.stderr = io.IOStream(sys.stderr)
  305. def init_magics(self):
  306. super(TerminalInteractiveShell, self).init_magics()
  307. self.register_magics(TerminalMagics)
  308. def init_alias(self):
  309. # The parent class defines aliases that can be safely used with any
  310. # frontend.
  311. super(TerminalInteractiveShell, self).init_alias()
  312. # Now define aliases that only make sense on the terminal, because they
  313. # need direct access to the console in a way that we can't emulate in
  314. # GUI or web frontend
  315. if os.name == 'posix':
  316. for cmd in ['clear', 'more', 'less', 'man']:
  317. self.alias_manager.soft_define_alias(cmd, cmd)
  318. def __init__(self, *args, **kwargs):
  319. super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
  320. self.init_prompt_toolkit_cli()
  321. self.init_term_title()
  322. self.keep_running = True
  323. self.debugger_history = InMemoryHistory()
  324. def ask_exit(self):
  325. self.keep_running = False
  326. rl_next_input = None
  327. def pre_prompt(self):
  328. if self.rl_next_input:
  329. self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
  330. self.rl_next_input = None
  331. def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
  332. if display_banner is not DISPLAY_BANNER_DEPRECATED:
  333. warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
  334. self.keep_running = True
  335. while self.keep_running:
  336. print(self.separate_in, end='')
  337. try:
  338. code = self.prompt_for_code()
  339. except EOFError:
  340. if (not self.confirm_exit) \
  341. or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
  342. self.ask_exit()
  343. else:
  344. if code:
  345. self.run_cell(code, store_history=True)
  346. def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
  347. # An extra layer of protection in case someone mashing Ctrl-C breaks
  348. # out of our internal code.
  349. if display_banner is not DISPLAY_BANNER_DEPRECATED:
  350. warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
  351. while True:
  352. try:
  353. self.interact()
  354. break
  355. except KeyboardInterrupt:
  356. print("\nKeyboardInterrupt escaped interact()\n")
  357. _inputhook = None
  358. def inputhook(self, context):
  359. if self._inputhook is not None:
  360. self._inputhook(context)
  361. def enable_gui(self, gui=None):
  362. if gui:
  363. self._inputhook = get_inputhook_func(gui)
  364. else:
  365. self._inputhook = None
  366. # Run !system commands directly, not through pipes, so terminal programs
  367. # work correctly.
  368. system = InteractiveShell.system_raw
  369. def auto_rewrite_input(self, cmd):
  370. """Overridden from the parent class to use fancy rewriting prompt"""
  371. if not self.show_rewritten_input:
  372. return
  373. tokens = self.prompts.rewrite_prompt_tokens()
  374. if self.pt_cli:
  375. self.pt_cli.print_tokens(tokens)
  376. print(cmd)
  377. else:
  378. prompt = ''.join(s for t, s in tokens)
  379. print(prompt, cmd, sep='')
  380. _prompts_before = None
  381. def switch_doctest_mode(self, mode):
  382. """Switch prompts to classic for %doctest_mode"""
  383. if mode:
  384. self._prompts_before = self.prompts
  385. self.prompts = ClassicPrompts(self)
  386. elif self._prompts_before:
  387. self.prompts = self._prompts_before
  388. self._prompts_before = None
  389. self._update_layout()
  390. InteractiveShellABC.register(TerminalInteractiveShell)
  391. if __name__ == '__main__':
  392. TerminalInteractiveShell.instance().interact()