diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index e9ba812..312b691 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -449,22 +449,28 @@ class TerminalInteractiveShell(InteractiveShell): else: default = '' - with patch_stdout(raw=True): - # In order to make sure that asyncio code written in the - # interactive shell doesn't interfere with the prompt, we run the - # prompt in a different event loop. - # If we don't do this, people could spawn coroutine with a - # while/true inside which will freeze the prompt. + # In order to make sure that asyncio code written in the + # interactive shell doesn't interfere with the prompt, we run the + # prompt in a different event loop. + # If we don't do this, people could spawn coroutine with a + # while/true inside which will freeze the prompt. + try: old_loop = asyncio.get_event_loop() - asyncio.set_event_loop(self.pt_loop) - try: + except RuntimeError: + # This happens when the user used `asyncio.run()`. + old_loop = None + + asyncio.set_event_loop(self.pt_loop) + try: + with patch_stdout(raw=True): text = self.pt_app.prompt( default=default, **self._extra_prompt_options()) - finally: - # Restore the original event loop. - asyncio.set_event_loop(old_loop) + finally: + # Restore the original event loop. + asyncio.set_event_loop(old_loop) + return text def enable_win_unicode_console(self): @@ -580,11 +586,22 @@ class TerminalInteractiveShell(InteractiveShell): # For prompt_toolkit 3.0. We have to create an asyncio event loop with # this inputhook. if PTK3: - if self._inputhook: - from prompt_toolkit.eventloop import new_eventloop_with_inputhook + import asyncio + from prompt_toolkit.eventloop import new_eventloop_with_inputhook + + if gui == 'asyncio': + # When we integrate the asyncio event loop, run the UI in the + # same event loop as the rest of the code. don't use an actual + # input hook. (Asyncio is not made for nesting event loops.) + self.pt_loop = asyncio.get_event_loop() + + elif self._inputhook: + # If an inputhook was set, create a new asyncio event loop with + # this inputhook for the prompt. self.pt_loop = new_eventloop_with_inputhook(self._inputhook) else: - import asyncio + # When there's no inputhook, run the prompt in a separate + # asyncio event loop. self.pt_loop = asyncio.new_event_loop() # Run !system commands directly, not through pipes, so terminal programs diff --git a/IPython/terminal/pt_inputhooks/asyncio.py b/IPython/terminal/pt_inputhooks/asyncio.py index e286038..95cf194 100644 --- a/IPython/terminal/pt_inputhooks/asyncio.py +++ b/IPython/terminal/pt_inputhooks/asyncio.py @@ -28,6 +28,10 @@ prompt_toolkit`s `patch_stdout`):: """ import asyncio +from prompt_toolkit import __version__ as ptk_version + +PTK3 = ptk_version.startswith('3.') + # Keep reference to the original asyncio loop, because getting the event loop # within the input hook would return the other loop. @@ -35,6 +39,19 @@ loop = asyncio.get_event_loop() def inputhook(context): + """ + Inputhook for asyncio event loop integration. + """ + # For prompt_toolkit 3.0, this input hook literally doesn't do anything. + # The event loop integration here is implemented in `interactiveshell.py` + # by running the prompt itself in the current asyncio loop. The main reason + # for this is that nesting asyncio event loops is unreliable. + if PTK3: + return + + # For prompt_toolkit 2.0, we can run the current asyncio event loop, + # because prompt_toolkit 2.0 uses a different event loop internally. + def stop(): loop.stop() @@ -44,3 +61,4 @@ def inputhook(context): loop.run_forever() finally: loop.remove_reader(fileno) +