mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
formatting
This commit is contained in:
@@ -28,4 +28,4 @@ class BasicApp(App):
|
||||
await self.view.mount(widget1=Placeholder(), widget2=Placeholder())
|
||||
|
||||
|
||||
SmoothApp.run(log="textual.log")
|
||||
BasicApp.run(log="textual.log")
|
||||
|
||||
@@ -7,14 +7,14 @@ from typing import Any, Callable, ClassVar, Type, TypeVar
|
||||
import warnings
|
||||
|
||||
from rich.control import Control
|
||||
from rich.highlighter import ReprHighlighter
|
||||
|
||||
import rich.repr
|
||||
from rich.screen import Screen
|
||||
from rich.console import Console, RenderableType
|
||||
from rich.measure import Measurement
|
||||
from rich.pretty import Pretty
|
||||
|
||||
from rich.traceback import Traceback
|
||||
from rich.tree import Tree
|
||||
|
||||
|
||||
from . import events
|
||||
from . import actions
|
||||
@@ -137,21 +137,6 @@ class App(DOMNode):
|
||||
def view(self) -> DockView:
|
||||
return self._view_stack[-1]
|
||||
|
||||
@property
|
||||
def tree(self) -> Tree:
|
||||
highlighter = ReprHighlighter()
|
||||
tree = Tree(highlighter(repr(self)))
|
||||
|
||||
def add_children(tree, node):
|
||||
for child in node.children:
|
||||
branch = tree.add(Pretty(child))
|
||||
if tree.children:
|
||||
add_children(branch, child)
|
||||
|
||||
branch = tree.add(Pretty(self.view))
|
||||
add_children(branch, self.view)
|
||||
return tree
|
||||
|
||||
@property
|
||||
def css_type(self) -> str:
|
||||
return "app"
|
||||
|
||||
@@ -134,8 +134,8 @@ class StylesBuilder:
|
||||
def process_border(self, name: str, tokens: list[Token]) -> None:
|
||||
border = self._parse_border("border", tokens)
|
||||
styles = self.styles
|
||||
styles._border_top = styles._border_right = border
|
||||
styles._border_bottom = styles._border_left = border
|
||||
styles._rule_border_top = styles._rule_border_right = border
|
||||
styles._rule_border_bottom = styles._rule_border_left = border
|
||||
|
||||
def process_border_top(self, name: str, tokens: list[Token]) -> None:
|
||||
self._process_border("top", name, tokens)
|
||||
@@ -156,8 +156,8 @@ class StylesBuilder:
|
||||
def process_outline(self, name: str, tokens: list[Token]) -> None:
|
||||
border = self._parse_border("outline", tokens)
|
||||
styles = self.styles
|
||||
styles._outline_top = styles._outline_right = border
|
||||
styles._outline_bottom = styles._outline_left = border
|
||||
styles._rule_outline_top = styles._rule_outline_right = border
|
||||
styles._rule_outline_bottom = styles._rule_outline_left = border
|
||||
|
||||
def process_outline_top(self, name: str, tokens: list[Token]) -> None:
|
||||
self._process_outline("top", name, tokens)
|
||||
@@ -182,7 +182,7 @@ class StylesBuilder:
|
||||
self.error(name, token1, f"expected a number (found {token1.value!r})")
|
||||
if token2.name != "number":
|
||||
self.error(name, token2, f"expected a number (found {token1.value!r})")
|
||||
self.styles._offset = Offset(
|
||||
self.styles._rule_offset = Offset(
|
||||
int(float(token1.value)), int(float(token2.value))
|
||||
)
|
||||
|
||||
@@ -194,7 +194,7 @@ class StylesBuilder:
|
||||
else:
|
||||
x = int(float(tokens[0].value))
|
||||
y = self.styles.offset.y
|
||||
self.styles._offset = Offset(x, y)
|
||||
self.styles._rule_offset = Offset(x, y)
|
||||
|
||||
def process_offset_y(self, name: str, tokens: list[Token]) -> None:
|
||||
if not tokens:
|
||||
@@ -204,18 +204,21 @@ class StylesBuilder:
|
||||
else:
|
||||
y = int(float(tokens[0].value))
|
||||
x = self.styles.offset.x
|
||||
self.styles._offset = Offset(x, y)
|
||||
self.styles._rule_offset = Offset(x, y)
|
||||
|
||||
def process_text(self, name: str, tokens: list[Token]) -> None:
|
||||
style_definition = " ".join(token.value for token in tokens)
|
||||
style = Style.parse(style_definition)
|
||||
self.styles._text = style
|
||||
self.styles._rule_text = style
|
||||
|
||||
def process_text_color(self, name: str, tokens: list[Token]) -> None:
|
||||
for token in tokens:
|
||||
if token.name in ("color", "token"):
|
||||
try:
|
||||
self.styles._text += Style(color=Color.parse(token.value))
|
||||
new_style = (self.styles._rule_text or Style()) + Style(
|
||||
color=Color.parse(token.value)
|
||||
)
|
||||
self.styles._rule_text = new_style
|
||||
except Exception as error:
|
||||
self.error(
|
||||
name, token, f"failed to parse color {token.value!r}; {error}"
|
||||
@@ -229,7 +232,10 @@ class StylesBuilder:
|
||||
for token in tokens:
|
||||
if token.name in ("color", "token"):
|
||||
try:
|
||||
self.styles._text += Style(bgcolor=Color.parse(token.value))
|
||||
new_style = (self.styles._rule_text or Style()) + Style(
|
||||
bgcolor=Color.parse(token.value)
|
||||
)
|
||||
self.styles._rule_text = new_style
|
||||
except Exception as error:
|
||||
self.error(
|
||||
name, token, f"failed to parse color {token.value!r}; {error}"
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
|
||||
from rich import print
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Iterable
|
||||
|
||||
from ..dom import DOMNode
|
||||
from .styles import Styles
|
||||
from .tokenize import Token
|
||||
from .types import Specificity3
|
||||
@@ -30,13 +33,18 @@ class Location:
|
||||
column: tuple[int, int]
|
||||
|
||||
|
||||
def _default_check(node: DOMNode) -> bool | None:
|
||||
return True
|
||||
|
||||
|
||||
@dataclass
|
||||
class Selector:
|
||||
name: str
|
||||
combinator: CombinatorType = CombinatorType.SAME
|
||||
combinator: CombinatorType = CombinatorType.DESCENDENT
|
||||
type: SelectorType = SelectorType.TYPE
|
||||
pseudo_classes: list[str] = field(default_factory=list)
|
||||
specificity: Specificity3 = field(default_factory=lambda: (0, 0, 0))
|
||||
_name_lower: str = ""
|
||||
|
||||
@property
|
||||
def css(self) -> str:
|
||||
@@ -50,6 +58,42 @@ class Selector:
|
||||
else:
|
||||
return f"#{self.name}{psuedo_suffix}"
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self._name_lower = self.name.lower()
|
||||
self._checks = {
|
||||
SelectorType.UNIVERSAL: self._check_universal,
|
||||
SelectorType.TYPE: self._check_type,
|
||||
SelectorType.CLASS: self._check_class,
|
||||
SelectorType.ID: self._check_id,
|
||||
}
|
||||
|
||||
def check(self, node: DOMNode) -> bool | None:
|
||||
return self._checks[self.type](node)
|
||||
|
||||
def _check_universal(self, node: DOMNode) -> bool | None:
|
||||
return True
|
||||
|
||||
def _check_type(self, node: DOMNode) -> bool | None:
|
||||
if node.css_type != self._name_lower:
|
||||
return False
|
||||
if self.pseudo_classes and not node.has_psuedo_class(*self.pseudo_classes):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _check_class(self, node: DOMNode) -> bool | None:
|
||||
if not node.has_class(self._name_lower):
|
||||
return False
|
||||
if self.pseudo_classes and not node.has_psuedo_class(*self.pseudo_classes):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _check_id(self, node: DOMNode) -> bool | None:
|
||||
if not node.id == self._name_lower:
|
||||
return False
|
||||
if self.pseudo_classes and not node.has_psuedo_class(*self.pseudo_classes):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@dataclass
|
||||
class Declaration:
|
||||
|
||||
@@ -34,7 +34,7 @@ def parse_rule_set(tokens: Iterator[Token], token: Token) -> Iterable[RuleSet]:
|
||||
rule_set = RuleSet()
|
||||
|
||||
get_selector = SELECTOR_MAP.get
|
||||
combinator = CombinatorType.SAME
|
||||
combinator = CombinatorType.DESCENDENT
|
||||
selectors: list[Selector] = []
|
||||
rule_selectors: list[list[Selector]] = []
|
||||
styles_builder = StylesBuilder()
|
||||
@@ -109,6 +109,11 @@ def parse(css: str) -> Iterable[RuleSet]:
|
||||
|
||||
if __name__ == "__main__":
|
||||
test = """
|
||||
|
||||
App View {
|
||||
text: red;
|
||||
}
|
||||
|
||||
.foo.bar baz:focus, #egg .foo.baz {
|
||||
/* ignore me, I'm a comment */
|
||||
display: block;
|
||||
|
||||
@@ -250,7 +250,5 @@ if __name__ == "__main__":
|
||||
|
||||
print(styles)
|
||||
print(styles.css)
|
||||
print(dir(styles))
|
||||
print(RULE_NAMES)
|
||||
|
||||
print(styles.extract_rules((0, 1, 0)))
|
||||
|
||||
@@ -45,11 +45,13 @@ class Stylesheet:
|
||||
styles: list[tuple[Specificity3, Styles]] = []
|
||||
|
||||
for rule in self.rules:
|
||||
print(rule)
|
||||
self.apply_rule(rule, node)
|
||||
|
||||
def apply_rule(self, rule: RuleSet, node: DOMNode) -> None:
|
||||
for selector_set in rule.selector_set:
|
||||
self.check_selectors(selector_set.selectors, node)
|
||||
if self.check_selectors(selector_set.selectors, node):
|
||||
print(rule.css)
|
||||
|
||||
def check_selectors(self, selectors: list[Selector], node: DOMNode) -> bool:
|
||||
node_path = node.css_path
|
||||
@@ -60,19 +62,80 @@ class Stylesheet:
|
||||
return False
|
||||
node, siblings = node_siblings
|
||||
|
||||
for selector in selectors:
|
||||
if selector.type == SelectorType.UNIVERSAL:
|
||||
continue
|
||||
elif selector.type == SelectorType.TYPE:
|
||||
while node.css_type != selector.name:
|
||||
node_siblings = next(nodes, None)
|
||||
if node_siblings is None:
|
||||
SAME = CombinatorType.SAME
|
||||
DESCENDENT = CombinatorType.DESCENDENT
|
||||
CHILD = CombinatorType.CHILD
|
||||
|
||||
try:
|
||||
for selector in selectors:
|
||||
if selector.combinator == SAME:
|
||||
if not selector.check(node):
|
||||
return False
|
||||
node, siblings = node_siblings
|
||||
elif selector.type == SelectorType.CLASS:
|
||||
while node.css_type != selector.name:
|
||||
node_siblings = next(nodes, None)
|
||||
if node_siblings is None:
|
||||
elif selector.combinator == DESCENDENT:
|
||||
while True:
|
||||
node, siblings = next(nodes)
|
||||
if selector.check(node):
|
||||
break
|
||||
elif selector.combinator == CHILD:
|
||||
node, siblings = next(nodes)
|
||||
if not selector.check(node):
|
||||
return False
|
||||
node, siblings = node_siblings
|
||||
except StopIteration:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
class Widget(DOMNode):
|
||||
pass
|
||||
|
||||
class View(DOMNode):
|
||||
pass
|
||||
|
||||
class App(DOMNode):
|
||||
pass
|
||||
|
||||
app = App()
|
||||
main_view = View(id="main")
|
||||
help_view = View(id="help")
|
||||
app.add_child(main_view)
|
||||
app.add_child(help_view)
|
||||
|
||||
widget1 = Widget(id="widget1")
|
||||
widget2 = Widget(id="widget2")
|
||||
sidebar = Widget()
|
||||
sidebar.add_class("float")
|
||||
|
||||
helpbar = Widget()
|
||||
helpbar.add_class("float")
|
||||
|
||||
main_view.add_child(widget1)
|
||||
main_view.add_child(widget2)
|
||||
main_view.add_child(sidebar)
|
||||
|
||||
help = Widget(id="markdown")
|
||||
help_view.add_child(help)
|
||||
help_view.add_child(helpbar)
|
||||
|
||||
from rich import print
|
||||
|
||||
print(app.tree)
|
||||
|
||||
CSS = """
|
||||
|
||||
App > View {
|
||||
text: red;
|
||||
}
|
||||
|
||||
Widget.float {
|
||||
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
stylesheet = Stylesheet()
|
||||
stylesheet.parse(CSS)
|
||||
|
||||
stylesheet.apply(sidebar)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.highlighter import ReprHighlighter
|
||||
import rich.repr
|
||||
from rich.pretty import Pretty
|
||||
from rich.tree import Tree
|
||||
|
||||
from .css.styles import Styles
|
||||
from .message_pump import MessagePump
|
||||
@@ -41,21 +44,44 @@ class DOMNode(MessagePump):
|
||||
def classes(self) -> frozenset[str]:
|
||||
return frozenset(self._classes)
|
||||
|
||||
@property
|
||||
def psuedo_classes(self) -> set[str]:
|
||||
"""Get a set of all psuedo classes"""
|
||||
return set()
|
||||
|
||||
@property
|
||||
def css_type(self) -> str:
|
||||
return self.__class__.__name__.lower()
|
||||
|
||||
@property
|
||||
def css_path(self) -> list[tuple[DOMNode, list[DOMNode]]]:
|
||||
result: list[tuple[DOMNode, list[DOMNode]]] = []
|
||||
result: list[tuple[DOMNode, list[DOMNode]]] = [(self, self.children[:])]
|
||||
append = result.append
|
||||
|
||||
node: DOMNode = self
|
||||
while isinstance(node._parent, DOMNode):
|
||||
append((node, node.children[:]))
|
||||
node = node._parent
|
||||
append((node, node.children[:]))
|
||||
return result[::-1]
|
||||
|
||||
@property
|
||||
def tree(self) -> Tree:
|
||||
highlighter = ReprHighlighter()
|
||||
tree = Tree(highlighter(repr(self)))
|
||||
|
||||
def add_children(tree, node):
|
||||
for child in node.children:
|
||||
branch = tree.add(Pretty(child))
|
||||
if tree.children:
|
||||
add_children(branch, child)
|
||||
|
||||
add_children(tree, self)
|
||||
return tree
|
||||
|
||||
def add_child(self, node: DOMNode) -> None:
|
||||
self.children._append(node)
|
||||
node.set_parent(self)
|
||||
|
||||
def has_class(self, *class_names: str) -> bool:
|
||||
return self._classes.issuperset(class_names)
|
||||
|
||||
@@ -71,12 +97,7 @@ class DOMNode(MessagePump):
|
||||
"""Toggle class names"""
|
||||
self._classes.symmetric_difference_update(class_names)
|
||||
|
||||
def has_psuedo_class(self, class_name: str) -> bool:
|
||||
def has_psuedo_class(self, *class_names: str) -> bool:
|
||||
"""Check for psuedo class (such as hover, focus etc)"""
|
||||
classes = self.get_psuedo_classes()
|
||||
has_psuedo_class = class_name in classes
|
||||
return has_psuedo_class
|
||||
|
||||
def get_psuedo_classes(self) -> set[str]:
|
||||
"""Get a set of all psuedo classes"""
|
||||
return set()
|
||||
has_psuedo_classes = self.psuedo_classes.issuperset(class_names)
|
||||
return has_psuedo_classes
|
||||
|
||||
@@ -123,16 +123,16 @@ class Widget(DOMNode):
|
||||
widget (Widget): Widget
|
||||
"""
|
||||
self.app.register(widget, self)
|
||||
self.registry._append(widget)
|
||||
self.children._append(widget)
|
||||
return widget
|
||||
|
||||
def get_child(self, name: str | None = None, id: str | None = None) -> Widget:
|
||||
if name is not None:
|
||||
for widget in self.registry:
|
||||
for widget in self.children:
|
||||
if widget.name == name:
|
||||
return widget
|
||||
if id is not None:
|
||||
for widget in self.registry:
|
||||
for widget in self.children:
|
||||
if widget.id == id:
|
||||
return widget
|
||||
raise errors.MissingWidget(f"Widget named {name!r} was not found in {self}")
|
||||
|
||||
Reference in New Issue
Block a user