This manual, written by Protesilaos Stavrou, describes the
customization options for the Emacs package called beframe
(or
beframe.el
), and provides every other piece of information pertinent
to it.
The documentation furnished herein corresponds to stable version {{{stable-version}}}, released on {{{release-date}}}. Any reference to a newer feature which does not yet form part of the latest tagged commit, is explicitly marked as such.
Current development target is {{{development-version}}}.
- Package name (GNU ELPA):
beframe
- Official manual: https://protesilaos.com/emacs/beframe
- Change log: https://protesilaos.com/emacs/beframe-changelog
- Git repositories:
- Video demo: https://protesilaos.com/codelog/2023-02-28-emacs-beframe-demo/
- Backronym: Buffers Encapsulated in Frames Realise Advanced Management of Emacs.
If you are viewing the README.org version of this file, please note that the GNU ELPA machinery automatically generates an Info manual out of it.
beframe
enables a frame-oriented Emacs workflow where each frame has
access to the list of buffers visited therein. In the interest of
brevity, we call buffers that belong to frames “beframed”. Beframing
is achieved in three main ways:
- By calling the command
beframe-switch-buffer
. It is like the standardswitch-to-buffer
except the list of candidates is limited to those that the current frame knows about.
- By enabling the global minor mode
beframe-mode
. It sets theread-buffer-function
to one that filters buffers per frame. As such, commands likeswitch-to-buffer
,next-buffer
, andprevious-buffer
automatically work in a beframed way.
- The command
beframe-buffer-menu
produces a dedicated buffer with a list of buffers for the current frame. This is the counterpart ofbeframe-switch-buffer
. When called with a prefix argument (C-u
with default key bindings), it prompts for a frame whose buffers it will display.
Features of beframe-mode.
Producing multiple frames does not generate multiple buffer lists. There still is only one global list of buffers. Beframing them simply filters the list.
The user option beframe-global-buffers
contains a list of regular
expressions or major mode symbols that are matched against buffers.
The matching buffers are never beframed and are available in all
frames. The default value contains the buffers *scratch*
,
*Messages*
, and *Backtrace*
(more preciselly, it matches the
regular expressions \\*scratch\\*
, \\*Messages\\*
\\*Backtrace\\*
). If the value is nil
, no buffer enjoys such
special treatment: they all follow the beframing scheme of remaining
associated with the frame that opened them.
The user option beframe-create-frame-scratch-buffer
allows
beframe-mode
to create a frame-specific scratch buffer that runs the
initial-major-mode
. This is done upon the creation of a new frame
and the scratch buffer is named after the frame it belongs to. For
example, if the frame is called modus-themes
, the corresponding
scratch buffer is *scratch for modus-themes*
. Set this user option
to nil
to disable the creation of such scratch buffers.
The user option beframe-kill-frame-scratch-buffer
is the counterpart
of beframe-create-frame-scratch-buffer
. It kills the frame-specific
scratch buffer after the frame is deleted. Set this user option to
nil
to disable the killing of such buffers.
Beframe makes it possible to add or remove buffers from the list of buffers associated with the current frame. This provides for a flexible workflow where buffers can be initially beframed yet consolidated into new lists on demand.
The Beframe keymap
To assume buffers is to include them in the buffer list associated with the current frame.
- The command
beframe-assume-frame-buffers
prompts for a frame and then copies its buffer list into the current frame. - The command
beframe-assume-frame-buffers-selectively
adds buffers from a given frame to the current frame. In interactive use, the command first prompts for a frame and then asks about the list of buffers therein. The to-be-assumed buffer list is compiled withcompleting-read-multiple
. This means that the user can select multiple buffers, each separated by thecrm-separator
(typically a comma). - The command
beframe-assume-buffers-selectively-all-frames
prompts with minibuffer completion for a list of buffers to assume. The interface is the same as that ofbeframe-assume-frame-buffers-selectively
except that there is no prompt for a frame: buffers belong to the consolidated buffer list (all frames). - The command
beframe-assume-all-buffers-no-prompts
unconditionally assumes the consolidated buffer list. - The command
beframe-assume-buffers-matching-regexp-all-frames
prompts for a regular expression to match against buffer names. The matching buffers are assumed by the current frame. With an optional prefix argument forMATCH-MODE-NAMES
, the regular expression is matched against the buffer name or major mode. - The
beframe-kill-buffers-matching-regexp
command prompts for a regular expression to match against buffer names. If there are matches, it asks for confirmation and then proceeds to kill them. If the user optionbeframe-kill-buffers-no-confirm
is non-nil, it skips that confirmation step to carry out its action outright. Note that Emacs may still prompt for further confirmation if the given buffer is unsaved, has a running process, and the like. Also note that this operation applies to all frames because buffers are shared by the Emacs session even though Beframe only exposes those that pertain to a particular frame (Features of ~beframe-mode~).
To unassume buffers is to omit them from the buffer list associated with the current frame.
- The command
beframe-unassume-frame-buffers
prompts for a frame and then removes its buffer list from the current frame. - The command
beframe-unassume-current-frame-buffers-selectively
removes buffers from the current frame. In interactive use, the to-be-unassumed buffer list is compiled withcompleting-read-multiple
. This means that the user can select multiple buffers, each separated by thecrm-separator
(typically a comma). - The command
beframe-unassume-all-buffers-no-prompts
unconditionally unassumes the consolidated buffer list, but preserves the list stored in the user optionbeframe-global-buffers
. - The
beframe-unassume-buffers-matching-regexp-all-frames
prompts for a regular expression to match against buffer names. The matching buffers are unassumed by the current frame. With an optional prefix argument forMATCH-MODE-NAMES
, the regular expression is matched against the buffer name or major mode.
The beframe-mode
does the following:
- Sets the value of
read-buffer-function
to a function that beframes all commands that read that variable. This includes the likes ofswitch-to-buffer
,next-buffer
, andprevious-buffer
. - Add a filter to newly created frames so that their
buffer-predicate
parameter beframes buffers.
- Renames newly created frames so that they have a potentially more
meaningful title. The user option
beframe-rename-function
specifies the function that handles this process. When its value is nil, no renaming is performed.
- When the user option
beframe-functions-in-frames
contains a list of functions, it makes them run withother-frame-prefix
, meaning that they are called in a new frame. For example, the user can add a list that includesproject-prompt-project-dir
from the built-inproject
library. With that the new project buffer appears in its own frame and, thus, becomes part of a beframed list of buffers, isolated from all other frames. - Handles the creation and deletion of frame-specific scratch buffers,
per the user options
beframe-create-frame-scratch-buffer
,beframe-kill-frame-scratch-buffer
(Overview).
Those granted, it is not necessary to enable the beframe-mode
to use
Beframe’s commands. Those are available on demand (The Beframe keymap).
The beframe-prefix-map
defines key bindings for the Beframe commands
documented herein. We call it a prefix keymap because it is not
available from anywhere unless the user attaches it to a key sequence.
For example:
(define-key global-map (kbd "C-c b") #'beframe-prefix-map)
With the above code, C-c b
becomes the prefix key that invokes
Beframe commands. Type C-c b C-h
to show the available key
bindings (by default C-h
as a suffix to an incomplete key sequence
produces a Help buffer that links to all the available bindings).
The beframe-prefix-map
and beframe-mode
are used independent of
each other (Features of beframe-mode).
Users who prefer a more graphical interface can bind the
beframe-transient
instead of the aforementioned prefix map. For
example:
(define-key global-map (kbd "C-c b") #'beframe-transient)
The package is available as beframe
. Simply do:
M-x package-refresh-contents M-x package-install
And search for it.
GNU ELPA provides the latest stable release. Those who prefer to follow the development process in order to report bugs or suggest changes, can use the version of the package from the GNU-devel ELPA archive. Read: https://protesilaos.com/codelog/2022-05-13-emacs-elpa-devel/.
Assuming your Emacs files are found in ~/.emacs.d/
, execute the
following commands in a shell prompt:
cd ~/.emacs.d
# Create a directory for manually-installed packages
mkdir manual-packages
# Go to the new directory
cd manual-packages
# Clone this repo, naming it "beframe"
git clone https://github.com/protesilaos/beframe beframe
Finally, in your init.el
(or equivalent) evaluate this:
;; Make Elisp files in that directory available to the user.
(add-to-list 'load-path "~/.emacs.d/manual-packages/beframe")
Everything is in place to set up the package.
(require 'beframe)
;; This is the default value. Write here the names of buffers that
;; should not be beframed.
(setq beframe-global-buffers '("*scratch*" "*Messages*" "*Backtrace*"))
(beframe-mode 1)
;; Bind Beframe commands to a prefix key, such as C-c b:
(define-key global-map (kbd "C-c b") #'beframe-prefix-map)
;; OR use the transient instead of the prefix map:
(define-key global-map (kbd "C-c b") #'beframe-transient)
The consult
package by Daniel Mendler provides several commands that
enhance the standard minibuffer interface of Emacs. One of them is
consult-buffer
which lists buffers, recent files, bookmarks, and
possibly other sources in a single interface. With consult-buffer
the user can see previews of the given completion candidate and also
narrow to a specific source.
It is possible to add beframed buffers to the list of sources the
consult-buffer
command reads from. Just add the following to the
beframe
configuration:
(defvar consult-buffer-sources)
(declare-function consult--buffer-state "consult")
(with-eval-after-load 'consult
(defface beframe-buffer
'((t :inherit font-lock-string-face))
"Face for `consult' framed buffers.")
(defun my-beframe-buffer-names-sorted (&optional frame)
"Return the list of buffers from `beframe-buffer-names' sorted by visibility.
With optional argument FRAME, return the list of buffers of FRAME."
(beframe-buffer-names frame :sort #'beframe-buffer-sort-visibility))
(defvar beframe-consult-source
`( :name "Frame-specific buffers (current frame)"
:narrow ?F
:category buffer
:face beframe-buffer
:history beframe-history
:items ,#'my-beframe-buffer-names-sorted
:action ,#'switch-to-buffer
:state ,#'consult--buffer-state))
(add-to-list 'consult-buffer-sources 'beframe-consult-source))
As you can see from the snippet above, much like consult--buffer-query
itself, the beframe-buffer-names
function may take a keyword argument
:sort
. In our case, it is set to beframe-buffer-sort-visibility
,
which groups buffers by visibility, the first element of the list
being the most recently selected buffer other than the current one.
This is not perfect because frames can have duplicate buffers, but it works:
(with-eval-after-load 'ibuffer
(defun beframe-buffer-in-frame (buf frame)
"Return non-nil if BUF is in FRAME."
(memq buf (beframe-buffer-list (beframe-frame-object frame))))
(defun beframe-frame-name-list ()
"Return list with frame names."
(mapcar #'car (make-frame-names-alist)))
(defun beframe-generate-ibuffer-filter-groups ()
"Create a set of ibuffer filter groups based on the Frame of buffers."
(mapcar
(lambda (frame)
(list (format "%s" frame)
(list 'predicate 'beframe-buffer-in-frame '(current-buffer) frame)))
(beframe-frame-name-list)))
(setq ibuffer-saved-filter-groups
`(("Frames" ,@(beframe-generate-ibuffer-filter-groups))))
(define-ibuffer-filter frame
"Limit current view to buffers in frames."
(:description "frame")
(memq buf (beframe-buffer-list))))
Beframe is meant to be a collective effort. Every bit of help matters.
- Author/maintainer
- Protesilaos Stavrou.
- Contributions to code or the manual
- Bruno Boal, Edgar Vincent, Fritz Grabo, Stefan Monnier, Tony Zorman, Vedang Manerikar.
- Ideas and/or user feedback
- Derek Passen, Karan Ahlawat, Karthik Chikmagalur, Valentino, duli.
Copyright (C) 2023-2025 Free Software Foundation, Inc.
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, with the Front-Cover Texts being “A GNU Manual,” and with the Back-Cover Texts as in (a) below. A copy of the license is included in the section entitled “GNU Free Documentation License.”
(a) The FSF’s Back-Cover Text is: “You have the freedom to copy and modify this GNU manual.”