1616
1717from textual ._text_area_theme import TextAreaTheme
1818from textual ._tree_sitter import TREE_SITTER , get_language
19+ from textual .cache import LRUCache
1920from textual .color import Color
2021from textual .document ._document import (
2122 Document ,
@@ -512,6 +513,8 @@ def __init__(
512513 self .set_reactive (TextArea .line_number_start , line_number_start )
513514 self .set_reactive (TextArea .highlight_cursor_line , highlight_cursor_line )
514515
516+ self ._line_cache : LRUCache [tuple , Strip ] = LRUCache (1024 )
517+
515518 self ._set_document (text , language )
516519
517520 self .language = language
@@ -611,6 +614,9 @@ def _get_builtin_highlight_query(language_name: str) -> str:
611614
612615 return highlight_query
613616
617+ def notify_style_update (self ) -> None :
618+ self ._line_cache .clear ()
619+
614620 def check_consume_key (self , key : str , character : str | None = None ) -> bool :
615621 """Check if the widget may consume the given key.
616622
@@ -634,6 +640,7 @@ def check_consume_key(self, key: str, character: str | None = None) -> bool:
634640
635641 def _build_highlight_map (self ) -> None :
636642 """Query the tree for ranges to highlights, and update the internal highlights mapping."""
643+ self ._line_cache .clear ()
637644 highlights = self ._highlights
638645 highlights .clear ()
639646 if not self ._highlight_query :
@@ -838,6 +845,8 @@ def _set_theme(self, theme: str) -> None:
838845 if background :
839846 self .styles .background = Color .from_rich_color (background )
840847
848+ theme_object .apply_css (self )
849+
841850 @property
842851 def available_themes (self ) -> set [str ]:
843852 """A list of the names of the themes available to the `TextArea`.
@@ -1108,15 +1117,42 @@ def get_line(self, line_index: int) -> Text:
11081117 line_string = self .document .get_line (line_index )
11091118 return Text (line_string , end = "" )
11101119
1111- def render_lines (self , crop : Region ) -> list [Strip ]:
1112- theme = self ._theme
1113- if theme :
1114- theme .apply_css (self )
1115- return super ().render_lines (crop )
1116-
11171120 def render_line (self , y : int ) -> Strip :
11181121 """Render a single line of the TextArea. Called by Textual.
11191122
1123+ Args:
1124+ y: Y Coordinate of line relative to the widget region.
1125+
1126+ Returns:
1127+ A rendered line.
1128+ """
1129+ scroll_x , scroll_y = self .scroll_offset
1130+ absolute_y = scroll_y + y
1131+ selection = self .selection
1132+ cache_key = (
1133+ self .size ,
1134+ scroll_x ,
1135+ absolute_y ,
1136+ (
1137+ selection
1138+ if selection .contains_line (absolute_y )
1139+ else selection .end [0 ] == absolute_y
1140+ ),
1141+ self ._cursor_visible ,
1142+ self .cursor_blink ,
1143+ self .theme ,
1144+ self ._matching_bracket_location ,
1145+ self .match_cursor_bracket ,
1146+ )
1147+ if (cached_line := self ._line_cache .get (cache_key )) is not None :
1148+ return cached_line
1149+ line = self ._render_line (y )
1150+ self ._line_cache [cache_key ] = line
1151+ return line
1152+
1153+ def _render_line (self , y : int ) -> Strip :
1154+ """Render a single line of the TextArea. Called by Textual.
1155+
11201156 Args:
11211157 y: Y Coordinate of line relative to the widget region.
11221158
@@ -1310,7 +1346,10 @@ def render_line(self, y: int) -> Strip:
13101346
13111347 # Crop the line to show only the visible part (some may be scrolled out of view)
13121348 console = self .app .console
1313- text_strip = Strip (console .render (line ), cell_length = line .cell_len )
1349+ text_strip = Strip (
1350+ console .render (line , options = console .options .update_width (line .cell_len )),
1351+ cell_length = line .cell_len ,
1352+ )
13141353 if not self .soft_wrap :
13151354 text_strip = text_strip .crop (scroll_x , scroll_x + virtual_width )
13161355
0 commit comments