feat(vc-gutter): add +diff-hl backend

This adds an alternative backend to the :ui vc-gutter module, enabled
with the +diff-hl flag. In the future, I intend for diff-hl to replace
git-gutter, as it is slightly faster and depends on more native
functionality (vc.el), but it's still a little buggy. It will remain
opt-in until those issues are sorted out.
This commit is contained in:
Henrik Lissner 2022-08-08 17:46:31 +02:00
parent cd9bc5a1fd
commit 27a448b04b
No known key found for this signature in database
GPG key ID: B60957CA074D39A3
10 changed files with 137 additions and 89 deletions

View file

@ -349,11 +349,11 @@
:desc "Kill link to remote" "y" #'+vc/browse-at-remote-kill :desc "Kill link to remote" "y" #'+vc/browse-at-remote-kill
:desc "Kill link to homepage" "Y" #'+vc/browse-at-remote-kill-homepage :desc "Kill link to homepage" "Y" #'+vc/browse-at-remote-kill-homepage
(:when (featurep! :ui vc-gutter) (:when (featurep! :ui vc-gutter)
:desc "Git revert hunk" "r" #'git-gutter:revert-hunk :desc "Git revert hunk" "r" #'+vc-gutter/revert-hunk
:desc "Git stage hunk" "s" #'git-gutter:stage-hunk :desc "Git stage hunk" "s" #'+vc-gutter/stage-hunk
:desc "Git time machine" "t" #'git-timemachine-toggle :desc "Git time machine" "t" #'git-timemachine-toggle
:desc "Jump to next hunk" "n" #'git-gutter:next-hunk :desc "Jump to next hunk" "n" #'+vc-gutter/next-hunk
:desc "Jump to previous hunk" "p" #'git-gutter:previous-hunk) :desc "Jump to previous hunk" "p" #'+vc-gutter/previous-hunk)
(:when (featurep! :tools magit) (:when (featurep! :tools magit)
:desc "Magit dispatch" "/" #'magit-dispatch :desc "Magit dispatch" "/" #'magit-dispatch
:desc "Magit file dispatch" "." #'magit-file-dispatch :desc "Magit file dispatch" "." #'magit-file-dispatch

View file

@ -452,11 +452,11 @@
(:when (featurep! :ui vc-gutter) (:when (featurep! :ui vc-gutter)
(:when (featurep! :ui hydra) (:when (featurep! :ui hydra)
:desc "VCGutter" "." #'+vc/gutter-hydra/body) :desc "VCGutter" "." #'+vc/gutter-hydra/body)
:desc "Revert hunk" "r" #'git-gutter:revert-hunk :desc "Revert hunk at point" "r" #'+vc-gutter/revert-hunk
:desc "Git stage hunk" "s" #'git-gutter:stage-hunk :desc "stage hunk at point" "s" #'+vc-gutter/stage-hunk
:desc "Git time machine" "t" #'git-timemachine-toggle :desc "Git time machine" "t" #'git-timemachine-toggle
:desc "Jump to next hunk" "]" #'git-gutter:next-hunk :desc "Jump to next hunk" "]" #'+vc-gutter/next-hunk
:desc "Jump to previous hunk" "[" #'git-gutter:previous-hunk) :desc "Jump to previous hunk" "[" #'+vc-gutter/previous-hunk)
(:when (featurep! :tools magit) (:when (featurep! :tools magit)
:desc "Magit dispatch" "/" #'magit-dispatch :desc "Magit dispatch" "/" #'magit-dispatch
:desc "Magit file dispatch" "." #'magit-file-dispatch :desc "Magit file dispatch" "." #'magit-file-dispatch

View file

@ -445,8 +445,8 @@ directives. By default, this only recognizes C directives.")
:m "]x" #'+web:encode-html-entities :m "]x" #'+web:encode-html-entities
:m "[x" #'+web:decode-html-entities) :m "[x" #'+web:decode-html-entities)
(:when (featurep! :ui vc-gutter) (:when (featurep! :ui vc-gutter)
:m "]d" #'git-gutter:next-hunk :m "]d" #'+vc-gutter/next-hunk
:m "[d" #'git-gutter:previous-hunk) :m "[d" #'+vc-gutter/previous-hunk)
(:when (featurep! :ui hl-todo) (:when (featurep! :ui hl-todo)
:m "]t" #'hl-todo-next :m "]t" #'hl-todo-next
:m "[t" #'hl-todo-previous) :m "[t" #'hl-todo-previous)

View file

@ -72,11 +72,12 @@ Fixes #3939: unsortable dired entries on Windows."
(use-package! diff-hl (use-package! diff-hl
:hook (dired-mode . diff-hl-dired-mode-unless-remote) :when (featurep! :ui vc-gutter)
:hook (magit-post-refresh . diff-hl-magit-post-refresh) :hook (dired-mode-hook . diff-hl-margin-local-mode)
:config :init
;; use margin instead of fringe (unless (featurep! :ui vc-gutter +diff-hl)
(diff-hl-margin-mode)) (add-hook 'dired-mode-hook #'diff-hl-dired-mode-unless-remote)
(add-hook 'magit-post-refresh-hook #'diff-hl-magit-post-refresh)))
(use-package! ranger (use-package! ranger

View file

@ -13,6 +13,10 @@ Supports Git, Svn, Hg, and Bzr.
[[doom-contrib-maintainer:][Become a maintainer?]] [[doom-contrib-maintainer:][Become a maintainer?]]
** Module flags ** Module flags
- +diff-hl ::
Use [[doom-package:][diff-hl]] instead of git-gutter to power the VC gutter. It is a little
faster, but is slightly more prone to visual glitching. [[doom-package:][diff-hl]] is intended to
replace git-gutter at some point in the future.
- +pretty :: - +pretty ::
Apply some stylistic defaults to the fringe, enabling thin bars in the fringe. Apply some stylistic defaults to the fringe, enabling thin bars in the fringe.
This look takes after the modern look of git-gutter in VSCode and Sublime This look takes after the modern look of git-gutter in VSCode and Sublime
@ -22,7 +26,8 @@ Supports Git, Svn, Hg, and Bzr.
diff-hl's faces (like modus-themes does). diff-hl's faces (like modus-themes does).
** Packages ** Packages
- [[doom-package:][git-gutter-fringe]] - [[doom-package:][git-gutter-fringe]] unless [[doom-module:][+diff-hl]]
- [[doom-package:][diff-hl]] if [[doom-module:][+diff-hl]]
** TODO Hacks ** TODO Hacks
#+begin_quote #+begin_quote

View file

@ -1,34 +0,0 @@
;;; ui/vc-gutter/autoload.el -*- lexical-binding: t; -*-
;;;###if (featurep! :ui hydra)
;;;###autoload (autoload '+vc/gutter-hydra/body "ui/vc-gutter/autoload" nil t)
(defhydra +vc/gutter-hydra
(:body-pre (git-gutter-mode 1) :hint nil)
"
[git gutter]
Movement Hunk Actions Misc. +%-4s(car (git-gutter:statistic))/ -%-4s(cdr (git-gutter:statistic))
^_g_^ [_s_] stage [_R_] set start Rev
^_k_^ [_r_] revert
^ ^ [_m_] mark
^ ^ [_p_] popup
^_j_^ [_q_] quit
^_G_^ [_Q_] Quit and disable"
("j" (progn (git-gutter:next-hunk 1) (recenter)))
("k" (progn (git-gutter:previous-hunk 1) (recenter)))
("g" (progn (goto-char (point-min)) (git-gutter:next-hunk 1)))
("G" (progn (goto-char (point-min)) (git-gutter:previous-hunk 1)))
("s" git-gutter:stage-hunk)
("r" git-gutter:revert-hunk)
("m" git-gutter:mark-hunk)
("p" git-gutter:popup-hunk)
("R" git-gutter:set-start-revision)
("q"
(when (get-buffer git-gutter:popup-buffer)
(kill-buffer (get-buffer git-gutter:popup-buffer)))
:color blue)
("Q"
(progn (git-gutter-mode -1)
(when (get-buffer git-gutter:popup-buffer)
(kill-buffer (get-buffer git-gutter:popup-buffer))))
:color blue))

View file

@ -0,0 +1,11 @@
;;; ui/vc-gutter/autoload/diff-hl.el -*- lexical-binding: t; -*-
;;;###if (featurep! +diff-hl)
;;;###autoload
(defalias '+vc-gutter/stage-hunk #'diff-hl-stage-current-hunk)
;;;###autoload
(defalias '+vc-gutter/revert-hunk #'diff-hl-revert-hunk)
;;;###autoload
(defalias '+vc-gutter/next-hunk #'diff-hl-next-hunk)
;;;###autoload
(defalias '+vc-gutter/previous-hunk #'diff-hl-previous-hunk)

View file

@ -0,0 +1,11 @@
;;; ui/vc-gutter/autoload/vc-gutter.el -*- lexical-binding: t; -*-
;;;###if (not (featurep! +diff-hl))
;;;###autoload
(defalias '+vc-gutter/stage-hunk #'git-gutter:stage-hunk)
;;;###autoload
(defalias '+vc-gutter/revert-hunk #'git-gutter:revert-hunk)
;;;###autoload
(defalias '+vc-gutter/next-hunk #'git-gutter:next-hunk)
;;;###autoload
(defalias '+vc-gutter/previous-hunk #'git-gutter:previous-hunk)

View file

@ -1,20 +1,12 @@
;;; ui/vc-gutter/config.el -*- lexical-binding: t; -*- ;;; ui/vc-gutter/config.el -*- lexical-binding: t; -*-
;; TODO Implement me
(defvar +vc-gutter-in-margin nil (defvar +vc-gutter-in-margin nil
"If non-nil, use the margin for diffs instead of the fringe.") "If non-nil, use the margin for diffs instead of the fringe.")
(defvar +vc-gutter-in-remote-files nil (defvar +vc-gutter-in-remote-files nil
"If non-nil, enable the vc gutter in remote files (e.g. open through TRAMP).") "If non-nil, enable the vc gutter in remote files (e.g. open through TRAMP).")
(defvar +vc-gutter-diff-unsaved-buffer nil
"If non-nil, `diff-hl-flydiff-mode' will be activated. This allows on-the-fly
diffing, even for unsaved buffers.")
(defvar +vc-gutter-default-style t
"If non-nil, enable the default look of the vc gutter.
This means subtle thin bitmaps on the left, an arrow bitmap for flycheck, and
flycheck indicators moved to the right fringe.")
;; ;;
;;; Default styles ;;; Default styles
@ -31,13 +23,29 @@ flycheck indicators moved to the right fringe.")
;; to shrink the fringe and sacrifice precious space for other fringe ;; to shrink the fringe and sacrifice precious space for other fringe
;; indicators (like flycheck or flyspell). ;; indicators (like flycheck or flyspell).
;; TODO Extract these into a package with faces that themes can target. ;; TODO Extract these into a package with faces that themes can target.
(after! git-gutter-fringe (if (not (featurep! +diff-hl))
(define-fringe-bitmap 'git-gutter-fr:added [224] (after! git-gutter-fringe
nil nil '(center repeated)) (define-fringe-bitmap 'git-gutter-fr:added [224]
(define-fringe-bitmap 'git-gutter-fr:modified [224] nil nil '(center repeated))
nil nil '(center repeated)) (define-fringe-bitmap 'git-gutter-fr:modified [224]
(define-fringe-bitmap 'git-gutter-fr:deleted [128 192 224 240] nil nil '(center repeated))
nil nil 'bottom)) (define-fringe-bitmap 'git-gutter-fr:deleted [128 192 224 240]
nil nil 'bottom))
(defadvice! +vc-gutter-define-thin-bitmaps-a (&rest args)
:override #'diff-hl-define-bitmaps
(set-face-background 'diff-hl-insert nil)
(set-face-background 'diff-hl-delete nil)
(set-face-background 'diff-hl-change nil)
(define-fringe-bitmap 'diff-hl-bmp-middle [224] nil nil '(center repeated))
(define-fringe-bitmap 'diff-hl-bmp-delete [240 224 192 128] nil nil 'top))
(defun +vc-gutter-type-face-fn (type _pos)
(intern (format "diff-hl-%s" type)))
(defun +vc-gutter-type-at-pos-fn (type _pos)
(if (eq type 'delete)
'diff-hl-bmp-delete
'diff-hl-bmp-middle))
(setq diff-hl-fringe-bmp-function #'+vc-gutter-type-at-pos-fn
diff-hl-draw-borders nil))
;; FIX: To minimize overlap between flycheck indicators and git-gutter/diff-hl ;; FIX: To minimize overlap between flycheck indicators and git-gutter/diff-hl
;; indicators in the left fringe. ;; indicators in the left fringe.
@ -50,9 +58,10 @@ flycheck indicators moved to the right fringe.")
;; ;;
;;; Packages ;;; git-gutter
(use-package! git-gutter (use-package! git-gutter
:unless (featurep! +diff-hl)
:commands git-gutter:revert-hunk git-gutter:stage-hunk :commands git-gutter:revert-hunk git-gutter:stage-hunk
:init :init
(add-hook! 'find-file-hook (add-hook! 'find-file-hook
@ -129,26 +138,69 @@ is deferred until the file is saved. Respects `git-gutter:disabled-modes'."
:from-end is-reverse))) :from-end is-reverse)))
;; subtle diff indicators in the fringe ;;
(after! git-gutter-fringe ;;; diff-hl
(when +vc-gutter-default-style
;; standardize default fringe width
(if (fboundp 'fringe-mode) (fringe-mode '4))
;; places the git gutter outside the margins. (use-package! diff-hl
(setq-default fringes-outside-margins t) :when (featurep! +diff-hl)
;; thin fringe bitmaps :hook (doom-first-file . global-diff-hl-mode)
(define-fringe-bitmap 'git-gutter-fr:added [224] :hook (diff-hl-mode . diff-hl-flydiff-mode)
nil nil '(center repeated)) :config
(define-fringe-bitmap 'git-gutter-fr:modified [224] (set-popup-rule! "^\\*diff-hl" :select nil :size '+popup-shrink-to-fit)
nil nil '(center repeated))
(define-fringe-bitmap 'git-gutter-fr:deleted [128 192 224 240]
nil nil 'bottom)))
(after! flycheck ;; PERF: reduce load on remote
(when +vc-gutter-default-style (defvaralias 'diff-hl-disable-on-remote '+vc-gutter-in-remote-files)
;; let diff have left fringe, flycheck can have right fringe ;; PERF: A slightly faster algorithm for diffing.
(setq flycheck-indication-mode 'right-fringe) (setq vc-git-diff-switches '("--histogram"))
;; A non-descript, left-pointing arrow ;; PERF: Slightly more conservative delay before updating the diff
(define-fringe-bitmap 'flycheck-fringe-bitmap-double-arrow (setq diff-hl-flydiff-delay 0.5) ; default: 0.3
[16 48 112 240 112 48 16] nil nil 'center)))
;; UX: get realtime feedback in diffs after staging/unstaging hunks.
(setq diff-hl-show-staged-changes nil)
;; UX: Update diffs when it makes sense too, without being too slow
(when (featurep! :editor evil)
(map! :after diff-hl-show-hunk
:map diff-hl-show-hunk-map
:n "p" #'diff-hl-show-hunk-previous
:n "n" #'diff-hl-show-hunk-next
:n "c" #'diff-hl-show-hunk-copy-original-text
:n "r" #'diff-hl-show-hunk-revert-hunk
:n "[" #'diff-hl-show-hunk-previous
:n "]" #'diff-hl-show-hunk-next
:n "{" #'diff-hl-show-hunk-previous
:n "}" #'diff-hl-show-hunk-next
:n "S" #'diff-hl-show-hunk-stage-hunk))
;; UX: Refresh git-gutter on ESC or refocusing the Emacs frame.
(add-hook! '(doom-escape-hook doom-switch-window-hook) :append
(defun +vc-gutter-update-h (&rest _)
"Return nil to prevent shadowing other `doom-escape-hook' hooks."
(ignore (or inhibit-redisplay
(and (or (bound-and-true-p diff-hl-mode)
(bound-and-true-p diff-hl-dir-mode))
(diff-hl-update-once))))))
;; UX: Update diff-hl when magit alters git state.
(when (featurep! :tools magit)
(add-hook 'magit-pre-refresh-hook #'diff-hl-magit-pre-refresh)
(add-hook 'magit-post-refresh-hook #'diff-hl-magit-post-refresh)
(add-hook 'magit-post-stage-hook #'+vc-gutter-update-h)
(add-hook 'magit-post-unstage-hook #'+vc-gutter-update-h))
;; UX: Don't delete the current hunk's indicators while we're editing
(add-hook! 'diff-hl-flydiff-mode-hook
(defun +vc-gutter-init-flydiff-mode-h ()
(if diff-hl-flydiff-mode
(progn
(advice-remove #'diff-hl-overlay-modified #'ignore)
(when (featurep! :editor evil)
(add-hook 'evil-insert-state-exit-hook #'diff-hl-flydiff-update)))
(remove-hook 'evil-insert-state-exit-hook #'diff-hl-flydiff-update))))
;; FIX: Reverting a hunk causes the cursor to be moved to an unexpected place,
;; often far from the target hunk.
(defadvice! +vc-gutter--save-excursion-a (fn &rest args)
"Suppresses unexpected cursor movement by `diff-hl-revert-hunk'."
:around #'diff-hl-revert-hunk
(let ((pt (point)))
(prog1 (apply fn args)
(goto-char pt)))))

View file

@ -1,4 +1,6 @@
;; -*- no-byte-compile: t; -*- ;; -*- no-byte-compile: t; -*-
;;; ui/vc-gutter/packages.el ;;; ui/vc-gutter/packages.el
(package! git-gutter-fringe :pin "648cb5b57faec55711803cdc9434e55a733c3eba") (if (featurep! +diff-hl)
(package! diff-hl :pin "dabb7be6283488abd8d232ea8ce590d502713ed8")
(package! git-gutter-fringe :pin "648cb5b57faec55711803cdc9434e55a733c3eba"))