on main help sign in
fix(ui): fix selection of code blocks with tabs inside markdown (#2039)
aae4c30Andrey Nering
internal/stringext/string.go+12
@@ -1,6 +1,8 @@
1package stringext
1package stringext
2
2
3import (
3import (
4+ "strings"
5+
4 "golang.org/x/text/cases"
6 "golang.org/x/text/cases"
5 "golang.org/x/text/language"
7 "golang.org/x/text/language"
6)
8)
@@ -8,3 +10,13 @@ import (
8func Capitalize(text string) string {
10func Capitalize(text string) string {
9 return cases.Title(language.English, cases.Compact).String(text)
11 return cases.Title(language.English, cases.Compact).String(text)
10}
12}
13+
14+// NormalizeSpace normalizes whitespace in the given content string.
15+// It replaces Windows-style line endings with Unix-style line endings,
16+// converts tabs to four spaces, and trims leading and trailing whitespace.
17+func NormalizeSpace(content string) string {
18+ content = strings.ReplaceAll(content, "\r\n", "\n")
19+ content = strings.ReplaceAll(content, "\t", " ")
20+ content = strings.TrimSpace(content)
21+ return content
22+}
internal/ui/chat/tools.go+4−8
@@ -15,6 +15,7 @@ import (
15 "github.com/charmbracelet/crush/internal/diff"
15 "github.com/charmbracelet/crush/internal/diff"
16 "github.com/charmbracelet/crush/internal/fsext"
16 "github.com/charmbracelet/crush/internal/fsext"
17 "github.com/charmbracelet/crush/internal/message"
17 "github.com/charmbracelet/crush/internal/message"
18+ "github.com/charmbracelet/crush/internal/stringext"
18 "github.com/charmbracelet/crush/internal/ui/anim"
19 "github.com/charmbracelet/crush/internal/ui/anim"
19 "github.com/charmbracelet/crush/internal/ui/common"
20 "github.com/charmbracelet/crush/internal/ui/common"
20 "github.com/charmbracelet/crush/internal/ui/styles"
21 "github.com/charmbracelet/crush/internal/ui/styles"
@@ -531,9 +532,7 @@ func toolHeader(sty *styles.Styles, status ToolStatus, name string, width int, n
531
532
532// toolOutputPlainContent renders plain text with optional expansion support.
533// toolOutputPlainContent renders plain text with optional expansion support.
533func toolOutputPlainContent(sty *styles.Styles, content string, width int, expanded bool) string {
534func toolOutputPlainContent(sty *styles.Styles, content string, width int, expanded bool) string {
534− content = strings.ReplaceAll(content, "\r\n", "\n")
535+ content = stringext.NormalizeSpace(content)
535− content = strings.ReplaceAll(content, "\t", " ")
536− content = strings.TrimSpace(content)
537 lines := strings.Split(content, "\n")
536 lines := strings.Split(content, "\n")
538
537
539 maxLines := responseContextHeight
538 maxLines := responseContextHeight
@@ -566,8 +565,7 @@ func toolOutputPlainContent(sty *styles.Styles, content string, width int, expan
566
565
567// toolOutputCodeContent renders code with syntax highlighting and line numbers.
566// toolOutputCodeContent renders code with syntax highlighting and line numbers.
568func toolOutputCodeContent(sty *styles.Styles, path, content string, offset, width int, expanded bool) string {
567func toolOutputCodeContent(sty *styles.Styles, path, content string, offset, width int, expanded bool) string {
569− content = strings.ReplaceAll(content, "\r\n", "\n")
568+ content = stringext.NormalizeSpace(content)
570− content = strings.ReplaceAll(content, "\t", " ")
571
569
572 lines := strings.Split(content, "\n")
570 lines := strings.Split(content, "\n")
573 maxLines := responseContextHeight
571 maxLines := responseContextHeight
@@ -776,9 +774,7 @@ func roundedEnumerator(lPadding, width int) tree.Enumerator {
776
774
777// toolOutputMarkdownContent renders markdown content with optional truncation.
775// toolOutputMarkdownContent renders markdown content with optional truncation.
778func toolOutputMarkdownContent(sty *styles.Styles, content string, width int, expanded bool) string {
776func toolOutputMarkdownContent(sty *styles.Styles, content string, width int, expanded bool) string {
779− content = strings.ReplaceAll(content, "\r\n", "\n")
777+ content = stringext.NormalizeSpace(content)
780− content = strings.ReplaceAll(content, "\t", " ")
781− content = strings.TrimSpace(content)
782
778
783 // Cap width for readability.
779 // Cap width for readability.
784 if width > maxTextWidth {
780 if width > maxTextWidth {
internal/ui/list/highlight.go+3
@@ -5,6 +5,7 @@ import (
5 "strings"
5 "strings"
6
6
7 "charm.land/lipgloss/v2"
7 "charm.land/lipgloss/v2"
8+ "github.com/charmbracelet/crush/internal/stringext"
8 uv "github.com/charmbracelet/ultraviolet"
9 uv "github.com/charmbracelet/ultraviolet"
9)
10)
10
11
@@ -53,6 +54,8 @@ func Highlight(content string, area image.Rectangle, startLine, startCol, endLin
53// HighlightBuffer highlights a region of text within the given content and
54// HighlightBuffer highlights a region of text within the given content and
54// region, returning a [uv.ScreenBuffer].
55// region, returning a [uv.ScreenBuffer].
55func HighlightBuffer(content string, area image.Rectangle, startLine, startCol, endLine, endCol int, highlighter Highlighter) *uv.ScreenBuffer {
56func HighlightBuffer(content string, area image.Rectangle, startLine, startCol, endLine, endCol int, highlighter Highlighter) *uv.ScreenBuffer {
57+ content = stringext.NormalizeSpace(content)
58+
56 if startLine < 0 || startCol < 0 {
59 if startLine < 0 || startCol < 0 {
57 return nil
60 return nil
58 }
61 }