From 60779c9aed3bbe483809c9ac72fc63833788c485 Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Thu, 21 Jun 2018 21:14:00 +0200 Subject: [PATCH] feature/version-control => ui/vc-gutter, emacs/vc Reorganize vcs functionality. Moves the custom fringe bitmaps into :ui vc-gutter. --- init.example.el | 3 +- modules/config/default/+bindings.el | 4 +- modules/config/default/+evil-commands.el | 4 +- .../version-control => emacs/vc}/autoload.el | 27 +-- .../version-control => emacs/vc}/config.el | 62 +++++-- modules/emacs/vc/packages.el | 7 + modules/feature/version-control/+git.el | 78 --------- modules/feature/version-control/packages.el | 15 -- modules/ui/doom/config.el | 23 --- modules/ui/vc-gutter/config.el | 161 ++++++++++++++++++ modules/ui/vc-gutter/packages.el | 5 + 11 files changed, 237 insertions(+), 152 deletions(-) rename modules/{feature/version-control => emacs/vc}/autoload.el (72%) rename modules/{feature/version-control => emacs/vc}/config.el (61%) create mode 100644 modules/emacs/vc/packages.el delete mode 100644 modules/feature/version-control/+git.el delete mode 100644 modules/feature/version-control/packages.el create mode 100644 modules/ui/vc-gutter/config.el create mode 100644 modules/ui/vc-gutter/packages.el diff --git a/init.example.el b/init.example.el index d874f4f27..bd93a2c2f 100644 --- a/init.example.el +++ b/init.example.el @@ -13,7 +13,6 @@ spellcheck ; tasing you for misspelling mispelling (syntax-checker ; tasing you for every semicolon you forget +childframe) ; use childframes for error popups (Emacs 26+ only) - version-control ; remember, remember that commit in November workspaces ; tab emulation, persistence & separate workspaces :completion @@ -42,6 +41,7 @@ ;pretty-code ; replace bits of code with pretty symbols ;tabbar ; FIXME an (incomplete) tab bar for Emacs ;unicode ; extended unicode support for various languages + vc-gutter ; vcs diff in the fringe vi-tilde-fringe ; fringe tildes to mark beyond EOB window-select ; visually switch windows @@ -55,6 +55,7 @@ ;eshell ; a consistent, cross-platform shell (WIP) imenu ; an imenu sidebar and searchable code index ;term ; terminals in Emacs + vc ; version-control and Emacs, sitting in a tree :tools editorconfig ; let someone else argue about tabs vs spaces diff --git a/modules/config/default/+bindings.el b/modules/config/default/+bindings.el index bc1e71dda..0f97bca81 100644 --- a/modules/config/default/+bindings.el +++ b/modules/config/default/+bindings.el @@ -597,10 +597,10 @@ :desc "Magit file delete" :n "x" #'magit-file-delete :desc "List gists" :n "G" #'+gist:list :desc "Initialize repo" :n "i" #'magit-init - :desc "Browse issues tracker" :n "I" #'+vcs/git-browse-issues + :desc "Browse issues tracker" :n "I" #'+vc/git-browse-issues :desc "Magit buffer log" :n "l" #'magit-log-buffer-file :desc "List repositories" :n "L" #'magit-list-repositories - :desc "Browse remote" :n "o" #'+vcs/git-browse + :desc "Browse remote" :n "o" #'+vc/git-browse :desc "Magit push popup" :n "p" #'magit-push-popup :desc "Magit pull popup" :n "P" #'magit-pull-popup :desc "Git revert hunk" :n "r" #'git-gutter:revert-hunk diff --git a/modules/config/default/+evil-commands.el b/modules/config/default/+evil-commands.el index 732e6070c..7e2b7391b 100644 --- a/modules/config/default/+evil-commands.el +++ b/modules/config/default/+evil-commands.el @@ -70,8 +70,8 @@ command from the current directory instead of the project root." ;; GIT (ex! "gist" #'+gist:send) ; send current buffer/region to gist (ex! "gistl" #'+gist:list) ; list gists by user -(ex! "gbrowse" #'+vcs/git-browse) ; show file in github/gitlab -(ex! "gissues" #'+vcs/git-browse-issues) ; show github issues +(ex! "gbrowse" #'+vc/git-browse) ; show file in github/gitlab +(ex! "gissues" #'+vc/git-browse-issues) ; show github issues (ex! "git" #'magit-status) ; open magit status window (ex! "gstage" #'magit-stage) (ex! "gunstage" #'magit-unstage) diff --git a/modules/feature/version-control/autoload.el b/modules/emacs/vc/autoload.el similarity index 72% rename from modules/feature/version-control/autoload.el rename to modules/emacs/vc/autoload.el index 8f01cff67..65e007f1d 100644 --- a/modules/feature/version-control/autoload.el +++ b/modules/emacs/vc/autoload.el @@ -1,7 +1,7 @@ -;;; feature/version-control/autoload.el -*- lexical-binding: t; -*- +;;; emacs/vc/autoload.el -*- lexical-binding: t; -*- ;;;###autoload -(defun +vcs-root () +(defun +vc-git-root-url () "Return the root git repo URL for the current file." (require 'git-link) (let* ((remote (git-link--select-remote)) @@ -13,7 +13,7 @@ (defvar git-link-open-in-browser) ;;;###autoload -(defun +vcs/git-browse () +(defun +vc/git-browse () "Open the website for the current version controlled file. Fallback to repository root." (interactive) @@ -24,19 +24,19 @@ repository root." (git-link (git-link--select-remote) beg end)))) ;;;###autoload -(defun +vcs/git-browse-issues () +(defun +vc/git-browse-issues () "Open the issues page for current repo." (interactive) - (browse-url (format "%s/issues" (+vcs-root)))) + (browse-url (format "%s/issues" (+vc-git-root-url)))) ;;;###autoload -(defun +vcs/git-browse-pulls () +(defun +vc/git-browse-pulls () "Open the pull requests page for current repo." (interactive) - (browse-url (format "%s/pulls" (+vcs-root)))) + (browse-url (format "%s/pulls" (+vc-git-root-url)))) ;;;###autoload -(defun +vcs*update-header-line (revision) +(defun +vc*update-header-line (revision) "Show revision details in the header-line, instead of the minibuffer. Sometimes I forget `git-timemachine' is enabled in a buffer. Putting revision @@ -50,14 +50,3 @@ info in the `header-line-format' is a good indication." (propertize author 'face 'git-timemachine-minibuffer-author-face) (propertize sha-or-subject 'face 'git-timemachine-minibuffer-detail-face) date-full date-relative)))) - -;;;###autoload -(defun +vcs|enable-smerge-mode-maybe () - "Auto-enable `smerge-mode' when merge conflict is detected." - (save-excursion - (goto-char (point-min)) - (when (re-search-forward "^<<<<<<< " nil :noerror) - (smerge-mode 1) - (when (and (featurep 'hydra) - +vcs-auto-hydra-smerge) - (+hydra-smerge/body))))) diff --git a/modules/feature/version-control/config.el b/modules/emacs/vc/config.el similarity index 61% rename from modules/feature/version-control/config.el rename to modules/emacs/vc/config.el index d3d83f234..7558f4be4 100644 --- a/modules/feature/version-control/config.el +++ b/modules/emacs/vc/config.el @@ -1,26 +1,63 @@ -;;; feature/version-control/config.el -*- lexical-binding: t; -*- +;;; emacs/vc/config.el -*- lexical-binding: t; -*- -(load! "+git") -;; TODO (load! "+hg") - -;; -(setq vc-make-backup-files nil) - -(defvar +vcs-auto-hydra-smerge t +(defvar +vc-auto-hydra-smerge t "When entering `smerge-mode' automatically open associated hydra.") +;; +;; Plugins +;; + +;; `git-timemachine' +(after! git-timemachine + ;; Sometimes I forget `git-timemachine' is enabled in a buffer, so instead of + ;; showing revision details in the minibuffer, show them in + ;; `header-line-format', which has better visibility. + (setq git-timemachine-show-minibuffer-details t) + (advice-add #'git-timemachine--show-minibuffer-details :override #'+vc*update-header-line) + + (after! evil + ;; Force evil to rehash keybindings for the current state + (add-hook 'git-timemachine-mode-hook #'evil-normalize-keymaps))) + + +;; `git-commit-mode' +;; see https://chris.beams.io/posts/git-commit/ +(setq git-commit-fill-column 72 + git-commit-summary-max-length 50 + git-commit-style-convention-checks '(overlong-summary-line non-empty-second-line)) +(when (featurep! :feature evil) + (add-hook 'git-commit-mode-hook #'evil-insert-state)) + + +;; +;; `vc' +;; + +;; `vc-hooks' +(setq vc-make-backup-files nil) + +;; `vc-annotate' (after! vc-annotate (set-popup-rules! - '(("^\\vc-d" :select) ; *vc-diff* + '(("^\\vc-d" :select nil) ; *vc-diff* ("^\\vc-c" :select t))) ; *vc-change-log* (set-evil-initial-state! '(vc-annotate-mode vc-git-log-view-mode) 'normal)) -(def-package! smerge-mode - :hook (find-file . +vcs|enable-smerge-mode-maybe) - :config +;; `smerge-mode' +(defun +vcs|enable-smerge-mode-maybe () + "Auto-enable `smerge-mode' when merge conflict is detected." + (save-excursion + (goto-char (point-min)) + (when (re-search-forward "^<<<<<<< " nil :noerror) + (smerge-mode 1) + (when (and (featurep 'hydra) +vc-auto-hydra-smerge) + (+hydra-smerge/body))))) +(add-hook 'find-file-hook #'+vcs|enable-smerge-mode-maybe) + +(after! smerge-mode ; built-in (unless EMACS26+ (with-no-warnings (defalias #'smerge-keep-upper #'smerge-keep-mine) @@ -65,3 +102,4 @@ ("r" smerge-resolve) ("R" smerge-kill-current) ("q" nil :color blue))) + diff --git a/modules/emacs/vc/packages.el b/modules/emacs/vc/packages.el new file mode 100644 index 000000000..3506fa5a0 --- /dev/null +++ b/modules/emacs/vc/packages.el @@ -0,0 +1,7 @@ +;; -*- no-byte-compile: t; -*- +;;; emacs/vc/packages.el + +(package! git-link) +(package! git-timemachine) +(package! gitconfig-mode) +(package! gitignore-mode) diff --git a/modules/feature/version-control/+git.el b/modules/feature/version-control/+git.el deleted file mode 100644 index 5828a2157..000000000 --- a/modules/feature/version-control/+git.el +++ /dev/null @@ -1,78 +0,0 @@ -;;; feature/version-control/+git.el -*- lexical-binding: t; -*- - -;; see https://chris.beams.io/posts/git-commit/ -(setq git-commit-fill-column 72 - git-commit-summary-max-length 50 - git-commit-style-convention-checks '(overlong-summary-line non-empty-second-line)) - -(when (featurep! :feature evil) - (add-hook 'git-commit-mode-hook #'evil-insert-state)) - - -(def-package! git-gutter-fringe - :defer t - :init - (defun +version-control|git-gutter-maybe () - "Enable `git-gutter-mode' in non-remote buffers." - (when (and (buffer-file-name) - (not (file-remote-p (buffer-file-name)))) - (require 'git-gutter-fringe) - (git-gutter-mode +1))) - (add-hook! (text-mode prog-mode conf-mode) #'+version-control|git-gutter-maybe) - :config - (set-popup-rule! "^\\*git-gutter" :select nil) - - ;; Update git-gutter on focus (in case I was using git externally) - (add-hook 'focus-in-hook #'git-gutter:update-all-windows) - - (defun +version-control|update-git-gutter (&rest _) - "Refresh git-gutter on ESC. Return nil to prevent shadowing other -`doom-escape-hook' hooks." - (when git-gutter-mode - (ignore (git-gutter)))) - - (add-hook 'doom-escape-hook #'+version-control|update-git-gutter t) - - ;; update git-gutter when using these commands - (advice-add #'magit-stage :after #'+version-control|update-git-gutter) - (advice-add #'magit-unstage :after #'+version-control|update-git-gutter) - (advice-add #'magit-stage-file :after #'+version-control|update-git-gutter) - (advice-add #'magit-unstage-file :after #'+version-control|update-git-gutter) - - (defhydra +version-control@git-gutter - (:body-pre (git-gutter-mode 1) :hint nil) - " - ╭─────────────────┐ - Movement Hunk Actions Misc. │ gg: +%-4s(car (git-gutter:statistic))/ -%-3s(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" nil :color blue) - ("Q" (git-gutter-mode -1) :color blue))) - - -(def-package! git-timemachine - :defer t - :config - ;; Sometimes I forget `git-timemachine' is enabled in a buffer, so instead of - ;; showing revision details in the minibuffer, show them in - ;; `header-line-format', which has better visibility. - (setq git-timemachine-show-minibuffer-details t) - (advice-add #'git-timemachine--show-minibuffer-details :override #'+vcs*update-header-line) - - (after! evil - ;; Force evil to rehash keybindings for the current state - (add-hook 'git-timemachine-mode-hook #'evil-force-normal-state))) diff --git a/modules/feature/version-control/packages.el b/modules/feature/version-control/packages.el deleted file mode 100644 index 088ec8cd9..000000000 --- a/modules/feature/version-control/packages.el +++ /dev/null @@ -1,15 +0,0 @@ -;; -*- no-byte-compile: t; -*- -;;; feature/version-control/packages.el - -;;; config.el -;; n/a - -;;; +git -(unless (featurep! -git) - (package! git-gutter-fringe) - (package! git-link) - (package! git-timemachine) - (package! gitconfig-mode) - (package! gitignore-mode)) - -;;; TODO +hg diff --git a/modules/ui/doom/config.el b/modules/ui/doom/config.el index 6dbac3128..9f160bc02 100644 --- a/modules/ui/doom/config.el +++ b/modules/ui/doom/config.el @@ -81,26 +81,3 @@ ov 'display (propertize " [...] " 'face '+doom-folded-face)))) (setq hs-set-up-overlay #'+doom-set-up-overlay)) - -;; NOTE Adjust these bitmaps if you change `doom-fringe-size' -(after! flycheck - ;; because git-gutter is in the left 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)) - -;; subtle diff indicators in the fringe -(after! git-gutter-fringe - ;; 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)) - -;; standardize default fringe width -(if (fboundp 'fringe-mode) (fringe-mode '4)) diff --git a/modules/ui/vc-gutter/config.el b/modules/ui/vc-gutter/config.el new file mode 100644 index 000000000..5c8417ead --- /dev/null +++ b/modules/ui/vc-gutter/config.el @@ -0,0 +1,161 @@ +;;; ui/vc-gutter/config.el -*- lexical-binding: t; -*- + +(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.") + + +;; +;; Plugins +;; + +(def-package! git-gutter-fringe + :defer t + :init + (defun +version-control|git-gutter-maybe () + "Enable `git-gutter-mode' in non-remote buffers." + (when (and buffer-file-name + (vc-backend buffer-file-name) + (or +vc-gutter-in-remote-files + (not (file-remote-p buffer-file-name)))) + (require 'git-gutter-fringe) + (git-gutter-mode +1))) + (add-hook! (text-mode prog-mode conf-mode after-save) + #'+version-control|git-gutter-maybe) + ;; standardize default fringe width + (if (fboundp 'fringe-mode) (fringe-mode '4)) + :config + (set-popup-rule! "^\\*git-gutter" :select nil) + + ;; Update git-gutter on focus (in case I was using git externally) + (add-hook 'focus-in-hook #'git-gutter:update-all-windows) + + (defun +version-control|update-git-gutter (&rest _) + "Refresh git-gutter on ESC. Return nil to prevent shadowing other +`doom-escape-hook' hooks." + (when git-gutter-mode + (ignore (git-gutter)))) + (add-hook 'doom-escape-hook #'+version-control|update-git-gutter t) + + ;; update git-gutter when using these commands + (add-hook 'magit-post-refresh-hook #'+version-control|update-git-gutter) + + (defhydra +version-control@git-gutter + (:body-pre (git-gutter-mode 1) :hint nil) + " + ╭─────────────────┐ + Movement Hunk Actions Misc. │ gg: +%-4s(car (git-gutter:statistic))/ -%-3s(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" nil :color blue) + ("Q" (git-gutter-mode -1) :color blue)) + + ;; subtle diff indicators in the fringe + (when +vc-gutter-default-style + ;; 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) + ;; let diff have left fringe, flycheck can have right fringe + (after! flycheck + (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)))) + + +;; (def-package! diff-hl +;; :defer t +;; :init +;; (defun +vc-gutter|init () +;; "Start `diff-hl-mode' if in a file-visiting and tracked buffer." +;; (when (and buffer-file-name +;; (vc-state buffer-file-name) +;; (or +vc-gutter-in-remote-files +;; (not (file-remote-p buffer-file-name)))) +;; (diff-hl-mode +1))) +;; (add-hook! (text-mode prog-mode conf-mode after-save) +;; #'+vc-gutter|init) +;; ;; standardize fringe size +;; (if (fboundp 'fringe-mode) (fringe-mode '4)) +;; :config +;; (setq vc-git-diff-switches '("--histogram")) +;; ;; Update diffs when it makes sense too, without being too slow +;; (if (not +vc-gutter-diff-unsaved-buffer) +;; (add-hook! '(doom-escape-hook focus-in-hook) #'diff-hl-update) +;; (diff-hl-flydiff-mode +1) +;; (add-hook! '(doom-escape-hook focus-in-hook) #'diff-hl-flydiff-update) +;; (when (featurep! :feature evil) +;; (when diff-hl-flydiff-timer +;; (cancel-timer diff-hl-flydiff-timer)) +;; (add-hook 'evil-insert-state-exit-hook #'diff-hl-flydiff-update))) +;; ;; Don't delete the current hunk's indicators while we're editing +;; (advice-remove #'diff-hl-overlay-modified #'ignore) +;; ;; Update diff-hl when magit refreshes +;; (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh) +;; ;; update git-gutter when using these commands +;; (advice-add #'magit-stage :after #'+version-control|update-git-gutter) +;; (advice-add #'magit-unstage :after #'+version-control|update-git-gutter) +;; (advice-add #'magit-stage-file :after #'+version-control|update-git-gutter) +;; (advice-add #'magit-unstage-file :after #'+version-control|update-git-gutter) +;; ;; Draw me like one of your French editors +;; (setq-default fringes-outside-margins t) +;; (cond ((or +vc-gutter-in-margin (not (display-graphic-p))) +;; (diff-hl-margin-mode) +;; (setq diff-hl-margin-symbols-alist +;; '((insert . "❙") (delete . "^") (change . "❙") +;; (unknown . "❙") (ignored . "❙")))) +;; (t +;; ;; Because diff-hl is in the left fringe +;; (setq flycheck-indication-mode 'right-fringe) +;; (defun +vc-gutter|setup-fringe-bitmaps () +;; "Define thin fringe bitmaps for maximum sexiness." +;; (define-fringe-bitmap 'diff-hl-bmp-top [224] nil nil '(center repeated)) +;; (define-fringe-bitmap 'diff-hl-bmp-middle [224] nil nil '(center repeated)) +;; (define-fringe-bitmap 'diff-hl-bmp-bottom [224] nil nil '(center repeated)) +;; (define-fringe-bitmap 'diff-hl-bmp-insert [224] nil nil '(center repeated)) +;; (define-fringe-bitmap 'diff-hl-bmp-single [224] nil nil '(center repeated)) +;; (define-fringe-bitmap 'diff-hl-bmp-delete [240 224 192 128] nil nil 'top)) +;; (defun +vc-gutter-type-at-pos (type _pos) +;; "Return the bitmap for `diff-hl' to use for change at point." +;; (pcase type +;; (`unknown 'question-mark) +;; (`delete 'diff-hl-bmp-delete) +;; (`change 'diff-hl-bmp-middle) +;; (`ignored 'diff-hl-bmp-i) +;; (x (intern (format "diff-hl-bmp-%s" x))))) +;; ;; Tweak the fringe bitmaps so we get long, elegant bars +;; (setq diff-hl-fringe-bmp-function #'+vc-gutter-type-at-pos +;; diff-hl-draw-borders nil) +;; (add-hook 'diff-hl-mode-hook #'+vc-gutter|setup-fringe-bitmaps)))) + diff --git a/modules/ui/vc-gutter/packages.el b/modules/ui/vc-gutter/packages.el new file mode 100644 index 000000000..921b14a94 --- /dev/null +++ b/modules/ui/vc-gutter/packages.el @@ -0,0 +1,5 @@ +;; -*- no-byte-compile: t; -*- +;;; ui/vc-gutter/packages.el + +(package! git-gutter-fringe) +;; (package! diff-hl)