diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index c88423db238..7bffba8b4ef 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2812,16 +2812,20 @@ def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=Tr result : :class:`ExecutionResult` """ result = None + if self.should_run_async(raw_cell): + runner = self.loop_runner + else: + runner = _pseudo_sync_runner try: - result = self._run_cell( - raw_cell, store_history, silent, shell_futures) + result = runner(self._run_cell( + raw_cell, store_history, silent, shell_futures)) finally: self.events.trigger('post_execute') if not silent: self.events.trigger('post_run_cell', result) return result - def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures:bool): + async def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures:bool): """Internal method to run a complete IPython cell.""" coro = self.run_cell_async( raw_cell, @@ -2834,13 +2838,9 @@ def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures # when this is the case, we want to run it using the pseudo_sync_runner # so that code can invoke eventloops (for example via the %run , and # `%paste` magic. - if self.should_run_async(raw_cell): - runner = self.loop_runner - else: - runner = _pseudo_sync_runner try: - return runner(coro) + return await coro except BaseException as e: info = ExecutionInfo(raw_cell, store_history, silent, shell_futures) result = ExecutionResult(info) diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 35cb0697849..12e1462c5ad 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -3,9 +3,13 @@ import os import sys import warnings + +import asyncio + from warnings import warn from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC +from IPython.core.async_helpers import _pseudo_sync_runner from IPython.utils import io from IPython.utils.py3compat import input from IPython.utils.terminal import toggle_set_term_title, set_term_title @@ -25,6 +29,7 @@ from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text from prompt_toolkit.styles import DynamicStyle, merge_styles from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict +from prompt_toolkit.eventloop.defaults import use_asyncio_event_loop from pygments.styles import get_style_by_name from pygments.style import Style @@ -44,6 +49,12 @@ class _NoStyle(Style): pass + +def mark_original(function): + function.original = True + return function + + _style_overrides_light_bg = { Token.Prompt: '#0000ff', Token.PromptNum: '#0000ee bold', @@ -234,9 +245,11 @@ def prompt(): while self.check_complete('\n'.join(lines))[0] == 'incomplete': lines.append( input(prompt_continuation) ) return '\n'.join(lines) + # asyncify this. self.prompt_for_code = prompt return + use_asyncio_event_loop() # Set up keyboard shortcuts key_bindings = create_ipython_shortcuts(self) @@ -256,6 +269,8 @@ def prompt(): editing_mode = getattr(EditingMode, self.editing_mode.upper()) + + # Tell prompt_toolkit to use the asyncio event loop. self.pt_app = PromptSession( editing_mode=editing_mode, key_bindings=key_bindings, @@ -370,7 +385,12 @@ def get_message(): 'inputhook': self.inputhook, } + @mark_original def prompt_for_code(self): + return _pseudo_sync_runner(self.prompt_for_code_async()) + + + async def prompt_for_code_async(self): if self.rl_next_input: default = self.rl_next_input self.rl_next_input = None @@ -378,9 +398,10 @@ def prompt_for_code(self): default = '' with patch_stdout(raw=True): - text = self.pt_app.prompt( + text = await self.pt_app.prompt( default=default, # pre_run=self.pre_prompt,# reset_current_buffer=True, + async_=True, **self._extra_prompt_options()) return text @@ -442,7 +463,10 @@ def ask_exit(self): rl_next_input = None - def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED): + def interact(self): + return _pseudo_sync_runner(self.interact_async()) + + async def interact_async(self, display_banner=DISPLAY_BANNER_DEPRECATED): if display_banner is not DISPLAY_BANNER_DEPRECATED: warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2) @@ -452,7 +476,10 @@ def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED): print(self.separate_in, end='') try: - code = self.prompt_for_code() + if getattr(self.prompt_for_code, 'original', False): + code = await self.prompt_for_code_async() + else: + code = self.prompt_for_code() except EOFError: if (not self.confirm_exit) \ or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'): @@ -460,18 +487,24 @@ def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED): else: if code: - self.run_cell(code, store_history=True) + await self.run_cell_async(code, store_history=True) def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED): # An extra layer of protection in case someone mashing Ctrl-C breaks # out of our internal code. if display_banner is not DISPLAY_BANNER_DEPRECATED: warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2) + loop = asyncio.get_event_loop() + loop.run_until_complete(asyncio.ensure_future(self._mainloop())) + + async def _mainloop(self): while True: try: - self.interact() + await self.interact_async() break except KeyboardInterrupt as e: + import traceback + traceback.print_exc() print("\n%s escaped interact()\n" % type(e).__name__) finally: # An interrupt during the eventloop will mess up the