featurep! will be renamed modulep! in the future, so it's been deprecated. They have identical interfaces, and can be replaced without issue. featurep! was never quite the right name for this macro. It implied that it had some connection to featurep, which it doesn't (only that it was similar in purpose; still, Doom modules are not features). To undo such implications and be consistent with its namespace (and since we're heading into a storm of breaking changes with the v3 release anyway), now was the best opportunity to begin the transition.
220 lines
9.9 KiB
EmacsLisp
220 lines
9.9 KiB
EmacsLisp
;;; 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).")
|
|
|
|
|
|
;;
|
|
;;; Default styles
|
|
|
|
(when (modulep! +pretty)
|
|
;; UI: make the fringe small enough that the diff bars aren't too domineering,
|
|
;; while leaving enough room for other indicators.
|
|
(if (fboundp 'fringe-mode) (fringe-mode '8))
|
|
;; UI: the gutter looks less cramped with some space between it and buffer.
|
|
(setq-default fringes-outside-margins t)
|
|
|
|
;; STYLE: Redefine fringe bitmaps to take up only half the horizontal space in
|
|
;; the fringe. This way we avoid overbearingly large diff bars without having
|
|
;; 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.
|
|
(if (not (modulep! +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
|
|
(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))
|
|
(advice-add #'diff-hl-fringe-bmp-from-pos :override #'+vc-gutter-type-at-pos-fn)
|
|
(advice-add #'diff-hl-fringe-bmp-from-type :override #'+vc-gutter-type-at-pos-fn)
|
|
(setq diff-hl-draw-borders nil)
|
|
(add-hook! 'diff-hl-mode-hook
|
|
(defun +vc-gutter-fix-diff-hl-faces-h ()
|
|
(mapc (doom-rpartial #'set-face-background nil)
|
|
'(diff-hl-insert
|
|
diff-hl-delete
|
|
diff-hl-change)))))
|
|
|
|
;; FIX: To minimize overlap between flycheck indicators and git-gutter/diff-hl
|
|
;; indicators in the left fringe.
|
|
(after! flycheck
|
|
;; 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)))
|
|
|
|
|
|
;;
|
|
;;; git-gutter
|
|
|
|
(use-package! git-gutter
|
|
:unless (modulep! +diff-hl)
|
|
:commands git-gutter:revert-hunk git-gutter:stage-hunk
|
|
:init
|
|
(add-hook! 'find-file-hook
|
|
(defun +vc-gutter-init-maybe-h ()
|
|
"Enable `git-gutter-mode' in the current buffer.
|
|
If the buffer doesn't represent an existing file, `git-gutter-mode's activation
|
|
is deferred until the file is saved. Respects `git-gutter:disabled-modes'."
|
|
(let ((file-name (buffer-file-name (buffer-base-buffer))))
|
|
(cond
|
|
((and (file-remote-p (or file-name default-directory))
|
|
(not +vc-gutter-in-remote-files)))
|
|
;; UX: If not a valid file, wait until it is written/saved to activate
|
|
;; git-gutter.
|
|
((not (and file-name (vc-backend file-name)))
|
|
(add-hook 'after-save-hook #'+vc-gutter-init-maybe-h nil 'local))
|
|
;; UX: Allow git-gutter or git-gutter-fringe to activate based on the
|
|
;; type of frame we're in. This allows git-gutter to work for silly
|
|
;; geese who open both tty and gui frames from the daemon.
|
|
((if (and (display-graphic-p)
|
|
(require 'git-gutter-fringe nil t))
|
|
(setq-local git-gutter:init-function #'git-gutter-fr:init
|
|
git-gutter:view-diff-function #'git-gutter-fr:view-diff-infos
|
|
git-gutter:clear-function #'git-gutter-fr:clear
|
|
git-gutter:window-width -1)
|
|
(setq-local git-gutter:init-function 'nil
|
|
git-gutter:view-diff-function #'git-gutter:view-diff-infos
|
|
git-gutter:clear-function #'git-gutter:clear-diff-infos
|
|
git-gutter:window-width 1))
|
|
(unless (memq major-mode git-gutter:disabled-modes)
|
|
(git-gutter-mode +1)
|
|
(remove-hook 'after-save-hook #'+vc-gutter-init-maybe-h 'local)))))))
|
|
|
|
;; UX: Disable in Org mode, as per syl20bnr/spacemacs#10555 and
|
|
;; syohex/emacs-git-gutter#24. Apparently, the mode-enabling function for
|
|
;; global minor modes gets called for new buffers while they are still in
|
|
;; `fundamental-mode', before a major mode has been assigned. I don't know why
|
|
;; this is the case, but adding `fundamental-mode' here fixes the issue.
|
|
(setq git-gutter:disabled-modes '(fundamental-mode image-mode pdf-view-mode))
|
|
:config
|
|
(set-popup-rule! "^\\*git-gutter" :select nil :size '+popup-shrink-to-fit)
|
|
|
|
;; PERF: Only enable the backends that are available, so it doesn't have to
|
|
;; check when opening each buffer.
|
|
(setq git-gutter:handled-backends
|
|
(cons 'git (cl-remove-if-not #'executable-find (list 'hg 'svn 'bzr)
|
|
:key #'symbol-name)))
|
|
|
|
;; UX: update git-gutter on focus (in case I was using git externally)
|
|
(add-hook 'focus-in-hook #'git-gutter:update-all-windows)
|
|
|
|
(add-hook! '(doom-escape-hook doom-switch-window-hook) :append
|
|
(defun +vc-gutter-update-h (&rest _)
|
|
"Refresh git-gutter on ESC. Return nil to prevent shadowing other
|
|
`doom-escape-hook' hooks."
|
|
(ignore (or (memq this-command '(git-gutter:stage-hunk
|
|
git-gutter:revert-hunk))
|
|
inhibit-redisplay
|
|
(if git-gutter-mode
|
|
(git-gutter)
|
|
(+vc-gutter-init-maybe-h))))))
|
|
;; UX: update git-gutter when using magit commands
|
|
(advice-add #'magit-stage-file :after #'+vc-gutter-update-h)
|
|
(advice-add #'magit-unstage-file :after #'+vc-gutter-update-h)
|
|
|
|
(defadvice! +vc-gutter--fix-linearity-of-hunks-a (diffinfos is-reverse)
|
|
"FIX: `git-gutter:next-hunk' and `git-gutter:previous-hunk' sometimes
|
|
jumping to random hunks."
|
|
:override #'git-gutter:search-near-diff-index
|
|
(cl-position-if (let ((lineno (line-number-at-pos))
|
|
(fn (if is-reverse #'> #'<)))
|
|
(lambda (line) (funcall fn lineno line)))
|
|
diffinfos
|
|
:key #'git-gutter-hunk-start-line
|
|
:from-end is-reverse)))
|
|
|
|
|
|
;;
|
|
;;; diff-hl
|
|
|
|
(use-package! diff-hl
|
|
:when (modulep! +diff-hl)
|
|
:hook (find-file . diff-hl-mode)
|
|
:hook (vc-dir-mode . diff-hl-dir-mode)
|
|
:hook (dired-mode . diff-hl-dired-mode)
|
|
:hook (diff-hl-mode . diff-hl-flydiff-mode)
|
|
:config
|
|
(set-popup-rule! "^\\*diff-hl" :select nil :size '+popup-shrink-to-fit)
|
|
|
|
;; 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 (modulep! :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 (modulep! :tools magit)
|
|
(add-hook 'magit-pre-refresh-hook #'diff-hl-magit-pre-refresh)
|
|
(add-hook 'magit-post-refresh-hook #'diff-hl-magit-post-refresh))
|
|
|
|
;; FIX: The revert popup consumes 50% of the frame, whether or not you're
|
|
;; reverting 2 lines or 20. This fix resizes the popup to match its contents.
|
|
(defadvice! +vc-gutter--shrink-popup-a (fn &rest args)
|
|
:around #'diff-hl-revert-hunk-1
|
|
(letf! ((refine-mode diff-auto-refine-mode)
|
|
(diff-auto-refine-mode t)
|
|
(defun diff-refine-hunk ()
|
|
(when refine-mode
|
|
(funcall diff-refine-hunk))
|
|
(shrink-window-if-larger-than-buffer)))
|
|
(apply fn args)))
|
|
|
|
;; UX: Don't delete the current hunk's indicators while we're editing
|
|
(when (modulep! :editor evil)
|
|
(add-hook! 'diff-hl-flydiff-mode-hook
|
|
(defun +vc-gutter-init-flydiff-mode-h ()
|
|
(if (not diff-hl-flydiff-mode)
|
|
(remove-hook 'evil-insert-state-exit-hook #'diff-hl-flydiff-update)
|
|
(add-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)))))
|