Skip to content

Commit a48a558

Browse files
authored
Merge pull request #6180 from Textualize/strip-cache
Improve caching of strips
2 parents eed9aa0 + 65d6a1a commit a48a558

File tree

5 files changed

+38
-22
lines changed

5 files changed

+38
-22
lines changed

src/textual/_compositor.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def render_segments(self, console: Console) -> str:
7575
class LayoutUpdate(CompositorUpdate):
7676
"""A renderable containing the result of a render for a given region."""
7777

78-
def __init__(self, strips: list[Strip], region: Region) -> None:
78+
def __init__(self, strips: list[Iterable[Strip]], region: Region) -> None:
7979
self.strips = strips
8080
self.region = region
8181

@@ -87,7 +87,8 @@ def __rich_console__(
8787
move_to = Control.move_to
8888
for last, (y, line) in loop_last(enumerate(self.strips, self.region.y)):
8989
yield move_to(x, y).segment
90-
yield from line
90+
for strip in line:
91+
yield from strip
9192
if not last:
9293
yield new_line
9394

@@ -102,11 +103,12 @@ def render_segments(self, console: Console) -> str:
102103
"""
103104
sequences: list[str] = []
104105
append = sequences.append
106+
extend = sequences.extend
105107
x = self.region.x
106108
move_to = Control.move_to
107109
for last, (y, line) in loop_last(enumerate(self.strips, self.region.y)):
108110
append(move_to(x, y).segment.text)
109-
append(line.render(console))
111+
extend([strip.render(console) for strip in line])
110112
if not last:
111113
append("\n")
112114
return "".join(sequences)
@@ -239,7 +241,6 @@ def render_segments(self, console: Console) -> str:
239241
Returns:
240242
Raw data with escape sequences.
241243
"""
242-
243244
sequences: list[str] = []
244245
append = sequences.append
245246

@@ -613,8 +614,9 @@ def add_widget(
613614
- widget.scrollbar_size_horizontal
614615
)
615616
)
616-
widget.set_reactive(Widget.scroll_y, new_scroll_y)
617-
widget.set_reactive(Widget.scroll_target_y, new_scroll_y)
617+
capped_scroll_y = widget.validate_scroll_y(new_scroll_y)
618+
widget.set_reactive(Widget.scroll_y, capped_scroll_y)
619+
widget.set_reactive(Widget.scroll_target_y, capped_scroll_y)
618620
widget.vertical_scrollbar._reactive_position = new_scroll_y
619621

620622
if visible_only:
@@ -1132,14 +1134,15 @@ def render_full_update(self, simplify: bool = False) -> LayoutUpdate:
11321134
self._dirty_regions.clear()
11331135
crop = screen_region
11341136
chops = self._render_chops(crop, lambda y: True)
1137+
render_strips: list[Iterable[Strip]]
11351138
if simplify:
11361139
# Simplify is done when exporting to SVG
11371140
# It doesn't make things faster
11381141
render_strips = [
1139-
Strip.join(chop.values()).simplify().discard_meta() for chop in chops
1142+
[Strip.join(chop.values()).simplify().discard_meta()] for chop in chops
11401143
]
11411144
else:
1142-
render_strips = [Strip.join(chop.values()) for chop in chops]
1145+
render_strips = [chop.values() for chop in chops]
11431146

11441147
return LayoutUpdate(render_strips, screen_region)
11451148

