Merge pull request #384 from Textualize/margin-padding-granularity

tweak to log
This commit is contained in:
Will McGugan
2022-04-13 15:12:36 +01:00
committed by GitHub
8 changed files with 115 additions and 40 deletions

View File

@@ -58,7 +58,7 @@ App > Screen {
#header { #header {
color: $text-primary-darken-1; color: $text-primary-darken-1;
background: $primary-darken-1; background: $primary-darken-1;
height: 3; height: 3
} }
@@ -76,7 +76,7 @@ Tweet {
max-width: 80; max-width: 80;
margin: 1 3; margin: 1 3;
background: $panel; background: $panel;
color: $text-panel color: $text-panel;
layout: vertical; layout: vertical;
/* border: outer $primary; */ /* border: outer $primary; */
padding: 1; padding: 1;

View File

@@ -4,6 +4,7 @@ import asyncio
import inspect import inspect
import os import os
import platform import platform
from time import perf_counter
import warnings import warnings
from asyncio import AbstractEventLoop from asyncio import AbstractEventLoop
from pathlib import Path from pathlib import Path
@@ -29,7 +30,7 @@ from ._callback import invoke
from ._context import active_app from ._context import active_app
from ._event_broker import extract_handler_actions, NoHandler from ._event_broker import extract_handler_actions, NoHandler
from .binding import Bindings, NoBinding from .binding import Bindings, NoBinding
from .css.stylesheet import Stylesheet, StylesheetParseError, StylesheetError from .css.stylesheet import Stylesheet
from .devtools.client import DevtoolsClient, DevtoolsConnectionError from .devtools.client import DevtoolsClient, DevtoolsConnectionError
from .design import ColorSystem from .design import ColorSystem
from .dom import DOMNode from .dom import DOMNode
@@ -100,8 +101,9 @@ class App(DOMNode):
driver_class (Type[Driver], optional): Driver class, or None to use default. Defaults to None. driver_class (Type[Driver], optional): Driver class, or None to use default. Defaults to None.
title (str, optional): Title of the application. Defaults to "Textual Application". title (str, optional): Title of the application. Defaults to "Textual Application".
""" """
self.console = Console(markup=False, highlight=False) self.console = Console(markup=False, highlight=False, emoji=False)
self.error_console = Console(markup=False, stderr=True) self.error_console = Console(markup=False, stderr=True)
self._screen = screen self._screen = screen
self.driver_class = driver_class or self.get_driver_class() self.driver_class = driver_class or self.get_driver_class()
self._title = title self._title = title
@@ -121,7 +123,20 @@ class App(DOMNode):
self.bindings = Bindings() self.bindings = Bindings()
self._title = title self._title = title
self.log_file = open(log, "wt") if log else None self._log_console: Console | None = None
if log:
self.log_file = open(log, "wt")
self._log_console = Console(
file=self.log_file,
markup=False,
emoji=False,
highlight=False,
width=100,
)
else:
self._log_console = None
self._log_file = None
self.log_verbosity = log_verbosity self.log_verbosity = log_verbosity
self.bindings.bind("ctrl+c", "quit", show=False, allow_forward=False) self.bindings.bind("ctrl+c", "quit", show=False, allow_forward=False)
@@ -219,30 +234,36 @@ class App(DOMNode):
_textual_calling_frame (inspect.FrameInfo | None): The frame info to include in _textual_calling_frame (inspect.FrameInfo | None): The frame info to include in
the log message sent to the devtools server. the log message sent to the devtools server.
""" """
output = "" if verbosity > self.log_verbosity:
return
try: try:
output = f" ".join(str(arg) for arg in objects) if len(objects) == 1 and not kwargs:
if kwargs: if self._log_console is not None:
key_values = " ".join(f"{key}={value}" for key, value in kwargs.items()) self._log_console.print(objects[0])
output = " ".join((output, key_values)) if self.devtools.is_connected:
if not _textual_calling_frame:
if not _textual_calling_frame: _textual_calling_frame = inspect.stack()[1]
_textual_calling_frame = inspect.stack()[1] calling_path = _textual_calling_frame.filename
calling_lineno = _textual_calling_frame.lineno
calling_path = _textual_calling_frame.filename
calling_lineno = _textual_calling_frame.lineno
if self.devtools.is_connected and verbosity <= self.log_verbosity:
if len(objects) > 1 or len(kwargs) >= 1 and output:
self.devtools.log(output, path=calling_path, lineno=calling_lineno)
else:
self.devtools.log( self.devtools.log(
*objects, path=calling_path, lineno=calling_lineno objects[0], path=calling_path, lineno=calling_lineno
) )
else:
if self.log_file and verbosity <= self.log_verbosity: output = " ".join(str(arg) for arg in objects)
self.log_file.write(output + "\n") if kwargs:
self.log_file.flush() key_values = " ".join(
f"{key}={value}" for key, value in kwargs.items()
)
output = " ".join((output, key_values))
if self._log_console is not None:
self._log_console.print(output, soft_wrap=True)
if self.devtools.is_connected:
if not _textual_calling_frame:
_textual_calling_frame = inspect.stack()[1]
calling_path = _textual_calling_frame.filename
calling_lineno = _textual_calling_frame.lineno
self.devtools.log(output, path=calling_path, lineno=calling_lineno)
except Exception: except Exception:
pass pass
@@ -306,15 +327,18 @@ class App(DOMNode):
event_loop.close() event_loop.close()
async def _on_css_change(self) -> None: async def _on_css_change(self) -> None:
"""Called when the CSS changes (if watch_css is True)."""
if self.css_file is not None: if self.css_file is not None:
stylesheet = Stylesheet(variables=self.get_css_variables()) stylesheet = Stylesheet(variables=self.get_css_variables())
try: try:
self.log("loading", self.css_file) time = perf_counter()
stylesheet.read(self.css_file) stylesheet.read(self.css_file)
except StylesheetError as error: elapsed = (perf_counter() - time) * 1000
self.log(error) self.log(f"loaded {self.css_file} in {elapsed:.0f}ms")
except Exception as error:
# TODO: catch specific exceptions
self.console.bell() self.console.bell()
self.log(error)
else: else:
self.reset_styles() self.reset_styles()
self.stylesheet = stylesheet self.stylesheet = stylesheet

View File

@@ -275,8 +275,8 @@ class StylesBuilder:
space: list[int] = [] space: list[int] = []
append = space.append append = space.append
for token in tokens: for token in tokens:
token_name, value, _, _, location, _ = token token_name, value, _, _, _, _ = token
if token_name in ("number", "scalar"): if token_name == "number":
try: try:
append(int(value)) append(int(value))
except ValueError: except ValueError:
@@ -289,11 +289,42 @@ class StylesBuilder:
) )
self.styles._rules[name] = Spacing.unpack(cast(SpacingDimensions, tuple(space))) self.styles._rules[name] = Spacing.unpack(cast(SpacingDimensions, tuple(space)))
def process_padding(self, name: str, tokens: list[Token]) -> None: def _process_space_partial(self, name: str, tokens: list[Token]) -> None:
self._process_space(name, tokens) """Process granular margin / padding declarations."""
if len(tokens) != 1:
self.error(name, tokens[0], "expected a single token here")
def process_margin(self, name: str, tokens: list[Token]) -> None: _EDGE_SPACING_MAP = {"top": 0, "right": 1, "bottom": 2, "left": 3}
self._process_space(name, tokens) token = tokens[0]
token_name, value, _, _, _, _ = token
if token_name == "number":
space = int(value)
else:
self.error(name, token, f"expected a number here; found {value!r}")
style_name, _, edge = name.replace("-", "_").partition("_")
current_spacing = cast(
"tuple[int, int, int, int]",
self.styles._rules.get(style_name, (0, 0, 0, 0)),
)
spacing_list = list(current_spacing)
spacing_list[_EDGE_SPACING_MAP[edge]] = space
self.styles._rules[style_name] = Spacing(*spacing_list)
process_padding = _process_space
process_margin = _process_space
process_margin_top = _process_space_partial
process_margin_right = _process_space_partial
process_margin_bottom = _process_space_partial
process_margin_left = _process_space_partial
process_padding_top = _process_space_partial
process_padding_right = _process_space_partial
process_padding_bottom = _process_space_partial
process_padding_left = _process_space_partial
def _parse_border(self, name: str, tokens: list[Token]) -> tuple[str, Color]: def _parse_border(self, name: str, tokens: list[Token]) -> tuple[str, Color]:
border_type = "solid" border_type = "solid"

