mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
extend trips to line API
This commit is contained in:
@@ -26,7 +26,6 @@ from . import errors
|
||||
from ._cells import cell_len
|
||||
from ._loop import loop_last
|
||||
from .strip import Strip
|
||||
from ._types import Strips
|
||||
from ._typing import TypeAlias
|
||||
from .geometry import NULL_OFFSET, Offset, Region, Size
|
||||
|
||||
@@ -67,7 +66,7 @@ CompositorMap: TypeAlias = "dict[Widget, MapGeometry]"
|
||||
class LayoutUpdate:
|
||||
"""A renderable containing the result of a render for a given region."""
|
||||
|
||||
def __init__(self, strips: Strips, region: Region) -> None:
|
||||
def __init__(self, strips: list[Strip], region: Region) -> None:
|
||||
self.strips = strips
|
||||
self.region = region
|
||||
|
||||
@@ -634,7 +633,7 @@ class Compositor:
|
||||
|
||||
def _get_renders(
|
||||
self, crop: Region | None = None
|
||||
) -> Iterable[tuple[Region, Region, Strips]]:
|
||||
) -> Iterable[tuple[Region, Region, list[Strip]]]:
|
||||
"""Get rendered widgets (lists of segments) in the composition.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -10,7 +10,6 @@ from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
from ._cells import cell_len
|
||||
from ._types import Strips
|
||||
from .css.types import AlignHorizontal, AlignVertical
|
||||
from .geometry import Size
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ from ._border import get_box, render_row
|
||||
from ._filter import LineFilter
|
||||
from ._opacity import _apply_opacity
|
||||
from ._segment_tools import line_crop, line_pad, line_trim
|
||||
from ._types import Strips
|
||||
from ._typing import TypeAlias
|
||||
from .color import Color
|
||||
from .geometry import Region, Size, Spacing
|
||||
|
||||
@@ -29,6 +29,5 @@ class EventTarget(Protocol):
|
||||
...
|
||||
|
||||
|
||||
Strips = List["Strip"]
|
||||
SegmentLines = List[List["Segment"]]
|
||||
CallbackType = Union[Callable[[], Awaitable[None]], Callable[[], None]]
|
||||
|
||||
@@ -42,6 +42,32 @@ class Strip:
|
||||
yield self._segments
|
||||
yield self.cell_length
|
||||
|
||||
@classmethod
|
||||
def blank(cls, cell_length: int, style: Style | None) -> Strip:
|
||||
"""Create a blank strip.
|
||||
|
||||
Args:
|
||||
cell_length (int): Desired cell length.
|
||||
style (Style | None): Style of blank.
|
||||
|
||||
Returns:
|
||||
Strip: New strip.
|
||||
"""
|
||||
return cls([Segment(" " * cell_length, style)], cell_length)
|
||||
|
||||
@classmethod
|
||||
def from_lines(cls, lines: list[list[Segment]], cell_length: int) -> list[Strip]:
|
||||
"""Convert lines (lists of segments) to a list of Strips.
|
||||
|
||||
Args:
|
||||
lines (list[list[Segment]]): List of lines, where a line is a list of segments.
|
||||
cell_length (int): Cell length of lines (must be same).
|
||||
|
||||
Returns:
|
||||
list[Strip]: List of strips.
|
||||
"""
|
||||
return [cls(segments, cell_length) for segments in lines]
|
||||
|
||||
@property
|
||||
def cell_length(self) -> int:
|
||||
"""Get the number of cells required to render this object."""
|
||||
@@ -194,6 +220,8 @@ class Strip:
|
||||
Returns:
|
||||
Strip: A new Strip.
|
||||
"""
|
||||
if start == 0 and end == self.cell_length:
|
||||
return self
|
||||
cache_key = (start, end)
|
||||
cached = self._crop_cache.get(cache_key)
|
||||
if cached is not None:
|
||||
|
||||
@@ -155,7 +155,7 @@ class RenderCache(NamedTuple):
|
||||
"""Stores results of a previous render."""
|
||||
|
||||
size: Size
|
||||
lines: list[list[Segment]]
|
||||
lines: list[Strip]
|
||||
|
||||
|
||||
class WidgetError(Exception):
|
||||
@@ -2097,11 +2097,11 @@ class Widget(DOMNode):
|
||||
align_vertical,
|
||||
)
|
||||
)
|
||||
|
||||
self._render_cache = RenderCache(self.size, lines)
|
||||
strips = [Strip(line, width) for line in lines]
|
||||
self._render_cache = RenderCache(self.size, strips)
|
||||
self._dirty_regions.clear()
|
||||
|
||||
def render_line(self, y: int) -> list[Segment]:
|
||||
def render_line(self, y: int) -> Strip:
|
||||
"""Render a line of content.
|
||||
|
||||
Args:
|
||||
@@ -2115,7 +2115,7 @@ class Widget(DOMNode):
|
||||
try:
|
||||
line = self._render_cache.lines[y]
|
||||
except IndexError:
|
||||
line = [Segment(" " * self.size.width, self.rich_style)]
|
||||
line = Strip.blank(self.size.width, self.rich_style)
|
||||
return line
|
||||
|
||||
def render_lines(self, crop: Region) -> list[Strip]:
|
||||
|
||||
@@ -19,6 +19,7 @@ from ..geometry import Region, Size, Spacing, clamp
|
||||
from ..reactive import Reactive
|
||||
from ..render import measure
|
||||
from ..scroll_view import ScrollView
|
||||
from ..strip import Strip
|
||||
from .._typing import Literal
|
||||
|
||||
CursorType = Literal["cell", "row", "column"]
|
||||
@@ -214,9 +215,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
tuple[int, int, Style, bool, bool], SegmentLines
|
||||
]
|
||||
self._cell_render_cache = LRUCache(10000)
|
||||
self._line_cache: LRUCache[
|
||||
tuple[int, int, int, int, int, int, Style], list[Segment]
|
||||
]
|
||||
self._line_cache: LRUCache[tuple[int, int, int, int, int, int, Style], Strip]
|
||||
self._line_cache = LRUCache(1000)
|
||||
|
||||
self._line_no = 0
|
||||
@@ -567,9 +566,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
raise LookupError("Y coord {y!r} is greater than total height")
|
||||
return self._y_offsets[y]
|
||||
|
||||
def _render_line(
|
||||
self, y: int, x1: int, x2: int, base_style: Style
|
||||
) -> list[Segment]:
|
||||
def _render_line(self, y: int, x1: int, x2: int, base_style: Style) -> Strip:
|
||||
"""Render a line in to a list of segments.
|
||||
|
||||
Args:
|
||||
@@ -587,7 +584,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
try:
|
||||
row_index, line_no = self._get_offsets(y)
|
||||
except LookupError:
|
||||
return [Segment(" " * width, base_style)]
|
||||
return Strip.blank(width, base_style)
|
||||
cursor_column = (
|
||||
self.cursor_column
|
||||
if (self.show_cursor and self.cursor_row == row_index)
|
||||
@@ -617,10 +614,11 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
segments = Segment.adjust_line_length(segments, width, style=base_style)
|
||||
simplified_segments = list(Segment.simplify(segments))
|
||||
|
||||
self._line_cache[cache_key] = simplified_segments
|
||||
return segments
|
||||
strip = Strip(simplified_segments, width)
|
||||
self._line_cache[cache_key] = strip
|
||||
return strip
|
||||
|
||||
def render_line(self, y: int) -> list[Segment]:
|
||||
def render_line(self, y: int) -> Strip:
|
||||
width, height = self.size
|
||||
scroll_x, scroll_y = self.scroll_offset
|
||||
fixed_top_row_count = sum(
|
||||
|
||||
@@ -15,7 +15,7 @@ from ..geometry import Size, Region
|
||||
from ..scroll_view import ScrollView
|
||||
from .._cache import LRUCache
|
||||
from .._segment_tools import line_crop
|
||||
from .._types import Strips
|
||||
from ..strip import Strip
|
||||
|
||||
|
||||
class TextLog(ScrollView, can_focus=True):
|
||||
@@ -48,8 +48,8 @@ class TextLog(ScrollView, can_focus=True):
|
||||
super().__init__(name=name, id=id, classes=classes)
|
||||
self.max_lines = max_lines
|
||||
self._start_line: int = 0
|
||||
self.lines: list[list[Segment]] = []
|
||||
self._line_cache: LRUCache[tuple[int, int, int, int], list[Segment]]
|
||||
self.lines: list[Strip] = []
|
||||
self._line_cache: LRUCache[tuple[int, int, int, int], Strip]
|
||||
self._line_cache = LRUCache(1024)
|
||||
self.max_width: int = 0
|
||||
self.min_width = min_width
|
||||
@@ -120,7 +120,8 @@ class TextLog(ScrollView, can_focus=True):
|
||||
self.max_width,
|
||||
max(sum(segment.cell_length for segment in _line) for _line in lines),
|
||||
)
|
||||
self.lines.extend(lines)
|
||||
strips = Strip.from_lines(lines, render_width)
|
||||
self.lines.extend(strips)
|
||||
|
||||
if self.max_lines is not None and len(self.lines) > self.max_lines:
|
||||
self._start_line += len(self.lines) - self.max_lines
|
||||
@@ -138,13 +139,13 @@ class TextLog(ScrollView, can_focus=True):
|
||||
self.virtual_size = Size(self.max_width, len(self.lines))
|
||||
self.refresh()
|
||||
|
||||
def render_line(self, y: int) -> list[Segment]:
|
||||
def render_line(self, y: int) -> Strip:
|
||||
scroll_x, scroll_y = self.scroll_offset
|
||||
line = self._render_line(scroll_y + y, scroll_x, self.size.width)
|
||||
line = list(Segment.apply_style(line, self.rich_style))
|
||||
return line
|
||||
strip = Strip(Segment.apply_style(line, self.rich_style), self.size.width)
|
||||
return strip
|
||||
|
||||
def render_lines(self, crop: Region) -> Strips:
|
||||
def render_lines(self, crop: Region) -> list[Strip]:
|
||||
"""Render the widget in to lines.
|
||||
|
||||
Args:
|
||||
@@ -156,19 +157,20 @@ class TextLog(ScrollView, can_focus=True):
|
||||
lines = self._styles_cache.render_widget(self, crop)
|
||||
return lines
|
||||
|
||||
def _render_line(self, y: int, scroll_x: int, width: int) -> list[Segment]:
|
||||
def _render_line(self, y: int, scroll_x: int, width: int) -> Strip:
|
||||
|
||||
if y >= len(self.lines):
|
||||
return [Segment(" " * width, self.rich_style)]
|
||||
return Strip.blank(width, self.rich_style)
|
||||
|
||||
key = (y + self._start_line, scroll_x, width, self.max_width)
|
||||
if key in self._line_cache:
|
||||
return self._line_cache[key]
|
||||
|
||||
line = self.lines[y]
|
||||
line = Segment.adjust_line_length(
|
||||
line, max(self.max_width, width), self.rich_style
|
||||
line = (
|
||||
self.lines[y]
|
||||
.adjust_cell_length(max(self.max_width, width), self.rich_style)
|
||||
.crop(scroll_x, scroll_x + width)
|
||||
)
|
||||
line = line_crop(line, scroll_x, scroll_x + width, self.max_width)
|
||||
|
||||
self._line_cache[key] = line
|
||||
return line
|
||||
|
||||
@@ -5,22 +5,21 @@ from typing import ClassVar, Generic, NewType, TypeVar
|
||||
|
||||
import rich.repr
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style, NULL_STYLE
|
||||
from rich.style import NULL_STYLE, Style
|
||||
from rich.text import Text, TextType
|
||||
|
||||
|
||||
from ..binding import Binding
|
||||
from ..geometry import clamp, Region, Size
|
||||
from .._loop import loop_last
|
||||
from .. import events
|
||||
from .._cache import LRUCache
|
||||
from ..message import Message
|
||||
from ..reactive import reactive, var
|
||||
from .._loop import loop_last
|
||||
from .._segment_tools import line_crop, line_pad
|
||||
from .._types import MessageTarget
|
||||
from .._typing import TypeAlias
|
||||
from ..binding import Binding
|
||||
from ..geometry import Region, Size, clamp
|
||||
from ..message import Message
|
||||
from ..reactive import reactive, var
|
||||
from ..scroll_view import ScrollView
|
||||
|
||||
from .. import events
|
||||
from ..strip import Strip
|
||||
|
||||
NodeID = NewType("NodeID", int)
|
||||
TreeDataType = TypeVar("TreeDataType")
|
||||
@@ -365,7 +364,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
self._current_id = 0
|
||||
self.root = self._add_node(None, text_label, data)
|
||||
|
||||
self._line_cache: LRUCache[LineCacheKey, list[Segment]] = LRUCache(1024)
|
||||
self._line_cache: LRUCache[LineCacheKey, Strip] = LRUCache(1024)
|
||||
self._tree_lines_cached: list[_TreeLine] | None = None
|
||||
self._cursor_node: TreeNode[TreeDataType] | None = None
|
||||
|
||||
@@ -666,7 +665,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
self.cursor_line = -1
|
||||
self.refresh()
|
||||
|
||||
def render_line(self, y: int) -> list[Segment]:
|
||||
def render_line(self, y: int) -> Strip:
|
||||
width = self.size.width
|
||||
scroll_x, scroll_y = self.scroll_offset
|
||||
style = self.rich_style
|
||||
@@ -677,14 +676,12 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
style,
|
||||
)
|
||||
|
||||
def _render_line(
|
||||
self, y: int, x1: int, x2: int, base_style: Style
|
||||
) -> list[Segment]:
|
||||
def _render_line(self, y: int, x1: int, x2: int, base_style: Style) -> Strip:
|
||||
tree_lines = self._tree_lines
|
||||
width = self.size.width
|
||||
|
||||
if y >= len(tree_lines):
|
||||
return [Segment(" " * width, base_style)]
|
||||
return Strip.blank(width, base_style)
|
||||
|
||||
line = tree_lines[y]
|
||||
|
||||
@@ -699,7 +696,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
tuple(node._updates for node in line.path),
|
||||
)
|
||||
if cache_key in self._line_cache:
|
||||
segments = self._line_cache[cache_key]
|
||||
strip = self._line_cache[cache_key]
|
||||
else:
|
||||
base_guide_style = self.get_component_rich_style(
|
||||
"tree--guides", partial=True
|
||||
@@ -785,11 +782,10 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
segments = list(guides.render(self.app.console))
|
||||
pad_width = max(self.virtual_size.width, width)
|
||||
segments = line_pad(segments, 0, pad_width - guides.cell_len, line_style)
|
||||
self._line_cache[cache_key] = segments
|
||||
strip = self._line_cache[cache_key] = Strip(segments)
|
||||
|
||||
segments = line_crop(segments, x1, x2, Segment.get_line_length(segments))
|
||||
|
||||
return segments
|
||||
strip = strip.crop(x1, x2)
|
||||
return strip
|
||||
|
||||
def _on_resize(self, event: events.Resize) -> None:
|
||||
self._line_cache.grow(event.size.height)
|
||||
|
||||
@@ -4,14 +4,13 @@ from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
from textual._styles_cache import StylesCache
|
||||
from textual._types import Strips
|
||||
from textual.color import Color
|
||||
from textual.css.styles import Styles
|
||||
from textual.geometry import Region, Size
|
||||
from textual.strip import Strip
|
||||
|
||||
|
||||
def _extract_content(lines: Strips):
|
||||
def _extract_content(lines: list[list[Segment]]):
|
||||
"""Extract the text content from lines."""
|
||||
content = ["".join(segment.text for segment in line) for line in lines]
|
||||
return content
|
||||
|
||||
Reference in New Issue
Block a user