Skip to content

Commit 2003e9c

Browse files
idoubiclaude
andcommitted
feat: tool-aware output rendering
- Read: show line count summary when collapsed - Edit: show "file updated" when collapsed, diff colors when expanded - Write: show "file written" summary - Bash: show first 3 lines collapsed, 30 expanded - Glob: show up to 8 file paths collapsed - Grep: show up to 5 matches collapsed - All tools: smarter truncation with ctrl+o expand hint Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f127f14 commit 2003e9c

1 file changed

Lines changed: 155 additions & 23 deletions

File tree

internal/tui/model.go

Lines changed: 155 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,9 @@ func (m *Model) renderToolBlock(block DisplayBlock) string {
10511051

10521052
func (m *Model) renderToolState(tool *ToolState, isActive bool) string {
10531053
var b strings.Builder
1054+
w := m.width
10541055

1056+
// Status indicator
10551057
var dot string
10561058
var nameStyle lipgloss.Style
10571059
switch tool.Status {
@@ -1069,49 +1071,179 @@ func (m *Model) renderToolState(tool *ToolState, isActive bool) string {
10691071
nameStyle = lipgloss.NewStyle().Bold(true)
10701072
}
10711073

1074+
// Header line: ⏺ ToolName(args)
10721075
name := nameStyle.Render(tool.Name)
10731076
args := ""
10741077
if tool.InputStr != "" {
10751078
args = theme.MutedStyle.Render("(" + truncate(tool.InputStr, 80) + ")")
10761079
}
10771080
b.WriteString(fmt.Sprintf(" %s %s%s\n", dot, name, args))
10781081

1082+
// Running indicator
10791083
if tool.Status == "running" && isActive {
10801084
b.WriteString(" " + theme.MutedStyle.Render("Running...") + "\n")
10811085
}
10821086

1083-
if tool.Status == "done" && !tool.EndTime.IsZero() {
1084-
dur := tool.EndTime.Sub(tool.StartTime)
1085-
if dur > time.Second {
1086-
b.WriteString(" " + theme.DimText.Render(fmt.Sprintf("(%s)", formatDuration(dur))) + "\n")
1087+
// Output section — tool-specific rendering
1088+
if tool.Output != "" {
1089+
expanded := m.transcriptMode || tool.Expanded
1090+
b.WriteString(m.renderToolOutput(tool, expanded, w))
1091+
}
1092+
1093+
// Error
1094+
if tool.Error != "" {
1095+
errLines := strings.Split(tool.Error, "\n")
1096+
for i, line := range errLines {
1097+
if i >= 5 {
1098+
b.WriteString(" " + theme.ErrorStyle.Render(fmt.Sprintf("... +%d more error lines", len(errLines)-5)) + "\n")
1099+
break
1100+
}
1101+
b.WriteString(" " + theme.ErrorStyle.Render(truncate(line, w-8)) + "\n")
10871102
}
10881103
}
10891104

1090-
// Tool output
1091-
if tool.Output != "" && (m.transcriptMode || tool.Expanded) {
1092-
outputLines := strings.Split(tool.Output, "\n")
1093-
maxLines := 20
1094-
if len(outputLines) > maxLines {
1095-
for _, line := range outputLines[:maxLines] {
1096-
b.WriteString(" " + theme.DimText.Render(truncate(line, m.width-8)) + "\n")
1105+
return b.String()
1106+
}
1107+
1108+
func (m *Model) renderToolOutput(tool *ToolState, expanded bool, w int) string {
1109+
var b strings.Builder
1110+
output := tool.Output
1111+
lines := strings.Split(output, "\n")
1112+
totalLines := len(lines)
1113+
1114+
// How many lines to show when collapsed
1115+
collapsedMax := 3
1116+
expandedMax := 30
1117+
1118+
switch tool.Name {
1119+
case "Read":
1120+
// For Read tool: show line count summary
1121+
if !expanded {
1122+
b.WriteString(fmt.Sprintf(" ⎿ %d lines\n", totalLines))
1123+
} else {
1124+
maxShow := expandedMax
1125+
if totalLines < maxShow {
1126+
maxShow = totalLines
1127+
}
1128+
for i := 0; i < maxShow; i++ {
1129+
b.WriteString(" " + theme.DimText.Render(truncate(lines[i], w-8)) + "\n")
1130+
}
1131+
if totalLines > maxShow {
1132+
b.WriteString(" " + theme.DimText.Render(fmt.Sprintf("... +%d lines", totalLines-maxShow)) + "\n")
1133+
}
1134+
}
1135+
1136+
case "Bash":
1137+
// For Bash: show output, stderr-aware
1138+
maxShow := collapsedMax
1139+
if expanded {
1140+
maxShow = expandedMax
1141+
}
1142+
if totalLines <= maxShow {
1143+
for _, line := range lines {
1144+
b.WriteString(" " + theme.DimText.Render(truncate(line, w-8)) + "\n")
10971145
}
1098-
b.WriteString(" " + theme.DimText.Render(fmt.Sprintf("... +%d lines (ctrl+o to expand)", len(outputLines)-maxLines)) + "\n")
10991146
} else {
1100-
for _, line := range outputLines {
1101-
b.WriteString(" " + theme.DimText.Render(truncate(line, m.width-8)) + "\n")
1147+
for i := 0; i < maxShow; i++ {
1148+
b.WriteString(" " + theme.DimText.Render(truncate(lines[i], w-8)) + "\n")
11021149
}
1150+
b.WriteString(" " + theme.DimText.Render(fmt.Sprintf("... +%d lines (ctrl+o to expand)", totalLines-maxShow)) + "\n")
11031151
}
1104-
} else if tool.Output != "" {
1105-
lines := strings.Count(tool.Output, "\n") + 1
1106-
firstLine := strings.SplitN(tool.Output, "\n", 2)[0]
1107-
b.WriteString(" ⎿ " + theme.DimText.Render(truncate(firstLine, m.width-10)) + "\n")
1108-
if lines > 1 {
1109-
b.WriteString(" " + theme.DimText.Render(fmt.Sprintf(" ... +%d lines (ctrl+o to expand)", lines-1)) + "\n")
1152+
1153+
case "Edit":
1154+
// For Edit: show what was changed
1155+
if !expanded {
1156+
b.WriteString(" ⎿ " + theme.SuccessText.Render("file updated") + "\n")
1157+
} else {
1158+
maxShow := expandedMax
1159+
if totalLines < maxShow {
1160+
maxShow = totalLines
1161+
}
1162+
for i := 0; i < maxShow; i++ {
1163+
line := lines[i]
1164+
if strings.HasPrefix(line, "+") {
1165+
b.WriteString(" " + theme.SuccessText.Render(truncate(line, w-8)) + "\n")
1166+
} else if strings.HasPrefix(line, "-") {
1167+
b.WriteString(" " + theme.ErrorStyle.Render(truncate(line, w-8)) + "\n")
1168+
} else {
1169+
b.WriteString(" " + theme.DimText.Render(truncate(line, w-8)) + "\n")
1170+
}
1171+
}
1172+
if totalLines > maxShow {
1173+
b.WriteString(" " + theme.DimText.Render(fmt.Sprintf("... +%d lines", totalLines-maxShow)) + "\n")
1174+
}
11101175
}
1111-
}
11121176

1113-
if tool.Error != "" {
1114-
b.WriteString(" " + theme.ErrorStyle.Render("⎿ Error: "+truncate(tool.Error, m.width-12)) + "\n")
1177+
case "Write":
1178+
// For Write: show file created/written
1179+
if !expanded {
1180+
b.WriteString(" ⎿ " + theme.SuccessText.Render("file written") + "\n")
1181+
} else {
1182+
maxShow := expandedMax
1183+
if totalLines < maxShow {
1184+
maxShow = totalLines
1185+
}
1186+
for i := 0; i < maxShow; i++ {
1187+
b.WriteString(" " + theme.DimText.Render(truncate(lines[i], w-8)) + "\n")
1188+
}
1189+
if totalLines > maxShow {
1190+
b.WriteString(" " + theme.DimText.Render(fmt.Sprintf("... +%d lines", totalLines-maxShow)) + "\n")
1191+
}
1192+
}
1193+
1194+
case "Glob":
1195+
// Show file list
1196+
maxShow := 8
1197+
if expanded {
1198+
maxShow = 30
1199+
}
1200+
if totalLines <= maxShow {
1201+
for _, line := range lines {
1202+
b.WriteString(" " + theme.DimText.Render(truncate(line, w-8)) + "\n")
1203+
}
1204+
} else {
1205+
for i := 0; i < maxShow; i++ {
1206+
b.WriteString(" " + theme.DimText.Render(truncate(lines[i], w-8)) + "\n")
1207+
}
1208+
b.WriteString(" " + theme.DimText.Render(fmt.Sprintf("... +%d files", totalLines-maxShow)) + "\n")
1209+
}
1210+
1211+
case "Grep":
1212+
// Show search results
1213+
maxShow := 5
1214+
if expanded {
1215+
maxShow = 30
1216+
}
1217+
if totalLines <= maxShow {
1218+
for _, line := range lines {
1219+
b.WriteString(" " + theme.DimText.Render(truncate(line, w-8)) + "\n")
1220+
}
1221+
} else {
1222+
for i := 0; i < maxShow; i++ {
1223+
b.WriteString(" " + theme.DimText.Render(truncate(lines[i], w-8)) + "\n")
1224+
}
1225+
b.WriteString(" " + theme.DimText.Render(fmt.Sprintf("... +%d matches", totalLines-maxShow)) + "\n")
1226+
}
1227+
1228+
default:
1229+
// Generic: show first few lines
1230+
maxShow := collapsedMax
1231+
if expanded {
1232+
maxShow = expandedMax
1233+
}
1234+
if totalLines <= maxShow {
1235+
for _, line := range lines {
1236+
if line != "" {
1237+
b.WriteString(" ⎿ " + theme.DimText.Render(truncate(line, w-10)) + "\n")
1238+
}
1239+
}
1240+
} else {
1241+
firstLine := lines[0]
1242+
b.WriteString(" ⎿ " + theme.DimText.Render(truncate(firstLine, w-10)) + "\n")
1243+
if totalLines > 1 {
1244+
b.WriteString(" " + theme.DimText.Render(fmt.Sprintf(" ... +%d lines (ctrl+o to expand)", totalLines-1)) + "\n")
1245+
}
1246+
}
11151247
}
11161248

11171249
return b.String()

0 commit comments

Comments
 (0)