karthink / popper

Emacs minor-mode to summon and dismiss buffers easily.
GNU General Public License v3.0
440 stars 19 forks source link

+title: Popper: Popup Buffers for Emacs

+html: GNU ELPA

+html: GNU-devel ELPA

+html: MELPA

+html: MELPA Stable

Popper is a minor-mode to tame the flood of ephemeral windows Emacs produces, while still keeping them within arm's reach.

Designate any buffer to "popup" status, and it will stay out of your way. Disimss or summon it easily with one key. Cycle through all your "popups" or just the ones relevant to your current buffer. Group popups automatically so you're presented with the most relevant ones. Useful for many things, including toggling display of REPLs, documentation, compilation or shell output: any buffer you need instant access to but want kept out of your way!

There is a [[https://www.youtube.com/watch?v=E-xUNlZi3rI][detailed demo of Popper here]]. [Note (10/2021): This demo is quite out of date at this point but covers the basics.]

You can pre-designate any buffer (by name or major-mode) as a popup, and the status will be automatically applied when Emacs creates it.

By default, your popups are displayed in a non-obtrusive way, but Popper respects window rules for buffers that you might have in =display-buffer-alist= or created using a window management package like =shackle.el=. Popper summons windows defined by the user as "popups" by simply calling =display-buffer=.

*** Toggle a popup:

+ATTR_ORG: :width 500

+ATTR_HTML: :width 500px

[[file:images/popper-toggle-latest.gif]]

Here I toggle a REPL for quick access.

https://user-images.githubusercontent.com/8607532/135746327-c400aaf9-4aa1-4b6e-8b0a-0dd58c2690bb.mp4

*** Cycle through all your popups:

+ATTR_ORG: :width 500

+ATTR_HTML: :width 500px

[[file:images/popper-cycle.gif]]

Here I cycle through all "popup buffers" in quick succession. My popup buffers are the usual suspects: help buffers, REPLs, grep and occur buffers, shell and compilation output, log buffers etc.

https://user-images.githubusercontent.com/8607532/135746363-aa3c3a25-cc9d-4907-a85f-07ea0d764238.mp4

Note that popup buffers are indicated here by the marker "POP" in their modelines. *** Or jump to them instantly with hinting You can see your popups in the echo area and jump to them with a key.

https://user-images.githubusercontent.com/8607532/135746395-dfe3b3e8-9d5a-4309-b521-9555a34bb73d.mp4 *** Group your popups according to context With grouping turned on, I'm only shown the popups relevant to the current context (in this case the Popper project).

https://user-images.githubusercontent.com/8607532/135746404-d8673390-d220-46fe-9b57-9dc81458cecd.mp4

The context can be anything, see below. Projectile, Perspective and Project.el are supported out of the box. *** Turn a regular window into a popup:

+ATTR_ORG: :width 500

+ATTR_HTML: :width 500px

[[file:images/popper-demote.gif]]

https://user-images.githubusercontent.com/8607532/135746418-21d32c74-e1f1-48f3-ba19-792c7cb2a51a.mp4

Or promote a popup to regular window status. *** Popper respects your display buffer settings

https://user-images.githubusercontent.com/8607532/135746477-93f8fc3d-4806-4901-beae-904059584e72.mp4

And windows open the way you have specified them to: in reused windows, side windows, new or child frames, etc. All display-buffer actions are supported except for displaying in popups in new frames and in atomic windows. *** ... you can toggle all your popups at once:

+ATTR_ORG: :width 500

+ATTR_HTML: :width 500px

[[file:images/popper-toggle-all.png]]

[[file:images/popper-toggle-all.gif]]

There are two commands for displaying popups, you can bind them as convenient:

To automatically designate buffers as popups, see the customization section. Additionally, you can kill an open popup buffer with =popper-kill-latest-popup=.

If you want the echo-area hints, turn on =popper-echo-mode=.

** With =use-package=

+BEGIN_SRC emacs-lisp

(use-package popper :ensure t ; or :straight t :bind (("C-" . popper-toggle) ("M-" . popper-cycle) ("C-M-`" . popper-toggle-type)) :init (setq popper-reference-buffers '("\Messages\" "Output\*$" "\Async Shell Command\" help-mode compilation-mode)) (popper-mode +1) (popper-echo-mode +1)) ; For echo area hints

+END_SRC

See the Customization section for details on specifying buffer types as popups.

** Without =use-package=

+BEGIN_SRC emacs-lisp

(require 'popper) (setq popper-reference-buffers '("\Messages\" "Output\*$" "\Async Shell Command\" help-mode compilation-mode)) (global-set-key (kbd "C-") 'popper-toggle) (global-set-key (kbd "M-") 'popper-cycle) (global-set-key (kbd "C-M-`") 'popper-toggle-type) (popper-mode +1)

;; For echo-area hints (require 'popper-echo) (popper-echo-mode +1)

+END_SRC

See the Customization section for details on specifying buffer types as popups.

https://user-images.githubusercontent.com/8607532/135748097-268f5aae-ad42-44fa-9435-b63b960d45cf.mp4

In this example:

** Grouping popups by context Popper can group popups by "context", so that the popups available for display are limited to those that are relevant to the context in which =popper-toggle= or =popper-cycle= is called. For example, when cycling popups from a project buffer, you may only want to see the popups (REPLs, help buffers and compilation output, say) that were spawned from buffers in that project. This is intended to approximate DWIM behavior, so that the most relevant popup in any context is never more than one command away.

Built in contexts include projects as defined in Emacs' built in =project.el= and =projectile=, using =perspective= names (from =persp.el=), as well as the default directory of a buffer. To set this, customize =popper-group-function= or use one of

+BEGIN_SRC emacs-lisp

(setq popper-group-function #'popper-group-by-project) ; project.el projects

(setq popper-group-function #'popper-group-by-projectile) ; projectile projects

(setq popper-group-function #'popper-group-by-directory) ; group by project.el ; project root, with ; fall back to ; default-directory (setq popper-group-function #'popper-group-by-perspective) ; group by perspective

+END_SRC

You can also provide a custom function that takes no arguments, is executed in the context of a popup buffer and returns a string or symbol that represents the group/context it belongs to. This function will group all popups under the symbol =my-popup-group=:

+BEGIN_SRC emacs-lisp

(defun popper-group-by-my-rule () "This function should return a string or symbol that is the name of the group this buffer belongs to. It is called with each popup buffer as current, so you can use buffer-local variables."

'my-popup-group)

(setq popper-group-function #'popper-group-by-my-rule)

+END_SRC

** Managing popup placement In keeping with the principle of least surprise, all popups are shown in the same location: At the bottom of the frame. You can customize =popper-display-function= to change how popups are displayed.

However this means you can't have more than one popup open at a time. You may also want more control over where individual popups appear. For example, you may want an IDE-like set-up, with all help windows open on the right, REPLs on top and compilation windows at the bottom. This is best done by customizing Emacs' =display-buffer-alist=. Since this is a [[https://www.gnu.org/software/emacs/manual/html_node/elisp/The-Zen-of-Buffer-Display.html#The-Zen-of-Buffer-Display][singularly confusing task]], I recommend using =popper= with a package that locks window placements, /e.g./ [[https://depp.brause.cc/shackle/][Shackle]].

*** Default popup placement:

+begin_src emacs-lisp

(setq popper-display-control t) ;This is the DEFAULT behavior

+end_src

You can customize =popper-display-function= to show popups any way you'd like. Any =display-buffer= [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Buffer-Display-Action-Functions.html][action function]] can work, or you can write your own. For example, setting it as

+BEGIN_SRC emacs-lisp

(setq popper-display-function #'display-buffer-in-child-frame)

+END_SRC

will cause popups to be displayed in a child frame.

*** Popup placement controlled using =display-buffer-alist= or =shackle.el=: If you already have rules in place for how various buffers should be displayed, such as by customizing =display-buffer-alist= or with =shackle.el=, popper will respect them once you set =popper-display-control= to nil:

+begin_src emacs-lisp

(use-package shackle ;; -- shackle rules here -- )

(use-package popper ;; -- popper customizations here--

:config (setq popper-display-control nil))

+end_src

** Suppressing popups Popper can suppress popups when they are first created. The buffer will be registered in the list of popups but will not show up on your screen. Instead, a message ("Popup suppressed: $buffer-name") will be printed to the echo area. You can then raise it using =popper-toggle= or =popper-cycle= at your convenience. It behaves as a regular popup from that point on:

[[https://user-images.githubusercontent.com/8607532/132929265-37eee976-131f-4631-9bad-73090bf17231.mp4]]

[[file:images/popper-hide-popup.gif]]

This is generally useful to keep buffers that are created as a side effect from interrupting your work.

To specify popups to auto-hide, use a cons cell with the =hide= symbol when specifying =popup-reference-buffers=:

+begin_src emacs-lisp

(setq popper-reference-buffers '(("Output\$" . hide) (completion-list-mode . hide) occur-mode "\*Messages\"))

+end_src

This assignment will suppress all buffers ending in =Output*= and the Completions buffer. The other entries are treated as normal popups.

You can combine the hiding feature with predicates for classifying buffers as popups:

+BEGIN_SRC emacs-lisp

(defun popper-shell-output-empty-p (buf) (and (string-match-p "\Async Shell Command\" (buffer-name buf)) (= (buffer-size buf) 0)))

(add-to-list 'popper-reference-buffers '(popper-shell-output-empty-p . hide))

+END_SRC

This assignment will suppress display of the async shell command output buffer, but only when there is no output (stdout). Once it is hidden it will be treated as a popup on par with other entries in =popper-reference-buffers=.

** Mode line and Echo area customization

Packages like [[https://depp.brause.cc/shackle/][Shackle]] help with specifying how certain buffers should be displayed, but don't give you an easy way to access them beyond calling display-buffer. Popper is mainly concerned with the latter and is thus more or less orthogonal to Shackle. Moreover, most window management packages for Emacs are opinionated in how windows should be displayed, or provide an additional API to customize this (e.g. [[https://github.com/emacsorphanage/popwin][Popwin]]). While Popper defaults to displaying popups a certain way, it tries to stay out of the business of display rules and focuses on providing one-key access to the buffers you're most likely to need next.

By default, it installs a single rule in =display-buffer-alist= to handle displaying popups. If =popper-display-control= is set to =nil=, this rule is ignored. You can change how the popups are shown by customizing =popper-display-function=, the function used by =display-buffer= to display popups, although you are better off customizing =display-buffer-alist= directly or using Shackle.