doomemacs/modules/tools/magit/config.el
Henrik Lissner a16c40d493
tools/magit: split sub-windows to the side
When a log or diff buffer is opened from the magit status buffer, it
would display them in a random window (or split, if none were
available). This changes forces it to predictably open to the right of
the magit status window (using the next window over, if available).
2020-08-06 00:22:39 -04:00

204 lines
8.7 KiB
EmacsLisp

;;; tools/magit/config.el -*- lexical-binding: t; -*-
(defvar +magit-open-windows-in-direction 'right
"What direction to open new windows from the status buffer.
For example, diffs and log buffers. Accepts `left', `right', `up', and `down'.")
;;
;;; Packages
(use-package! magit
:commands magit-file-delete
:defer-incrementally (dash f s with-editor git-commit package eieio lv transient)
:init
(setq magit-auto-revert-mode nil) ; we do this ourselves further down
;; Must be set early to prevent ~/.emacs.d/transient from being created
(setq transient-levels-file (concat doom-etc-dir "transient/levels")
transient-values-file (concat doom-etc-dir "transient/values")
transient-history-file (concat doom-etc-dir "transient/history"))
:config
(setq transient-default-level 5
magit-diff-refine-hunk t ; show granular diffs in selected hunk
;; Don't autosave repo buffers. This is too magical, and saving can
;; trigger a bunch of unwanted side-effects, like save hooks and
;; formatters. Trust us to know what we're doing.
magit-save-repository-buffers nil)
(add-hook 'magit-process-mode-hook #'goto-address-mode)
(defadvice! +magit-revert-repo-buffers-deferred-a (&rest _)
:after '(magit-checkout magit-branch-and-checkout)
;; Since the project likely now contains new files, best we undo the
;; projectile cache so it can be regenerated later.
(projectile-invalidate-cache nil)
;; Use a more efficient strategy to auto-revert buffers whose git state has
;; changed: refresh the visible buffers immediately...
(+magit-mark-stale-buffers-h))
;; ...then refresh the rest only when we switch to them, not all at once.
(add-hook 'doom-switch-buffer-hook #'+magit-revert-buffer-maybe-h)
;; Center the target file, because it's poor UX to have it at the bottom of
;; the window after invoking `magit-status-here'.
(advice-add #'magit-status-here :after #'doom-recenter-a)
;; The default location for git-credential-cache is in
;; ~/.cache/git/credential. However, if ~/.git-credential-cache/ exists, then
;; it is used instead. Magit seems to be hardcoded to use the latter, so here
;; we override it to have more correct behavior.
(unless (file-exists-p "~/.git-credential-cache/")
(setq magit-credential-cache-daemon-socket
(doom-glob (or (getenv "XDG_CACHE_HOME")
"~/.cache/")
"git/credential/socket")))
;; Prevent scrolling when manipulating magit-status hunks. Otherwise you must
;; reorient yourself every time you stage/unstage/discard/etc a hunk.
;; Especially so on larger projects."
(defvar +magit--pos nil)
(add-hook! 'magit-pre-refresh-hook
(defun +magit--set-window-state-h ()
(setq-local +magit--pos (list (current-buffer) (point) (window-start)))))
(add-hook! 'magit-post-refresh-hook
(defun +magit--restore-window-state-h ()
(when (and +magit--pos (eq (current-buffer) (car +magit--pos)))
(goto-char (cadr +magit--pos))
(set-window-start nil (caddr +magit--pos) t)
(kill-local-variable '+magit--pos))))
;; Magit uses `magit-display-buffer-traditional' to display windows, by
;; default, which is a little primitive. `+magit-display-buffer' marries
;; `magit-display-buffer-fullcolumn-most-v1' with
;; `magit-display-buffer-same-window-except-diff-v1', except:
;;
;; 1. Magit sub-buffers (like `magit-log') that aren't spawned from a status
;; screen are opened as popups.
;; 2. The status screen isn't buried when viewing diffs or logs from the
;; status screen.
(setq transient-display-buffer-action '(display-buffer-below-selected)
magit-display-buffer-function #'+magit-display-buffer-fn)
(set-popup-rule! "^\\(?:\\*magit\\|magit:\\| \\*transient\\*\\)" :ignore t)
(add-hook 'magit-popup-mode-hook #'hide-mode-line-mode)
;; Add additional switches that seem common enough
(transient-append-suffix 'magit-fetch "-p"
'("-t" "Fetch all tags" ("-t" "--tags")))
(transient-append-suffix 'magit-pull "-r"
'("-a" "Autostash" "--autostash"))
;; so magit buffers can be switched to (except for process buffers)
(add-hook! 'doom-real-buffer-functions
(defun +magit-buffer-p (buf)
(with-current-buffer buf
(and (derived-mode-p 'magit-mode)
(not (eq major-mode 'magit-process-mode))))))
;; Clean up after magit by killing leftover magit buffers and reverting
;; affected buffers (or at least marking them as need-to-be-reverted).
(define-key magit-status-mode-map [remap magit-mode-bury-buffer] #'+magit/quit)
;; Close transient with ESC
(define-key transient-map [escape] #'transient-quit-one))
(use-package! forge
:when (featurep! +forge)
;; We defer loading even further because forge's dependencies will try to
;; compile emacsql, which is a slow and blocking operation.
:after-call magit-status
:commands forge-create-pullreq forge-create-issue
:preface
(setq forge-database-file (concat doom-etc-dir "forge/forge-database.sqlite"))
:config
;; All forge list modes are derived from `forge-topic-list-mode'
(map! :map forge-topic-list-mode-map :n "q" #'kill-current-buffer)
(set-popup-rule! "^\\*?[0-9]+:\\(?:new-\\|[0-9]+$\\)" :size 0.45 :modeline t :ttl 0 :quit nil)
(set-popup-rule! "^\\*\\(?:[^/]+/[^ ]+ #[0-9]+\\*$\\|Issues\\|Pull-Requests\\|forge\\)" :ignore t)
(defadvice! +magit--forge-get-repository-lazily-a (&rest _)
"Make `forge-get-repository' return nil if the binary isn't built yet.
This prevents emacsql getting compiled, which appears to come out of the blue
and blocks Emacs for a short while."
:before-while #'forge-get-repository
(file-executable-p emacsql-sqlite-executable))
(defadvice! +magit--forge-build-binary-lazily-a (&rest _)
"Make `forge-dispatch' only build emacsql if necessary.
Annoyingly, the binary gets built as soon as Forge is loaded. Since we've
disabled that in `+magit--forge-get-repository-lazily-a', we must manually
ensure it is built when we actually use Forge."
:before #'forge-dispatch
(unless (file-executable-p emacsql-sqlite-executable)
(emacsql-sqlite-compile 2)
(if (not (file-executable-p emacsql-sqlite-executable))
(message (concat "Failed to build emacsql; forge may not work correctly.\n"
"See *Compile-Log* buffer for details"))
;; HACK Due to changes upstream, forge doesn't initialize completely if
;; it doesn't find `emacsql-sqlite-executable', so we have to do it
;; manually after installing it.
(setq forge--sqlite-available-p t)
(magit-add-section-hook 'magit-status-sections-hook 'forge-insert-pullreqs nil t)
(magit-add-section-hook 'magit-status-sections-hook 'forge-insert-issues nil t)
(after! forge-topic
(dolist (hook forge-bug-reference-hooks)
(add-hook hook #'forge-bug-reference-setup)))))))
(use-package! github-review
:after magit
:config
(transient-append-suffix 'magit-merge "i"
'("y" "Review pull request" +magit/start-github-review))
(after! forge
(transient-append-suffix 'forge-dispatch "c u"
'("c r" "Review pull request" +magit/start-github-review))))
(use-package! magit-todos
:after magit
:config
(setq magit-todos-keyword-suffix "\\(?:([^)]+)\\)?:?") ; make colon optional
(define-key magit-todos-section-map "j" nil))
(use-package! magit-gitflow
:hook (magit-mode . turn-on-magit-gitflow))
(use-package! evil-magit
:when (featurep! :editor evil +everywhere)
:after magit
:init
(setq evil-magit-state 'normal
evil-magit-use-z-for-folds t)
:config
(undefine-key! magit-mode-map
;; Replaced by z1, z2, z3, etc
"M-1" "M-2" "M-3" "M-4"
"1" "2" "3" "4"
"0") ; moved to g=
(evil-define-key* 'normal magit-status-mode-map [escape] nil) ; q is enough
(evil-define-key* '(normal visual) magit-mode-map
"%" #'magit-gitflow-popup
"zt" #'evil-scroll-line-to-top
"zz" #'evil-scroll-line-to-center
"zb" #'evil-scroll-line-to-bottom
"g=" #'magit-diff-default-context
"gi" #'forge-jump-to-issues
"gm" #'forge-jump-to-pullreqs)
(define-key! 'normal
(magit-status-mode-map
magit-stash-mode-map
magit-revision-mode-map
magit-diff-mode-map)
[tab] #'magit-section-toggle)
(after! git-rebase
(dolist (key '(("M-k" . "gk") ("M-j" . "gj")))
(when-let (desc (assoc (car key) evil-magit-rebase-commands-w-descriptions))
(setcar desc (cdr key))))
(evil-define-key* evil-magit-state git-rebase-mode-map
"gj" #'git-rebase-move-line-down
"gk" #'git-rebase-move-line-up))
(transient-replace-suffix 'magit-dispatch 'magit-worktree
'("%" "Gitflow" magit-gitflow-popup))
(transient-append-suffix 'magit-dispatch '(0 -1 -1)
'("*" "Worktree" magit-worktree)))