From 49059f8ddb8c9270484c082e1d5a20c50df1af39 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 14 Jul 2025 12:00:11 -0400 Subject: [PATCH] fix(tui): escape ANSI escape sequences and control characters in tool call content This would ensure that the content is displayed correctly in the terminal, without any unintended formatting or control characters or escape sequences. It will also style the escaped content for display in the terminal, making it more readable. --- .../tui/components/chat/messages/renderer.go | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/internal/tui/components/chat/messages/renderer.go b/internal/tui/components/chat/messages/renderer.go index 87eb2c8476655fe7d11fc8c787e73b32d4584de4..ecd095798633e62e004a165c82b5d1816797c175 100644 --- a/internal/tui/components/chat/messages/renderer.go +++ b/internal/tui/components/chat/messages/renderer.go @@ -3,6 +3,7 @@ package messages import ( "encoding/json" "fmt" + "strconv" "strings" "time" @@ -656,6 +657,7 @@ func joinHeaderBody(header, body string) string { func renderPlainContent(v *toolCallCmp, content string) string { t := styles.CurrentTheme() content = strings.TrimSpace(content) + content = escapeContent(t, content) lines := strings.Split(content, "\n") width := v.textWidth() - 2 // -2 for left padding @@ -694,6 +696,7 @@ func pad(v any, width int) string { func renderCodeContent(v *toolCallCmp, path, content string, offset int) string { t := styles.CurrentTheme() + content = escapeContent(t, content) truncated := truncateHeight(content, responseContextHeight) highlighted, _ := highlight.SyntaxHighlight(truncated, path, t.BgBase) @@ -766,3 +769,41 @@ func prettifyToolName(name string) string { return name } } + +// escapeContent escapes ANSI escape sequences and control characters in the +// content and styles it for display in the terminal. +func escapeContent(t *styles.Theme, content string) string { + lines := strings.Split(content, "\n") + for i, line := range lines { + lines[i] = escapeLine(t, line) + } + + content = strings.Join(lines, "\n") + return content +} + +// escapeLine escapes ANSI escape sequences and control characters and styles +// them for display in the terminal. +func escapeLine(t *styles.Theme, text string) string { + var ( + sb strings.Builder + state byte + seq string + n int + w int + ) + faint := ansi.NewStyle().Faint().ForegroundColor(t.FgMuted) + for len(text) > 0 { + seq, w, n, state = ansi.DecodeSequence(text, state, nil) + if w > 0 { + sb.WriteString(seq) + } else { + quote := strconv.Quote(seq) + quote = strings.TrimPrefix(quote, "\"") + quote = strings.TrimSuffix(quote, "\"") + sb.WriteString(faint.Styled(quote)) + } + text = text[n:] + } + return sb.String() +}