@@ -1,6 +1,7 @@
1package diffview
1package diffview
5 "image/color"
6 "image/color"
@@ -59,6 +60,13 @@ type DiffView struct {
59 extraColOnAfter bool // add extra column on after panel
60 extraColOnAfter bool // add extra column on after panel
60 beforeNumDigits int
61 beforeNumDigits int
61 afterNumDigits int
62 afterNumDigits int
64+ // Cache lexer to avoid expensive file pattern matching on every line
65+ cachedLexer chroma.Lexer
67+ // Cache highlighted lines to avoid re-highlighting the same content
68+ // Key: hash of (content + background color), Value: highlighted string
69+ syntaxCache map[string]string
64// New creates a new DiffView with default settings.
72// New creates a new DiffView with default settings.
@@ -68,6 +76,7 @@ func New() *DiffView {
68 contextLines: udiff.DefaultContextLines,
76 contextLines: udiff.DefaultContextLines,
69 lineNumbers: true,
77 lineNumbers: true,
70 tabWidth: 8,
78 tabWidth: 8,
79+ syntaxCache: make(map[string]string),
72 dv.style = DefaultDarkStyle()
81 dv.style = DefaultDarkStyle()
@@ -88,15 +97,26 @@ func (dv *DiffView) Split() *DiffView {
88// Before sets the "before" file for the DiffView.
97// Before sets the "before" file for the DiffView.
89func (dv *DiffView) Before(path, content string) *DiffView {
98func (dv *DiffView) Before(path, content string) *DiffView {
90 dv.before = file{path: path, content: content}
99 dv.before = file{path: path, content: content}
100+ // Clear caches when content changes
91 return dv
102 return dv
94// After sets the "after" file for the DiffView.
105// After sets the "after" file for the DiffView.
95func (dv *DiffView) After(path, content string) *DiffView {
106func (dv *DiffView) After(path, content string) *DiffView {
96 dv.after = file{path: path, content: content}
107 dv.after = file{path: path, content: content}
108+ // Clear caches when content changes
97 return dv
110 return dv
113+// clearCaches clears all caches when content or major settings change.
114+func (dv *DiffView) clearCaches() {
115+ dv.cachedLexer = nil
116+ dv.clearSyntaxCache()
117+ dv.isComputed = false
100// ContextLines sets the number of context lines for the DiffView.
120// ContextLines sets the number of context lines for the DiffView.
101func (dv *DiffView) ContextLines(contextLines int) *DiffView {
121func (dv *DiffView) ContextLines(contextLines int) *DiffView {
102 dv.contextLines = contextLines
122 dv.contextLines = contextLines
@@ -156,9 +176,21 @@ func (dv *DiffView) TabWidth(tabWidth int) *DiffView {
156// If nil, no syntax highlighting will be applied.
176// If nil, no syntax highlighting will be applied.
157func (dv *DiffView) ChromaStyle(style *chroma.Style) *DiffView {
177func (dv *DiffView) ChromaStyle(style *chroma.Style) *DiffView {
158 dv.chromaStyle = style
178 dv.chromaStyle = style
179+ // Clear syntax cache when style changes since highlighting will be different
180+ dv.clearSyntaxCache()
159 return dv
181 return dv
184+// clearSyntaxCache clears the syntax highlighting cache.
185+func (dv *DiffView) clearSyntaxCache() {
186+ if dv.syntaxCache != nil {
187+ // Clear the map but keep it allocated
188+ for k := range dv.syntaxCache {
189+ delete(dv.syntaxCache, k)
162// String returns the string representation of the DiffView.
194// String returns the string representation of the DiffView.
163func (dv *DiffView) String() string {
195func (dv *DiffView) String() string {
164 dv.replaceTabs()
196 dv.replaceTabs()
@@ -700,7 +732,15 @@ func (dv *DiffView) hightlightCode(source string, bgColor color.Color) string {
700 return source
732 return source
735+ // Create cache key from content and background color
736+ cacheKey := dv.createSyntaxCacheKey(source, bgColor)
738+ // Check if we already have this highlighted
739+ if cached, exists := dv.syntaxCache[cacheKey]; exists {
703− l := dv.getChromaLexer(source)
743+ l := dv.getChromaLexer()
704 f := dv.getChromaFormatter(bgColor)
744 f := dv.getChromaFormatter(bgColor)
706 it, err := l.Tokenise(nil, source)
746 it, err := l.Tokenise(nil, source)
@@ -712,22 +752,47 @@ func (dv *DiffView) hightlightCode(source string, bgColor color.Color) string {
712 if err := f.Format(&b, dv.chromaStyle, it); err != nil {
752 if err := f.Format(&b, dv.chromaStyle, it); err != nil {
713 return source
753 return source
715− return b.String()
756+ result := b.String()
758+ // Cache the result for future use
759+ dv.syntaxCache[cacheKey] = result
764+// createSyntaxCacheKey creates a cache key from source content and background color.
765+// We use a simple hash to keep memory usage reasonable.
766+func (dv *DiffView) createSyntaxCacheKey(source string, bgColor color.Color) string {
767+ // Convert color to string representation
768+ r, g, b, a := bgColor.RGBA()
769+ colorStr := fmt.Sprintf("%d,%d,%d,%d", r, g, b, a)
771+ // Create a hash of the content + color to use as cache key
773+ h.Write([]byte(source))
774+ h.Write([]byte(colorStr))
775+ return fmt.Sprintf("%x", h.Sum(nil))
718−func (dv *DiffView) getChromaLexer(source string) chroma.Lexer {
778+func (dv *DiffView) getChromaLexer() chroma.Lexer {
779+ if dv.cachedLexer != nil {
780+ return dv.cachedLexer
719 l := lexers.Match(dv.before.path)
783 l := lexers.Match(dv.before.path)
720 if l == nil {
784 if l == nil {
721− l = lexers.Analyse(source)
785+ l = lexers.Analyse(dv.before.content)
723 if l == nil {
787 if l == nil {
724 l = lexers.Fallback
788 l = lexers.Fallback
726− return chroma.Coalesce(l)
790+ dv.cachedLexer = chroma.Coalesce(l)
791+ return dv.cachedLexer
729−func (dv *DiffView) getChromaFormatter(gbColor color.Color) chroma.Formatter {
794+func (dv *DiffView) getChromaFormatter(bgColor color.Color) chroma.Formatter {
730 return chromaFormatter{
795 return chromaFormatter{
731− bgColor: gbColor,
796+ bgColor: bgColor,