mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
content simplify
This commit is contained in:
@@ -614,6 +614,7 @@ class Compositor:
|
||||
)
|
||||
)
|
||||
widget.set_reactive(Widget.scroll_y, new_scroll_y)
|
||||
widget.set_reactive(Widget.scroll_target_y, new_scroll_y)
|
||||
widget.vertical_scrollbar._reactive_position = new_scroll_y
|
||||
|
||||
if visible_only:
|
||||
|
||||
@@ -114,3 +114,30 @@ class TrackedSlugs:
|
||||
if used:
|
||||
slugged = f"{slugged}-{used}"
|
||||
return slugged
|
||||
|
||||
|
||||
VALID_ID_CHARACTERS = frozenset("abcdefghijklmnopqrstuvwxyz0123456789-")
|
||||
|
||||
|
||||
def slug_for_tcss_id(text: str) -> str:
|
||||
"""Produce a slug usable as a TCSS id from the given text.
|
||||
|
||||
Args:
|
||||
text: Text.
|
||||
|
||||
Returns:
|
||||
A slugified version of text suitable for use as a TCSS id.
|
||||
"""
|
||||
slug = "".join(
|
||||
(
|
||||
character
|
||||
if character in VALID_ID_CHARACTERS
|
||||
else ord(character).__format__("x")
|
||||
)
|
||||
for character in text
|
||||
)
|
||||
if not slug:
|
||||
return "-"
|
||||
if slug[0].isdecimal():
|
||||
return f"-{slug}"
|
||||
return slug
|
||||
|
||||
@@ -395,6 +395,34 @@ class Content(Visual):
|
||||
text_append(end)
|
||||
return cls("".join(text), spans)
|
||||
|
||||
def simplify(self) -> Content:
|
||||
"""Simplify spans in place.
|
||||
|
||||
This joins contiguous spans together which can produce faster renders.
|
||||
|
||||
Note that this is only typically worth it if you have appended a large number of Content instances together,
|
||||
and it only needs to be done once.
|
||||
|
||||
Returns:
|
||||
Self.
|
||||
"""
|
||||
spans = self.spans
|
||||
if not spans:
|
||||
return self
|
||||
last_span = Span(0, 0, Style())
|
||||
new_spans: list[Span] = []
|
||||
changed: bool = False
|
||||
for span in self._spans:
|
||||
if span.start == last_span.end and span.style == last_span.style:
|
||||
last_span = new_spans[-1] = Span(last_span.start, span.end, span.style)
|
||||
changed = True
|
||||
else:
|
||||
new_spans.append(span)
|
||||
last_span = span
|
||||
if changed:
|
||||
self._spans[:] = new_spans
|
||||
return self
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
"""Compares text only, so that markup doesn't effect sorting."""
|
||||
if isinstance(other, str):
|
||||
@@ -528,7 +556,6 @@ class Content(Visual):
|
||||
return None
|
||||
|
||||
for y, line in enumerate(self.split(allow_blank=True)):
|
||||
|
||||
if post_style is not None:
|
||||
line = line.stylize(post_style)
|
||||
|
||||
@@ -1201,6 +1228,11 @@ class Content(Visual):
|
||||
]
|
||||
return segments
|
||||
|
||||
def __rich__(self):
|
||||
from rich.segment import Segments
|
||||
|
||||
return Segments(self.render_segments(Style(), "\n"))
|
||||
|
||||
def _divide_spans(self, offsets: tuple[int, ...]) -> list[tuple[Span, int, int]]:
|
||||
"""Divide content from a list of offset to cut.
|
||||
|
||||
@@ -1568,7 +1600,6 @@ class _FormattedLine:
|
||||
def _apply_link_style(
|
||||
self, link_style: RichStyle, segments: list[Segment]
|
||||
) -> list[Segment]:
|
||||
|
||||
_Segment = Segment
|
||||
segments = [
|
||||
_Segment(
|
||||
|
||||
@@ -5,12 +5,37 @@ Descriptors to define properties on your widget, screen, or App.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Generic, overload
|
||||
from typing import Generic, TypeVar, overload
|
||||
|
||||
from textual.css.query import NoMatches, QueryType, WrongType
|
||||
from textual.dom import DOMNode
|
||||
from textual.widget import Widget
|
||||
|
||||
AppType = TypeVar("AppType", bound="App")
|
||||
|
||||
|
||||
class app(Generic[AppType]):
|
||||
"""A typed getter for the app.
|
||||
|
||||
Example:
|
||||
```python
|
||||
class MyWidget(Widget):
|
||||
app = getters.app(MyApp)
|
||||
```
|
||||
|
||||
|
||||
Args:
|
||||
Generic (_type_): _description_
|
||||
"""
|
||||
|
||||
def __init__(self, app_type: type[AppType]) -> None:
|
||||
self._app_type = app_type
|
||||
|
||||
def __get__(self, obj: DOMNode, obj_type: type[DOMNode]) -> AppType:
|
||||
app = obj.app
|
||||
assert isinstance(app, self._app_type)
|
||||
return app
|
||||
|
||||
|
||||
class query_one(Generic[QueryType]):
|
||||
"""Create a query one property.
|
||||
|
||||
@@ -196,6 +196,7 @@ class Visual(ABC):
|
||||
height: int | None,
|
||||
style: Style,
|
||||
*,
|
||||
apply_selection: bool = True,
|
||||
pad: bool = False,
|
||||
post_style: Style | None = None,
|
||||
) -> list[Strip]:
|
||||
@@ -207,6 +208,7 @@ class Visual(ABC):
|
||||
width: Desired width (in cells).
|
||||
height: Desired height (in lines) or `None` for no limit.
|
||||
style: A (Visual) Style instance.
|
||||
apply_selection: Automatically apply selection styles?
|
||||
pad: Pad to desired width?
|
||||
post_style: Optional Style to apply to strips after rendering.
|
||||
|
||||
@@ -229,7 +231,7 @@ class Visual(ABC):
|
||||
RenderOptions(
|
||||
widget._get_style,
|
||||
widget.styles,
|
||||
selection,
|
||||
selection if apply_selection else None,
|
||||
selection_style,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ from markdown_it.token import Token
|
||||
from rich.text import Text
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
from textual._slug import TrackedSlugs, slug
|
||||
from textual._slug import TrackedSlugs, slug_for_tcss_id
|
||||
from textual.app import ComposeResult
|
||||
from textual.await_complete import AwaitComplete
|
||||
from textual.containers import Horizontal, Vertical, VerticalScroll
|
||||
@@ -1271,7 +1271,9 @@ class Markdown(Widget):
|
||||
elif token_type.endswith("_close"):
|
||||
block = stack.pop()
|
||||
if token.type == "heading_close":
|
||||
block.id = f"heading-{slug(block._content.plain)}-{id(block)}"
|
||||
block.id = (
|
||||
f"heading-{slug_for_tcss_id(block._content.plain)}-{id(block)}"
|
||||
)
|
||||
if stack:
|
||||
stack[-1]._blocks.append(block)
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from textual._slug import TrackedSlugs, slug
|
||||
from textual._slug import TrackedSlugs, slug_for_tcss_id
|
||||
|
||||
|
||||
@pytest.mark.xdist_group("group1")
|
||||
@@ -31,7 +31,7 @@ from textual._slug import TrackedSlugs, slug
|
||||
)
|
||||
def test_simple_slug(text: str, expected: str) -> None:
|
||||
"""The simple slug function should produce the expected slug."""
|
||||
assert slug(text) == expected
|
||||
assert slug_for_tcss_id(text) == expected
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
Reference in New Issue
Block a user