View File

@@ -28,7 +28,7 @@ from ..dom import DOMNode
from .. import log from .. import log
class StylesheetParseError(Exception): class StylesheetParseError(StylesheetError):
def __init__(self, errors: StylesheetErrors) -> None: def __init__(self, errors: StylesheetErrors) -> None:
self.errors = errors self.errors = errors

View File

@@ -202,7 +202,7 @@ class Key(InputEvent):
@rich.repr.auto @rich.repr.auto
class MouseEvent(InputEvent, bubble=True): class MouseEvent(InputEvent, bubble=True, verbosity=2):
"""Sent in response to a mouse event""" """Sent in response to a mouse event"""
__slots__ = [ __slots__ = [
@@ -344,7 +344,7 @@ class MouseScrollDown(InputEvent, verbosity=3, bubble=True):
self.y = y self.y = y
class MouseScrollUp(MouseScrollDown, bubble=True): class MouseScrollUp(MouseScrollDown, verbosity=3, bubble=True):
pass pass

View File

@@ -91,7 +91,6 @@ class Screen(Widget):
def on_idle(self, event: events.Idle) -> None: def on_idle(self, event: events.Idle) -> None:
# Check for any widgets marked as 'dirty' (needs a repaint) # Check for any widgets marked as 'dirty' (needs a repaint)
if self._dirty_widgets: if self._dirty_widgets:
self.log(dirty=len(self._dirty_widgets))
for widget in self._dirty_widgets: for widget in self._dirty_widgets:
# Repaint widgets # Repaint widgets
# TODO: Combine these in to a single update. # TODO: Combine these in to a single update.

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
@@ -9,6 +11,7 @@ from textual.css.stylesheet import Stylesheet, StylesheetParseError
from textual.css.tokenize import tokenize from textual.css.tokenize import tokenize
from textual.css.tokenizer import Token, ReferencedBy from textual.css.tokenizer import Token, ReferencedBy
from textual.css.transition import Transition from textual.css.transition import Transition
from textual.geometry import Spacing
from textual.layouts.dock import DockLayout from textual.layouts.dock import DockLayout
@@ -1065,3 +1068,19 @@ class TestParseOpacity:
with pytest.raises(StylesheetParseError): with pytest.raises(StylesheetParseError):
stylesheet.parse(css) stylesheet.parse(css)
assert stylesheet.rules[0].errors assert stylesheet.rules[0].errors
class TestParseMargin:
def test_margin_partial(self):
css = "#foo {margin: 1; margin-top: 2; margin-right: 3; margin-bottom: -1;}"
stylesheet = Stylesheet()
stylesheet.parse(css)
assert stylesheet.rules[0].styles.margin == Spacing(2, 3, -1, 1)
class TestParsePadding:
def test_padding_partial(self):
css = "#foo {padding: 1; padding-top: 2; padding-right: 3; padding-bottom: -1;}"
stylesheet = Stylesheet()
stylesheet.parse(css)
assert stylesheet.rules[0].styles.padding == Spacing(2, 3, -1, 1)

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from textual.css.tokenize import tokenize from textual.css.tokenize import tokenize