From ccba2340e91672e88001fd55a055391d0d4e5ff5 Mon Sep 17 00:00:00 2001 From: MasterofJOKers Date: Sun, 2 Feb 2025 23:46:24 +0100 Subject: [PATCH] Support prompt_toolkit's `in_thread` We expose the functionality to run `CliMenu` in a background thread that the underlying `prompt_toolkit` library supports through a `run_in_thread` argument. This functionality can be necessary if we get called from a running event loop as the documentation of `in_thread` describes: :param in_thread: When true, run the application in a background thread, and block the current thread until the application terminates. This is useful if we need to be sure the application won't use the current event loop (asyncio does not support nested event loops). A new event loop will be created in this background thread, and that loop will also be closed when the background thread terminates. When this is used, it's especially important to make sure that all asyncio background tasks are managed through `get_appp().create_background_task()`, so that unfinished tasks are properly cancelled before the event loop is closed. This is used for instance in ptpython. --- clintermission/climenu.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/clintermission/climenu.py b/clintermission/climenu.py index 8913bfc..2483746 100644 --- a/clintermission/climenu.py +++ b/clintermission/climenu.py @@ -95,7 +95,8 @@ def set_default_cursor(cls, cursor): def __init__(self, options=None, header=None, cursor=None, style=None, indent=2, dedent_selection=False, initial_pos=0, - option_prefix=' ', option_suffix='', right_pad_options=False): + option_prefix=' ', option_suffix='', right_pad_options=False, + run_in_thread=False): self._items = [] self._item_num = 0 self._ran = False @@ -108,6 +109,7 @@ def __init__(self, options=None, header=None, cursor=None, style=None, self._header_indent = indent self._dedent_selection = dedent_selection self._right_pad_options = right_pad_options + self._run_in_thread = run_in_thread self._cursor = cursor if cursor is not None else self.default_cursor self._style = style if style is not None else self.default_style @@ -361,7 +363,7 @@ def accept_search(event): full_screen=False, mouse_support=False) - app.run() + app.run(in_thread=self._run_in_thread) self._ran = True @@ -455,9 +457,10 @@ def _accept(self, event): def cli_select_item(options, header=None, abort_exc=ValueError, abort_text="Selection aborted.", style=None, - return_single=True): + return_single=True, run_in_thread=None): """Helper function to quickly get a selection with just a few arguments""" - menu = CliMenu(header=header, options=options, style=style) + menu = CliMenu(header=header, options=options, style=style, + run_in_thread=run_in_thread) if return_single and menu.num_options == 1: item = menu.get_options()[0]