@@ -655,6 +655,7 @@ func joinHeaderBody(header, body string) string {
656func renderPlainContent(v *toolCallCmp, content string) string {
656func renderPlainContent(v *toolCallCmp, content string) string {
657 t := styles.CurrentTheme()
657 t := styles.CurrentTheme()
658+ content = strings.ReplaceAll(content, "\r\n", "\n") // Normalize line endings
658 content = strings.TrimSpace(content)
659 content = strings.TrimSpace(content)
659 lines := strings.Split(content, "\n")
660 lines := strings.Split(content, "\n")
@@ -664,6 +665,7 @@ func renderPlainContent(v *toolCallCmp, content string) string {
664 if i >= responseContextHeight {
665 if i >= responseContextHeight {
668+ ln = escapeContent(ln)
667 ln = " " + ln // left padding
669 ln = " " + ln // left padding
668 if len(ln) > width {
670 if len(ln) > width {
669 ln = v.fit(ln, width)
671 ln = v.fit(ln, width)
@@ -680,6 +682,7 @@ func renderPlainContent(v *toolCallCmp, content string) string {
680 Width(width).
682 Width(width).
681 Render(fmt.Sprintf("… (%d lines)", len(lines)-responseContextHeight)))
683 Render(fmt.Sprintf("… (%d lines)", len(lines)-responseContextHeight)))
683 return strings.Join(out, "\n")
686 return strings.Join(out, "\n")
@@ -694,10 +697,17 @@ func pad(v any, width int) string {
695func renderCodeContent(v *toolCallCmp, path, content string, offset int) string {
698func renderCodeContent(v *toolCallCmp, path, content string, offset int) string {
696 t := styles.CurrentTheme()
699 t := styles.CurrentTheme()
700+ content = strings.ReplaceAll(content, "\r\n", "\n") // Normalize line endings
701+ content = strings.ReplaceAll(content, "\t", " ") // Replace tabs with spaces
697 truncated := truncateHeight(content, responseContextHeight)
702 truncated := truncateHeight(content, responseContextHeight)
704+ lines := strings.Split(truncated, "\n")
705+ for i, ln := range lines {
706+ lines[i] = escapeContent(ln)
699− highlighted, _ := highlight.SyntaxHighlight(truncated, path, t.BgBase)
709+ highlighted, _ := highlight.SyntaxHighlight(strings.Join(lines, "\n"), path, t.BgBase)
700− lines := strings.Split(highlighted, "\n")
710+ lines = strings.Split(highlighted, "\n")
702 if len(strings.Split(content, "\n")) > responseContextHeight {
712 if len(strings.Split(content, "\n")) > responseContextHeight {
703 lines = append(lines, t.S().Muted.
713 lines = append(lines, t.S().Muted.
@@ -721,6 +731,7 @@ func renderCodeContent(v *toolCallCmp, path, content string, offset int) string
721 PaddingLeft(1).
731 PaddingLeft(1).
722 Render(v.fit(ln, w-1)))
732 Render(v.fit(ln, w-1)))
724 return lipgloss.JoinVertical(lipgloss.Left, lines...)
735 return lipgloss.JoinVertical(lipgloss.Left, lines...)
@@ -766,3 +777,20 @@ func prettifyToolName(name string) string {
766 return name
777 return name
781+// escapeContent replaces control characters with their Unicode Control Picture
782+// representations to ensure they are displayed correctly in the UI.
783+func escapeContent(content string) string {
784+ var sb strings.Builder
785+ for _, r := range content {
787+ case r >= 0 && r <= 0x1f: // Control characters 0x00-0x1F
788+ sb.WriteRune('\u2400' + r)
790+ sb.WriteRune('\u2421')