revert: the width changes in #2127 (#2135)

Kujtim Hoxha created

This reverts commit fd437468b74e250f4d197b29c7857ce1ebbb406e.

Change summary

internal/ui/chat/agent.go       | 14 +++++++-----
internal/ui/chat/assistant.go   | 10 +++++---
internal/ui/chat/bash.go        | 19 ++++++++++-------
internal/ui/chat/diagnostics.go |  7 +++--
internal/ui/chat/fetch.go       | 27 ++++++++++++++-----------
internal/ui/chat/file.go        | 27 ++++++++++++++-----------
internal/ui/chat/generic.go     |  9 ++++---
internal/ui/chat/lsp_restart.go |  7 +++--
internal/ui/chat/mcp.go         | 11 +++++----
internal/ui/chat/messages.go    |  5 ++++
internal/ui/chat/references.go  |  7 +++--
internal/ui/chat/search.go      | 36 +++++++++++++++++++---------------
internal/ui/chat/todos.go       |  9 ++++---
internal/ui/chat/tools.go       |  8 +++++++
internal/ui/chat/user.go        | 14 +++++++-----
15 files changed, 124 insertions(+), 86 deletions(-)

Detailed changes

internal/ui/chat/agent.go 🔗

@@ -99,6 +99,7 @@ type AgentToolRenderContext struct {
 
 // RenderTool implements the [ToolRenderer] interface.
 func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if !opts.ToolCall.Finished && !opts.IsCanceled() && len(r.agent.nestedTools) == 0 {
 		return pendingTool(sty, "Agent", opts.Anim)
 	}
@@ -109,7 +110,7 @@ func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
 	prompt := params.Prompt
 	prompt = strings.ReplaceAll(prompt, "\n", " ")
 
-	header := toolHeader(sty, opts.Status, "Agent", width, opts.Compact)
+	header := toolHeader(sty, opts.Status, "Agent", cappedWidth, opts.Compact)
 	if opts.Compact {
 		return header
 	}
@@ -119,7 +120,7 @@ func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
 	taskTagWidth := lipgloss.Width(taskTag)
 
 	// Calculate remaining width for prompt.
-	remainingWidth := width - taskTagWidth - 3 // -3 for spacing
+	remainingWidth := min(cappedWidth-taskTagWidth-3, maxTextWidth-taskTagWidth-3) // -3 for spacing
 
 	promptText := sty.Tool.AgentPrompt.Width(remainingWidth).Render(prompt)
 
@@ -156,7 +157,7 @@ func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
 
 	// Add body content when completed.
 	if opts.HasResult() && opts.Result.Content != "" {
-		body := toolOutputMarkdownContent(sty, opts.Result.Content, width-toolBodyLeftPaddingTotal, opts.ExpandedContent)
+		body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth-toolBodyLeftPaddingTotal, opts.ExpandedContent)
 		return joinToolParts(result, body)
 	}
 
@@ -229,6 +230,7 @@ type agenticFetchParams struct {
 
 // RenderTool implements the [ToolRenderer] interface.
 func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if !opts.ToolCall.Finished && !opts.IsCanceled() && len(r.fetch.nestedTools) == 0 {
 		return pendingTool(sty, "Agentic Fetch", opts.Anim)
 	}
@@ -245,7 +247,7 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
 		toolParams = append(toolParams, params.URL)
 	}
 
-	header := toolHeader(sty, opts.Status, "Agentic Fetch", width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, "Agentic Fetch", cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
@@ -255,7 +257,7 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
 	promptTagWidth := lipgloss.Width(promptTag)
 
 	// Calculate remaining width for prompt text.
-	remainingWidth := width - promptTagWidth - 3 // -3 for spacing
+	remainingWidth := min(cappedWidth-promptTagWidth-3, maxTextWidth-promptTagWidth-3) // -3 for spacing
 
 	promptText := sty.Tool.AgentPrompt.Width(remainingWidth).Render(prompt)
 
@@ -292,7 +294,7 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
 
 	// Add body content when completed.
 	if opts.HasResult() && opts.Result.Content != "" {
-		body := toolOutputMarkdownContent(sty, opts.Result.Content, width-toolBodyLeftPaddingTotal, opts.ExpandedContent)
+		body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth-toolBodyLeftPaddingTotal, opts.ExpandedContent)
 		return joinToolParts(result, body)
 	}
 

