Add separator before truncated tool footers
- commit
- 47257c8a544bc7f86a215f446167282e4be4bd97
- author
- patriceckhart · · 2026-05-22 15:12 UTC
- parent
- 14ffdb6
- signature
- unsigned
2 files · +17 -2
M internal/agent/tools/read.go +3 -0
| @@ -131,6 +131,9 @@ func (t *ReadTool) Execute(ctx context.Context, raw json.RawMessage, progress fu | |||
| 131 | 131 | sb.WriteString(line) | |
| 132 | 132 | sb.WriteByte('\n') | |
| 133 | 133 | } | |
| 134 | + | if truncLines || truncBytes { | |
| 135 | + | sb.WriteString("\n") | |
| 136 | + | } | |
| 134 | 137 | if truncLines { | |
| 135 | 138 | sb.WriteString(fmt.Sprintf("... [truncated at %d lines]\n", maxReadLines)) | |
| 136 | 139 | } | |
| @@ -131,6 +131,9 @@ func (t *ReadTool) Execute(ctx context.Context, raw json.RawMessage, progress fu | |||||
| 131 | sb.WriteString(line) | 131 | sb.WriteString(line) | ||
| 132 | sb.WriteByte('\n') | 132 | sb.WriteByte('\n') | ||
| 133 | } | 133 | } | ||
| 134 | + | if truncLines || truncBytes { | |||
| 135 | + | sb.WriteString("\n") | |||
| 136 | + | } | |||
| 134 | if truncLines { | 137 | if truncLines { | ||
| 135 | sb.WriteString(fmt.Sprintf("... [truncated at %d lines]\n", maxReadLines)) | 138 | sb.WriteString(fmt.Sprintf("... [truncated at %d lines]\n", maxReadLines)) | ||
| 136 | } | 139 | } | ||
M internal/tui/view.go +14 -2
| @@ -1113,7 +1113,9 @@ func (v *View) collapseToolBody(lines []string, hasImage bool) []string { | |||
| 1113 | 1113 | total := len(lines) | |
| 1114 | 1114 | footer := fmt.Sprintf(" ... (%d more lines, %d total, ctrl+o to expand)", hidden, total) | |
| 1115 | 1115 | footer = v.Theme.FG256(v.Theme.Muted, footer) | |
| 1116 | - | return append(append([]string(nil), kept...), footer) | |
| 1116 | + | out := append([]string(nil), kept...) | |
| 1117 | + | out = append(out, "") | |
| 1118 | + | return append(out, footer) | |
| 1117 | 1119 | } | |
| 1118 | 1120 | ||
| 1119 | 1121 | // renderToolText renders a text block inside a tool result. If the | |
| @@ -1179,6 +1181,11 @@ func (v *View) renderToolText(text string, width, defaultColor int, sourcePath s | |||
| 1179 | 1181 | out = append(out, " "+v.Theme.FG256(v.Theme.Muted, l)) | |
| 1180 | 1182 | continue | |
| 1181 | 1183 | } | |
| 1184 | + | if inDiff && strings.TrimSpace(l) == "..." { | |
| 1185 | + | out = append(out, "") | |
| 1186 | + | out = append(out, " "+v.Theme.FG256(v.Theme.Muted, "...")) | |
| 1187 | + | continue | |
| 1188 | + | } | |
| 1182 | 1189 | if inDiff && len(l) > 0 { | |
| 1183 | 1190 | switch l[0] { | |
| 1184 | 1191 | case '+': | |
| @@ -1716,7 +1723,9 @@ func (v *View) renderRawFile(text, sourcePath string, startLine int) []string { | |||
| 1716 | 1723 | lines = lines[:n-1] | |
| 1717 | 1724 | } | |
| 1718 | 1725 | // Split code from trailing footer lines ("... [truncated ...]") | |
| 1719 | - | // so we don't number the footer. | |
| 1726 | + | // so we don't number the footer. A single blank separator line | |
| 1727 | + | // immediately before the footer is also pulled out so the | |
| 1728 | + | // gutter doesn't render a phantom number for it. | |
| 1720 | 1729 | codeEnd := len(lines) | |
| 1721 | 1730 | for i := len(lines) - 1; i >= 0; i-- { | |
| 1722 | 1731 | if strings.HasPrefix(lines[i], "...") { | |
| @@ -1725,6 +1734,9 @@ func (v *View) renderRawFile(text, sourcePath string, startLine int) []string { | |||
| 1725 | 1734 | } | |
| 1726 | 1735 | break | |
| 1727 | 1736 | } | |
| 1737 | + | if codeEnd > 0 && codeEnd < len(lines) && lines[codeEnd-1] == "" { | |
| 1738 | + | codeEnd-- | |
| 1739 | + | } | |
| 1728 | 1740 | code := lines[:codeEnd] | |
| 1729 | 1741 | footer := lines[codeEnd:] | |
| 1730 | 1742 | ||
| @@ -1113,7 +1113,9 @@ func (v *View) collapseToolBody(lines []string, hasImage bool) []string { | |||||
| 1113 | total := len(lines) | 1113 | total := len(lines) | ||
| 1114 | footer := fmt.Sprintf(" ... (%d more lines, %d total, ctrl+o to expand)", hidden, total) | 1114 | footer := fmt.Sprintf(" ... (%d more lines, %d total, ctrl+o to expand)", hidden, total) | ||
| 1115 | footer = v.Theme.FG256(v.Theme.Muted, footer) | 1115 | footer = v.Theme.FG256(v.Theme.Muted, footer) | ||
| 1116 | - | return append(append([]string(nil), kept...), footer) | 1116 | + | out := append([]string(nil), kept...) |
| 1117 | + | out = append(out, "") | |||
| 1118 | + | return append(out, footer) | |||
| 1117 | } | 1119 | } | ||
| 1118 | 1120 | ||||
| 1119 | // renderToolText renders a text block inside a tool result. If the | 1121 | // renderToolText renders a text block inside a tool result. If the | ||
| @@ -1179,6 +1181,11 @@ func (v *View) renderToolText(text string, width, defaultColor int, sourcePath s | |||||
| 1179 | out = append(out, " "+v.Theme.FG256(v.Theme.Muted, l)) | 1181 | out = append(out, " "+v.Theme.FG256(v.Theme.Muted, l)) | ||
| 1180 | continue | 1182 | continue | ||
| 1181 | } | 1183 | } | ||
| 1184 | + | if inDiff && strings.TrimSpace(l) == "..." { | |||
| 1185 | + | out = append(out, "") | |||
| 1186 | + | out = append(out, " "+v.Theme.FG256(v.Theme.Muted, "...")) | |||
| 1187 | + | continue | |||
| 1188 | + | } | |||
| 1182 | if inDiff && len(l) > 0 { | 1189 | if inDiff && len(l) > 0 { | ||
| 1183 | switch l[0] { | 1190 | switch l[0] { | ||
| 1184 | case '+': | 1191 | case '+': | ||
| @@ -1716,7 +1723,9 @@ func (v *View) renderRawFile(text, sourcePath string, startLine int) []string { | |||||
| 1716 | lines = lines[:n-1] | 1723 | lines = lines[:n-1] | ||
| 1717 | } | 1724 | } | ||
| 1718 | // Split code from trailing footer lines ("... [truncated ...]") | 1725 | // Split code from trailing footer lines ("... [truncated ...]") | ||
| 1719 | - | // so we don't number the footer. | 1726 | + | // so we don't number the footer. A single blank separator line |
| 1727 | + | // immediately before the footer is also pulled out so the | |||
| 1728 | + | // gutter doesn't render a phantom number for it. | |||
| 1720 | codeEnd := len(lines) | 1729 | codeEnd := len(lines) | ||
| 1721 | for i := len(lines) - 1; i >= 0; i-- { | 1730 | for i := len(lines) - 1; i >= 0; i-- { | ||
| 1722 | if strings.HasPrefix(lines[i], "...") { | 1731 | if strings.HasPrefix(lines[i], "...") { | ||
| @@ -1725,6 +1734,9 @@ func (v *View) renderRawFile(text, sourcePath string, startLine int) []string { | |||||
| 1725 | } | 1734 | } | ||
| 1726 | break | 1735 | break | ||
| 1727 | } | 1736 | } | ||
| 1737 | + | if codeEnd > 0 && codeEnd < len(lines) && lines[codeEnd-1] == "" { | |||
| 1738 | + | codeEnd-- | |||
| 1739 | + | } | |||
| 1728 | code := lines[:codeEnd] | 1740 | code := lines[:codeEnd] | ||
| 1729 | footer := lines[codeEnd:] | 1741 | footer := lines[codeEnd:] | ||
| 1730 | 1742 | ||||