| """ |
| Tools for running functions on the terminal above the current application or prompt. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from asyncio import Future, ensure_future |
| from contextlib import asynccontextmanager |
| from typing import AsyncGenerator, Awaitable, Callable, TypeVar |
|
|
| from prompt_toolkit.eventloop import run_in_executor_with_context |
|
|
| from .current import get_app_or_none |
|
|
| __all__ = [ |
| "run_in_terminal", |
| "in_terminal", |
| ] |
|
|
| _T = TypeVar("_T") |
|
|
|
|
| def run_in_terminal( |
| func: Callable[[], _T], render_cli_done: bool = False, in_executor: bool = False |
| ) -> Awaitable[_T]: |
| """ |
| Run function on the terminal above the current application or prompt. |
| |
| What this does is first hiding the prompt, then running this callable |
| (which can safely output to the terminal), and then again rendering the |
| prompt which causes the output of this function to scroll above the |
| prompt. |
| |
| ``func`` is supposed to be a synchronous function. If you need an |
| asynchronous version of this function, use the ``in_terminal`` context |
| manager directly. |
| |
| :param func: The callable to execute. |
| :param render_cli_done: When True, render the interface in the |
| 'Done' state first, then execute the function. If False, |
| erase the interface first. |
| :param in_executor: When True, run in executor. (Use this for long |
| blocking functions, when you don't want to block the event loop.) |
| |
| :returns: A `Future`. |
| """ |
|
|
| async def run() -> _T: |
| async with in_terminal(render_cli_done=render_cli_done): |
| if in_executor: |
| return await run_in_executor_with_context(func) |
| else: |
| return func() |
|
|
| return ensure_future(run()) |
|
|
|
|
| @asynccontextmanager |
| async def in_terminal(render_cli_done: bool = False) -> AsyncGenerator[None, None]: |
| """ |
| Asynchronous context manager that suspends the current application and runs |
| the body in the terminal. |
| |
| .. code:: |
| |
| async def f(): |
| async with in_terminal(): |
| call_some_function() |
| await call_some_async_function() |
| """ |
| app = get_app_or_none() |
| if app is None or not app._is_running: |
| yield |
| return |
|
|
| |
| |
| previous_run_in_terminal_f = app._running_in_terminal_f |
| new_run_in_terminal_f: Future[None] = Future() |
| app._running_in_terminal_f = new_run_in_terminal_f |
|
|
| |
| if previous_run_in_terminal_f is not None: |
| await previous_run_in_terminal_f |
|
|
| |
| |
| |
| if app.output.responds_to_cpr: |
| await app.renderer.wait_for_cpr_responses() |
|
|
| |
| if render_cli_done: |
| app._redraw(render_as_done=True) |
| else: |
| app.renderer.erase() |
|
|
| |
| app._running_in_terminal = True |
|
|
| |
| try: |
| with app.input.detach(): |
| with app.input.cooked_mode(): |
| yield |
| finally: |
| |
| try: |
| app._running_in_terminal = False |
| app.renderer.reset() |
| app._request_absolute_cursor_position() |
| app._redraw() |
| finally: |
| |
| |
| if not new_run_in_terminal_f.done(): |
| new_run_in_terminal_f.set_result(None) |
|
|