on main help sign in
revert: the width changes in #2127 (#2135)

This reverts commit fd437468b74e250f4d197b29c7857ce1ebbb406e.

commitf0c9985dde2485c8e384de20f67959b9e4fb9090 authorKujtim Hoxha <kujtimii.h@gmail.com> dateThu 5 Feb 2026, 14:12 (8 months ago) parentd29d0e2
15 files changed+124−86
internal/ui/chat/agent.go+8−6 internal/ui/chat/assistant.go+6−4 internal/ui/chat/bash.go+11−8 internal/ui/chat/diagnostics.go+4−3 internal/ui/chat/fetch.go+15−12 internal/ui/chat/file.go+15−12 internal/ui/chat/generic.go+5−4 internal/ui/chat/lsp_restart.go+4−3 internal/ui/chat/mcp.go+6−5 internal/ui/chat/messages.go+5 internal/ui/chat/references.go+4−3 internal/ui/chat/search.go+20−16 internal/ui/chat/todos.go+5−4 internal/ui/chat/tools.go+8 internal/ui/chat/user.go+8−6
internal/ui/chat/agent.go+8−6
@@ -99,6 +99,7 @@ type AgentToolRenderContext struct {
9999
100100// RenderTool implements the [ToolRenderer] interface.
101101func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
102+ cappedWidth := cappedMessageWidth(width)
102103 if !opts.ToolCall.Finished && !opts.IsCanceled() && len(r.agent.nestedTools) == 0 {
103104 return pendingTool(sty, "Agent", opts.Anim)
104105 }
@@ -109,7 +110,7 @@ func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
109110 prompt := params.Prompt
110111 prompt = strings.ReplaceAll(prompt, "\n", " ")
111112
112− header := toolHeader(sty, opts.Status, "Agent", width, opts.Compact)
113+ header := toolHeader(sty, opts.Status, "Agent", cappedWidth, opts.Compact)
113114 if opts.Compact {
114115 return header
115116 }
@@ -119,7 +120,7 @@ func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
119120 taskTagWidth := lipgloss.Width(taskTag)
120121
121122 // 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
123124
124125 promptText := sty.Tool.AgentPrompt.Width(remainingWidth).Render(prompt)
125126
@@ -156,7 +157,7 @@ func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
156157
157158 // Add body content when completed.
158159 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)
160161 return joinToolParts(result, body)
161162 }
162163
@@ -229,6 +230,7 @@ type agenticFetchParams struct {
229230
230231// RenderTool implements the [ToolRenderer] interface.
231232func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
233+ cappedWidth := cappedMessageWidth(width)
232234 if !opts.ToolCall.Finished && !opts.IsCanceled() && len(r.fetch.nestedTools) == 0 {
233235 return pendingTool(sty, "Agentic Fetch", opts.Anim)
234236 }
@@ -245,7 +247,7 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
245247 toolParams = append(toolParams, params.URL)
246248 }
247249
248− header := toolHeader(sty, opts.Status, "Agentic Fetch", width, opts.Compact, toolParams...)
250+ header := toolHeader(sty, opts.Status, "Agentic Fetch", cappedWidth, opts.Compact, toolParams...)
249251 if opts.Compact {
250252 return header
251253 }
@@ -255,7 +257,7 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
255257 promptTagWidth := lipgloss.Width(promptTag)
256258
257259 // 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
259261
260262 promptText := sty.Tool.AgentPrompt.Width(remainingWidth).Render(prompt)
261263
@@ -292,7 +294,7 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
292294
293295 // Add body content when completed.
294296 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)
296298 return joinToolParts(result, body)
297299 }
298300
internal/ui/chat/assistant.go+6−4
@@ -79,20 +79,22 @@ func (a *AssistantMessageItem) ID() string {
7979
8080// RawRender implements [MessageItem].
8181func (a *AssistantMessageItem) RawRender(width int) string {
82+ cappedWidth := cappedMessageWidth(width)
83+
8284 var spinner string
8385 if a.isSpinning() {
8486 spinner = a.renderSpinning()
8587 }
8688
87− content, height, ok := a.getCachedRender(width)
89+ content, height, ok := a.getCachedRender(cappedWidth)
8890 if !ok {
89− content = a.renderMessageContent(width)
91+ content = a.renderMessageContent(cappedWidth)
9092 height = lipgloss.Height(content)
9193 // cache the rendered content
92− a.setCachedRender(content, width, height)
94+ a.setCachedRender(content, cappedWidth, height)
9395 }
9496
95− highlightedContent := a.renderHighlighted(content, width, height)
97+ highlightedContent := a.renderHighlighted(content, cappedWidth, height)
9698 if spinner != "" {
9799 if highlightedContent != "" {
98100 highlightedContent += "\n\n"
internal/ui/chat/bash.go+11−8
@@ -39,6 +39,7 @@ type BashToolRenderContext struct{}
3939
4040// RenderTool implements the [ToolRenderer] interface.
4141func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
42+ cappedWidth := cappedMessageWidth(width)
4243 if opts.IsPending() {
4344 return pendingTool(sty, "Bash", opts.Anim)
4445 }
@@ -57,7 +58,7 @@ func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
5758 if meta.Background {
5859 description := cmp.Or(meta.Description, params.Command)
5960 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)
6162 }
6263
6364 // Regular bash command.
@@ -68,12 +69,12 @@ func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
6869 toolParams = append(toolParams, "background", "true")
6970 }
7071
71− header := toolHeader(sty, opts.Status, "Bash", width, opts.Compact, toolParams...)
72+ header := toolHeader(sty, opts.Status, "Bash", cappedWidth, opts.Compact, toolParams...)
7273 if opts.Compact {
7374 return header
7475 }
7576
76− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
77+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
7778 return joinToolParts(header, earlyState)
7879 }
7980
@@ -89,7 +90,7 @@ func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
8990 return header
9091 }
9192
92− bodyWidth := width - toolBodyLeftPaddingTotal
93+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
9394 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, output, bodyWidth, opts.ExpandedContent))
9495 return joinToolParts(header, body)
9596}
@@ -120,13 +121,14 @@ type JobOutputToolRenderContext struct{}
120121
121122// RenderTool implements the [ToolRenderer] interface.
122123func (j *JobOutputToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
124+ cappedWidth := cappedMessageWidth(width)
123125 if opts.IsPending() {
124126 return pendingTool(sty, "Job", opts.Anim)
125127 }
126128
127129 var params tools.JobOutputParams
128130 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)
130132 }
131133
132134 var description string
@@ -141,7 +143,7 @@ func (j *JobOutputToolRenderContext) RenderTool(sty *styles.Styles, width int, o
141143 if opts.HasResult() {
142144 content = opts.Result.Content
143145 }
144− return renderJobTool(sty, opts, width, "Output", params.ShellID, description, content)
146+ return renderJobTool(sty, opts, cappedWidth, "Output", params.ShellID, description, content)
145147}
146148
147149// -----------------------------------------------------------------------------
@@ -170,13 +172,14 @@ type JobKillToolRenderContext struct{}
170172
171173// RenderTool implements the [ToolRenderer] interface.
172174func (j *JobKillToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
175+ cappedWidth := cappedMessageWidth(width)
173176 if opts.IsPending() {
174177 return pendingTool(sty, "Job", opts.Anim)
175178 }
176179
177180 var params tools.JobKillParams
178181 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)
180183 }
181184
182185 var description string
@@ -191,7 +194,7 @@ func (j *JobKillToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
191194 if opts.HasResult() {
192195 content = opts.Result.Content
193196 }
194− return renderJobTool(sty, opts, width, "Kill", params.ShellID, description, content)
197+ return renderJobTool(sty, opts, cappedWidth, "Kill", params.ShellID, description, content)
195198}
196199
197200// renderJobTool renders a job-related tool with the common pattern:
internal/ui/chat/diagnostics.go+4−3
@@ -35,6 +35,7 @@ type DiagnosticsToolRenderContext struct{}
3535
3636// RenderTool implements the [ToolRenderer] interface.
3737func (d *DiagnosticsToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
38+ cappedWidth := cappedMessageWidth(width)
3839 if opts.IsPending() {
3940 return pendingTool(sty, "Diagnostics", opts.Anim)
4041 }
@@ -48,12 +49,12 @@ func (d *DiagnosticsToolRenderContext) RenderTool(sty *styles.Styles, width int,
4849 mainParam = fsext.PrettyPath(params.FilePath)
4950 }
5051
51− header := toolHeader(sty, opts.Status, "Diagnostics", width, opts.Compact, mainParam)
52+ header := toolHeader(sty, opts.Status, "Diagnostics", cappedWidth, opts.Compact, mainParam)
5253 if opts.Compact {
5354 return header
5455 }
5556
56− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
57+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
5758 return joinToolParts(header, earlyState)
5859 }
5960
@@ -61,7 +62,7 @@ func (d *DiagnosticsToolRenderContext) RenderTool(sty *styles.Styles, width int,
6162 return header
6263 }
6364
64− bodyWidth := width - toolBodyLeftPaddingTotal
65+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
6566 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
6667 return joinToolParts(header, body)
6768}
internal/ui/chat/fetch.go+15−12
@@ -34,13 +34,14 @@ type FetchToolRenderContext struct{}
3434
3535// RenderTool implements the [ToolRenderer] interface.
3636func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
37+ cappedWidth := cappedMessageWidth(width)
3738 if opts.IsPending() {
3839 return pendingTool(sty, "Fetch", opts.Anim)
3940 }
4041
4142 var params tools.FetchParams
4243 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)
4445 }
4546
4647 toolParams := []string{params.URL}
@@ -51,12 +52,12 @@ func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
5152 toolParams = append(toolParams, "timeout", formatTimeout(params.Timeout))
5253 }
5354
54− header := toolHeader(sty, opts.Status, "Fetch", width, opts.Compact, toolParams...)
55+ header := toolHeader(sty, opts.Status, "Fetch", cappedWidth, opts.Compact, toolParams...)
5556 if opts.Compact {
5657 return header
5758 }
5859
59− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
60+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
6061 return joinToolParts(header, earlyState)
6162 }
6263
@@ -66,7 +67,7 @@ func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
6667
6768 // Determine file extension for syntax highlighting based on format.
6869 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)
7071 return joinToolParts(header, body)
7172}
7273
@@ -108,22 +109,23 @@ type WebFetchToolRenderContext struct{}
108109
109110// RenderTool implements the [ToolRenderer] interface.
110111func (w *WebFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
112+ cappedWidth := cappedMessageWidth(width)
111113 if opts.IsPending() {
112114 return pendingTool(sty, "Fetch", opts.Anim)
113115 }
114116
115117 var params tools.WebFetchParams
116118 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)
118120 }
119121
120122 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...)
122124 if opts.Compact {
123125 return header
124126 }
125127
126− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
128+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
127129 return joinToolParts(header, earlyState)
128130 }
129131
@@ -131,7 +133,7 @@ func (w *WebFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, op
131133 return header
132134 }
133135
134− body := toolOutputMarkdownContent(sty, opts.Result.Content, width, opts.ExpandedContent)
136+ body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth, opts.ExpandedContent)
135137 return joinToolParts(header, body)
136138}
137139
@@ -161,22 +163,23 @@ type WebSearchToolRenderContext struct{}
161163
162164// RenderTool implements the [ToolRenderer] interface.
163165func (w *WebSearchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
166+ cappedWidth := cappedMessageWidth(width)
164167 if opts.IsPending() {
165168 return pendingTool(sty, "Search", opts.Anim)
166169 }
167170
168171 var params tools.WebSearchParams
169172 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)
171174 }
172175
173176 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...)
175178 if opts.Compact {
176179 return header
177180 }
178181
179− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
182+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
180183 return joinToolParts(header, earlyState)
181184 }
182185
@@ -184,6 +187,6 @@ func (w *WebSearchToolRenderContext) RenderTool(sty *styles.Styles, width int, o
184187 return header
185188 }
186189
187− body := toolOutputMarkdownContent(sty, opts.Result.Content, width, opts.ExpandedContent)
190+ body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth, opts.ExpandedContent)
188191 return joinToolParts(header, body)
189192}
internal/ui/chat/file.go+15−12
@@ -37,13 +37,14 @@ type ViewToolRenderContext struct{}
3737
3838// RenderTool implements the [ToolRenderer] interface.
3939func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
40+ cappedWidth := cappedMessageWidth(width)
4041 if opts.IsPending() {
4142 return pendingTool(sty, "View", opts.Anim)
4243 }
4344
4445 var params tools.ViewParams
4546 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)
4748 }
4849
4950 file := fsext.PrettyPath(params.FilePath)
@@ -55,12 +56,12 @@ func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
5556 toolParams = append(toolParams, "offset", fmt.Sprintf("%d", params.Offset))
5657 }
5758
58− header := toolHeader(sty, opts.Status, "View", width, opts.Compact, toolParams...)
59+ header := toolHeader(sty, opts.Status, "View", cappedWidth, opts.Compact, toolParams...)
5960 if opts.Compact {
6061 return header
6162 }
6263
63− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
64+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
6465 return joinToolParts(header, earlyState)
6566 }
6667
@@ -86,7 +87,7 @@ func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
8687 }
8788
8889 // 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)
9091 return joinToolParts(header, body)
9192}
9293
@@ -116,22 +117,23 @@ type WriteToolRenderContext struct{}
116117
117118// RenderTool implements the [ToolRenderer] interface.
118119func (w *WriteToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
120+ cappedWidth := cappedMessageWidth(width)
119121 if opts.IsPending() {
120122 return pendingTool(sty, "Write", opts.Anim)
121123 }
122124
123125 var params tools.WriteParams
124126 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)
126128 }
127129
128130 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)
130132 if opts.Compact {
131133 return header
132134 }
133135
134− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
136+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
135137 return joinToolParts(header, earlyState)
136138 }
137139
@@ -140,7 +142,7 @@ func (w *WriteToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
140142 }
141143
142144 // 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)
144146 return joinToolParts(header, body)
145147}
146148
@@ -301,13 +303,14 @@ type DownloadToolRenderContext struct{}
301303
302304// RenderTool implements the [ToolRenderer] interface.
303305func (d *DownloadToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
306+ cappedWidth := cappedMessageWidth(width)
304307 if opts.IsPending() {
305308 return pendingTool(sty, "Download", opts.Anim)
306309 }
307310
308311 var params tools.DownloadParams
309312 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)
311314 }
312315
313316 toolParams := []string{params.URL}
@@ -318,12 +321,12 @@ func (d *DownloadToolRenderContext) RenderTool(sty *styles.Styles, width int, op
318321 toolParams = append(toolParams, "timeout", formatTimeout(params.Timeout))
319322 }
320323
321− header := toolHeader(sty, opts.Status, "Download", width, opts.Compact, toolParams...)
324+ header := toolHeader(sty, opts.Status, "Download", cappedWidth, opts.Compact, toolParams...)
322325 if opts.Compact {
323326 return header
324327 }
325328
326− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
329+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
327330 return joinToolParts(header, earlyState)
328331 }
329332
@@ -331,7 +334,7 @@ func (d *DownloadToolRenderContext) RenderTool(sty *styles.Styles, width int, op
331334 return header
332335 }
333336
334− bodyWidth := width - toolBodyLeftPaddingTotal
337+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
335338 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
336339 return joinToolParts(header, body)
337340}
internal/ui/chat/generic.go+5−4
@@ -31,6 +31,7 @@ type GenericToolRenderContext struct{}
3131
3232// RenderTool implements the [ToolRenderer] interface.
3333func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
34+ cappedWidth := cappedMessageWidth(width)
3435 name := genericPrettyName(opts.ToolCall.Name)
3536
3637 if opts.IsPending() {
@@ -39,7 +40,7 @@ func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
3940
4041 var params map[string]any
4142 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)
4344 }
4445
4546 var toolParams []string
@@ -48,12 +49,12 @@ func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
4849 toolParams = append(toolParams, string(parsed))
4950 }
5051
51− header := toolHeader(sty, opts.Status, name, width, opts.Compact, toolParams...)
52+ header := toolHeader(sty, opts.Status, name, cappedWidth, opts.Compact, toolParams...)
5253 if opts.Compact {
5354 return header
5455 }
5556
56− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
57+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
5758 return joinToolParts(header, earlyState)
5859 }
5960
@@ -61,7 +62,7 @@ func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
6162 return header
6263 }
6364
64− bodyWidth := width - toolBodyLeftPaddingTotal
65+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
6566
6667 // Handle image data.
6768 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{}
3030
3131// RenderTool implements the [ToolRenderer] interface.
3232func (r *LSPRestartToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
33+ cappedWidth := cappedMessageWidth(width)
3334 if opts.IsPending() {
3435 return pendingTool(sty, "Restart LSP", opts.Anim)
3536 }
@@ -42,12 +43,12 @@ func (r *LSPRestartToolRenderContext) RenderTool(sty *styles.Styles, width int,
4243 toolParams = append(toolParams, params.Name)
4344 }
4445
45− header := toolHeader(sty, opts.Status, "Restart LSP", width, opts.Compact, toolParams...)
46+ header := toolHeader(sty, opts.Status, "Restart LSP", cappedWidth, opts.Compact, toolParams...)
4647 if opts.Compact {
4748 return header
4849 }
4950
50− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
51+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
5152 return joinToolParts(header, earlyState)
5253 }
5354
@@ -55,7 +56,7 @@ func (r *LSPRestartToolRenderContext) RenderTool(sty *styles.Styles, width int,
5556 return header
5657 }
5758
58− bodyWidth := width - toolBodyLeftPaddingTotal
59+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
5960 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
6061 return joinToolParts(header, body)
6162}
internal/ui/chat/mcp.go+6−5
@@ -32,9 +32,10 @@ type MCPToolRenderContext struct{}
3232
3333// RenderTool implements the [ToolRenderer] interface.
3434func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
35+ cappedWidth := cappedMessageWidth(width)
3536 toolNameParts := strings.SplitN(opts.ToolCall.Name, "_", 3)
3637 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)
3839 }
3940 mcpName := prettyName(toolNameParts[1])
4041 toolName := prettyName(toolNameParts[2])
@@ -50,7 +51,7 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T
5051
5152 var params map[string]any
5253 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)
5455 }
5556
5657 var toolParams []string
@@ -59,12 +60,12 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T
5960 toolParams = append(toolParams, string(parsed))
6061 }
6162
62− header := toolHeader(sty, opts.Status, name, width, opts.Compact, toolParams...)
63+ header := toolHeader(sty, opts.Status, name, cappedWidth, opts.Compact, toolParams...)
6364 if opts.Compact {
6465 return header
6566 }
6667
67− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
68+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
6869 return joinToolParts(header, earlyState)
6970 }
7071
@@ -72,7 +73,7 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T
7273 return header
7374 }
7475
75− bodyWidth := width - toolBodyLeftPaddingTotal
76+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
7677 // see if the result is json
7778 var result json.RawMessage
7879 var body string
internal/ui/chat/messages.go+5
@@ -245,6 +245,11 @@ func (a *AssistantInfoItem) renderContent(width int) string {
245245 return common.Section(a.sty, assistant, width)
246246}
247247
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+
248253// ExtractMessageItems extracts [MessageItem]s from a [message.Message]. It
249254// returns all parts of the message as [MessageItem]s.
250255//
internal/ui/chat/references.go+4−3
@@ -31,6 +31,7 @@ type ReferencesToolRenderContext struct{}
3131
3232// RenderTool implements the [ToolRenderer] interface.
3333func (r *ReferencesToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
34+ cappedWidth := cappedMessageWidth(width)
3435 if opts.IsPending() {
3536 return pendingTool(sty, "Find References", opts.Anim)
3637 }
@@ -43,12 +44,12 @@ func (r *ReferencesToolRenderContext) RenderTool(sty *styles.Styles, width int,
4344 toolParams = append(toolParams, "path", fsext.PrettyPath(params.Path))
4445 }
4546
46− header := toolHeader(sty, opts.Status, "Find References", width, opts.Compact, toolParams...)
47+ header := toolHeader(sty, opts.Status, "Find References", cappedWidth, opts.Compact, toolParams...)
4748 if opts.Compact {
4849 return header
4950 }
5051
51− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
52+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
5253 return joinToolParts(header, earlyState)
5354 }
5455
@@ -56,7 +57,7 @@ func (r *ReferencesToolRenderContext) RenderTool(sty *styles.Styles, width int,
5657 return header
5758 }
5859
59− bodyWidth := width - toolBodyLeftPaddingTotal
60+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
6061 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
6162 return joinToolParts(header, body)
6263}
internal/ui/chat/search.go+20−16
@@ -35,13 +35,14 @@ type GlobToolRenderContext struct{}
3535
3636// RenderTool implements the [ToolRenderer] interface.
3737func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
38+ cappedWidth := cappedMessageWidth(width)
3839 if opts.IsPending() {
3940 return pendingTool(sty, "Glob", opts.Anim)
4041 }
4142
4243 var params tools.GlobParams
4344 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)
4546 }
4647
4748 toolParams := []string{params.Pattern}
@@ -49,12 +50,12 @@ func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
4950 toolParams = append(toolParams, "path", params.Path)
5051 }
5152
52− header := toolHeader(sty, opts.Status, "Glob", width, opts.Compact, toolParams...)
53+ header := toolHeader(sty, opts.Status, "Glob", cappedWidth, opts.Compact, toolParams...)
5354 if opts.Compact {
5455 return header
5556 }
5657
57− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
58+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
5859 return joinToolParts(header, earlyState)
5960 }
6061
@@ -62,7 +63,7 @@ func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
6263 return header
6364 }
6465
65− bodyWidth := width - toolBodyLeftPaddingTotal
66+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
6667 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
6768 return joinToolParts(header, body)
6869}
@@ -93,13 +94,14 @@ type GrepToolRenderContext struct{}
9394
9495// RenderTool implements the [ToolRenderer] interface.
9596func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
97+ cappedWidth := cappedMessageWidth(width)
9698 if opts.IsPending() {
9799 return pendingTool(sty, "Grep", opts.Anim)
98100 }
99101
100102 var params tools.GrepParams
101103 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)
103105 }
104106
105107 toolParams := []string{params.Pattern}
@@ -113,12 +115,12 @@ func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
113115 toolParams = append(toolParams, "literal", "true")
114116 }
115117
116− header := toolHeader(sty, opts.Status, "Grep", width, opts.Compact, toolParams...)
118+ header := toolHeader(sty, opts.Status, "Grep", cappedWidth, opts.Compact, toolParams...)
117119 if opts.Compact {
118120 return header
119121 }
120122
121− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
123+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
122124 return joinToolParts(header, earlyState)
123125 }
124126
@@ -126,7 +128,7 @@ func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
126128 return header
127129 }
128130
129− bodyWidth := width - toolBodyLeftPaddingTotal
131+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
130132 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
131133 return joinToolParts(header, body)
132134}
@@ -157,13 +159,14 @@ type LSToolRenderContext struct{}
157159
158160// RenderTool implements the [ToolRenderer] interface.
159161func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
162+ cappedWidth := cappedMessageWidth(width)
160163 if opts.IsPending() {
161164 return pendingTool(sty, "List", opts.Anim)
162165 }
163166
164167 var params tools.LSParams
165168 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)
167170 }
168171
169172 path := params.Path
@@ -172,12 +175,12 @@ func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *To
172175 }
173176 path = fsext.PrettyPath(path)
174177
175− header := toolHeader(sty, opts.Status, "List", width, opts.Compact, path)
178+ header := toolHeader(sty, opts.Status, "List", cappedWidth, opts.Compact, path)
176179 if opts.Compact {
177180 return header
178181 }
179182
180− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
183+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
181184 return joinToolParts(header, earlyState)
182185 }
183186
@@ -185,7 +188,7 @@ func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *To
185188 return header
186189 }
187190
188− bodyWidth := width - toolBodyLeftPaddingTotal
191+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
189192 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
190193 return joinToolParts(header, body)
191194}
@@ -216,13 +219,14 @@ type SourcegraphToolRenderContext struct{}
216219
217220// RenderTool implements the [ToolRenderer] interface.
218221func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
222+ cappedWidth := cappedMessageWidth(width)
219223 if opts.IsPending() {
220224 return pendingTool(sty, "Sourcegraph", opts.Anim)
221225 }
222226
223227 var params tools.SourcegraphParams
224228 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)
226230 }
227231
228232 toolParams := []string{params.Query}
@@ -233,12 +237,12 @@ func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int,
233237 toolParams = append(toolParams, "context", formatNonZero(params.ContextWindow))
234238 }
235239
236− header := toolHeader(sty, opts.Status, "Sourcegraph", width, opts.Compact, toolParams...)
240+ header := toolHeader(sty, opts.Status, "Sourcegraph", cappedWidth, opts.Compact, toolParams...)
237241 if opts.Compact {
238242 return header
239243 }
240244
241− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
245+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
242246 return joinToolParts(header, earlyState)
243247 }
244248
@@ -246,7 +250,7 @@ func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int,
246250 return header
247251 }
248252
249− bodyWidth := width - toolBodyLeftPaddingTotal
253+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
250254 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
251255 return joinToolParts(header, body)
252256}
internal/ui/chat/todos.go+5−4
@@ -39,6 +39,7 @@ type TodosToolRenderContext struct{}
3939
4040// RenderTool implements the [ToolRenderer] interface.
4141func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
42+ cappedWidth := cappedMessageWidth(width)
4243 if opts.IsPending() {
4344 return pendingTool(sty, "To-Do", opts.Anim)
4445 }
@@ -81,7 +82,7 @@ func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
8182 } else {
8283 headerText = fmt.Sprintf("created %d todos", meta.Total)
8384 }
84− body = FormatTodosList(sty, meta.Todos, styles.ArrowRightIcon, width)
85+ body = FormatTodosList(sty, meta.Todos, styles.ArrowRightIcon, cappedWidth)
8586 } else {
8687 // Build header based on what changed.
8788 hasCompleted := len(meta.JustCompleted) > 0
@@ -107,7 +108,7 @@ func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
107108 // Build body with details.
108109 if allCompleted {
109110 // 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)
111112 } else if meta.JustStarted != "" {
112113 body = sty.Tool.TodoInProgressIcon.Render(styles.ArrowRightIcon+" ") +
113114 sty.Base.Render(meta.JustStarted)
@@ -118,12 +119,12 @@ func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
118119 }
119120
120121 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...)
122123 if opts.Compact {
123124 return header
124125 }
125126
126− if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
127+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
127128 return joinToolParts(header, earlyState)
128129 }
129130
internal/ui/chat/tools.go+8
@@ -295,6 +295,9 @@ func (t *baseToolMessageItem) Animate(msg anim.StepMsg) tea.Cmd {
295295// RawRender implements [MessageItem].
296296func (t *baseToolMessageItem) RawRender(width int) string {
297297 toolItemWidth := width - MessageLeftPaddingTotal
298+ if t.hasCappedWidth {
299+ toolItemWidth = cappedMessageWidth(width)
300+ }
298301
299302 content, height, ok := t.getCachedRender(toolItemWidth)
300303 // if we are spinning or there is no cache rerender
@@ -770,6 +773,11 @@ func roundedEnumerator(lPadding, width int) tree.Enumerator {
770773func toolOutputMarkdownContent(sty *styles.Styles, content string, width int, expanded bool) string {
771774 content = stringext.NormalizeSpace(content)
772775
776+ // Cap width for readability.
777+ if width > maxTextWidth {
778+ width = maxTextWidth
779+ }
780+
773781 renderer := common.PlainMarkdownRenderer(sty, width)
774782 rendered, err := renderer.Render(content)
775783 if err != nil {
internal/ui/chat/user.go+8−6
@@ -36,13 +36,15 @@ func NewUserMessageItem(sty *styles.Styles, message *message.Message, attachment
3636
3737// RawRender implements [MessageItem].
3838func (m *UserMessageItem) RawRender(width int) string {
39− content, height, ok := m.getCachedRender(width)
39+ cappedWidth := cappedMessageWidth(width)
40+
41+ content, height, ok := m.getCachedRender(cappedWidth)
4042 // cache hit
4143 if ok {
42− return m.renderHighlighted(content, width, height)
44+ return m.renderHighlighted(content, cappedWidth, height)
4345 }
4446
45− renderer := common.MarkdownRenderer(m.sty, width)
47+ renderer := common.MarkdownRenderer(m.sty, cappedWidth)
4648
4749 msgContent := strings.TrimSpace(m.message.Content().Text)
4850 result, err := renderer.Render(msgContent)
@@ -53,7 +55,7 @@ func (m *UserMessageItem) RawRender(width int) string {
5355 }
5456
5557 if len(m.message.BinaryContent()) > 0 {
56− attachmentsStr := m.renderAttachments(width)
58+ attachmentsStr := m.renderAttachments(cappedWidth)
5759 if content == "" {
5860 content = attachmentsStr
5961 } else {
@@ -62,8 +64,8 @@ func (m *UserMessageItem) RawRender(width int) string {
6264 }
6365
6466 height = lipgloss.Height(content)
65− m.setCachedRender(content, width, height)
66− return m.renderHighlighted(content, width, height)
67+ m.setCachedRender(content, cappedWidth, height)
68+ return m.renderHighlighted(content, cappedWidth, height)
6769}
6870
6971// Render implements MessageItem.