internal/ui/chat/assistant.go 🔗

@@ -79,20 +79,22 @@ func (a *AssistantMessageItem) ID() string {
 
 // RawRender implements [MessageItem].
 func (a *AssistantMessageItem) RawRender(width int) string {
+	cappedWidth := cappedMessageWidth(width)
+
 	var spinner string
 	if a.isSpinning() {
 		spinner = a.renderSpinning()
 	}
 
-	content, height, ok := a.getCachedRender(width)
+	content, height, ok := a.getCachedRender(cappedWidth)
 	if !ok {
-		content = a.renderMessageContent(width)
+		content = a.renderMessageContent(cappedWidth)
 		height = lipgloss.Height(content)
 		// cache the rendered content
-		a.setCachedRender(content, width, height)
+		a.setCachedRender(content, cappedWidth, height)
 	}
 
-	highlightedContent := a.renderHighlighted(content, width, height)
+	highlightedContent := a.renderHighlighted(content, cappedWidth, height)
 	if spinner != "" {
 		if highlightedContent != "" {
 			highlightedContent += "\n\n"

internal/ui/chat/bash.go 🔗

@@ -39,6 +39,7 @@ type BashToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Bash", opts.Anim)
 	}
@@ -57,7 +58,7 @@ func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
 	if meta.Background {
 		description := cmp.Or(meta.Description, params.Command)
 		content := "Command: " + params.Command + "\n" + opts.Result.Content
-		return renderJobTool(sty, opts, width, "Start", meta.ShellID, description, content)
+		return renderJobTool(sty, opts, cappedWidth, "Start", meta.ShellID, description, content)
 	}
 
 	// Regular bash command.
@@ -68,12 +69,12 @@ func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
 		toolParams = append(toolParams, "background", "true")
 	}
 
-	header := toolHeader(sty, opts.Status, "Bash", width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, "Bash", cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -89,7 +90,7 @@ func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
 		return header
 	}
 
-	bodyWidth := width - toolBodyLeftPaddingTotal
+	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
 	body := sty.Tool.Body.Render(toolOutputPlainContent(sty, output, bodyWidth, opts.ExpandedContent))
 	return joinToolParts(header, body)
 }
@@ -120,13 +121,14 @@ type JobOutputToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (j *JobOutputToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Job", opts.Anim)
 	}
 
 	var params tools.JobOutputParams
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	var description string
@@ -141,7 +143,7 @@ func (j *JobOutputToolRenderContext) RenderTool(sty *styles.Styles, width int, o
 	if opts.HasResult() {
 		content = opts.Result.Content
 	}
-	return renderJobTool(sty, opts, width, "Output", params.ShellID, description, content)
+	return renderJobTool(sty, opts, cappedWidth, "Output", params.ShellID, description, content)
 }
 
 // -----------------------------------------------------------------------------
@@ -170,13 +172,14 @@ type JobKillToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (j *JobKillToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Job", opts.Anim)
 	}
 
 	var params tools.JobKillParams
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	var description string
@@ -191,7 +194,7 @@ func (j *JobKillToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
 	if opts.HasResult() {
 		content = opts.Result.Content
 	}
-	return renderJobTool(sty, opts, width, "Kill", params.ShellID, description, content)
+	return renderJobTool(sty, opts, cappedWidth, "Kill", params.ShellID, description, content)
 }
 
 // renderJobTool renders a job-related tool with the common pattern:

internal/ui/chat/diagnostics.go 🔗

@@ -35,6 +35,7 @@ type DiagnosticsToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (d *DiagnosticsToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Diagnostics", opts.Anim)
 	}
@@ -48,12 +49,12 @@ func (d *DiagnosticsToolRenderContext) RenderTool(sty *styles.Styles, width int,
 		mainParam = fsext.PrettyPath(params.FilePath)
 	}
 
-	header := toolHeader(sty, opts.Status, "Diagnostics", width, opts.Compact, mainParam)
+	header := toolHeader(sty, opts.Status, "Diagnostics", cappedWidth, opts.Compact, mainParam)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -61,7 +62,7 @@ func (d *DiagnosticsToolRenderContext) RenderTool(sty *styles.Styles, width int,
 		return header
 	}
 
-	bodyWidth := width - toolBodyLeftPaddingTotal
+	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
 	body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
 	return joinToolParts(header, body)
 }

