@@ -6,6 +6,7 @@ import (
66 "strings"
77 "time"
88
9+ "github.com/charmbracelet/crush/internal/ansiext"
910 "github.com/charmbracelet/crush/internal/fsext"
1011 "github.com/charmbracelet/crush/internal/llm/agent"
1112 "github.com/charmbracelet/crush/internal/llm/tools"
@@ -702,7 +703,7 @@ func renderPlainContent(v *toolCallCmp, content string) string {
702703 if i >= responseContextHeight {
703704 break
704705 }
705− ln = escapeContent(ln)
706+ ln = ansiext.Escape(ln)
706707 ln = " " + ln // left padding
707708 if len(ln) > width {
708709 ln = v.fit(ln, width)
@@ -740,7 +741,7 @@ func renderCodeContent(v *toolCallCmp, path, content string, offset int) string
740741
741742 lines := strings.Split(truncated, "\n")
742743 for i, ln := range lines {
743− lines[i] = escapeContent(ln)
744+ lines[i] = ansiext.Escape(ln)
744745 }
745746
746747 highlighted, _ := highlight.SyntaxHighlight(strings.Join(lines, "\n"), path, t.BgBase)
@@ -816,20 +817,3 @@ func prettifyToolName(name string) string {
816817 return name
817818 }
818819}
819−
820−// escapeContent replaces control characters with their Unicode Control Picture
821−// representations to ensure they are displayed correctly in the UI.
822−func escapeContent(content string) string {
823− var sb strings.Builder
824− for _, r := range content {
825− switch {
826− case r >= 0 && r <= 0x1f: // Control characters 0x00-0x1F
827− sb.WriteRune('\u2400' + r)
828− case r == ansi.DEL:
829− sb.WriteRune('\u2421')
830− default:
831− sb.WriteRune(r)
832− }
833− }
834− return sb.String()
835−}