summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorTom <[email protected]>2025-07-19 21:28:40 +0700
committerGitHub <[email protected]>2025-07-19 09:28:40 -0500
commit2b44dbdbf105f1c5d1cd34b7ae86925ff41e4c79 (patch)
treed38af1226cf2cf88287e79a0ab2014e4b83d1a07
parent4bbbbac5f661de72a2b25d7a85e70c00788e194f (diff)
downloadopencode-2b44dbdbf105f1c5d1cd34b7ae86925ff41e4c79.tar.gz
opencode-2b44dbdbf105f1c5d1cd34b7ae86925ff41e4c79.zip
fix: prevent sparse spacing in hyphenated words (#1102)
-rw-r--r--packages/tui/internal/components/chat/message.go14
-rw-r--r--packages/tui/internal/components/textarea/textarea.go4
-rw-r--r--packages/tui/internal/util/file.go12
-rw-r--r--packages/tui/internal/util/text.go47
4 files changed, 70 insertions, 7 deletions
diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go
index d3263b053..ef097c09c 100644
--- a/packages/tui/internal/components/chat/message.go
+++ b/packages/tui/internal/components/chat/message.go
@@ -196,6 +196,8 @@ func renderText(
case opencode.UserMessage:
ts = time.UnixMilli(int64(casted.Time.Created))
base := styles.NewStyle().Foreground(t.Text()).Background(backgroundColor)
+
+ // Process @ mentions and styling with hyphen preservation
words := strings.Fields(text)
for i, word := range words {
if strings.HasPrefix(word, "@") {
@@ -204,9 +206,14 @@ func renderText(
words[i] = base.Render(word + " ")
}
}
- text = strings.Join(words, "")
- text = ansi.WordwrapWc(text, width-6, " -")
- content = base.Width(width - 6).Render(text)
+ styledText := strings.Join(words, "")
+
+ // Apply word wrapping with hyphen preservation
+ frameSize := util.GetMessageContainerFrame()
+ wrappedText := util.ProcessTextWithHyphens(styledText, func(t string) string {
+ return ansi.WordwrapWc(t, width-frameSize, " ")
+ })
+ content = base.Width(width - frameSize).Render(wrappedText)
}
timestamp := ts.
@@ -707,5 +714,4 @@ func renderDiagnostics(
// if !ok {
// return ""
// }
-
}
diff --git a/packages/tui/internal/components/textarea/textarea.go b/packages/tui/internal/components/textarea/textarea.go
index cc073e27d..a4b954d49 100644
--- a/packages/tui/internal/components/textarea/textarea.go
+++ b/packages/tui/internal/components/textarea/textarea.go
@@ -2051,6 +2051,10 @@ func wrapInterfaces(content []any, width int) [][]any {
if unicode.IsSpace(r) {
isSpace = true
}
+ // Use hyphen-aware word boundary detection
+ if r == '-' {
+ isSpace = false
+ }
itemW = rw.RuneWidth(r)
} else if att, ok := item.(*Attachment); ok {
itemW = uniseg.StringWidth(att.Display)
diff --git a/packages/tui/internal/util/file.go b/packages/tui/internal/util/file.go
index b079f24cd..841f2b519 100644
--- a/packages/tui/internal/util/file.go
+++ b/packages/tui/internal/util/file.go
@@ -83,11 +83,17 @@ func Extension(path string) string {
}
func ToMarkdown(content string, width int, backgroundColor compat.AdaptiveColor) string {
- r := styles.GetMarkdownRenderer(width-6, backgroundColor)
+ renderWidth := width - GetMarkdownContainerFrame()
+ r := styles.GetMarkdownRenderer(renderWidth, backgroundColor)
content = strings.ReplaceAll(content, RootPath+"/", "")
- rendered, _ := r.Render(content)
- lines := strings.Split(rendered, "\n")
+ // Apply hyphen preservation during markdown rendering
+ rendered := ProcessTextWithHyphens(content, func(t string) string {
+ result, _ := r.Render(t)
+ return result
+ })
+ lines := strings.Split(rendered, "\n")
+ // Clean up empty lines at start/end
if len(lines) > 0 {
firstLine := lines[0]
cleaned := ansi.Strip(firstLine)
diff --git a/packages/tui/internal/util/text.go b/packages/tui/internal/util/text.go
new file mode 100644
index 000000000..6a1e81bb8
--- /dev/null
+++ b/packages/tui/internal/util/text.go
@@ -0,0 +1,47 @@
+package util
+
+import (
+ "strings"
+
+ "github.com/charmbracelet/lipgloss/v2"
+)
+
+// PreventHyphenBreaks replaces regular hyphens with non-breaking hyphens to prevent
+// sparse word breaks in hyphenated terms like "claude-code-action".
+// This improves readability by keeping hyphenated words together.
+func PreventHyphenBreaks(text string) string {
+ return strings.ReplaceAll(text, "-", "\u2011")
+}
+
+// RestoreHyphens converts non-breaking hyphens back to regular hyphens.
+// This should be called after text processing (like word wrapping) is complete.
+func RestoreHyphens(text string) string {
+ return strings.ReplaceAll(text, "\u2011", "-")
+}
+
+// ProcessTextWithHyphens applies hyphen preservation to text during processing.
+// It wraps the provided processFunc with hyphen handling.
+func ProcessTextWithHyphens(text string, processFunc func(string) string) string {
+ preserved := PreventHyphenBreaks(text)
+ processed := processFunc(preserved)
+ return RestoreHyphens(processed)
+}
+
+// GetMessageContainerFrame calculates the actual horizontal frame size
+// (padding + borders) for message containers based on current theme.
+func GetMessageContainerFrame() int {
+ style := lipgloss.NewStyle().
+ BorderStyle(lipgloss.ThickBorder()).
+ BorderLeft(true).
+ BorderRight(true).
+ PaddingLeft(2).
+ PaddingRight(2)
+ return style.GetHorizontalFrameSize()
+}
+
+// GetMarkdownContainerFrame calculates the actual horizontal frame size
+// for markdown containers based on current theme.
+func GetMarkdownContainerFrame() int {
+ // Markdown containers use the same styling as message containers
+ return GetMessageContainerFrame()
+}