on main help sign in
revert: the width changes in #2127 (#2135)
f0c9985Kujtim Hoxha
internal/ui/chat/agent.go+8−6
@@ -99,6 +99,7 @@ type AgentToolRenderContext struct {
99
99
100// RenderTool implements the [ToolRenderer] interface.
100// RenderTool implements the [ToolRenderer] interface.
101func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
101func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
102+ cappedWidth := cappedMessageWidth(width)
102 if !opts.ToolCall.Finished && !opts.IsCanceled() && len(r.agent.nestedTools) == 0 {
103 if !opts.ToolCall.Finished && !opts.IsCanceled() && len(r.agent.nestedTools) == 0 {
103 return pendingTool(sty, "Agent", opts.Anim)
104 return pendingTool(sty, "Agent", opts.Anim)
104 }
105 }
@@ -109,7 +110,7 @@ func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
109 prompt := params.Prompt
110 prompt := params.Prompt
110 prompt = strings.ReplaceAll(prompt, "\n", " ")
111 prompt = strings.ReplaceAll(prompt, "\n", " ")
111
112
112− header := toolHeader(sty, opts.Status, "Agent", width, opts.Compact)
113+ header := toolHeader(sty, opts.Status, "Agent", cappedWidth, opts.Compact)
113 if opts.Compact {
114 if opts.Compact {
114 return header
115 return header
115 }
116 }
@@ -119,7 +120,7 @@ func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
119 taskTagWidth := lipgloss.Width(taskTag)
120 taskTagWidth := lipgloss.Width(taskTag)
120
121
121 // Calculate remaining width for prompt.
122 // Calculate remaining width for prompt.
122− remainingWidth := width - taskTagWidth - 3 // -3 for spacing
123+ remainingWidth := min(cappedWidth-taskTagWidth-3, maxTextWidth-taskTagWidth-3) // -3 for spacing
123
124
124 promptText := sty.Tool.AgentPrompt.Width(remainingWidth).Render(prompt)
125 promptText := sty.Tool.AgentPrompt.Width(remainingWidth).Render(prompt)
125
126
@@ -156,7 +157,7 @@ func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
156
157
157 // Add body content when completed.
158 // Add body content when completed.
158 if opts.HasResult() && opts.Result.Content != "" {
159 if opts.HasResult() && opts.Result.Content != "" {
159− body := toolOutputMarkdownContent(sty, opts.Result.Content, width-toolBodyLeftPaddingTotal, opts.ExpandedContent)
160+ body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth-toolBodyLeftPaddingTotal, opts.ExpandedContent)
160 return joinToolParts(result, body)
161 return joinToolParts(result, body)
161 }
162 }
162
163
@@ -229,6 +230,7 @@ type agenticFetchParams struct {
229
230
230// RenderTool implements the [ToolRenderer] interface.
231// RenderTool implements the [ToolRenderer] interface.
231func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
232func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
233+ cappedWidth := cappedMessageWidth(width)
232 if !opts.ToolCall.Finished && !opts.IsCanceled() && len(r.fetch.nestedTools) == 0 {
234 if !opts.ToolCall.Finished && !opts.IsCanceled() && len(r.fetch.nestedTools) == 0 {
233 return pendingTool(sty, "Agentic Fetch", opts.Anim)
235 return pendingTool(sty, "Agentic Fetch", opts.Anim)
234 }
236 }
@@ -245,7 +247,7 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
245 toolParams = append(toolParams, params.URL)
247 toolParams = append(toolParams, params.URL)
246 }
248 }
247
249
248− header := toolHeader(sty, opts.Status, "Agentic Fetch", width, opts.Compact, toolParams...)
250+ header := toolHeader(sty, opts.Status, "Agentic Fetch", cappedWidth, opts.Compact, toolParams...)
249 if opts.Compact {
251 if opts.Compact {
250 return header
252 return header
251 }
253 }
@@ -255,7 +257,7 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
255 promptTagWidth := lipgloss.Width(promptTag)
257 promptTagWidth := lipgloss.Width(promptTag)
256
258
257 // Calculate remaining width for prompt text.
259 // Calculate remaining width for prompt text.
258− remainingWidth := width - promptTagWidth - 3 // -3 for spacing
260+ remainingWidth := min(cappedWidth-promptTagWidth-3, maxTextWidth-promptTagWidth-3) // -3 for spacing
259
261
260 promptText := sty.Tool.AgentPrompt.Width(remainingWidth).Render(prompt)
262 promptText := sty.Tool.AgentPrompt.Width(remainingWidth).Render(prompt)
261
263
@@ -292,7 +294,7 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
292
294
293 // Add body content when completed.
295 // Add body content when completed.
294 if opts.HasResult() && opts.Result.Content != "" {
296 if opts.HasResult() && opts.Result.Content != "" {
295− body := toolOutputMarkdownContent(sty, opts.Result.Content, width-toolBodyLeftPaddingTotal, opts.ExpandedContent)
297+ body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth-toolBodyLeftPaddingTotal, opts.ExpandedContent)
296 return joinToolParts(result, body)
298 return joinToolParts(result, body)
297 }
299 }
298
300
internal/ui/chat/assistant.go+6−4
@@ -79,20 +79,22 @@ func (a *AssistantMessageItem) ID() string {
79
79
80// RawRender implements [MessageItem].
80// RawRender implements [MessageItem].
81func (a *AssistantMessageItem) RawRender(width int) string {
81func (a *AssistantMessageItem) RawRender(width int) string {
82+ cappedWidth := cappedMessageWidth(width)
83+
82 var spinner string
84 var spinner string
83 if a.isSpinning() {
85 if a.isSpinning() {
84 spinner = a.renderSpinning()
86 spinner = a.renderSpinning()
85 }
87 }
86
88
87− content, height, ok := a.getCachedRender(width)
89+ content, height, ok := a.getCachedRender(cappedWidth)
88 if !ok {
90 if !ok {
89− content = a.renderMessageContent(width)
91+ content = a.renderMessageContent(cappedWidth)
90 height = lipgloss.Height(content)
92 height = lipgloss.Height(content)
91 // cache the rendered content
93 // cache the rendered content
92− a.setCachedRender(content, width, height)
94+ a.setCachedRender(content, cappedWidth, height)
93 }
95 }
94
96
95− highlightedContent := a.renderHighlighted(content, width, height)
97+ highlightedContent := a.renderHighlighted(content, cappedWidth, height)
96 if spinner != "" {
98 if spinner != "" {
97 if highlightedContent != "" {
99 if highlightedContent != "" {
98 highlightedContent += "\n\n"
100 highlightedContent += "\n\n"
internal/ui/chat/bash.go+11−8
@@ -39,6 +39,7 @@ type BashToolRenderContext struct{}
39
39
40// RenderTool implements the [ToolRenderer] interface.
40// RenderTool implements the [ToolRenderer] interface.
41func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
41func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
42+ cappedWidth := cappedMessageWidth(width)
42 if opts.IsPending() {
43 if opts.IsPending() {
43 return pendingTool(sty, "Bash", opts.Anim)
44 return pendingTool(sty, "Bash", opts.Anim)
44 }
45 }
@@ -57,7 +58,7 @@ func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
57 if meta.Background {
58 if meta.Background {
58 description := cmp.Or(meta.Description, params.Command)
59 description := cmp.Or(meta.Description, params.Command)
59 content := "Command: " + params.Command + "\n" + opts.Result.Content
60 content := "Command: " + params.Command + "\n" + opts.Result.Content
60− return renderJobTool(sty, opts, width, "Start", meta.ShellID, description, content)
61+ return renderJobTool(sty, opts, cappedWidth, "Start", meta.ShellID, description, content)
61 }
62 }
62
63
63 // Regular bash command.
64 // Regular bash command.
@@ -68,12 +69,12 @@ func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
68 toolParams = append(toolParams, "background", "true")
69 toolParams = append(toolParams, "background", "true")
69 }
70 }
70
71
71− header := toolHeader(sty, opts.Status, "Bash", width, opts.Compact, toolParams...)
72+ header := toolHeader(sty, opts.Status, "Bash", cappedWidth, opts.Compact, toolParams...)
72 if opts.Compact {
73 if opts.Compact {
73 return header
74 return header
74 }
75 }
75
76
76− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
77+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
77 return joinToolParts(header, earlyState)
78 return joinToolParts(header, earlyState)
78 }
79 }
79
80
@@ -89,7 +90,7 @@ func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
89 return header
90 return header
90 }
91 }
91
92
92− bodyWidth := width - toolBodyLeftPaddingTotal
93+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
93 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, output, bodyWidth, opts.ExpandedContent))
94 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, output, bodyWidth, opts.ExpandedContent))
94 return joinToolParts(header, body)
95 return joinToolParts(header, body)
95}
96}
@@ -120,13 +121,14 @@ type JobOutputToolRenderContext struct{}
120
121
121// RenderTool implements the [ToolRenderer] interface.
122// RenderTool implements the [ToolRenderer] interface.
122func (j *JobOutputToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
123func (j *JobOutputToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
124+ cappedWidth := cappedMessageWidth(width)
123 if opts.IsPending() {
125 if opts.IsPending() {
124 return pendingTool(sty, "Job", opts.Anim)
126 return pendingTool(sty, "Job", opts.Anim)
125 }
127 }
126
128
127 var params tools.JobOutputParams
129 var params tools.JobOutputParams
128 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
130 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
129− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
131+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
130 }
132 }
131
133
132 var description string
134 var description string
@@ -141,7 +143,7 @@ func (j *JobOutputToolRenderContext) RenderTool(sty *styles.Styles, width int, o
141 if opts.HasResult() {
143 if opts.HasResult() {
142 content = opts.Result.Content
144 content = opts.Result.Content
143 }
145 }
144− return renderJobTool(sty, opts, width, "Output", params.ShellID, description, content)
146+ return renderJobTool(sty, opts, cappedWidth, "Output", params.ShellID, description, content)
145}
147}
146
148
147// -----------------------------------------------------------------------------
149// -----------------------------------------------------------------------------
@@ -170,13 +172,14 @@ type JobKillToolRenderContext struct{}
170
172
171// RenderTool implements the [ToolRenderer] interface.
173// RenderTool implements the [ToolRenderer] interface.
172func (j *JobKillToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
174func (j *JobKillToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
175+ cappedWidth := cappedMessageWidth(width)
173 if opts.IsPending() {
176 if opts.IsPending() {
174 return pendingTool(sty, "Job", opts.Anim)
177 return pendingTool(sty, "Job", opts.Anim)
175 }
178 }
176
179
177 var params tools.JobKillParams
180 var params tools.JobKillParams
178 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
181 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
179− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
182+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
180 }
183 }
181
184
182 var description string
185 var description string
@@ -191,7 +194,7 @@ func (j *JobKillToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
191 if opts.HasResult() {
194 if opts.HasResult() {
192 content = opts.Result.Content
195 content = opts.Result.Content
193 }
196 }
194− return renderJobTool(sty, opts, width, "Kill", params.ShellID, description, content)
197+ return renderJobTool(sty, opts, cappedWidth, "Kill", params.ShellID, description, content)
195}
198}
196
199
197// renderJobTool renders a job-related tool with the common pattern:
200// renderJobTool renders a job-related tool with the common pattern:
internal/ui/chat/diagnostics.go+4−3
@@ -35,6 +35,7 @@ type DiagnosticsToolRenderContext struct{}
35
35
36// RenderTool implements the [ToolRenderer] interface.
36// RenderTool implements the [ToolRenderer] interface.
37func (d *DiagnosticsToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
37func (d *DiagnosticsToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
38+ cappedWidth := cappedMessageWidth(width)
38 if opts.IsPending() {
39 if opts.IsPending() {
39 return pendingTool(sty, "Diagnostics", opts.Anim)
40 return pendingTool(sty, "Diagnostics", opts.Anim)
40 }
41 }
@@ -48,12 +49,12 @@ func (d *DiagnosticsToolRenderContext) RenderTool(sty *styles.Styles, width int,
48 mainParam = fsext.PrettyPath(params.FilePath)
49 mainParam = fsext.PrettyPath(params.FilePath)
49 }
50 }
50
51
51− header := toolHeader(sty, opts.Status, "Diagnostics", width, opts.Compact, mainParam)
52+ header := toolHeader(sty, opts.Status, "Diagnostics", cappedWidth, opts.Compact, mainParam)
52 if opts.Compact {
53 if opts.Compact {
53 return header
54 return header
54 }
55 }
55
56
56− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
57+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
57 return joinToolParts(header, earlyState)
58 return joinToolParts(header, earlyState)
58 }
59 }
59
60
@@ -61,7 +62,7 @@ func (d *DiagnosticsToolRenderContext) RenderTool(sty *styles.Styles, width int,
61 return header
62 return header
62 }
63 }
63
64
64− bodyWidth := width - toolBodyLeftPaddingTotal
65+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
65 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
66 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
66 return joinToolParts(header, body)
67 return joinToolParts(header, body)
67}
68}
internal/ui/chat/fetch.go+15−12
@@ -34,13 +34,14 @@ type FetchToolRenderContext struct{}
34
34
35// RenderTool implements the [ToolRenderer] interface.
35// RenderTool implements the [ToolRenderer] interface.
36func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
36func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
37+ cappedWidth := cappedMessageWidth(width)
37 if opts.IsPending() {
38 if opts.IsPending() {
38 return pendingTool(sty, "Fetch", opts.Anim)
39 return pendingTool(sty, "Fetch", opts.Anim)
39 }
40 }
40
41
41 var params tools.FetchParams
42 var params tools.FetchParams
42 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
43 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
43− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
44+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
44 }
45 }
45
46
46 toolParams := []string{params.URL}
47 toolParams := []string{params.URL}
@@ -51,12 +52,12 @@ func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
51 toolParams = append(toolParams, "timeout", formatTimeout(params.Timeout))
52 toolParams = append(toolParams, "timeout", formatTimeout(params.Timeout))
52 }
53 }
53
54
54− header := toolHeader(sty, opts.Status, "Fetch", width, opts.Compact, toolParams...)
55+ header := toolHeader(sty, opts.Status, "Fetch", cappedWidth, opts.Compact, toolParams...)
55 if opts.Compact {
56 if opts.Compact {
56 return header
57 return header
57 }
58 }
58
59
59− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
60+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
60 return joinToolParts(header, earlyState)
61 return joinToolParts(header, earlyState)
61 }
62 }
62
63
@@ -66,7 +67,7 @@ func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
66
67
67 // Determine file extension for syntax highlighting based on format.
68 // Determine file extension for syntax highlighting based on format.
68 file := getFileExtensionForFormat(params.Format)
69 file := getFileExtensionForFormat(params.Format)
69− body := toolOutputCodeContent(sty, file, opts.Result.Content, 0, width, opts.ExpandedContent)
70+ body := toolOutputCodeContent(sty, file, opts.Result.Content, 0, cappedWidth, opts.ExpandedContent)
70 return joinToolParts(header, body)
71 return joinToolParts(header, body)
71}
72}
72
73
@@ -108,22 +109,23 @@ type WebFetchToolRenderContext struct{}
108
109
109// RenderTool implements the [ToolRenderer] interface.
110// RenderTool implements the [ToolRenderer] interface.
110func (w *WebFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
111func (w *WebFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
112+ cappedWidth := cappedMessageWidth(width)
111 if opts.IsPending() {
113 if opts.IsPending() {
112 return pendingTool(sty, "Fetch", opts.Anim)
114 return pendingTool(sty, "Fetch", opts.Anim)
113 }
115 }
114
116
115 var params tools.WebFetchParams
117 var params tools.WebFetchParams
116 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
118 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
117− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
119+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
118 }
120 }
119
121
120 toolParams := []string{params.URL}
122 toolParams := []string{params.URL}
121− header := toolHeader(sty, opts.Status, "Fetch", width, opts.Compact, toolParams...)
123+ header := toolHeader(sty, opts.Status, "Fetch", cappedWidth, opts.Compact, toolParams...)
122 if opts.Compact {
124 if opts.Compact {
123 return header
125 return header
124 }
126 }
125
127
126− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
128+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
127 return joinToolParts(header, earlyState)
129 return joinToolParts(header, earlyState)
128 }
130 }
129
131
@@ -131,7 +133,7 @@ func (w *WebFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, op
131 return header
133 return header
132 }
134 }
133
135
134− body := toolOutputMarkdownContent(sty, opts.Result.Content, width, opts.ExpandedContent)
136+ body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth, opts.ExpandedContent)
135 return joinToolParts(header, body)
137 return joinToolParts(header, body)
136}
138}
137
139
@@ -161,22 +163,23 @@ type WebSearchToolRenderContext struct{}
161
163
162// RenderTool implements the [ToolRenderer] interface.
164// RenderTool implements the [ToolRenderer] interface.
163func (w *WebSearchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
165func (w *WebSearchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
166+ cappedWidth := cappedMessageWidth(width)
164 if opts.IsPending() {
167 if opts.IsPending() {
165 return pendingTool(sty, "Search", opts.Anim)
168 return pendingTool(sty, "Search", opts.Anim)
166 }
169 }
167
170
168 var params tools.WebSearchParams
171 var params tools.WebSearchParams
169 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
172 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
170− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
173+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
171 }
174 }
172
175
173 toolParams := []string{params.Query}
176 toolParams := []string{params.Query}
174− header := toolHeader(sty, opts.Status, "Search", width, opts.Compact, toolParams...)
177+ header := toolHeader(sty, opts.Status, "Search", cappedWidth, opts.Compact, toolParams...)
175 if opts.Compact {
178 if opts.Compact {
176 return header
179 return header
177 }
180 }
178
181
179− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
182+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
180 return joinToolParts(header, earlyState)
183 return joinToolParts(header, earlyState)
181 }
184 }
182
185
@@ -184,6 +187,6 @@ func (w *WebSearchToolRenderContext) RenderTool(sty *styles.Styles, width int, o
184 return header
187 return header
185 }
188 }
186
189
187− body := toolOutputMarkdownContent(sty, opts.Result.Content, width, opts.ExpandedContent)
190+ body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth, opts.ExpandedContent)
188 return joinToolParts(header, body)
191 return joinToolParts(header, body)
189}
192}
internal/ui/chat/file.go+15−12
@@ -37,13 +37,14 @@ type ViewToolRenderContext struct{}
37
37
38// RenderTool implements the [ToolRenderer] interface.
38// RenderTool implements the [ToolRenderer] interface.
39func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
39func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
40+ cappedWidth := cappedMessageWidth(width)
40 if opts.IsPending() {
41 if opts.IsPending() {
41 return pendingTool(sty, "View", opts.Anim)
42 return pendingTool(sty, "View", opts.Anim)
42 }
43 }
43
44
44 var params tools.ViewParams
45 var params tools.ViewParams
45 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
46 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
46− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
47+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
47 }
48 }
48
49
49 file := fsext.PrettyPath(params.FilePath)
50 file := fsext.PrettyPath(params.FilePath)
@@ -55,12 +56,12 @@ func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
55 toolParams = append(toolParams, "offset", fmt.Sprintf("%d", params.Offset))
56 toolParams = append(toolParams, "offset", fmt.Sprintf("%d", params.Offset))
56 }
57 }
57
58
58− header := toolHeader(sty, opts.Status, "View", width, opts.Compact, toolParams...)
59+ header := toolHeader(sty, opts.Status, "View", cappedWidth, opts.Compact, toolParams...)
59 if opts.Compact {
60 if opts.Compact {
60 return header
61 return header
61 }
62 }
62
63
63− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
64+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
64 return joinToolParts(header, earlyState)
65 return joinToolParts(header, earlyState)
65 }
66 }
66
67
@@ -86,7 +87,7 @@ func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
86 }
87 }
87
88
88 // Render code content with syntax highlighting.
89 // Render code content with syntax highlighting.
89− body := toolOutputCodeContent(sty, params.FilePath, content, params.Offset, width, opts.ExpandedContent)
90+ body := toolOutputCodeContent(sty, params.FilePath, content, params.Offset, cappedWidth, opts.ExpandedContent)
90 return joinToolParts(header, body)
91 return joinToolParts(header, body)
91}
92}
92
93
@@ -116,22 +117,23 @@ type WriteToolRenderContext struct{}
116
117
117// RenderTool implements the [ToolRenderer] interface.
118// RenderTool implements the [ToolRenderer] interface.
118func (w *WriteToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
119func (w *WriteToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
120+ cappedWidth := cappedMessageWidth(width)
119 if opts.IsPending() {
121 if opts.IsPending() {
120 return pendingTool(sty, "Write", opts.Anim)
122 return pendingTool(sty, "Write", opts.Anim)
121 }
123 }
122
124
123 var params tools.WriteParams
125 var params tools.WriteParams
124 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
126 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
125− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
127+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
126 }
128 }
127
129
128 file := fsext.PrettyPath(params.FilePath)
130 file := fsext.PrettyPath(params.FilePath)
129− header := toolHeader(sty, opts.Status, "Write", width, opts.Compact, file)
131+ header := toolHeader(sty, opts.Status, "Write", cappedWidth, opts.Compact, file)
130 if opts.Compact {
132 if opts.Compact {
131 return header
133 return header
132 }
134 }
133
135
134− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
136+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
135 return joinToolParts(header, earlyState)
137 return joinToolParts(header, earlyState)
136 }
138 }
137
139
@@ -140,7 +142,7 @@ func (w *WriteToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
140 }
142 }
141
143
142 // Render code content with syntax highlighting.
144 // Render code content with syntax highlighting.
143− body := toolOutputCodeContent(sty, params.FilePath, params.Content, 0, width, opts.ExpandedContent)
145+ body := toolOutputCodeContent(sty, params.FilePath, params.Content, 0, cappedWidth, opts.ExpandedContent)
144 return joinToolParts(header, body)
146 return joinToolParts(header, body)
145}
147}
146
148
@@ -301,13 +303,14 @@ type DownloadToolRenderContext struct{}
301
303
302// RenderTool implements the [ToolRenderer] interface.
304// RenderTool implements the [ToolRenderer] interface.
303func (d *DownloadToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
305func (d *DownloadToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
306+ cappedWidth := cappedMessageWidth(width)
304 if opts.IsPending() {
307 if opts.IsPending() {
305 return pendingTool(sty, "Download", opts.Anim)
308 return pendingTool(sty, "Download", opts.Anim)
306 }
309 }
307
310
308 var params tools.DownloadParams
311 var params tools.DownloadParams
309 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
312 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
310− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
313+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
311 }
314 }
312
315
313 toolParams := []string{params.URL}
316 toolParams := []string{params.URL}
@@ -318,12 +321,12 @@ func (d *DownloadToolRenderContext) RenderTool(sty *styles.Styles, width int, op
318 toolParams = append(toolParams, "timeout", formatTimeout(params.Timeout))
321 toolParams = append(toolParams, "timeout", formatTimeout(params.Timeout))
319 }
322 }
320
323
321− header := toolHeader(sty, opts.Status, "Download", width, opts.Compact, toolParams...)
324+ header := toolHeader(sty, opts.Status, "Download", cappedWidth, opts.Compact, toolParams...)
322 if opts.Compact {
325 if opts.Compact {
323 return header
326 return header
324 }
327 }
325
328
326− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
329+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
327 return joinToolParts(header, earlyState)
330 return joinToolParts(header, earlyState)
328 }
331 }
329
332
@@ -331,7 +334,7 @@ func (d *DownloadToolRenderContext) RenderTool(sty *styles.Styles, width int, op
331 return header
334 return header
332 }
335 }
333
336
334− bodyWidth := width - toolBodyLeftPaddingTotal
337+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
335 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
338 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
336 return joinToolParts(header, body)
339 return joinToolParts(header, body)
337}
340}
internal/ui/chat/generic.go+5−4
@@ -31,6 +31,7 @@ type GenericToolRenderContext struct{}
31
31
32// RenderTool implements the [ToolRenderer] interface.
32// RenderTool implements the [ToolRenderer] interface.
33func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
33func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
34+ cappedWidth := cappedMessageWidth(width)
34 name := genericPrettyName(opts.ToolCall.Name)
35 name := genericPrettyName(opts.ToolCall.Name)
35
36
36 if opts.IsPending() {
37 if opts.IsPending() {
@@ -39,7 +40,7 @@ func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
39
40
40 var params map[string]any
41 var params map[string]any
41 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
42 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
42− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
43+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
43 }
44 }
44
45
45 var toolParams []string
46 var toolParams []string
@@ -48,12 +49,12 @@ func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
48 toolParams = append(toolParams, string(parsed))
49 toolParams = append(toolParams, string(parsed))
49 }
50 }
50
51
51− header := toolHeader(sty, opts.Status, name, width, opts.Compact, toolParams...)
52+ header := toolHeader(sty, opts.Status, name, cappedWidth, opts.Compact, toolParams...)
52 if opts.Compact {
53 if opts.Compact {
53 return header
54 return header
54 }
55 }
55
56
56− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
57+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
57 return joinToolParts(header, earlyState)
58 return joinToolParts(header, earlyState)
58 }
59 }
59
60
@@ -61,7 +62,7 @@ func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
61 return header
62 return header
62 }
63 }
63
64
64− bodyWidth := width - toolBodyLeftPaddingTotal
65+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
65
66
66 // Handle image data.
67 // Handle image data.
67 if opts.Result.Data != "" && strings.HasPrefix(opts.Result.MIMEType, "image/") {
68 if opts.Result.Data != "" && strings.HasPrefix(opts.Result.MIMEType, "image/") {
internal/ui/chat/lsp_restart.go+4−3
@@ -30,6 +30,7 @@ type LSPRestartToolRenderContext struct{}
30
30
31// RenderTool implements the [ToolRenderer] interface.
31// RenderTool implements the [ToolRenderer] interface.
32func (r *LSPRestartToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
32func (r *LSPRestartToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
33+ cappedWidth := cappedMessageWidth(width)
33 if opts.IsPending() {
34 if opts.IsPending() {
34 return pendingTool(sty, "Restart LSP", opts.Anim)
35 return pendingTool(sty, "Restart LSP", opts.Anim)
35 }
36 }
@@ -42,12 +43,12 @@ func (r *LSPRestartToolRenderContext) RenderTool(sty *styles.Styles, width int,
42 toolParams = append(toolParams, params.Name)
43 toolParams = append(toolParams, params.Name)
43 }
44 }
44
45
45− header := toolHeader(sty, opts.Status, "Restart LSP", width, opts.Compact, toolParams...)
46+ header := toolHeader(sty, opts.Status, "Restart LSP", cappedWidth, opts.Compact, toolParams...)
46 if opts.Compact {
47 if opts.Compact {
47 return header
48 return header
48 }
49 }
49
50
50− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
51+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
51 return joinToolParts(header, earlyState)
52 return joinToolParts(header, earlyState)
52 }
53 }
53
54
@@ -55,7 +56,7 @@ func (r *LSPRestartToolRenderContext) RenderTool(sty *styles.Styles, width int,
55 return header
56 return header
56 }
57 }
57
58
58− bodyWidth := width - toolBodyLeftPaddingTotal
59+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
59 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
60 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
60 return joinToolParts(header, body)
61 return joinToolParts(header, body)
61}
62}
internal/ui/chat/mcp.go+6−5
@@ -32,9 +32,10 @@ type MCPToolRenderContext struct{}
32
32
33// RenderTool implements the [ToolRenderer] interface.
33// RenderTool implements the [ToolRenderer] interface.
34func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
34func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
35+ cappedWidth := cappedMessageWidth(width)
35 toolNameParts := strings.SplitN(opts.ToolCall.Name, "_", 3)
36 toolNameParts := strings.SplitN(opts.ToolCall.Name, "_", 3)
36 if len(toolNameParts) != 3 {
37 if len(toolNameParts) != 3 {
37− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid tool name"}, width)
38+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid tool name"}, cappedWidth)
38 }
39 }
39 mcpName := prettyName(toolNameParts[1])
40 mcpName := prettyName(toolNameParts[1])
40 toolName := prettyName(toolNameParts[2])
41 toolName := prettyName(toolNameParts[2])
@@ -50,7 +51,7 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T
50
51
51 var params map[string]any
52 var params map[string]any
52 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
53 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
53− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
54+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
54 }
55 }
55
56
56 var toolParams []string
57 var toolParams []string
@@ -59,12 +60,12 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T
59 toolParams = append(toolParams, string(parsed))
60 toolParams = append(toolParams, string(parsed))
60 }
61 }
61
62
62− header := toolHeader(sty, opts.Status, name, width, opts.Compact, toolParams...)
63+ header := toolHeader(sty, opts.Status, name, cappedWidth, opts.Compact, toolParams...)
63 if opts.Compact {
64 if opts.Compact {
64 return header
65 return header
65 }
66 }
66
67
67− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
68+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
68 return joinToolParts(header, earlyState)
69 return joinToolParts(header, earlyState)
69 }
70 }
70
71
@@ -72,7 +73,7 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T
72 return header
73 return header
73 }
74 }
74
75
75− bodyWidth := width - toolBodyLeftPaddingTotal
76+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
76 // see if the result is json
77 // see if the result is json
77 var result json.RawMessage
78 var result json.RawMessage
78 var body string
79 var body string
internal/ui/chat/messages.go+5
@@ -245,6 +245,11 @@ func (a *AssistantInfoItem) renderContent(width int) string {
245 return common.Section(a.sty, assistant, width)
245 return common.Section(a.sty, assistant, width)
246}
246}
247
247
248+// cappedMessageWidth returns the maximum width for message content for readability.
249+func cappedMessageWidth(availableWidth int) int {
250+ return min(availableWidth-MessageLeftPaddingTotal, maxTextWidth)
251+}
252+
248// ExtractMessageItems extracts [MessageItem]s from a [message.Message]. It
253// ExtractMessageItems extracts [MessageItem]s from a [message.Message]. It
249// returns all parts of the message as [MessageItem]s.
254// returns all parts of the message as [MessageItem]s.
250//
255//
internal/ui/chat/references.go+4−3
@@ -31,6 +31,7 @@ type ReferencesToolRenderContext struct{}
31
31
32// RenderTool implements the [ToolRenderer] interface.
32// RenderTool implements the [ToolRenderer] interface.
33func (r *ReferencesToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
33func (r *ReferencesToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
34+ cappedWidth := cappedMessageWidth(width)
34 if opts.IsPending() {
35 if opts.IsPending() {
35 return pendingTool(sty, "Find References", opts.Anim)
36 return pendingTool(sty, "Find References", opts.Anim)
36 }
37 }
@@ -43,12 +44,12 @@ func (r *ReferencesToolRenderContext) RenderTool(sty *styles.Styles, width int,
43 toolParams = append(toolParams, "path", fsext.PrettyPath(params.Path))
44 toolParams = append(toolParams, "path", fsext.PrettyPath(params.Path))
44 }
45 }
45
46
46− header := toolHeader(sty, opts.Status, "Find References", width, opts.Compact, toolParams...)
47+ header := toolHeader(sty, opts.Status, "Find References", cappedWidth, opts.Compact, toolParams...)
47 if opts.Compact {
48 if opts.Compact {
48 return header
49 return header
49 }
50 }
50
51
51− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
52+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
52 return joinToolParts(header, earlyState)
53 return joinToolParts(header, earlyState)
53 }
54 }
54
55
@@ -56,7 +57,7 @@ func (r *ReferencesToolRenderContext) RenderTool(sty *styles.Styles, width int,
56 return header
57 return header
57 }
58 }
58
59
59− bodyWidth := width - toolBodyLeftPaddingTotal
60+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
60 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
61 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
61 return joinToolParts(header, body)
62 return joinToolParts(header, body)
62}
63}
internal/ui/chat/search.go+20−16
@@ -35,13 +35,14 @@ type GlobToolRenderContext struct{}
35
35
36// RenderTool implements the [ToolRenderer] interface.
36// RenderTool implements the [ToolRenderer] interface.
37func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
37func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
38+ cappedWidth := cappedMessageWidth(width)
38 if opts.IsPending() {
39 if opts.IsPending() {
39 return pendingTool(sty, "Glob", opts.Anim)
40 return pendingTool(sty, "Glob", opts.Anim)
40 }
41 }
41
42
42 var params tools.GlobParams
43 var params tools.GlobParams
43 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
44 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
44− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
45+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
45 }
46 }
46
47
47 toolParams := []string{params.Pattern}
48 toolParams := []string{params.Pattern}
@@ -49,12 +50,12 @@ func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
49 toolParams = append(toolParams, "path", params.Path)
50 toolParams = append(toolParams, "path", params.Path)
50 }
51 }
51
52
52− header := toolHeader(sty, opts.Status, "Glob", width, opts.Compact, toolParams...)
53+ header := toolHeader(sty, opts.Status, "Glob", cappedWidth, opts.Compact, toolParams...)
53 if opts.Compact {
54 if opts.Compact {
54 return header
55 return header
55 }
56 }
56
57
57− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
58+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
58 return joinToolParts(header, earlyState)
59 return joinToolParts(header, earlyState)
59 }
60 }
60
61
@@ -62,7 +63,7 @@ func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
62 return header
63 return header
63 }
64 }
64
65
65− bodyWidth := width - toolBodyLeftPaddingTotal
66+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
66 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
67 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
67 return joinToolParts(header, body)
68 return joinToolParts(header, body)
68}
69}
@@ -93,13 +94,14 @@ type GrepToolRenderContext struct{}
93
94
94// RenderTool implements the [ToolRenderer] interface.
95// RenderTool implements the [ToolRenderer] interface.
95func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
96func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
97+ cappedWidth := cappedMessageWidth(width)
96 if opts.IsPending() {
98 if opts.IsPending() {
97 return pendingTool(sty, "Grep", opts.Anim)
99 return pendingTool(sty, "Grep", opts.Anim)
98 }
100 }
99
101
100 var params tools.GrepParams
102 var params tools.GrepParams
101 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
103 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
102− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
104+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
103 }
105 }
104
106
105 toolParams := []string{params.Pattern}
107 toolParams := []string{params.Pattern}
@@ -113,12 +115,12 @@ func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
113 toolParams = append(toolParams, "literal", "true")
115 toolParams = append(toolParams, "literal", "true")
114 }
116 }
115
117
116− header := toolHeader(sty, opts.Status, "Grep", width, opts.Compact, toolParams...)
118+ header := toolHeader(sty, opts.Status, "Grep", cappedWidth, opts.Compact, toolParams...)
117 if opts.Compact {
119 if opts.Compact {
118 return header
120 return header
119 }
121 }
120
122
121− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
123+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
122 return joinToolParts(header, earlyState)
124 return joinToolParts(header, earlyState)
123 }
125 }
124
126
@@ -126,7 +128,7 @@ func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
126 return header
128 return header
127 }
129 }
128
130
129− bodyWidth := width - toolBodyLeftPaddingTotal
131+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
130 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
132 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
131 return joinToolParts(header, body)
133 return joinToolParts(header, body)
132}
134}
@@ -157,13 +159,14 @@ type LSToolRenderContext struct{}
157
159
158// RenderTool implements the [ToolRenderer] interface.
160// RenderTool implements the [ToolRenderer] interface.
159func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
161func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
162+ cappedWidth := cappedMessageWidth(width)
160 if opts.IsPending() {
163 if opts.IsPending() {
161 return pendingTool(sty, "List", opts.Anim)
164 return pendingTool(sty, "List", opts.Anim)
162 }
165 }
163
166
164 var params tools.LSParams
167 var params tools.LSParams
165 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
168 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
166− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
169+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
167 }
170 }
168
171
169 path := params.Path
172 path := params.Path
@@ -172,12 +175,12 @@ func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *To
172 }
175 }
173 path = fsext.PrettyPath(path)
176 path = fsext.PrettyPath(path)
174
177
175− header := toolHeader(sty, opts.Status, "List", width, opts.Compact, path)
178+ header := toolHeader(sty, opts.Status, "List", cappedWidth, opts.Compact, path)
176 if opts.Compact {
179 if opts.Compact {
177 return header
180 return header
178 }
181 }
179
182
180− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
183+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
181 return joinToolParts(header, earlyState)
184 return joinToolParts(header, earlyState)
182 }
185 }
183
186
@@ -185,7 +188,7 @@ func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *To
185 return header
188 return header
186 }
189 }
187
190
188− bodyWidth := width - toolBodyLeftPaddingTotal
191+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
189 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
192 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
190 return joinToolParts(header, body)
193 return joinToolParts(header, body)
191}
194}
@@ -216,13 +219,14 @@ type SourcegraphToolRenderContext struct{}
216
219
217// RenderTool implements the [ToolRenderer] interface.
220// RenderTool implements the [ToolRenderer] interface.
218func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
221func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
222+ cappedWidth := cappedMessageWidth(width)
219 if opts.IsPending() {
223 if opts.IsPending() {
220 return pendingTool(sty, "Sourcegraph", opts.Anim)
224 return pendingTool(sty, "Sourcegraph", opts.Anim)
221 }
225 }
222
226
223 var params tools.SourcegraphParams
227 var params tools.SourcegraphParams
224 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
228 if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
225− return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
229+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
226 }
230 }
227
231
228 toolParams := []string{params.Query}
232 toolParams := []string{params.Query}
@@ -233,12 +237,12 @@ func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int,
233 toolParams = append(toolParams, "context", formatNonZero(params.ContextWindow))
237 toolParams = append(toolParams, "context", formatNonZero(params.ContextWindow))
234 }
238 }
235
239
236− header := toolHeader(sty, opts.Status, "Sourcegraph", width, opts.Compact, toolParams...)
240+ header := toolHeader(sty, opts.Status, "Sourcegraph", cappedWidth, opts.Compact, toolParams...)
237 if opts.Compact {
241 if opts.Compact {
238 return header
242 return header
239 }
243 }
240
244
241− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
245+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
242 return joinToolParts(header, earlyState)
246 return joinToolParts(header, earlyState)
243 }
247 }
244
248
@@ -246,7 +250,7 @@ func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int,
246 return header
250 return header
247 }
251 }
248
252
249− bodyWidth := width - toolBodyLeftPaddingTotal
253+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
250 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
254 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
251 return joinToolParts(header, body)
255 return joinToolParts(header, body)
252}
256}
internal/ui/chat/todos.go+5−4
@@ -39,6 +39,7 @@ type TodosToolRenderContext struct{}
39
39
40// RenderTool implements the [ToolRenderer] interface.
40// RenderTool implements the [ToolRenderer] interface.
41func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
41func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
42+ cappedWidth := cappedMessageWidth(width)
42 if opts.IsPending() {
43 if opts.IsPending() {
43 return pendingTool(sty, "To-Do", opts.Anim)
44 return pendingTool(sty, "To-Do", opts.Anim)
44 }
45 }
@@ -81,7 +82,7 @@ func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
81 } else {
82 } else {
82 headerText = fmt.Sprintf("created %d todos", meta.Total)
83 headerText = fmt.Sprintf("created %d todos", meta.Total)
83 }
84 }
84− body = FormatTodosList(sty, meta.Todos, styles.ArrowRightIcon, width)
85+ body = FormatTodosList(sty, meta.Todos, styles.ArrowRightIcon, cappedWidth)
85 } else {
86 } else {
86 // Build header based on what changed.
87 // Build header based on what changed.
87 hasCompleted := len(meta.JustCompleted) > 0
88 hasCompleted := len(meta.JustCompleted) > 0
@@ -107,7 +108,7 @@ func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
107 // Build body with details.
108 // Build body with details.
108 if allCompleted {
109 if allCompleted {
109 // Show all todos when all are completed, like when created.
110 // Show all todos when all are completed, like when created.
110− body = FormatTodosList(sty, meta.Todos, styles.ArrowRightIcon, width)
111+ body = FormatTodosList(sty, meta.Todos, styles.ArrowRightIcon, cappedWidth)
111 } else if meta.JustStarted != "" {
112 } else if meta.JustStarted != "" {
112 body = sty.Tool.TodoInProgressIcon.Render(styles.ArrowRightIcon+" ") +
113 body = sty.Tool.TodoInProgressIcon.Render(styles.ArrowRightIcon+" ") +
113 sty.Base.Render(meta.JustStarted)
114 sty.Base.Render(meta.JustStarted)
@@ -118,12 +119,12 @@ func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
118 }
119 }
119
120
120 toolParams := []string{headerText}
121 toolParams := []string{headerText}
121− header := toolHeader(sty, opts.Status, "To-Do", width, opts.Compact, toolParams...)
122+ header := toolHeader(sty, opts.Status, "To-Do", cappedWidth, opts.Compact, toolParams...)
122 if opts.Compact {
123 if opts.Compact {
123 return header
124 return header
124 }
125 }
125
126
126− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
127+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
127 return joinToolParts(header, earlyState)
128 return joinToolParts(header, earlyState)
128 }
129 }
129
130
internal/ui/chat/tools.go+8
@@ -295,6 +295,9 @@ func (t *baseToolMessageItem) Animate(msg anim.StepMsg) tea.Cmd {
295// RawRender implements [MessageItem].
295// RawRender implements [MessageItem].
296func (t *baseToolMessageItem) RawRender(width int) string {
296func (t *baseToolMessageItem) RawRender(width int) string {
297 toolItemWidth := width - MessageLeftPaddingTotal
297 toolItemWidth := width - MessageLeftPaddingTotal
298+ if t.hasCappedWidth {
299+ toolItemWidth = cappedMessageWidth(width)
300+ }
298
301
299 content, height, ok := t.getCachedRender(toolItemWidth)
302 content, height, ok := t.getCachedRender(toolItemWidth)
300 // if we are spinning or there is no cache rerender
303 // if we are spinning or there is no cache rerender
@@ -770,6 +773,11 @@ func roundedEnumerator(lPadding, width int) tree.Enumerator {
770func toolOutputMarkdownContent(sty *styles.Styles, content string, width int, expanded bool) string {
773func toolOutputMarkdownContent(sty *styles.Styles, content string, width int, expanded bool) string {
771 content = stringext.NormalizeSpace(content)
774 content = stringext.NormalizeSpace(content)
772
775
776+ // Cap width for readability.
777+ if width > maxTextWidth {
778+ width = maxTextWidth
779+ }
780+
773 renderer := common.PlainMarkdownRenderer(sty, width)
781 renderer := common.PlainMarkdownRenderer(sty, width)
774 rendered, err := renderer.Render(content)
782 rendered, err := renderer.Render(content)
775 if err != nil {
783 if err != nil {
internal/ui/chat/user.go+8−6
@@ -36,13 +36,15 @@ func NewUserMessageItem(sty *styles.Styles, message *message.Message, attachment
36
36
37// RawRender implements [MessageItem].
37// RawRender implements [MessageItem].
38func (m *UserMessageItem) RawRender(width int) string {
38func (m *UserMessageItem) RawRender(width int) string {
39+ cappedWidth := cappedMessageWidth(width)
40+
39− content, height, ok := m.getCachedRender(width)
41+ content, height, ok := m.getCachedRender(cappedWidth)
40 // cache hit
42 // cache hit
41 if ok {
43 if ok {
42− return m.renderHighlighted(content, width, height)
44+ return m.renderHighlighted(content, cappedWidth, height)
43 }
45 }
44
46
45− renderer := common.MarkdownRenderer(m.sty, width)
47+ renderer := common.MarkdownRenderer(m.sty, cappedWidth)
46
48
47 msgContent := strings.TrimSpace(m.message.Content().Text)
49 msgContent := strings.TrimSpace(m.message.Content().Text)
48 result, err := renderer.Render(msgContent)
50 result, err := renderer.Render(msgContent)
@@ -53,7 +55,7 @@ func (m *UserMessageItem) RawRender(width int) string {
53 }
55 }
54
56
55 if len(m.message.BinaryContent()) > 0 {
57 if len(m.message.BinaryContent()) > 0 {
56− attachmentsStr := m.renderAttachments(width)
58+ attachmentsStr := m.renderAttachments(cappedWidth)
57 if content == "" {
59 if content == "" {
58 content = attachmentsStr
60 content = attachmentsStr
59 } else {
61 } else {
@@ -62,8 +64,8 @@ func (m *UserMessageItem) RawRender(width int) string {
62 }
64 }
63
65
64 height = lipgloss.Height(content)
66 height = lipgloss.Height(content)
65− m.setCachedRender(content, width, height)
67+ m.setCachedRender(content, cappedWidth, height)
66− return m.renderHighlighted(content, width, height)
68+ return m.renderHighlighted(content, cappedWidth, height)
67}
69}
68
70
69// Render implements MessageItem.
71// Render implements MessageItem.