Optimize reactive text property updates: only update relevant buffers

1. When using setq-local, only update the current buffer
2. When using setq, update all buffers that have the text property

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-12-20 13:05:28 +00:00
parent a32abe5101
commit 08e7dfc728
2 changed files with 97 additions and 15 deletions

View File

@ -2680,5 +2680,69 @@ Returns list of (START END VALUE) intervals."
(ignore-errors (makunbound 'tp-test-init-name)) (ignore-errors (makunbound 'tp-test-init-name))
(ignore-errors (makunbound 'tp-test-init-other))))) (ignore-errors (makunbound 'tp-test-init-other)))))
(ert-deftest tp-test-setq-local-only-updates-current-buffer ()
"Test that setq-local only updates text properties in the current buffer."
(let ((buf1 nil)
(buf2 nil))
(unwind-protect
(progn
;; Define a reactive layer
(tp-define-layer test-multi-buf-layer
:props (face (:foreground $tp-test-multi-color)))
;; Create first buffer with layer applied
(setq buf1 (generate-new-buffer " *test-buf1*"))
(with-current-buffer buf1
(insert "Hello World")
(tp-set 1 6 'test-multi-buf-layer))
;; Create second buffer with layer applied
(setq buf2 (generate-new-buffer " *test-buf2*"))
(with-current-buffer buf2
(insert "Hello World")
(tp-set 1 6 'test-multi-buf-layer))
;; Use setq-local in buf1
(with-current-buffer buf1
(setq-local tp-test-multi-color "red"))
;; buf1 should be updated
(with-current-buffer buf1
(should (equal (plist-get (get-text-property 1 'face) :foreground) "red")))
;; buf2 should NOT be updated (still nil)
(with-current-buffer buf2
(should (equal (plist-get (get-text-property 1 'face) :foreground) nil))))
;; Cleanup
(when (buffer-live-p buf1) (kill-buffer buf1))
(when (buffer-live-p buf2) (kill-buffer buf2))
(ignore-errors (makunbound 'tp-test-multi-color)))))
(ert-deftest tp-test-setq-updates-all-buffers-with-property ()
"Test that setq updates text properties in all buffers that have the property."
(let ((buf1 nil)
(buf2 nil))
(unwind-protect
(progn
;; Define a reactive layer
(tp-define-layer test-global-layer
:props (face (:foreground $tp-test-global-color)))
;; Create first buffer with layer applied
(setq buf1 (generate-new-buffer " *test-buf1*"))
(with-current-buffer buf1
(insert "Hello World")
(tp-set 1 6 'test-global-layer))
;; Create second buffer with layer applied
(setq buf2 (generate-new-buffer " *test-buf2*"))
(with-current-buffer buf2
(insert "Hello World")
(tp-set 1 6 'test-global-layer))
;; Use global setq
(setq tp-test-global-color "blue")
;; Both buffers should be updated
(with-current-buffer buf1
(should (equal (plist-get (get-text-property 1 'face) :foreground) "blue")))
(with-current-buffer buf2
(should (equal (plist-get (get-text-property 1 'face) :foreground) "blue"))))
;; Cleanup
(when (buffer-live-p buf1) (kill-buffer buf1))
(when (buffer-live-p buf2) (kill-buffer buf2))
(ignore-errors (makunbound 'tp-test-global-color)))))
(provide 'tp-ert-tests) (provide 'tp-ert-tests)
;;; tp-ert-tests.el ends here ;;; tp-ert-tests.el ends here

48
tp.el
View File

@ -206,11 +206,14 @@ Only the reactive portions of the properties are stored for each variable."
(tp--unregister-layer-computed layer-name) (tp--unregister-layer-computed layer-name)
(tp--unregister-layer-data layer-name)) (tp--unregister-layer-data layer-name))
(defun tp--reactive-variable-watcher (symbol newval operation _where) (defun tp--reactive-variable-watcher (symbol newval operation where)
"Watcher function called when a reactive variable changes. "Watcher function called when a reactive variable changes.
SYMBOL is the variable that changed. SYMBOL is the variable that changed.
NEWVAL is the new value being set. NEWVAL is the new value being set.
OPERATION is the type of operation (set, let, unlet, makunbound, defvaralias). OPERATION is the type of operation (set, let, unlet, makunbound, defvaralias).
WHERE indicates where the variable was set:
- nil for global `setq' or `set'
- a buffer for `setq-local'
Updates all layers that depend on this variable. Updates all layers that depend on this variable.
Only 'set' operations trigger updates because: Only 'set' operations trigger updates because:
@ -243,8 +246,10 @@ Only 'set' operations trigger updates because:
(cl-loop for (key val) on resolved-props by #'cddr (cl-loop for (key val) on resolved-props by #'cddr
do (setq current-props (plist-put current-props key val))) do (setq current-props (plist-put current-props key val)))
(tp--set-layer-props layer-name current-props)))))) (tp--set-layer-props layer-name current-props))))))
;; Update all text regions with this layer ;; Update text regions with this layer
(tp--update-layer-regions layer-name)))))) ;; If WHERE is a buffer (setq-local), only update that buffer
;; If WHERE is nil (setq), update all buffers that have the text property
(tp--update-layer-regions layer-name where))))))
(defun tp--invoke-layer-watchers (layer-name symbol newval oldval) (defun tp--invoke-layer-watchers (layer-name symbol newval oldval)
"Invoke all registered watcher callbacks for LAYER-NAME watching SYMBOL. "Invoke all registered watcher callbacks for LAYER-NAME watching SYMBOL.
@ -401,25 +406,38 @@ If a variable is not bound, define it with the initial value (nil if not specifi
(unless (boundp var-sym) (unless (boundp var-sym)
(set var-sym initial-val))))) (set var-sym initial-val)))))
(defun tp--update-layer-regions (layer-name) (defun tp--update-layer-regions (layer-name &optional where)
"Update all text regions that have LAYER-NAME applied. "Update text regions that have LAYER-NAME applied.
Re-applies the layer properties using tp-search-map and tp-add." Re-applies the layer properties using tp-search-map and tp-add.
WHERE specifies which buffers to update:
- If WHERE is a buffer, only update that buffer (setq-local case).
- If WHERE is nil, update all buffers that have the text property (setq case)."
(let ((props (tp-layer-props layer-name))) (let ((props (tp-layer-props layer-name)))
(when props (when props
;; Update in all buffers (if (and where (bufferp where) (buffer-live-p where))
(dolist (buf (buffer-list)) ;; setq-local case: only update the specific buffer
(when (buffer-live-p buf) (with-current-buffer where
(with-current-buffer buf
;; Use tp-search-map to find all regions with this layer
;; The callback uses start and end to set properties directly
(save-excursion (save-excursion
(tp-search-map (tp-search-map
(lambda (_text start end) (lambda (_text start end)
;; Apply the new properties directly to the buffer region
(tp-add start end props) (tp-add start end props)
;; Return nil to skip text replacement
nil) nil)
'tp-name layer-name)))))))) 'tp-name layer-name)))
;; setq case: update all buffers that have the text property
(dolist (buf (buffer-list))
(when (buffer-live-p buf)
(with-current-buffer buf
;; Use tp-search-map to find all regions with this layer
;; The callback uses start and end to set properties directly
(save-excursion
(tp-search-map
(lambda (_text start end)
;; Apply the new properties directly to the buffer region
(tp-add start end props)
;; Return nil to skip text replacement
nil)
'tp-name layer-name)))))))))
(defun tp-reactive-reset () (defun tp-reactive-reset ()
"Reset all reactive text property watchers and dependencies." "Reset all reactive text property watchers and dependencies."