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 homepage" "Y" #'+vc/browse-at-remote-kill-homepage
(:when (featurep! :ui vc-gutter)
:desc "Git revert hunk" "r" #'git-gutter:revert-hunk
:desc "Git stage hunk" "s" #'git-gutter:stage-hunk
:desc "Git revert hunk" "r" #'+vc-gutter/revert-hunk
:desc "Git stage hunk" "s" #'+vc-gutter/stage-hunk
:desc "Git time machine" "t" #'git-timemachine-toggle
:desc "Jump to next hunk" "n" #'git-gutter:next-hunk
:desc "Jump to previous hunk" "p" #'git-gutter:previous-hunk)
:desc "Jump to next hunk" "n" #'+vc-gutter/next-hunk
:desc "Jump to previous hunk" "p" #'+vc-gutter/previous-hunk)
(:when (featurep! :tools magit)
:desc "Magit dispatch" "/" #'magit-dispatch
:desc "Magit file dispatch" "." #'magit-file-dispatch

View file

@ -452,11 +452,11 @@
(:when (featurep! :ui vc-gutter)
(:when (featurep! :ui hydra)
:desc "VCGutter" "." #'+vc/gutter-hydra/body)
:desc "Revert hunk" "r" #'git-gutter:revert-hunk
:desc "Git stage hunk" "s" #'git-gutter:stage-hunk
:desc "Revert hunk at point" "r" #'+vc-gutter/revert-hunk
:desc "stage hunk at point" "s" #'+vc-gutter/stage-hunk
:desc "Git time machine" "t" #'git-timemachine-toggle
:desc "Jump to next hunk" "]" #'git-gutter:next-hunk
:desc "Jump to previous hunk" "[" #'git-gutter:previous-hunk)
:desc "Jump to next hunk" "]" #'+vc-gutter/next-hunk
:desc "Jump to previous hunk" "[" #'+vc-gutter/previous-hunk)
(:when (featurep! :tools magit)
:desc "Magit dispatch" "/" #'magit-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:decode-html-entities)
(:when (featurep! :ui vc-gutter)
:m "]d" #'git-gutter:next-hunk
:m "[d" #'git-gutter:previous-hunk)
:m "]d" #'+vc-gutter/next-hunk
:m "[d" #'+vc-gutter/previous-hunk)
(:when (featurep! :ui hl-todo)
:m "]t" #'hl-todo-next
:m "[t" #'hl-todo-previous)

View file

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

View file

@ -13,6 +13,10 @@ Supports Git, Svn, Hg, and Bzr.
[[doom-contrib-maintainer:][Become a maintainer?]]
** 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 ::
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
@ -22,7 +26,8 @@ Supports Git, Svn, Hg, and Bzr.
diff-hl's faces (like modus-themes does).
** 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
#+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; -*-
;; TODO Implement me
(defvar +vc-gutter-in-margin nil
"If non-nil, use the margin for diffs instead of the fringe.")
(defvar +vc-gutter-in-remote-files nil
"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
@ -31,13 +23,29 @@ flycheck indicators moved to the right fringe.")
;; to shrink the fringe and sacrifice precious space for other fringe
;; indicators (like flycheck or flyspell).
;; TODO Extract these into a package with faces that themes can target.
(after! git-gutter-fringe
(define-fringe-bitmap 'git-gutter-fr:added [224]
nil nil '(center repeated))
(define-fringe-bitmap 'git-gutter-fr:modified [224]
nil nil '(center repeated))
(define-fringe-bitmap 'git-gutter-fr:deleted [128 192 224 240]
nil nil 'bottom))
(if (not (featurep! +diff-hl))
(after! git-gutter-fringe
(define-fringe-bitmap 'git-gutter-fr:added [224]
nil nil '(center repeated))
(define-fringe-bitmap 'git-gutter-fr:modified [224]
nil nil '(center repeated))
(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
;; indicators in the left fringe.
@ -50,9 +58,10 @@ flycheck indicators moved to the right fringe.")
;;
;;; Packages
;;; git-gutter
(use-package! git-gutter
:unless (featurep! +diff-hl)
:commands git-gutter:revert-hunk git-gutter:stage-hunk
:init
(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)))
;; subtle diff indicators in the fringe
(after! git-gutter-fringe
(when +vc-gutter-default-style
;; standardize default fringe width
(if (fboundp 'fringe-mode) (fringe-mode '4))
;;
;;; diff-hl
;; places the git gutter outside the margins.
(setq-default fringes-outside-margins t)
;; thin fringe bitmaps
(define-fringe-bitmap 'git-gutter-fr:added [224]
nil nil '(center repeated))
(define-fringe-bitmap 'git-gutter-fr:modified [224]
nil nil '(center repeated))
(define-fringe-bitmap 'git-gutter-fr:deleted [128 192 224 240]
nil nil 'bottom)))
(use-package! diff-hl
:when (featurep! +diff-hl)
:hook (doom-first-file . global-diff-hl-mode)
:hook (diff-hl-mode . diff-hl-flydiff-mode)
:config
(set-popup-rule! "^\\*diff-hl" :select nil :size '+popup-shrink-to-fit)
(after! flycheck
(when +vc-gutter-default-style
;; let diff have left fringe, flycheck can have right fringe
(setq flycheck-indication-mode 'right-fringe)
;; A non-descript, left-pointing arrow
(define-fringe-bitmap 'flycheck-fringe-bitmap-double-arrow
[16 48 112 240 112 48 16] nil nil 'center)))
;; PERF: reduce load on remote
(defvaralias 'diff-hl-disable-on-remote '+vc-gutter-in-remote-files)
;; PERF: A slightly faster algorithm for diffing.
(setq vc-git-diff-switches '("--histogram"))
;; PERF: Slightly more conservative delay before updating the diff
(setq diff-hl-flydiff-delay 0.5) ; default: 0.3
;; 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; -*-
;;; 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"))