mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
fixes for variable css errors
This commit is contained in:
@@ -28,7 +28,7 @@ import rich.repr
|
|||||||
from rich.console import Console, RenderableType
|
from rich.console import Console, RenderableType
|
||||||
from rich.measure import Measurement
|
from rich.measure import Measurement
|
||||||
from rich.protocol import is_renderable
|
from rich.protocol import is_renderable
|
||||||
from rich.segment import Segments
|
from rich.segment import Segment, Segments
|
||||||
from rich.traceback import Traceback
|
from rich.traceback import Traceback
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
@@ -1016,10 +1016,11 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
is_renderable(renderable) for renderable in renderables
|
is_renderable(renderable) for renderable in renderables
|
||||||
), "Can only call panic with strings or Rich renderables"
|
), "Can only call panic with strings or Rich renderables"
|
||||||
|
|
||||||
pre_rendered = [
|
def render(renderable: RenderableType) -> list[Segment]:
|
||||||
Segments(self.console.render(renderable, self.console.options))
|
segments = list(self.console.render(renderable, self.console.options))
|
||||||
for renderable in renderables
|
return segments
|
||||||
]
|
|
||||||
|
pre_rendered = [Segments(render(renderable)) for renderable in renderables]
|
||||||
|
|
||||||
self._exit_renderables.extend(pre_rendered)
|
self._exit_renderables.extend(pre_rendered)
|
||||||
self._close_messages_no_wait()
|
self._close_messages_no_wait()
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class BorderApp(App):
|
|||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
self.text.styles.border = (
|
self.text.styles.border = (
|
||||||
event.button.id,
|
event.button.id,
|
||||||
self.stylesheet.variables["secondary"],
|
self.stylesheet._variables["secondary"],
|
||||||
)
|
)
|
||||||
self.bell()
|
self.bell()
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
|
import rich.repr
|
||||||
from rich.console import Console, ConsoleOptions, RenderResult
|
from rich.console import Console, ConsoleOptions, RenderResult
|
||||||
|
|
||||||
from rich.highlighter import ReprHighlighter
|
from rich.highlighter import ReprHighlighter
|
||||||
from rich.markup import render
|
from rich.markup import render
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
@@ -42,6 +42,7 @@ class Example:
|
|||||||
yield _markup_and_highlight(f" [dim]e.g. [/][i]{self.markup}[/]")
|
yield _markup_and_highlight(f" [dim]e.g. [/][i]{self.markup}[/]")
|
||||||
|
|
||||||
|
|
||||||
|
@rich.repr.auto
|
||||||
class Bullet:
|
class Bullet:
|
||||||
"""Renderable for a single 'bullet point' containing information and optionally some examples
|
"""Renderable for a single 'bullet point' containing information and optionally some examples
|
||||||
pertaining to that information.
|
pertaining to that information.
|
||||||
@@ -59,10 +60,11 @@ class Bullet:
|
|||||||
def __rich_console__(
|
def __rich_console__(
|
||||||
self, console: Console, options: ConsoleOptions
|
self, console: Console, options: ConsoleOptions
|
||||||
) -> RenderResult:
|
) -> RenderResult:
|
||||||
yield _markup_and_highlight(f"{self.markup}")
|
yield _markup_and_highlight(self.markup)
|
||||||
yield from self.examples
|
yield from self.examples
|
||||||
|
|
||||||
|
|
||||||
|
@rich.repr.auto
|
||||||
class HelpText:
|
class HelpText:
|
||||||
"""Renderable for help text - the user is shown this when they
|
"""Renderable for help text - the user is shown this when they
|
||||||
encounter a style-related error (e.g. setting a style property to an invalid
|
encounter a style-related error (e.g. setting a style property to an invalid
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import cast, Iterable, NoReturn, Sequence
|
from typing import Iterable, NoReturn, Sequence, cast
|
||||||
|
|
||||||
import rich.repr
|
import rich.repr
|
||||||
|
|
||||||
|
from .._border import BorderValue, normalize_border_value
|
||||||
|
from .._duration import _duration_as_seconds
|
||||||
|
from .._easing import EASING
|
||||||
|
from ..color import Color, ColorParseError
|
||||||
|
from ..geometry import Spacing, SpacingDimensions, clamp
|
||||||
|
from ..suggestions import get_suggestion
|
||||||
from ._error_tools import friendly_list
|
from ._error_tools import friendly_list
|
||||||
from ._help_renderables import HelpText
|
from ._help_renderables import HelpText
|
||||||
from ._help_text import (
|
from ._help_text import (
|
||||||
@@ -33,34 +39,28 @@ from .constants import (
|
|||||||
VALID_ALIGN_VERTICAL,
|
VALID_ALIGN_VERTICAL,
|
||||||
VALID_BORDER,
|
VALID_BORDER,
|
||||||
VALID_BOX_SIZING,
|
VALID_BOX_SIZING,
|
||||||
VALID_EDGE,
|
|
||||||
VALID_DISPLAY,
|
VALID_DISPLAY,
|
||||||
|
VALID_EDGE,
|
||||||
VALID_OVERFLOW,
|
VALID_OVERFLOW,
|
||||||
VALID_VISIBILITY,
|
|
||||||
VALID_STYLE_FLAGS,
|
|
||||||
VALID_SCROLLBAR_GUTTER,
|
VALID_SCROLLBAR_GUTTER,
|
||||||
|
VALID_STYLE_FLAGS,
|
||||||
VALID_TEXT_ALIGN,
|
VALID_TEXT_ALIGN,
|
||||||
|
VALID_VISIBILITY,
|
||||||
)
|
)
|
||||||
from .errors import DeclarationError, StyleValueError
|
from .errors import DeclarationError, StyleValueError
|
||||||
from .model import Declaration
|
from .model import Declaration
|
||||||
from .scalar import (
|
from .scalar import (
|
||||||
Scalar,
|
Scalar,
|
||||||
ScalarOffset,
|
|
||||||
Unit,
|
|
||||||
ScalarError,
|
ScalarError,
|
||||||
|
ScalarOffset,
|
||||||
ScalarParseError,
|
ScalarParseError,
|
||||||
|
Unit,
|
||||||
percentage_string_to_float,
|
percentage_string_to_float,
|
||||||
)
|
)
|
||||||
from .styles import DockGroup, Styles
|
from .styles import Styles
|
||||||
from .tokenize import Token
|
from .tokenize import Token
|
||||||
from .transition import Transition
|
from .transition import Transition
|
||||||
from .types import BoxSizing, Edge, Display, Overflow, Visibility, EdgeType
|
from .types import BoxSizing, Display, Edge, EdgeType, Overflow, Visibility
|
||||||
from .._border import normalize_border_value, BorderValue
|
|
||||||
from ..color import Color, ColorParseError
|
|
||||||
from .._duration import _duration_as_seconds
|
|
||||||
from .._easing import EASING
|
|
||||||
from ..geometry import Spacing, SpacingDimensions, clamp
|
|
||||||
from ..suggestions import get_suggestion
|
|
||||||
|
|
||||||
|
|
||||||
def _join_tokens(tokens: Iterable[Token], joiner: str = "") -> str:
|
def _join_tokens(tokens: Iterable[Token], joiner: str = "") -> str:
|
||||||
@@ -434,6 +434,7 @@ class StylesBuilder:
|
|||||||
process_padding_left = _process_space_partial
|
process_padding_left = _process_space_partial
|
||||||
|
|
||||||
def _parse_border(self, name: str, tokens: list[Token]) -> BorderValue:
|
def _parse_border(self, name: str, tokens: list[Token]) -> BorderValue:
|
||||||
|
|
||||||
border_type: EdgeType = "solid"
|
border_type: EdgeType = "solid"
|
||||||
border_color = Color(0, 255, 0)
|
border_color = Color(0, 255, 0)
|
||||||
|
|
||||||
@@ -553,7 +554,7 @@ class StylesBuilder:
|
|||||||
self.styles._rules["offset"] = ScalarOffset(x, y)
|
self.styles._rules["offset"] = ScalarOffset(x, y)
|
||||||
|
|
||||||
def process_layout(self, name: str, tokens: list[Token]) -> None:
|
def process_layout(self, name: str, tokens: list[Token]) -> None:
|
||||||
from ..layouts.factory import get_layout, MissingLayout
|
from ..layouts.factory import MissingLayout, get_layout
|
||||||
|
|
||||||
if tokens:
|
if tokens:
|
||||||
if len(tokens) != 1:
|
if len(tokens) != 1:
|
||||||
@@ -602,7 +603,6 @@ class StylesBuilder:
|
|||||||
|
|
||||||
if color is not None or alpha is not None:
|
if color is not None or alpha is not None:
|
||||||
if alpha is not None:
|
if alpha is not None:
|
||||||
|
|
||||||
color = (color or Color(255, 255, 255)).with_alpha(alpha)
|
color = (color or Color(255, 255, 255)).with_alpha(alpha)
|
||||||
self.styles._rules[name] = color
|
self.styles._rules[name] = color
|
||||||
|
|
||||||
|
|||||||
@@ -301,9 +301,7 @@ def substitute_references(
|
|||||||
for _token in reference_tokens:
|
for _token in reference_tokens:
|
||||||
yield _token.with_reference(
|
yield _token.with_reference(
|
||||||
ReferencedBy(
|
ReferencedBy(
|
||||||
name=ref_name,
|
ref_name, ref_location, ref_length, token.code
|
||||||
location=ref_location,
|
|
||||||
length=ref_length,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -318,13 +316,10 @@ def substitute_references(
|
|||||||
variable_tokens = variables[variable_name]
|
variable_tokens = variables[variable_name]
|
||||||
ref_location = token.location
|
ref_location = token.location
|
||||||
ref_length = len(token.value)
|
ref_length = len(token.value)
|
||||||
for token in variable_tokens:
|
ref_code = token.code
|
||||||
yield token.with_reference(
|
for _token in variable_tokens:
|
||||||
ReferencedBy(
|
yield _token.with_reference(
|
||||||
name=variable_name,
|
ReferencedBy(variable_name, ref_location, ref_length, ref_code)
|
||||||
location=ref_location,
|
|
||||||
length=ref_length,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_unresolved(variable_name, variables.keys(), token)
|
_unresolved(variable_name, variables.keys(), token)
|
||||||
@@ -336,6 +331,7 @@ def parse(
|
|||||||
css: str,
|
css: str,
|
||||||
path: str | PurePath,
|
path: str | PurePath,
|
||||||
variables: dict[str, str] | None = None,
|
variables: dict[str, str] | None = None,
|
||||||
|
variable_tokens: dict[str, list[Token]] | None = None,
|
||||||
is_default_rules: bool = False,
|
is_default_rules: bool = False,
|
||||||
tie_breaker: int = 0,
|
tie_breaker: int = 0,
|
||||||
) -> Iterable[RuleSet]:
|
) -> Iterable[RuleSet]:
|
||||||
@@ -349,7 +345,11 @@ def parse(
|
|||||||
is_default_rules (bool): True if the rules we're extracting are
|
is_default_rules (bool): True if the rules we're extracting are
|
||||||
default (i.e. in Widget.DEFAULT_CSS) rules. False if they're from user defined CSS.
|
default (i.e. in Widget.DEFAULT_CSS) rules. False if they're from user defined CSS.
|
||||||
"""
|
"""
|
||||||
variable_tokens = tokenize_values(variables or {})
|
|
||||||
|
reference_tokens = tokenize_values(variables) if variables is not None else {}
|
||||||
|
if variable_tokens:
|
||||||
|
reference_tokens.update(variable_tokens)
|
||||||
|
|
||||||
tokens = iter(substitute_references(tokenize(css, path), variable_tokens))
|
tokens = iter(substitute_references(tokenize(css, path), variable_tokens))
|
||||||
while True:
|
while True:
|
||||||
token = next(tokens, None)
|
token = next(tokens, None)
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import partial
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
from typing import Iterable, NamedTuple, cast
|
from typing import Iterable, NamedTuple, Sequence, cast
|
||||||
|
|
||||||
import rich.repr
|
import rich.repr
|
||||||
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
|
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||||
@@ -39,20 +38,15 @@ class StylesheetParseError(StylesheetError):
|
|||||||
|
|
||||||
|
|
||||||
class StylesheetErrors:
|
class StylesheetErrors:
|
||||||
def __init__(
|
def __init__(self, rules: list[RuleSet]) -> None:
|
||||||
self, rules: list[RuleSet], variables: dict[str, str] | None = None
|
|
||||||
) -> None:
|
|
||||||
self.rules = rules
|
self.rules = rules
|
||||||
self.variables: dict[str, str] = {}
|
self.variables: dict[str, str] = {}
|
||||||
self._css_variables: dict[str, list[Token]] = {}
|
|
||||||
if variables:
|
|
||||||
self.set_variables(variables)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_snippet(cls, code: str, line_no: int) -> RenderableType:
|
def _get_snippet(cls, code: str, line_no: int) -> RenderableType:
|
||||||
syntax = Syntax(
|
syntax = Syntax(
|
||||||
code,
|
code,
|
||||||
lexer="scss",
|
lexer="sass",
|
||||||
theme="ansi_light",
|
theme="ansi_light",
|
||||||
line_numbers=True,
|
line_numbers=True,
|
||||||
indent_guides=True,
|
indent_guides=True,
|
||||||
@@ -61,11 +55,6 @@ class StylesheetErrors:
|
|||||||
)
|
)
|
||||||
return syntax
|
return syntax
|
||||||
|
|
||||||
def set_variables(self, variable_map: dict[str, str]) -> None:
|
|
||||||
"""Pre-populate CSS variables."""
|
|
||||||
self.variables.update(variable_map)
|
|
||||||
self._css_variables = tokenize_values(self.variables)
|
|
||||||
|
|
||||||
def __rich_console__(
|
def __rich_console__(
|
||||||
self, console: Console, options: ConsoleOptions
|
self, console: Console, options: ConsoleOptions
|
||||||
) -> RenderResult:
|
) -> RenderResult:
|
||||||
@@ -105,7 +94,10 @@ class StylesheetErrors:
|
|||||||
title = Text.assemble(Text("Error at ", style="bold red"), path_text)
|
title = Text.assemble(Text("Error at ", style="bold red"), path_text)
|
||||||
yield ""
|
yield ""
|
||||||
yield Panel(
|
yield Panel(
|
||||||
self._get_snippet(token.code, line_no),
|
self._get_snippet(
|
||||||
|
token.referenced_by.code if token.referenced_by else token.code,
|
||||||
|
line_no,
|
||||||
|
),
|
||||||
title=title,
|
title=title,
|
||||||
title_align="left",
|
title_align="left",
|
||||||
border_style="red",
|
border_style="red",
|
||||||
@@ -138,13 +130,20 @@ class Stylesheet:
|
|||||||
def __init__(self, *, variables: dict[str, str] | None = None) -> None:
|
def __init__(self, *, variables: dict[str, str] | None = None) -> None:
|
||||||
self._rules: list[RuleSet] = []
|
self._rules: list[RuleSet] = []
|
||||||
self._rules_map: dict[str, list[RuleSet]] | None = None
|
self._rules_map: dict[str, list[RuleSet]] | None = None
|
||||||
self.variables = variables or {}
|
self._variables = variables or {}
|
||||||
|
self.__variable_tokens: dict[str, list[Token]] | None = None
|
||||||
self.source: dict[str, CssSource] = {}
|
self.source: dict[str, CssSource] = {}
|
||||||
self._require_parse = False
|
self._require_parse = False
|
||||||
|
|
||||||
def __rich_repr__(self) -> rich.repr.Result:
|
def __rich_repr__(self) -> rich.repr.Result:
|
||||||
yield list(self.source.keys())
|
yield list(self.source.keys())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _variable_tokens(self) -> dict[str, list[Token]]:
|
||||||
|
if self.__variable_tokens is None:
|
||||||
|
self.__variable_tokens = tokenize_values(self._variables)
|
||||||
|
return self.__variable_tokens
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rules(self) -> list[RuleSet]:
|
def rules(self) -> list[RuleSet]:
|
||||||
"""List of rule sets.
|
"""List of rule sets.
|
||||||
@@ -183,7 +182,7 @@ class Stylesheet:
|
|||||||
Returns:
|
Returns:
|
||||||
Stylesheet: New stylesheet.
|
Stylesheet: New stylesheet.
|
||||||
"""
|
"""
|
||||||
stylesheet = Stylesheet(variables=self.variables.copy())
|
stylesheet = Stylesheet(variables=self._variables.copy())
|
||||||
stylesheet.source = self.source.copy()
|
stylesheet.source = self.source.copy()
|
||||||
return stylesheet
|
return stylesheet
|
||||||
|
|
||||||
@@ -193,7 +192,8 @@ class Stylesheet:
|
|||||||
Args:
|
Args:
|
||||||
variables (dict[str, str]): A mapping of name to variable.
|
variables (dict[str, str]): A mapping of name to variable.
|
||||||
"""
|
"""
|
||||||
self.variables = variables
|
self._variables = variables
|
||||||
|
self._variables_tokens = None
|
||||||
|
|
||||||
def _parse_rules(
|
def _parse_rules(
|
||||||
self,
|
self,
|
||||||
@@ -222,7 +222,7 @@ class Stylesheet:
|
|||||||
parse(
|
parse(
|
||||||
css,
|
css,
|
||||||
path,
|
path,
|
||||||
variables=self.variables,
|
variable_tokens=self._variable_tokens,
|
||||||
is_default_rules=is_default_rules,
|
is_default_rules=is_default_rules,
|
||||||
tie_breaker=tie_breaker,
|
tie_breaker=tie_breaker,
|
||||||
)
|
)
|
||||||
@@ -317,7 +317,7 @@ class Stylesheet:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# Do this in a fresh Stylesheet so if there are errors we don't break self.
|
# Do this in a fresh Stylesheet so if there are errors we don't break self.
|
||||||
stylesheet = Stylesheet(variables=self.variables)
|
stylesheet = Stylesheet(variables=self._variables)
|
||||||
for path, (css, is_defaults, tie_breaker) in self.source.items():
|
for path, (css, is_defaults, tie_breaker) in self.source.items():
|
||||||
stylesheet.add_source(
|
stylesheet.add_source(
|
||||||
css, path, is_default_css=is_defaults, tie_breaker=tie_breaker
|
css, path, is_default_css=is_defaults, tie_breaker=tie_breaker
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ class ReferencedBy(NamedTuple):
|
|||||||
name: str
|
name: str
|
||||||
location: tuple[int, int]
|
location: tuple[int, int]
|
||||||
length: int
|
length: int
|
||||||
|
code: str
|
||||||
|
|
||||||
|
|
||||||
@rich.repr.auto
|
@rich.repr.auto
|
||||||
@@ -209,6 +210,7 @@ class Tokenizer:
|
|||||||
message,
|
message,
|
||||||
)
|
)
|
||||||
iter_groups = iter(match.groups())
|
iter_groups = iter(match.groups())
|
||||||
|
|
||||||
next(iter_groups)
|
next(iter_groups)
|
||||||
|
|
||||||
for name, value in zip(expect.names, iter_groups):
|
for name, value in zip(expect.names, iter_groups):
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class Button(Widget, can_focus=True):
|
|||||||
height: 3;
|
height: 3;
|
||||||
background: $panel;
|
background: $panel;
|
||||||
color: $text;
|
color: $text;
|
||||||
border: none;
|
border: none;
|
||||||
border-top: tall $panel-lighten-2;
|
border-top: tall $panel-lighten-2;
|
||||||
border-bottom: tall $panel-darken-3;
|
border-bottom: tall $panel-darken-3;
|
||||||
content-align: center middle;
|
content-align: center middle;
|
||||||
|
|||||||
Reference in New Issue
Block a user