diff --git a/internal/tui/exp/diffview/diffview.go b/internal/tui/exp/diffview/diffview.go index 8bcb9b019a8769ee03b4de4cf53a6bccd6734774..4d679b078175a38166c9b0335ec754bd2edef37d 100644 --- a/internal/tui/exp/diffview/diffview.go +++ b/internal/tui/exp/diffview/diffview.go @@ -1,6 +1,7 @@ package diffview import ( + "crypto/sha256" "fmt" "image/color" "strconv" @@ -59,6 +60,13 @@ type DiffView struct { extraColOnAfter bool // add extra column on after panel beforeNumDigits int afterNumDigits int + + // Cache lexer to avoid expensive file pattern matching on every line + cachedLexer chroma.Lexer + + // Cache highlighted lines to avoid re-highlighting the same content + // Key: hash of (content + background color), Value: highlighted string + syntaxCache map[string]string } // New creates a new DiffView with default settings. @@ -68,6 +76,7 @@ func New() *DiffView { contextLines: udiff.DefaultContextLines, lineNumbers: true, tabWidth: 8, + syntaxCache: make(map[string]string), } dv.style = DefaultDarkStyle() return dv @@ -88,15 +97,26 @@ func (dv *DiffView) Split() *DiffView { // Before sets the "before" file for the DiffView. func (dv *DiffView) Before(path, content string) *DiffView { dv.before = file{path: path, content: content} + // Clear caches when content changes + dv.clearCaches() return dv } // After sets the "after" file for the DiffView. func (dv *DiffView) After(path, content string) *DiffView { dv.after = file{path: path, content: content} + // Clear caches when content changes + dv.clearCaches() return dv } +// clearCaches clears all caches when content or major settings change. +func (dv *DiffView) clearCaches() { + dv.cachedLexer = nil + dv.clearSyntaxCache() + dv.isComputed = false +} + // ContextLines sets the number of context lines for the DiffView. func (dv *DiffView) ContextLines(contextLines int) *DiffView { dv.contextLines = contextLines @@ -156,9 +176,21 @@ func (dv *DiffView) TabWidth(tabWidth int) *DiffView { // If nil, no syntax highlighting will be applied. func (dv *DiffView) ChromaStyle(style *chroma.Style) *DiffView { dv.chromaStyle = style + // Clear syntax cache when style changes since highlighting will be different + dv.clearSyntaxCache() return dv } +// clearSyntaxCache clears the syntax highlighting cache. +func (dv *DiffView) clearSyntaxCache() { + if dv.syntaxCache != nil { + // Clear the map but keep it allocated + for k := range dv.syntaxCache { + delete(dv.syntaxCache, k) + } + } +} + // String returns the string representation of the DiffView. func (dv *DiffView) String() string { dv.replaceTabs() @@ -700,7 +732,15 @@ func (dv *DiffView) hightlightCode(source string, bgColor color.Color) string { return source } - l := dv.getChromaLexer(source) + // Create cache key from content and background color + cacheKey := dv.createSyntaxCacheKey(source, bgColor) + + // Check if we already have this highlighted + if cached, exists := dv.syntaxCache[cacheKey]; exists { + return cached + } + + l := dv.getChromaLexer() f := dv.getChromaFormatter(bgColor) it, err := l.Tokenise(nil, source) @@ -712,22 +752,47 @@ func (dv *DiffView) hightlightCode(source string, bgColor color.Color) string { if err := f.Format(&b, dv.chromaStyle, it); err != nil { return source } - return b.String() + + result := b.String() + + // Cache the result for future use + dv.syntaxCache[cacheKey] = result + + return result } -func (dv *DiffView) getChromaLexer(source string) chroma.Lexer { +// createSyntaxCacheKey creates a cache key from source content and background color. +// We use a simple hash to keep memory usage reasonable. +func (dv *DiffView) createSyntaxCacheKey(source string, bgColor color.Color) string { + // Convert color to string representation + r, g, b, a := bgColor.RGBA() + colorStr := fmt.Sprintf("%d,%d,%d,%d", r, g, b, a) + + // Create a hash of the content + color to use as cache key + h := sha256.New() + h.Write([]byte(source)) + h.Write([]byte(colorStr)) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +func (dv *DiffView) getChromaLexer() chroma.Lexer { + if dv.cachedLexer != nil { + return dv.cachedLexer + } + l := lexers.Match(dv.before.path) if l == nil { - l = lexers.Analyse(source) + l = lexers.Analyse(dv.before.content) } if l == nil { l = lexers.Fallback } - return chroma.Coalesce(l) + dv.cachedLexer = chroma.Coalesce(l) + return dv.cachedLexer } -func (dv *DiffView) getChromaFormatter(gbColor color.Color) chroma.Formatter { +func (dv *DiffView) getChromaFormatter(bgColor color.Color) chroma.Formatter { return chromaFormatter{ - bgColor: gbColor, + bgColor: bgColor, } }