Skip to content

Commit cc950b0

Browse files
committed
Reuse project file cache and avoid relinkify churn
1 parent e727567 commit cc950b0

2 files changed

Lines changed: 105 additions & 21 deletions

File tree

ai-code-session-link.el

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,15 @@ terminal output redraw."
155155
(defvar-local ai-code-session-link--pending-tail-width 0
156156
"Pending tail width to rescan when delayed session linkification runs.")
157157

158+
(defvar-local ai-code-session-link--buffer-project-files-cache nil
159+
"Buffer-local project file cache reused across session relinkify passes.")
160+
161+
(defvar-local ai-code-session-link--last-region-bounds nil
162+
"Last relinkified region bounds used to skip unchanged property churn.")
163+
164+
(defvar-local ai-code-session-link--last-region-text nil
165+
"Last relinkified region text used to skip unchanged property churn.")
166+
158167
(defvar ai-code-session-link--project-files-cache nil
159168
"Dynamic cache of project file lists used during one linkify pass.")
160169

@@ -184,6 +193,17 @@ terminal output redraw."
184193
cached))
185194
(funcall compute)))
186195

196+
(defun ai-code-session-link--buffer-project-files-cache ()
197+
"Return the buffer-local cache of enumerated project files."
198+
(or ai-code-session-link--buffer-project-files-cache
199+
(setq ai-code-session-link--buffer-project-files-cache
200+
(make-hash-table :test 'equal))))
201+
202+
(defun ai-code-session-link--unchanged-region-p (bounds region-text)
203+
"Return non-nil when BOUNDS and REGION-TEXT match the last relinkified region."
204+
(and (equal ai-code-session-link--last-region-bounds bounds)
205+
(equal ai-code-session-link--last-region-text region-text)))
206+
187207
(defun ai-code-session-link--project-files (root)
188208
"Return absolute project files for ROOT."
189209
(when (file-directory-p root)
@@ -468,7 +488,8 @@ terminal output redraw."
468488

469489
(defun ai-code-session-link--linkify-file-region (start end)
470490
"Apply file session links between START and END."
471-
(let ((ai-code-session-link--project-files-cache (make-hash-table :test 'equal))
491+
(let ((ai-code-session-link--project-files-cache
492+
(ai-code-session-link--buffer-project-files-cache))
472493
(ai-code-session-link--resolved-path-cache (make-hash-table :test 'equal)))
473494
(let ((file-links (ai-code-session-link--collect-file-links start end)))
474495
(while file-links
@@ -586,26 +607,31 @@ terminal output redraw."
586607
(widen)
587608
(setq start (max (point-min) start)
588609
end (min (point-max) end))
589-
(let ((pos start))
590-
(while (< pos end)
591-
(let ((next (or (next-single-property-change
592-
pos 'ai-code-session-link nil end)
593-
end)))
594-
(when (get-text-property pos 'ai-code-session-link)
595-
(remove-text-properties
596-
pos next
597-
'(ai-code-session-link nil
598-
ai-code-session-symbol-link nil
599-
ai-code-session-symbol-file nil
600-
mouse-face nil
601-
help-echo nil
602-
keymap nil
603-
follow-link nil
604-
font-lock-face nil
605-
face nil)))
606-
(setq pos next))))
607-
(ai-code-session-link--linkify-url-region start end)
608-
(ai-code-session-link--linkify-file-region start end))))))
610+
(let ((bounds (cons start end))
611+
(region-text (buffer-substring-no-properties start end)))
612+
(unless (ai-code-session-link--unchanged-region-p bounds region-text)
613+
(let ((pos start))
614+
(while (< pos end)
615+
(let ((next (or (next-single-property-change
616+
pos 'ai-code-session-link nil end)
617+
end)))
618+
(when (get-text-property pos 'ai-code-session-link)
619+
(remove-text-properties
620+
pos next
621+
'(ai-code-session-link nil
622+
ai-code-session-symbol-link nil
623+
ai-code-session-symbol-file nil
624+
mouse-face nil
625+
help-echo nil
626+
keymap nil
627+
follow-link nil
628+
font-lock-face nil
629+
face nil)))
630+
(setq pos next))))
631+
(ai-code-session-link--linkify-url-region start end)
632+
(ai-code-session-link--linkify-file-region start end)
633+
(setq ai-code-session-link--last-region-bounds bounds
634+
ai-code-session-link--last-region-text region-text))))))))
609635

610636
(defun ai-code-session-link--recent-output-tail-width (output)
611637
"Return the tail width to rescan after OUTPUT."

test/test_ai-code-session-link.el

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,64 @@
479479
(when (file-directory-p root)
480480
(delete-directory root t)))))
481481

482+
(ert-deftest ai-code-session-link-test-linkify-session-region-reuses-project-files-across-passes ()
483+
"Repeated relinkify passes should reuse project file enumeration."
484+
(let* ((root (make-temp-file "ai-code-session-links-project-cache-passes-" t))
485+
(project-files-count 0)
486+
(ai-code-session-link-enabled t))
487+
(unwind-protect
488+
(cl-letf (((symbol-function 'project-current)
489+
(lambda (&optional _maybe-prompt _dir)
490+
'mock-project))
491+
((symbol-function 'project-root)
492+
(lambda (_project)
493+
root))
494+
((symbol-function 'project-files)
495+
(lambda (_project &optional _dirs)
496+
(cl-incf project-files-count)
497+
'("src/UserService.java"))))
498+
(with-temp-buffer
499+
(setq-local ai-code-backends-infra--session-directory root)
500+
(insert "UserService.java:1\n")
501+
(ai-code-session-link--linkify-session-region (point-min) (point-max))
502+
(ai-code-session-link--linkify-session-region (point-min) (point-max))
503+
(should (= project-files-count 1))))
504+
(when (file-directory-p root)
505+
(delete-directory root t)))))
506+
507+
(ert-deftest ai-code-session-link-test-linkify-session-region-skips-unchanged-property-churn ()
508+
"Repeated linkify should not churn properties for unchanged session text."
509+
(let* ((root (make-temp-file "ai-code-session-links-stable-region-" t))
510+
(src-dir (expand-file-name "src" root))
511+
(file (expand-file-name "FileABC.java" src-dir))
512+
(ai-code-session-link-enabled t))
513+
(unwind-protect
514+
(progn
515+
(make-directory src-dir t)
516+
(with-temp-file file
517+
(insert "class FileABC {}\n"))
518+
(with-temp-buffer
519+
(setq-local ai-code-backends-infra--session-directory root)
520+
(insert "src/FileABC.java:42\n")
521+
(ai-code-session-link--linkify-session-region (point-min) (point-max))
522+
(let ((add-count 0)
523+
(remove-count 0)
524+
(orig-add (symbol-function 'add-text-properties))
525+
(orig-remove (symbol-function 'remove-text-properties)))
526+
(cl-letf (((symbol-function 'add-text-properties)
527+
(lambda (start end props &optional object)
528+
(cl-incf add-count)
529+
(funcall orig-add start end props object)))
530+
((symbol-function 'remove-text-properties)
531+
(lambda (start end props &optional object)
532+
(cl-incf remove-count)
533+
(funcall orig-remove start end props object))))
534+
(ai-code-session-link--linkify-session-region (point-min) (point-max)))
535+
(should (zerop add-count))
536+
(should (zerop remove-count)))))
537+
(when (file-directory-p root)
538+
(delete-directory root t)))))
539+
482540
(ert-deftest ai-code-session-link-test-navigate-symbol-at-point-falls-back-to-associated-file ()
483541
"Symbol navigation should fall back to the nearby file and move to the symbol."
484542
(let* ((root (make-temp-file "ai-code-session-links-symbol-nav-" t))

0 commit comments

Comments
 (0)