Skip to content

Commit 97d7e09

Browse files
Accept asyncio coroutines as key binding handlers.
Also fix a bug in the system toolbar: execution of system commands was broken.
1 parent 85df4a8 commit 97d7e09

File tree

4 files changed

+53
-6
lines changed

4 files changed

+53
-6
lines changed

docs/pages/advanced_topics/key_bindings.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,33 @@ called, even if there's an active `ab` binding.
282282
283283
This is mainly useful in order to conditionally override another binding.
284284

285+
Asyncio coroutines
286+
------------------
287+
288+
Key binding handlers can be asyncio coroutines.
289+
290+
.. code:: python
291+
292+
from prompt_toolkit.application import in_terminal
293+
294+
@bindings.add('x')
295+
async def print_hello(event):
296+
"""
297+
Pressing 'x' will print 5 times "hello" in the background above the
298+
prompt.
299+
"""
300+
for i in range(5):
301+
# Print hello above the current prompt.
302+
async with in_terminal():
303+
print('hello')
304+
305+
# Sleep, but allow further input editing in the meantime.
306+
await asyncio.sleep(1)
307+
308+
If the user accepts the input on the prompt, while this coroutine is not yet
309+
finished , an `asyncio.CancelledError` exception will be thrown in this
310+
coroutine.
311+
285312

286313
Timeouts
287314
--------

examples/prompts/custom-key-binding.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
"""
33
Example of adding a custom key binding to a prompt.
44
"""
5+
import asyncio
6+
57
from prompt_toolkit import prompt
6-
from prompt_toolkit.application import run_in_terminal
8+
from prompt_toolkit.application import in_terminal, run_in_terminal
79
from prompt_toolkit.key_binding import KeyBindings
810

911

@@ -52,6 +54,19 @@ def print_hello():
5254

5355
run_in_terminal(print_hello)
5456

57+
@bindings.add("c-k")
58+
async def _(event):
59+
"""
60+
Example of asyncio coroutine as a key binding.
61+
"""
62+
try:
63+
for i in range(5):
64+
async with in_terminal():
65+
print("hello")
66+
await asyncio.sleep(1)
67+
except asyncio.CancelledError:
68+
print("Prompt terminated before we completed.")
69+
5570
# Read input.
5671
print('Press F4 to insert "hello world", type "xy" to insert "z":')
5772
text = prompt("> ", key_bindings=bindings)

prompt_toolkit/key_binding/key_bindings.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def my_key_binding(event):
3737
from abc import ABCMeta, abstractmethod, abstractproperty
3838
from typing import (
3939
TYPE_CHECKING,
40+
Awaitable,
4041
Callable,
4142
Hashable,
4243
List,
@@ -67,7 +68,7 @@ def my_key_binding(event):
6768
"GlobalOnlyKeyBindings",
6869
]
6970

70-
KeyHandlerCallable = Callable[["KeyPressEvent"], None]
71+
KeyHandlerCallable = Callable[["KeyPressEvent"], Union[None, Awaitable[None]]]
7172

7273

7374
class Binding:
@@ -98,7 +99,11 @@ def __init__(
9899
self.record_in_macro = to_filter(record_in_macro)
99100

100101
def call(self, event: "KeyPressEvent") -> None:
101-
self.handler(event)
102+
result = self.handler(event)
103+
104+
# If the handler is a coroutine, create an asyncio task.
105+
if result is not None:
106+
event.app.create_background_task(result)
102107

103108
def __repr__(self) -> str:
104109
return "%s(keys=%r, handler=%r)" % (

prompt_toolkit/widgets/toolbars.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@ def _cancel(event: E) -> None:
127127
event.app.layout.focus_last()
128128

129129
@handle("enter", filter=focused)
130-
def _accept(event: E) -> None:
130+
async def _accept(event: E) -> None:
131131
" Run system command. "
132-
event.app.run_system_command(
132+
await event.app.run_system_command(
133133
self.system_buffer.text,
134134
display_before_text=self._get_display_before_text(),
135135
)
@@ -149,7 +149,7 @@ def _cancel_vi(event: E) -> None:
149149
event.app.layout.focus_last()
150150

151151
@handle("enter", filter=focused)
152-
def _accept_vi(event: E) -> None:
152+
async def _accept_vi(event: E) -> None:
153153
" Run system command. "
154154
event.app.vi_state.input_mode = InputMode.NAVIGATION
155155
event.app.run_system_command(

0 commit comments

Comments
 (0)