internal/ui/chat/fetch.go 🔗

@@ -34,13 +34,14 @@ type FetchToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Fetch", opts.Anim)
 	}
 
 	var params tools.FetchParams
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	toolParams := []string{params.URL}
@@ -51,12 +52,12 @@ func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
 		toolParams = append(toolParams, "timeout", formatTimeout(params.Timeout))
 	}
 
-	header := toolHeader(sty, opts.Status, "Fetch", width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, "Fetch", cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -66,7 +67,7 @@ func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
 
 	// Determine file extension for syntax highlighting based on format.
 	file := getFileExtensionForFormat(params.Format)
-	body := toolOutputCodeContent(sty, file, opts.Result.Content, 0, width, opts.ExpandedContent)
+	body := toolOutputCodeContent(sty, file, opts.Result.Content, 0, cappedWidth, opts.ExpandedContent)
 	return joinToolParts(header, body)
 }
 
@@ -108,22 +109,23 @@ type WebFetchToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (w *WebFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Fetch", opts.Anim)
 	}
 
 	var params tools.WebFetchParams
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	toolParams := []string{params.URL}
-	header := toolHeader(sty, opts.Status, "Fetch", width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, "Fetch", cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -131,7 +133,7 @@ func (w *WebFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, op
 		return header
 	}
 
-	body := toolOutputMarkdownContent(sty, opts.Result.Content, width, opts.ExpandedContent)
+	body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth, opts.ExpandedContent)
 	return joinToolParts(header, body)
 }
 
@@ -161,22 +163,23 @@ type WebSearchToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (w *WebSearchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Search", opts.Anim)
 	}
 
 	var params tools.WebSearchParams
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	toolParams := []string{params.Query}
-	header := toolHeader(sty, opts.Status, "Search", width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, "Search", cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -184,6 +187,6 @@ func (w *WebSearchToolRenderContext) RenderTool(sty *styles.Styles, width int, o
 		return header
 	}
 
-	body := toolOutputMarkdownContent(sty, opts.Result.Content, width, opts.ExpandedContent)
+	body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth, opts.ExpandedContent)
 	return joinToolParts(header, body)
 }

internal/ui/chat/file.go 🔗

@@ -37,13 +37,14 @@ type ViewToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "View", opts.Anim)
 	}
 
 	var params tools.ViewParams
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	file := fsext.PrettyPath(params.FilePath)
@@ -55,12 +56,12 @@ func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
 		toolParams = append(toolParams, "offset", fmt.Sprintf("%d", params.Offset))
 	}
 
-	header := toolHeader(sty, opts.Status, "View", width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, "View", cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -86,7 +87,7 @@ func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
 	}
 
 	// Render code content with syntax highlighting.
-	body := toolOutputCodeContent(sty, params.FilePath, content, params.Offset, width, opts.ExpandedContent)
+	body := toolOutputCodeContent(sty, params.FilePath, content, params.Offset, cappedWidth, opts.ExpandedContent)
 	return joinToolParts(header, body)
 }
 
@@ -116,22 +117,23 @@ type WriteToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (w *WriteToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Write", opts.Anim)
 	}
 
 	var params tools.WriteParams
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	file := fsext.PrettyPath(params.FilePath)
-	header := toolHeader(sty, opts.Status, "Write", width, opts.Compact, file)
+	header := toolHeader(sty, opts.Status, "Write", cappedWidth, opts.Compact, file)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -140,7 +142,7 @@ func (w *WriteToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
 	}
 
 	// Render code content with syntax highlighting.
