detached 4d92b3d

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 {
906906 lines = append(lines, compactToolBlank(v.Theme, width))
907907 }
908908 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)
910910 for _, l := range v.collapseToolBody(body, false) {
911911 _, stripped := parseImageFootprint(l)
912912 lines = append(lines, toolBodyLine(v.Theme, stripped, width, v.CompactMode))
@@ -918,7 +918,7 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string {
918918 }
919919 lines = append(lines, toolBoxTop(v.Theme, label, width))
920920 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)
922922 for _, l := range v.collapseToolBody(body, false) {
923923 imgCells, stripped := parseImageFootprint(l)
924924 if hasImageEscapeLine(stripped) {
@@ -932,6 +932,13 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string {
932932 return lines
933933 }
934934
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+
935942 // renderLiveToolBody renders the in-flight preview of a streaming
936943 // tool call. Supported tools:
937944 //
@@ -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 lines932 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 streaming942 // 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) {
8787 }},
8888 }
8989
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"} {
9293 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)
94103 }
95104 }
96105 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)") {

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close