@@ -27,7 +27,24 @@ const (
2727 minWidth = 80
2828)
2929
30+ type toolRenderCache struct {
31+ // Line counts - computed once, never change
32+ added int
33+ removed int
34+ lineCounted bool
35+
36+ // Rendered output - invalidated when width/splitView/status changes
37+ rendered string
38+ renderCached bool
39+ renderedWidth int
40+ renderedSplit bool
41+ renderedStatus types.ToolStatus
42+ }
43+
3044var (
45+ cache = make (map [string ]* toolRenderCache ) // keyed by toolCallID
46+ cacheMu sync.RWMutex
47+
3148 lexerCache = make (map [string ]chroma.Lexer )
3249 lexerCacheMu sync.RWMutex
3350)
@@ -44,7 +61,53 @@ type linePair struct {
4461 newLineNum int
4562}
4663
64+ func getOrCreateCache (toolCallID string ) * toolRenderCache {
65+ cacheMu .RLock ()
66+ if c , ok := cache [toolCallID ]; ok {
67+ cacheMu .RUnlock ()
68+ return c
69+ }
70+ cacheMu .RUnlock ()
71+
72+ cacheMu .Lock ()
73+ defer cacheMu .Unlock ()
74+ // Double-check after acquiring write lock
75+ if c , ok := cache [toolCallID ]; ok {
76+ return c
77+ }
78+ c := & toolRenderCache {}
79+ cache [toolCallID ] = c
80+ return c
81+ }
82+
4783func renderEditFile (toolCall tools.ToolCall , width int , splitView bool , toolStatus types.ToolStatus ) string {
84+ c := getOrCreateCache (toolCall .ID )
85+
86+ cacheMu .RLock ()
87+ if c .renderCached &&
88+ c .renderedWidth == width &&
89+ c .renderedSplit == splitView &&
90+ c .renderedStatus == toolStatus {
91+ result := c .rendered
92+ cacheMu .RUnlock ()
93+ return result
94+ }
95+ cacheMu .RUnlock ()
96+
97+ result := renderEditFileUncached (toolCall , width , splitView , toolStatus )
98+
99+ cacheMu .Lock ()
100+ c .rendered = result
101+ c .renderCached = true
102+ c .renderedWidth = width
103+ c .renderedSplit = splitView
104+ c .renderedStatus = toolStatus
105+ cacheMu .Unlock ()
106+
107+ return result
108+ }
109+
110+ func renderEditFileUncached (toolCall tools.ToolCall , width int , splitView bool , toolStatus types.ToolStatus ) string {
48111 var args builtin.EditFileArgs
49112 if err := json .Unmarshal ([]byte (toolCall .Function .Arguments ), & args ); err != nil {
50113 return ""
@@ -71,6 +134,56 @@ func renderEditFile(toolCall tools.ToolCall, width int, splitView bool, toolStat
71134 return output .String ()
72135}
73136
137+ // countDiffLines returns the number of added and removed lines for the edit.
138+ // Results are cached per tool call since arguments are immutable.
139+ func countDiffLines (toolCall tools.ToolCall , _ types.ToolStatus ) (added , removed int ) {
140+ c := getOrCreateCache (toolCall .ID )
141+
142+ cacheMu .RLock ()
143+ if c .lineCounted {
144+ added , removed = c .added , c .removed
145+ cacheMu .RUnlock ()
146+ return added , removed
147+ }
148+ cacheMu .RUnlock ()
149+
150+ added , removed = countDiffLinesUncached (toolCall )
151+
152+ cacheMu .Lock ()
153+ c .added = added
154+ c .removed = removed
155+ c .lineCounted = true
156+ cacheMu .Unlock ()
157+
158+ return added , removed
159+ }
160+
161+ func countDiffLinesUncached (toolCall tools.ToolCall ) (added , removed int ) {
162+ var args builtin.EditFileArgs
163+ if err := json .Unmarshal ([]byte (toolCall .Function .Arguments ), & args ); err != nil {
164+ return 0 , 0
165+ }
166+
167+ for _ , edit := range args .Edits {
168+ edits := udiff .Strings (edit .OldText , edit .NewText )
169+ diff , err := udiff .ToUnifiedDiff ("old" , "new" , edit .OldText , edits , 0 )
170+ if err != nil {
171+ continue
172+ }
173+ for _ , hunk := range diff .Hunks {
174+ for _ , line := range hunk .Lines {
175+ switch line .Kind {
176+ case udiff .Insert :
177+ added ++
178+ case udiff .Delete :
179+ removed ++
180+ }
181+ }
182+ }
183+ }
184+ return added , removed
185+ }
186+
74187func computeDiff (path , oldText , newText string , toolStatus types.ToolStatus ) []* udiff.Hunk {
75188 currentContent , err := os .ReadFile (path )
76189 if err != nil {
@@ -91,7 +204,6 @@ func computeDiff(path, oldText, newText string, toolStatus types.ToolStatus) []*
91204 oldContent = strings .Replace (newContent , newText , oldText , 1 )
92205 }
93206
94- // Now compute diff between old and new
95207 edits := udiff .Strings (oldContent , newContent )
96208
97209 diff , err := udiff .ToUnifiedDiff ("old" , "new" , oldContent , edits , 3 )
0 commit comments