11"""Output widget for Aider TUI using Textual's RichLog widget."""
22
33import re
4+ import textwrap
45
56from rich .markdown import Markdown
67from rich .padding import Padding
@@ -41,6 +42,8 @@ def __init__(self, **kwargs):
4142 super ().__init__ (** kwargs )
4243 # Line buffer for streaming text to avoid word-per-line issue
4344 self ._line_buffer = ""
45+ # Track if we're on the first line of the current response
46+ self ._first_line_of_response = True
4447
4548 # Enable markup for rich formatting
4649 self .highlight = True
@@ -51,6 +54,33 @@ async def start_response(self):
5154 """Start a new LLM response section with streaming support."""
5255 # Clear the line buffer for new response
5356 self ._line_buffer = ""
57+ # Reset first line flag
58+ self ._first_line_of_response = True
59+
60+ def _wrap_text_with_prefix (self , text : str , prefix : str = "• " ) -> str :
61+ """Wrap text with prefix and proper indentation.
62+
63+ Args:
64+ text: The text to wrap
65+ prefix: The prefix to use for the first line
66+
67+ Returns:
68+ Wrapped text with prefix and indentation
69+ """
70+ if not text .strip ():
71+ return ""
72+
73+ # Get available width for wrapping
74+ # Subtract 2 to account for potential borders or scrollbars
75+ width = self .content_size .width - 2 if self .content_size .width else 80
76+ indent = " " * len (prefix )
77+
78+ # Wrap the text using textwrap
79+ wrapped_text = textwrap .fill (
80+ text , width = width , initial_indent = prefix , subsequent_indent = indent
81+ )
82+
83+ return wrapped_text
5484
5585 async def stream_chunk (self , text : str ):
5686 """Stream a chunk of markdown text."""
@@ -66,10 +96,21 @@ async def stream_chunk(self, text: str):
6696 # Process complete lines from buffer
6797 while "\n " in self ._line_buffer :
6898 line , self ._line_buffer = self ._line_buffer .split ("\n " , 1 )
69- # self.write(Padding(line.strip(), (0, 0, 0, 1)))
7099 if line .rstrip ():
71100 self .set_last_write_type ("assistant" )
72- self .output (line .rstrip (), render_markdown = True )
101+ # Format with prefix on first line, proper indentation on subsequent lines
102+ if self ._first_line_of_response :
103+ wrapped_line = self ._wrap_text_with_prefix (line .rstrip (), prefix = "• " )
104+ self ._first_line_of_response = False
105+ else :
106+ # For subsequent lines, we need to wrap with proper indentation
107+ # but without the bullet prefix
108+ wrapped_line = self ._wrap_text_with_prefix (line .rstrip (), prefix = " " )
109+
110+ # Output each wrapped line
111+ for wrapped in wrapped_line .split ("\n " ):
112+ if wrapped .strip ():
113+ self .output (wrapped , render_markdown = True )
73114
74115 async def end_response (self ):
75116 """End the current LLM response."""
@@ -79,15 +120,32 @@ async def _stop_stream(self):
79120 """Stop the current markdown stream."""
80121 # Flush any remaining buffer content
81122 if self ._line_buffer .rstrip ():
82- self .output (self ._line_buffer .rstrip (), render_markdown = True )
123+ # Format remaining content based on whether it's first line or not
124+ if self ._first_line_of_response :
125+ wrapped_line = self ._wrap_text_with_prefix (self ._line_buffer .rstrip (), prefix = "• " )
126+ else :
127+ wrapped_line = self ._wrap_text_with_prefix (self ._line_buffer .rstrip (), prefix = " " )
128+
129+ # Output each wrapped line
130+ for wrapped in wrapped_line .split ("\n " ):
131+ if wrapped .strip ():
132+ self .output (wrapped , render_markdown = True )
83133 self ._line_buffer = ""
84134
85135 def add_user_message (self , text : str ):
86136 """Add a user message (displayed differently from LLM output)."""
87137 # User messages shown with > prefix in green color
88138 self .auto_scroll = True
89139 self .set_last_write_type ("user" )
90- self .output (f"[bold medium_spring_green]> { text } [/bold medium_spring_green]" )
140+
141+ # Wrap the entire user message with "> " prefix
142+ wrapped_text = self ._wrap_text_with_prefix (text , prefix = "> " )
143+
144+ # Output each wrapped line with green styling
145+ for line in wrapped_text .split ("\n " ):
146+ if line .strip ():
147+ self .output (f"[bold medium_spring_green]{ line } [/bold medium_spring_green]" )
148+
91149 self .scroll_end (animate = False )
92150
93151 def add_system_message (self , text : str , dim = True ):
@@ -158,8 +216,9 @@ def add_tool_call(self, lines: list):
158216 arg_string_list = re .split (r"(^\S+:)" , clean_line , maxsplit = 1 )[1 :]
159217
160218 if len (arg_string_list ) > 1 :
219+ tool_property = arg_string_list [0 ].replace ("_" , " " ).title ()
161220 content = Text ()
162- content .append (f"ᴸ{ arg_string_list [ 0 ] } " , style = "dim bright_cyan" )
221+ content .append (f"ᴸ{ tool_property } " , style = "dim bright_cyan" )
163222 content .append (arg_string_list [1 ], style = "dim" )
164223 self .output (Padding (content , (0 , 0 , 0 , 2 )))
165224 else :
0 commit comments