@@ -1182,7 +1185,7 @@ def _render_chops(
11821185
self,
11831186
crop: Region,
11841187
is_rendered_line: Callable[[int], bool],
1185-
) -> Sequence[Mapping[int, Strip | None]]:
1188+
) -> Sequence[Mapping[int, Strip]]:
11861189
"""Render update 'chops'.
11871190
11881191
Args:
@@ -1221,8 +1224,7 @@ def _render_chops(
12211224
for cut, strip in zip(final_cuts, cut_strips):
12221225
if get_chops_line(cut) is None:
12231226
chops_line[cut] = strip
1224-
1225-
return chops
1227+
return cast("Sequence[Mapping[int, Strip]]", chops)
12261228

12271229
def __rich__(self) -> StripRenderable:
12281230
return StripRenderable(self.render_strips())

src/textual/app.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4576,9 +4576,11 @@ def suspend(self) -> Iterator[None]:
45764576
# app, and we don't want to have the driver auto-restart
45774577
# application mode when the application comes back to the
45784578
# foreground, in this context.
4579-
with self._driver.no_automatic_restart(), redirect_stdout(
4580-
sys.__stdout__
4581-
), redirect_stderr(sys.__stderr__):
4579+
with (
4580+
self._driver.no_automatic_restart(),
4581+
redirect_stdout(sys.__stdout__),
4582+
redirect_stderr(sys.__stderr__),
4583+
):
45824584
yield
45834585
# We're done with the dev's code so resume application mode.
45844586
self._driver.resume_application_mode()

src/textual/content.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,8 +395,8 @@ def assemble(
395395
def simplify(self) -> Content:
396396
"""Simplify spans by joining contiguous spans together.
397397
398-
This can produce faster renders but typically only worth it if you have appended a
399-
large number of Content instances together.
398+
This may produce faster renders if you have concatenated a large number of small pieces
399+
of content with repeating styles.
400400
401401
Note that this modifies the Content instance in-place, which might appear
402402
to violate the immutability constraints, but it will not change the rendered output,

src/textual/strip.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -290,14 +290,21 @@ def join(cls, strips: Iterable[Strip | None]) -> Strip:
290290
Returns:
291291
A new combined strip.
292292
"""
293-
join_strips = [strip for strip in strips if strip is not None]
293+
join_strips = [
294+
strip for strip in strips if strip is not None and strip.cell_count
295+
]
294296
segments = [segment for strip in join_strips for segment in strip._segments]
295297
cell_length: int | None = None
296298
if any([strip._cell_length is None for strip in join_strips]):
297299
cell_length = None
298300
else:
299301
cell_length = sum([strip._cell_length or 0 for strip in join_strips])
300-
return cls(segments, cell_length)
302+
joined_strip = cls(segments, cell_length)
303+
if all(strip._render_cache is not None for strip in join_strips):
304+
joined_strip._render_cache = "".join(
305+
[strip._render_cache for strip in join_strips]
306+
)
307+
return joined_strip
301308

302309
def __add__(self, other: Strip) -> Strip:
303310
return Strip.join([self, other])
@@ -579,9 +586,7 @@ def divide(self, cuts: Iterable[int]) -> Sequence[Strip]:
579586
cell_length = self.cell_length
580587
cuts = [cut for cut in cuts if cut <= cell_length]
581588
cache_key = tuple(cuts)
582-
cached = self._divide_cache.get(cache_key)
583-
584-
if cached is not None:
589+
if (cached := self._divide_cache.get(cache_key)) is not None:
585590
return cached
586591

587592
strips: list[Strip]
@@ -721,6 +726,7 @@ def render(self, console: Console) -> str:
721726
for text, style, _ in self._segments
722727
]
723728
)
729+
724730
return self._render_cache
725731

726732
def crop_pad(self, cell_length: int, left: int, right: int, style: Style) -> Strip:
@@ -805,5 +811,6 @@ def apply_offsets(self, x: int, y: int) -> Strip:
805811
)
806812
x += len(segment.text)
807813
strip = Strip(strip_segments, self._cell_length)
814+
strip._render_cache = self._render_cache
808815
self._offsets_cache[cache_key] = strip
809816
return strip

src/textual/widgets/_text_area.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1485,7 +1485,10 @@ def _render_line(self, y: int) -> Strip:
14851485
line_style = theme.base_style if theme else None
14861486

14871487
text_strip = text_strip.extend_cell_length(target_width, line_style)
1488-
strip = Strip.join([Strip(gutter, cell_length=gutter_width), text_strip])
1488+
if gutter:
1489+
strip = Strip.join([Strip(gutter, cell_length=gutter_width), text_strip])
1490+
else:
1491+
strip = text_strip
14891492

14901493
return strip.apply_style(base_style)
14911494

@@ -2343,6 +2346,8 @@ def insert(
23432346
Returns:
23442347
An `EditResult` containing information about the edit.
23452348
"""
2349+
if len(text) > 1:
2350+
self._restart_blink()
23462351
if location is None:
23472352
location = self.cursor_location
23482353
return self.edit(Edit(text, location, location, maintain_selection_offset))

0 commit comments

Comments
 (0)