This commit is contained in:
Will McGugan
2022-01-01 14:48:28 +00:00
parent f47b3e089c
commit 15fee1f5ac
6 changed files with 65 additions and 39 deletions

View File

@@ -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/) The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/). 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 ## [0.1.12] - 2021-09-20
### Added ### Added

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "textual" name = "textual"
version = "0.1.12" version = "0.1.13"
homepage = "https://github.com/willmcgugan/textual" homepage = "https://github.com/willmcgugan/textual"
description = "Text User Interface using Rich" description = "Text User Interface using Rich"
authors = ["Will McGugan <willmcgugan@gmail.com>"] authors = ["Will McGugan <willmcgugan@gmail.com>"]

View File

@@ -113,10 +113,10 @@ class Animator:
async def start(self) -> None: async def start(self) -> None:
if self._timer_task is 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: async def stop(self) -> None:
self._timer.stop() await self._timer.stop()
if self._timer_task: if self._timer_task:
await self._timer_task await self._timer_task
self._timer_task = None self._timer_task = None

View File

@@ -7,7 +7,6 @@ import selectors
import signal import signal
import sys import sys
import termios import termios
from time import time
import tty import tty
from typing import Any, TYPE_CHECKING from typing import Any, TYPE_CHECKING
from threading import Event, Thread from threading import Event, Thread
@@ -15,12 +14,14 @@ from threading import Event, Thread
if TYPE_CHECKING: if TYPE_CHECKING:
from rich.console import Console from rich.console import Console
from . import log
from . import events from . import events
from .driver import Driver from .driver import Driver
from .geometry import Size from .geometry import Size
from ._types import MessageTarget from ._types import MessageTarget
from ._xterm_parser import XTermParser from ._xterm_parser import XTermParser
from ._profile import timer
class LinuxDriver(Driver): class LinuxDriver(Driver):
@@ -53,7 +54,7 @@ class LinuxDriver(Driver):
write("\x1b[?1006h") # SET_SGR_EXT_MODE_MOUSE write("\x1b[?1006h") # SET_SGR_EXT_MODE_MOUSE
# write("\x1b[?1007h") # write("\x1b[?1007h")
# self.console.file.flush() self.console.file.flush()
# Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr
# extensions. # extensions.
@@ -64,7 +65,7 @@ class LinuxDriver(Driver):
write("\x1b[?1003l") # write("\x1b[?1003l") #
write("\x1b[?1015l") write("\x1b[?1015l")
write("\x1b[?1006l") write("\x1b[?1006l")
# self.console.file.flush() self.console.file.flush()
def start_application_mode(self): def start_application_mode(self):
@@ -110,7 +111,7 @@ class LinuxDriver(Driver):
self.console.show_cursor(False) self.console.show_cursor(False)
self.console.file.write("\033[?1003h\n") self.console.file.write("\033[?1003h\n")
self.console.file.flush()
self._key_thread = Thread( self._key_thread = Thread(
target=self.run_input_thread, args=(asyncio.get_event_loop(),) target=self.run_input_thread, args=(asyncio.get_event_loop(),)
) )
@@ -145,23 +146,28 @@ class LinuxDriver(Driver):
if not self.exit_event.is_set(): if not self.exit_event.is_set():
signal.signal(signal.SIGWINCH, signal.SIG_DFL) signal.signal(signal.SIGWINCH, signal.SIG_DFL)
self._disable_mouse_support() self._disable_mouse_support()
termios.tcflush(self.fileno, termios.TCIFLUSH)
self.exit_event.set() self.exit_event.set()
if self._key_thread is not None: if self._key_thread is not None:
self._key_thread.join() self._key_thread.join()
except Exception: except Exception as error:
# TODO: log this # TODO: log this
pass pass
def stop_application_mode(self) -> None: def stop_application_mode(self) -> None:
with timer("disable_input"):
self.disable_input() self.disable_input()
with timer("tcsetattr"):
if self.attrs_before is not None: if self.attrs_before is not None:
try: try:
termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before) termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before)
except termios.error: except termios.error:
pass pass
with timer("set_alt_screen False, show cursor"):
with self.console:
self.console.set_alt_screen(False) self.console.set_alt_screen(False)
self.console.show_cursor(True) self.console.show_cursor(True)
@@ -180,7 +186,7 @@ class LinuxDriver(Driver):
def more_data() -> bool: def more_data() -> bool:
"""Check if there is more data to parse.""" """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: if events:
return True return True
return False return False
@@ -199,10 +205,10 @@ class LinuxDriver(Driver):
unicode_data = decode(read(fileno, 1024)) unicode_data = decode(read(fileno, 1024))
for event in parser.feed(unicode_data): for event in parser.feed(unicode_data):
self.process_event(event) self.process_event(event)
except Exception: except Exception as error:
pass log(error)
# TODO: log
finally: finally:
with timer("selector.close"):
selector.close() selector.close()

View File

@@ -1,7 +1,13 @@
from __future__ import annotations from __future__ import annotations
import weakref 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 time import monotonic
from typing import Awaitable, Callable, Union from typing import Awaitable, Callable, Union
@@ -42,7 +48,6 @@ class Timer:
self._callback = callback self._callback = callback
self._repeat = repeat self._repeat = repeat
self._skip = skip self._skip = skip
self._stop_event = Event()
self._active = Event() self._active = Event()
if not pause: if not pause:
self._active.set() self._active.set()
@@ -59,22 +64,33 @@ class Timer:
raise EventTargetGone() raise EventTargetGone()
return target return target
def stop(self) -> None: def start(self) -> Task:
self._active.set() """Start the timer return the task.
self._stop_event.set()
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: def pause(self) -> None:
"""Pause the timer."""
self._active.clear() self._active.clear()
def resume(self) -> None: def resume(self) -> None:
"""Result a paused timer."""
self._active.set() self._active.set()
async def run(self) -> None: async def _run(self) -> None:
"""Run the timer."""
count = 0 count = 0
_repeat = self._repeat _repeat = self._repeat
_interval = self._interval _interval = self._interval
_wait = self._stop_event.wait
_wait_active = self._active.wait
start = monotonic() start = monotonic()
try: try:
while _repeat is None or count <= _repeat: while _repeat is None or count <= _repeat:
@@ -82,11 +98,9 @@ class Timer:
if self._skip and next_timer < monotonic(): if self._skip and next_timer < monotonic():
count += 1 count += 1
continue continue
try: wait_time = max(0, next_timer - monotonic())
if await wait_for(_wait(), max(0, next_timer - monotonic())): if wait_time:
break await sleep(wait_time)
except TimeoutError:
pass
event = events.Timer( event = events.Timer(
self.sender, timer=self, count=count, callback=self._callback self.sender, timer=self, count=count, callback=self._callback
) )
@@ -95,7 +109,6 @@ class Timer:
await self.target.post_message(event) await self.target.post_message(event)
except EventTargetGone: except EventTargetGone:
break break
await self._active.wait()
await _wait_active()
except CancelledError: except CancelledError:
pass pass

View File

@@ -127,8 +127,7 @@ class MessagePump:
name: str | None = None, name: str | None = None,
) -> Timer: ) -> Timer:
timer = Timer(self, delay, self, name=name, callback=callback, repeat=0) 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.start())
self._child_tasks.add(timer_task)
return timer return timer
def set_interval( def set_interval(
@@ -142,7 +141,7 @@ class MessagePump:
timer = Timer( timer = Timer(
self, interval, self, name=name, callback=callback, repeat=repeat or None 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 return timer
async def call_later(self, callback: Callable, *args, **kwargs) -> None: async def call_later(self, callback: Callable, *args, **kwargs) -> None: