3

diff-mode provides diff-refine-hunk function to refine the highlighting of the current hunk. However, by default, only diff-hunk-next or explicit diff-refine-hunk calls will do this job -- hunks will not be refined by default. I wrote the following snippet to refine all hunks when I enter diff-mode:

(defun my-diff-hunks-highlight-all ()
  "Highlight all hunks in diff-mode."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (let ((last-point))
      (while (not (equal (point) last-point))
        (setq last-point (point))
        (diff-refine-hunk)
        (diff-hunk-next)))))
(add-hook 'diff-mode-hook 'my-diff-hunks-highlight-all)

However, it turns out that only the last hunk will be refined. Is there any reason that this approach fails? What should be the proper way to do it?

Related Emacs bug: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=18128

xuhdev
  • 1,839
  • 13
  • 26

2 Answers2

3

The actual problem is the following form in the definition of diff-mode-hook. It removes all the refinement overlays that you created with your function my-diff-hunks-highlight-all immediately after starting up diff-mode and initializing font-lock-mode.

(declare-function 'diff-hunk-next "diff-mode")

(defun my-diff-refine-all ()
  "Refine all diffs."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (condition-case nil
        (diff-beginning-of-hunk t)
      (error (diff-hunk-next)))
    (condition-case nil
        (while (not (eobp))
          (diff-refine-hunk)
          (diff-hunk-next))
      (user-error) ;; catches "No next hunk." from `diff-hunk-next' if there is garbage at the end of the file.
      )))

(defun my-diff-hunks-highlight-all ()
  "Highlight all hunks in diff-mode."
  (add-hook 'font-lock-mode-hook #'my-diff-refine-all t t))

(add-hook 'diff-mode-hook 'my-diff-hunks-highlight-all)
politza
  • 3,316
  • 14
  • 16
Tobias
  • 32,569
  • 1
  • 34
  • 75
  • The call of `(diff-beginning-of-hunk t)` does not seem necessary (as diff-refine-hunk calls it), and it causes problems when reading git format patches: diff-beginning-of-file-and-junk: Can’t find the beginning of the file – xuhdev Nov 01 '16 at 18:44
  • Seems the other parts also cause error for git format patches... But running `diff-hunk-next` after loading a git format patch works as expected. Don't know why. – xuhdev Nov 01 '16 at 18:54
  • @xuhdev This is a problem of `diff --git ...`. This form of `diff` appends and prepends stuff and includes binary diffs encoded as ascii. I tried to make the lisp code more roboust for these cases. – Tobias Nov 01 '16 at 19:48
  • This actually still cause some issues with [diff-hl](https://github.com/dgutov/diff-hl). It works well after replacing all the `condition-case` with `ignore-errors`. – xuhdev Nov 04 '16 at 07:53
2

The following works for me when I test on two small files (i.e., there are not many diffs).

(defun my-diff-hunks-highlight-all ()
  "Highlight all hunks in diff-mode."
  (interactive)
  (run-at-time 0.1 nil
               (lambda ()
                 (save-excursion
                   (goto-char (point-min))
                   (while (not (eobp))
                     (diff-hunk-next 1)
                     (sit-for 0.01))))))

(add-hook 'diff-mode-hook 'my-diff-hunks-highlight-all)
  • I didn't call diff-refine-hunk explicitly since it will be called from diff-hunk-next if diff-auto-refine-mode is on (it is by default).
  • (run-at-time 0.1 ...): gives 0.1 seconds to diff-mode to be ready
  • (sit-for 0.01): gives 0.01 seconds to diff-refine-hunk to make effect

The above is not a very good solution, I think. You might need to adjust the times. There should be other element way. I don't know diff-mode very much.

xuchunyang
  • 14,302
  • 1
  • 18
  • 39