Detailed changes
@@ -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)
}
@@ -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"
@@ -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), ¶ms); 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), ¶ms); 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:
@@ -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)
}
@@ -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), ¶ms); 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), ¶ms); 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), ¶ms); 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)
}
@@ -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), ¶ms); 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), ¶ms); 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), ¶ms); 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)
}
@@ -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), ¶ms); 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/") {
@@ -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)
}
@@ -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), ¶ms); 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
@@ -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.
//
@@ -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)
}
@@ -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), ¶ms); 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), ¶ms); 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), ¶ms); 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), ¶ms); 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)
}
@@ -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)
}
@@ -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 {
@@ -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.