textual
textual copied to clipboard
Allow `Driver`s to handle key events after being restarted
Change: clear driver exit events after key-handling threads are joined in the two concrete Driver implementations, which allows Driver.start_application_mode() to be called again successfully after Driver.stop_application_mode().
This lets something like this work:
import sys
import time
from contextlib import contextmanager, redirect_stdout, redirect_stderr
from typing import Iterator
from textual.app import App, ComposeResult
from textual.widgets import Footer, Header
class SuspendApp(App):
BINDINGS = [
("d", "toggle_dark", "Toggle dark mode"),
("s", "suspend", "Suspend and print a message"),
]
def compose(self) -> ComposeResult:
"""Create child widgets for the app."""
yield Header()
yield Footer()
def action_toggle_dark(self) -> None:
"""An action to toggle dark mode."""
self.dark = not self.dark
def action_suspend(self) -> None:
with self.suspend():
print("Hi!")
print("Resuming soon...")
time.sleep(2)
@contextmanager
def suspend(self) -> Iterator[None]:
driver = self._driver
if driver is not None:
driver.stop_application_mode()
with redirect_stdout(sys.__stdout__), redirect_stderr(sys.__stderr__):
yield
driver.start_application_mode()
if __name__ == "__main__":
SuspendApp().run()
Without this change, when application modes resumes at the end of SuspendApp.suspend, the Driver's key thread will be started but will immediately stop because the exit event is still set, so the bindings won't work anymore. With this change, bindings work after the suspend finishes.
I put the event reset immediately after the key thread is joined to follow the general feeling of Driver.stop_application_mode trying to put the system back in its initial state.
I made a few attempts to write tests for this but was stymied by the dependence of the Driver on the event loop and a message pump to send message to - I didn't see an existing pattern in the test suite to copy for tests that rely on the event loop or message pumps, but maybe I just missed it?
I've tested manually on Linux locally (using the above example script), but not on Windows or Mac. Happy to continue hacking on tests for this behavior since it'd be easy to inadvertently regress it, but I think I need some guidance on how to do so.
(For context, I'm using this trick to play around with dropping out of the Textual app and into another CLI app, namely IPython https://github.com/JoshKarpel/spiel/blob/9da45e1f22b4a284ac9691fa9125329598ed28ad/spiel/app.py#L151-L176)
@JoshKarpel can confirm this works when I tested it on my code to drop the user into an editor and resuming textual. This will resolve issues #1148, #1093 and discussion https://github.com/Textualize/textual/discussions/165.
Thanks for this @JoshKarpel, it looks really useful - we'll try and review it soon!
Can confirm this fixed the issue inside my file manager app :)
Thanks @JoshKarpel
@darrenburns Care to merge once tests pass?
Thanks all!