doomemacs/modules/ui/popup/+hacks.el
Henrik Lissner d8b1e469bc
Introduce autodefs to replace some settings
+ :popup -> set-popup-rule!
+ :popups -> set-popup-rules!
+ :company-backend -> set-company-backend!
+ :evil-state -> set-evil-initial-state!

I am slowly phasing out the setting system (def-setting! and set!),
starting with these.

What are autodefs? These are functions that are always defined, whether
or not their respective modules are enabled. However, when their modules
are disabled, they are replaced with macros that no-op and don't
waste time evaluating their arguments.

The old set! function will still work, for a while.
2018-06-15 03:42:01 +02:00

303 lines
12 KiB
EmacsLisp

;;; ui/popup/+hacks.el -*- lexical-binding: t; -*-
;; What follows are all the hacks needed to get various parts of Emacs and other
;; plugins to cooperate with the popup management system. Essentially, it comes
;; down to:
;;
;; 1. Making plugins that control their own window environment less greedy (e.g.
;; org agenda, which tries to reconfigure the entire frame (by deleting all
;; other windows) just to pop up one tiny window).
;; 2. Forcing plugins to use `display-buffer' and `pop-to-buffer' instead of
;; `switch-to-buffer' (which is unaffected by `display-buffer-alist', which
;; this module heavily relies on).
;; 3. Closing popups (temporarily) before functions that are highly destructive
;; to the illusion of popup control get run (with the use of the
;; `save-popups!' macro).
;;
;; Keep in mind, all this black magic may break in future updates, and will need
;; to be watched carefully for corner cases. Also, once this file is loaded, its
;; changes are irreversible without restarting Emacs! I don't like it either,
;; but I will address this over time.
;;
;; Hacks should be kept in alphabetical order, named after the feature they
;; modify, and should follow a ;; `package-name' header line.
;;
;; Core functions
;;
;; Don't try to resize popup windows
(advice-add #'balance-windows :around #'+popup*save)
;;
;; External functions
;;
;; `company'
(after! company
(defun +popup*dont-select-me (orig-fn &rest args)
(let ((+popup--inhibit-select t))
(apply orig-fn args)))
(advice-add #'company-show-doc-buffer :around #'+popup*dont-select-me))
;; `eshell'
(after! eshell
(setq eshell-destroy-buffer-when-process-dies t)
;; When eshell runs a visual command (see `eshell-visual-commands'), it spawns
;; a term buffer to run it in, but where it spawns it is the problem...
(defun +popup*eshell-undedicate-popup (orig-fn &rest args)
"Force spawned term buffer to share with the eshell popup (if necessary)."
(when (+popup-window-p)
(set-window-dedicated-p nil nil)
(add-transient-hook! #'eshell-query-kill-processes :after
(set-window-dedicated-p nil t)))
(apply orig-fn args))
(advice-add #'eshell-exec-visual :around #'+popup*eshell-undedicate-popup))
;; `evil'
(after! evil
(defun +popup*evil-command-window (hist cmd-key execute-fn)
"The evil command window has a mind of its own (uses `switch-to-buffer'). We
monkey patch it to use pop-to-buffer, and to remember the previous window."
(when (eq major-mode 'evil-command-window-mode)
(user-error "Cannot recursively open command line window"))
(dolist (win (window-list))
(when (equal (buffer-name (window-buffer win))
"*Command Line*")
(kill-buffer (window-buffer win))
(delete-window win)))
(setq evil-command-window-current-buffer (current-buffer))
(ignore-errors (kill-buffer "*Command Line*"))
(with-current-buffer (pop-to-buffer "*Command Line*")
(setq-local evil-command-window-execute-fn execute-fn)
(setq-local evil-command-window-cmd-key cmd-key)
(evil-command-window-mode)
(evil-command-window-insert-commands hist)))
(defun +popup*evil-command-window-execute ()
"Execute the command under the cursor in the appropriate buffer, rather than
the command buffer."
(interactive)
(let ((result (buffer-substring (line-beginning-position)
(line-end-position)))
(execute-fn evil-command-window-execute-fn)
(execute-window (get-buffer-window evil-command-window-current-buffer))
(popup (selected-window)))
(if execute-window
(select-window execute-window)
(user-error "Originating buffer is no longer active"))
;; (kill-buffer "*Command Line*")
(delete-window popup)
(funcall execute-fn result)
(setq evil-command-window-current-buffer nil)))
;; Make evil-mode cooperate with popups
(advice-add #'evil-command-window :override #'+popup*evil-command-window)
(advice-add #'evil-command-window-execute :override #'+popup*evil-command-window-execute)
;; Don't mess with popups
(advice-add #'+evil--window-swap :around #'+popup*save)
(advice-add #'evil-window-move-very-bottom :around #'+popup*save)
(advice-add #'evil-window-move-very-top :around #'+popup*save)
(advice-add #'evil-window-move-far-left :around #'+popup*save)
(advice-add #'evil-window-move-far-right :around #'+popup*save))
;; `help-mode'
(after! help-mode
(defun doom--switch-from-popup (location)
(let (origin)
(save-popups!
(switch-to-buffer (car location) nil t)
(if (not (cdr location))
(message "Unable to find location in file")
(goto-char (cdr location))
(recenter)
(setq origin (selected-window))))
(select-window origin)))
;; Help buffers use `pop-to-window' to decide where to open followed links,
;; which can be unpredictable. It should *only* replace the original buffer we
;; opened the popup from. To fix this these three button types need to be
;; redefined to set aside the popup before following a link.
(define-button-type 'help-function-def
:supertype 'help-xref
'help-function
(lambda (fun file)
(require 'find-func)
(when (eq file 'C-source)
(setq file (help-C-file-name (indirect-function fun) 'fun)))
(doom--switch-from-popup (find-function-search-for-symbol fun nil file))))
(define-button-type 'help-variable-def
:supertype 'help-xref
'help-function
(lambda (var &optional file)
(when (eq file 'C-source)
(setq file (help-C-file-name var 'var)))
(doom--switch-from-popup (find-variable-noselect var file))))
(define-button-type 'help-face-def
:supertype 'help-xref
'help-function
(lambda (fun file)
(require 'find-func)
(doom--switch-from-popup (find-function-search-for-symbol fun 'defface file)))))
;; `helpful'
(after! helpful
(defun +popup*helpful--navigate (button)
(let ((path (substring-no-properties (button-get button 'path)))
origin)
(save-popups!
(find-file path)
;; We use `get-text-property' to work around an Emacs 25 bug:
;; http://git.savannah.gnu.org/cgit/emacs.git/commit/?id=f7c4bad17d83297ee9a1b57552b1944020f23aea
(-when-let (pos (get-text-property button 'position
(marker-buffer button)))
(goto-char pos))
(setq origin (selected-window))
(recenter))
(select-window origin)))
(advice-add #'helpful--navigate :override #'+popup*helpful--navigate))
;; `helm-ag'
(after! helm-ag
(defun +helm*pop-to-buffer (orig-fn &rest args)
(pop-to-buffer
(save-window-excursion (apply orig-fn args)
(current-buffer))))
(advice-add #'helm-ag--edit :around #'+helm*pop-to-buffer))
;; `Info'
(defun +popup*switch-to-info-window (&rest _)
(when-let* ((win (get-buffer-window "*info*")))
(when (+popup-window-p win)
(select-window win))))
(advice-add #'info-lookup-symbol :after #'+popup*switch-to-info-window)
;; `multi-term'
(setq multi-term-buffer-name "doom terminal")
;; `neotree'
(after! neotree
(advice-add #'neo-util--set-window-width :override #'ignore)
(advice-remove #'balance-windows #'ad-Advice-balance-windows))
;; `org'
(after! org
;; Org has a scorched-earth window management system I'm not fond of. i.e. it
;; kills all windows and monopolizes the frame. No thanks. We can do better
;; ourselves.
(defun +popup*suppress-delete-other-windows (orig-fn &rest args)
(if +popup-mode
(cl-letf (((symbol-function 'delete-other-windows)
(symbol-function 'ignore)))
(apply orig-fn args))
(apply orig-fn args)))
(advice-add #'org-add-log-note :around #'+popup*suppress-delete-other-windows)
(advice-add #'org-capture-place-template :around #'+popup*suppress-delete-other-windows)
(advice-add #'org-export--dispatch-ui :around #'+popup*suppress-delete-other-windows)
(defun +popup*org-src-pop-to-buffer (orig-fn buffer context)
"Hand off the src-block window to the popup system by using `display-buffer'
instead of switch-to-buffer-*."
(if +popup-mode
(if (eq org-src-window-setup 'switch-invisibly) ; for internal calls
(set-buffer buffer)
(display-buffer buffer))
(funcall orig-fn buffer context)))
(advice-add #'org-src-switch-to-buffer :around #'+popup*org-src-pop-to-buffer)
(setq org-src-window-setup 'other-window)
;; Ensure todo, agenda, and other minor popups are delegated to the popup system.
(defun +popup*org-pop-to-buffer (orig-fn buf &optional norecord)
"Use `pop-to-buffer' instead of `switch-to-buffer' to open buffer.'"
(if +popup-mode
(pop-to-buffer
(cond ((stringp buf) (get-buffer-create buf))
((bufferp buf) buf)
(t (error "Invalid buffer %s" buf))))
(funcall orig-fn buf norecord)))
(advice-add #'org-switch-to-buffer-other-window :around #'+popup*org-pop-to-buffer)
;; `org-agenda'
(setq org-agenda-window-setup 'other-window
org-agenda-restore-windows-after-quit nil)
;; Don't monopolize frame!
(advice-add #'org-agenda :around #'+popup*suppress-delete-other-windows))
;; `persp-mode'
(progn
(defun +popup*persp-mode-restore-popups (&rest _)
"Restore popup windows when loading a perspective from file."
(dolist (window (window-list))
(when (+popup-parameter 'popup window)
(+popup--init window nil))))
(advice-add #'persp-load-state-from-file :after #'+popup*persp-mode-restore-popups))
;; `pdf-tools'
(after! pdf-tools
(setq tablist-context-window-display-action
'((+popup-display-buffer)
(side . left)
(slot . 2)
(window-height . 0.3)
(inhibit-same-window . t))
pdf-annot-list-display-buffer-action
'((+popup-display-buffer)
(side . left)
(slot . 3)
(inhibit-same-window . t)))
(add-hook 'pdf-annot-list-mode-hook #'hide-mode-line-mode)
(set-popup-rule! "\\(^\\*Contents\\|'s annots\\*$\\)" :ignore t))
;; `wgrep'
(progn
;; close the popup after you're done with a wgrep buffer
(advice-add #'wgrep-abort-changes :after #'+popup*close)
(advice-add #'wgrep-finish-edit :after #'+popup*close))
;; `which-key'
(setq which-key-popup-type 'custom
which-key-custom-popup-max-dimensions-function (lambda (_) (which-key--side-window-max-dimensions))
which-key-custom-hide-popup-function #'which-key--hide-buffer-side-window
which-key-custom-show-popup-function
(lambda (act-popup-dim)
(cl-letf (((symbol-function 'display-buffer-in-side-window)
(lambda (buffer alist)
(+popup-display-buffer
buffer (append '((vslot . -9999)) alist)))))
(which-key--show-buffer-side-window act-popup-dim))))
;; `windmove'
(progn
;; Users should be about to hop into popups easily, but Elisp shouldn't.
(defun doom*ignore-window-parameters (orig-fn &rest args)
"Allow *interactive* window moving commands to traverse popups."
(cl-letf (((symbol-function #'windmove-find-other-window)
(lambda (dir &optional arg window)
(window-in-direction
(pcase dir (`up 'above) (`down 'below) (_ dir))
window (bound-and-true-p +popup-mode) arg windmove-wrap-around t))))
(apply orig-fn args)))
(advice-add #'windmove-up :around #'doom*ignore-window-parameters)
(advice-add #'windmove-down :around #'doom*ignore-window-parameters)
(advice-add #'windmove-left :around #'doom*ignore-window-parameters)
(advice-add #'windmove-right :around #'doom*ignore-window-parameters))