From e66b2b532bedbfb7f1aa232358adaa50ad33125c Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 7 Oct 2026 19:17:32 -0400 Subject: [PATCH] Retain matching prepared wrapping and settle scrollbar geometry --- src/textual/widgets/_prepared_text_area.py | 14 +++++++++++++- tests/text_area/test_prepared_document.py | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/textual/widgets/_prepared_text_area.py b/src/textual/widgets/_prepared_text_area.py index edf56a4d4b..8d1f2c6211 100644 --- a/src/textual/widgets/_prepared_text_area.py +++ b/src/textual/widgets/_prepared_text_area.py @@ -76,6 +76,17 @@ async def _prepare_wrapping(self) -> None: while self.is_attached and not self._pruning: document = self.document width, tabs = self.wrap_width, self.indent_width + wrapped = self.wrapped_document + if (wrapped.document is document and + (wrapped._width, wrapped._tab_width) == (width, tabs)): + # Native edits update this same view incrementally. A resize + # with unchanged wrapping inputs needs scrollbar/size refresh, + # not another detached document and navigator. + self._refresh_size() + if (width, tabs) == (self.wrap_width, self.indent_width): + return + # Scrollbar publication can itself change the wrapping width. + continue prepared = await self._prepare_document(document, width, tabs) if not self.is_attached or self._pruning: return @@ -97,4 +108,5 @@ async def _prepare_wrapping(self) -> None: offset = prepared.location_to_offset(reader) self.scroll_to(x=offset.x, y=offset.y, animate=False, immediate=True) self.record_cursor_width() - return + # Admit the resulting scrollbar geometry through the same matching + # resource decision before declaring this worker complete. diff --git a/tests/text_area/test_prepared_document.py b/tests/text_area/test_prepared_document.py index 1b42161954..0da6d47ea3 100644 --- a/tests/text_area/test_prepared_document.py +++ b/tests/text_area/test_prepared_document.py @@ -32,10 +32,25 @@ async def test_prepared_load_keeps_selection_and_source_reader_on_resize(): wrapped = area.wrapped_document await area.load_text_prepared(text).wait() assert area.document is document and area.wrapped_document is wrapped + navigator = area.navigator + for _ in range(3): + area._rewrap_and_refresh_virtual_size() + await settle(area, pilot) + assert area.wrapped_document is wrapped + assert area.navigator is navigator + assert (wrapped._width, wrapped._tab_width) == (area.wrap_width, area.indent_width) await pilot.resize_terminal(44, 18) await settle(area, pilot) assert area.document is document and area.selected_text == selected assert area.wrapped_document.offset_to_location(area.scroll_offset)[0] == reader[0] + resized = area.wrapped_document + assert resized is not wrapped + assert (resized._width, resized._tab_width) == (area.wrap_width, area.indent_width) + area.indent_width = 8 + await settle(area, pilot) + assert area.wrapped_document is not resized + assert area.wrapped_document.document is document + assert (area.wrapped_document._width, area.wrapped_document._tab_width) == (area.wrap_width, 8) async def test_new_prepared_source_replaces_only_its_original_native_load():