From 08e7dfc728a1698c12a0d6ed888b40b57f8d0ce8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 13:05:28 +0000 Subject: [PATCH] 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> --- tp-tests.el | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++ tp.el | 48 +++++++++++++++++++++++++++------------- 2 files changed, 97 insertions(+), 15 deletions(-) diff --git a/tp-tests.el b/tp-tests.el index 24da28b..46208a6 100644 --- a/tp-tests.el +++ b/tp-tests.el @@ -2680,5 +2680,69 @@ Returns list of (START END VALUE) intervals." (ignore-errors (makunbound 'tp-test-init-name)) (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) ;;; tp-ert-tests.el ends here diff --git a/tp.el b/tp.el index 2e73861..9c0bf76 100644 --- a/tp.el +++ b/tp.el @@ -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-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. SYMBOL is the variable that changed. NEWVAL is the new value being set. 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. 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 do (setq current-props (plist-put current-props key val))) (tp--set-layer-props layer-name current-props)))))) - ;; Update all text regions with this layer - (tp--update-layer-regions layer-name)))))) + ;; Update text regions with this layer + ;; 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) "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) (set var-sym initial-val))))) -(defun tp--update-layer-regions (layer-name) - "Update all text regions that have LAYER-NAME applied. -Re-applies the layer properties using tp-search-map and tp-add." +(defun tp--update-layer-regions (layer-name &optional where) + "Update text regions that have LAYER-NAME applied. +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))) (when props - ;; Update in all buffers - (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 + (if (and where (bufferp where) (buffer-live-p where)) + ;; setq-local case: only update the specific buffer + (with-current-buffer where (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)))))))) + '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 () "Reset all reactive text property watchers and dependencies."