💥 Replace core-popup with new feature/popup module

This is a breaking change! Update your :popup settings. Old ones will
throw errors!

Doom's new popup management system casts off its shackles (hur hur) and
replaces them with the monster that is `display-buffer-alist`, and
window parameters.

However, this is highly experimental! Expect edge cases.  Particularly
with org-mode and magit (or anything that does its own window
management).

Relevant to #261, #263, #325
This commit is contained in:
Henrik Lissner 2018-01-06 01:23:22 -05:00
parent 065091bdca
commit 91357a3e5d
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
33 changed files with 631 additions and 1038 deletions

View file

@ -1,418 +0,0 @@
;;; core/autoload/popups.el -*- lexical-binding: t; -*-
;;;###autoload
(defun doom-popup-p (&optional target)
"Return t if TARGET (a window or buffer) is a popup. Uses current window if
omitted."
(when-let* ((target (or target (selected-window))))
(cond ((bufferp target)
(and (buffer-live-p target)
(buffer-local-value 'doom-popup-mode target)))
((windowp target)
(and (window-live-p target)
(window-parameter target 'popup))))))
;;;###autoload
(defun doom-popup-buffer (buffer &optional plist extend-p)
"Display BUFFER in a shackle popup with PLIST rules. See `shackle-rules' for
possible rules. If EXTEND-P is non-nil, don't overwrite the original rules for
this popup, just the specified properties. Returns the new popup window."
(declare (indent defun))
(unless (bufferp buffer)
(error "%s is not a valid buffer" buffer))
(shackle-display-buffer
buffer
nil (or (if extend-p
(append plist (shackle-match buffer))
plist)
(shackle-match buffer))))
;;;###autoload
(defun doom-popup-switch-to-buffer (buffer)
"Switch the current (or closest) pop-up window to BUFFER."
(unless (doom-popup-p)
(if-let* ((popups (doom-popup-windows)))
(select-window (car popups))
(error "No popups to switch to")))
(set-window-dedicated-p nil nil)
(switch-to-buffer buffer nil t)
(prog1 (selected-window)
(set-window-dedicated-p nil t)))
;;;###autoload
(defun doom-popup-fit-to-buffer (&optional window max-size)
"Fit WINDOW to the size of its content."
(unless (string-empty-p (buffer-string))
(let* ((window-size (doom-popup-size window))
(max-size (or max-size (doom-popup-property :size window)))
(size (+ 2 (if (floatp max-size) (truncate (* max-size window-size)) window-size))))
(fit-window-to-buffer window size nil size))))
;;;###autoload
(defun doom-popup-move (direction)
"Move a popup window to another side of the frame, in DIRECTION, which can be
one of the following: 'left 'right 'above 'below"
(when (doom-popup-p)
(let ((buffer (current-buffer))
(doom-popup-inhibit-autokill t))
(doom/popup-close)
(doom-popup-buffer buffer `(:align ,direction) 'extend))))
;;;###autoload
(defun doom-popup-file (file &optional plist extend-p)
"Display FILE in a shackle popup, with PLIST rules. See `shackle-rules' for
possible rules."
(unless (file-exists-p file)
(user-error "Can't display file in popup, it doesn't exist: %s" file))
(doom-popup-buffer (find-file-noselect file t) plist extend-p))
;;;###autoload
(defun doom-popup-windows (&optional filter-static-p)
"Get a list of open pop up windows."
(cl-loop for window in doom-popup-windows
if (and (doom-popup-p window)
(not (and filter-static-p
(doom-popup-property :static window))))
collect window))
;;;###autoload
(defun doom-popup-properties (window-or-buffer)
"Returns a window's popup property list, if possible. The buffer-local
`doom-popup-rules' always takes priority, but this will fall back to the popup
window parameter."
(cond ((windowp window-or-buffer)
(or (window-parameter window-or-buffer 'popup)
(doom-popup-properties (window-buffer window-or-buffer))))
((bufferp window-or-buffer)
(buffer-local-value 'doom-popup-rules window-or-buffer))))
;;;###autoload
(defun doom-popup-property (prop &optional window)
"Returns a `doom-popup-rules' PROPerty from WINDOW."
(or (plist-get (doom-popup-properties (or window (selected-window)))
prop)
(pcase prop
(:size shackle-default-size)
(:align shackle-default-alignment))))
;;;###autoload
(defun doom-popup-side (&optional window)
"Return what side a popup WINDOW came from ('left 'right 'above or 'below)."
(let ((align (doom-popup-property :align window)))
(when (eq align t)
(setq align shackle-default-alignment))
(when (functionp align)
(setq align (funcall align)))
align))
;;;###autoload
(defun doom-popup-size (&optional window)
"Return the size of a popup WINDOW."
(pcase (doom-popup-side window)
((or 'left 'right) (window-width window))
((or 'above 'below) (window-height window))))
(defun doom--popup-data (window)
(when-let* ((buffer (window-buffer window)))
`(,(buffer-name buffer)
:file ,(buffer-file-name buffer)
:rules ,(window-parameter window 'popup)
:size ,(doom-popup-size window))))
;;;###autoload
(defmacro with-popup-rules! (rules &rest body)
"TODO"
(declare (indent defun))
`(let (shackle-rules)
,@(cl-loop for rule in rules
collect `(set! :popup ,@rule))
,@body))
;;;###autoload
(defmacro save-popups! (&rest body)
"Sets aside all popups before executing the original function, usually to
prevent the popup(s) from messing up the UI (or vice versa)."
`(let ((in-popup-p (doom-popup-p))
(popups (doom-popup-windows))
(doom-popup-remember-history t)
(doom-popup-inhibit-autokill t))
(when popups
(mapc #'doom/popup-close popups))
(unwind-protect
(progn ,@body)
(when popups
(let ((origin (selected-window)))
(doom/popup-restore)
(unless in-popup-p
(select-window origin)))))))
;; --- Commands ---------------------------
;;;###autoload
(defun doom/popup-restore ()
"Restore the last open popups. If the buffers have been killed, and
represented real files, they will be restored. Dead special buffers or buffers
with non-nil :autokill properties will not be.
Returns t if popups were restored, nil otherwise."
(interactive)
(unless doom-popup-history
(error "No popups to restore"))
(let (any-p)
(dolist (spec doom-popup-history)
(let ((buffer (get-buffer (car spec)))
(file (plist-get (cdr spec) :file))
(rules (plist-get (cdr spec) :rules))
(size (plist-get (cdr spec) :size)))
(when (and (not buffer) file)
(setq buffer
(if-let* ((buf (get-file-buffer file)))
(clone-indirect-buffer (buffer-name buf) nil t)
(find-file-noselect file t))))
(when size
(setq rules (plist-put rules :size size)))
(when (and buffer (doom-popup-buffer buffer rules) (not any-p))
(setq any-p t))))
(when any-p
(setq doom-popup-history '()))
any-p))
;;;###autoload
(defun doom/popup-toggle ()
"Toggle popups on and off. If used outside of popups (and popups are
available), it will select the nearest popup window."
(interactive)
(when (doom-popup-p)
(if doom-popup-other-window
(select-window doom-popup-other-window)
(other-window 1)))
(if (doom-popup-windows t)
(let ((doom-popup-inhibit-autokill t))
(doom/popup-close-all t))
(doom/popup-restore)))
;;;###autoload
(defun doom/popup-close (&optional window)
"Find and close WINDOW if it's a popup. If WINDOW is omitted, defaults to
`selected-window'. The contained buffer is buried, unless it has an :autokill
property."
(interactive)
(when (doom-popup-p window)
(delete-window window)))
;;;###autoload
(defun doom/popup-close-all (&optional force-p)
"Closes most open popups.
Does not close popups that are :static or don't have an :autoclose property (see
`shackle-rules').
If FORCE-P is non-nil (or this function is called interactively), ignore popups'
:autoclose property. This command will never close :static popups."
(interactive
(list (called-interactively-p 'interactive)))
(when-let* ((popups (doom-popup-windows t)))
(let (success doom-popup-remember-history)
(setq doom-popup-history (delq nil (mapcar #'doom--popup-data popups)))
(dolist (window popups success)
(when (or force-p (doom-popup-property :autoclose window))
(kill-buffer (window-buffer window))
(setq success t))))))
;;;###autoload
(defun doom/popup-close-maybe ()
"Close the current popup *if* its window doesn't have a noesc parameter."
(interactive)
(if (doom-popup-property :noesc)
(call-interactively
(if (featurep 'evil)
#'evil-force-normal-state
#'keyboard-quit))
(kill-this-buffer)))
;;;###autoload
(defun doom/popup-kill-all ()
"Like `doom/popup-close-all', but kill *all* popups, including :static ones,
without leaving any trace behind (muahaha)."
(interactive)
(when-let* ((popups (doom-popup-windows)))
(let (doom-popup-remember-history)
(setq doom-popup-history nil)
(dolist (win popups)
(kill-buffer (window-buffer win))))))
;;;###autoload
(defun doom/popup-this-buffer ()
"Display currently selected buffer in a popup window."
(interactive)
(doom-popup-buffer (current-buffer) '(:align t :autokill t)))
;;;###autoload
(defun doom/popup-toggle-messages ()
"Toggle *Messages* buffer."
(interactive)
(if-let* ((win (get-buffer-window "*Messages*")))
(doom/popup-close win)
(doom-popup-buffer (get-buffer "*Messages*"))))
;;;###autoload
(defun doom/other-popup (count)
"Cycle through popup windows. Like `other-window', but for popups."
(interactive "p")
(if-let* ((popups (if (doom-popup-p)
(cdr (memq (selected-window) doom-popup-windows))
(setq doom-popup-other-window (selected-window))
doom-popup-windows)))
(ignore-errors (select-window (nth (mod (1- count) (length popups)) popups)))
(unless (eq (selected-window) doom-popup-other-window)
(when doom-popup-other-window
(select-window doom-popup-other-window t)
(cl-decf count))
(when (/= count 0)
(other-window count)))))
;;;###autoload
(defalias 'other-popup #'doom/other-popup)
;;;###autoload
(defun doom/popup-raise (&optional window)
"Turn a popup window into a normal window."
(interactive)
(let ((window (or window (selected-window))))
(unless (doom-popup-p window)
(user-error "Not a valid popup to raise"))
(with-selected-window window
(doom-popup-mode -1))))
;;;###autoload
(defun doom/popup-move-top () "See `doom-popup-move'." (interactive) (doom-popup-move 'above))
;;;###autoload
(defun doom/popup-move-bottom () "See `doom-popup-move'." (interactive) (doom-popup-move 'below))
;;;###autoload
(defun doom/popup-move-left () "See `doom-popup-move'." (interactive) (doom-popup-move 'left))
;;;###autoload
(defun doom/popup-move-right () "See `doom-popup-move'." (interactive) (doom-popup-move 'right))
;; --- doom-popup-mode --------------------
;;;###autoload
(define-minor-mode doom-popup-mode
"Minor mode for popup windows."
:init-value nil
:keymap doom-popup-mode-map
(let ((window (selected-window)))
;; If `doom-popup-rules' isn't set for some reason, try to set it
(setq-local doom-popup-rules (doom-popup-properties window))
;; Ensure that buffer-opening functions/commands (like
;; `switch-to-buffer-other-window' won't use this window).
(set-window-parameter window 'no-other-window doom-popup-mode)
;; Makes popup window resist interactively changing its buffer.
(unless (plist-get doom-popup-rules :same)
(set-window-dedicated-p window doom-popup-mode))
(cond (doom-popup-mode
(when doom-popup-no-fringes
(set-window-fringes window 0 0 fringes-outside-margins))
;; Save metadata into window parameters so it can be saved by window
;; config persisting plugins like workgroups or persp-mode.
(set-window-parameter window 'popup (or doom-popup-rules t))
(when doom-popup-rules
(cl-loop for param in doom-popup-window-parameters
when (plist-get doom-popup-rules param)
do (set-window-parameter window param it))))
(t
(when doom-popup-no-fringes
(set-window-fringes window
doom-fringe-size doom-fringe-size
fringes-outside-margins))
;; Ensure window parameters are cleaned up
(set-window-parameter window 'popup nil)
(dolist (param doom-popup-window-parameters)
(set-window-parameter window param nil))))))
(put 'doom-popup-mode 'permanent-local t)
;;;###autoload
(defun doom|hide-modeline-in-popup ()
"Don't show modeline in popup windows without a :modeline rule. If one exists
and it's a symbol, use `doom-modeline' to grab the format. If non-nil, show the
mode-line as normal. If nil (or omitted, by default), then hide the modeline
entirely."
(if doom-popup-mode
(let ((modeline (plist-get doom-popup-rules :modeline)))
(cond ((or (eq modeline 'nil)
(not modeline))
(doom-hide-modeline-mode +1))
((and (symbolp modeline)
(not (eq modeline 't)))
(setq-local doom--modeline-format (doom-modeline modeline))
(when doom--modeline-format
(doom-hide-modeline-mode +1)))))
(when doom-hide-modeline-mode
(doom-hide-modeline-mode -1))))
;; --- Advice functions -------------------
;;;###autoload
(defun doom*shackle-always-align (plist)
"Ensure popups are always aligned and selected by default. Eliminates the need
for :align t on every rule."
(when plist
(unless (or (plist-member plist :align)
(plist-member plist :same)
(plist-member plist :frame))
(plist-put plist :align t))
(unless (or (plist-member plist :select)
(plist-member plist :noselect))
(plist-put plist :select t)))
plist)
;;;###autoload
(defun doom*popup-init (orig-fn &rest args)
"Initializes a window as a popup window by enabling `doom-popup-mode' in it
and setting `doom-popup-rules' within it. Returns the window."
(unless (doom-popup-p)
(setq doom-popup-other-window (selected-window)))
(let* ((target (car args))
(plist (or (nth 2 args)
(cond ((windowp target)
(and (window-live-p target)
(shackle-match (window-buffer target))))
((bufferp target)
(and (buffer-live-p target)
(shackle-match target))))))
(buffer (get-buffer target))
(window-min-height (if (plist-get plist :modeline) 4 2))
window)
(when (and (doom-real-buffer-p buffer)
(get-buffer-window-list buffer nil t))
(setq plist (append (list :autokill t) plist))
(setcar args (clone-indirect-buffer (buffer-name target) nil t)))
(unless (setq window (apply orig-fn args))
(error "No popup window was found for %s: %s" target plist))
(cl-pushnew window doom-popup-windows :test #'eq)
(with-selected-window window
(unless (eq plist t)
(setq-local doom-popup-rules plist))
(doom-popup-mode +1)
(when (plist-get plist :autofit)
(doom-popup-fit-to-buffer window)))
window))
;;;###autoload
(defun doom*popups-save (orig-fn &rest args)
"Sets aside all popups before executing the original function, usually to
prevent the popup(s) from messing up the UI (or vice versa)."
(save-popups! (apply orig-fn args)))
;;;###autoload
(defun doom*delete-popup-window (&optional window)
"Do popup bookkeeping before the popup window is deleted."
(unless window
(setq window (selected-window)))
(when (doom-popup-p window)
(setq doom-popup-windows (delq window doom-popup-windows))
(when doom-popup-remember-history
(setq doom-popup-history (list (doom--popup-data window))))))

View file

@ -37,11 +37,11 @@
(current-buffer))))
;;;###autoload
(defun doom/open-scratch-buffer ()
"Opens a temporary scratch buffer in a popup window. It is discarded once it
is closed. If a region is active, copy it to the scratch buffer."
(interactive)
(doom-popup-buffer (doom--create-scratch-buffer)))
(defun doom/open-scratch-buffer (&optional in-project-p)
"Opens a temporary scratch buffer. It is discarded once it is closed. If a
region is active, copy it to the scratch buffer."
(interactive "P")
(pop-to-buffer (doom--create-scratch-buffer in-project-p)))
;;;###autoload
(defun doom/open-project-scratch-buffer ()
@ -49,5 +49,5 @@ is closed. If a region is active, copy it to the scratch buffer."
popup window. Scratch buffers are stored in `doom-scratch-files-dir'. If a
region is active, copy it to the scratch buffer."
(interactive)
(doom-popup-buffer (doom--create-scratch-buffer t)))
(doom/open-scratch-buffer 'in-project))

View file

@ -1,544 +0,0 @@
;;; core-popups.el -*- lexical-binding: t; -*-
;; I want a "real"-buffer-first policy in my Emacsian utpoia; popup buffers
;; ought to be second-class citizens to "real" buffers. No need for a wall or
;; controversial immigration policies -- all we need is `shackle' (and it will
;; actually work).
;;
;; The gist is: popups should be displayed on one side of the frame, away from
;; 'real' buffers. They should be easy to dispose of when we don't want to see
;; them and easily brought back in case we change our minds. Also, popups should
;; typically have no mode-line.
;;
;; Be warned, this requires a lot of hackery voodoo that could break with an
;; emacs update or an update to any of the packages it tries to tame (like helm
;; or org-mode).
(defvar doom-popup-history nil
"A list of popups that were last closed. Used by `doom/popup-restore' and
`doom*popups-save'.")
(defvar doom-popup-other-window nil
"The last window selected before a popup was opened.")
(defvar doom-popup-no-fringes t
"If non-nil, disable fringes in popup windows.")
(defvar doom-popup-windows ()
"A list of open popup windows.")
(defvar-local doom-popup-rules nil
"The shackle rule that caused this buffer to be recognized as a popup. Don't
edit this directly.")
(put 'doom-popup-rules 'permanent-local t)
(defvar doom-popup-window-parameters
'(:noesc :modeline :autokill :autoclose :autofit :static)
"A list of window parameters that are set (and cleared) when `doom-popup-mode
is enabled/disabled.'")
(defvar doom-popup-remember-history t
"Don't modify this directly. If non-nil, DOOM will remember the last popup(s)
that was/were open in `doom-popup-history'.")
(defvar doom-popup-inhibit-autokill nil
"Don't modify this directly. When it is non-nil, no buffers will be killed
when their associated popup windows are closed, despite their :autokill
property.")
(defvar doom-popup-mode-map (make-sparse-keymap)
"Active keymap in popup windows.")
(def-setting! :popup (&rest rules)
"Prepend a new popup rule to `shackle-rules' (see for format details).
Several custom properties have been added that are not part of shackle, but are
recognized by DOOM's popup system. They are:
:noesc If non-nil, the popup won't be closed if you press ESC from *inside*
its window. Used by `doom/popup-close-maybe'.
:modeline By default, mode-lines are hidden in popups unless this is non-nil.
If it is a symbol, it'll use `doom-modeline' to fetch a modeline
config (in `doom-popup-mode').
:autokill If non-nil, the popup's buffer will be killed when the popup is
closed. Used by `doom*delete-popup-window'. NOTE
`doom/popup-restore' can't restore non-file popups that have an
:autokill property.
:autoclose If non-nil, close popup if ESC is pressed from outside the popup
window.
:autofit If non-nil, resize the popup to fit its content. Uses the value of
the :size property as the maximum height/width. This will not work
if the popup has no content when displayed.
:static If non-nil, don't treat this window like a popup. This makes it
impervious to being automatically closed or tracked in popup
history. Excellent for permanent sidebars."
(if (cl-every #'listp (mapcar #'doom-unquote rules))
`(setq shackle-rules (nconc (list ,@rules) shackle-rules))
`(push (list ,@rules) shackle-rules)))
;;
;;
;;
;; (defvar doom-popup-parameters
;; '(:esc :modeline :transient :fit :align :size)
;; "TODO")
;; (defvar doom-popup-whitelist
;; '(("^ ?\\*" :size 15 :noselect t :autokill t :autoclose t))
;; "TODO")
(defvar doom-popup-blacklist
'("^\\*magit")
"TODO")
;;
;; Bootstrap
;;
(def-package! shackle
:init
(setq shackle-default-alignment 'below
shackle-default-size 8
shackle-rules
'(("^\\*eww" :regexp t :size 0.5 :select t :autokill t :noesc t)
("^\\*ftp " :noselect t :autokill t :noesc t)
;; doom
("^\\*doom:scratch" :regexp t :size 15 :noesc t :select t :modeline t :autokill t :static t)
("^\\*doom:" :regexp t :size 0.35 :noesc t :select t)
("^ ?\\*doom " :regexp t :noselect t :autokill t :autoclose t :autofit t)
;; built-in (emacs)
("*compilation*" :size 0.25 :noselect t :autokill t :autoclose t)
("*ert*" :same t :modeline t)
("*info*" :size 0.5 :select t :autokill t)
("*Backtrace*" :size 20 :noselect t)
("*Warnings*" :size 12 :noselect t :autofit t)
("*Messages*" :size 12 :noselect t)
("*Help*" :size 0.3 :autokill t)
("^\\*.*Shell Command.*\\*$" :regexp t :size 20 :noselect t :autokill t)
(apropos-mode :size 0.3 :autokill t :autoclose t)
(Buffer-menu-mode :size 20 :autokill t)
(comint-mode :noesc t)
(grep-mode :size 25 :noselect t :autokill t)
(profiler-report-mode :size 0.3 :regexp t :autokill t :modeline minimal)
(tabulated-list-mode :noesc t)
("^ ?\\*" :regexp t :size 15 :noselect t :autokill t :autoclose t)))
:config
(add-hook 'doom-unreal-buffer-p #'doom-popup-p)
;; NOTE This is a temporary fix while I rewrite core-popups
(defun doom-display-buffer-condition (buffer _action)
(and (cl-loop for re in doom-popup-blacklist
when (string-match-p re buffer)
return nil
finally return t)
(shackle-match buffer)))
(defun doom-display-buffer-action (buffer alist)
(shackle-display-buffer buffer alist (shackle-match buffer)))
(defun doom|autokill-popups ()
"TODO"
(or (not (doom-popup-p))
(if (and (not doom-popup-inhibit-autokill)
(plist-get doom-popup-rules :autokill))
(progn
(when-let* ((process (get-buffer-process (current-buffer))))
(set-process-query-on-exit-flag process nil))
t)
(doom-popup-mode -1)
(delete-window)
nil)))
(defun doom|init-popups ()
"TODO"
(setq display-buffer-alist
(cons '(doom-display-buffer-condition doom-display-buffer-action)
display-buffer-alist))
;; bootstrap popup system
(advice-add #'shackle-display-buffer :around #'doom*popup-init)
(advice-add #'balance-windows :around #'doom*popups-save)
(advice-add #'delete-window :before #'doom*delete-popup-window)
;; ensure every rule without an :align, :same or :frame property has an
;; implicit :align (see `shackle-default-alignment')
(advice-add #'shackle--match :filter-return #'doom*shackle-always-align)
;; autokill popups with a non-nil :autokill property
(add-hook 'kill-buffer-query-functions #'doom|autokill-popups)
;; no modeline in popups
(add-hook 'doom-popup-mode-hook #'doom|hide-modeline-in-popup)
;; Tell `window-state-get' and `current-window-configuration' to recognize
;; these custom parameters. Helpful for `persp-mode' and persisting window
;; configs that have popups in them.
(dolist (param `(popup ,@doom-popup-window-parameters))
(push (cons param 'writable) window-persistent-parameters)))
(add-hook 'doom-post-init-hook #'doom|init-popups)
(let ((map doom-popup-mode-map))
(define-key map [escape] #'doom/popup-close-maybe)
(define-key map (kbd "ESC") #'doom/popup-close-maybe)
(define-key map [remap quit-window] #'doom/popup-close-maybe)
(define-key map [remap kill-buffer] #'doom/popup-close)
(define-key map [remap split-window-right] #'ignore)
(define-key map [remap split-window-below] #'ignore)
(define-key map [remap split-window-horizontally] #'ignore)
(define-key map [remap split-window-vertically] #'ignore)
(define-key map [remap mouse-split-window-horizontally] #'ignore)
(define-key map [remap mouse-split-window-vertically] #'ignore)))
;;
;; Hacks
;;
(progn ; hacks for built-in functions
(defun doom*suppress-pop-to-buffer-same-window (orig-fn &rest args)
(cl-letf (((symbol-function 'pop-to-buffer-same-window)
(symbol-function 'pop-to-buffer)))
(apply orig-fn args)))
(advice-add #'info :around #'doom*suppress-pop-to-buffer-same-window)
(advice-add #'eww :around #'doom*suppress-pop-to-buffer-same-window)
(advice-add #'eww-browse-url :around #'doom*suppress-pop-to-buffer-same-window)
(defun doom*popup-buffer-menu (&optional arg)
"Open `buffer-menu' in a popup window."
(interactive "P")
(with-selected-window (doom-popup-buffer (list-buffers-noselect arg))
(setq mode-line-format "Commands: d, s, x, u; f, o, 1, 2, m, v; ~, %; q to quit; ? for help.")))
(advice-add #'buffer-menu :override #'doom*popup-buffer-menu))
(after! comint
(defun doom|popup-close-comint-buffer ()
(when (and (doom-popup-p)
(derived-mode-p 'comint-mode)
(not (process-live-p (get-buffer-process (current-buffer)))))
(delete-window)))
(add-hook '+evil-esc-hook #'doom|popup-close-comint-buffer t))
(after! eshell
;; By tying buffer life to its process, we ensure that we land back in the
;; eshell buffer after term dies. May cause problems with short-lived
;; processes.
;; FIXME replace with a 'kill buffer' keybinding.
(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 doom*eshell-undedicate-popup (orig-fn &rest args)
"Force spawned term buffer to share with the eshell popup (if necessary)."
(when (doom-popup-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 #'doom*eshell-undedicate-popup))
(after! evil
(let ((map doom-popup-mode-map))
(define-key map [remap evil-window-delete] #'doom/popup-close-maybe)
(define-key map [remap evil-save-modified-and-close] #'doom/popup-close-maybe)
(define-key map [remap evil-window-move-very-bottom] #'doom/popup-move-bottom)
(define-key map [remap evil-window-move-very-top] #'doom/popup-move-top)
(define-key map [remap evil-window-move-far-left] #'doom/popup-move-left)
(define-key map [remap evil-window-move-far-right] #'doom/popup-move-right)
(define-key map [remap evil-window-split] #'ignore)
(define-key map [remap evil-window-vsplit] #'ignore))
(defun doom|popup-close-maybe ()
"If current window is a popup, close it. If minibuffer is open, close it. If
not in a popup, close all popups with an :autoclose property."
(if (doom-popup-p)
(unless (doom-popup-property :noesc)
(delete-window))
(doom/popup-close-all)))
(add-hook '+evil-esc-hook #'doom|popup-close-maybe t)
;; Make evil-mode cooperate with popups
(advice-add #'evil-command-window :override #'doom*popup-evil-command-window)
(advice-add #'evil-command-window-execute :override #'doom*popup-evil-command-window-execute)
(defun doom*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 doom*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)
(popup (selected-window)))
(select-window doom-popup-other-window)
(unless (equal evil-command-window-current-buffer (current-buffer))
(user-error "Originating buffer is no longer active"))
;; (kill-buffer "*Command Line*")
(doom/popup-close popup)
(funcall execute-fn result)
(setq evil-command-window-current-buffer nil)))
;; Don't mess with popups
(advice-add #'doom-evil-window-move :around #'doom*popups-save)
(advice-add #'evil-window-move-very-bottom :around #'doom*popups-save)
(advice-add #'evil-window-move-very-top :around #'doom*popups-save)
(advice-add #'evil-window-move-far-left :around #'doom*popups-save)
(advice-add #'evil-window-move-far-right :around #'doom*popups-save)
;; Don't block moving to/from popup windows
(defun doom*ignore-window-parameters-in-popups (dir &optional arg window)
(window-in-direction (cond ((eq dir 'up) 'above)
((eq dir 'down) 'below)
(t dir))
window t arg windmove-wrap-around t))
(advice-add #'windmove-find-other-window :override #'doom*ignore-window-parameters-in-popups))
(after! helm
;; Helm tries to clean up after itself, but shackle has already done this,
;; causing problems. This fixes that. To reproduce, add a helm rule in
;; `shackle-rules', open two splits side-by-side, move to the buffer on the
;; right and invoke helm. It will close all but the left-most buffer.
(setq-default helm-reuse-last-window-split-state t
helm-split-window-in-side-p t)
(after! helm-swoop
(setq helm-swoop-split-window-function #'pop-to-buffer))
(after! helm-ag
;; This prevents helm-ag from switching between windows and buffers.
(defun doom*helm-ag-edit-done (orig-fn &rest args)
(cl-letf (((symbol-function 'select-window) #'ignore))
(apply orig-fn args))
(doom/popup-close))
(advice-add #'helm-ag--edit-commit :around #'doom*helm-ag-edit-done)
(advice-add #'helm-ag--edit-abort :around #'doom*helm-ag-edit-done)
(defun doom*helm-ag-edit (orig-fn &rest args)
(cl-letf (((symbol-function 'other-window) #'ignore)
((symbol-function 'switch-to-buffer) #'doom-popup-buffer))
(apply orig-fn args)
(with-current-buffer (get-buffer "*helm-ag-edit*")
(use-local-map helm-ag-edit-map))))
(advice-add #'helm-ag--edit :around #'doom*helm-ag-edit)))
(defsubst doom--switch-from-popup (location)
(doom/popup-close)
(switch-to-buffer (car location) nil t)
(if (not (cdr location))
(message "Unable to find location in file")
(goto-char (cdr location))
(recenter)))
(after! help-mode
;; Help buffers use `other-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)))))
(after! magit
(add-hook 'magit-mode-hook #'doom-hide-modeline-mode))
(after! mu4e
(defun doom*mu4e-popup-window (buf _height)
(doom-popup-buffer buf '(:size 10 :noselect t))
buf)
(advice-add #'mu4e~temp-window :override #'doom*mu4e-popup-window))
(after! multi-term
(setq multi-term-buffer-name "doom:terminal"))
(after! neotree
;; Neotree has its own window/popup management built-in, which is difficult to
;; police. For example, switching perspectives will cause neotree to forget it
;; is a neotree pane.
;;
;; By handing neotree over to shackle, which is better integrated into the
;; rest of my config (and persp-mode), this is no longer a problem.
(set! :popup " *NeoTree*" :align neo-window-position :size neo-window-width :static t)
(defun +evil-neotree-display-fn (buf _alist)
"Hand neotree off to shackle."
(let ((win (doom-popup-buffer buf)))
(setq neo-global--buffer (window-buffer win)
neo-global--window win)))
(setq neo-display-action '(+evil-neotree-display-fn))
(defun +evil|neotree-fix-popup ()
"Repair neotree state whenever its popup state is restored. This ensures
that `doom*popup-save' won't break it."
(when (equal (buffer-name) neo-buffer-name)
(setq neo-global--window (selected-window))
;; Fix neotree shrinking when closing nearby vertical splits
(when neo-window-fixed-size
(doom-resize-window neo-global--window neo-window-width t t))))
(add-hook 'doom-popup-mode-hook #'+evil|neotree-fix-popup))
(after! persp-mode
(defun doom*persp-mode-restore-popups (&rest _)
"Restore popup windows when loading a perspective from file."
(dolist (window (window-list))
(when-let* ((plist (doom-popup-properties window)))
(with-selected-window window
(unless doom-popup-mode
(setq-local doom-popup-rules plist)
(doom-popup-mode +1))))))
(advice-add #'persp-load-state-from-file :after #'doom*persp-mode-restore-popups))
(after! quickrun
;; don't auto-focus quickrun windows, shackle handles that
(setq quickrun-focus-p nil))
(after! twittering-mode
(setq twittering-pop-to-buffer-function #'pop-to-buffer))
(after! wgrep
;; close the popup after you're done with a wgrep buffer
(advice-add #'wgrep-abort-changes :after #'doom/popup-close)
(advice-add #'wgrep-finish-edit :after #'doom/popup-close))
(after! xref
(defun doom*xref-follow-and-close (orig-fn &rest args)
"Jump to the xref on the current line, select its window and close the popup
you came from."
(interactive)
(let ((popup-p (doom-popup-p))
(window (selected-window)))
(apply orig-fn args)
(when popup-p (doom/popup-close window))))
(advice-add #'xref-goto-xref :around #'doom*xref-follow-and-close))
;;
;; Major modes
;;
(after! plantuml-mode
(defun doom*plantuml-preview-in-popup-window (orig-fn &rest args)
(save-window-excursion
(apply orig-fn args))
(pop-to-buffer plantuml-preview-buffer))
(advice-add #'plantuml-preview-string
:around #'doom*plantuml-preview-in-popup-window))
;; Ensure these settings are loaded as late as possible, giving other modules a
;; chance to reconfigure org popup settings before the defaults kick in.
(defun doom|init-org-popups ()
(add-hook! org-load
(set! :popup
'("*Calendar*" :size 0.4 :noselect t)
'(" *Org todo*" :size 5 :noselect t)
'("*Org Note*" :size 10)
'("*Org Select*" :size 20 :noselect t)
'("*Org Links*" :size 5 :noselect t)
'("*Org Export Dispatcher*" :noselect t)
'(" *Agenda Commands*" :noselect t)
'("^\\*Org Agenda" :regexp t :size 20)
'("*Org Clock*" :noselect t)
'("^\\*Org Src" :regexp t :size 0.35 :noesc t)
'("*Edit Formulas*" :size 10)
'("^\\*Org-Babel" :regexp t :size 25 :noselect t)
'("^CAPTURE.*\\.org$" :regexp t :size 20))
;; 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 with shackle's help.
(defun doom*suppress-delete-other-windows (orig-fn &rest args)
(cl-letf (((symbol-function 'delete-other-windows)
(symbol-function 'ignore)))
(apply orig-fn args)))
(advice-add #'org-add-log-note :around #'doom*suppress-delete-other-windows)
(advice-add #'org-capture-place-template :around #'doom*suppress-delete-other-windows)
(advice-add #'org-export--dispatch-ui :around #'doom*suppress-delete-other-windows)
;; Hand off the src-block window to a shackle popup window.
(defun doom*org-src-pop-to-buffer (buffer _context)
"Open the src-edit in a way that shackle can detect."
(if (eq org-src-window-setup 'switch-invisibly)
(set-buffer buffer)
(pop-to-buffer buffer)))
(advice-add #'org-src-switch-to-buffer :override #'doom*org-src-pop-to-buffer)
;; Ensure todo, agenda, and other minor popups are delegated to shackle.
(defun doom*org-pop-to-buffer (&rest args)
"Use `pop-to-buffer' instead of `switch-to-buffer' to open buffer.'"
(let ((buf (car args)))
(pop-to-buffer
(cond ((stringp buf) (get-buffer-create buf))
((bufferp buf) buf)
(t (error "Invalid buffer %s" buf))))))
(advice-add #'org-switch-to-buffer-other-window :override #'doom*org-pop-to-buffer)
;; org-agenda
(setq org-agenda-window-setup 'other-window
org-agenda-restore-windows-after-quit nil)
;; Hide modeline in org-agenda
(add-hook 'org-agenda-finalize-hook #'doom-hide-modeline-mode)
(add-hook 'org-agenda-finalize-hook #'org-fit-window-to-buffer)
;; Don't monopolize frame!
(advice-add #'org-agenda :around #'doom*suppress-delete-other-windows)
;; ensure quit keybindings work propertly
(map! :map* org-agenda-mode-map
:m [escape] 'org-agenda-Quit
:m "ESC" 'org-agenda-Quit)))
(add-hook 'doom-init-hook #'doom|init-org-popups)
(provide 'core-popups)
;;; core-popups.el ends here

View file

@ -172,7 +172,6 @@ ability to invoke the debugger in debug mode."
(unless noninteractive
(load! core-ui) ; draw me like one of your French editors
(load! core-popups) ; taming sudden yet inevitable windows
(load! core-editor) ; baseline configuration for text editing
(load! core-projects) ; making Emacs project-aware
(load! core-keybinds)) ; centralized keybind system + which-key

View file

@ -30,6 +30,7 @@
(require 'core (concat user-emacs-directory "core/core"))
(doom! :feature
popup ; tame sudden yet inevitable temporary windows
;debugger ; FIXME stepping through code, to help you add bugs
eval ; run code, run (also, repls)
evil ; come to the dark side, we have cookies

View file

@ -46,7 +46,6 @@ http://regexr.com/foo.html?q=bar
https://mediatemple.net"
"TODO")
(set! :popup
'("*doom-regex*" :size 4 :select t :noesc t)
'("*doom-regex-groups*" :align left :size 30 :noselect t :noesc t))
(set! :popup "^\\*doom-regex\\*$" '((size . 4)) '((escape-quit)))
(set! :popup "^\\*doom-regex-groups" '((side . left)) '((select) (escape-quit)))

View file

@ -16,8 +16,6 @@
twittering-initial-timeline-spec-string
'(":home" ":mentions" ":direct_messages"))
(set! :popup "*twittering-edit*" :size 12 :select t)
(add-hook! twittering-mode
(setq header-line-format (or (doom-modeline 'twitter) mode-line-format)
mode-line-format nil))

View file

@ -42,7 +42,6 @@
(after! helm-mode
(add-to-list 'helm-completing-read-handlers-alist '(find-file-at-point . nil)))
(set! :popup "\\` ?\\*[hH]elm.*?\\*\\'" :size 14 :regexp t)
(setq projectile-completion-system 'helm)
;;; Helm hacks

View file

@ -84,7 +84,6 @@ immediately runs it on the current candidate (ending the ivy session)."
(setq counsel-find-file-ignore-regexp "\\(?:^[#.]\\)\\|\\(?:[#~]$\\)\\|\\(?:^Icon?\\)")
;; Configure `counsel-rg', `counsel-ag' & `counsel-pt'
(set! :popup 'ivy-occur-grep-mode :size (+ 2 ivy-height) :regexp t :autokill t)
(dolist (cmd '(counsel-ag counsel-rg counsel-pt))
(ivy-add-actions
cmd

View file

@ -3,9 +3,7 @@
(def-package! realgud
:commands (realgud:gdb realgud:trepanjs realgud:bashdb realgud:zshdb)
:config
(set! :popup
'("^\\*\\(g\\|zsh\\|bash\\)db.*?\\*$" :size 20 :regexp t)
'("^\\*trepanjs.*?\\*$" :size 20 :regexp t))
(set! :popup "^\\*\\(?trepanjs:\\(?:g\\|zsh\\|bash\\)db\\)" '((size . 20)))
;; TODO Temporary Ex commands for the debugger
;; (def-tmp-excmd! doom:def-debug-on doom:def-debug-off

View file

@ -16,10 +16,6 @@
function that creates and returns the REPL buffer."
`(push (cons ,mode ,command) +eval-repls))
(set! :popup
'(:custom (lambda (b &rest _) (buffer-local-value '+eval-repl-mode b)))
:size 16 :noesc t)
;;
;; Evaluation
@ -70,7 +66,8 @@ function that creates and returns the REPL buffer."
(unless (boundp 'display-line-numbers)
(add-hook 'quickrun--mode-hook #'nlinum-mode))
:config
(set! :popup "*quickrun*" :size 6 :autokill t :autoclose t)
(set! :popup "^\\*\\(?:doom eval\\|Pp Eval Output\\|quickrun\\)"
'((window-height . 10)) '((transient . 0)))
(defun +eval*quickrun-auto-close (&rest _)
"Allows us to silently re-run quickrun from within the quickrun buffer."

View file

@ -32,14 +32,14 @@
there and there is only one window, split in that direction and place this
window there. If there are no windows and this isn't the only window, use
evil-window-move-* (e.g. `evil-window-move-far-left')"
(when (doom-popup-p)
(doom/popup-raise))
(let* ((this-window (get-buffer-window))
(when (window-dedicated-p)
(user-error "Cannot swap a dedicated window"))
(let* ((this-window (selected-window))
(this-buffer (current-buffer))
(that-window (windmove-find-other-window direction nil this-window))
(that-buffer (window-buffer that-window)))
(when (or (minibufferp that-buffer)
(doom-popup-p that-window))
(window-dedicated-p this-window))
(setq that-buffer nil that-window nil))
(if (not (or that-window (one-window-p t)))
(funcall (pcase direction

View file

@ -42,9 +42,8 @@
(add-hook 'doom-init-hook #'evil-mode)
(evil-select-search-module 'evil-search-module 'evil-search)
(set! :popup
'("*evil-registers*" :size 0.3)
'("*Command Line*" :size 8))
(set! :popup "^\\*evil-registers" '((size . 0.3)))
(set! :popup "^\\*Command Line" '((size . 8)))
;; Don't interfere with localleader key
(define-key evil-motion-state-map "\\" nil)

View file

@ -91,8 +91,7 @@ properties:
(after! xref
;; By default, `etags--xref-backend' is the default xref backend. No need.
;; We'll set these up ourselves in other modules.
(setq-default xref-backend-functions '(t))
(set! :popup "*xref*" :noselect t :autokill t :autoclose t))
(setq-default xref-backend-functions '(t)))
(defun +lookup|init-xref-backends ()
"Set `+lookup-current-functions' for the current buffer.

View file

@ -0,0 +1,49 @@
#+TITLE: :feature popup
A short summary about what this module does.
If necessary, include a longer description below it that goes into more detail. This may be as long as you like.
+ If possible, include a list of features
+ Include links to major plugins that the module uses, if applicable
+ Use links whenever you can
+ Mention dependencies on other modules here
* Table of Contents :TOC:
- [[#install][Install]]
- [[#main-dependencies][Main dependencies]]
- [[#extra-dependencies][Extra Dependencies]]
- [[#configuration][Configuration]]
- [[#usage][Usage]]
- [[#appendix][Appendix]]
- [[#commands][Commands]]
- [[#hacks][Hacks]]
* Install
** Main dependencies
*** MacOS
#+BEGIN_SRC sh :tangle (if (doom-system-os 'macos) "yes")
brew install x
#+END_SRC
*** Arch Linux
#+BEGIN_SRC sh :dir /sudo:: :tangle (if (doom-system-os 'arch) "yes")
sudo pacman --needed --noconfirm -S X
#+END_SRC
** Extra Dependencies
+ A
+ B
+ C
#+BEGIN_SRC sh
Y install A B C
#+END_SRC
* Configuration
* Usage
* Appendix
** Commands
** Hacks

View file

@ -0,0 +1,328 @@
;;; feature/popup/autoload.el -*- lexical-binding: t; -*-
(defun +popup--cancel-buffer-timer ()
"Cancel the current buffer's transient timer."
(when (timerp +popup--timer)
(message "Cancelled timer")
(cancel-timer +popup--timer)
(setq +popup--timer nil))
t)
(defun +popup--remember (windows)
"Remember WINDOWS (a list of windows) for later restoration."
(cl-assert (cl-every #'windowp windows) t)
(setq +popup--last
(cl-loop for w in windows
collect (list (window-buffer w)
(window-parameter w 'alist)
(window-state-get w)))))
(defun +popup--kill-buffer (buffer)
"Tries to kill BUFFER, as was requested by a transient timer. If it fails, eg.
the buffer is visible, then set another timer and try again later."
(when (buffer-live-p buffer)
(if (get-buffer-window buffer)
(with-current-buffer buffer
(setq +popup--timer
(run-at-time (timer--time +popup--timer)
nil #'+popup--kill-buffer buffer)))
(with-demoted-errors "Error killing transient buffer: %s"
(let ((inhibit-message (not doom-debug-mode)))
(message "Cleaned up transient buffer: %s" buffer))
(kill-buffer buffer)))))
(defun +popup--init (window alist)
"Initializes a popup window. Run any time a popup is opened. It sets the
default window parameters for popup windows, clears leftover transient timers
and enables `+popup-buffer-mode'."
(with-selected-window window
(set-window-parameter window 'no-other-window t)
(set-window-parameter window 'delete-window #'+popup--destroy)
(set-window-parameter window 'alist alist)
(window-preserve-size
window (memq (window-parameter window 'window-side) '(left right)) t)
(+popup--cancel-buffer-timer)
(+popup-buffer-mode +1)))
(defun +popup--destroy (window)
"Do housekeeping before destroying a popup window.
+ Disables `+popup-buffer-mode' so that any hooks attached to it get a chance to
run and do cleanup of its own.
+ Either kills the buffer or sets a transient timer, if the window has a
`transient' window parameter (see `+popup-window-parameters').
+ And finally deletes the window!"
(let ((ttl (+popup-parameter 'transient window))
(buffer (window-buffer window)))
(let ((ignore-window-parameters t))
(delete-window window))
(unless (window-live-p window)
(with-current-buffer buffer
(+popup-buffer-mode -1)
;; t = default
;; integer = ttl
;; nil = no timer
(when ttl
(when (eq ttl t)
(setq ttl +popup-ttl))
(cl-assert (integerp ttl) t)
(if (= ttl 0)
(+popup--kill-buffer buffer)
(setq +popup--timer
(run-at-time ttl nil #'+popup--kill-buffer buffer))))))))
(defun +popup--normalize-alist (alist)
"Merge `+popup-default-alist' and `+popup-default-parameters' with ALIST."
(if (not alist)
(setq alist +popup-default-alist)
(let* ((alist (map-merge 'list +popup-default-alist alist))
(params (map-merge 'list
+popup-default-parameters
(cdr (assq 'window-parameters alist)))))
(setq alist (assq-delete-all 'window-parameters alist))
(push (cons 'window-parameters params) alist)
(nreverse alist))))
;;
;; Public library
;;
;;;###autoload
(defun +popup-p (&optional target)
"Return t if TARGET is a popup window or buffer. If TARGET is nil, use the
current buffer."
(unless target
(setq target (current-buffer)))
(cond ((windowp target)
(+popup-p (window-buffer target)))
((bufferp target)
(buffer-local-value '+popup-buffer-mode target))
(t
(error "Expected a window/buffer, got %s (%s)"
(type-of target) target))))
;;;###autoload
(defun +popup-buffer (buffer &optional alist)
"Open BUFFER in a popup window. ALIST describes its features."
(let* ((old-window (selected-window))
(alist (+popup--normalize-alist alist))
(new-window (or (display-buffer-reuse-window buffer alist)
(display-buffer-in-side-window buffer alist))))
(+popup--init new-window alist)
(select-window
(if (+popup-parameter 'select new-window)
new-window
old-window))
new-window))
;;;###autoload
(defun +popup-parameter (parameter &optional window)
"Fetch the window parameter of WINDOW"
(window-parameter (or window (selected-window)) parameter))
;;;###autoload
(defun +popup-windows ()
"Returns a list of all popup windows."
(cl-remove-if-not #'+popup-p (window-list)))
;;
;; Minor mode
;;
;;;###autoload
(define-minor-mode +popup-mode
"Global minor mode for popups."
:init-value nil
:global t
:keymap +popup-mode-map
(cond (+popup-mode
(add-hook 'doom-unreal-buffer-functions #'+popup-p)
(add-hook '+evil-esc-hook #'+popup|close-on-escape t)
(setq +popup--old-display-buffer-alist display-buffer-alist
display-buffer-alist +popup--display-buffer-alist)
(dolist (prop +popup-window-parameters)
(push (cons prop 'writeable) window-persistent-parameters)))
(t
(remove-hook 'doom-unreal-buffer-functions #'+popup-p)
(remove-hook '+evil-esc-hook #'+popup|close-on-escape)
(setq display-buffer-alist +popup--old-display-buffer-alist)
(dolist (prop +popup-window-parameters)
(assq-delete-all prop window-persistent-parameters)))))
;;;###autoload
(define-minor-mode +popup-buffer-mode
"Minor mode for popup windows."
:init-value nil
:keymap +popup-buffer-mode-map)
;;
;; Hooks
;;
;;;###autoload
(defun +popup|adjust-fringes ()
"Hides the fringe in popup windows, restoring them if `+popup-buffer-mode' is
disabled."
(let ((f (if +popup-buffer-mode 0 doom-fringe-size)))
(set-window-fringes nil f f fringes-outside-margins)))
;;;###autoload
(defun +popup|set-modeline ()
"Don't show modeline in popup windows without a `modeline' window-parameter.
+ If one exists and it's a symbol, use `doom-modeline' to grab the format.
+ If non-nil, show the mode-line as normal.
+ If nil (or omitted), then hide the modeline entirely (the default)."
(if +popup-buffer-mode
(let ((modeline (+popup-parameter 'modeline)))
(cond ((or (eq modeline 'nil)
(not modeline))
(doom-hide-modeline-mode +1))
((and (symbolp modeline)
(not (eq modeline 't)))
(setq-local doom--modeline-format (doom-modeline modeline))
(when doom--modeline-format
(doom-hide-modeline-mode +1)))))
(when doom-hide-modeline-mode
(doom-hide-modeline-mode -1))))
;;;###autoload
(defun +popup|close-on-escape ()
"If called inside a popup, try to close that popup window (see
`+popup/close'). If called outside, try to close all popup windows (see
`+popup/close-all')."
(call-interactively
(if (+popup-p)
#'+popup/close
#'+popup/close-all)))
;;
;; Commands
;;
;;;###autoload
(defalias 'other-popup #'+popup/other)
;;;###autoload
(defun +popup/other ()
"Cycle through popup windows, like `other-window'. Ignores regular windows."
(interactive)
(let ((popups (+popup-windows))
(window (selected-window)))
(unless popups
(user-error "No popups are open"))
(select-window (if (+popup-p)
(or (car-safe (cdr (memq window popups)))
(car (delq window popups))
(car popups))
(car popups)))))
;;;###autoload
(defun +popup/close (&optional window force-p)
"Close WINDOW, if it's a popup window.
This will do nothing if the popup's `escape-quit' window parameter is either nil
or 'other. This window parameter is ignored if FORCE-P is non-nil."
(interactive
(list (selected-window)
current-prefix-arg))
(unless window
(setq window (selected-window)))
(when (and (+popup-p window)
(or force-p
(memq (+popup-parameter 'escape-quit window)
'(t current))))
(when +popup--remember-last
(+popup--remember (list window)))
(delete-window window)
t))
;;;###autoload
(defun +popup/close-all (&optional force-p)
"Close all open popup windows.
This will ignore popups with an `escape-quit' parameter that is either nil or
'current. This window parameter is ignored if FORCE-P is non-nil."
(interactive "P")
(let (targets +popup--remember-last)
(dolist (window (+popup-windows))
(when (or force-p
(memq (+popup-parameter 'escape-quit window)
'(t other)))
(push window targets)))
(when targets
(+popup--remember targets)
(mapc #'delete-window targets)
t)))
;;;###autoload
(defun +popup/toggle ()
"If popups are open, close them. If they aren't, restore the last one or open
the message buffer in a popup window."
(interactive)
(cond ((+popup-windows)
(+popup/close-all))
((ignore-errors (+popup/restore)))
((display-buffer (get-buffer "*Messages*")))))
;;;###autoload
(defun +popup/restore ()
"Restore the last popups that were closed, if any."
(interactive)
(unless +popup--last
(error "No popups to restore"))
(cl-loop for (buffer alist state) in +popup--last
if (and (buffer-live-p buffer)
(+popup-buffer buffer alist))
do (window-state-put state it))
(setq +popup--last nil))
;;;###autoload
(defun +popup/raise ()
"Raise the current popup window into a regular window."
(interactive)
(unless (+popup-p)
(user-error "Cannot raise a non-popup window"))
(let ((window (selected-window))
(buffer (current-buffer))
+popup--remember-last)
(set-window-parameter window 'transient nil)
(+popup/close window 'force)
(display-buffer-pop-up-window buffer nil)))
;;
;; Macros
;;
;;;###autoload
(defmacro without-popups! (&rest body)
"Run BODY with a default `display-buffer-alist', ignoring the popup rules set
with the :popup setting."
`(let ((display-buffer-alist +popup--old-display-buffer-alist))
,@body))
;;;###autoload
(defmacro save-popups! (&rest body)
"Sets aside all popups before executing the original function, usually to
prevent the popup(s) from messing up the UI (or vice versa)."
`(let* ((in-popup-p (+popup-p))
(popups (+popup-windows))
(popup-states
(cl-loop for p in popups
collect (cons (window-buffer p) (window-state-get p))))
+popup--last)
(dolist (p popups)
(+popup/close p 'force))
(unwind-protect
(progn ,@body)
(when popups
(let ((origin (selected-window)))
(+popup/restore)
(unless in-popup-p
(select-window origin)))))))

View file

@ -0,0 +1,196 @@
;;; config.el -*- lexical-binding: t; -*-
(defconst +popup-window-parameters
'(transient escape-quit select modeline alist)
"A list of custom parameters to be added to `window-persistent-parameters'.
Modifying this has no effect, unless done before feature/popup loads.
(transient . CDR)
CDR can be t, an integer or nil. It represents the number of seconds before
the buffer belonging to a closed popup window is killed.
If t, CDR will default to `+popup-ttl'.
If 0, the buffer is immediately killed.
If nil, the buffer won't be killed.
(escape-quit . CDR)
CDR can be t, 'other, 'current or nil. This determines the behavior of the
escape key in or outside of popup windows.
If t, close the popup if escape is pressed inside or outside of popups.
If 'other, close this popup if escape is pressed outside of any popup. This is
great for popups you just want to peek at and discard.
If 'current, close the current popup if escape is pressed from inside of
the popup.
If nil, pressing escape will never close this buffer.
(select . BOOl)
CDR is a boolean that determines whether to focus the popup window after it
opens.
(modeline . CDR)
CDR can be t (show the default modeline), a symbol representing the name of a
modeline defined with `def-modeline!', or nil (show no modeline).
(alist . CDR)
This is an internal parameter and should not be set or modified.
Since I can't find this information anywhere but the Emacs manual, I'll include
a brief description of some native window parameters that Emacs uses:
(delete-window . CDR)
(delete-other-window . CDR)
(split-window . CDR)
(other-window . CDR)
This applies to all four of the above: CDR can be t or a function. If t, using
those functions on this window will ignore all window parameters.
If CDR is a function, it will replace the native function when used on this
window. e.g. if CDR is #'ignore (delete-window popup) will run (ignore popup)
instead of deleting the window!
(no-other-window . BOOL)
If CDR is non-nil, this window becomes invisible to `other-window' and
`pop-to-buffer'. Doom popups sets this. The default is nil.")
(defvar +popup-default-alist
'((slot . 1)
(window-height . 0.14)
(window-width . 26)
(reusable-frames . visible))
"The default alist for `display-buffer-alist' rules.")
(defvar +popup-default-parameters
'((transient . t)
(escape-quit . t))
"The default window parameters to add alists fed to `display-buffer-alist'.")
(defvar +popup-ttl 10
"The default time-to-live for transient buffers whose popup buffers have been
deleted.")
(defvar +popup-mode-map (make-sparse-keymap)
"Active keymap in a session with the popup system enabled. See
`+popup-mode'.")
(defvar +popup-buffer-mode-map (make-sparse-keymap)
"Active keymap in popup windows. See `+popup-buffer-mode'.")
(defvar +popup--display-buffer-alist nil)
(defvar +popup--old-display-buffer-alist nil)
(defvar +popup--remember-last t)
(defvar +popup--last nil)
(defvar-local +popup--timer nil)
;;
(def-setting! :popup (condition &optional alist parameters)
"Register a popup rule.
CONDITION can be a regexp string or a function. See `display-buffer' for a list
of possible entries for ALIST, which tells the display system how to initialize
the popup window. PARAMETERS is an alist of window parameters. See
`+popup-window-parameters' for a list of custom parameters provided by the popup
module."
`(let ((alist ,alist)
(parameters ,parameters))
,(when alist
'(when-let* ((size (cdr (assq 'size alist))))
(setq alist (assq-delete-all 'size alist))
(push (cons (pcase (cdr (or (assq 'side alist)
(assq 'side +popup-default-alist)))
((or `left `right) 'window-width)
(_ 'window-height))
size)
alist)))
(prog1 (push (append (list ,condition '(+popup-buffer))
alist
(list (cons 'window-parameters parameters)))
+popup--display-buffer-alist)
(when (bound-and-true-p +popup-mode)
(setq display-buffer-alist +popup--display-buffer-alist)))))
;;
;; Default popup rules & bootstrap
;;
(eval-when-compile
(set! :popup "^ \\*")
(set! :popup "^\\*" nil '((select . t)))
(set! :popup "^\\*\\(?:scratch\\|Messages\\)" nil '((transient)))
(set! :popup "^\\*Help"
'((window-height . 0.2))
'((select . t)))
(set! :+popup "^\\*doom:"
'((window-height . 0.35))
'((select . t) (escape-quit) (transient))))
(setq +popup--display-buffer-alist (eval-when-compile +popup--display-buffer-alist))
(add-hook 'doom-init-ui-hook #'+popup-mode)
(add-hook '+popup-buffer-mode-hook #'+popup|adjust-fringes)
(add-hook '+popup-buffer-mode-hook #'+popup|set-modeline)
;;
;; Hacks
;;
(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 (direction &optional window ignore sign wrap mini)
(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)
(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))))
(+popup/close)
(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)))))
(provide 'config)
;;; config.el ends here

View file

@ -24,7 +24,7 @@
(git-gutter-mode +1)))
(add-hook! (text-mode prog-mode conf-mode) #'+version-control|git-gutter-maybe)
:config
(set! :popup "^\\*git-gutter.+\\*$" :regexp t :size 15 :noselect t)
(set! :popup "^\\*git-gutter" nil '((select)))
;; Update git-gutter on focus (in case I was using git externally)
(add-hook 'focus-in-hook #'git-gutter:update-all-windows)

View file

@ -11,10 +11,8 @@
(after! vc-annotate
(set! :popup
'("*vc-diff*" :size 15 :noselect t)
'("*vc-change-log*" :size 15)
'(vc-annotate-mode :same t))
(set! :popup "^\\vc-d" nil '((select))) ; *vc-diff*
(set! :popup "^\\vc-c" nil '((select . t))) ; *vc-change-log*
(set! :evil-state 'vc-annotate-mode 'normal)
(set! :evil-state 'vc-git-log-view-mode 'normal))

View file

@ -35,7 +35,7 @@
(setq nrepl-hide-special-buffers t)
;; settings for cider repl as a popup (prevent it from being closed on escape, especially.)
(set! :popup "^\\*cider" :regexp t :noselect t :noesc t)
(set! :popup "^\\*cider" nil '((escape-quit) (select)))
;; Setup cider for clojurescript / figwheel dev.
(setq cider-cljs-lein-repl

View file

@ -115,8 +115,7 @@
(def-package! overseer
:commands overseer-test
:init (set! :popup "*overseer*" :size 12))
:commands overseer-test)
;;

View file

@ -10,7 +10,6 @@
(add-hook! 'intero-mode-hook #'(flycheck-mode eldoc-mode))
(set! :popup "^intero:backend:" :regex t :size 12)
(set! :lookup 'haskell-mode :definition #'intero-goto-definition))

View file

@ -33,7 +33,7 @@
LaTeX-section-section
LaTeX-section-label))
(set! :popup " output\\*$" :regexp t :size 15 :noselect t :autoclose t :autokill t)
(set! :popup " output\\*$" '((size . 15)))
(map! :map LaTeX-mode-map "C-j" nil))

View file

@ -4,7 +4,7 @@
:mode "\\.p\\(lant\\)?uml$"
:config
(setq plantuml-jar-path (concat doom-etc-dir "plantuml.jar"))
(set! :popup "*PLANTUML Preview*" :size 25 :noselect t :autokill t)
(set! :popup "^\\*PLANTUML" '((size . 0.4)) '((select) (transient . 0)))
(unless (executable-find "java")
(warn "plantuml-mode: can't find java, preview disabled."))

View file

@ -78,7 +78,7 @@ environment variables."
anaconda-mode-eldoc-as-single-line t)
:config
(add-hook 'anaconda-mode-hook #'anaconda-eldoc-mode)
(set! :popup "*anaconda-mode*" :size 10 :noselect t :autoclose t :autokill t)
(set! :popup "^\\*anaconda-mode" nil '((select)))
(map! :map anaconda-mode-map :m "gd" #'anaconda-mode-find-definitions)
(advice-add #'anaconda-mode-doc-buffer :after #'doom*anaconda-mode-doc-buffer))
@ -113,7 +113,7 @@ environment variables."
:init
(associate! nose-mode :match "/test_.+\\.py$" :modes (python-mode))
:config
(set! :popup "*nosetests*" :size 0.4 :noselect t)
(set! :popup "^\\*nosetests" '((size . 0.4)) '((select)))
(set! :yas-minor-mode 'nose-mode)
(map! :map nose-mode-map
:localleader

View file

@ -4,7 +4,7 @@
:commands restclient-mode
:mode ("\\.http$" . restclient-mode)
:config
(set! :popup "*HTTP Response*" :size 30 :select t :noesc t :autokill t)
(set! :popup "^\\*HTTP Response" '((size . 0.4)) '((escape-quit . other)))
(map! :mode restclient-mode
:n [M-return] 'restclient-http-send-current
:localleader

View file

@ -24,8 +24,8 @@
"M--" #'text-scale-decrease
;; Simple window navigation/manipulation
"C-`" #'doom/popup-toggle
"C-~" #'doom/popup-raise
"C-`" #'+popup/toggle
"C-~" #'+popup/raise
"M-t" #'+workspace/new
"M-T" #'+workspace/display
"M-w" #'delete-window
@ -63,7 +63,7 @@
:en "C-k" #'evil-window-up
:en "C-l" #'evil-window-right
"C-x p" #'doom/other-popup
"C-x p" #'+popup/other
;; --- <leader> -------------------------------------
@ -78,7 +78,7 @@
:desc "Switch workspace buffer" :n "," #'persp-switch-to-buffer
:desc "Switch buffer" :n "<" #'switch-to-buffer
:desc "Browse files" :n "." #'find-file
:desc "Toggle last popup" :n "~" #'doom/popup-toggle
:desc "Toggle last popup" :n "~" #'+popup/toggle
:desc "Eval expression" :n "`" #'eval-expression
:desc "Blink cursor line" :n "DEL" #'+doom/blink-cursor
:desc "Jump to bookmark" :n "RET" #'bookmark-jump
@ -195,7 +195,7 @@
:desc "Apropos" :n "a" #'apropos
:desc "Reload theme" :n "R" #'doom//reload-theme
:desc "Find library" :n "l" #'find-library
:desc "Toggle Emacs log" :n "m" #'doom/popup-toggle-messages
:desc "Toggle Emacs log" :n "m" #'view-echo-area-messages
:desc "Command log" :n "L" #'global-command-log-mode
:desc "Describe function" :n "f" #'describe-function
:desc "Describe key" :n "k" #'describe-key
@ -723,6 +723,10 @@
"M-;" #'eval-expression
"A-;" #'eval-expression)
(:when (featurep! :feature popup)
(:map +popup-mode-map
"M-w" #'delete-window))
(:after tabulated-list
(:map tabulated-list-mode-map
[remap evil-record-macro] #'quit-window))

View file

@ -7,7 +7,6 @@
(def-package! gist
:commands (gist-list gist-region-or-buffer-private gist-region-or-buffer)
:config
(set! :popup "*github:gists*" :size 15 :select t :autokill t)
(set! :evil-state 'gist-list-mode 'normal)
(defun +gist*list-render (orig-fn &rest args)

View file

@ -8,18 +8,16 @@
(def-package! imenu-list
:commands imenu-list-minor-mode
:config
(setq imenu-list-focus-after-activation t)
(set! :popup imenu-list-buffer-name :size 35 :align 'right)
(set! :popup "^\\*Ilist"
'((side . right) (size . 35))
'((escape-quit . current) (select) (transient . 0)))
;; use popups
(defun doom*imenu-list-show ()
(doom-popup-buffer (get-buffer imenu-list-buffer-name)))
(advice-add #'imenu-list-show :override #'doom*imenu-list-show)
(advice-add #'imenu-list-show-noselect :override #'doom*imenu-list-show)
;; auto kill imenu-list on deactivation
(defun doom|kill-imenu-list ()
(when (and (not imenu-list-minor-mode)
(get-buffer imenu-list-buffer-name))
(kill-buffer (get-buffer imenu-list-buffer-name))))
(add-hook 'imenu-list-minor-mode-hook #'doom|kill-imenu-list))
(defun +imenu|cleanup-on-popup-close ()
"Clean up after `imenu-list-minor-mode' when killing the list window."
(unless +popup-buffer-mode
(when imenu-list--displayed-buffer
(with-current-buffer imenu-list--displayed-buffer
(imenu-list-minor-mode -1)))
(when (equal (buffer-name) imenu-list-buffer-name)
(kill-buffer (get-buffer imenu-list-buffer-name)))))
(add-hook '+popup-buffer-mode-hook #'+imenu|cleanup-on-popup-close))

View file

@ -33,5 +33,9 @@
"~$"
"^#.*#$"))
(set! :popup "^ ?\\*NeoTree"
`((side . ,neo-window-position) (width-width . ,neo-window-width))
'((escape-quit . current) (select . t)))
(when (bound-and-true-p winner-mode)
(push neo-buffer-name winner-boring-buffers)))

View file

@ -21,7 +21,7 @@
:commands pass
:config
(set! :evil-state 'pass-mode 'emacs)
(set! :popup "*Password-Store*" :align 'left :size 32 :select t :autokill t :noesc t)
(set! :popup "^\\*Password-Store" '((side . left)) '((escape-quit)))
(map! :map pass-mode-map
"j" #'pass-next-entry
"k" #'pass-prev-entry

View file

@ -19,14 +19,7 @@
(add-hook 'doom-init-theme-hook #'doom-themes-neotree-config)
(setq doom-neotree-enable-variable-pitch t
doom-neotree-file-icons 'simple
doom-neotree-line-spacing 2)
(after! neotree
(defun +doom|neotree-fix-popup ()
"Ensure the fringe settings are maintained on popup restore."
(neo-global--when-window
(doom--neotree-no-fringes)))
(add-hook 'doom-popup-mode-hook #'+doom|neotree-fix-popup)))
doom-neotree-line-spacing 2))
(def-package! solaire-mode

View file

@ -10,10 +10,10 @@
(tabbar-mode)
(defun +tabbar|disable-in-popups ()
(when tabbar-mode
(when (and +popup-buffer-mode tabbar-mode)
(tabbar-local-mode -1)
(setq-local header-line-format nil)))
(add-hook 'doom-popup-mode-hook #'+tabbar|disable-in-popups)
(add-hook '+popup-buffer-mode-hook #'+tabbar|disable-in-popups)
(defun +tabbar-display-tab (tab)
"Return a label for TAB that resembles tabs in Atom."