extend trips to line API

This commit is contained in:
Will McGugan
2022-12-29 10:13:37 +00:00
parent cce244ddd0
commit 51c7fef2e1
10 changed files with 76 additions and 57 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -29,6 +29,5 @@ class EventTarget(Protocol):
...
Strips = List["Strip"]
SegmentLines = List[List["Segment"]]
CallbackType = Union[Callable[[], Awaitable[None]], Callable[[], None]]

View File

@@ -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:

View File

@@ -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]:

View File

@@ -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(

View File

@@ -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

View File

@@ -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)

View File

@@ -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