Просмотр исходного кода

fix(tui): better message rendering performance

adamdottv 7 месяцев назад
Родитель
Сommit
33b5fe236a

+ 8 - 14
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 {

+ 45 - 69
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...)
 	})
 

+ 4 - 0
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

+ 4 - 0
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

+ 2 - 0
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