branch: elpa/magit
commit c556fee1bd3ccc44da9f2322ec17dc5c3f0ef5be
Author: Jonas Bernoulli <jo...@bernoul.li>
Commit: Jonas Bernoulli <jo...@bernoul.li>

    Generalize section painting
    
    Hunks are still the only sections whose bodies have to painted when
    they are highlighted and unhighlighted.  The reason this is necessary
    is that highlighting is done by using (an) overlay(s) to change the
    background color of selected sections, which shadows an significant
    background color used in section bodies.
    
    Hunks use different background colors to indicate added and removed
    lines.  Instead of using overlays, the background colors of these
    lines (and also of context lines) are changed using text properties,
    when a hunk gains or loses focus.  I.e., they are "painted".
    
    Such painting is more expensive than merely removing or adding one
    or more overlays, because the text has to be parsed again, so we have
    to avoid doing it unless actually necessary.
    
    We do not only want to avoid needlessly painting hunks when moving
    around in the buffer, we also want to avoid it for (hunk) sections
    whose bodies are not visible yet.  To accomplish that the `washer'
    section slot was added.  Its purpose is to delay "washing" until
    the section is expanded.
    
    This slot was originally added specifically for hunk sections, but
    by now it is used by many other sections too.  In most cases it is
    even used to not only delay washing (~= "parsing" and "painting")
    but to even delay inserting any section content until that time.
    
    With this commit, we *stop* using the function from the `washer'
    slot for hunk painting, and replace that with a new mechanism that
    is inspired by the old washer mechanism, but incorporates lessons
    learned since.
    
    The two drivers are still `magit-section-update-highlight' and
    `magit-section-show'.  These two functions are the main driving
    forces behind the two events that make *might* make it necessary
    to repaint.
    
    Replace the hook `magit-section-unhighlight-hook' with a new generic
    function `magit-section-paint'.  Create such a method for diffs, using
    the hunk-specific parts of removed `magit-diff-paint-hunk'.  The parts
    that function are not hunk-specific, are replaced with similar logic
    in new `magit-section-update-paint'.
    
    `magit-section-update-paint' is where we now decide whether repainting
    is actually necessary.
---
 lisp/magit-diff.el    | 119 +++++++++++++++++++-------------------------------
 lisp/magit-mode.el    |   4 +-
 lisp/magit-section.el |  81 ++++++++++++++++++++++++----------
 3 files changed, 103 insertions(+), 101 deletions(-)

diff --git a/lisp/magit-diff.el b/lisp/magit-diff.el
index e89669c146..02cbbc71b5 100644
--- a/lisp/magit-diff.el
+++ b/lisp/magit-diff.el
@@ -2617,7 +2617,6 @@ function errors."
       (magit-delete-line)
       (magit-insert-section
           ( hunk value nil
-            :washer #'magit-diff-paint-hunk
             :combined combined
             :from-range (if combined (butlast ranges) (car ranges))
             :to-range (car (last ranges))
@@ -3266,82 +3265,52 @@ actually a `diff' but a `diffstat' section."
                      (byte-code-function-p last-command))
                  (eq (region-end) (region-beginning))))))
 
-;;; Diff Highlight
-
-(add-hook 'magit-section-unhighlight-hook #'magit-diff-unhighlight)
-
-(defun magit-diff-unhighlight (section selection)
-  "Remove the highlighting of the diff-related SECTION."
-  (when (magit-hunk-section-p section)
-    (magit-diff-paint-hunk section selection nil)
-    t))
-
 ;;; Hunk Paint
 