-	body := toolOutputCodeContent(sty, params.FilePath, params.Content, 0, width, opts.ExpandedContent)
+	body := toolOutputCodeContent(sty, params.FilePath, params.Content, 0, cappedWidth, opts.ExpandedContent)
 	return joinToolParts(header, body)
 }
 
@@ -301,13 +303,14 @@ type DownloadToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (d *DownloadToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Download", opts.Anim)
 	}
 
 	var params tools.DownloadParams
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	toolParams := []string{params.URL}
@@ -318,12 +321,12 @@ func (d *DownloadToolRenderContext) RenderTool(sty *styles.Styles, width int, op
 		toolParams = append(toolParams, "timeout", formatTimeout(params.Timeout))
 	}
 
-	header := toolHeader(sty, opts.Status, "Download", width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, "Download", cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -331,7 +334,7 @@ func (d *DownloadToolRenderContext) RenderTool(sty *styles.Styles, width int, op
 		return header
 	}
 
-	bodyWidth := width - toolBodyLeftPaddingTotal
+	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
 	body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
 	return joinToolParts(header, body)
 }

internal/ui/chat/generic.go 🔗

@@ -31,6 +31,7 @@ type GenericToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	name := genericPrettyName(opts.ToolCall.Name)
 
 	if opts.IsPending() {
@@ -39,7 +40,7 @@ func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
 
 	var params map[string]any
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	var toolParams []string
@@ -48,12 +49,12 @@ func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
 		toolParams = append(toolParams, string(parsed))
 	}
 
-	header := toolHeader(sty, opts.Status, name, width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, name, cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -61,7 +62,7 @@ func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opt
 		return header
 	}
 
-	bodyWidth := width - toolBodyLeftPaddingTotal
+	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
 
 	// Handle image data.
 	if opts.Result.Data != "" && strings.HasPrefix(opts.Result.MIMEType, "image/") {

internal/ui/chat/lsp_restart.go 🔗

@@ -30,6 +30,7 @@ type LSPRestartToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (r *LSPRestartToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Restart LSP", opts.Anim)
 	}
@@ -42,12 +43,12 @@ func (r *LSPRestartToolRenderContext) RenderTool(sty *styles.Styles, width int,
 		toolParams = append(toolParams, params.Name)
 	}
 
-	header := toolHeader(sty, opts.Status, "Restart LSP", width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, "Restart LSP", cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -55,7 +56,7 @@ func (r *LSPRestartToolRenderContext) RenderTool(sty *styles.Styles, width int,
 		return header
 	}
 
-	bodyWidth := width - toolBodyLeftPaddingTotal
+	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
 	body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
 	return joinToolParts(header, body)
 }

internal/ui/chat/mcp.go 🔗

@@ -32,9 +32,10 @@ type MCPToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	toolNameParts := strings.SplitN(opts.ToolCall.Name, "_", 3)
 	if len(toolNameParts) != 3 {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid tool name"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid tool name"}, cappedWidth)
 	}
 	mcpName := prettyName(toolNameParts[1])
 	toolName := prettyName(toolNameParts[2])
@@ -50,7 +51,7 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T
 
 	var params map[string]any
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	var toolParams []string
@@ -59,12 +60,12 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T
 		toolParams = append(toolParams, string(parsed))
 	}
 
-	header := toolHeader(sty, opts.Status, name, width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, name, cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -72,7 +73,7 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T
 		return header
 	}
 
-	bodyWidth := width - toolBodyLeftPaddingTotal
+	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
 	// see if the result is json
 	var result json.RawMessage
 	var body string

internal/ui/chat/messages.go 🔗

@@ -245,6 +245,11 @@ func (a *AssistantInfoItem) renderContent(width int) string {
 	return common.Section(a.sty, assistant, width)
 }
 
+// cappedMessageWidth returns the maximum width for message content for readability.
+func cappedMessageWidth(availableWidth int) int {
+	return min(availableWidth-MessageLeftPaddingTotal, maxTextWidth)
+}
+
 // ExtractMessageItems extracts [MessageItem]s from a [message.Message]. It
 // returns all parts of the message as [MessageItem]s.
 //

