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

optimize edit-tool rendering (#463)

Co-authored-by: opencode <[email protected]>
Co-authored-by: Adam <[email protected]>
Gal Schlezinger 7 месяцев назад
Родитель
Сommit
f618e569ab

+ 5 - 0
packages/tui/internal/components/chat/messages.go

@@ -16,6 +16,7 @@ import (
 	"github.com/sst/opencode/internal/layout"
 	"github.com/sst/opencode/internal/styles"
 	"github.com/sst/opencode/internal/theme"
+	"github.com/sst/opencode/internal/util"
 )
 
 type MessagesComponent interface {
@@ -121,9 +122,13 @@ func (m *messagesComponent) renderView() {
 		return
 	}
 
+	measure := util.Measure("messages.renderView")
+	defer measure("messageCount", len(m.app.Messages))
+
 	t := theme.CurrentTheme()
 	blocks := make([]string, 0)
 	previousBlockType := none
+
 	for _, message := range m.app.Messages {
 		var content string
 		var cached bool

+ 23 - 9
packages/tui/internal/components/diff/diff.go

@@ -8,6 +8,7 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
+	"sync"
 
 	"github.com/alecthomas/chroma/v2"
 	"github.com/alecthomas/chroma/v2/formatters"
@@ -19,6 +20,7 @@ import (
 	"github.com/sergi/go-diff/diffmatchpatch"
 	stylesi "github.com/sst/opencode/internal/styles"
 	"github.com/sst/opencode/internal/theme"
+	"github.com/sst/opencode/internal/util"
 )
 
 // -------------------------------------------------------------------------
@@ -939,11 +941,22 @@ func RenderSideBySideHunk(fileName string, h Hunk, opts ...SideBySideOption) str
 	leftWidth := colWidth
 	rightWidth := config.TotalWidth - colWidth
 	var sb strings.Builder
-	for _, p := range pairs {
-		leftStr := renderLeftColumn(fileName, p.left, leftWidth)
-		rightStr := renderRightColumn(fileName, p.right, rightWidth)
-		sb.WriteString(leftStr + rightStr + "\n")
-	}
+
+	util.WriteStringsPar(&sb, pairs, func(p linePair) string {
+		wg := &sync.WaitGroup{}
+		var leftStr, rightStr string
+		wg.Add(2)
+		go func() {
+			defer wg.Done()
+			leftStr = renderLeftColumn(fileName, p.left, leftWidth)
+		}()
+		go func() {
+			defer wg.Done()
+			rightStr = renderRightColumn(fileName, p.right, rightWidth)
+		}()
+		wg.Wait()
+		return leftStr + rightStr + "\n"
+	})
 
 	return sb.String()
 }
@@ -957,7 +970,8 @@ func FormatUnifiedDiff(filename string, diffText string, opts ...UnifiedOption)
 
 	var sb strings.Builder
 	for _, h := range diffResult.Hunks {
-		sb.WriteString(RenderUnifiedHunk(filename, h, opts...))
+		unifiedDiff := RenderUnifiedHunk(filename, h, opts...)
+		sb.WriteString(unifiedDiff)
 	}
 
 	return sb.String(), nil
@@ -973,7 +987,7 @@ func FormatDiff(filename string, diffText string, opts ...SideBySideOption) (str
 
 	var sb strings.Builder
 	// config := NewSideBySideConfig(opts...)
-	for _, h := range diffResult.Hunks {
+	util.WriteStringsPar(&sb, diffResult.Hunks, func(h Hunk) string {
 		// sb.WriteString(
 		// 	lipgloss.NewStyle().
 		// 		Background(t.DiffHunkHeader()).
@@ -981,8 +995,8 @@ func FormatDiff(filename string, diffText string, opts ...SideBySideOption) (str
 		// 		Width(config.TotalWidth).
 		// 		Render(h.Header) + "\n",
 		// )
-		sb.WriteString(RenderSideBySideHunk(filename, h, opts...))
-	}
+		return RenderSideBySideHunk(filename, h, opts...)
+	})
 
 	return sb.String(), nil
 }

+ 50 - 0
packages/tui/internal/util/concurrency.go

@@ -0,0 +1,50 @@
+package util
+
+import (
+	"strings"
+	"sync"
+)
+
+// MapReducePar performs a parallel map-reduce operation on a slice of items.
+// It applies a function to each item in the slice concurrently,
+// and combines the results serially using a reducer returned from
+// each one of the functions, allowing the use of closures.
+func MapReducePar[a, b any](items []a, init b, fn func(a) func(b) b) b {
+	itemCount := len(items)
+	locks := make([]*sync.Mutex, itemCount)
+	mapped := make([]func(b) b, itemCount)
+
+	for i, value := range items {
+		lock := &sync.Mutex{}
+		lock.Lock()
+		locks[i] = lock
+		go func() {
+			defer lock.Unlock()
+			mapped[i] = fn(value)
+		}()
+	}
+
+	result := init
+	for i := range itemCount {
+		locks[i].Lock()
+		defer locks[i].Unlock()
+		f := mapped[i]
+		if f != nil {
+			result = f(result)
+		}
+	}
+
+	return result
+}
+
+// WriteStringsPar allows to iterate over a list and compute strings in parallel,
+// yet write them in order.
+func WriteStringsPar[a any](sb *strings.Builder, items []a, fn func(a) string) {
+	MapReducePar(items, sb, func(item a) func(*strings.Builder) *strings.Builder {
+		str := fn(item)
+		return func(sbdr *strings.Builder) *strings.Builder {
+			sbdr.WriteString(str)
+			return sbdr
+		}
+	})
+}

+ 10 - 0
packages/tui/internal/util/util.go

@@ -1,8 +1,10 @@
 package util
 
 import (
+	"log/slog"
 	"os"
 	"strings"
+	"time"
 
 	tea "github.com/charmbracelet/bubbletea/v2"
 )
@@ -35,3 +37,11 @@ func IsWsl() bool {
 
 	return false
 }
+
+func Measure(tag string) func(...any) {
+	startTime := time.Now()
+	return func(tags ...any) {
+		args := append([]any{"timeTakenMs", time.Since(startTime).Milliseconds()}, tags...)
+		slog.Info(tag, args...)
+	}
+}