summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-07-01 07:57:31 -0500
committeradamdottv <[email protected]>2025-07-01 07:57:45 -0500
commit33b5fe236a204a3d1d935a94e1d1d5f3a6f312a8 (patch)
treeae9ad03002ee312652497539e9f9c668e235922e
parentd56991006c8c94b954bf1b1734280719fe3be239 (diff)
downloadopencode-33b5fe236a204a3d1d935a94e1d1d5f3a6f312a8.tar.gz
opencode-33b5fe236a204a3d1d935a94e1d1d5f3a6f312a8.zip
fix(tui): better message rendering performance
-rw-r--r--packages/tui/internal/components/chat/messages.go22
-rw-r--r--packages/tui/internal/components/diff/diff.go114
-rw-r--r--packages/tui/internal/theme/loader.go4
-rw-r--r--packages/tui/internal/theme/system.go4
-rw-r--r--packages/tui/internal/theme/theme.go2
5 files changed, 63 insertions, 83 deletions
diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go
index 8a2d0db04..86779439b 100644
--- a/packages/tui/internal/components/chat/messages.go
+++ b/packages/tui/internal/components/chat/messages.go
@@ -104,14 +104,15 @@ func (m *messagesComponent) renderView() {
defer measure("messageCount", len(m.app.Messages))
t := theme.CurrentTheme()
- blocks := make([]string, 0)
align := lipgloss.Center
width := layout.Current.Container.Width
- for _, message := range m.app.Messages {
+ sb := strings.Builder{}
+ util.WriteStringsPar(&sb, m.app.Messages, func(message opencode.Message) string {
var content string
var cached bool
+ blocks := make([]string, 0)
switch message.Role {
case opencode.MessageRoleUser:
@@ -224,7 +225,6 @@ func (m *messagesComponent) renderView() {
}
}
}
-
}
error := ""
@@ -247,20 +247,14 @@ func (m *messagesComponent) renderView() {
)
blocks = append(blocks, error)
}
- }
- centered := []string{}
- for _, block := range blocks {
- centered = append(centered, lipgloss.PlaceHorizontal(
- m.width,
- lipgloss.Center,
- block+"\n",
- styles.WhitespaceStyle(t.Background()),
- ))
- }
+ return strings.Join(blocks, "\n\n")
+ })
+
+ content := sb.String()
m.viewport.SetHeight(m.height - lipgloss.Height(m.header()) + 1)
- m.viewport.SetContent("\n" + strings.Join(centered, "\n"))
+ m.viewport.SetContent("\n" + content)
}
func (m *messagesComponent) header() string {
diff --git a/packages/tui/internal/components/diff/diff.go b/packages/tui/internal/components/diff/diff.go
index 4b5c62008..3d0e41fc3 100644
--- a/packages/tui/internal/components/diff/diff.go
+++ b/packages/tui/internal/components/diff/diff.go
@@ -1,6 +1,7 @@
package diff
import (
+ "bufio"
"bytes"
"fmt"
"image/color"
@@ -148,101 +149,87 @@ func WithWidth(width int) UnifiedOption {
func ParseUnifiedDiff(diff string) (DiffResult, error) {
var result DiffResult
var currentHunk *Hunk
+ result.Hunks = make([]Hunk, 0, 10) // Pre-allocate with a reasonable capacity
- hunkHeaderRe := regexp.MustCompile(`^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@`)
- lines := strings.Split(diff, "\n")
-
+ scanner := bufio.NewScanner(strings.NewReader(diff))
var oldLine, newLine int
inFileHeader := true
- for _, line := range lines {
- // Parse file headers
+ for scanner.Scan() {
+ line := scanner.Text()
+
if inFileHeader {
if strings.HasPrefix(line, "--- a/") {
- result.OldFile = strings.TrimPrefix(line, "--- a/")
+ result.OldFile = line[6:]
continue
}
if strings.HasPrefix(line, "+++ b/") {
- result.NewFile = strings.TrimPrefix(line, "+++ b/")
+ result.NewFile = line[6:]
inFileHeader = false
continue
}
}
- // Parse hunk headers
- if matches := hunkHeaderRe.FindStringSubmatch(line); matches != nil {
+ if strings.HasPrefix(line, "@@") {
if currentHunk != nil {
result.Hunks = append(result.Hunks, *currentHunk)
}
currentHunk = &Hunk{
Header: line,
- Lines: []DiffLine{},
+ Lines: make([]DiffLine, 0, 10), // Pre-allocate
}
- oldStart, _ := strconv.Atoi(matches[1])
- newStart, _ := strconv.Atoi(matches[3])
- oldLine = oldStart
- newLine = newStart
- continue
- }
-
- // Ignore "No newline at end of file" markers
- if strings.HasPrefix(line, "\\ No newline at end of file") {
+ // Manual parsing of hunk header is faster than regex
+ parts := strings.Split(line, " ")
+ if len(parts) > 2 {
+ oldRange := strings.Split(parts[1][1:], ",")
+ newRange := strings.Split(parts[2][1:], ",")
+ oldLine, _ = strconv.Atoi(oldRange[0])
+ newLine, _ = strconv.Atoi(newRange[0])
+ }
continue
}
- if currentHunk == nil {
+ if strings.HasPrefix(line, "\\ No newline at end of file") || currentHunk == nil {
continue
}
- // Process the line based on its prefix
+ var dl DiffLine
+ dl.Content = line
if len(line) > 0 {
switch line[0] {
case '+':
- currentHunk.Lines = append(currentHunk.Lines, DiffLine{
- OldLineNo: 0,
- NewLineNo: newLine,
- Kind: LineAdded,
- Content: line[1:],
- })
+ dl.Kind = LineAdded
+ dl.NewLineNo = newLine
+ dl.Content = line[1:]
newLine++
case '-':
- currentHunk.Lines = append(currentHunk.Lines, DiffLine{
- OldLineNo: oldLine,
- NewLineNo: 0,
- Kind: LineRemoved,
- Content: line[1:],
- })
+ dl.Kind = LineRemoved
+ dl.OldLineNo = oldLine
+ dl.Content = line[1:]
oldLine++
- default:
- currentHunk.Lines = append(currentHunk.Lines, DiffLine{
- OldLineNo: oldLine,
- NewLineNo: newLine,
- Kind: LineContext,
- Content: line,
- })
+ default: // context line
+ dl.Kind = LineContext
+ dl.OldLineNo = oldLine
+ dl.NewLineNo = newLine
oldLine++
newLine++
}
- } else {
- // Handle empty lines
- currentHunk.Lines = append(currentHunk.Lines, DiffLine{
- OldLineNo: oldLine,
- NewLineNo: newLine,
- Kind: LineContext,
- Content: "",
- })
+ } else { // empty context line
+ dl.Kind = LineContext
+ dl.OldLineNo = oldLine
+ dl.NewLineNo = newLine
oldLine++
newLine++
}
+ currentHunk.Lines = append(currentHunk.Lines, dl)
}
- // Add the last hunk if there is one
if currentHunk != nil {
result.Hunks = append(result.Hunks, *currentHunk)
}
- return result, nil
+ return result, scanner.Err()
}
// HighlightIntralineChanges updates lines in a hunk to show character-level differences
@@ -744,8 +731,6 @@ func renderLineContent(fileName string, dl DiffLine, bgStyle stylesi.Style, high
content,
width,
"...",
- // stylesi.NewStyleWithColors(t.TextMuted(), bgStyle.GetBackground()).Render("..."),
- // stylesi.WithForeground(stylesi.NewStyle().Background(bgStyle.GetBackground()), t.TextMuted()).Render("..."),
),
)
}
@@ -912,10 +897,11 @@ func RenderUnifiedHunk(fileName string, h Hunk, opts ...UnifiedOption) string {
HighlightIntralineChanges(&hunkCopy)
var sb strings.Builder
- for _, line := range hunkCopy.Lines {
- sb.WriteString(renderUnifiedLine(fileName, line, config.Width, theme.CurrentTheme()))
- sb.WriteString("\n")
- }
+ sb.Grow(len(hunkCopy.Lines) * config.Width)
+
+ util.WriteStringsPar(&sb, hunkCopy.Lines, func(line DiffLine) string {
+ return renderUnifiedLine(fileName, line, config.Width, theme.CurrentTheme()) + "\n"
+ })
return sb.String()
}
@@ -969,32 +955,22 @@ func FormatUnifiedDiff(filename string, diffText string, opts ...UnifiedOption)
}
var sb strings.Builder
- for _, h := range diffResult.Hunks {
- unifiedDiff := RenderUnifiedHunk(filename, h, opts...)
- sb.WriteString(unifiedDiff)
- }
+ util.WriteStringsPar(&sb, diffResult.Hunks, func(h Hunk) string {
+ return RenderUnifiedHunk(filename, h, opts...)
+ })
return sb.String(), nil
}
// FormatDiff creates a side-by-side formatted view of a diff
func FormatDiff(filename string, diffText string, opts ...SideBySideOption) (string, error) {
- // t := theme.CurrentTheme()
diffResult, err := ParseUnifiedDiff(diffText)
if err != nil {
return "", err
}
var sb strings.Builder
- // config := NewSideBySideConfig(opts...)
util.WriteStringsPar(&sb, diffResult.Hunks, func(h Hunk) string {
- // sb.WriteString(
- // lipgloss.NewStyle().
- // Background(t.DiffHunkHeader()).
- // Foreground(t.Background()).
- // Width(config.TotalWidth).
- // Render(h.Header) + "\n",
- // )
return RenderSideBySideHunk(filename, h, opts...)
})
diff --git a/packages/tui/internal/theme/loader.go b/packages/tui/internal/theme/loader.go
index 82c2fcd24..b3d2f0982 100644
--- a/packages/tui/internal/theme/loader.go
+++ b/packages/tui/internal/theme/loader.go
@@ -27,6 +27,10 @@ type LoadedTheme struct {
name string
}
+func (t *LoadedTheme) Name() string {
+ return t.name
+}
+
type colorRef struct {
value any
resolved bool
diff --git a/packages/tui/internal/theme/system.go b/packages/tui/internal/theme/system.go
index 7524bb3f9..8dd48cfec 100644
--- a/packages/tui/internal/theme/system.go
+++ b/packages/tui/internal/theme/system.go
@@ -27,6 +27,10 @@ func NewSystemTheme(terminalBg color.Color, isDark bool) *SystemTheme {
return theme
}
+func (t *SystemTheme) Name() string {
+ return "system"
+}
+
// initializeColors sets up all theme colors
func (t *SystemTheme) initializeColors() {
// Generate gray scale based on terminal background
diff --git a/packages/tui/internal/theme/theme.go b/packages/tui/internal/theme/theme.go
index 9b5b7b915..d5d27a1e1 100644
--- a/packages/tui/internal/theme/theme.go
+++ b/packages/tui/internal/theme/theme.go
@@ -8,6 +8,8 @@ import (
// All colors must be defined as compat.AdaptiveColor to support
// both light and dark terminal backgrounds.
type Theme interface {
+ Name() string
+
// Background colors
Background() compat.AdaptiveColor // Radix 1
BackgroundPanel() compat.AdaptiveColor // Radix 2