-(cl-defun magit-diff-paint-hunk
-    (section &optional selection
-             (highlight (magit-section-selected-p section selection)))
-  (let (paint)
-    (unless magit-diff-highlight-hunk-body
-      (setq highlight nil))
-    (cond (highlight
-           (unless (oref section hidden)
-             (cl-pushnew section magit-section-highlighted-sections)
-             (cond ((memq section magit-section-unhighlight-sections)
-                    (setq magit-section-unhighlight-sections
-                          (delq section magit-section-unhighlight-sections)))
-                   (magit-diff-highlight-hunk-body
-                    (setq paint t)))))
-          ((and (oref section hidden)
-                (memq section magit-section-unhighlight-sections))
-           (cl-pushnew section magit-section-highlighted-sections)
-           (setq magit-section-unhighlight-sections
-                 (delq section magit-section-unhighlight-sections)))
-          (t
-           (setq paint t)))
-    (when paint
-      (save-excursion
-        (goto-char (oref section start))
-        (let ((end (oref section end))
-              (merging (looking-at "@@@"))
-              (diff-type (magit-diff-type))
-              (stage nil)
-              (tab-width (magit-diff-tab-width
-                          (magit-section-parent-value section))))
-          (forward-line)
-          (while (< (point) end)
-            (when (and magit-diff-hide-trailing-cr-characters
-                       (char-equal ?\r (char-before (line-end-position))))
-              (put-text-property (1- (line-end-position)) (line-end-position)
-                                 'invisible t))
-            (put-text-property
-             (point) (1+ (line-end-position)) 'font-lock-face
-             (cond
-              ((looking-at "^\\+\\+?\\([<=|>]\\)\\{7\\}")
-               (setq stage (pcase (list (match-string 1) highlight)
-                             ('("<" nil) 'magit-diff-our)
-                             ('("<"   t) 'magit-diff-our-highlight)
-                             ('("|" nil) 'magit-diff-base)
-                             ('("|"   t) 'magit-diff-base-highlight)
-                             ('("=" nil) 'magit-diff-their)
-                             ('("="   t) 'magit-diff-their-highlight)
-                             ('(">" nil) nil)))
-               'magit-diff-conflict-heading)
-              ((looking-at (if merging "^\\(\\+\\| \\+\\)" "^\\+"))
-               (magit-diff-paint-tab merging tab-width)
-               (magit-diff-paint-whitespace merging 'added diff-type)
-               (or stage
-                   (if highlight 'magit-diff-added-highlight 
'magit-diff-added)))
-              ((looking-at (if merging "^\\(-\\| -\\)" "^-"))
-               (magit-diff-paint-tab merging tab-width)
-               (magit-diff-paint-whitespace merging 'removed diff-type)
-               (if highlight 'magit-diff-removed-highlight 
'magit-diff-removed))
-              (t
-               (magit-diff-paint-tab merging tab-width)
-               (magit-diff-paint-whitespace merging 'context diff-type)
-               (if highlight 'magit-diff-context-highlight 
'magit-diff-context))))
-            (forward-line))))
-      (magit-diff-update-hunk-refinement section))))
+(cl-defmethod magit-section-paint ((section magit-hunk-section) highlight)
+  (unless magit-diff-highlight-hunk-body
+    (setq highlight nil))
+  (let ((end (oref section end))
+        (merging (looking-at "@@@"))
+        (diff-type (magit-diff-type))
+        (stage nil)
+        (tab-width (magit-diff-tab-width
+                    (magit-section-parent-value section))))
+    (forward-line)
+    (while (< (point) end)
+      (when (and magit-diff-hide-trailing-cr-characters
+                 (char-equal ?\r (char-before (line-end-position))))
+        (put-text-property (1- (line-end-position)) (line-end-position)
+                           'invisible t))
+      (put-text-property
+       (point) (1+ (line-end-position)) 'font-lock-face
+       (cond
+        ((looking-at "^\\+\\+?\\([<=|>]\\)\\{7\\}")
+         (setq stage (pcase (list (match-string 1) highlight)
+                       ('("<" nil) 'magit-diff-our)
+                       ('("<"   t) 'magit-diff-our-highlight)
+                       ('("|" nil) 'magit-diff-base)
+                       ('("|"   t) 'magit-diff-base-highlight)
+                       ('("=" nil) 'magit-diff-their)
+                       ('("="   t) 'magit-diff-their-highlight)
+                       ('(">" nil) nil)))
+         'magit-diff-conflict-heading)
+        ((looking-at (if merging "^\\(\\+\\| \\+\\)" "^\\+"))
+         (magit-diff-paint-tab merging tab-width)
+         (magit-diff-paint-whitespace merging 'added diff-type)
+         (or stage
+             (if highlight 'magit-diff-added-highlight 'magit-diff-added)))
+        ((looking-at (if merging "^\\(-\\| -\\)" "^-"))
+         (magit-diff-paint-tab merging tab-width)
+         (magit-diff-paint-whitespace merging 'removed diff-type)
+         (if highlight 'magit-diff-removed-highlight 'magit-diff-removed))
+        (t
+         (magit-diff-paint-tab merging tab-width)
+         (magit-diff-paint-whitespace merging 'context diff-type)
+         (if highlight 'magit-diff-context-highlight 'magit-diff-context))))
+      (forward-line)))
+  (magit-diff-update-hunk-refinement section)
+  (oset section painted (if highlight 'highlight 'plain)))
 
 (defvar magit-diff--tab-width-cache nil)
 
diff --git a/lisp/magit-mode.el b/lisp/magit-mode.el
index 0ddfc10717..34af242211 100644
--- a/lisp/magit-mode.el
+++ b/lisp/magit-mode.el
@@ -1070,7 +1070,8 @@ Run hooks `magit-pre-refresh-hook' and 
`magit-post-refresh-hook'."
 (defun magit-refresh-buffer (&optional created)
   "Refresh the current Magit buffer."
   (interactive)
-  (let ((magit--refresh-start-time (current-time))
+  (let ((magit--refreshing-buffer-p t)
+        (magit--refresh-start-time (current-time))
         (magit--refresh-cache (or magit--refresh-cache (list (cons 0 0))))
         (refresh (intern (format "%s-refresh-buffer"
                                  (substring (symbol-name major-mode) 0 -5)))))
@@ -1098,7 +1099,6 @@ Run hooks `magit-pre-refresh-hook' and 
`magit-post-refresh-hook'."
         (setq magit-section-highlight-overlays nil)
         (setq magit-section-selection-overlays nil)
         (setq magit-section-highlighted-sections nil)
-        (setq magit-section-unhighlight-sections nil)
         (let ((inhibit-read-only t))
           (erase-buffer)
           (save-excursion
diff --git a/lisp/magit-section.el b/lisp/magit-section.el
index a56faf4f55..9d486ffd6c 100644
--- a/lisp/magit-section.el
+++ b/lisp/magit-section.el
@@ -108,13 +108,6 @@ similar defect.")
   "Hook run by `magit-section-goto'.
 That function in turn is used by all section movement commands.")
 
-(defvar magit-section-unhighlight-hook nil
-  "Functions used to unhighlight the previously current section.
-Each function is run with the current section as only argument
-until one of them returns non-nil.  Most sections are properly
-unhighlighted without requiring a specialized unhighlighter,
-diff-related sections being the only exception.")
-
 (defvar magit-section-set-visibility-hook
   (list #'magit-section-cached-visibility)
   "Hook used to set the initial visibility of a section.
@@ -315,13 +308,16 @@ no effect.  This also has no effect for Emacs >= 28, where
 (defvar-local magit-section-highlight-force-update nil)
 (defvar-local magit-section-highlight-overlays nil)
 (defvar-local magit-section-selection-overlays nil)
-(defvar-local magit-section-highlighted-sections nil)
-(defvar-local magit-section-unhighlight-sections nil)
+(defvar-local magit-section-highlighted-sections nil
+  "List of highlighted sections that may have to be repainted on focus 
change.")
 (defvar-local magit-section-focused-sections nil)
 
 (defvar-local magit-section-inhibit-markers nil)
 (defvar-local magit-section-insert-in-reverse nil)
 
+(defvar-local magit--refreshing-buffer-p nil
+  "Whether the current buffer is presently being refreshed.")
+
 ;;; Faces
 
 (defgroup magit-section-faces nil
@@ -924,6 +920,7 @@ With a prefix argument also expand it." heading)
   (interactive (list (magit-current-section)))
   (oset section hidden nil)
   (magit-section--opportunistic-wash section)
+  (magit-section--opportunistic-paint section)
   (when-let ((beg (oref section content)))
     (remove-overlays beg (oref section end) 'invisible t))
   (magit-section-maybe-update-visibility-indicator section)
@@ -1675,15 +1672,12 @@ evaluated its BODY.  Admittedly that's a bit of a hack."
     (oset section washer nil)
     (let ((inhibit-read-only t)
           (magit-insert-section--parent section)
-          (magit-insert-section--current section)
-          (content (oref section content)))
+          (magit-insert-section--current section))
       (save-excursion
-        (if (and content (< content (oref section end)))
-            (funcall washer section) ; already partially washed (hunk)
-          (goto-char (oref section end))
-          (oset section content (point-marker))
-          (funcall washer)
-          (oset section end (point-marker)))))
+        (goto-char (oref section end))
+        (oset section content (point-marker))
+        (funcall washer)
+        (oset section end (point-marker))))
     (setq magit-section-highlight-force-update t)))
 
 ;;; Highlight
@@ -1725,7 +1719,8 @@ evaluated its BODY.  Admittedly that's a bit of a hack."
   (setq magit-section-highlight-force-update t))
 
 (defun magit-section-update-highlight (&optional force)
-  (let ((section (magit-current-section)))
+  (let ((section (magit-current-section))
+        (focused (magit-focused-sections)))
     (cond
      ((or force
           magit-section-highlight-force-update
@@ -1738,9 +1733,6 @@ evaluated its BODY.  Admittedly that's a bit of a hack."
         (mapc #'delete-overlay magit-section-selection-overlays)
         (setq magit-section-highlight-overlays nil)
         (setq magit-section-selection-overlays nil)
-        (setq magit-section-unhighlight-sections
-              magit-section-highlighted-sections)
-        (setq magit-section-highlighted-sections nil)
         (cond ((magit-section--maybe-enable-long-lines-shortcuts))
               ((eq section magit-root-section))
               ((not magit-section-highlight-current)
@@ -1751,9 +1743,9 @@ evaluated its BODY.  Admittedly that's a bit of a hack."
               (t
                (mapc #'magit-section-highlight selection)
                (magit-section-highlight-selection selection)))
-        (dolist (s magit-section-unhighlight-sections)
-          (run-hook-with-args-until-success
-           'magit-section-unhighlight-hook s selection))
+        (dolist (section (cl-union magit-section-highlighted-sections focused))
+          (when (slot-boundp section 'painted)
+            (magit-section-update-paint section focused)))
         (restore-buffer-modified-p nil)))
      ((and (eq magit-section-pre-command-section section)
            magit-section-selection-overlays
@@ -1812,6 +1804,47 @@ evaluated its BODY.  Admittedly that's a bit of a hack."
       (and-let* ((children (oref section children)))
         (magit-section-selective-highlight-p (car children) t))))
 
+;;; Paint
+
+(defun magit-section-update-paint (section focused-sections)
+  (cl-flet ((paint (highlight)
+              (let ((inhibit-read-only t))
+                (save-excursion
+                  (goto-char (oref section start))
+                  (magit-section-paint section highlight))))
+            (unregister ()
+              (setq magit-section-highlighted-sections
+                    (delq section magit-section-highlighted-sections))))
+    (if (magit-section-hidden section)
+        ;; If the section is highlighted but unfocused, it remains
+        ;; highlighted, but `magit-section--opportunistic-paint' via
+        ;; `magit-section-show' will unhighlight on expansion, and
+        ;; before then (or if a refresh occurs first) it doesn't matter.
+        (unregister)
+      (pcase (list (if (memq section focused-sections) 'focus 'unfocus)
+                   (oref section painted))
+        (`(focus ,(or 'nil 'plain))
+         (paint t)
+         (cl-pushnew section magit-section-highlighted-sections))
+        (`(unfocus ,(or 'nil 'highlight))
+         (paint nil)
+         (unregister))
+        ('(unfocus plain)
+         (unregister))))))
+
+(cl-defmethod magit-section-paint ((section magit-section) _highlight)
+  (error "Slot `paint' bound but `magit-section-paint' not implemented for 
`%s'"
+         (eieio-object-class-name section)))
+
+(defun magit-section--opportunistic-paint (section)
+  (when (and (not (oref section hidden))
+             (slot-boundp section 'painted))
+    (if magit--refreshing-buffer-p
+        ;; Defer to `magit-section-update-highlight'.
+        (unless (oref section painted)
+          (cl-pushnew section magit-section-highlighted-sections))
+      (magit-section-update-paint section (magit-focused-sections)))))
+
 ;;; Long Lines
 
 (defvar magit-show-long-lines-warning t)

Reply via email to