(defmacro diff-hl-with-diff-switches (body)
`(let ((vc-git-diff-switches nil)
- (vc-hg-diff-switches nil)
- (vc-svn-diff-switches nil)
- (vc-diff-switches '("-U0"))
- (vc-disable-async-diff t))
+ (vc-hg-diff-switches nil)
+ (vc-svn-diff-switches nil)
+ (vc-diff-switches '("-U0"))
+ (vc-disable-async-diff t))
,body))
+(defun diff-hl-make-temp-file-name (file rev &optional manual)
+ "Return a backup file name for REV or the current version of FILE.
+If MANUAL is non-nil it means that a name for backups created by
+the user should be returned."
+ (let* ((auto-save-file-name-transforms
+ `((".*" ,temporary-file-directory t))))
+ (expand-file-name
+ (concat (make-auto-save-file-name)
+ ".~" (subst-char-in-string
+ ?/ ?_ rev)
+ (unless manual ".") "~")
+ temporary-file-directory)))
+
+(defun diff-hl-create-revision (file revision)
+ "Read REVISION of FILE into a buffer and return the buffer."
+ (let ((automatic-backup (diff-hl-make-temp-file-name file revision))
+ (filebuf (get-file-buffer file))
+ (filename (diff-hl-make-temp-file-name file revision 'manual)))
+ (unless (file-exists-p filename)
+ (if (file-exists-p automatic-backup)
+ (rename-file automatic-backup filename nil)
+ (with-current-buffer filebuf
+ (let ((failed t)
+ (coding-system-for-read 'no-conversion)
+ (coding-system-for-write 'no-conversion))
+ (unwind-protect
+ (with-temp-file filename
+ (let ((outbuf (current-buffer)))
+ ;; Change buffer to get local value of
+ ;; vc-checkout-switches.
+ (with-current-buffer filebuf
+ (vc-call find-revision file revision outbuf))))
+ (setq failed nil)
+ (when (and failed (file-exists-p filename))
+ (delete-file filename)))))))
+ filename))
+
+(defun diff-hl-diff-buffer-with-revision (revision)
+ "View the differences between BUFFER and its associated file.
+This requires the external program `diff' to be in your `exec-path'."
+ (interactive)
+ (vc-ensure-vc-buffer)
+ (with-current-buffer (get-buffer (current-buffer))
+ (let ((rev (diff-hl-create-revision
+ buffer-file-name
+ (or revision
+ (vc-working-revision buffer-file-name
+ (vc-responsible-backend buffer-file-name)
+ t))))
+ (temporary-file-directory
+ (if (file-directory-p "/dev/shm/")
+ "/dev/shm/"
+ temporary-file-directory)))
+ (diff-hl-with-diff-switches
+ (diff-no-select rev (current-buffer) "-U 0" 'noasync
+ (get-buffer-create " *diff-hl-diff*"))))))
+
+(defvar diff-hl-modified-tick 0)
+(defvar diff-hl-flydiff-timer)
+(make-variable-buffer-local 'diff-hl-modified-tick)
+
(defun diff-hl-changes ()
(let* ((file buffer-file-name)
(backend (vc-backend file)))
(when backend
(let ((state (vc-state file backend)))
(cond
- ((or (eq state 'edited)
- (and (eq state 'up-to-date)
- ;; VC state is stale in after-revert-hook.
- (or revert-buffer-in-progress-p
- ;; Diffing against an older revision.
- diff-hl-reference-revision)))
- (let* ((buf-name " *diff-hl* ")
- diff-auto-refine-mode
- res)
- (diff-hl-with-diff-switches
- (vc-call-backend backend 'diff (list file)
- diff-hl-reference-revision nil
- buf-name))
- (with-current-buffer buf-name
- (goto-char (point-min))
- (unless (eobp)
- (ignore-errors
- (diff-beginning-of-hunk t))
- (while (looking-at diff-hunk-header-re-unified)
- (let ((line (string-to-number (match-string 3)))
- (len (let ((m (match-string 4)))
- (if m (string-to-number m) 1)))
- (beg (point)))
- (diff-end-of-hunk)
- (let* ((inserts (diff-count-matches "^\\+" beg (point)))
- (deletes (diff-count-matches "^-" beg (point)))
- (type (cond ((zerop deletes) 'insert)
- ((zerop inserts) 'delete)
- (t 'change))))
- (when (eq type 'delete)
- (setq len 1)
- (cl-incf line))
- (push (list line len type) res))))))
- (nreverse res)))
- ((eq state 'added)
- `((1 ,(line-number-at-pos (point-max)) insert)))
- ((eq state 'removed)
- `((1 ,(line-number-at-pos (point-max)) delete))))))))
-
-(defun diff-hl-update ()
- (let ((changes (diff-hl-changes))
- (current-line 1))
- (diff-hl-remove-overlays)
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (dolist (c changes)
- (cl-destructuring-bind (line len type) c
- (forward-line (- line current-line))
- (setq current-line line)
- (let ((hunk-beg (point)))
- (while (cl-plusp len)
- (diff-hl-add-highlighting
- type
- (cond
- ((not diff-hl-draw-borders) 'empty)
- ((and (= len 1) (= line current-line)) 'single)
- ((= len 1) 'bottom)
- ((= line current-line) 'top)
- (t 'middle)))
- (forward-line 1)
- (cl-incf current-line)
- (cl-decf len))
- (let ((h (make-overlay hunk-beg (point)))
- (hook '(diff-hl-overlay-modified)))
- (overlay-put h 'diff-hl t)
- (overlay-put h 'diff-hl-hunk t)
- (overlay-put h 'modification-hooks hook)
- (overlay-put h 'insert-in-front-hooks hook)
- (overlay-put h 'insert-behind-hooks hook)))))))))
+ ((or
+ (and
+ diff-hl-flydiff-mode
+ (buffer-modified-p))
+ (eq state 'edited)
+ (and (eq state 'up-to-date)
+ ;; VC state is stale in after-revert-hook.
+ (or revert-buffer-in-progress-p
+ ;; Diffing against an older revision.
+ diff-hl-reference-revision)))
+ (let (diff-auto-refine-mode res)
+ (with-current-buffer (diff-hl-diff-buffer-with-revision
+ diff-hl-reference-revision)
+ (goto-char (point-min))
+ (unless (eobp)
+ (ignore-errors
+ (diff-beginning-of-hunk t))
+ (while (looking-at diff-hunk-header-re-unified)
+ (let ((line (string-to-number (match-string 3)))
+ (len (let ((m (match-string 4)))
+ (if m (string-to-number m) 1)))
+ (beg (point)))
+ (diff-end-of-hunk)
+ (let* ((inserts (diff-count-matches "^\\+" beg (point)))
+ (deletes (diff-count-matches "^-" beg (point)))
+ (type (cond ((zerop deletes) 'insert)
+ ((zerop inserts) 'delete)
+ (t 'change))))
+ (when (eq type 'delete)
+ (setq len 1)
+ (cl-incf line))
+ (push (list line len type) res))))))
+ (setq diff-hl-modified-tick (buffer-modified-tick))
+ (nreverse res)))
+ ((eq state 'added)
+ `((1 ,(line-number-at-pos (point-max)) insert)))
+ ((eq state 'removed)
+ `((1 ,(line-number-at-pos (point-max)) delete))))))))
+
+(defun diff-hl-update (&optional auto)
+ (unless (and auto
+ (or
+ (= diff-hl-modified-tick (buffer-modified-tick))
+ (file-remote-p default-directory)
+ (not (buffer-modified-p))))
+ (let ((changes (diff-hl-changes))
+ (current-line 1))
+ (diff-hl-remove-overlays)
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char (point-min))
+ (dolist (c changes)
+ (cl-destructuring-bind (line len type) c
+ (forward-line (- line current-line))
+ (setq current-line line)
+ (let ((hunk-beg (point)))
+ (while (cl-plusp len)
+ (diff-hl-add-highlighting
+ type
+ (cond
+ ((not diff-hl-draw-borders) 'empty)
+ ((and (= len 1) (= line current-line)) 'single)
+ ((= len 1) 'bottom)
+ ((= line current-line) 'top)
+ (t 'middle)))
+ (forward-line 1)
+ (cl-incf current-line)
+ (cl-decf len))
+ (let ((h (make-overlay hunk-beg (point)))
+ (hook '(diff-hl-overlay-modified)))
+ (overlay-put h 'diff-hl t)
+ (overlay-put h 'diff-hl-hunk t)
+ (overlay-put h 'modification-hooks hook)
+ (overlay-put h 'insert-in-front-hooks hook)
+ (overlay-put h 'insert-behind-hooks hook))))))))))
(defun diff-hl-add-highlighting (type shape)
(let ((o (make-overlay (point) (point))))
(defun diff-hl-overlay-modified (ov after-p _beg _end &optional _length)
"Delete the hunk overlay and all our line overlays inside it."
- (unless after-p
+ (unless (or
+ diff-hl-flydiff-mode
+ after-p)
(when (overlay-buffer ov)
(diff-hl-remove-overlays (overlay-start ov) (overlay-end ov))
(delete-overlay ov))))
(add-hook 'magit-not-reverted-hook 'diff-hl-update nil t)
(add-hook 'auto-revert-mode-hook 'diff-hl-update nil t)
(add-hook 'text-scale-mode-hook 'diff-hl-define-bitmaps nil t))
+
+ (diff-hl-flydiff-mode -1)
(remove-hook 'after-save-hook 'diff-hl-update t)
(remove-hook 'after-change-functions 'diff-hl-edit t)
(remove-hook 'find-file-hook 'diff-hl-update t)
(when diff-hl-dir-mode
(diff-hl-dir-mode -1))))))
-(provide 'diff-hl)
+(define-minor-mode diff-hl-flydiff-mode
+ "Highlight diffs on-the-fly"
+ :lighter ""
+ (if diff-hl-flydiff-mode
+ (progn
+ (unless (or
+ diff-hl-mode
+ diff-hl-dir-mode)
+ (turn-on-diff-hl-mode))
+ (remove-hook 'after-change-functions #'diff-hl-edit t)
+ (setq diff-hl-flydiff-timer
+ (run-with-idle-timer 0.3 t #'diff-hl-update t)))
+ (cancel-timer diff-hl-flydiff-timer)
+ (add-hook 'after-change-functions 'diff-hl-edit nil t)))
+(provide 'diff-hl)
;;; diff-hl.el ends here