@@ -1,6 +1,8 @@
package stringext
import (
+ "strings"
+
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
@@ -8,3 +10,13 @@ import (
func Capitalize(text string) string {
return cases.Title(language.English, cases.Compact).String(text)
}
+
+// NormalizeSpace normalizes whitespace in the given content string.
+// It replaces Windows-style line endings with Unix-style line endings,
+// converts tabs to four spaces, and trims leading and trailing whitespace.
+func NormalizeSpace(content string) string {
+ content = strings.ReplaceAll(content, "\r\n", "\n")
+ content = strings.ReplaceAll(content, "\t", " ")
+ content = strings.TrimSpace(content)
+ return content
+}
@@ -15,6 +15,7 @@ import (
"github.com/charmbracelet/crush/internal/diff"
"github.com/charmbracelet/crush/internal/fsext"
"github.com/charmbracelet/crush/internal/message"
+ "github.com/charmbracelet/crush/internal/stringext"
"github.com/charmbracelet/crush/internal/ui/anim"
"github.com/charmbracelet/crush/internal/ui/common"
"github.com/charmbracelet/crush/internal/ui/styles"
@@ -531,9 +532,7 @@ func toolHeader(sty *styles.Styles, status ToolStatus, name string, width int, n
// toolOutputPlainContent renders plain text with optional expansion support.
func toolOutputPlainContent(sty *styles.Styles, content string, width int, expanded bool) string {
- content = strings.ReplaceAll(content, "\r\n", "\n")
- content = strings.ReplaceAll(content, "\t", " ")
- content = strings.TrimSpace(content)
+ content = stringext.NormalizeSpace(content)
lines := strings.Split(content, "\n")
maxLines := responseContextHeight
@@ -566,8 +565,7 @@ func toolOutputPlainContent(sty *styles.Styles, content string, width int, expan
// toolOutputCodeContent renders code with syntax highlighting and line numbers.
func toolOutputCodeContent(sty *styles.Styles, path, content string, offset, width int, expanded bool) string {
- content = strings.ReplaceAll(content, "\r\n", "\n")
- content = strings.ReplaceAll(content, "\t", " ")
+ content = stringext.NormalizeSpace(content)
lines := strings.Split(content, "\n")
maxLines := responseContextHeight
@@ -776,9 +774,7 @@ func roundedEnumerator(lPadding, width int) tree.Enumerator {
// toolOutputMarkdownContent renders markdown content with optional truncation.
func toolOutputMarkdownContent(sty *styles.Styles, content string, width int, expanded bool) string {
- content = strings.ReplaceAll(content, "\r\n", "\n")
- content = strings.ReplaceAll(content, "\t", " ")
- content = strings.TrimSpace(content)
+ content = stringext.NormalizeSpace(content)
// Cap width for readability.
if width > maxTextWidth {
@@ -5,6 +5,7 @@ import (
"strings"
"charm.land/lipgloss/v2"
+ "github.com/charmbracelet/crush/internal/stringext"
uv "github.com/charmbracelet/ultraviolet"
)
@@ -53,6 +54,8 @@ func Highlight(content string, area image.Rectangle, startLine, startCol, endLin
// HighlightBuffer highlights a region of text within the given content and
// region, returning a [uv.ScreenBuffer].
func HighlightBuffer(content string, area image.Rectangle, startLine, startCol, endLine, endCol int, highlighter Highlighter) *uv.ScreenBuffer {
+ content = stringext.NormalizeSpace(content)
+
if startLine < 0 || startCol < 0 {
return nil
}