diff --git a/CHANGELOG.md b/CHANGELOG.md index 129c94cd7..11f0edd6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). + +## [0.1.13] - 2022-01-01 + +### Fixed + +- Fixed spurious characters when exiting app +- Fixed increasing delay when exiting + ## [0.1.12] - 2021-09-20 ### Added diff --git a/pyproject.toml b/pyproject.toml index 7d52be98f..b96ddb871 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "textual" -version = "0.1.12" +version = "0.1.13" homepage = "https://github.com/willmcgugan/textual" description = "Text User Interface using Rich" authors = ["Will McGugan "] diff --git a/src/textual/_animator.py b/src/textual/_animator.py index 2d8f4c3bd..b3bc42b6b 100644 --- a/src/textual/_animator.py +++ b/src/textual/_animator.py @@ -113,10 +113,10 @@ class Animator: async def start(self) -> None: if self._timer_task is None: - self._timer_task = asyncio.get_event_loop().create_task(self._timer.run()) + self._timer_task = self._timer.start() async def stop(self) -> None: - self._timer.stop() + await self._timer.stop() if self._timer_task: await self._timer_task self._timer_task = None diff --git a/src/textual/_linux_driver.py b/src/textual/_linux_driver.py index 7f4df92b2..a20e19f0e 100644 --- a/src/textual/_linux_driver.py +++ b/src/textual/_linux_driver.py @@ -7,7 +7,6 @@ import selectors import signal import sys import termios -from time import time import tty from typing import Any, TYPE_CHECKING from threading import Event, Thread @@ -15,12 +14,14 @@ from threading import Event, Thread if TYPE_CHECKING: from rich.console import Console +from . import log from . import events from .driver import Driver from .geometry import Size from ._types import MessageTarget from ._xterm_parser import XTermParser +from ._profile import timer class LinuxDriver(Driver): @@ -53,7 +54,7 @@ class LinuxDriver(Driver): write("\x1b[?1006h") # SET_SGR_EXT_MODE_MOUSE # write("\x1b[?1007h") - # self.console.file.flush() + self.console.file.flush() # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr # extensions. @@ -64,7 +65,7 @@ class LinuxDriver(Driver): write("\x1b[?1003l") # write("\x1b[?1015l") write("\x1b[?1006l") - # self.console.file.flush() + self.console.file.flush() def start_application_mode(self): @@ -110,7 +111,7 @@ class LinuxDriver(Driver): self.console.show_cursor(False) self.console.file.write("\033[?1003h\n") - + self.console.file.flush() self._key_thread = Thread( target=self.run_input_thread, args=(asyncio.get_event_loop(),) ) @@ -145,25 +146,30 @@ class LinuxDriver(Driver): if not self.exit_event.is_set(): signal.signal(signal.SIGWINCH, signal.SIG_DFL) self._disable_mouse_support() + termios.tcflush(self.fileno, termios.TCIFLUSH) self.exit_event.set() if self._key_thread is not None: self._key_thread.join() - except Exception: + except Exception as error: # TODO: log this pass def stop_application_mode(self) -> None: - self.disable_input() + with timer("disable_input"): + self.disable_input() - if self.attrs_before is not None: - try: - termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before) - except termios.error: - pass + with timer("tcsetattr"): + if self.attrs_before is not None: + try: + termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before) + except termios.error: + pass - self.console.set_alt_screen(False) - self.console.show_cursor(True) + with timer("set_alt_screen False, show cursor"): + with self.console: + self.console.set_alt_screen(False) + self.console.show_cursor(True) def run_input_thread(self, loop) -> None: try: @@ -180,7 +186,7 @@ class LinuxDriver(Driver): def more_data() -> bool: """Check if there is more data to parse.""" - for key, events in selector.select(0.1): + for key, events in selector.select(0.01): if events: return True return False @@ -199,11 +205,11 @@ class LinuxDriver(Driver): unicode_data = decode(read(fileno, 1024)) for event in parser.feed(unicode_data): self.process_event(event) - except Exception: - pass - # TODO: log + except Exception as error: + log(error) finally: - selector.close() + with timer("selector.close"): + selector.close() if __name__ == "__main__": diff --git a/src/textual/_timer.py b/src/textual/_timer.py index 1e8999d9b..cbeef27cd 100644 --- a/src/textual/_timer.py +++ b/src/textual/_timer.py @@ -1,7 +1,13 @@ from __future__ import annotations import weakref -from asyncio import CancelledError, Event, TimeoutError, wait_for +from asyncio import ( + get_event_loop, + CancelledError, + Event, + sleep, + Task, +) from time import monotonic from typing import Awaitable, Callable, Union @@ -42,7 +48,6 @@ class Timer: self._callback = callback self._repeat = repeat self._skip = skip - self._stop_event = Event() self._active = Event() if not pause: self._active.set() @@ -59,22 +64,33 @@ class Timer: raise EventTargetGone() return target - def stop(self) -> None: - self._active.set() - self._stop_event.set() + def start(self) -> Task: + """Start the timer return the task. + + Returns: + Task: A Task instance for the timer. + """ + self._task = get_event_loop().create_task(self._run()) + return self._task + + async def stop(self) -> None: + """Stop the timer, and block until it exists.""" + self._task.cancel() + await self._task def pause(self) -> None: + """Pause the timer.""" self._active.clear() def resume(self) -> None: + """Result a paused timer.""" self._active.set() - async def run(self) -> None: + async def _run(self) -> None: + """Run the timer.""" count = 0 _repeat = self._repeat _interval = self._interval - _wait = self._stop_event.wait - _wait_active = self._active.wait start = monotonic() try: while _repeat is None or count <= _repeat: @@ -82,11 +98,9 @@ class Timer: if self._skip and next_timer < monotonic(): count += 1 continue - try: - if await wait_for(_wait(), max(0, next_timer - monotonic())): - break - except TimeoutError: - pass + wait_time = max(0, next_timer - monotonic()) + if wait_time: + await sleep(wait_time) event = events.Timer( self.sender, timer=self, count=count, callback=self._callback ) @@ -95,7 +109,6 @@ class Timer: await self.target.post_message(event) except EventTargetGone: break - - await _wait_active() + await self._active.wait() except CancelledError: pass diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index 0da7c6953..50a37e424 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -127,8 +127,7 @@ class MessagePump: name: str | None = None, ) -> Timer: timer = Timer(self, delay, self, name=name, callback=callback, repeat=0) - timer_task = asyncio.get_event_loop().create_task(timer.run()) - self._child_tasks.add(timer_task) + self._child_tasks.add(timer.start()) return timer def set_interval( @@ -142,7 +141,7 @@ class MessagePump: timer = Timer( self, interval, self, name=name, callback=callback, repeat=repeat or None ) - self._child_tasks.add(asyncio.get_event_loop().create_task(timer.run())) + self._child_tasks.add(timer.start()) return timer async def call_later(self, callback: Callable, *args, **kwargs) -> None: