diff --git a/src/textual/_xterm_parser.py b/src/textual/_xterm_parser.py index 4c8ec1794..a0f09ac67 100644 --- a/src/textual/_xterm_parser.py +++ b/src/textual/_xterm_parser.py @@ -65,11 +65,12 @@ class XTermParser(Parser[events.Event]): while not self.is_eof: character = yield read1() + log.debug("character=%r", character) if character == ESC and ((yield self.peek_buffer()) or more_data()): sequence: str = character while True: sequence += yield read1() - log.debug(f"sequence={sequence!r}") + log.debug(f"sequence=%r", sequence) keys = get_ansi_sequence(sequence, None) if keys is not None: for key in keys: diff --git a/src/textual/app.py b/src/textual/app.py index ef5e0420e..a1a2db67f 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -113,13 +113,10 @@ class App(MessagePump): except Exception: log.exception("error starting application mode") raise - try: - await super().process_messages() - finally: - try: - driver.stop_application_mode() - finally: - loop.remove_signal_handler(signal.SIGINT) + await super().process_messages() + + await self.view.close_messages() + driver.stop_application_mode() async def add(self, child: MessagePump) -> None: self.children.add(child) @@ -132,45 +129,16 @@ class App(MessagePump): async def shutdown(self): driver = self._driver driver.disable_input() - - async def shutdown_procedure() -> None: - log.debug("1") - await self.stop_messages() - log.debug("2") - await self.view.stop_messages() - log.debug("3") - log.debug("4") - await self.remove(self.view) - if self.children: - log.debug("5") - - async def close_all() -> None: - for child in self.children: - await child.close_messages(wait=False) - await asyncio.gather(*(child.task for child in self.children)) - - try: - await asyncio.wait_for(close_all(), timeout=5) - log.debug("6") - except asyncio.TimeoutError as error: - raise ShutdownError("Timeout closing messages pump(s)") from None - log.debug("7") - - log.debug("8") - await self.view.close_messages() - log.debug("9") - await self.close_messages() - log.debug("10") - - await asyncio.create_task(shutdown_procedure()) + await self.close_messages() def refresh(self) -> None: - console = self.console - try: - with console: - console.print(Screen(Control.home(), self.view, Control.home())) - except Exception: - log.exception("refresh failed") + if not self._closed: + console = self.console + try: + with console: + console.print(Screen(Control.home(), self.view, Control.home())) + except Exception: + log.exception("refresh failed") async def on_event(self, event: events.Event) -> None: if isinstance(event, events.Key): diff --git a/src/textual/events.py b/src/textual/events.py index 86bcc80cf..9d572f78c 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -50,6 +50,7 @@ class EventType(Enum): CUSTOM = 1000 +@rich_repr class Event(Message): type: ClassVar[EventType] diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index 45a1a591f..67cd6f219 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -28,7 +28,6 @@ class MessagePump: self._pending_message: Message | None = None self._task: Task | None = None self._child_tasks: set[Task] = set() - self._queue_empty_event = Event() @property def task(self) -> Task: @@ -111,34 +110,16 @@ class MessagePump: asyncio.get_event_loop().create_task(timer.run()) return timer - async def stop_messages(self) -> None: - if not self._closing: - await self.post_message(events.NoneEvent(self)) - self._closing = True - return - if not (self._closing or self._closed): - self._queue_empty_event.clear() - await self.post_message(events.NoneEvent(self)) - self._closing = True - await self._queue_empty_event.wait() - self._queue_empty_event.clear() - async def close_messages(self, wait: bool = True) -> None: """Close message queue, and optionally wait for queue to finish processing.""" if self._closed: return - log.debug("close_messages %r wait=%r", self, wait) + self._closing = True - log.debug("close 1 %r", self) + await self._message_queue.put(None) + for task in self._child_tasks: task.cancel() - log.debug("close 2 %r", self) - await self._message_queue.put(None) - log.debug("close 3 %r", self) - if wait and self._task is not None: - await self._task - self._task = None - log.debug("close 4 %r", self) def start_messages(self) -> None: self._task = asyncio.create_task(self.process_messages()) @@ -149,7 +130,6 @@ class MessagePump: try: message = await self.get_message() except MessagePumpClosed: - log.debug("CLOSED %r", self) break except Exception as error: log.exception("error in get_message()") @@ -157,7 +137,7 @@ class MessagePump: log.debug("%r -> %r", message, self) # Combine any pending messages that may supersede this one - while True: + while not (self._closed or self._closing): pending = self.peek_message() if pending is None or not message.can_batch(pending): break @@ -172,21 +152,14 @@ class MessagePump: log.exception("error in dispatch_message") raise finally: - log.debug("a") if self._message_queue.empty(): - log.debug("b") - self._queue_empty_event.set() if not self._closed: idle_handler = getattr(self, "on_idle", None) - log.debug("c %r", idle_handler) if idle_handler is not None and not self._closed: - log.debug("d") await idle_handler(events.Idle(self)) - log.debug("e") - self._queue_empty_event.set() + log.debug("CLOSED %r", self) async def dispatch_message(self, message: Message) -> bool | None: - log.debug("dispatch_message %r", message) if isinstance(message, events.Event): await self.on_event(message) else: @@ -196,7 +169,6 @@ class MessagePump: async def on_event(self, event: events.Event) -> None: method_name = f"on_{event.name}" dispatch_function: MessageHandler = getattr(self, method_name, None) - log.debug("dispatching to %r", dispatch_function) if dispatch_function is not None: await dispatch_function(event) if event.bubble and self._parent and not event._stop_propagaton: @@ -217,15 +189,11 @@ class MessagePump: return True async def post_message(self, message: Message) -> bool: - log.debug("%r post_message 1", self) if self._closing or self._closed: return False - log.debug("%r post_message 2", self) if not self.check_message_enabled(message): return True - log.debug("%r post_message 3", self) await self._message_queue.put(message) - log.debug("%r post_message 4", self) return True async def post_message_from_child(self, message: Message) -> bool: