1818from textual ._tree_sitter import TREE_SITTER , get_language
1919from textual .cache import LRUCache
2020from textual .color import Color
21+ from textual .content import Content
2122from textual .document ._document import (
2223 Document ,
2324 DocumentBase ,
3637from textual .document ._wrapped_document import WrappedDocument
3738from textual .expand_tabs import expand_tabs_inline , expand_text_tabs_from_widths
3839from textual .screen import Screen
40+ from textual .style import Style as ContentStyle
3941
4042if TYPE_CHECKING :
4143 from tree_sitter import Language , Query
@@ -143,6 +145,14 @@ class TextArea(ScrollView):
143145 background: $foreground 30%;
144146 }
145147
148+ & .text-area--suggestion {
149+ color: $text-muted;
150+ }
151+
152+ & .text-area--placeholder {
153+ color: $text 40%;
154+ }
155+
146156 &:focus {
147157 border: tall $border;
148158 }
@@ -183,6 +193,8 @@ class TextArea(ScrollView):
183193 "text-area--cursor-line" ,
184194 "text-area--selection" ,
185195 "text-area--matching-bracket" ,
196+ "text-area--suggestion" ,
197+ "text-area--placeholder" ,
186198 }
187199 """
188200 `TextArea` offers some component classes which can be used to style aspects of the widget.
@@ -197,6 +209,8 @@ class TextArea(ScrollView):
197209 | `text-area--cursor-line` | Target the line the cursor is on. |
198210 | `text-area--selection` | Target the current selection. |
199211 | `text-area--matching-bracket` | Target matching brackets. |
212+ | `text-area--suggestion` | Target the text set in the `suggestion` reactive. |
213+ | `text-area--placeholder` | Target the placeholder text. |
200214 """
201215
202216 BINDINGS = [
@@ -392,6 +406,12 @@ class TextArea(ScrollView):
392406 """Indicates where the cursor is in the blink cycle. If it's currently
393407 not visible due to blinking, this is False."""
394408
409+ suggestion : Reactive [str ] = reactive ("" )
410+ """A suggestion for auto-complete (pressing right will insert it)."""
411+
412+ placeholder : Reactive [str | Content ] = reactive ("" )
413+ """Text to show when the text area has no content."""
414+
395415 @dataclass
396416 class Changed (Message ):
397417 """Posted when the content inside the TextArea changes.
@@ -443,6 +463,7 @@ def __init__(
443463 tooltip : RenderableType | None = None ,
444464 compact : bool = False ,
445465 highlight_cursor_line : bool = True ,
466+ placeholder : str | Content = "" ,
446467 ) -> None :
447468 """Construct a new `TextArea`.
448469
@@ -464,6 +485,7 @@ def __init__(
464485 tooltip: Optional tooltip.
465486 compact: Enable compact style (without borders).
466487 highlight_cursor_line: Highlight the line under the cursor.
488+ placeholder: Text to display when there is not content.
467489 """
468490 super ().__init__ (name = name , id = id , classes = classes , disabled = disabled )
469491
@@ -524,6 +546,7 @@ def __init__(
524546 self .set_reactive (TextArea .show_line_numbers , show_line_numbers )
525547 self .set_reactive (TextArea .line_number_start , line_number_start )
526548 self .set_reactive (TextArea .highlight_cursor_line , highlight_cursor_line )
549+ self .set_reactive (TextArea .placeholder , placeholder )
527550
528551 self ._line_cache : LRUCache [tuple , Strip ] = LRUCache (1024 )
529552
@@ -565,6 +588,7 @@ def code_editor(
565588 tooltip : RenderableType | None = None ,
566589 compact : bool = False ,
567590 highlight_cursor_line : bool = True ,
591+ placeholder : str | Content = "" ,
568592 ) -> TextArea :
569593 """Construct a new `TextArea` with sensible defaults for editing code.
570594
@@ -607,6 +631,7 @@ def code_editor(
607631 tooltip = tooltip ,
608632 compact = compact ,
609633 highlight_cursor_line = highlight_cursor_line ,
634+ placeholder = placeholder ,
610635 )
611636
612637 @staticmethod
@@ -632,6 +657,7 @@ def _get_builtin_highlight_query(language_name: str) -> str:
632657
633658 def notify_style_update (self ) -> None :
634659 self ._line_cache .clear ()
660+ super ().notify_style_update ()
635661
636662 def check_consume_key (self , key : str , character : str | None = None ) -> bool :
637663 """Check if the widget may consume the given key.
@@ -1173,6 +1199,25 @@ def render_line(self, y: int) -> Strip:
11731199 Returns:
11741200 A rendered line.
11751201 """
1202+ if y == 0 and not self .text and self .placeholder :
1203+ style = self .get_visual_style ("text-area--placeholder" )
1204+ content = (
1205+ Content (self .placeholder )
1206+ if isinstance (self .placeholder , str )
1207+ else self .placeholder
1208+ )
1209+ content = content .stylize (style )
1210+ if self ._draw_cursor :
1211+ theme = self ._theme
1212+ cursor_style = theme .cursor_style if theme else None
1213+ if cursor_style :
1214+ content = content .stylize (
1215+ ContentStyle .from_rich_style (cursor_style ), 0 , 1
1216+ )
1217+ return Strip (
1218+ content .render_segments (self .visual_style ), content .cell_length
1219+ )
1220+
11761221 scroll_x , scroll_y = self .scroll_offset
11771222 absolute_y = scroll_y + y
11781223 selection = self .selection
@@ -1201,6 +1246,7 @@ def render_line(self, y: int) -> Strip:
12011246 self .show_line_numbers ,
12021247 self .read_only ,
12031248 self .show_cursor ,
1249+ self .suggestion ,
12041250 )
12051251 if (cached_line := self ._line_cache .get (cache_key )) is not None :
12061252 return cached_line
@@ -1332,6 +1378,16 @@ def _render_line(self, y: int) -> Strip:
13321378 cursor_column + 1 ,
13331379 )
13341380
1381+ if self .suggestion and self .has_focus :
1382+ suggestion_style = self .get_component_rich_style (
1383+ "text-area--suggestion"
1384+ )
1385+ line = Text .assemble (
1386+ line [:cursor_column ],
1387+ (self .suggestion , suggestion_style ),
1388+ line [cursor_column :],
1389+ )
1390+
13351391 if draw_cursor :
13361392 cursor_style = theme .cursor_style if theme else None
13371393 if cursor_style :
@@ -1474,6 +1530,7 @@ def edit(self, edit: Edit) -> EditResult:
14741530 Data relating to the edit that may be useful. The data returned
14751531 may be different depending on the edit performed.
14761532 """
1533+ self .suggestion = ""
14771534 old_gutter_width = self .gutter_width
14781535 result = edit .do (self )
14791536 self .history .record (edit )
@@ -2030,6 +2087,9 @@ def action_cursor_right(self, select: bool = False) -> None:
20302087 if not self ._has_cursor :
20312088 self .scroll_right ()
20322089 return
2090+ if self .suggestion :
2091+ self .insert (self .suggestion )
2092+ return
20332093 target = (
20342094 self .get_cursor_right_location ()
20352095 if select or self .selection .is_empty
0 commit comments