Skip to content

Commit a81e593

Browse files
Handle SIGINT (when sent from another process) and allow binding it to a key binding.
1 parent e9eac2e commit a81e593

File tree

5 files changed

+33
-1
lines changed

5 files changed

+33
-1
lines changed

prompt_toolkit/application/application.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ async def run_async(
630630
self,
631631
pre_run: Optional[Callable[[], None]] = None,
632632
set_exception_handler: bool = True,
633+
handle_sigint: bool = True,
633634
) -> _AppResult:
634635
"""
635636
Run the prompt_toolkit :class:`~prompt_toolkit.application.Application`
@@ -646,6 +647,8 @@ async def run_async(
646647
:param set_exception_handler: When set, in case of an exception, go out
647648
of the alternate screen and hide the application, display the
648649
exception, and wait for the user to press ENTER.
650+
:param handle_sigint: Handle SIGINT signal. Call the key binding for
651+
`Keys.SIGINT`. (This only works in the main thread.)
649652
"""
650653
assert not self._is_running, "Application is already running."
651654

@@ -781,6 +784,15 @@ async def _run_async2() -> _AppResult:
781784
self._invalidated = False
782785

783786
loop = get_event_loop()
787+
788+
if handle_sigint:
789+
loop.add_signal_handler(
790+
signal.SIGINT,
791+
lambda *_: loop.call_soon_threadsafe(
792+
self.key_processor.send_sigint
793+
),
794+
)
795+
784796
if set_exception_handler:
785797
previous_exc_handler = loop.get_exception_handler()
786798
loop.set_exception_handler(self._handle_exception)
@@ -812,13 +824,17 @@ async def _run_async2() -> _AppResult:
812824
if set_exception_handler:
813825
loop.set_exception_handler(previous_exc_handler)
814826

827+
if handle_sigint:
828+
loop.remove_signal_handler(signal.SIGINT)
829+
815830
return await _run_async2()
816831

817832
def run(
818833
self,
819834
pre_run: Optional[Callable[[], None]] = None,
820835
set_exception_handler: bool = True,
821836
in_thread: bool = False,
837+
handle_sigint: bool = True,
822838
) -> _AppResult:
823839
"""
824840
A blocking 'run' call that waits until the UI is finished.
@@ -843,6 +859,8 @@ def run(
843859
`get_appp().create_background_task()`, so that unfinished tasks are
844860
properly cancelled before the event loop is closed. This is used
845861
for instance in ptpython.
862+
:param handle_sigint: Handle SIGINT signal. Call the key binding for
863+
`Keys.SIGINT`. (This only works in the main thread.)
846864
"""
847865
if in_thread:
848866
result: _AppResult
@@ -852,7 +870,10 @@ def run_in_thread() -> None:
852870
nonlocal result, exception
853871
try:
854872
result = self.run(
855-
pre_run=pre_run, set_exception_handler=set_exception_handler
873+
pre_run=pre_run,
874+
set_exception_handler=set_exception_handler,
875+
# Signal handling only works in the main thread.
876+
handle_sigint=False,
856877
)
857878
except BaseException as e:
858879
exception = e

prompt_toolkit/key_binding/bindings/basic.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ def load_basic_bindings() -> KeyBindings:
120120
@handle("insert")
121121
@handle("s-insert")
122122
@handle("c-insert")
123+
@handle("<sigint>")
123124
@handle(Keys.Ignore)
124125
def _ignore(event: E) -> None:
125126
"""

prompt_toolkit/key_binding/key_processor.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,13 @@ def flush_keys() -> None:
411411
self._flush_wait_task.cancel()
412412
self._flush_wait_task = app.create_background_task(wait())
413413

414+
def send_sigint(self) -> None:
415+
"""
416+
Send SIGINT. Immediately call the SIGINT key handler.
417+
"""
418+
self.feed(KeyPress(key=Keys.SIGINT), first=True)
419+
self.process_keys()
420+
414421

415422
class KeyPressEvent:
416423
"""

prompt_toolkit/keys.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ class Keys(str, Enum):
184184
WindowsMouseEvent = "<windows-mouse-event>"
185185
BracketedPaste = "<bracketed-paste>"
186186

187+
SIGINT = "<sigint>"
188+
187189
# For internal use: key which is ignored.
188190
# (The key binding for this key should not do anything.)
189191
Ignore = "<ignore>"

prompt_toolkit/shortcuts/prompt.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,7 @@ def _complete_like_readline(event: E) -> None:
807807
display_completions_like_readline(event)
808808

809809
@handle("c-c", filter=default_focused)
810+
@handle("<sigint>")
810811
def _keyboard_interrupt(event: E) -> None:
811812
"Abort when Control-C has been pressed."
812813
event.app.exit(exception=KeyboardInterrupt, style="class:aborting")

0 commit comments

Comments
 (0)