/popper

Emacs minor-mode to summon and dismiss buffers easily.

Primary LanguageEmacs LispGNU General Public License v3.0GPL-3.0

Popper.el

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. Useful for many things, including toggling display of REPLs, documentation, compilation or shell output, etc.

There is a detailed demo of Popper here.

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:

images/popper-toggle-latest.gif

Cycle through all your popups:

images/popper-cycle.gif

Turn a regular window into a popup:

images/popper-demote.gif

You can also promote a popup to a normal window.

Or toggle all your popups at once:

images/popper-toggle-all.gif

Usage

To designate popups in your init file, see the customization section.

There are two primary commands, you can bind them as convenient:

  • popper-toggle-latest: Show/hide the latest popup. Does more with prefix args.
  • popper-cycle: Cycle through your popups in sequence. With a prefix arg, cycle backwards.

Additionally, you can turn a regular window into a popup (or vice-versa) with popper-toggle-type, and kill an open popup buffer with popper-kill-latest-popup.

Setup

popper is available in MELPA, so you can install it with M-x package-install RET popper RET after adding MELPA to your package archives list.

With use-package

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

See Customization for details on specifying buffer types as popups.

Without use-package

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

See Customization for details on specifying buffer types as popups.

Customization

To get started, customize this variable:
  • popper-reference-buffers: List of buffers to treat as popups. Each entry in the list can be a regexp (string) to match buffer names against, or a major-mode (symbol) to match buffer major-modes against.

    Example:

    '("\\*Messages\\*"
      "Output\\*$"
      help-mode
      compilation-mode)
        

    Will treat the following as popups: The Messages buffer, any buffer ending in “Output*”, and all help and compilation buffers.

    There are other customization options, check the popper group.

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-latest 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, as well as the default directory of a buffer. To set this, customize popper-group-function or use one of

(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

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:

(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)

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 singularly confusing task, I recommend using popper with a package that locks window placements, e.g. Shackle.

Default popup placement:

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

You can customize popper-display-function to show popups any way you’d like. Any display-buffer action function can work, or you can write your own. For example, setting it as

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

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:

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

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

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

Technical notes

popper uses a buffer local variable (popper-popup-status) to identify if a given buffer should be treated as a popup. Matching is always by buffer and not window, so having two windows of a buffer, one treated as a popup and one as a regular window, isn’t possible (although you can do this with indirect clones). In addition, it maintains an alist of popup windows/buffers for cycling through.

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.