From f9a5b2a0e8282b005fe8d9779a9d034f70727bb4 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 9 Feb 2023 17:27:39 +0000 Subject: [PATCH 1/2] typing fixes --- pyproject.toml | 1 + src/textual/_border.py | 2 +- src/textual/css/_style_properties.py | 44 +++++---- src/textual/css/_styles_builder.py | 45 +++++---- src/textual/css/model.py | 3 +- src/textual/css/parse.py | 3 +- src/textual/css/styles.py | 137 +++++++++++++-------------- src/textual/css/stylesheet.py | 4 +- src/textual/dom.py | 6 +- src/textual/drivers/win32.py | 10 +- src/textual/file_monitor.py | 4 +- src/textual/scroll_view.py | 12 +-- src/textual/widget.py | 19 ++-- src/textual/widgets/_tree.py | 13 +-- 14 files changed, 162 insertions(+), 141 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1d8d28a24..5f7ca9aa4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,7 @@ syrupy = "^3.0.0" mkdocs-rss-plugin = "^1.5.0" httpx = "^0.23.1" msgpack-types = "^0.2.0" +types-setuptools = "^67.2.0.1" [tool.black] includes = "src" diff --git a/src/textual/_border.py b/src/textual/_border.py index 2da79b0ee..1a0c10ea0 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -191,7 +191,7 @@ BORDER_LOCATIONS: dict[ INVISIBLE_EDGE_TYPES = cast("frozenset[EdgeType]", frozenset(("", "none", "hidden"))) -BorderValue: TypeAlias = Tuple[EdgeType, Union[str, Color, Style]] +BorderValue: TypeAlias = Tuple[EdgeType, Color] BoxSegments: TypeAlias = Tuple[ Tuple[Segment, Segment, Segment], diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py index f69d10f16..f892ac4b9 100644 --- a/src/textual/css/_style_properties.py +++ b/src/textual/css/_style_properties.py @@ -10,11 +10,12 @@ when setting and getting. from __future__ import annotations from operator import attrgetter -from typing import TYPE_CHECKING, Generic, Iterable, NamedTuple, TypeVar, cast +from typing import TYPE_CHECKING, Generic, Iterable, NamedTuple, Sequence, TypeVar, cast import rich.errors import rich.repr from rich.style import Style +from typing_extensions import TypeAlias from .._border import normalize_border_value from ..color import Color, ColorParseError @@ -51,7 +52,7 @@ if TYPE_CHECKING: from .types import AlignHorizontal, AlignVertical, DockEdge, EdgeType -BorderDefinition = ( +BorderDefinition: TypeAlias = ( "Sequence[tuple[EdgeType, str | Color] | None] | tuple[EdgeType, str | Color]" ) @@ -153,7 +154,7 @@ class ScalarProperty: Returns: The Scalar object or ``None`` if it's not set. """ - return obj.get_rule(self.name) + return cast("Scalar | None", obj.get_rule(self.name)) def __set__( self, obj: StylesBase, value: float | int | Scalar | str | None @@ -233,7 +234,7 @@ class ScalarListProperty: def __get__( self, obj: StylesBase, objtype: type[StylesBase] | None = None ) -> tuple[Scalar, ...] | None: - return obj.get_rule(self.name) + return cast("tuple[Scalar, ...]", obj.get_rule(self.name)) def __set__( self, obj: StylesBase, value: str | Iterable[str | float] | None @@ -289,7 +290,10 @@ class BoxProperty: A ``tuple[EdgeType, Style]`` containing the string type of the box and it's style. Example types are "rounded", "solid", and "dashed". """ - return obj.get_rule(self.name) or ("", self._default_color) + return cast( + "tuple[EdgeType, Color]", + obj.get_rule(self.name) or ("", self._default_color), + ) def __set__(self, obj: Styles, border: tuple[EdgeType, str | Color] | None): """Set the box property. @@ -452,7 +456,7 @@ class BorderProperty: check_refresh() return if isinstance(border, tuple) and len(border) == 2: - _border = normalize_border_value(border) + _border = normalize_border_value(border) # type: ignore setattr(obj, top, _border) setattr(obj, right, _border) setattr(obj, bottom, _border) @@ -462,15 +466,15 @@ class BorderProperty: count = len(border) if count == 1: - _border = normalize_border_value(border[0]) + _border = normalize_border_value(border[0]) # type: ignore setattr(obj, top, _border) setattr(obj, right, _border) setattr(obj, bottom, _border) setattr(obj, left, _border) elif count == 2: _border1, _border2 = ( - normalize_border_value(border[0]), - normalize_border_value(border[1]), + normalize_border_value(border[0]), # type: ignore + normalize_border_value(border[1]), # type: ignore ) setattr(obj, top, _border1) setattr(obj, bottom, _border1) @@ -478,10 +482,10 @@ class BorderProperty: setattr(obj, left, _border2) elif count == 4: _border1, _border2, _border3, _border4 = ( - normalize_border_value(border[0]), - normalize_border_value(border[1]), - normalize_border_value(border[2]), - normalize_border_value(border[3]), + normalize_border_value(border[0]), # type: ignore + normalize_border_value(border[1]), # type: ignore + normalize_border_value(border[2]), # type: ignore + normalize_border_value(border[3]), # type: ignore ) setattr(obj, top, _border1) setattr(obj, right, _border2) @@ -513,7 +517,7 @@ class SpacingProperty: Returns: The Spacing. If unset, returns the null spacing ``(0, 0, 0, 0)``. """ - return obj.get_rule(self.name, NULL_SPACING) + return cast(Spacing, obj.get_rule(self.name, NULL_SPACING)) def __set__(self, obj: StylesBase, spacing: SpacingDimensions | None): """Set the Spacing. @@ -594,7 +598,7 @@ class LayoutProperty: Returns: The ``Layout`` object. """ - return obj.get_rule(self.name) + return cast("Layout | None", obj.get_rule(self.name)) def __set__(self, obj: StylesBase, layout: str | Layout | None): """ @@ -648,7 +652,7 @@ class OffsetProperty: The ``ScalarOffset`` indicating the adjustment that will be made to widget position prior to it being rendered. """ - return obj.get_rule(self.name, NULL_SCALAR) + return cast("ScalarOffset", obj.get_rule(self.name, NULL_SCALAR)) def __set__( self, obj: StylesBase, offset: tuple[int | str, int | str] | ScalarOffset | None @@ -734,7 +738,7 @@ class StringEnumProperty: Returns: The string property value. """ - return obj.get_rule(self.name, self._default) + return cast(str, obj.get_rule(self.name, self._default)) def _before_refresh(self, obj: StylesBase, value: str | None) -> None: """Do any housekeeping before asking for a layout refresh after a value change.""" @@ -795,7 +799,7 @@ class NameProperty: Returns: The name. """ - return obj.get_rule(self.name, "") + return cast(str, obj.get_rule(self.name, "")) def __set__(self, obj: StylesBase, name: str | None): """Set the name property. @@ -929,7 +933,7 @@ class StyleFlagsProperty: Returns: The ``Style`` object. """ - return obj.get_rule(self.name, Style.null()) + return cast(Style, obj.get_rule(self.name, Style.null())) def __set__(self, obj: StylesBase, style_flags: Style | str | None): """Set the style using a style flag string. @@ -992,7 +996,7 @@ class TransitionsProperty: e.g. ``{"offset": Transition(...), ...}``. If no transitions have been set, an empty ``dict`` is returned. """ - return obj.get_rule("transitions", {}) + return cast("dict[str, Transition]", obj.get_rule("transitions", {})) def __set__(self, obj: Styles, transitions: dict[str, Transition] | None) -> None: _rich_traceback_omit = True diff --git a/src/textual/css/_styles_builder.py b/src/textual/css/_styles_builder.py index ff66e9c84..26b67b07c 100644 --- a/src/textual/css/_styles_builder.py +++ b/src/textual/css/_styles_builder.py @@ -113,7 +113,6 @@ class StylesBuilder: suggested_property_name=suggested_property_name, ), ) - return tokens = declaration.tokens @@ -182,7 +181,13 @@ class StylesBuilder: """ if len(tokens) != 1: - string_enum_help_text(name, valid_values=list(valid_values), context="css"), + self.error( + name, + tokens[0], + string_enum_help_text( + name, valid_values=list(valid_values), context="css" + ), + ) token = tokens[0] token_name, value, _, _, location, _ = token @@ -239,7 +244,7 @@ class StylesBuilder: return if len(tokens) == 1: try: - self.styles._rules[name.replace("-", "_")] = Scalar.parse( + self.styles._rules[name.replace("-", "_")] = Scalar.parse( # type: ignore tokens[0].value ) except ScalarParseError: @@ -390,7 +395,7 @@ class StylesBuilder: name, num_values_supplied=len(space), context="css" ), ) - self.styles._rules[name] = Spacing.unpack(cast(SpacingDimensions, tuple(space))) + self.styles._rules[name] = Spacing.unpack(cast(SpacingDimensions, tuple(space))) # type: ignore def _process_space_partial(self, name: str, tokens: list[Token]) -> None: """Process granular margin / padding declarations.""" @@ -418,7 +423,7 @@ class StylesBuilder: spacing_list = list(current_spacing) spacing_list[_EDGE_SPACING_MAP[edge]] = space - self.styles._rules[style_name] = Spacing(*spacing_list) + self.styles._rules[style_name] = Spacing(*spacing_list) # type: ignore process_padding = _process_space process_margin = _process_space @@ -444,7 +449,7 @@ class StylesBuilder: token_name, value, _, _, _, _ = token if token_name == "token": if value in VALID_BORDER: - border_type = value + border_type = value # type: ignore else: try: border_color = Color.parse(value) @@ -464,7 +469,7 @@ class StylesBuilder: def _process_border_edge(self, edge: str, name: str, tokens: list[Token]) -> None: border = self._parse_border(name, tokens) - self.styles._rules[f"border_{edge}"] = border + self.styles._rules[f"border_{edge}"] = border # type: ignore def process_border(self, name: str, tokens: list[Token]) -> None: border = self._parse_border(name, tokens) @@ -486,7 +491,7 @@ class StylesBuilder: def _process_outline(self, edge: str, name: str, tokens: list[Token]) -> None: border = self._parse_border(name, tokens) - self.styles._rules[f"outline_{edge}"] = border + self.styles._rules[f"outline_{edge}"] = border # type: ignore def process_outline(self, name: str, tokens: list[Token]) -> None: border = self._parse_border(name, tokens) @@ -579,14 +584,14 @@ class StylesBuilder: color: Color | None = None alpha: float | None = None - self.styles._rules[f"auto_{name}"] = False + self.styles._rules[f"auto_{name}"] = False # type: ignore for token in tokens: if ( "background" not in name and token.name == "token" and token.value == "auto" ): - self.styles._rules[f"auto_{name}"] = True + self.styles._rules[f"auto_{name}"] = True # type: ignore elif token.name == "scalar": alpha_scalar = Scalar.parse(token.value) if alpha_scalar.unit != Unit.PERCENT: @@ -608,7 +613,7 @@ class StylesBuilder: if color is not None or alpha is not None: if alpha is not None: color = (color or Color(255, 255, 255)).with_alpha(alpha) - self.styles._rules[name] = color + self.styles._rules[name] = color # type: ignore process_tint = process_color process_background = process_color @@ -636,7 +641,7 @@ class StylesBuilder: ) style_definition = " ".join(token.value for token in tokens) - self.styles._rules[name.replace("-", "_")] = style_definition + self.styles._rules[name.replace("-", "_")] = style_definition # type: ignore process_link_style = process_text_style process_link_hover_style = process_text_style @@ -653,7 +658,7 @@ class StylesBuilder: text_align_help_text(), ) - self.styles._rules["text_align"] = tokens[0].value + self.styles._rules["text_align"] = tokens[0].value # type: ignore def process_dock(self, name: str, tokens: list[Token]) -> None: if not tokens: @@ -766,8 +771,8 @@ class StylesBuilder: align_error(name, token_horizontal) name = name.replace("-", "_") - self.styles._rules[f"{name}_horizontal"] = token_horizontal.value - self.styles._rules[f"{name}_vertical"] = token_vertical.value + self.styles._rules[f"{name}_horizontal"] = token_horizontal.value # type: ignore + self.styles._rules[f"{name}_vertical"] = token_vertical.value # type: ignore def process_align_horizontal(self, name: str, tokens: list[Token]) -> None: try: @@ -779,7 +784,7 @@ class StylesBuilder: string_enum_help_text(name, VALID_ALIGN_HORIZONTAL, context="css"), ) else: - self.styles._rules[name.replace("-", "_")] = value + self.styles._rules[name.replace("-", "_")] = value # type: ignore def process_align_vertical(self, name: str, tokens: list[Token]) -> None: try: @@ -791,7 +796,7 @@ class StylesBuilder: string_enum_help_text(name, VALID_ALIGN_VERTICAL, context="css"), ) else: - self.styles._rules[name.replace("-", "_")] = value + self.styles._rules[name.replace("-", "_")] = value # type: ignore process_content_align = process_align process_content_align_horizontal = process_align_horizontal @@ -807,7 +812,7 @@ class StylesBuilder: string_enum_help_text(name, VALID_SCROLLBAR_GUTTER, context="css"), ) else: - self.styles._rules[name.replace("-", "_")] = value + self.styles._rules[name.replace("-", "_")] = value # type: ignore def process_scrollbar_size(self, name: str, tokens: list[Token]) -> None: def scrollbar_size_error(name: str, token: Token) -> None: @@ -876,7 +881,7 @@ class StylesBuilder: token, table_rows_or_columns_help_text(name, token.value, context="css"), ) - self.styles._rules[name.replace("-", "_")] = scalars + self.styles._rules[name.replace("-", "_")] = scalars # type: ignore process_grid_rows = _process_grid_rows_or_columns process_grid_columns = _process_grid_rows_or_columns @@ -893,7 +898,7 @@ class StylesBuilder: value = int(token.value) if value == 0: self.error(name, token, integer_help_text(name)) - self.styles._rules[name.replace("-", "_")] = value + self.styles._rules[name.replace("-", "_")] = value # type: ignore process_grid_gutter_horizontal = _process_integer process_grid_gutter_vertical = _process_integer diff --git a/src/textual/css/model.py b/src/textual/css/model.py index cc68ee0af..3766606de 100644 --- a/src/textual/css/model.py +++ b/src/textual/css/model.py @@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Iterable import rich.repr +from ._help_renderables import HelpText from .styles import Styles from .tokenize import Token from .types import Specificity3 @@ -155,7 +156,7 @@ class SelectorSet: class RuleSet: selector_set: list[SelectorSet] = field(default_factory=list) styles: Styles = field(default_factory=Styles) - errors: list[tuple[Token, str]] = field(default_factory=list) + errors: list[tuple[Token, str | HelpText]] = field(default_factory=list) is_default_rules: bool = False tie_breaker: int = 0 diff --git a/src/textual/css/parse.py b/src/textual/css/parse.py index fe23c084d..a9a8fd668 100644 --- a/src/textual/css/parse.py +++ b/src/textual/css/parse.py @@ -5,6 +5,7 @@ from pathlib import PurePath from typing import Iterable, Iterator, NoReturn from ..suggestions import get_suggestion +from ._help_renderables import HelpText from ._styles_builder import DeclarationError, StylesBuilder from .errors import UnresolvedVariableError from .model import ( @@ -130,7 +131,7 @@ def parse_rule_set( declaration = Declaration(token, "") - errors: list[tuple[Token, str]] = [] + errors: list[tuple[Token, str | HelpText]] = [] while True: token = next(tokens) diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index ba2efa93c..644d94f74 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -367,8 +367,8 @@ class StylesBase(ABC): def auto_dimensions(self) -> bool: """Check if width or height are set to 'auto'.""" has_rule = self.has_rule - return (has_rule("width") and self.width.is_auto) or ( - has_rule("height") and self.height.is_auto + return (has_rule("width") and self.width.is_auto) or ( # type: ignore + has_rule("height") and self.height.is_auto # type: ignore ) @abstractmethod @@ -603,7 +603,7 @@ class Styles(StylesBase): Returns: ``True`` if a rule was cleared, or ``False`` if it was already not set. """ - changed = self._rules.pop(rule, None) is not None + changed = self._rules.pop(rule, None) is not None # type: ignore if changed: self._updates += 1 return changed @@ -622,12 +622,12 @@ class Styles(StylesBase): ``True`` if the rule changed, otherwise ``False``. """ if value is None: - changed = self._rules.pop(rule, None) is not None + changed = self._rules.pop(rule, None) is not None # type: ignore if changed: self._updates += 1 return changed current = self._rules.get(rule) - self._rules[rule] = value + self._rules[rule] = value # type: ignore changed = current != value if changed: self._updates += 1 @@ -646,7 +646,7 @@ class Styles(StylesBase): def reset(self) -> None: """Reset the rules to initial state.""" self._updates += 1 - self._rules.clear() + self._rules.clear() # type: ignore def merge(self, other: Styles) -> None: """Merge values from another Styles. @@ -736,25 +736,25 @@ class Styles(StylesBase): left = get_rule(f"{name}_left") if top == right and right == bottom and bottom == left: - border_type, border_color = rules[f"{name}_top"] + border_type, border_color = rules[f"{name}_top"] # type: ignore yield name, f"{border_type} {border_color.hex}" return # Check for edges if has_top: - border_type, border_color = rules[f"{name}_top"] + border_type, border_color = rules[f"{name}_top"] # type: ignore yield f"{name}-top", f"{border_type} {border_color.hex}" if has_right: - border_type, border_color = rules[f"{name}_right"] + border_type, border_color = rules[f"{name}_right"] # type: ignore yield f"{name}-right", f"{border_type} {border_color.hex}" if has_bottom: - border_type, border_color = rules[f"{name}_bottom"] + border_type, border_color = rules[f"{name}_bottom"] # type: ignore yield f"{name}-bottom", f"{border_type} {border_color.hex}" if has_left: - border_type, border_color = rules[f"{name}_left"] + border_type, border_color = rules[f"{name}_left"] # type: ignore yield f"{name}-left", f"{border_type} {border_color.hex}" @property @@ -770,15 +770,14 @@ class Styles(StylesBase): rules = self.get_rules() get_rule = rules.get - has_rule = rules.__contains__ - if has_rule("display"): + if "display" in rules: append_declaration("display", rules["display"]) - if has_rule("visibility"): + if "visibility" in rules: append_declaration("visibility", rules["visibility"]) - if has_rule("padding"): + if "padding" in rules: append_declaration("padding", rules["padding"].css) - if has_rule("margin"): + if "margin" in rules: append_declaration("margin", rules["margin"].css) for name, rule in self._get_border_css_lines(rules, "border"): @@ -787,90 +786,90 @@ class Styles(StylesBase): for name, rule in self._get_border_css_lines(rules, "outline"): append_declaration(name, rule) - if has_rule("offset"): + if "offset" in rules: x, y = self.offset append_declaration("offset", f"{x} {y}") - if has_rule("dock"): + if "dock" in rules: append_declaration("dock", rules["dock"]) - if has_rule("layers"): + if "layers" in rules: append_declaration("layers", " ".join(self.layers)) - if has_rule("layer"): + if "layer" in rules: append_declaration("layer", self.layer) - if has_rule("layout"): + if "layout" in rules: assert self.layout is not None append_declaration("layout", self.layout.name) - if has_rule("color"): + if "color" in rules: append_declaration("color", self.color.hex) - if has_rule("background"): + if "background" in rules: append_declaration("background", self.background.hex) - if has_rule("text_style"): + if "text_style" in rules: append_declaration("text-style", str(get_rule("text_style"))) - if has_rule("tint"): + if "tint" in rules: append_declaration("tint", self.tint.css) - if has_rule("overflow_x"): + if "overflow_x" in rules: append_declaration("overflow-x", self.overflow_x) - if has_rule("overflow_y"): + if "overflow_y" in rules: append_declaration("overflow-y", self.overflow_y) - if has_rule("scrollbar_color"): + if "scrollbar_color" in rules: append_declaration("scrollbar-color", self.scrollbar_color.css) - if has_rule("scrollbar_color_hover"): + if "scrollbar_color_hover" in rules: append_declaration("scrollbar-color-hover", self.scrollbar_color_hover.css) - if has_rule("scrollbar_color_active"): + if "scrollbar_color_active" in rules: append_declaration( "scrollbar-color-active", self.scrollbar_color_active.css ) - if has_rule("scrollbar_corner_color"): + if "scrollbar_corner_color" in rules: append_declaration( "scrollbar-corner-color", self.scrollbar_corner_color.css ) - if has_rule("scrollbar_background"): + if "scrollbar_background" in rules: append_declaration("scrollbar-background", self.scrollbar_background.css) - if has_rule("scrollbar_background_hover"): + if "scrollbar_background_hover" in rules: append_declaration( "scrollbar-background-hover", self.scrollbar_background_hover.css ) - if has_rule("scrollbar_background_active"): + if "scrollbar_background_active" in rules: append_declaration( "scrollbar-background-active", self.scrollbar_background_active.css ) - if has_rule("scrollbar_gutter"): + if "scrollbar_gutter" in rules: append_declaration("scrollbar-gutter", self.scrollbar_gutter) - if has_rule("scrollbar_size"): + if "scrollbar_size" in rules: append_declaration( "scrollbar-size", f"{self.scrollbar_size_horizontal} {self.scrollbar_size_vertical}", ) else: - if has_rule("scrollbar_size_horizontal"): + if "scrollbar_size_horizontal" in rules: append_declaration( "scrollbar-size-horizontal", str(self.scrollbar_size_horizontal) ) - if has_rule("scrollbar_size_vertical"): + if "scrollbar_size_vertical" in rules: append_declaration( "scrollbar-size-vertical", str(self.scrollbar_size_vertical) ) - if has_rule("box_sizing"): + if "box_sizing" in rules: append_declaration("box-sizing", self.box_sizing) - if has_rule("width"): + if "width" in rules: append_declaration("width", str(self.width)) - if has_rule("height"): + if "height" in rules: append_declaration("height", str(self.height)) - if has_rule("min_width"): + if "min_width" in rules: append_declaration("min-width", str(self.min_width)) - if has_rule("min_height"): + if "min_height" in rules: append_declaration("min-height", str(self.min_height)) - if has_rule("max_width"): + if "max_width" in rules: append_declaration("max-width", str(self.min_width)) - if has_rule("max_height"): + if "max_height" in rules: append_declaration("max-height", str(self.min_height)) - if has_rule("transitions"): + if "transitions" in rules: append_declaration( "transition", ", ".join( @@ -879,74 +878,74 @@ class Styles(StylesBase): ), ) - if has_rule("align_horizontal") and has_rule("align_vertical"): + if "align_horizontal" in rules and "align_vertical" in rules: append_declaration( "align", f"{self.align_horizontal} {self.align_vertical}" ) - elif has_rule("align_horizontal"): + elif "align_horizontal" in rules: append_declaration("align-horizontal", self.align_horizontal) - elif has_rule("align_vertical"): + elif "align_vertical" in rules: append_declaration("align-vertical", self.align_vertical) - if has_rule("content_align_horizontal") and has_rule("content_align_vertical"): + if "content_align_horizontal" in rules and "content_align_vertical" in rules: append_declaration( "content-align", f"{self.content_align_horizontal} {self.content_align_vertical}", ) - elif has_rule("content_align_horizontal"): + elif "content_align_horizontal" in rules: append_declaration( "content-align-horizontal", self.content_align_horizontal ) - elif has_rule("content_align_vertical"): + elif "content_align_vertical" in rules: append_declaration("content-align-vertical", self.content_align_vertical) - if has_rule("text_align"): + if "text_align" in rules: append_declaration("text-align", self.text_align) - if has_rule("opacity"): + if "opacity" in rules: append_declaration("opacity", str(self.opacity)) - if has_rule("text_opacity"): + if "text_opacity" in rules: append_declaration("text-opacity", str(self.text_opacity)) - if has_rule("grid_columns"): + if "grid_columns" in rules: append_declaration( "grid-columns", " ".join(str(scalar) for scalar in self.grid_columns or ()), ) - if has_rule("grid_rows"): + if "grid_rows" in rules: append_declaration( "grid-rows", " ".join(str(scalar) for scalar in self.grid_rows or ()), ) - if has_rule("grid_size_columns"): + if "grid_size_columns" in rules: append_declaration("grid-size-columns", str(self.grid_size_columns)) - if has_rule("grid_size_rows"): + if "grid_size_rows" in rules: append_declaration("grid-size-rows", str(self.grid_size_rows)) - if has_rule("grid_gutter_horizontal"): + if "grid_gutter_horizontal" in rules: append_declaration( "grid-gutter-horizontal", str(self.grid_gutter_horizontal) ) - if has_rule("grid_gutter_vertical"): + if "grid_gutter_vertical" in rules: append_declaration("grid-gutter-vertical", str(self.grid_gutter_vertical)) - if has_rule("row_span"): + if "row_span" in rules: append_declaration("row-span", str(self.row_span)) - if has_rule("column_span"): + if "column_span" in rules: append_declaration("column-span", str(self.column_span)) - if has_rule("link_color"): + if "link_color" in rules: append_declaration("link-color", self.link_color.css) - if has_rule("link_background"): + if "link_background" in rules: append_declaration("link-background", self.link_background.css) - if has_rule("link_style"): + if "link_style" in rules: append_declaration("link-style", str(self.link_style)) - if has_rule("link_hover_color"): + if "link_hover_color" in rules: append_declaration("link-hover-color", self.link_hover_color.css) - if has_rule("link_hover_background"): + if "link_hover_background" in rules: append_declaration("link-hover-background", self.link_hover_background.css) - if has_rule("link_hover_style"): + if "link_hover_style" in rules: append_declaration("link-hover-style", str(self.link_hover_style)) lines.sort() diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index 0b676776d..975326202 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -4,7 +4,7 @@ import os from collections import defaultdict from operator import itemgetter from pathlib import Path, PurePath -from typing import Iterable, NamedTuple, cast +from typing import Iterable, NamedTuple, Sequence, cast import rich.repr from rich.console import Console, ConsoleOptions, RenderableType, RenderResult @@ -248,7 +248,7 @@ class Stylesheet: self.source[str(path)] = CssSource(css, False, 0) self._require_parse = True - def read_all(self, paths: list[PurePath]) -> None: + def read_all(self, paths: Sequence[PurePath]) -> None: """Read multiple CSS files, in order. Args: diff --git a/src/textual/dom.py b/src/textual/dom.py index 07f994958..0497f623f 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -132,10 +132,12 @@ class DOMNode(MessagePump): check_identifiers("class name", *_classes) self._classes.update(_classes) - self.children = NodeList() + self.children: NodeList = NodeList() self._css_styles: Styles = Styles(self) self._inline_styles: Styles = Styles(self) - self.styles = RenderStyles(self, self._css_styles, self._inline_styles) + self.styles: RenderStyles = RenderStyles( + self, self._css_styles, self._inline_styles + ) # A mapping of class names to Styles set in COMPONENT_CLASSES self._component_styles: dict[str, RenderStyles] = {} diff --git a/src/textual/drivers/win32.py b/src/textual/drivers/win32.py index 34edc9930..30a335bd9 100644 --- a/src/textual/drivers/win32.py +++ b/src/textual/drivers/win32.py @@ -7,12 +7,12 @@ from ctypes import Structure, Union, byref, wintypes from ctypes.wintypes import BOOL, CHAR, DWORD, HANDLE, SHORT, UINT, WCHAR, WORD from typing import IO, Callable, List, Optional -from .._types import EventTarget +from .._types import MessageTarget from .._xterm_parser import XTermParser from ..events import Event, Resize from ..geometry import Size -KERNEL32 = ctypes.WinDLL("kernel32", use_last_error=True) +KERNEL32 = ctypes.WinDLL("kernel32", use_last_error=True) # type: ignore # Console input modes ENABLE_ECHO_INPUT = 0x0004 @@ -130,7 +130,7 @@ def _set_console_mode(file: IO, mode: int) -> bool: Returns: True on success, otherwise False. """ - windows_filehandle = msvcrt.get_osfhandle(file.fileno()) + windows_filehandle = msvcrt.get_osfhandle(file.fileno()) # type: ignore success = KERNEL32.SetConsoleMode(windows_filehandle, mode) return success @@ -144,7 +144,7 @@ def _get_console_mode(file: IO) -> int: Returns: The current console mode. """ - windows_filehandle = msvcrt.get_osfhandle(file.fileno()) + windows_filehandle = msvcrt.get_osfhandle(file.fileno()) # type: ignore mode = wintypes.DWORD() KERNEL32.GetConsoleMode(windows_filehandle, ctypes.byref(mode)) return mode.value @@ -211,7 +211,7 @@ class EventMonitor(threading.Thread): self, loop: AbstractEventLoop, app, - target: EventTarget, + target: MessageTarget, exit_event: threading.Event, process_event: Callable[[Event], None], ) -> None: diff --git a/src/textual/file_monitor.py b/src/textual/file_monitor.py index 778884865..a50625566 100644 --- a/src/textual/file_monitor.py +++ b/src/textual/file_monitor.py @@ -2,7 +2,7 @@ from __future__ import annotations import os from pathlib import PurePath -from typing import Callable +from typing import Callable, Sequence import rich.repr @@ -13,7 +13,7 @@ from ._callback import invoke class FileMonitor: """Monitors files for changes and invokes a callback when it does.""" - def __init__(self, paths: list[PurePath], callback: Callable) -> None: + def __init__(self, paths: Sequence[PurePath], callback: Callable) -> None: self.paths = paths self.callback = callback self._modified = self._get_last_modified_time() diff --git a/src/textual/scroll_view.py b/src/textual/scroll_view.py index e50bfcbfd..53c6c238a 100644 --- a/src/textual/scroll_view.py +++ b/src/textual/scroll_view.py @@ -30,14 +30,14 @@ class ScrollView(Widget): """Not transparent, i.e. renders something.""" return False - def watch_scroll_x(self, new_value: float) -> None: - if self.show_horizontal_scrollbar: - self.horizontal_scrollbar.position = int(new_value) + def watch_scroll_x(self, old_value: float, new_value: float) -> None: + if self.show_horizontal_scrollbar and round(old_value) != round(new_value): + self.horizontal_scrollbar.position = round(new_value) self.refresh() - def watch_scroll_y(self, new_value: float) -> None: - if self.show_vertical_scrollbar: - self.vertical_scrollbar.position = int(new_value) + def watch_scroll_y(self, old_value: float, new_value: float) -> None: + if self.show_vertical_scrollbar and round(old_value) != round(new_value): + self.vertical_scrollbar.position = round(new_value) self.refresh() def on_mount(self): diff --git a/src/textual/widget.py b/src/textual/widget.py index 4cb9fbbc5..197eb6147 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -117,7 +117,7 @@ class _Styled: """ def __init__( - self, renderable: "RenderableType", style: Style, link_style: Style | None + self, renderable: "ConsoleRenderable", style: Style, link_style: Style | None ) -> None: self.renderable = renderable self.style = style @@ -133,7 +133,7 @@ class _Styled: if style: apply = style.__add__ result_segments = ( - _Segment(text, apply(_style), control) + _Segment(text, apply(_style), None) for text, _style, control in result_segments ) link_style = self.link_style @@ -141,19 +141,22 @@ class _Styled: result_segments = ( _Segment( text, - style - if style._meta is None - else (style + link_style if "@click" in style.meta else style), + ( + style + if style._meta is None + else (style + link_style if "@click" in style.meta else style) + ), control, ) for text, style, control in result_segments + if style is not None ) return result_segments def __rich_measure__( self, console: "Console", options: "ConsoleOptions" ) -> Measurement: - return self.renderable.__rich_measure__(console, options) + return Measurement.get(console, options, self.renderable) class RenderCache(NamedTuple): @@ -1414,6 +1417,7 @@ class Widget(DOMNode): easing = DEFAULT_SCROLL_EASING if maybe_scroll_x: + assert x is not None self.scroll_target_x = x if x != self.scroll_x: self.animate( @@ -1425,6 +1429,7 @@ class Widget(DOMNode): ) scrolled_x = True if maybe_scroll_y: + assert y is not None self.scroll_target_y = y if y != self.scroll_y: self.animate( @@ -1438,10 +1443,12 @@ class Widget(DOMNode): else: if maybe_scroll_x: + assert x is not None scroll_x = self.scroll_x self.scroll_target_x = self.scroll_x = x scrolled_x = scroll_x != self.scroll_x if maybe_scroll_y: + assert y is not None scroll_y = self.scroll_y self.scroll_target_y = self.scroll_y = y scrolled_y = scroll_y != self.scroll_y diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 9421d47ad..3786b080d 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING, ClassVar, Generic, NewType, TypeVar +from typing import TYPE_CHECKING, ClassVar, Generic, Iterable, NewType, TypeVar, cast import rich.repr from rich.style import NULL_STYLE, Style @@ -783,7 +783,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): assert self._tree_lines_cached is not None return self._tree_lines_cached - def _on_idle(self) -> None: + async def _on_idle(self, event: events.Idle) -> None: """Check tree needs a rebuild on idle.""" # Property calls build if required self._tree_lines @@ -891,6 +891,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): Returns: Strings for space, vertical, terminator and cross. """ + lines: tuple[Iterable[str], Iterable[str], Iterable[str], Iterable[str]] if self.show_guides: lines = self.LINES["default"] if style.bold: @@ -901,11 +902,11 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): lines = (" ", " ", " ", " ") guide_depth = max(0, self.guide_depth - 2) - lines = tuple( - f"{vertical}{horizontal * guide_depth} " - for vertical, horizontal in lines + guide_lines = tuple( + f"{characters[0]}{characters[1] * guide_depth} " + for characters in lines ) - return lines + return cast("tuple[str, str, str, str]", guide_lines) if is_hover: line_style = self.get_component_rich_style("tree--highlight-line") From 4fd7e19e8991c19cace092453f1c01abef023a41 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 9 Feb 2023 17:30:02 +0000 Subject: [PATCH 2/2] lockfile --- poetry.lock | 53 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/poetry.lock b/poetry.lock index a2448ef8b..a1bdd2099 100644 --- a/poetry.lock +++ b/poetry.lock @@ -654,7 +654,7 @@ python-versions = ">=3.7" [[package]] name = "platformdirs" -version = "2.6.2" +version = "3.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false @@ -664,8 +664,8 @@ python-versions = ">=3.7" typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} [package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" @@ -959,6 +959,25 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "types-docutils" +version = "0.19.1.3" +description = "Typing stubs for docutils" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "types-setuptools" +version = "67.2.0.1" +description = "Typing stubs for setuptools" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +types-docutils = "*" + [[package]] name = "typing-extensions" version = "4.4.0" @@ -990,7 +1009,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.18.0" +version = "20.19.0" description = "Virtual Python Environment builder" category = "dev" optional = false @@ -1000,7 +1019,7 @@ python-versions = ">=3.7" distlib = ">=0.3.6,<1" filelock = ">=3.4.1,<4" importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} -platformdirs = ">=2.4,<3" +platformdirs = ">=2.4,<4" [package.extras] docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] @@ -1032,7 +1051,7 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "zipp" -version = "3.12.1" +version = "3.13.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false @@ -1048,7 +1067,7 @@ dev = ["aiohttp", "click", "msgpack"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "425fac5cc893af33128a6baf4fc9e296781d322e64eea5d9341d1265c6637d3c" +content-hash = "ddf5ab753228b6e3b623cd1ef4d26dad17348e6d28b1b8d39e21989be0e7b5fb" [metadata.files] aiohttp = [ @@ -1685,8 +1704,8 @@ pathspec = [ {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, ] platformdirs = [ - {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, - {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, + {file = "platformdirs-3.0.0-py3-none-any.whl", hash = "sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567"}, + {file = "platformdirs-3.0.0.tar.gz", hash = "sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -1895,6 +1914,14 @@ typed-ast = [ {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] +types-docutils = [ + {file = "types-docutils-0.19.1.3.tar.gz", hash = "sha256:36fe30de56f1ece1a9f7a990d47daa781b5af831d2b3f2dcb7dfd01b857cc3d4"}, + {file = "types_docutils-0.19.1.3-py3-none-any.whl", hash = "sha256:d608e6b91ccf0e8e01c586a0af5b0e0462382d3be65b734af82d40c9d010735d"}, +] +types-setuptools = [ + {file = "types-setuptools-67.2.0.1.tar.gz", hash = "sha256:07648088bc2cbf0f2745107d394e619ba2a747f68a5904e6e4089c0cb8322065"}, + {file = "types_setuptools-67.2.0.1-py3-none-any.whl", hash = "sha256:f15b2924122dca5f99a9f6a96a872145721373fe1bb6d656cf269c2a8b73a74b"}, +] typing-extensions = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, @@ -1908,8 +1935,8 @@ urllib3 = [ {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, ] virtualenv = [ - {file = "virtualenv-20.18.0-py3-none-any.whl", hash = "sha256:9d61e4ec8d2c0345dab329fb825eb05579043766a4b26a2f66b28948de68c722"}, - {file = "virtualenv-20.18.0.tar.gz", hash = "sha256:f262457a4d7298a6b733b920a196bf8b46c8af15bf1fd9da7142995eff15118e"}, + {file = "virtualenv-20.19.0-py3-none-any.whl", hash = "sha256:54eb59e7352b573aa04d53f80fc9736ed0ad5143af445a1e539aada6eb947dd1"}, + {file = "virtualenv-20.19.0.tar.gz", hash = "sha256:37a640ba82ed40b226599c522d411e4be5edb339a0c0de030c0dc7b646d61590"}, ] watchdog = [ {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a09483249d25cbdb4c268e020cb861c51baab2d1affd9a6affc68ffe6a231260"}, @@ -2018,6 +2045,6 @@ yarl = [ {file = "yarl-1.8.2.tar.gz", hash = "sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562"}, ] zipp = [ - {file = "zipp-3.12.1-py3-none-any.whl", hash = "sha256:6c4fe274b8f85ec73c37a8e4e3fa00df9fb9335da96fb789e3b96b318e5097b3"}, - {file = "zipp-3.12.1.tar.gz", hash = "sha256:a3cac813d40993596b39ea9e93a18e8a2076d5c378b8bc88ec32ab264e04ad02"}, + {file = "zipp-3.13.0-py3-none-any.whl", hash = "sha256:e8b2a36ea17df80ffe9e2c4fda3f693c3dad6df1697d3cd3af232db680950b0b"}, + {file = "zipp-3.13.0.tar.gz", hash = "sha256:23f70e964bc11a34cef175bc90ba2914e1e4545ea1e3e2f67c079671883f9cb6"}, ]