Preserve colors in tool previews
- commit
- 4d92b3d135d6e3927d81aaf0e5a2fca954903f35
- author
- patriceckhart · · 2026-07-21 17:21 UTC
- parent
- b7f2f12
- signature
- unsigned
- refs
- tag: v0.2.99
2 files · +21 -5
M packages/tui/view.go +9 -2
| @@ -906,7 +906,7 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string { | |||
| 906 | 906 | lines = append(lines, compactToolBlank(v.Theme, width)) | |
| 907 | 907 | } | |
| 908 | 908 | lines = append(lines, toolHeaderLine(v.Theme, label, width, v.CompactMode)) | |
| 909 | - | body := toolResultBlock(v.Theme, bodyText, flatToolBodyRenderWidth(width), color) | |
| 909 | + | body := v.renderLiveToolResult(bodyText, flatToolBodyRenderWidth(width), color, tc.LivePath) | |
| 910 | 910 | for _, l := range v.collapseToolBody(body, false) { | |
| 911 | 911 | _, stripped := parseImageFootprint(l) | |
| 912 | 912 | lines = append(lines, toolBodyLine(v.Theme, stripped, width, v.CompactMode)) | |
| @@ -918,7 +918,7 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string { | |||
| 918 | 918 | } | |
| 919 | 919 | lines = append(lines, toolBoxTop(v.Theme, label, width)) | |
| 920 | 920 | lines = append(lines, toolBoxSide(v.Theme, "", width)) | |
| 921 | - | body := toolResultBlock(v.Theme, bodyText, toolBoxBodyRenderWidth(width), color) | |
| 921 | + | body := v.renderLiveToolResult(bodyText, toolBoxBodyRenderWidth(width), color, tc.LivePath) | |
| 922 | 922 | for _, l := range v.collapseToolBody(body, false) { | |
| 923 | 923 | imgCells, stripped := parseImageFootprint(l) | |
| 924 | 924 | if hasImageEscapeLine(stripped) { | |
| @@ -932,6 +932,13 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string { | |||
| 932 | 932 | return lines | |
| 933 | 933 | } | |
| 934 | 934 | ||
| 935 | + | // renderLiveToolResult uses the transcript's text renderer so every rich | |
| 936 | + | // confirmation preview immediately gets the same colors, numbered gutters, | |
| 937 | + | // and syntax highlighting as its final tool result. | |
| 938 | + | func (v *View) renderLiveToolResult(text string, width, color int, sourcePath string) []string { | |
| 939 | + | return v.renderToolText(text, width, color, sourcePath, 1) | |
| 940 | + | } | |
| 941 | + | ||
| 935 | 942 | // renderLiveToolBody renders the in-flight preview of a streaming | |
| 936 | 943 | // tool call. Supported tools: | |
| 937 | 944 | // | |
| @@ -906,7 +906,7 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string { | |||||
| 906 | lines = append(lines, compactToolBlank(v.Theme, width)) | 906 | lines = append(lines, compactToolBlank(v.Theme, width)) | ||
| 907 | } | 907 | } | ||
| 908 | lines = append(lines, toolHeaderLine(v.Theme, label, width, v.CompactMode)) | 908 | lines = append(lines, toolHeaderLine(v.Theme, label, width, v.CompactMode)) | ||
| 909 | - | body := toolResultBlock(v.Theme, bodyText, flatToolBodyRenderWidth(width), color) | 909 | + | body := v.renderLiveToolResult(bodyText, flatToolBodyRenderWidth(width), color, tc.LivePath) |
| 910 | for _, l := range v.collapseToolBody(body, false) { | 910 | for _, l := range v.collapseToolBody(body, false) { | ||
| 911 | _, stripped := parseImageFootprint(l) | 911 | _, stripped := parseImageFootprint(l) | ||
| 912 | lines = append(lines, toolBodyLine(v.Theme, stripped, width, v.CompactMode)) | 912 | lines = append(lines, toolBodyLine(v.Theme, stripped, width, v.CompactMode)) | ||
| @@ -918,7 +918,7 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string { | |||||
| 918 | } | 918 | } | ||
| 919 | lines = append(lines, toolBoxTop(v.Theme, label, width)) | 919 | lines = append(lines, toolBoxTop(v.Theme, label, width)) | ||
| 920 | lines = append(lines, toolBoxSide(v.Theme, "", width)) | 920 | lines = append(lines, toolBoxSide(v.Theme, "", width)) | ||
| 921 | - | body := toolResultBlock(v.Theme, bodyText, toolBoxBodyRenderWidth(width), color) | 921 | + | body := v.renderLiveToolResult(bodyText, toolBoxBodyRenderWidth(width), color, tc.LivePath) |
| 922 | for _, l := range v.collapseToolBody(body, false) { | 922 | for _, l := range v.collapseToolBody(body, false) { | ||
| 923 | imgCells, stripped := parseImageFootprint(l) | 923 | imgCells, stripped := parseImageFootprint(l) | ||
| 924 | if hasImageEscapeLine(stripped) { | 924 | if hasImageEscapeLine(stripped) { | ||
| @@ -932,6 +932,13 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string { | |||||
| 932 | return lines | 932 | return lines | ||
| 933 | } | 933 | } | ||
| 934 | 934 | ||||
| 935 | + | // renderLiveToolResult uses the transcript's text renderer so every rich | |||
| 936 | + | // confirmation preview immediately gets the same colors, numbered gutters, | |||
| 937 | + | // and syntax highlighting as its final tool result. | |||
| 938 | + | func (v *View) renderLiveToolResult(text string, width, color int, sourcePath string) []string { | |||
| 939 | + | return v.renderToolText(text, width, color, sourcePath, 1) | |||
| 940 | + | } | |||
| 941 | + | ||||
| 935 | // renderLiveToolBody renders the in-flight preview of a streaming | 942 | // renderLiveToolBody renders the in-flight preview of a streaming | ||
| 936 | // tool call. Supported tools: | 943 | // tool call. Supported tools: | ||
| 937 | // | 944 | // | ||
M packages/tui/view_tool_overlay_test.go +12 -3
| @@ -87,10 +87,19 @@ func TestEditConfirmationPreviewRendersDiffInsteadOfNewText(t *testing.T) { | |||
| 87 | 87 | }}, | |
| 88 | 88 | } | |
| 89 | 89 | ||
| 90 | - | plain := stripANSI(strings.Join(v.Build(80), "\n")) | |
| 91 | - | for _, want := range []string{"-old value", "+new value"} { | |
| 90 | + | rendered := strings.Join(v.Build(80), "\n") | |
| 91 | + | plain := stripANSI(rendered) | |
| 92 | + | for _, want := range []string{"- 1 old value", "+ 1 new value"} { | |
| 92 | 93 | if !strings.Contains(plain, want) { | |
| 93 | - | t.Fatalf("confirmation preview missing %q:\n%s", want, plain) | |
| 94 | + | t.Fatalf("confirmation preview missing numbered diff row %q:\n%s", want, plain) | |
| 95 | + | } | |
| 96 | + | } | |
| 97 | + | for _, want := range []string{ | |
| 98 | + | Dark.FG256(Dark.Error, "- 1 "), | |
| 99 | + | Dark.FG256(Dark.Tool, "+ 1 "), | |
| 100 | + | } { | |
| 101 | + | if !strings.Contains(rendered, want) { | |
| 102 | + | t.Fatalf("confirmation preview missing diff color %q:\n%s", want, rendered) | |
| 94 | 103 | } | |
| 95 | 104 | } | |
| 96 | 105 | if strings.Contains(plain, "edit 1 (streaming)") { | |
| @@ -87,10 +87,19 @@ func TestEditConfirmationPreviewRendersDiffInsteadOfNewText(t *testing.T) { | |||||
| 87 | }}, | 87 | }}, | ||
| 88 | } | 88 | } | ||
| 89 | 89 | ||||
| 90 | - | plain := stripANSI(strings.Join(v.Build(80), "\n")) | 90 | + | rendered := strings.Join(v.Build(80), "\n") |
| 91 | - | for _, want := range []string{"-old value", "+new value"} { | 91 | + | plain := stripANSI(rendered) |
| 92 | + | for _, want := range []string{"- 1 old value", "+ 1 new value"} { | |||
| 92 | if !strings.Contains(plain, want) { | 93 | if !strings.Contains(plain, want) { | ||
| 93 | - | t.Fatalf("confirmation preview missing %q:\n%s", want, plain) | 94 | + | t.Fatalf("confirmation preview missing numbered diff row %q:\n%s", want, plain) |
| 95 | + | } | |||
| 96 | + | } | |||
| 97 | + | for _, want := range []string{ | |||
| 98 | + | Dark.FG256(Dark.Error, "- 1 "), | |||
| 99 | + | Dark.FG256(Dark.Tool, "+ 1 "), | |||
| 100 | + | } { | |||
| 101 | + | if !strings.Contains(rendered, want) { | |||
| 102 | + | t.Fatalf("confirmation preview missing diff color %q:\n%s", want, rendered) | |||
| 94 | } | 103 | } | ||
| 95 | } | 104 | } | ||
| 96 | if strings.Contains(plain, "edit 1 (streaming)") { | 105 | if strings.Contains(plain, "edit 1 (streaming)") { | ||