mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
fix for timer
This commit is contained in:
@@ -19,4 +19,4 @@ class BasicApp(App):
|
||||
)
|
||||
|
||||
|
||||
BasicApp.run(css_file="basic.css", watch_css=True)
|
||||
BasicApp.run(css_file="basic.css", watch_css=True, log="textual.log")
|
||||
|
||||
10
examples/colours.txt
Normal file
10
examples/colours.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
header blue white on #173f5f
|
||||
|
||||
sidebar #09312e on #3caea3
|
||||
sidebar border #09312e
|
||||
|
||||
content blue white #20639b
|
||||
content border #0f2b41
|
||||
|
||||
footer border #0f2b41
|
||||
footer yellow #3a3009 on #f6d55c;
|
||||
@@ -10,6 +10,7 @@ from dataclasses import dataclass
|
||||
|
||||
from . import log
|
||||
from ._easing import DEFAULT_EASING, EASING
|
||||
from ._profile import timer
|
||||
from ._timer import Timer
|
||||
from ._types import MessageTarget
|
||||
|
||||
@@ -124,17 +125,12 @@ class Animator:
|
||||
callback=self,
|
||||
pause=True,
|
||||
)
|
||||
self._timer_task: asyncio.Task | None = None
|
||||
|
||||
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.start()
|
||||
|
||||
async def stop(self) -> None:
|
||||
self._timer.stop()
|
||||
if self._timer_task:
|
||||
await self._timer_task
|
||||
self._timer_task = None
|
||||
await self._timer.stop()
|
||||
|
||||
def bind(self, obj: object) -> BoundAnimator:
|
||||
return BoundAnimator(self, obj)
|
||||
|
||||
@@ -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):
|
||||
@@ -149,23 +150,26 @@ class LinuxDriver(Driver):
|
||||
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)
|
||||
self.console.file.flush()
|
||||
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:
|
||||
@@ -182,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
|
||||
@@ -201,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__":
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import weakref
|
||||
from asyncio import CancelledError, Event, TimeoutError, wait_for
|
||||
from asyncio import (
|
||||
get_event_loop,
|
||||
CancelledError,
|
||||
Event,
|
||||
TimeoutError,
|
||||
sleep,
|
||||
wait,
|
||||
wait_for,
|
||||
Task,
|
||||
)
|
||||
from time import monotonic
|
||||
from typing import Awaitable, Callable, Union
|
||||
|
||||
from rich.repr import Result, rich_repr
|
||||
|
||||
from ._profile import timer
|
||||
from . import log
|
||||
from . import events
|
||||
from ._types import MessageTarget
|
||||
|
||||
@@ -42,7 +53,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,9 +69,16 @@ class Timer:
|
||||
raise EventTargetGone()
|
||||
return target
|
||||
|
||||
def stop(self) -> None:
|
||||
self._active.set()
|
||||
self._stop_event.set()
|
||||
def start(self) -> Task:
|
||||
self._task = get_event_loop().create_task(self.run())
|
||||
return self._task
|
||||
|
||||
async def stop(self) -> None:
|
||||
self._task.cancel()
|
||||
await self._task
|
||||
|
||||
async def wait(self) -> None:
|
||||
await self._task
|
||||
|
||||
def pause(self) -> None:
|
||||
self._active.clear()
|
||||
@@ -73,20 +90,19 @@ class 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:
|
||||
next_timer = start + ((count + 1) * _interval)
|
||||
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 +111,8 @@ class Timer:
|
||||
await self.target.post_message(event)
|
||||
except EventTargetGone:
|
||||
break
|
||||
await self._active.wait()
|
||||
|
||||
await _wait_active()
|
||||
except CancelledError:
|
||||
pass
|
||||
log(timer_id=id(self))
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
from typing import Callable, Generator
|
||||
from typing import Any, Callable, Generator
|
||||
|
||||
from . import log
|
||||
from . import events
|
||||
from ._types import MessageTarget
|
||||
from ._parser import Awaitable, Parser, TokenCallback
|
||||
@@ -22,8 +24,17 @@ class XTermParser(Parser[events.Event]):
|
||||
self.more_data = more_data
|
||||
self.last_x = 0
|
||||
self.last_y = 0
|
||||
|
||||
self._debug_log_file = (
|
||||
open("keys.log", "wt") if "TEXTUAL_DEBUG" in os.environ else None
|
||||
)
|
||||
|
||||
super().__init__()
|
||||
|
||||
def debug_log(self, *args: Any) -> None:
|
||||
if self._debug_log_file is not None:
|
||||
self._debug_log_file.write(" ".join(args) + "\n")
|
||||
|
||||
def parse_mouse_code(self, code: str, sender: MessageTarget) -> events.Event | None:
|
||||
sgr_match = self._re_sgr_mouse.match(code)
|
||||
if sgr_match:
|
||||
@@ -71,12 +82,14 @@ class XTermParser(Parser[events.Event]):
|
||||
|
||||
while not self.is_eof:
|
||||
character = yield read1()
|
||||
# log.debug("character=%r", character)
|
||||
self.debug_log(f"character={character!r}")
|
||||
# The more_data is to allow the parse to distinguish between an escape sequence
|
||||
# and the escape key pressed
|
||||
if character == ESC and ((yield self.peek_buffer()) or more_data()):
|
||||
sequence: str = character
|
||||
while True:
|
||||
sequence += yield read1()
|
||||
# log.debug(f"sequence=%r", sequence)
|
||||
self.debug_log(f"sequence={sequence!r}")
|
||||
keys = get_ansi_sequence(sequence, None)
|
||||
if keys is not None:
|
||||
for key in keys:
|
||||
|
||||
@@ -364,8 +364,10 @@ class App(DOMNode):
|
||||
await self.animator.start()
|
||||
await super().process_messages()
|
||||
log("PROCESS END")
|
||||
await self.animator.stop()
|
||||
await self.close_all()
|
||||
with timer("animator.stop()"):
|
||||
await self.animator.stop()
|
||||
with timer("self.close_all()"):
|
||||
await self.close_all()
|
||||
finally:
|
||||
driver.stop_application_mode()
|
||||
except:
|
||||
|
||||
@@ -125,8 +125,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(
|
||||
@@ -140,7 +139,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:
|
||||
|
||||
@@ -273,7 +273,6 @@ class Widget(DOMNode):
|
||||
elif repaint:
|
||||
self.clear_render_cache()
|
||||
self._repaint_required = True
|
||||
self.log("refresh", repaint, layout)
|
||||
self.post_message_no_wait(events.Null(self))
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
|
||||
Reference in New Issue
Block a user