internal/ui/chat/references.go 🔗

@@ -31,6 +31,7 @@ type ReferencesToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (r *ReferencesToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Find References", opts.Anim)
 	}
@@ -43,12 +44,12 @@ func (r *ReferencesToolRenderContext) RenderTool(sty *styles.Styles, width int,
 		toolParams = append(toolParams, "path", fsext.PrettyPath(params.Path))
 	}
 
-	header := toolHeader(sty, opts.Status, "Find References", width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, "Find References", cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -56,7 +57,7 @@ func (r *ReferencesToolRenderContext) RenderTool(sty *styles.Styles, width int,
 		return header
 	}
 
-	bodyWidth := width - toolBodyLeftPaddingTotal
+	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
 	body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
 	return joinToolParts(header, body)
 }

internal/ui/chat/search.go 🔗

@@ -35,13 +35,14 @@ type GlobToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Glob", opts.Anim)
 	}
 
 	var params tools.GlobParams
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	toolParams := []string{params.Pattern}
@@ -49,12 +50,12 @@ func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
 		toolParams = append(toolParams, "path", params.Path)
 	}
 
-	header := toolHeader(sty, opts.Status, "Glob", width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, "Glob", cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -62,7 +63,7 @@ func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
 		return header
 	}
 
-	bodyWidth := width - toolBodyLeftPaddingTotal
+	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
 	body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
 	return joinToolParts(header, body)
 }
@@ -93,13 +94,14 @@ type GrepToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Grep", opts.Anim)
 	}
 
 	var params tools.GrepParams
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	toolParams := []string{params.Pattern}
@@ -113,12 +115,12 @@ func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
 		toolParams = append(toolParams, "literal", "true")
 	}
 
-	header := toolHeader(sty, opts.Status, "Grep", width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, "Grep", cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -126,7 +128,7 @@ func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
 		return header
 	}
 
-	bodyWidth := width - toolBodyLeftPaddingTotal
+	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
 	body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
 	return joinToolParts(header, body)
 }
@@ -157,13 +159,14 @@ type LSToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "List", opts.Anim)
 	}
 
 	var params tools.LSParams
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	path := params.Path
@@ -172,12 +175,12 @@ func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *To
 	}
 	path = fsext.PrettyPath(path)
 
-	header := toolHeader(sty, opts.Status, "List", width, opts.Compact, path)
+	header := toolHeader(sty, opts.Status, "List", cappedWidth, opts.Compact, path)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -185,7 +188,7 @@ func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *To
 		return header
 	}
 
-	bodyWidth := width - toolBodyLeftPaddingTotal
+	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
 	body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
 	return joinToolParts(header, body)
 }
@@ -216,13 +219,14 @@ type SourcegraphToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "Sourcegraph", opts.Anim)
 	}
 
 	var params tools.SourcegraphParams
 	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
-		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
+		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 	}
 
 	toolParams := []string{params.Query}
@@ -233,12 +237,12 @@ func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int,
 		toolParams = append(toolParams, "context", formatNonZero(params.ContextWindow))
 	}
 
-	header := toolHeader(sty, opts.Status, "Sourcegraph", width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, "Sourcegraph", cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 
@@ -246,7 +250,7 @@ func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int,
 		return header
 	}
 
-	bodyWidth := width - toolBodyLeftPaddingTotal
+	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
 	body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
 	return joinToolParts(header, body)
 }

internal/ui/chat/todos.go 🔗

@@ -39,6 +39,7 @@ type TodosToolRenderContext struct{}
 
 // RenderTool implements the [ToolRenderer] interface.
 func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+	cappedWidth := cappedMessageWidth(width)
 	if opts.IsPending() {
 		return pendingTool(sty, "To-Do", opts.Anim)
 	}
@@ -81,7 +82,7 @@ func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
 					} else {
 						headerText = fmt.Sprintf("created %d todos", meta.Total)
 					}
