Skip to content

Latest commit

 

History

History
956 lines (775 loc) · 40.5 KB

README.org

File metadata and controls

956 lines (775 loc) · 40.5 KB

beframe: isolate buffers per frame

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}}}.

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.

1 Overview

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:

  1. By calling the command beframe-switch-buffer. It is like the standard switch-to-buffer except the list of candidates is limited to those that the current frame knows about.
  1. By enabling the global minor mode beframe-mode. It sets the read-buffer-function to one that filters buffers per frame. As such, commands like switch-to-buffer, next-buffer, and previous-buffer automatically work in a beframed way.
  1. The command beframe-buffer-menu produces a dedicated buffer with a list of buffers for the current frame. This is the counterpart of beframe-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.

2 Assuming and unassuming 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

2.1 Assuming buffers

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 with completing-read-multiple. This means that the user can select multiple buffers, each separated by the crm-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 of beframe-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 for MATCH-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 option beframe-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~).

2.2 Unassuming buffers

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 with completing-read-multiple. This means that the user can select multiple buffers, each separated by the crm-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 option beframe-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 for MATCH-MODE-NAMES, the regular expression is matched against the buffer name or major mode.

3 Features of beframe-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 of switch-to-buffer, next-buffer, and previous-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 with other-frame-prefix, meaning that they are called in a new frame. For example, the user can add a list that includes project-prompt-project-dir from the built-in project 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).

4 The Beframe keymap or transient user interface

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)

5 Installation

5.1 GNU ELPA package

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/.

5.2 Manual installation

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.

6 Sample configuration

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

6.1 Integration with Consult

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.

6.2 Integration with Ibuffer

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

7 Acknowledgements

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.

8 COPYING

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.”

9 GNU Free Documentation License