From 9e5bc82336e017e016645c746983c88e8e58e9fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 24 Oct 2023 10:51:15 +0100 Subject: [PATCH 1/8] Remove unused function. --- src/textual/css/_styles_builder.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/textual/css/_styles_builder.py b/src/textual/css/_styles_builder.py index 7f375dfaa6..06da1ca5d0 100644 --- a/src/textual/css/_styles_builder.py +++ b/src/textual/css/_styles_builder.py @@ -65,19 +65,6 @@ from .types import BoxSizing, Display, EdgeType, Overflow, Visibility -def _join_tokens(tokens: Iterable[Token], joiner: str = "") -> str: - """Convert tokens into a string by joining their values - - Args: - tokens: Tokens to join - joiner: String to join on. - - Returns: - The tokens, joined together to form a string. - """ - return joiner.join(token.value for token in tokens) - - class StylesBuilder: """ The StylesBuilder object takes tokens parsed from the CSS and converts From 2918011512d8ca1451472769b5f1ca82d1614611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 24 Oct 2023 10:52:05 +0100 Subject: [PATCH 2/8] Fix link in CSS error reporting (#3569). --- src/textual/css/stylesheet.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index 3362520293..ffaf1288fb 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -69,10 +69,13 @@ def __rich_console__( error_count += 1 if token.path: - path = Path(token.path) - filename = path.name + # The display path may end with a ":SomeWidget". + display_path = Path(token.path).absolute() + link_path = str(display_path).split(":")[0] + filename = display_path.name else: - path = None + display_path = "" + link_path = "" filename = "" if token.referenced_by: @@ -80,13 +83,14 @@ def __rich_console__( else: line_idx, col_idx = token.location line_no, col_no = line_idx + 1, col_idx + 1 - path_string = f"{path.absolute() if path else filename}:{line_no}:{col_no}" + path_string = f"{display_path or filename}:{line_no}:{col_no}" link_style = Style( - link=f"file://{path.absolute()}" if path else None, + link=f"file://{link_path}" if link_path else None, color="red", bold=True, italic=True, ) + path_text = Text(path_string, style=link_style) title = Text.assemble(Text("Error at ", style="bold red"), path_text) yield "" From 9076f41ce38e77788f744ee1a8364317b4b843cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 24 Oct 2023 17:16:58 +0100 Subject: [PATCH 3/8] Keep track of what widget/class variable CSS is read from. We already kept track of the file and widget CSS was read from. Now, we also keep track of the class variable it comes from and we create some structure to transfer that information across the program. --- src/textual/app.py | 26 +++++----- src/textual/css/parse.py | 23 ++++----- src/textual/css/query.py | 2 +- src/textual/css/styles.py | 9 ++-- src/textual/css/stylesheet.py | 95 +++++++++++++++++++++++------------ src/textual/css/tokenize.py | 17 ++++--- src/textual/css/tokenizer.py | 49 +++++++++++------- src/textual/css/types.py | 9 ++++ src/textual/dom.py | 30 +++++++---- src/textual/widget.py | 4 +- 10 files changed, 163 insertions(+), 101 deletions(-) diff --git a/src/textual/app.py b/src/textual/app.py index 0da2209a09..b004db1b89 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1753,20 +1753,19 @@ def _load_screen_css(self, screen: Screen): update = False for path in screen.css_path: - if not self.stylesheet.has_source(path): + if not self.stylesheet.has_source((str(path), "")): self.stylesheet.read(path) update = True if screen.CSS: try: - screen_css_path = ( - f"{inspect.getfile(screen.__class__)}:{screen.__class__.__name__}" - ) + screen_path = inspect.getfile(screen.__class__) except (TypeError, OSError): - screen_css_path = f"{screen.__class__.__name__}" - if not self.stylesheet.has_source(screen_css_path): + screen_path = "" + read_from = (screen_path, f"{screen.__class__.__name__}.CSS") + if not self.stylesheet.has_source(read_from): self.stylesheet.add_source( screen.CSS, - path=screen_css_path, + read_from=read_from, is_default_css=False, scope=screen._css_type_name if screen.SCOPED_CSS else "", ) @@ -2127,23 +2126,22 @@ async def _process_messages( try: if self.css_path: self.stylesheet.read_all(self.css_path) - for path, css, tie_breaker, scope in self._get_default_css(): + for read_from, css, tie_breaker, scope in self._get_default_css(): self.stylesheet.add_source( css, - path=path, + read_from=read_from, is_default_css=True, tie_breaker=tie_breaker, scope=scope, ) if self.CSS: try: - app_css_path = ( - f"{inspect.getfile(self.__class__)}:{self.__class__.__name__}" - ) + app_path = inspect.getfile(self.__class__) except (TypeError, OSError): - app_css_path = f"{self.__class__.__name__}" + app_path = "" + read_from = (app_path, f"{self.__class__.__name__}.CSS") self.stylesheet.add_source( - self.CSS, path=app_css_path, is_default_css=False + self.CSS, read_from=read_from, is_default_css=False ) except Exception as error: self._handle_exception(error) diff --git a/src/textual/css/parse.py b/src/textual/css/parse.py index a9ee06d15a..9be5047f30 100644 --- a/src/textual/css/parse.py +++ b/src/textual/css/parse.py @@ -1,7 +1,6 @@ from __future__ import annotations from functools import lru_cache -from pathlib import PurePath from typing import Iterable, Iterator, NoReturn from ..suggestions import get_suggestion @@ -19,7 +18,7 @@ from .styles import Styles from .tokenize import Token, tokenize, tokenize_declarations, tokenize_values from .tokenizer import EOFError, ReferencedBy -from .types import Specificity3 +from .types import CSSLocation, Specificity3 SELECTOR_MAP: dict[str, tuple[SelectorType, Specificity3]] = { "selector": (SelectorType.TYPE, (0, 0, 1)), @@ -38,7 +37,7 @@ def parse_selectors(css_selectors: str) -> tuple[SelectorSet, ...]: if not css_selectors.strip(): return () - tokens = iter(tokenize(css_selectors, "")) + tokens = iter(tokenize(css_selectors, ("", ""))) get_selector = SELECTOR_MAP.get combinator: CombinatorType | None = CombinatorType.DESCENDENT @@ -180,18 +179,18 @@ def parse_rule_set( yield rule_set -def parse_declarations(css: str, path: str) -> Styles: +def parse_declarations(css: str, read_from: CSSLocation) -> Styles: """Parse declarations and return a Styles object. Args: css: String containing CSS. - path: Path to the CSS, or something else to identify the location. + read_from: The location where the CSS was read from. Returns: A styles object. """ - tokens = iter(tokenize_declarations(css, path)) + tokens = iter(tokenize_declarations(css, read_from)) styles_builder = StylesBuilder() declaration: Declaration | None = None @@ -245,7 +244,7 @@ def _unresolved(variable_name: str, variables: Iterable[str], token: Token) -> N message += f"; did you mean '${suggested_variable}'?" raise UnresolvedVariableError( - token.path, + token.read_from, token.code, token.start, message, @@ -341,7 +340,7 @@ def substitute_references( def parse( scope: str, css: str, - path: str | PurePath, + read_from: CSSLocation, variables: dict[str, str] | None = None, variable_tokens: dict[str, list[Token]] | None = None, is_default_rules: bool = False, @@ -351,9 +350,9 @@ def parse( and generating rule sets from it. Args: - scope: CSS type name - css: The input CSS - path: Path to the CSS + scope: CSS type name. + css: The input CSS. + read_from: The source location of the CSS. variables: Substitution variables to substitute tokens for. is_default_rules: True if the rules we're extracting are default (i.e. in Widget.DEFAULT_CSS) rules. False if they're from user defined CSS. @@ -363,7 +362,7 @@ def parse( if variable_tokens: reference_tokens.update(variable_tokens) - tokens = iter(substitute_references(tokenize(css, path), variable_tokens)) + tokens = iter(substitute_references(tokenize(css, read_from), variable_tokens)) while True: token = next(tokens, None) if token is None: diff --git a/src/textual/css/query.py b/src/textual/css/query.py index a6af6ae679..7df103e7a3 100644 --- a/src/textual/css/query.py +++ b/src/textual/css/query.py @@ -407,7 +407,7 @@ def set_styles( node.set_styles(**update_styles) if css is not None: try: - new_styles = parse_declarations(css, path="set_styles") + new_styles = parse_declarations(css, read_from=("set_styles", "")) except DeclarationError as error: raise DeclarationError(error.name, error.token, error.message) from None for node in self: diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 03f9448529..b6b6cdd3f3 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -69,6 +69,7 @@ if TYPE_CHECKING: from .._layout import Layout from ..dom import DOMNode + from .types import CSSLocation class RulesMap(TypedDict, total=False): @@ -534,12 +535,14 @@ def is_animatable(cls, rule: str) -> bool: @classmethod @lru_cache(maxsize=1024) - def parse(cls, css: str, path: str, *, node: DOMNode | None = None) -> Styles: + def parse( + cls, css: str, read_from: CSSLocation, *, node: DOMNode | None = None + ) -> Styles: """Parse CSS and return a Styles object. Args: css: Textual CSS. - path: Path or string indicating source of CSS. + read_from: Location where the CSS was read from. node: Node to associate with the Styles. Returns: @@ -547,7 +550,7 @@ def parse(cls, css: str, path: str, *, node: DOMNode | None = None) -> Styles: """ from .parse import parse_declarations - styles = parse_declarations(css, path) + styles = parse_declarations(css, read_from) styles.node = node return styles diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index ffaf1288fb..2af8857bde 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -26,7 +26,7 @@ from .styles import RulesMap, Styles from .tokenize import Token, tokenize_values from .tokenizer import TokenError -from .types import Specificity3, Specificity6 +from .types import CSSLocation, Specificity3, Specificity6 _DEFAULT_STYLES = Styles() @@ -68,14 +68,12 @@ def __rich_console__( for token, message in errors: error_count += 1 - if token.path: - # The display path may end with a ":SomeWidget". - display_path = Path(token.path).absolute() - link_path = str(display_path).split(":")[0] - filename = display_path.name + if token.read_from: + display_path, widget_var = token.read_from + link_path = str(Path(display_path).absolute()) + filename = Path(link_path).name else: - display_path = "" - link_path = "" + link_path = display_path = widget_var = "" filename = "" if token.referenced_by: @@ -83,16 +81,39 @@ def __rich_console__( else: line_idx, col_idx = token.location line_no, col_no = line_idx + 1, col_idx + 1 - path_string = f"{display_path or filename}:{line_no}:{col_no}" + + if widget_var: + path_string = ( + f"{link_path or filename} in {widget_var}:{line_no}:{col_no}" + ) + else: + path_string = f"{link_path or filename}:{line_no}:{col_no}" + + # If we have a widget/variable from where the CSS was read, then line/column + # numbers are relative to the inline CSS and we'll display them next to the + # widget/variable. + # Otherwise, they're absolute positions in a TCSS file and we can show them + # next to the file path. + if widget_var: + path_string = link_path or filename + widget_text = Text( + f" in {widget_var}:{line_no}:{col_no}", style="bold red" + ) + else: + path_string = f"{link_path or filename}:{line_no}:{col_no}" + widget_text = Text() + link_style = Style( link=f"file://{link_path}" if link_path else None, color="red", bold=True, italic=True, ) - path_text = Text(path_string, style=link_style) - title = Text.assemble(Text("Error at ", style="bold red"), path_text) + + title = Text.assemble( + Text("Error at ", style="bold red"), path_text, widget_text + ) yield "" yield Panel( self._get_snippet( @@ -136,7 +157,7 @@ def __init__(self, *, variables: dict[str, str] | None = None) -> None: self._rules_map: dict[str, list[RuleSet]] | None = None self._variables = variables or {} self.__variable_tokens: dict[str, list[Token]] | None = None - self.source: dict[str, CssSource] = {} + self.source: dict[CSSLocation, CssSource] = {} self._require_parse = False self._invalid_css: set[str] = set() self._parse_cache: LRUCache[tuple, list[RuleSet]] = LRUCache(64) @@ -206,7 +227,7 @@ def set_variables(self, variables: dict[str, str]) -> None: def _parse_rules( self, css: str, - path: str | PurePath, + read_from: CSSLocation, is_default_rules: bool = False, tie_breaker: int = 0, scope: str = "", @@ -215,7 +236,7 @@ def _parse_rules( Args: css: String containing Textual CSS. - path: Path to CSS or unique identifier + read_from: Original CSS location. is_default_rules: True if the rules we're extracting are default (i.e. in Widget.DEFAULT_CSS) rules. False if they're from user defined CSS. scope: Scope of rules, or empty string for global scope. @@ -226,7 +247,7 @@ def _parse_rules( Returns: List of RuleSets. """ - cache_key = (css, path, is_default_rules, tie_breaker, scope) + cache_key = (css, read_from, is_default_rules, tie_breaker, scope) try: return self._parse_cache[cache_key] except KeyError: @@ -236,7 +257,7 @@ def _parse_rules( parse( scope, css, - path, + read_from, variable_tokens=self._variable_tokens, is_default_rules=is_default_rules, tie_breaker=tie_breaker, @@ -267,7 +288,7 @@ def read(self, filename: str | PurePath) -> None: path = os.path.abspath(filename) except Exception: raise StylesheetError(f"unable to read CSS file {filename!r}") from None - self.source[str(path)] = CssSource(css, False, 0) + self.source[(str(path), "")] = CssSource(css, False, 0) self._require_parse = True def read_all(self, paths: Sequence[PurePath]) -> None: @@ -283,18 +304,18 @@ def read_all(self, paths: Sequence[PurePath]) -> None: for path in paths: self.read(path) - def has_source(self, path: str | PurePath) -> bool: + def has_source(self, read_from: CSSLocation) -> bool: """Check if the stylesheet has this CSS source already. Returns: Whether the stylesheet is aware of this CSS source or not. """ - return str(path) in self.source + return read_from in self.source def add_source( self, css: str, - path: str | PurePath | None = None, + read_from: CSSLocation | None = None, is_default_css: bool = False, tie_breaker: int = 0, scope: str = "", @@ -303,6 +324,8 @@ def add_source( Args: css: String with CSS source. + location: The original location of the CSS as a pair of file path and class + variable (for the case where the CSS comes from a Python source file). path: The path of the source if a file, or some other identifier. is_default_css: True if the CSS is defined in the Widget, False if the CSS is defined in a user stylesheet. @@ -314,17 +337,18 @@ def add_source( StylesheetParseError: If the CSS is invalid. """ - if path is None: - path = str(hash(css)) - elif isinstance(path, PurePath): - path = str(css) - if path in self.source and self.source[path].content == css: - # Path already in source, and CSS is identical - content, is_defaults, source_tie_breaker, scope = self.source[path] + if read_from is None: + read_from = ("", str(hash(css))) + + if read_from in self.source and self.source[read_from].content == css: + # Location already in source and CSS is identical. + content, is_defaults, source_tie_breaker, scope = self.source[read_from] if source_tie_breaker > tie_breaker: - self.source[path] = CssSource(content, is_defaults, tie_breaker, scope) + self.source[read_from] = CssSource( + content, is_defaults, tie_breaker, scope + ) return - self.source[path] = CssSource(css, is_default_css, tie_breaker, scope) + self.source[read_from] = CssSource(css, is_default_css, tie_breaker, scope) self._require_parse = True def parse(self) -> None: @@ -336,13 +360,18 @@ def parse(self) -> None: rules: list[RuleSet] = [] add_rules = rules.extend - for path, (css, is_default_rules, tie_breaker, scope) in self.source.items(): + for read_from, ( + css, + is_default_rules, + tie_breaker, + scope, + ) in self.source.items(): if css in self._invalid_css: continue try: css_rules = self._parse_rules( css, - path, + read_from=read_from, is_default_rules=is_default_rules, tie_breaker=tie_breaker, scope=scope, @@ -368,10 +397,10 @@ def reparse(self) -> None: """ # Do this in a fresh Stylesheet so if there are errors we don't break self. stylesheet = Stylesheet(variables=self._variables) - for path, (css, is_defaults, tie_breaker, scope) in self.source.items(): + for read_from, (css, is_defaults, tie_breaker, scope) in self.source.items(): stylesheet.add_source( css, - path, + read_from=read_from, is_default_css=is_defaults, tie_breaker=tie_breaker, scope=scope, diff --git a/src/textual/css/tokenize.py b/src/textual/css/tokenize.py index 12e7ed6e26..de9f80787a 100644 --- a/src/textual/css/tokenize.py +++ b/src/textual/css/tokenize.py @@ -1,10 +1,12 @@ from __future__ import annotations import re -from pathlib import PurePath -from typing import Iterable +from typing import TYPE_CHECKING, Iterable -from textual.css.tokenizer import Expect, Token, Tokenizer +from .tokenizer import Expect, Token, Tokenizer + +if TYPE_CHECKING: + from .types import CSSLocation PERCENT = r"-?\d+\.?\d*%" DECIMAL = r"-?\d+\.?\d*" @@ -157,8 +159,8 @@ class TokenizerState: "declaration_set_end": expect_root_scope, } - def __call__(self, code: str, path: str | PurePath) -> Iterable[Token]: - tokenizer = Tokenizer(code, path=path) + def __call__(self, code: str, read_from: CSSLocation) -> Iterable[Token]: + tokenizer = Tokenizer(code, read_from=read_from) expect = self.EXPECT get_token = tokenizer.get_token get_state = self.STATE_MAP.get @@ -194,7 +196,7 @@ class ValueTokenizerState(TokenizerState): def tokenize_values(values: dict[str, str]) -> dict[str, list[Token]]: - """Tokens the values in a dict of strings. + """Tokenizes the values in a dict of strings. Args: values: A mapping of CSS variable name on to a value, to be @@ -204,6 +206,7 @@ def tokenize_values(values: dict[str, str]) -> dict[str, list[Token]]: A mapping of name on to a list of tokens, """ value_tokens = { - name: list(tokenize_value(value, "__name__")) for name, value in values.items() + name: list(tokenize_value(value, ("__name__", ""))) + for name, value in values.items() } return value_tokens diff --git a/src/textual/css/tokenizer.py b/src/textual/css/tokenizer.py index 276b22b54b..d7cfc449d3 100644 --- a/src/textual/css/tokenizer.py +++ b/src/textual/css/tokenizer.py @@ -1,8 +1,7 @@ from __future__ import annotations import re -from pathlib import PurePath -from typing import NamedTuple +from typing import TYPE_CHECKING, NamedTuple import rich.repr from rich.console import Group, RenderableType @@ -16,13 +15,16 @@ from ._error_tools import friendly_list from .constants import VALID_PSEUDO_CLASSES +if TYPE_CHECKING: + from .types import CSSLocation + class TokenError(Exception): """Error raised when the CSS cannot be tokenized (syntax error).""" def __init__( self, - path: str, + read_from: CSSLocation, code: str, start: tuple[int, int], message: str, @@ -30,14 +32,14 @@ def __init__( ) -> None: """ Args: - path: Path to source or "" if source is parsed from a literal. + read_from: The location where the CSS was read from. code: The code being parsed. start: Line number of the error. message: A message associated with the error. end: End location of token, or None if not known. """ - self.path = path + self.read_from = read_from self.code = code self.start = start self.end = end or start @@ -72,7 +74,12 @@ def __rich__(self) -> RenderableType: line_no, col_no = self.start - errors.append(highlighter(f" {self.path or ''}:{line_no}:{col_no}")) + path, widget_variable = self.read_from + if widget_variable: + css_location = f" {path}, {widget_variable}:{line_no}:{col_no}" + else: + css_location = f" {path}:{line_no}:{col_no}" + errors.append(highlighter(css_location)) errors.append(self._get_snippet()) final_message = "\n".join( @@ -126,7 +133,7 @@ class ReferencedBy(NamedTuple): class Token(NamedTuple): name: str value: str - path: str + read_from: CSSLocation code: str location: tuple[int, int] referenced_by: ReferencedBy | None = None @@ -153,7 +160,7 @@ def with_reference(self, by: ReferencedBy | None) -> "Token": return Token( name=self.name, value=self.value, - path=self.path, + read_from=self.read_from, code=self.code, location=self.location, referenced_by=by, @@ -165,15 +172,18 @@ def __str__(self) -> str: def __rich_repr__(self) -> rich.repr.Result: yield "name", self.name yield "value", self.value - yield "path", self.path + yield ( + "read_from", + self.read_from[0] if not self.read_from[1] else self.read_from, + ) yield "code", self.code if len(self.code) < 40 else self.code[:40] + "..." yield "location", self.location yield "referenced_by", self.referenced_by, None class Tokenizer: - def __init__(self, text: str, path: str | PurePath = "") -> None: - self.path = str(path) + def __init__(self, text: str, read_from: CSSLocation = ("", "")) -> None: + self.read_from = read_from self.code = text self.lines = text.splitlines(keepends=True) self.line_no = 0 @@ -187,14 +197,14 @@ def get_token(self, expect: Expect) -> Token: return Token( "eof", "", - self.path, + self.read_from, self.code, (line_no + 1, col_no + 1), None, ) else: raise EOFError( - self.path, + self.read_from, self.code, (line_no + 1, col_no + 1), "Unexpected end of file", @@ -205,7 +215,7 @@ def get_token(self, expect: Expect) -> Token: expected = friendly_list(" ".join(name.split("_")) for name in expect.names) message = f"Expected one of {expected}.; Did you forget a semicolon at the end of a line?" raise TokenError( - self.path, + self.read_from, self.code, (line_no, col_no), message, @@ -224,7 +234,7 @@ def get_token(self, expect: Expect) -> Token: token = Token( name, value, - self.path, + self.read_from, self.code, (line_no, col_no), referenced_by=None, @@ -239,14 +249,14 @@ def get_token(self, expect: Expect) -> Token: all_valid = f"must be one of {friendly_list(VALID_PSEUDO_CLASSES)}" if suggestion: raise TokenError( - self.path, + self.read_from, self.code, (line_no, col_no), f"unknown pseudo-class {pseudo_class!r}; did you mean {suggestion!r}?; {all_valid}", ) else: raise TokenError( - self.path, + self.read_from, self.code, (line_no, col_no), f"unknown pseudo-class {pseudo_class!r}; {all_valid}", @@ -267,7 +277,10 @@ def skip_to(self, expect: Expect) -> Token: while True: if line_no >= len(self.lines): raise EOFError( - self.path, self.code, (line_no, col_no), "Unexpected end of file" + self.read_from, + self.code, + (line_no, col_no), + "Unexpected end of file", ) line = self.lines[line_no] match = expect.search(line, col_no) diff --git a/src/textual/css/types.py b/src/textual/css/types.py index c723e0b5fa..ce4cebdd0b 100644 --- a/src/textual/css/types.py +++ b/src/textual/css/types.py @@ -42,3 +42,12 @@ Specificity3 = Tuple[int, int, int] Specificity6 = Tuple[int, int, int, int, int, int] + +CSSLocation = Tuple[str, str] +"""Represents the definition location of a piece of CSS code. + +The first element of the tuple is the file path from where the CSS was read. +If the CSS was read from a Python source file, the second element contains the class +variable from where the CSS was read (e.g., "Widget.DEFAULT_CSS"), otherwise it's an +empty string. +""" diff --git a/src/textual/dom.py b/src/textual/dom.py index bc8ab776bf..c380c6f4aa 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -416,33 +416,41 @@ def __rich_repr__(self) -> rich.repr.Result: if hasattr(self, "_classes") and self._classes: yield "classes", " ".join(self._classes) - def _get_default_css(self) -> list[tuple[str, str, int, str]]: + def _get_default_css(self) -> list[tuple[tuple[str, str], str, int, str]]: """Gets the CSS for this class and inherited from bases. Default CSS is inherited from base classes, unless `inherit_css` is set to `False` when subclassing. Returns: - A list of tuples containing (PATH, SOURCE, SPECIFICITY, SCOPE) for this - and inherited from base classes. + A list of tuples containing (LOCATION, SOURCE, SPECIFICITY, SCOPE) for this + class and inherited from base classes. """ - css_stack: list[tuple[str, str, int, str]] = [] + css_stack: list[tuple[tuple[str, str], str, int, str]] = [] - def get_path(base: Type[DOMNode]) -> str: - """Get a path to the DOM Node""" + def get_location(base: Type[DOMNode]) -> tuple[str, str]: + """Get the original location of this DEFAULT_CSS. + + Args: + base: The class from which the default css was extracted. + + Returns: + The filename where the class was defined (if possible) and the class + variable the CSS was extracted from. + """ try: - return f"{getfile(base)}:{base.__name__}" + return (getfile(base), f"{base.__name__}.DEFAULT_CSS") except (TypeError, OSError): - return f"{base.__name__}" + return ("", f"{base.__name__}.DEFAULT_CSS") for tie_breaker, base in enumerate(self._node_bases): - css: str = base.__dict__.get("DEFAULT_CSS", "").strip() + css: str = base.__dict__.get("DEFAULT_CSS", "") if css: scoped: bool = base.__dict__.get("SCOPED_CSS", True) css_stack.append( ( - get_path(base), + get_location(base), css, -tie_breaker, base._css_type_name if scoped else "", @@ -1136,7 +1144,7 @@ def set_styles(self, css: str | None = None, **update_styles) -> Self: if css is not None: try: - new_styles = parse_declarations(css, path="set_styles") + new_styles = parse_declarations(css, read_from=("set_styles", "")) except DeclarationError as error: raise DeclarationError(error.name, error.token, error.message) from None self._inline_styles.merge(new_styles) diff --git a/src/textual/widget.py b/src/textual/widget.py index 6960af7a2e..f9df5cba62 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -941,10 +941,10 @@ def _post_register(self, app: App) -> None: app: App instance. """ # Parse the Widget's CSS - for path, css, tie_breaker, scope in self._get_default_css(): + for read_from, css, tie_breaker, scope in self._get_default_css(): self.app.stylesheet.add_source( css, - path=path, + read_from=read_from, is_default_css=True, tie_breaker=tie_breaker, scope=scope, From 55fe9d3891228fa55f13f9f1b118ebbebfc37561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 24 Oct 2023 17:51:34 +0100 Subject: [PATCH 4/8] Fix tests. --- tests/css/test_parse.py | 218 +++++++++++++++++----------------- tests/css/test_styles.py | 2 +- tests/css/test_tokenize.py | 232 ++++++++++++++++++------------------- 3 files changed, 226 insertions(+), 226 deletions(-) diff --git a/tests/css/test_parse.py b/tests/css/test_parse.py index 6f31bf0946..124f820d53 100644 --- a/tests/css/test_parse.py +++ b/tests/css/test_parse.py @@ -17,12 +17,12 @@ class TestVariableReferenceSubstitution: def test_simple_reference(self): css = "$x: 1; #some-widget{border: $x;}" - variables = substitute_references(tokenize(css, "")) + variables = substitute_references(tokenize(css, ("", ""))) assert list(variables) == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -30,7 +30,7 @@ def test_simple_reference(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -38,7 +38,7 @@ def test_simple_reference(self): Token( name="number", value="1", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=None, @@ -46,7 +46,7 @@ def test_simple_reference(self): Token( name="variable_value_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 5), referenced_by=None, @@ -54,7 +54,7 @@ def test_simple_reference(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 6), referenced_by=None, @@ -62,7 +62,7 @@ def test_simple_reference(self): Token( name="selector_start_id", value="#some-widget", - path="", + read_from=("", ""), code=css, location=(0, 7), referenced_by=None, @@ -70,7 +70,7 @@ def test_simple_reference(self): Token( name="declaration_set_start", value="{", - path="", + read_from=("", ""), code=css, location=(0, 19), referenced_by=None, @@ -78,7 +78,7 @@ def test_simple_reference(self): Token( name="declaration_name", value="border:", - path="", + read_from=("", ""), code=css, location=(0, 20), referenced_by=None, @@ -86,7 +86,7 @@ def test_simple_reference(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 27), referenced_by=None, @@ -94,7 +94,7 @@ def test_simple_reference(self): Token( name="number", value="1", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=ReferencedBy( @@ -104,7 +104,7 @@ def test_simple_reference(self): Token( name="declaration_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 30), referenced_by=None, @@ -112,7 +112,7 @@ def test_simple_reference(self): Token( name="declaration_set_end", value="}", - path="", + read_from=("", ""), code=css, location=(0, 31), referenced_by=None, @@ -121,12 +121,12 @@ def test_simple_reference(self): def test_simple_reference_no_whitespace(self): css = "$x:1; #some-widget{border: $x;}" - variables = substitute_references(tokenize(css, "")) + variables = substitute_references(tokenize(css, ("", ""))) assert list(variables) == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -134,7 +134,7 @@ def test_simple_reference_no_whitespace(self): Token( name="number", value="1", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -142,7 +142,7 @@ def test_simple_reference_no_whitespace(self): Token( name="variable_value_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=None, @@ -150,7 +150,7 @@ def test_simple_reference_no_whitespace(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 5), referenced_by=None, @@ -158,7 +158,7 @@ def test_simple_reference_no_whitespace(self): Token( name="selector_start_id", value="#some-widget", - path="", + read_from=("", ""), code=css, location=(0, 6), referenced_by=None, @@ -166,7 +166,7 @@ def test_simple_reference_no_whitespace(self): Token( name="declaration_set_start", value="{", - path="", + read_from=("", ""), code=css, location=(0, 18), referenced_by=None, @@ -174,7 +174,7 @@ def test_simple_reference_no_whitespace(self): Token( name="declaration_name", value="border:", - path="", + read_from=("", ""), code=css, location=(0, 19), referenced_by=None, @@ -182,7 +182,7 @@ def test_simple_reference_no_whitespace(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 26), referenced_by=None, @@ -190,7 +190,7 @@ def test_simple_reference_no_whitespace(self): Token( name="number", value="1", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=ReferencedBy( @@ -200,7 +200,7 @@ def test_simple_reference_no_whitespace(self): Token( name="declaration_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 29), referenced_by=None, @@ -208,7 +208,7 @@ def test_simple_reference_no_whitespace(self): Token( name="declaration_set_end", value="}", - path="", + read_from=("", ""), code=css, location=(0, 30), referenced_by=None, @@ -218,11 +218,11 @@ def test_simple_reference_no_whitespace(self): def test_undefined_variable(self): css = ".thing { border: $not-defined; }" with pytest.raises(UnresolvedVariableError): - list(substitute_references(tokenize(css, ""))) + list(substitute_references(tokenize(css, ("", "")))) def test_empty_variable(self): css = "$x:\n* { background:$x; }" - result = list(substitute_references(tokenize(css, ""))) + result = list(substitute_references(tokenize(css, ("", "")))) assert [(t.name, t.value) for t in result] == [ ("variable_name", "$x:"), ("variable_value_end", "\n"), @@ -238,11 +238,11 @@ def test_empty_variable(self): def test_transitive_reference(self): css = "$x: 1\n$y: $x\n.thing { border: $y }" - assert list(substitute_references(tokenize(css, ""))) == [ + assert list(substitute_references(tokenize(css, ("", "")))) == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -250,7 +250,7 @@ def test_transitive_reference(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -258,7 +258,7 @@ def test_transitive_reference(self): Token( name="number", value="1", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=None, @@ -266,7 +266,7 @@ def test_transitive_reference(self): Token( name="variable_value_end", value="\n", - path="", + read_from=("", ""), code=css, location=(0, 5), referenced_by=None, @@ -274,7 +274,7 @@ def test_transitive_reference(self): Token( name="variable_name", value="$y:", - path="", + read_from=("", ""), code=css, location=(1, 0), referenced_by=None, @@ -282,7 +282,7 @@ def test_transitive_reference(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 3), referenced_by=None, @@ -290,7 +290,7 @@ def test_transitive_reference(self): Token( name="number", value="1", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=ReferencedBy( @@ -300,7 +300,7 @@ def test_transitive_reference(self): Token( name="variable_value_end", value="\n", - path="", + read_from=("", ""), code=css, location=(1, 6), referenced_by=None, @@ -308,7 +308,7 @@ def test_transitive_reference(self): Token( name="selector_start_class", value=".thing", - path="", + read_from=("", ""), code=css, location=(2, 0), referenced_by=None, @@ -316,7 +316,7 @@ def test_transitive_reference(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(2, 6), referenced_by=None, @@ -324,7 +324,7 @@ def test_transitive_reference(self): Token( name="declaration_set_start", value="{", - path="", + read_from=("", ""), code=css, location=(2, 7), referenced_by=None, @@ -332,7 +332,7 @@ def test_transitive_reference(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(2, 8), referenced_by=None, @@ -340,7 +340,7 @@ def test_transitive_reference(self): Token( name="declaration_name", value="border:", - path="", + read_from=("", ""), code=css, location=(2, 9), referenced_by=None, @@ -348,7 +348,7 @@ def test_transitive_reference(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(2, 16), referenced_by=None, @@ -356,7 +356,7 @@ def test_transitive_reference(self): Token( name="number", value="1", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=ReferencedBy( @@ -366,7 +366,7 @@ def test_transitive_reference(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(2, 19), referenced_by=None, @@ -374,7 +374,7 @@ def test_transitive_reference(self): Token( name="declaration_set_end", value="}", - path="", + read_from=("", ""), code=css, location=(2, 20), referenced_by=None, @@ -383,11 +383,11 @@ def test_transitive_reference(self): def test_multi_value_variable(self): css = "$x: 2 4\n$y: 6 $x 2\n.thing { border: $y }" - assert list(substitute_references(tokenize(css, ""))) == [ + assert list(substitute_references(tokenize(css, ("", "")))) == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -395,7 +395,7 @@ def test_multi_value_variable(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -403,7 +403,7 @@ def test_multi_value_variable(self): Token( name="number", value="2", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=None, @@ -411,7 +411,7 @@ def test_multi_value_variable(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 5), referenced_by=None, @@ -419,7 +419,7 @@ def test_multi_value_variable(self): Token( name="number", value="4", - path="", + read_from=("", ""), code=css, location=(0, 6), referenced_by=None, @@ -427,7 +427,7 @@ def test_multi_value_variable(self): Token( name="variable_value_end", value="\n", - path="", + read_from=("", ""), code=css, location=(0, 7), referenced_by=None, @@ -435,7 +435,7 @@ def test_multi_value_variable(self): Token( name="variable_name", value="$y:", - path="", + read_from=("", ""), code=css, location=(1, 0), referenced_by=None, @@ -443,7 +443,7 @@ def test_multi_value_variable(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 3), referenced_by=None, @@ -451,7 +451,7 @@ def test_multi_value_variable(self): Token( name="number", value="6", - path="", + read_from=("", ""), code=css, location=(1, 4), referenced_by=None, @@ -459,7 +459,7 @@ def test_multi_value_variable(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 5), referenced_by=None, @@ -467,7 +467,7 @@ def test_multi_value_variable(self): Token( name="number", value="2", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=ReferencedBy( @@ -477,7 +477,7 @@ def test_multi_value_variable(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 5), referenced_by=ReferencedBy( @@ -487,7 +487,7 @@ def test_multi_value_variable(self): Token( name="number", value="4", - path="", + read_from=("", ""), code=css, location=(0, 6), referenced_by=ReferencedBy( @@ -497,7 +497,7 @@ def test_multi_value_variable(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 8), referenced_by=None, @@ -505,7 +505,7 @@ def test_multi_value_variable(self): Token( name="number", value="2", - path="", + read_from=("", ""), code=css, location=(1, 9), referenced_by=None, @@ -513,7 +513,7 @@ def test_multi_value_variable(self): Token( name="variable_value_end", value="\n", - path="", + read_from=("", ""), code=css, location=(1, 10), referenced_by=None, @@ -521,7 +521,7 @@ def test_multi_value_variable(self): Token( name="selector_start_class", value=".thing", - path="", + read_from=("", ""), code=css, location=(2, 0), referenced_by=None, @@ -529,7 +529,7 @@ def test_multi_value_variable(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(2, 6), referenced_by=None, @@ -537,7 +537,7 @@ def test_multi_value_variable(self): Token( name="declaration_set_start", value="{", - path="", + read_from=("", ""), code=css, location=(2, 7), referenced_by=None, @@ -545,7 +545,7 @@ def test_multi_value_variable(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(2, 8), referenced_by=None, @@ -553,7 +553,7 @@ def test_multi_value_variable(self): Token( name="declaration_name", value="border:", - path="", + read_from=("", ""), code=css, location=(2, 9), referenced_by=None, @@ -561,7 +561,7 @@ def test_multi_value_variable(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(2, 16), referenced_by=None, @@ -569,7 +569,7 @@ def test_multi_value_variable(self): Token( name="number", value="6", - path="", + read_from=("", ""), code=css, location=(1, 4), referenced_by=ReferencedBy( @@ -579,7 +579,7 @@ def test_multi_value_variable(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 5), referenced_by=ReferencedBy( @@ -589,7 +589,7 @@ def test_multi_value_variable(self): Token( name="number", value="2", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=ReferencedBy( @@ -599,7 +599,7 @@ def test_multi_value_variable(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 5), referenced_by=ReferencedBy( @@ -609,7 +609,7 @@ def test_multi_value_variable(self): Token( name="number", value="4", - path="", + read_from=("", ""), code=css, location=(0, 6), referenced_by=ReferencedBy( @@ -619,7 +619,7 @@ def test_multi_value_variable(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 8), referenced_by=ReferencedBy( @@ -629,7 +629,7 @@ def test_multi_value_variable(self): Token( name="number", value="2", - path="", + read_from=("", ""), code=css, location=(1, 9), referenced_by=ReferencedBy( @@ -639,7 +639,7 @@ def test_multi_value_variable(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(2, 19), referenced_by=None, @@ -647,7 +647,7 @@ def test_multi_value_variable(self): Token( name="declaration_set_end", value="}", - path="", + read_from=("", ""), code=css, location=(2, 20), referenced_by=None, @@ -656,11 +656,11 @@ def test_multi_value_variable(self): def test_variable_used_inside_property_value(self): css = "$x: red\n.thing { border: on $x; }" - assert list(substitute_references(tokenize(css, ""))) == [ + assert list(substitute_references(tokenize(css, ("", "")))) == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -668,7 +668,7 @@ def test_variable_used_inside_property_value(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -676,7 +676,7 @@ def test_variable_used_inside_property_value(self): Token( name="token", value="red", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=None, @@ -684,7 +684,7 @@ def test_variable_used_inside_property_value(self): Token( name="variable_value_end", value="\n", - path="", + read_from=("", ""), code=css, location=(0, 7), referenced_by=None, @@ -692,7 +692,7 @@ def test_variable_used_inside_property_value(self): Token( name="selector_start_class", value=".thing", - path="", + read_from=("", ""), code=css, location=(1, 0), referenced_by=None, @@ -700,7 +700,7 @@ def test_variable_used_inside_property_value(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 6), referenced_by=None, @@ -708,7 +708,7 @@ def test_variable_used_inside_property_value(self): Token( name="declaration_set_start", value="{", - path="", + read_from=("", ""), code=css, location=(1, 7), referenced_by=None, @@ -716,7 +716,7 @@ def test_variable_used_inside_property_value(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 8), referenced_by=None, @@ -724,7 +724,7 @@ def test_variable_used_inside_property_value(self): Token( name="declaration_name", value="border:", - path="", + read_from=("", ""), code=css, location=(1, 9), referenced_by=None, @@ -732,7 +732,7 @@ def test_variable_used_inside_property_value(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 16), referenced_by=None, @@ -740,7 +740,7 @@ def test_variable_used_inside_property_value(self): Token( name="token", value="on", - path="", + read_from=("", ""), code=css, location=(1, 17), referenced_by=None, @@ -748,7 +748,7 @@ def test_variable_used_inside_property_value(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 19), referenced_by=None, @@ -756,7 +756,7 @@ def test_variable_used_inside_property_value(self): Token( name="token", value="red", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=ReferencedBy( @@ -766,7 +766,7 @@ def test_variable_used_inside_property_value(self): Token( name="declaration_end", value=";", - path="", + read_from=("", ""), code=css, location=(1, 22), referenced_by=None, @@ -774,7 +774,7 @@ def test_variable_used_inside_property_value(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 23), referenced_by=None, @@ -782,7 +782,7 @@ def test_variable_used_inside_property_value(self): Token( name="declaration_set_end", value="}", - path="", + read_from=("", ""), code=css, location=(1, 24), referenced_by=None, @@ -791,11 +791,11 @@ def test_variable_used_inside_property_value(self): def test_variable_definition_eof(self): css = "$x: 1" - assert list(substitute_references(tokenize(css, ""))) == [ + assert list(substitute_references(tokenize(css, ("", "")))) == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -803,7 +803,7 @@ def test_variable_definition_eof(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -811,7 +811,7 @@ def test_variable_definition_eof(self): Token( name="number", value="1", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=None, @@ -820,11 +820,11 @@ def test_variable_definition_eof(self): def test_variable_reference_whitespace_trimming(self): css = "$x: 123;.thing{border: $x}" - assert list(substitute_references(tokenize(css, ""))) == [ + assert list(substitute_references(tokenize(css, ("", "")))) == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -832,7 +832,7 @@ def test_variable_reference_whitespace_trimming(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -840,7 +840,7 @@ def test_variable_reference_whitespace_trimming(self): Token( name="number", value="123", - path="", + read_from=("", ""), code=css, location=(0, 7), referenced_by=None, @@ -848,7 +848,7 @@ def test_variable_reference_whitespace_trimming(self): Token( name="variable_value_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 10), referenced_by=None, @@ -856,7 +856,7 @@ def test_variable_reference_whitespace_trimming(self): Token( name="selector_start_class", value=".thing", - path="", + read_from=("", ""), code=css, location=(0, 11), referenced_by=None, @@ -864,7 +864,7 @@ def test_variable_reference_whitespace_trimming(self): Token( name="declaration_set_start", value="{", - path="", + read_from=("", ""), code=css, location=(0, 17), referenced_by=None, @@ -872,7 +872,7 @@ def test_variable_reference_whitespace_trimming(self): Token( name="declaration_name", value="border:", - path="", + read_from=("", ""), code=css, location=(0, 18), referenced_by=None, @@ -880,7 +880,7 @@ def test_variable_reference_whitespace_trimming(self): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 25), referenced_by=None, @@ -888,7 +888,7 @@ def test_variable_reference_whitespace_trimming(self): Token( name="number", value="123", - path="", + read_from=("", ""), code=css, location=(0, 7), referenced_by=ReferencedBy( @@ -898,7 +898,7 @@ def test_variable_reference_whitespace_trimming(self): Token( name="declaration_set_end", value="}", - path="", + read_from=("", ""), code=css, location=(0, 28), referenced_by=None, diff --git a/tests/css/test_styles.py b/tests/css/test_styles.py index b31ad46548..7a4578200c 100644 --- a/tests/css/test_styles.py +++ b/tests/css/test_styles.py @@ -115,7 +115,7 @@ def test_get_opacity_default(): def test_styles_css_property(): css = "opacity: 50%; text-opacity: 20%; background: green; color: red; tint: dodgerblue 20%;" - styles = Styles().parse(css, path="") + styles = Styles().parse(css, read_from=("", "")) assert styles.css == ( "background: #008000;\n" "color: #FF0000;\n" diff --git a/tests/css/test_tokenize.py b/tests/css/test_tokenize.py index 01945c9dba..d4dfba888e 100644 --- a/tests/css/test_tokenize.py +++ b/tests/css/test_tokenize.py @@ -21,11 +21,11 @@ @pytest.mark.parametrize("name", VALID_VARIABLE_NAMES) def test_variable_declaration_valid_names(name): css = f"${name}: black on red;" - assert list(tokenize(css, "")) == [ + assert list(tokenize(css, ("", ""))) == [ Token( name="variable_name", value=f"${name}:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -33,7 +33,7 @@ def test_variable_declaration_valid_names(name): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 14), referenced_by=None, @@ -41,7 +41,7 @@ def test_variable_declaration_valid_names(name): Token( name="token", value="black", - path="", + read_from=("", ""), code=css, location=(0, 15), referenced_by=None, @@ -49,7 +49,7 @@ def test_variable_declaration_valid_names(name): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 20), referenced_by=None, @@ -57,7 +57,7 @@ def test_variable_declaration_valid_names(name): Token( name="token", value="on", - path="", + read_from=("", ""), code=css, location=(0, 21), referenced_by=None, @@ -65,7 +65,7 @@ def test_variable_declaration_valid_names(name): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 23), referenced_by=None, @@ -73,7 +73,7 @@ def test_variable_declaration_valid_names(name): Token( name="token", value="red", - path="", + read_from=("", ""), code=css, location=(0, 24), referenced_by=None, @@ -81,7 +81,7 @@ def test_variable_declaration_valid_names(name): Token( name="variable_value_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 27), referenced_by=None, @@ -91,11 +91,11 @@ def test_variable_declaration_valid_names(name): def test_variable_declaration_multiple_values(): css = "$x: 2vw\t4% 6s red;" - assert list(tokenize(css, "")) == [ + assert list(tokenize(css, ("", ""))) == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -103,7 +103,7 @@ def test_variable_declaration_multiple_values(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -111,7 +111,7 @@ def test_variable_declaration_multiple_values(): Token( name="scalar", value="2vw", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=None, @@ -119,7 +119,7 @@ def test_variable_declaration_multiple_values(): Token( name="whitespace", value="\t", - path="", + read_from=("", ""), code=css, location=(0, 7), referenced_by=None, @@ -127,7 +127,7 @@ def test_variable_declaration_multiple_values(): Token( name="scalar", value="4%", - path="", + read_from=("", ""), code=css, location=(0, 8), referenced_by=None, @@ -135,7 +135,7 @@ def test_variable_declaration_multiple_values(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 10), referenced_by=None, @@ -143,7 +143,7 @@ def test_variable_declaration_multiple_values(): Token( name="duration", value="6s", - path="", + read_from=("", ""), code=css, location=(0, 11), referenced_by=None, @@ -151,7 +151,7 @@ def test_variable_declaration_multiple_values(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 13), referenced_by=None, @@ -159,7 +159,7 @@ def test_variable_declaration_multiple_values(): Token( name="token", value="red", - path="", + read_from=("", ""), code=css, location=(0, 15), referenced_by=None, @@ -167,7 +167,7 @@ def test_variable_declaration_multiple_values(): Token( name="variable_value_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 18), referenced_by=None, @@ -183,112 +183,112 @@ def test_single_line_comment(): } # Nada""" # Check the css parses # list(parse(css, "")) - result = list(tokenize(css, "")) + result = list(tokenize(css, ("", ""))) print(result) expected = [ Token( name="whitespace", value="\n", - path="", + read_from=("", ""), code=css, location=(0, 9), ), Token( name="selector_start_id", value="#foo", - path="", + read_from=("", ""), code=css, location=(1, 0), ), Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 4), ), Token( name="declaration_set_start", value="{", - path="", + read_from=("", ""), code=css, location=(1, 5), ), Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 6), ), Token( name="whitespace", value="\n", - path="", + read_from=("", ""), code=css, location=(1, 16), ), Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(2, 0), ), Token( name="declaration_name", value="color:", - path="", + read_from=("", ""), code=css, location=(2, 4), ), Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(2, 10), ), Token( name="token", value="red", - path="", + read_from=("", ""), code=css, location=(2, 11), ), Token( name="declaration_end", value=";", - path="", + read_from=("", ""), code=css, location=(2, 14), ), Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(2, 15), ), Token( name="whitespace", value="\n", - path="", + read_from=("", ""), code=css, location=(2, 30), ), Token( name="declaration_set_end", value="}", - path="", + read_from=("", ""), code=css, location=(3, 0), ), Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(3, 1), ), @@ -298,11 +298,11 @@ def test_single_line_comment(): def test_variable_declaration_comment_ignored(): css = "$x: red; /* comment */" - assert list(tokenize(css, "")) == [ + assert list(tokenize(css, ("", ""))) == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -310,7 +310,7 @@ def test_variable_declaration_comment_ignored(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -318,7 +318,7 @@ def test_variable_declaration_comment_ignored(): Token( name="token", value="red", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=None, @@ -326,7 +326,7 @@ def test_variable_declaration_comment_ignored(): Token( name="variable_value_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 7), referenced_by=None, @@ -334,7 +334,7 @@ def test_variable_declaration_comment_ignored(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 8), referenced_by=None, @@ -344,11 +344,11 @@ def test_variable_declaration_comment_ignored(): def test_variable_declaration_comment_interspersed_ignored(): css = "$x: re/* comment */d;" - assert list(tokenize(css, "")) == [ + assert list(tokenize(css, ("", ""))) == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -356,7 +356,7 @@ def test_variable_declaration_comment_interspersed_ignored(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -364,7 +364,7 @@ def test_variable_declaration_comment_interspersed_ignored(): Token( name="token", value="re", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=None, @@ -372,7 +372,7 @@ def test_variable_declaration_comment_interspersed_ignored(): Token( name="token", value="d", - path="", + read_from=("", ""), code=css, location=(0, 19), referenced_by=None, @@ -380,7 +380,7 @@ def test_variable_declaration_comment_interspersed_ignored(): Token( name="variable_value_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 20), referenced_by=None, @@ -390,11 +390,11 @@ def test_variable_declaration_comment_interspersed_ignored(): def test_variable_declaration_no_semicolon(): css = "$x: 1\n$y: 2" - assert list(tokenize(css, "")) == [ + assert list(tokenize(css, ("", ""))) == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -402,7 +402,7 @@ def test_variable_declaration_no_semicolon(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -410,7 +410,7 @@ def test_variable_declaration_no_semicolon(): Token( name="number", value="1", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=None, @@ -418,7 +418,7 @@ def test_variable_declaration_no_semicolon(): Token( name="variable_value_end", value="\n", - path="", + read_from=("", ""), code=css, location=(0, 5), referenced_by=None, @@ -426,7 +426,7 @@ def test_variable_declaration_no_semicolon(): Token( name="variable_name", value="$y:", - path="", + read_from=("", ""), code=css, location=(1, 0), referenced_by=None, @@ -434,7 +434,7 @@ def test_variable_declaration_no_semicolon(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(1, 3), referenced_by=None, @@ -442,7 +442,7 @@ def test_variable_declaration_no_semicolon(): Token( name="number", value="2", - path="", + read_from=("", ""), code=css, location=(1, 4), referenced_by=None, @@ -453,17 +453,17 @@ def test_variable_declaration_no_semicolon(): def test_variable_declaration_invalid_value(): css = "$x:(@$12x)" with pytest.raises(TokenError): - list(tokenize(css, "")) + list(tokenize(css, ("", ""))) def test_variables_declarations_amongst_rulesets(): css = "$x:1; .thing{text:red;} $y:2;" - tokens = list(tokenize(css, "")) + tokens = list(tokenize(css, ("", ""))) assert tokens == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -471,7 +471,7 @@ def test_variables_declarations_amongst_rulesets(): Token( name="number", value="1", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -479,7 +479,7 @@ def test_variables_declarations_amongst_rulesets(): Token( name="variable_value_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=None, @@ -487,7 +487,7 @@ def test_variables_declarations_amongst_rulesets(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 5), referenced_by=None, @@ -495,7 +495,7 @@ def test_variables_declarations_amongst_rulesets(): Token( name="selector_start_class", value=".thing", - path="", + read_from=("", ""), code=css, location=(0, 6), referenced_by=None, @@ -503,7 +503,7 @@ def test_variables_declarations_amongst_rulesets(): Token( name="declaration_set_start", value="{", - path="", + read_from=("", ""), code=css, location=(0, 12), referenced_by=None, @@ -511,7 +511,7 @@ def test_variables_declarations_amongst_rulesets(): Token( name="declaration_name", value="text:", - path="", + read_from=("", ""), code=css, location=(0, 13), referenced_by=None, @@ -519,7 +519,7 @@ def test_variables_declarations_amongst_rulesets(): Token( name="token", value="red", - path="", + read_from=("", ""), code=css, location=(0, 18), referenced_by=None, @@ -527,7 +527,7 @@ def test_variables_declarations_amongst_rulesets(): Token( name="declaration_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 21), referenced_by=None, @@ -535,7 +535,7 @@ def test_variables_declarations_amongst_rulesets(): Token( name="declaration_set_end", value="}", - path="", + read_from=("", ""), code=css, location=(0, 22), referenced_by=None, @@ -543,7 +543,7 @@ def test_variables_declarations_amongst_rulesets(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 23), referenced_by=None, @@ -551,7 +551,7 @@ def test_variables_declarations_amongst_rulesets(): Token( name="variable_name", value="$y:", - path="", + read_from=("", ""), code=css, location=(0, 24), referenced_by=None, @@ -559,7 +559,7 @@ def test_variables_declarations_amongst_rulesets(): Token( name="number", value="2", - path="", + read_from=("", ""), code=css, location=(0, 27), referenced_by=None, @@ -567,7 +567,7 @@ def test_variables_declarations_amongst_rulesets(): Token( name="variable_value_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 28), referenced_by=None, @@ -577,11 +577,11 @@ def test_variables_declarations_amongst_rulesets(): def test_variables_reference_in_rule_declaration_value(): css = ".warn{text: $warning;}" - assert list(tokenize(css, "")) == [ + assert list(tokenize(css, ("", ""))) == [ Token( name="selector_start_class", value=".warn", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -589,7 +589,7 @@ def test_variables_reference_in_rule_declaration_value(): Token( name="declaration_set_start", value="{", - path="", + read_from=("", ""), code=css, location=(0, 5), referenced_by=None, @@ -597,7 +597,7 @@ def test_variables_reference_in_rule_declaration_value(): Token( name="declaration_name", value="text:", - path="", + read_from=("", ""), code=css, location=(0, 6), referenced_by=None, @@ -605,7 +605,7 @@ def test_variables_reference_in_rule_declaration_value(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 11), referenced_by=None, @@ -613,7 +613,7 @@ def test_variables_reference_in_rule_declaration_value(): Token( name="variable_ref", value="$warning", - path="", + read_from=("", ""), code=css, location=(0, 12), referenced_by=None, @@ -621,7 +621,7 @@ def test_variables_reference_in_rule_declaration_value(): Token( name="declaration_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 20), referenced_by=None, @@ -629,7 +629,7 @@ def test_variables_reference_in_rule_declaration_value(): Token( name="declaration_set_end", value="}", - path="", + read_from=("", ""), code=css, location=(0, 21), referenced_by=None, @@ -639,11 +639,11 @@ def test_variables_reference_in_rule_declaration_value(): def test_variables_reference_in_rule_declaration_value_multiple(): css = ".card{padding: $pad-y $pad-x;}" - assert list(tokenize(css, "")) == [ + assert list(tokenize(css, ("", ""))) == [ Token( name="selector_start_class", value=".card", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -651,7 +651,7 @@ def test_variables_reference_in_rule_declaration_value_multiple(): Token( name="declaration_set_start", value="{", - path="", + read_from=("", ""), code=css, location=(0, 5), referenced_by=None, @@ -659,7 +659,7 @@ def test_variables_reference_in_rule_declaration_value_multiple(): Token( name="declaration_name", value="padding:", - path="", + read_from=("", ""), code=css, location=(0, 6), referenced_by=None, @@ -667,7 +667,7 @@ def test_variables_reference_in_rule_declaration_value_multiple(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 14), referenced_by=None, @@ -675,7 +675,7 @@ def test_variables_reference_in_rule_declaration_value_multiple(): Token( name="variable_ref", value="$pad-y", - path="", + read_from=("", ""), code=css, location=(0, 15), referenced_by=None, @@ -683,7 +683,7 @@ def test_variables_reference_in_rule_declaration_value_multiple(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 21), referenced_by=None, @@ -691,7 +691,7 @@ def test_variables_reference_in_rule_declaration_value_multiple(): Token( name="variable_ref", value="$pad-x", - path="", + read_from=("", ""), code=css, location=(0, 22), referenced_by=None, @@ -699,7 +699,7 @@ def test_variables_reference_in_rule_declaration_value_multiple(): Token( name="declaration_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 28), referenced_by=None, @@ -707,7 +707,7 @@ def test_variables_reference_in_rule_declaration_value_multiple(): Token( name="declaration_set_end", value="}", - path="", + read_from=("", ""), code=css, location=(0, 29), referenced_by=None, @@ -717,11 +717,11 @@ def test_variables_reference_in_rule_declaration_value_multiple(): def test_variables_reference_in_variable_declaration(): css = "$x: $y;" - assert list(tokenize(css, "")) == [ + assert list(tokenize(css, ("", ""))) == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -729,7 +729,7 @@ def test_variables_reference_in_variable_declaration(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -737,7 +737,7 @@ def test_variables_reference_in_variable_declaration(): Token( name="variable_ref", value="$y", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=None, @@ -745,7 +745,7 @@ def test_variables_reference_in_variable_declaration(): Token( name="variable_value_end", value=";", - path="", + read_from=("", ""), code=css, location=(0, 6), referenced_by=None, @@ -755,11 +755,11 @@ def test_variables_reference_in_variable_declaration(): def test_variable_references_in_variable_declaration_multiple(): css = "$x: $y $z\n" - assert list(tokenize(css, "")) == [ + assert list(tokenize(css, ("", ""))) == [ Token( name="variable_name", value="$x:", - path="", + read_from=("", ""), code=css, location=(0, 0), referenced_by=None, @@ -767,7 +767,7 @@ def test_variable_references_in_variable_declaration_multiple(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 3), referenced_by=None, @@ -775,7 +775,7 @@ def test_variable_references_in_variable_declaration_multiple(): Token( name="variable_ref", value="$y", - path="", + read_from=("", ""), code=css, location=(0, 4), referenced_by=None, @@ -783,7 +783,7 @@ def test_variable_references_in_variable_declaration_multiple(): Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=css, location=(0, 6), referenced_by=None, @@ -791,7 +791,7 @@ def test_variable_references_in_variable_declaration_multiple(): Token( name="variable_ref", value="$z", - path="", + read_from=("", ""), code=css, location=(0, 8), referenced_by=None, @@ -799,7 +799,7 @@ def test_variable_references_in_variable_declaration_multiple(): Token( name="variable_value_end", value="\n", - path="", + read_from=("", ""), code=css, location=(0, 10), referenced_by=None, @@ -809,92 +809,92 @@ def test_variable_references_in_variable_declaration_multiple(): def test_allow_new_lines(): css = ".foo{margin: 1\n1 0 0}" - tokens = list(tokenize(css, "")) + tokens = list(tokenize(css, ("", ""))) print(repr(tokens)) expected = [ Token( name="selector_start_class", value=".foo", - path="", + read_from=("", ""), code=".foo{margin: 1\n1 0 0}", location=(0, 0), ), Token( name="declaration_set_start", value="{", - path="", + read_from=("", ""), code=".foo{margin: 1\n1 0 0}", location=(0, 4), ), Token( name="declaration_name", value="margin:", - path="", + read_from=("", ""), code=".foo{margin: 1\n1 0 0}", location=(0, 5), ), Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=".foo{margin: 1\n1 0 0}", location=(0, 12), ), Token( name="number", value="1", - path="", + read_from=("", ""), code=".foo{margin: 1\n1 0 0}", location=(0, 13), ), Token( name="whitespace", value="\n", - path="", + read_from=("", ""), code=".foo{margin: 1\n1 0 0}", location=(0, 14), ), Token( name="number", value="1", - path="", + read_from=("", ""), code=".foo{margin: 1\n1 0 0}", location=(1, 0), ), Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=".foo{margin: 1\n1 0 0}", location=(1, 1), ), Token( name="number", value="0", - path="", + read_from=("", ""), code=".foo{margin: 1\n1 0 0}", location=(1, 2), ), Token( name="whitespace", value=" ", - path="", + read_from=("", ""), code=".foo{margin: 1\n1 0 0}", location=(1, 3), ), Token( name="number", value="0", - path="", + read_from=("", ""), code=".foo{margin: 1\n1 0 0}", location=(1, 4), ), Token( name="declaration_set_end", value="}", - path="", + read_from=("", ""), code=".foo{margin: 1\n1 0 0}", location=(1, 5), ), ] - assert list(tokenize(css, "")) == expected + assert list(tokenize(css, ("", ""))) == expected From 0f38ab7202ee6e3ccb89375b77216e4b0eb4d03c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 24 Oct 2023 17:52:08 +0100 Subject: [PATCH 5/8] Link to correct file location in CSS errors. --- src/textual/css/stylesheet.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index 2af8857bde..abfa1df2a0 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -99,12 +99,16 @@ def __rich_console__( widget_text = Text( f" in {widget_var}:{line_no}:{col_no}", style="bold red" ) + link_url = f"file://{link_path}" if link_path else None else: path_string = f"{link_path or filename}:{line_no}:{col_no}" widget_text = Text() + link_url = ( + f"file://{link_path}#{line_no}:{col_no}" if link_path else None + ) link_style = Style( - link=f"file://{link_path}" if link_path else None, + link=link_url, color="red", bold=True, italic=True, From 8b16776afc976862e8a5533dd6b18395ea6281a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 25 Oct 2023 12:00:31 +0100 Subject: [PATCH 6/8] Drop links in CSS error reporting. Instead of creating the link explicitly, we let terminal emulators auto-link to the file. This came after a discussion about how/whether we should try to support linking to specific file lines/columns for TCSS files and after some research to see how that would be possible. We decided to drop this feature when we couldn't find information in the standards for 'file://' regarding how to specify line/column numbers and after we found [this iTerm issue](https://gitlab.com/gnachman/iterm2/-/issues/9376) where the creator/maintainer of iTerm says that there is no standard API for opening a file to a particular line number. --- CHANGELOG.md | 2 ++ src/textual/css/stylesheet.py | 27 +++------------------------ 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6467ab5b43..816d6c5271 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - DataTable now has a max-height of 100vh rather than 100%, which doesn't work with auto - Breaking change: empty rules now result in an error https://github.com/Textualize/textual/pull/3566 - Improved startup time by caching CSS parsing https://github.com/Textualize/textual/pull/3575 +- CSS error reporting will no longer provide links to the files in question https://github.com/Textualize/textual/pull/3582 +- inline CSS error reporting will report widget/class variable where the CSS was read from https://github.com/Textualize/textual/pull/3582 ### Added diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index abfa1df2a0..057d3c606f 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -82,13 +82,6 @@ def __rich_console__( line_idx, col_idx = token.location line_no, col_no = line_idx + 1, col_idx + 1 - if widget_var: - path_string = ( - f"{link_path or filename} in {widget_var}:{line_no}:{col_no}" - ) - else: - path_string = f"{link_path or filename}:{line_no}:{col_no}" - # If we have a widget/variable from where the CSS was read, then line/column # numbers are relative to the inline CSS and we'll display them next to the # widget/variable. @@ -96,27 +89,13 @@ def __rich_console__( # next to the file path. if widget_var: path_string = link_path or filename - widget_text = Text( - f" in {widget_var}:{line_no}:{col_no}", style="bold red" - ) - link_url = f"file://{link_path}" if link_path else None + widget_string = f" in {widget_var}:{line_no}:{col_no}" else: path_string = f"{link_path or filename}:{line_no}:{col_no}" - widget_text = Text() - link_url = ( - f"file://{link_path}#{line_no}:{col_no}" if link_path else None - ) - - link_style = Style( - link=link_url, - color="red", - bold=True, - italic=True, - ) - path_text = Text(path_string, style=link_style) + widget_string = "" title = Text.assemble( - Text("Error at ", style="bold red"), path_text, widget_text + "Error at ", path_string, widget_string, style="bold red" ) yield "" yield Panel( From d84498232a6364584ea4dd62cb2a587013e7768f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 25 Oct 2023 13:21:56 +0100 Subject: [PATCH 7/8] Minor docstring/type hints cleanup. --- src/textual/css/stylesheet.py | 6 ++++-- src/textual/dom.py | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index 057d3c606f..1186f735d9 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -290,6 +290,9 @@ def read_all(self, paths: Sequence[PurePath]) -> None: def has_source(self, read_from: CSSLocation) -> bool: """Check if the stylesheet has this CSS source already. + Args: + read_from: The location source of the CSS. + Returns: Whether the stylesheet is aware of this CSS source or not. """ @@ -307,8 +310,7 @@ def add_source( Args: css: String with CSS source. - location: The original location of the CSS as a pair of file path and class - variable (for the case where the CSS comes from a Python source file). + read_from: The original source location of the CSS. path: The path of the source if a file, or some other identifier. is_default_css: True if the CSS is defined in the Widget, False if the CSS is defined in a user stylesheet. diff --git a/src/textual/dom.py b/src/textual/dom.py index c380c6f4aa..51a6578e48 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -50,6 +50,7 @@ from rich.console import RenderableType from .app import App from .css.query import DOMQuery, QueryType + from .css.types import CSSLocation from .message import Message from .screen import Screen from .widget import Widget @@ -416,7 +417,7 @@ def __rich_repr__(self) -> rich.repr.Result: if hasattr(self, "_classes") and self._classes: yield "classes", " ".join(self._classes) - def _get_default_css(self) -> list[tuple[tuple[str, str], str, int, str]]: + def _get_default_css(self) -> list[tuple[CSSLocation, str, int, str]]: """Gets the CSS for this class and inherited from bases. Default CSS is inherited from base classes, unless `inherit_css` is set to @@ -427,9 +428,9 @@ def _get_default_css(self) -> list[tuple[tuple[str, str], str, int, str]]: class and inherited from base classes. """ - css_stack: list[tuple[tuple[str, str], str, int, str]] = [] + css_stack: list[tuple[CSSLocation, str, int, str]] = [] - def get_location(base: Type[DOMNode]) -> tuple[str, str]: + def get_location(base: Type[DOMNode]) -> CSSLocation: """Get the original location of this DEFAULT_CSS. Args: From 6c6eecfd10d570f1ddc2d8581bd641615c502a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 25 Oct 2023 14:38:22 +0100 Subject: [PATCH 8/8] Address review feedback. --- src/textual/app.py | 7 ++++--- src/textual/css/stylesheet.py | 23 +++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/textual/app.py b/src/textual/app.py index b004db1b89..0027204c41 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1753,7 +1753,7 @@ def _load_screen_css(self, screen: Screen): update = False for path in screen.css_path: - if not self.stylesheet.has_source((str(path), "")): + if not self.stylesheet.has_source(str(path), ""): self.stylesheet.read(path) update = True if screen.CSS: @@ -1761,8 +1761,9 @@ def _load_screen_css(self, screen: Screen): screen_path = inspect.getfile(screen.__class__) except (TypeError, OSError): screen_path = "" - read_from = (screen_path, f"{screen.__class__.__name__}.CSS") - if not self.stylesheet.has_source(read_from): + screen_class_var = f"{screen.__class__.__name__}.CSS" + read_from = (screen_path, screen_class_var) + if not self.stylesheet.has_source(screen_path, screen_class_var): self.stylesheet.add_source( screen.CSS, read_from=read_from, diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index 1186f735d9..3acbb56527 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -12,7 +12,6 @@ from rich.markup import render from rich.padding import Padding from rich.panel import Panel -from rich.style import Style from rich.syntax import Syntax from rich.text import Text @@ -68,20 +67,19 @@ def __rich_console__( for token, message in errors: error_count += 1 - if token.read_from: - display_path, widget_var = token.read_from - link_path = str(Path(display_path).absolute()) - filename = Path(link_path).name - else: - link_path = display_path = widget_var = "" - filename = "" - if token.referenced_by: line_idx, col_idx = token.referenced_by.location else: line_idx, col_idx = token.location line_no, col_no = line_idx + 1, col_idx + 1 + display_path, widget_var = token.read_from + if display_path: + link_path = str(Path(display_path).absolute()) + filename = Path(link_path).name + else: + link_path = "" + filename = "" # If we have a widget/variable from where the CSS was read, then line/column # numbers are relative to the inline CSS and we'll display them next to the # widget/variable. @@ -287,16 +285,17 @@ def read_all(self, paths: Sequence[PurePath]) -> None: for path in paths: self.read(path) - def has_source(self, read_from: CSSLocation) -> bool: + def has_source(self, path: str, class_var: str = "") -> bool: """Check if the stylesheet has this CSS source already. Args: - read_from: The location source of the CSS. + path: The file path of the source in question. + class_var: The widget class variable we might be reading the CSS from. Returns: Whether the stylesheet is aware of this CSS source or not. """ - return read_from in self.source + return (path, class_var) in self.source def add_source( self,