-					body = FormatTodosList(sty, meta.Todos, styles.ArrowRightIcon, width)
+					body = FormatTodosList(sty, meta.Todos, styles.ArrowRightIcon, cappedWidth)
 				} else {
 					// Build header based on what changed.
 					hasCompleted := len(meta.JustCompleted) > 0
@@ -107,7 +108,7 @@ func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
 					// Build body with details.
 					if allCompleted {
 						// Show all todos when all are completed, like when created.
-						body = FormatTodosList(sty, meta.Todos, styles.ArrowRightIcon, width)
+						body = FormatTodosList(sty, meta.Todos, styles.ArrowRightIcon, cappedWidth)
 					} else if meta.JustStarted != "" {
 						body = sty.Tool.TodoInProgressIcon.Render(styles.ArrowRightIcon+" ") +
 							sty.Base.Render(meta.JustStarted)
@@ -118,12 +119,12 @@ func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
 	}
 
 	toolParams := []string{headerText}
-	header := toolHeader(sty, opts.Status, "To-Do", width, opts.Compact, toolParams...)
+	header := toolHeader(sty, opts.Status, "To-Do", cappedWidth, opts.Compact, toolParams...)
 	if opts.Compact {
 		return header
 	}
 
-	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
+	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 		return joinToolParts(header, earlyState)
 	}
 

internal/ui/chat/tools.go 🔗

@@ -295,6 +295,9 @@ func (t *baseToolMessageItem) Animate(msg anim.StepMsg) tea.Cmd {
 // RawRender implements [MessageItem].
 func (t *baseToolMessageItem) RawRender(width int) string {
 	toolItemWidth := width - MessageLeftPaddingTotal
+	if t.hasCappedWidth {
+		toolItemWidth = cappedMessageWidth(width)
+	}
 
 	content, height, ok := t.getCachedRender(toolItemWidth)
 	// if we are spinning or there is no cache rerender
@@ -770,6 +773,11 @@ func roundedEnumerator(lPadding, width int) tree.Enumerator {
 func toolOutputMarkdownContent(sty *styles.Styles, content string, width int, expanded bool) string {
 	content = stringext.NormalizeSpace(content)
 
+	// Cap width for readability.
+	if width > maxTextWidth {
+		width = maxTextWidth
+	}
+
 	renderer := common.PlainMarkdownRenderer(sty, width)
 	rendered, err := renderer.Render(content)
 	if err != nil {

internal/ui/chat/user.go 🔗

@@ -36,13 +36,15 @@ func NewUserMessageItem(sty *styles.Styles, message *message.Message, attachment
 
 // RawRender implements [MessageItem].
 func (m *UserMessageItem) RawRender(width int) string {
-	content, height, ok := m.getCachedRender(width)
+	cappedWidth := cappedMessageWidth(width)
+
+	content, height, ok := m.getCachedRender(cappedWidth)
 	// cache hit
 	if ok {
-		return m.renderHighlighted(content, width, height)
+		return m.renderHighlighted(content, cappedWidth, height)
 	}
 
-	renderer := common.MarkdownRenderer(m.sty, width)
+	renderer := common.MarkdownRenderer(m.sty, cappedWidth)
 
 	msgContent := strings.TrimSpace(m.message.Content().Text)
 	result, err := renderer.Render(msgContent)
@@ -53,7 +55,7 @@ func (m *UserMessageItem) RawRender(width int) string {
 	}
 
 	if len(m.message.BinaryContent()) > 0 {
-		attachmentsStr := m.renderAttachments(width)
+		attachmentsStr := m.renderAttachments(cappedWidth)
 		if content == "" {
 			content = attachmentsStr
 		} else {
@@ -62,8 +64,8 @@ func (m *UserMessageItem) RawRender(width int) string {
 	}
 
 	height = lipgloss.Height(content)
-	m.setCachedRender(content, width, height)
-	return m.renderHighlighted(content, width, height)
+	m.setCachedRender(content, cappedWidth, height)
+	return m.renderHighlighted(content, cappedWidth, height)
 }
 
 // Render implements MessageItem.