#+title: bufferlo.el - Frame/Tab Local Buffer Lists with Persistence
#+author: Florian Rommel, Stephane Marks
#+email: mail@florommel.de, shipmints@gmail.com
#+language: en
#+options: num:nil
#+options: toc:nil

Easy-to-use buffer management and workspace persistence tools for
Emacs workflow management. Headbutt your way to productivity and moove
ahead with bufferlo.

* Introduction

With bufferlo, every frame and tab (i.e., ~tab-bar-mode~ tab) has an
additional manageable local buffer list. A buffer is added to the
local buffer list when displayed in the frame/tab (e.g., by opening a
new file in the tab or by switching to the buffer from the global
buffer list).

Using Emacs's built-in buffer-list frame parameter, bufferlo
integrates seamlessly with all standard frame and tab management
facilities, including undeletion of frames and tabs, tab duplication
and moving, frame cloning, and session persistence with desktop.el
(though bufferlo frame and tab bookmarks offer an alternative
persistence method).

Bufferlo provides extensive management functions for its local lists
and offers features on top of switch-buffer functions, buffer menu,
and ~ibuffer~. You can configure any command that selects a buffer to
use the local buffer list via ~bufferlo-anywhere-mode~.

In addition, bufferlo offers lightweight Emacs bookmarks-based persistence for
frames, tabs, and sets of frames/tabs to help you manage your transient
workflows. Bufferlo bookmarks are compatible with built-in features such as
~bookmark-bmenu-list~ and third-party packages such as [[https://github.com/minad/consult][consult]] which offers
~consult-bookmark~ and ~consult-buffer~ for interactive bookmark and buffer
selection.

* Installation

Bufferlo is available in [[https://elpa.gnu.org/packages/bufferlo.html][GNU ELPA]].
Install it via ~package-install~ and enable ~bufferlo-mode~
#+begin_src emacs-lisp
  (bufferlo-mode)
#+end_src

Or via ~use-package~ (see below for a comprehensive configuration example)
#+begin_src emacs-lisp
  (use-package bufferlo
    :ensure t
    :init
    (bufferlo-mode))
#+end_src

Note: Although the Emacs ~tab-bar-mode~ is fully supported, you do not
have to use tabs to benefit from bufferlo; you can stick to a
frame-only workflow. If you use ~tab-line-mode~ without
~tab-bar-mode~, your bufferlo experience will be the same as a
frame-only user. Tab-oriented functions are active even if the tab-bar
is hidden.

The following code examples use ~setq~ to customize options. You may
also use ~M-x customize-group bufferlo~. Emacs 29 introduced ~setopt~
which works correctly in the presence of ~defcustom~ setters. Currently
the only bufferlo option with such a setter is
~bufferlo-bookmarks-auto-save-interval~ so be sure to set that
interval timer in advance of enabling ~bufferlo-mode~.

Many bufferlo commands have short-hand aliases to accommodate the
helpful ~which-key~ display (now a built-in for Emacs 30+). Bufferlo's
menu references these aliases and not the main commands. We recommend
that you use the short-hand aliases when binding your preferred keys
to ensure the bufferlo menu reflects your key bindings.

* Usage

** Buffer selection

Use bufferlo buffer-list commands as local-buffer alternatives to
built-in global-buffer commands:

- ~bufferlo-switch-to-buffer~: The command ~switch-to-buffer~ filtered
  for local buffers. Call it with a prefix argument to get the global
  list (all buffers).

- ~bufferlo-ibuffer~: The command ~ibuffer~ filtered for local
  buffers. Alternatively, use "/ l" in ibuffer.

- ~bufferlo-ibuffer-orphans~: The command ~ibuffer~ filtered for
  orphan buffers. Orphan buffers are buffers that are not in any
  frame/tab's local buffer list. Alternatively, use "/ L" in ibuffer.

- ~bufferlo-list-buffers~: Display a list of local buffers in a
  buffer-menu buffer.

- ~bufferlo-list-orphan-buffers~: Display a list of orphan buffers in
  a ~buffer-menu~ buffer. Orphan buffers are buffers that are not in any
  frame/tab's local buffer list.

** Manage local buffer lists

- ~bufferlo-clear~: Clear the frame/tab's local buffer list, retaining
  the current buffer. This is non-destructive to the buffers
  themselves.

- ~bufferlo-remove~: Remove a buffer from the frame/tab's buffer list.

- ~ibuffer~: Bufferlo adds the "-" key binding in ~ibuffer-mode~ to
  invoke ~bufferlo-remove~ on marked buffers.

- ~bufferlo-remove-non-exclusive-buffers~: Remove all buffers from the
  local list that are not exclusive to this frame/tab.

- ~bufferlo-bury~: Bury and remove the current buffer from the
  frame/tab's buffer list.

- ~bufferlo-kill-buffers~: Kill all buffers on the frame/tab local list.

- ~bufferlo-kill-orphan-buffers~: Kill all buffers that are *not* on
  any frame/tab local list.

- ~bufferlo-delete-frame-kill-buffers~: Delete the frame and kill all its local buffers.

- ~bufferlo-tab-close-kill-buffers~: Close the tab and kill its local buffers.

- ~bufferlo-isolate-project~: Isolate a project.el project in the
  frame or tab. This removes non-project buffers from the local buffer
  list. Use a prefix argument to further restrict the retained buffers
  to only those that are visiting files.

- ~bufferlo-find-buffer~: Switch to a frame/tab that contains the
  buffer in its local list.

- ~bufferlo-find-buffer-switch~: Switch to a frame/tab that contains
  the buffer in its local list, and select the buffer.

** Bookmark management for frames, tabs, and sets

Bufferlo can bookmark the buffers and windows belonging to individual
frames and tabs for later recall between Emacs sessions or within a
long-running session. Sets can be defined as collections of frames
and/or tabs to be recalled as a group. All you need to do is provide a
name for a bookmark and save it for later recall.

A tab bookmark includes the tab's window configuration, the state (not
the contents) of all bookmarkable local buffers, and the bufferlo
local buffer list. Tabs can be restored into any frame.

A frame bookmark saves every tab on a frame, each with the tab
contents stated above. Frames can be restored into the current frame,
replacing all tabs, into a new frame, or merged with the current
frame's tabs. Frames can also store their geometry for later
restoration.

A bookmark set saves a list of frame and tab bookmark names, where
constituent bookmarks behave as above, and can optionally restore each
frame's geometry. Bufferlo frame and tab bookmarks may be referenced
in multiple bookmark sets which can be useful for buffers that are
common across workflows.

*** General bookmark commands

The first three of these commands accept multiple selected bookmarks.
This can be made easier by leveraging Emacs completion packages such
as [[https://github.com/oantolin/orderless][orderless]] which adds regexp matching. This is even more convenient
in combination with a package like [[https://github.com/minad/vertico][vertico]].

- ~bufferlo-bookmarks-load-interactive~ (alias ~bufferlo-bms-load~):
  Load one or more stored saved bufferlo frame or tab bookmarks.

- ~bufferlo-bookmarks-load~: load stored bufferlo bookmarks that match
  your load predicates, or load all when using a prefix argument or
  when you call the function using passing t as its sole argument.
  Bookmarks already loaded are ignored.

- ~bufferlo-bookmarks-save-interactive~ (alias ~bufferlo-bms-save~):
  Save one or more currently active bufferlo frame or tab bookmarks.

- ~bufferlo-bookmarks-save~: save active bufferlo bookmarks that match
  your save predicates, or save all when using a prefix argument or
  when you call the function using passing t as its sole argument.

- ~bufferlo-bookmarks-close-interactive~ (alias ~bufferlo-bms-close~):
  Close one or more currently active bufferlo frame or tab bookmarks,
  killing the buffers from each local buffer list. You will not be
  prompted to save bookmarks or further confirm buffer kills except
  where their content requires saving or contain active processes;
  e.g., ~*shell*~ buffers.

- ~bufferlo-bookmarks-close~: Close all active bufferlo frame and tab
  bookmarks and kill their buffers. You will be prompted to save
  bookmarks using filter predicates or all unless a prefix argument is
  specified to inhibit the prompt and rely on your default policy.

- ~bufferlo-bookmark-raise~ (alias ~bufferlo-bm-raise~): Select the
  frame and/or frame/tab of the chosen active bookmark. Note: If you
  have duplicate active bookmarks, the first one found wins.

- ~bufferlo-clear-active-bookmarks~ Clear all active bufferlo frame
  and tab bookmarks. This leaves frames and tabs intact, content
  untouched, and does not impact stored bookmarks. You will be
  prompted to confirm clearing (which cannot be undone) unless a
  prefix argument is specified to inhibit the prompt.

  This is useful when you have accumulated a complex working set of
  frames, tabs, buffers and want to save new bookmarks without
  disturbing existing bookmarks, or where auto-saving is enabled and
  you want to avoid overwriting stored bookmarks, perhaps with
  transient work.

- ~bufferlo-maybe-clear-active-bookmark~ Clear the current frame
  and/or tab bufferlo bookmark. By default, this clears the active
  bookmark name only if there is another active bufferlo bookmark with
  the same name. Use a prefix argument or call the function with t to
  force clear the bookmark even if it is currently unique.

  This is useful if an active bookmark has been loaded more than once,
  and especially if you use the auto-save feature and want to ensure
  that only one bookmark is active.

- ~bookmark-bmenu-list~: Typically bound to ~C-x r l~, this loads the
  standard Emacs bookmark menu to select a bookmark and manage the
  bookmark list including non-bufferlo bookmarks. Bufferlo frame
  bookmarks are identified as "B-Frame", tab bookmarks as "B-Tab", and
  bookmark sets as "B-Set".

- ~bookmark-rename~: Invoke this command to rename a bookmark. This
  command will refuse to rename an active bufferlo bookmark (close or
  clear it and then rename). This function is also available via
  ~bookmark-bmenu-list~.

- ~bookmark-delete~: Invoke this command to delete a bookmark. This
  command will refuse to delete an active bufferlo bookmark (close or
  clear it and then delete). This function is also available via
  ~bookmark-bmenu-list~.

Note: Renaming or deleting a bufferlo tab or frame bookmark does not
rename or delete references to those bookmarks within bookmark sets.

*** Frame bookmark commands

- ~bufferlo-bookmark-frame-save~ (alias ~bufferlo-bm-frame-save~):
  Save a bookmark for the current frame under a new name or pick an
  existing name to reuse.

- ~bufferlo-bookmark-frame-save-current~ (alias
  ~bufferlo-bm-frame-save-curr~): Update the existing bookmark for the
  current frame.

- ~bufferlo-bookmark-frame-load~ (alias ~bufferlo-bm-frame-load~):
  Load a frame bookmark. This will overwrite your current frame
  content (no buffers are killed). Use a prefix argument to inhibit
  creating a new frame.

- ~bufferlo-bookmark-frame-load-current~ (alias
  ~bufferlo-bm-frame-load-curr~): Reload the existing bookmark for the
  current frame. This will overwrite your current frame content (no
  buffers are killed).

- ~bufferlo-bookmark-frame-load-merge~ (alias
  ~bufferlo-bm-frame-load-merge~): Load a frame bookmark, but instead
  of creating a new frame or overwriting the current frame content,
  this adds the loaded tabs into the current frame.

*** Tab bookmark commands

- ~bufferlo-bookmark-tab-save~ (alias ~bufferlo-bm-tab-save~): Save a
  bookmark for the current tab under a new name or pick an existing
  name to reuse.

- ~bufferlo-bookmark-tab-save-current~ (alias
  ~bufferlo-bm-tab-save-curr~): Update the existing bookmark for the
  current tab (no buffers are killed).

- ~bufferlo-bookmark-tab-load~ (alias ~bufferlo-bm-tab-load~): Load a
  tab bookmark. This will overwrite your current tab content (no
  buffers are killed). Use a prefix argument to inhibit creating a new
  tab.

- ~bufferlo-bookmark-tab-load-current~ (alias
  ~bufferlo-bm-tab-load-curr~): Reload the existing bookmark for the
  current tab. This will overwrite your current tab content (no
  buffers are killed).

*** Bookmark set commands

Most bookmark-set prompts use ~completing-read-multiple~ aka CRM, so keep in
mind the default CRM separator key is a comma which you should press between
each selection.

- ~bufferlo-set-save-interactive~ (alias ~bufferlo-set-save~): Save a
  bufferlo bookmark set for the specified active bookmarks. Frame
  bookmark names are stored along with their geometry for optional
  restoration. Tab bookmark names are grouped based on their shared
  frames along with each frame's geometry.

- ~bufferlo-set-save-current-interactive~ (alias
  ~bufferlo-set-save-curr~): Update the content of all active
  constituent bookmarks in selected bookmark sets.

- ~bufferlo-set-add-interactive~ (alias ~bufferlo-set-add~): Add an active
  bookmark to an active bookmark set.

- ~bufferlo-set-remove-interactive~ (alias ~bufferlo-set-remove~): Remove an
  active bookmark from an active bookmark set.

- ~bufferlo-set-load-interactive~ (alias ~bufferlo-set-load~): Prompt
  to load bufferlo set bookmarks. This will restore each set's
  constituent frame and tab bookmarks along with the tab bookmarks'
  shared frames. Frame geometry is optionally restored.

- ~bufferlo-set-clear-interactive~ (alias ~bufferlo-set-clear~): Clear
  the specified bookmark sets. This has the effect of leaving the
  set's constituent frame and tab bookmarks in place while indicating that
  the bookmark sets are no longer active.

- ~bufferlo-set-close-interactive~ (alias ~bufferlo-set-close~): Close
  the specified bookmark sets. This closes their constituent bookmarks
  and kills their buffers.

- ~bufferlo-set-list-interactive~ (alias ~bufferlo-set-list~): List
  the constituent bookmarks of the selected active sets in a
  ~special-mode~ buffer and pop to it. The display shows each
  bookmark's name, its type, the frame it's currently on, and, if a
  tab bookmark, its tab number. Typing ~<RET>~ or clicking ~mouse-1~
  will raise the selected bookmark. Type "q" to quit.

Notes:

- To curate a saved bookmark set, invoke
  ~bufferlo-set-save-interactive~ and save a new set of active
  bookmarks, replacing the existing bookmark set.
- Bookmark sets are unaware of constituent frame and tab bookmark
  renames or deletes.
- Bookmark sets are Emacs bookmarks and can be deleted or renamed
  using Emacs bookmark commands; e.g., via ~bookmark-bmenu-list~.
- While bookmark sets can be auto loaded, just as individual frame and
  tab bookmarks can be, bookmark sets cannot themselves be auto-saved.
  Constituent bookmarks are saved individually based on your auto-save
  predicates.

*** DWIM commands

These do-what-I-mean aka DWIM commands are conveniences that detect an
active frame or tab bookmark avoiding the need to to specify the frame
or tab variants of the equivalent commands.

Note: Bufferlo DWIM commands prioritize frame bookmarks over tab
bookmarks should both exist.

- ~bufferlo-bookmark-save-curr~ (alias ~bufferlo-bm-save~): Save the
  current frame or tab bookmark. This does not prompt to save a new
  bookmark if no bookmark is established.

- ~bufferlo-bookmark-load-curr~ (alias ~bufferlo-bm-load~): Reload the
  current frame or tab bookmark. This does not prompt to load a new
  bookmark if no bookmark is established.

- ~bufferlo-bookmark-close-curr~ (alias ~bufferlo-bm-close~): Close
  current frame or tab bookmark and kill its buffers.

*** Bufferlo buffer killing policies

To control bufferlo confirmation prompts when killing local or orphan
buffers:
#+begin_src emacs-lisp
  (setq bufferlo-kill-buffers-prompt t) ; default nil
#+end_src

To control bufferlo behavior when closing frame or tab bookmarks and
killing their local modified buffers or process buffers such as
~shell-mode~ or ~eshell-mode~:
#+begin_src emacs-lisp
  (setq bufferlo-kill-modified-buffers-policy nil) ; use normal Emacs prompting behavior
  (setq bufferlo-kill-modified-buffers-policy 'retain-modified) ; kill just unmodified
  (setq bufferlo-kill-modified-buffers-policy 'retain-modified-kill-without-file-name) ; kill unmodified and buffers without files
  (setq bufferlo-kill-modified-buffers-policy 'kill-modified) ; kill local buffers without prompting; the default
#+end_src

*** Automatic bookmark saving

You can configure bufferlo to automatically save some or all bookmarks
based on an interval timer and/or at Emacs exit. Similarly, you can
configure bufferlo to automatically load some or all bookmarks at
Emacs startup.

To set the automatic save timer, set the number of whole integer
seconds between saves that you prefer, or 0, the default, to disable
the timer:
#+begin_src emacs-lisp
  (setq bufferlo-bookmarks-auto-save-interval 120) ; do this in advance of enabling `bufferlo-mode'
  (setopt bufferlo-bookmarks-auto-save-interval 120) ; or use setopt, to invoke the custom setter
#+end_src

By default, bufferlo will save all active bookmarks. To select the
subset of bookmarks you want to save, write one or more predicate
tests that accept a bookmark name as its argument; it should return t
to indicate to save the bookmark, or nil otherwise.

Example auto-save predicate:

#+begin_src emacs-lisp
  (defun my/bufferlo-bookmarks-save-p (bookmark-name)
    "Auto save bufferlo bookmarks that contain \"=as\" for autosave."
    (string-match-p (rx "=as") bookmark-name))
  (setq bufferlo-bookmarks-save-predicate-functions nil) ; clear the default #'bufferlo-bookmarks-save-all-p
  (add-hook 'bufferlo-bookmarks-save-predicate-functions #'my/bufferlo-bookmarks-save-p)
#+end_src

You can control messages produced when bufferlo saves bookmarks:

#+begin_src emacs-lisp
  (setq bufferlo-bookmarks-auto-save-messages nil) ; inhibit messages (default)
  (setq bufferlo-bookmarks-auto-save-messages t) ; messages when saving and when there are no bookmarks to save
  (setq bufferlo-bookmarks-auto-save-messages 'saved) ; message only when bookmarks are saved
  (setq bufferlo-bookmarks-auto-save-messages 'notsaved) ; message only when there are no bookmarks to save
#+end_src

To save your bufferlo bookmarks when frames and tabs are closed:

#+BEGIN_SRC emacs-lisp
  (setq bufferlo-bookmark-frame-save-on-delete 'if-current)
  (setq bufferlo-bookmark-tab-save-on-close 'if-current)
  ;; See the variables' documentation for more options
#+END_SRC

To save your bufferlo bookmarks at Emacs exit (set in advance of
enabling ~bufferlo-mode~):

#+begin_src emacs-lisp
  (setq bufferlo-bookmarks-save-at-emacs-exit 'nosave) ; inhibit saving at exit (default)
  (setq bufferlo-bookmarks-save-at-emacs-exit 'pred) ; save active bookmark names that match your predicates
  (setq bufferlo-bookmarks-save-at-emacs-exit 'all) ; save all active bookmarks
#+end_src

Workflow tip: If you would like to be able to restore a bookmark's
original state and still benefit from auto-saving its current state,
simply save two copies. The first one with a base name; e.g.,
"bufferlo", and the second, which you should save immediately after
the first, called; e.g., "bufferlo=as". You can restore "bufferlo" and
get back to your original any time while the "=as" bookmark will save
your context as you work. Switch between them as you see fit.

*** Automatic bookmark loading

To automatically load some or all bufferlo bookmarks at Emacs startup
time (bufferlo uses ~window-setup-hook~ to load bookmarks after your
init.el has completed to maximize the chances for successful loading):
#+begin_src emacs-lisp
  (setq bufferlo-bookmarks-load-at-emacs-startup 'noload) ; inhibit loading at startup (default)
  (setq bufferlo-bookmarks-load-at-emacs-startup 'pred) ; load bookmark names that match your predicates
  (setq bufferlo-bookmarks-load-at-emacs-startup 'all) ; load all bufferlo bookmarks
#+end_src

To make a new frame to hold restored tabs at startup, or reuse the initial frame:
#+begin_src emacs-lisp
  (setq bufferlo-bookmarks-load-at-emacs-startup-tabs-make-frame nil) ; reuse the initial frame (default)
  (setq bufferlo-bookmarks-load-at-emacs-startup-tabs-make-frame t) ; make a new frame
#+end_src

Example auto-load predicate:
#+begin_src emacs-lisp
  (setq 'bufferlo-bookmarks-load-predicate-functions #'bufferlo-bookmarks-load-all-p) ; loads all bookmarks

  (defun my/bufferlo-bookmarks-load-p (bookmark-name)
    "Auto load bufferlo bookmarks that contain \"=al\"for autoload"
    (string-match-p (rx "=al") bookmark-name))
  (add-hook 'bufferlo-bookmarks-load-predicate-functions #'my/bufferlo-bookmarks-load-p)
#+end_src

If you have configured bufferlo to load bookmarks at Emacs startup,
you can inhibit bookmark loading without changing your configuration
by either using the command line or a semaphore file in your
~user-emacs-directory~:
#+begin_src shell
$ emacs --bufferlo-noload
$ touch ~/.emacs.d/bufferlo-noload # remove it to reenable automatic loading
#+end_src

*** Filter saved bookmark buffers

By default, bufferlo will save all buffers in the local frame/tab
buffer list, using Emacs facilities to bookmark what's bookmarkable
for restoration. You might want to exclude transient buffers
~*Completions*~ or ~*Help*~ or those which may not have bookmark
support such as ~*shell*~ buffers. To do that, combine the following
two variables, the first to exclude what you want to filter, and the
second to ensure that the buffers you want to keep from the first
filter are added back. For example:
#+begin_src emacs-lisp
  (setq bufferlo-bookmark-buffers-exclude-filters
        (list
         (rx bos " " (1+ anything)) ; ignores "invisible" buffers; e.g., " *Minibuf...", " markdown-code-fontification:..."
         (rx bos "*" (1+ anything) "*") ; ignores "special" buffers; e.g;, "*Messages*", "*scratch*", "*occur*"
         ))

  (setq bufferlo-bookmark-buffers-include-filters
        (list
         (rx bos "*shell*") ; if you have shell bookmark support
         (rx bos "*" (1+ anything) "-shell*") ; project.el shell buffers
         (rx bos "*eshell*")
         (rx bos "*" (1+ anything) "-eshell*") ; project.el eshell buffers
         ))
#+end_src

*** Bookmark duplicates

Bufferlo can discourage you from using multiple duplicate active
bookmarks, but does not prevent them. Having duplicates is confusing
and they present a race condition when saving as all copies will be
saved, overwriting one another without regard to ordering, with the
last one saved winning the race.

Note: The options to prevent duplicates are not enabled by default to
maintain backward compatibility with previous versions of bufferlo,
but they are likely to be enabled by default in the future.

#+begin_src emacs-lisp
  (setq bufferlo-bookmarks-save-duplicates-policy 'prompt) ; default
  (setq bufferlo-bookmarks-save-duplicates-policy 'allow) ; old default behavior
  (setq bufferlo-bookmarks-save-duplicates-policy 'disallow) ; even better
#+end_src

*** Changing bookmark types

You can save an active bookmark of one type, as another type. An active frame
bookmark can be saved as a tab or set bookmark under the same name as the
active one. Similarly, a tab can be saved as a frame or a set, and a set can
be saved as a tab or a frame.

Bufferlo will warn you if you are about to overwrite a bookmark of a different
type with the same name. If you choose to proceed, the active open
bookmark will be cleared in your session.

If you save an active tab or frame bookmark as a set, existing set bookmarks
are scanned to see if they contain the tab or frame bookmark, and bufferlo
will warn you if one does. Note: When you load an existing sets that contain
stale references to bookmarks now saved as a set, bufferlo skip those entries.

*** Save current, other, or all frame bookmarks

If you use batch or automatic saving, this option lets you control
which frames' bookmarks are saved. For example, some prefer not to
have their current working set be saved unless and until they choose.

#+begin_src emacs-lisp
  (setq bufferlo-bookmarks-save-frame-policy 'all) ; default
  (setq bufferlo-bookmarks-save-frame-policy 'other) ; saves unselected frames' bookmarks
  (setq bufferlo-bookmarks-save-frame-policy 'current) ; saves only the current frame bookmarks
#+end_src

*** Frame bookmark options

Refine these options to suit your workflow as you gain experience with
bufferlo. Refer to each option's documentation for additional
settings.

#+begin_src emacs-lisp
  ;; make a new frame, or reuse the current frame to hold loaded frame bookmarks
  (setq bufferlo-bookmark-frame-load-make-frame nil) ; reuse the current frame
  (setq bufferlo-bookmark-frame-load-make-frame t) ; default is nil for backward compatibility
  (setq bufferlo-bookmark-frame-load-make-frame 'restore-geometry) ; make a new frame and restore geometry
  (setq bufferlo-bookmark-frame-load-make-frame 'reuse-restore-geometry) ; reuse the current frame and restore geometry
#+end_src
#+begin_src emacs-lisp
  ;; policy when loading onto an already bookmarked frame
  (setq bufferlo-bookmark-frame-load-policy 'prompt) ; default
  (setq bufferlo-bookmark-frame-load-policy 'replace-frame-retain-current-bookmark) ; old default behavior
  (setq bufferlo-bookmark-frame-load-policy 'replace-frame-adopt-loaded-bookmark)
  (setq bufferlo-bookmark-frame-load-policy 'merge) ; best selected via prompting to merge new tabs into the existing frame
#+end_src
#+begin_src emacs-lisp
  ;; allow duplicate active frame bookmarks in the Emacs session
  (setq bufferlo-bookmark-frame-duplicate-policy 'prompt) ; default
  (setq bufferlo-bookmark-frame-duplicate-policy 'allow) ; old default behavior
  (setq bufferlo-bookmark-frame-duplicate-policy 'clear) ; silently clear the loaded frame bookmark
  (setq bufferlo-bookmark-frame-duplicate-policy 'clear-warn) ; clear the loaded frame bookmark with a message
  (setq bufferlo-bookmark-frame-duplicate-policy 'ignore) ; bypass loading the duplicate frame bookmark
  (setq bufferlo-bookmark-frame-duplicate-policy 'raise) ; do not load, raise the existing frame
#+end_src
Note: 'raise is considered to act as 'clear by bookmark set loading.

#+begin_src emacs-lisp
  ;; store frame names in their bookmarks, and restore them when loading
  (setq bufferlo-bookmark-frame-persist-frame-name t) ; default nil
#+end_src
#+begin_src emacs-lisp
  ;; control the restoration of tab groups
  (setq bufferlo-bookmark-restore-tab-groups nil) ; do not restore tab groups, the default
  (setq bufferlo-bookmark-restore-tab-groups t) ; restore tab groups
  (setq bufferlo-bookmark-restore-tab-groups 'frames) ; restore tab groups only for frame bookmarks
#+end_src

*** Tab bookmark options

Refine these options to suit your workflow as you gain experience with
bufferlo. Refer to each option's documentation for additional
settings.

#+begin_src emacs-lisp
   ;; make a new frame when loading a a batch of tab bookmarks
   (setq bufferlo-bookmarks-load-tabs-make-frame nil) ; default, it reuses the current frame
   (setq bufferlo-bookmarks-load-tabs-make-frame t) ; make a new tab when loading a batch of tab bookmarks
#+end_src
#+begin_src emacs-lisp
  ;; load a tab bookmark replacing the current tab or making a new tab
  (setq bufferlo-bookmark-tab-replace-policy 'replace) ; default (backward compatible behavior)
  (setq bufferlo-bookmark-tab-replace-policy 'new)
#+end_src
#+begin_src emacs-lisp
  ;; allow duplicate active tab bookmarks in the Emacs session
  (setq bufferlo-bookmark-tab-duplicate-policy 'prompt) ; default
  (setq bufferlo-bookmark-tab-duplicate-policy 'allow) ; old default behavior
  (setq bufferlo-bookmark-tab-duplicate-policy 'clear) ; silently clear the loaded tab bookmark
  (setq bufferlo-bookmark-tab-duplicate-policy 'clear-warn) ; clear the loaded tab bookmark with a message
  (setq bufferlo-bookmark-tab-duplicate-policy 'ignore) ; bypass loading the duplicate tab bookmark
  (setq bufferlo-bookmark-tab-duplicate-policy 'raise) ; do not load, raise the existing frame/tab
#+end_src
Note: 'raise is considered to act as 'clear by bookmark set loading.
#+begin_src emacs-lisp
  ;; allow inferior tab bookmark on a bookmarked frame (Note: frame bookmarks supersede tab bookmarks when saving)
  (setq bufferlo-bookmark-tab-in-bookmarked-frame-policy 'prompt) ; default
  (setq bufferlo-bookmark-tab-in-bookmarked-frame-policy 'allow) ; old default behavior
  (setq bufferlo-bookmark-tab-in-bookmarked-frame-policy 'clear) ; silently clear the loaded tab bookmark
  (setq bufferlo-bookmark-tab-in-bookmarked-frame-policy 'clear-warn) ; clear the loaded tab bookmark with a message
#+end_src
#+begin_src emacs-lisp
  ;; handle buffer bookmarks that could not be restored
  (setq bufferlo-bookmark-tab-failed-buffer-policy nil) ; ignore; the default
  (setq bufferlo-bookmark-tab-failed-buffer-policy 'placeholder) ; placeholder buffer and buffer name
  (setq bufferlo-bookmark-tab-failed-buffer-policy 'placeholder-orig) ; placeholder buffer with original buffer name
  (setq bufferlo-bookmark-tab-failed-buffer-policy "*scratch*") ; default to a specific buffer
  (setq bufferlo-bookmark-tab-failed-buffer-policy #'my/failed-bookmark-handler) ; function to call that returns a buffer
#+end_src
#+begin_src emacs-lisp
  ;; restore the tab's explicit name (if set)
  (setq bufferlo-bookmark-tab-restore-explicit-name t) ; default
#+end_src
#+begin_src emacs-lisp
  ;; control the restoration of tab groups
  (setq bufferlo-bookmark-restore-tab-groups nil) ; do not restore tab groups, the default
  (setq bufferlo-bookmark-restore-tab-groups t) ; restore tab groups
  (setq bufferlo-bookmark-restore-tab-groups 'tabs) ; restore tab groups only for tab bookmarks
#+end_src

*** Bookmark set options

Refine these options to suit your workflow as you gain experience with
bufferlo. Refer to each option's documentation for additional
settings.

#+begin_src emacs-lisp
  ;; frame geometry restoration policy
  (setq bufferlo-set-restore-geometry-policy 'all) ; restore frame and tab-frame geometries; the default
  (setq bufferlo-set-restore-geometry-policy 'frames) ; restore only frame geometries
  (setq bufferlo-set-restore-geometry-policy 'tab-frames) ; restore only tab-frame geometries
#+end_src

The following option is useful for auto-loading bookmark sets at
startup time or overlaying constituent tabs in the frame from which a
bookmark set is loaded.

#+begin_src emacs-lisp
  ;; make a new frame when loading a a batch of tab bookmarks
  (setq bufferlo-set-restore-tabs-reuse-init-frame 'reuse) ; reuse the existing first frame; the default
  (setq bufferlo-set-restore-tabs-reuse-init-frame 'reuse-reset-geometry) ; like 'reuse but also alters the reused frame's geometry
  (setq bufferlo-set-restore-tabs-reuse-init-frame nil) ; always make new frames
#+end_src

#+begin_src emacs-lisp
  ;; ignore already-active bufferlo bookmarks when loading a bookmark set
  (setq bufferlo-set-restore-ignore-already-active nil) ; prompt for each duplicate bookmark; the default
  (setq bufferlo-set-restore-ignore-already-active 'prompt) ; prompt to ignore already-active bookmarks in bulk
  (setq bufferlo-set-restore-ignore-already-active 'ignore) ; always ignore already-active bookmarks
#+end_src

*** Bookmark handler hooks

You can add your own functions to the following abnormal hooks to be
called upon successful loading of tab, frame, and set bookmarks. See
the docstrings for each function for its calling conventions.

Example: You could use a tab handler function to set the tab-bar group
for each loaded tab to its source bookmark name. While tab-bar does
have ~tab-bar-tab-post-open-functions~, the bookmark context will not
be available when those functions are called.

#+begin_src emacs-lisp
  (add-hook 'bufferlo-bookmark-tab-handler-functions #'tab-bookmark-fun)
  (add-hook 'bufferlo-bookmark-frame-handler-functions #'frame-bookmark-fun)
  (add-hook 'bufferlo-bookmark-set-handler-functions #'set-bookmark-fun)
#+end_src

*** Frame geometry options

Bufferlo provides wrappers around Emacs frame functions to provide
more precision. This is due to issues that affect ~make-frame~ and
hence ~frameset-restore~. One bug preventing pixel-level precision was
reported and fixed for Emacs 31.

Frames stored in bufferlo frame bookmarks have their geometries stored
individually and are recreated on demand. Bookmark sets frame
collections are implemented via ~frameset-save~ and are restored by
Emacs en masse.

Frame bookmarks saved via Emacs tty will not store a frame geometry
(none available on tty). Conversely, frame bookmarks saved via GUI and
restored on tty will ignore frame geometry.

Note: See below to adjust ~bufferlo-frame-sleep-for~ for your window
manager.

Note: Not much testing has been done in hybrid tty/GUI environments
using ~emacsclient~, or with multi-display setups where frames may be
expected to be restored on their originating displays.

#+begin_src emacs-lisp
  ;; function to determine a frame's pixelwise geometry (it is not
  ;; likely you will need to replace this--but is provided just in case)
  (setq bufferlo-frame-geometry-function #'bufferlo-frame-geometry-default) ; the default uses text-width and text-height
  (setq bufferlo-frame-geometry-function #'my/bufferlo-frame-geometry) ; or your own
#+end_src
#+begin_src emacs-lisp
  ;; function to set a frame's pixelwise geometry (it is not likely you
  ;; will need to replace this--but is provided just in case)
  (setq bufferlo-set-frame-geometry-function #'bufferlo-set-frame-geometry-default)
  (setq bufferlo-set-frame-geometry-function #'my/bufferlo-set-frame-geometry) ; or your own
#+end_src
#+begin_src emacs-lisp
  ;; seconds to sleep after each frame parameter change that requires
  ;; external window manager cooperation.
  (setq bufferlo-frame-sleep-for 0) ; the default, which seems to work on macOS
  (setq bufferlo-frame-sleep-for 0.3) ; seems to work for GTK/GNOME
#+end_src

#+begin_src emacs-lisp
  ;; methodology for bookmark-set frameset geometry restoration
  (setq bufferlo-frameset-restore-geometry 'bufferlo) ; the pixel-level precision default
  (setq bufferlo-frameset-restore-geometry 'native) ; uses `frameset-restore' geometry handling (buggy pre Emacs 31)
  (setq bufferlo-frameset-restore-geometry nil) ; inhibit frame geometry restoration
#+end_src
#+begin_src emacs-lisp
  ;; inhibit additional frame parameter symbols from being stored by `frameset-save'
  (setq bufferlo-frameset-save-filter nil)
  (setq bufferlo-frameset-save-filter '(my:frame-id ; practical example
                                        zoom--frame-snapshot))
#+end_src
#+begin_src emacs-lisp
    ;; inhibit additional frame parameter symbols from being restored by `frameset-restore'
    (setq bufferlo-frameset-restore-filter nil)
#+end_src
#+begin_src emacs-lisp
  ;; you can override bufferlos `frameset-restore' wrapper should you need to
  (setq bufferlo-frameset-restore-function #'bufferlo-frameset-restore-default) ; the default
  ;; a practical example that inhibits user-configured
  ;; `after-make-frame-functions' frame maximization by let-binding
  ;; my:frame-maximize to nil allowing `frameset-restore' and bufferlo
  ;; to control restored frame geometry.
  (defun my/bufferlo-frameset-restore-function (frameset)
    (let ((my:frame-maximize nil))
      (bufferlo-frameset-restore-default frameset)))
  (setq bufferlo-frameset-restore-function #'my/bufferlo-frameset-restore-function)
#+end_src
#+begin_src emacs-lisp
  (setq bufferlo-frameset-restore-parameters-function #'bufferlo-frameset-restore-parameters-default) ; default
  ;; a practical example where Emacs Linux/GTK behaves differently vs. macOS
  (defun my/bufferlo-frameset-restore-parameters ()
    "Function to create parameters for `frameset-restore', which see."
    (cond (my:on-linux-gnome
           (list :reuse-frames nil
                 :force-display nil ; bufferlo defaults to t which works on macOS
                 :force-onscreen (display-graphic-p)
                 :cleanup-frames nil))
          (t
           (bufferlo-frameset-restore-parameters-default))))
  (setq bufferlo-frameset-restore-parameters-function #'my/bufferlo-frameset-restore-parameters)
#+end_src

*** Bookmark addenda

Emacs bookmarks do not store your file or buffer contents, only
references to your files and buffers. Many Emacs modes support Emacs
bookmarks and can be saved and recalled including ~eshell~ and
~magit-status~ buffers. The state of non-bookmarkable buffers is not
saved. However, during bookmark saving, they are included in the
bookmark record. Emacs 31 has support for ~shell-mode~ local and
remote buffer bookmarks.

Restoring bookmarks correctly handles renamed buffers with unchanged
file association (e.g., when Emacs had to "uniquify" buffer names).

If files are deleted between Emacs sessions and a bookmarked buffer
cannot be restored, after loading a bookmark with a missing file, a
message similar to this can be found in your ~*Messages*~ buffer:

~Bufferlo bookmark: Could not restore file.txt (error (bookmark-error-no-filename stringp /etc/file.txt))~

Please note: Emacs ~bookmark-jump-other-frame~ and
~bookmark-jump-other-window~ commands are not compatible with bufferlo
bookmarks. A future version of bufferlo might wrap these functions for
convenience to either provide a warning or provide alternative jump
functionality.

It can be convenient to share bookmark files among your computers or
among colleagues. Bookmarks can be made more "portable" with the
following assumptions:

- You share an Emacs configuration including packages, mode settings,
  etc.

- You share a directory hierarchy for files in common such as
  programming or writing projects on which you collaborate.

** Mode line

Bufferlo's default mode-line indicator shows the currently active
frame- and/or tab-bookmark name and also indicates if at least one
bookmark set is active.

- If you prefer iconic mode-line prefixes, set one like this:
#+begin_src emacs-lisp
   (setq bufferlo-mode-line-prefix "🐮") ; bufferlos are cows
   (setq bufferlo-mode-line-prefix "🐃") ; some are water bufferlos
   (setq bufferlo-mode-line-prefix "Bfl") ; the text default
#+end_src
- To disable bufferlo's mode-line or provide your own custom mode-line function:
#+begin_src emacs-lisp
  (setq bufferlo-mode-line nil) ; disable the bufferlo mode-line
  (setq bufferlo-mode-line #'my/bufferlo-mode-line) ; or use your own
#+end_src
- To control the appearance of other mode-line features:
#+begin_src emacs-lisp
  (setq bufferlo-mode-line-set-active-prefix "Ⓢ")
  (setq bufferlo-mode-line-frame-prefix "Ⓕ")
  (setq bufferlo-mode-line-tab-prefix "Ⓣ")
  (setq bufferlo-mode-line-left-prefix nil) ; default "[" similar to flymake
  (setq bufferlo-mode-line-right-suffix nil) ; default "]"
#+end_src
- To control mode-line faces:
#+begin_src emacs-lisp
  (set-face-attribute 'bufferlo-mode-line-face nil
                      :box '(:line-width (-1 . -1) :color "#8aca9f")
                      :height 0.85)
  ;; below inherit bufferlo-mode-line-face
  (set-face-attribute 'bufferlo-mode-line-frame-bookmark-face nil
                      :foreground "#8aca0f")
  (set-face-attribute 'bufferlo-mode-line-tab-bookmark-face nil
                      :foreground "#00ffff")
  (set-face-attribute 'bufferlo-mode-line-set-face nil
                      :foreground "#000fff")
#+end_src

** Menu bar

Bufferlo enables its menu bar entry by default to encourage feature
discovery and menu-item entries are adorned with key mappings from
your configuration.

Note: Due to a limitation in Emacs where it does not reference key
bindings of commands via aliases, you must provide key mappings on
bufferlo's aliased commands, as the menu is defined in alias terms. We
default to aliases to reduce the text displayed by ~which-key-mode~ to
a readable width vs. fully-qualified command names.

#+begin_src emacs-lisp
  ;; To control the menu bar visibility before package initialization
  (setq bufferlo-menu-bar-show t) ; the default
  (setq bufferlo-menu-bar-show nil)
#+end_src

#+begin_src emacs-lisp
  ;; bufferlo menu buffer window behavior
  (setq bufferlo-menu-bar-list-buffers 'simple) ; show buffer lists using `Buffer-menu-mode'
  (setq bufferlo-menu-bar-list-buffers 'ibuffer) ; show buffer lists using `ibuffer'
  (setq bufferlo-menu-bar-list-buffers 'both) ; show both options; the default
  (setq bufferlo-menu-bar-list-buffers nil) ; show neither
#+end_src

** Initial buffer

By default, the currently-active buffer is shown in a newly created
tab so this buffer inevitably ends up in the new tab's local buffer
list. You can change the initial buffer by customizing
~tab-bar-new-tab-choice~:
#+begin_src emacs-lisp
  (setq tab-bar-new-tab-choice "*scratch*") ; or another buffer of your choice
#+end_src
This lets new tabs always start with the ~*scratch*~ buffer.

You can also create a local scratch buffer for each tab:
#+begin_src emacs-lisp
  (setq tab-bar-new-tab-choice #'bufferlo-create-local-scratch-buffer)
#+end_src
You can customize the name of the local scratch buffers by setting
~bufferlo-local-scratch-buffer-name~.

The same can be achieved for new frames. Use this to set the scratch
buffer as the initial buffer for new frames:
#+begin_src emacs-lisp
  (add-hook 'after-make-frame-functions #'bufferlo-switch-to-scratch-buffer)
#+end_src

Alternatively, create a new local scratch buffer for new frames:
#+begin_src emacs-lisp
  (add-hook 'after-make-frame-functions #'bufferlo-switch-to-local-scratch-buffer)
#+end_src

You can also set an arbitrary buffer as the initial frame buffer:
#+begin_src emacs-lisp
  (defun my/set-initial-frame-buffer (frame)
    (with-selected-frame frame
      (switch-to-buffer "<BUFFER_NAME>")))
  (add-hook 'after-make-frame-functions #'my/set-initial-frame-buffer)
#+end_src

** Bufferlo anywhere

"Bufferlo anywhere" lets you have bufferlo's frame/tab-local buffer
list anywhere you like, i.e. in any command with interactive buffer
selection (via ~read-buffer~, e.g., ~diff-buffers~, ~make-indirect-buffer~,
...) -- not just in the switch-buffer facilities.  You can configure
which commands use bufferlo's local list and which use the global
list.

Enable ~bufferlo-anywhere-mode~ to use bufferlo's local buffer list by
default.  Customize ~bufferlo-anywhere-filter~ and
~bufferlo-anywhere-filter-type~ to restrict the commands that use the
local list.  With the command prefix ~bufferlo-anywhere-disable-prefix~,
you can temporarily disable ~bufferlo-anywhere-mode~ for the next
command.

Instead of the minor mode, you can use the command prefix
~bufferlo-anywhere-enable-prefix~, which only temporarily enables
bufferlo's local buffer list for the next command.

* Package integration

** Consult

You can integrate bufferlo with ~consult-buffer~ in two ways.

The first can display both local and non-local bufferlo buffers in the
~consult~ buffer list.

The second ~consult~ integration is simpler to configure, but a little more
cumbersome when you want to select non-local buffers.

Don't configure both methods, pick one or the other to avoid consult narrowing
keys interfering with one another.

This is an example of the first configuration method:
#+begin_src emacs-lisp
  (defvar my:bufferlo-consult--source-local-buffers
    (list :name "Bufferlo Local Buffers"
          :narrow   ?l
          :category 'buffer
          :face     'consult-buffer
          :history  'buffer-name-history
          :state    #'consult--buffer-state
          :default  t
          :items    (lambda () (consult--buffer-query
                                :predicate #'bufferlo-local-buffer-p
                                :sort 'visibility
                                :as #'buffer-name)))
    "Local Bufferlo buffer candidate source for `consult-buffer'.")

  (defvar my:bufferlo-consult--source-other-buffers
    (list :name "Bufferlo Other Buffers"
          :narrow   ?o
          :category 'buffer
          :face     'consult-buffer
          :history  'buffer-name-history
          :state    #'consult--buffer-state
          :items    (lambda () (consult--buffer-query
                                :predicate #'bufferlo-non-local-buffer-p
                                :sort 'visibility
                                :as #'buffer-name)))
    "Non-local Bufferlo buffer candidate source for `consult-buffer'.")

  ;; add in the reverse order of display preference
  (add-to-list 'consult-buffer-sources 'my:bufferlo-consult--source-other-buffers)
  (add-to-list 'consult-buffer-sources 'my:bufferlo-consult--source-local-buffers)
#+end_src

[[./img/consult1.svg]]
Fig.1: All buffers are shown; the local buffers are grouped separately.

You can also configure ~consult-buffer~ to hide the non-local buffers by default:
#+begin_src emacs-lisp
  (defvar my:bufferlo-consult--source-all-buffers
    (list :name "Bufferlo All Buffers"
          :narrow   ?a
          :hidden   t
          :category 'buffer
          :face     'consult-buffer
          :history  'buffer-name-history
          :state    #'consult--buffer-state
          :items    (lambda () (consult--buffer-query
                                :sort 'visibility
                                :as #'buffer-name)))
    "All Bufferlo buffer candidate source for `consult-buffer'.")

  (defvar my:bufferlo-consult--source-local-buffers
    (list :name "Bufferlo Local Buffers"
          :narrow   ?l
          :category 'buffer
          :face     'consult-buffer
          :history  'buffer-name-history
          :state    #'consult--buffer-state
          :default  t
          :items    (lambda () (consult--buffer-query
                                :predicate #'bufferlo-local-buffer-p
                                :sort 'visibility
                                :as #'buffer-name)))
    "Local Bufferlo buffer candidate source for `consult-buffer'.")

  ;; add in the reverse order of display preference
  (add-to-list 'consult-buffer-sources #'consult--source-hidden-buffer)
  (add-to-list 'consult-buffer-sources #'my:bufferlo-consult--source-all-buffers)
  (add-to-list 'consult-buffer-sources #'my:bufferlo-consult--source-local-buffers)
#+end_src

[[./img/consult2.svg]]
Fig.2: By entering 'a'+<space>, the global buffer list is shown ("All Buffers").

A good alternative is to bind space to "All Buffers" (via ~:narrow
32~). By default, a space character prefix is used for hidden buffers
(~consult--source-hidden-buffer~). If you still need the hidden buffer
list, you can make a new source for it, for example, with period as
the narrowing key (~:narrow ?.~).

This is the second, simpler ~consult~ integration method using its native
buffer filter. This shows local bufferlo buffers as the default ~consult~
buffer list. To view non-local buffers, "narrow" the list using key sequence
"< o" (where "<" is the default consult narrowing character) and the ~consult~
buffer list switches to to non-local buffers. To return to the local buffer
list, enter the key sequence "< <" to undo list narrowing. See [[https://github.com/minad/consult?tab=readme-ov-file#narrowing-and-grouping][Consult
Narrowing and Grouping]] and [[https://github.com/minad/consult?tab=readme-ov-file#virtual-buffers][Virtual Buffers (aka Buffer Switching)]].

#+begin_src emacs-lisp
  ;; This defaults to #'buffer-list which shows all buffers.
  (setq consult-buffer-list-function #'bufferlo-local-buffers)
#+end_src

** Ivy

You can also integrate bufferlo with ~ivy~.

#+begin_src emacs-lisp
  (defun ivy-bufferlo-switch-buffer ()
    "Switch to another local buffer.
  If the prefix arument is given, include all buffers."
      (interactive)
      (if current-prefix-arg
          (ivy-switch-buffer)
        (ivy-read "Switch to local buffer: " #'internal-complete-buffer
                  :predicate (lambda (b) (bufferlo-local-buffer-p (cdr b)))
                  :keymap ivy-switch-buffer-map
                  :preselect (buffer-name (other-buffer (current-buffer)))
                  :action #'ivy--switch-buffer-action
                  :matcher #'ivy--switch-buffer-matcher
                  :caller 'ivy-switch-buffer)))
#+end_src

** shell-mode bookmarks

We may post some code on the bufferlo wiki illustrate how to enable
bookmarks for ~shell-mode~ buffers. We will help contribute this
feature to Emacs 31.

** save-place-mode

If you use ~save-place-mode~, and prefer to *always* use its
buffer-position history, overriding bufferlo's saved bookmark
positions, add this to your configuration:

#+begin_src emacs-lisp
  (setq bufferlo-bookmark-inhibit-bookmark-point t)
#+end_src

This takes effect when saving or updating a bufferlo bookmark.
Previously stored bufferlo bookmarks with an embedded point will
remain in force until they are saved if this policy is set to t.

** Complete configuration sample

#+begin_src emacs-lisp
  (global-unset-key (kbd "C-z")) ; free C-z to use as a prefix key

  (use-package bufferlo
    :demand t
    :after (ibuffer consult) ; also mark these :demand t or use explicit require
    :bind
    (
     ;; buffer / ibuffer
     ("C-z C-b" . bufferlo-ibuffer)
     ("C-z M-C-b" . bufferlo-ibuffer-orphans)
     ("C-z b -" . bufferlo-remove)
     ;; general bookmark (interactive)
     ("C-z b l" . bufferlo-bms-load)
     ("C-z b s" . bufferlo-bms-save)
     ("C-z b c" . bufferlo-bms-close)
     ("C-z b r" . bufferlo-bm-raise)
     ;; dwim frame or tab bookmarks
     ("C-z d s" . bufferlo-bm-save)
     ("C-z d l" . bufferlo-bm-load)
     ("C-z d 0" . bufferlo-bm-close)
     ;; tabs
     ("C-z t s" . bufferlo-bm-tab-save)               ; save
     ("C-z t u" . bufferlo-bm-tab-save-curr)          ; update
     ("C-z t l" . bufferlo-bm-tab-load)               ; load
     ("C-z t r" . bufferlo-bm-tab-load-curr)          ; reload
     ("C-z t 0" . bufferlo-bm-tab-close-curr)         ; kill
     ;; frames
     ("C-z f s" . bufferlo-bm-frame-save)             ; save
     ("C-z f u" . bufferlo-bm-frame-save-curr)        ; update
     ("C-z f l" . bufferlo-bm-frame-load)             ; load
     ("C-z f r" . bufferlo-bm-frame-load-curr)        ; reload
     ("C-z f m" . bufferlo-bm-frame-load-merge)       ; merge
     ("C-z f 0" . bufferlo-bm-frame-close-curr)       ; kill
     ;; sets
     ("C-z s s" . bufferlo-set-save)                  ; save
     ("C-z s u" . bufferlo-set-save-curr)             ; update
     ("C-z s +" . bufferlo-set-add)                   ; add bookmark
     ("C-z s =" . bufferlo-set-add)                   ; add bookmark
     ("C-z s -" . bufferlo-set-remove)                ; remove bookmark
     ("C-z s l" . bufferlo-set-load)                  ; load
     ("C-z s 0" . bufferlo-set-close)                 ; kill
     ("C-z s c" . bufferlo-set-clear)                 ; clear
     ("C-z s L" . bufferlo-set-list)                  ; list contents of selected active sets
     )
    :init
    ;; these must be set before the bufferlo package is loaded
    (setq bufferlo-menu-bar-show t)
    (setq bufferlo-menu-bar-list-buffers 'ibuffer)
    (setq bufferlo-prefer-local-buffers 'tabs)
    (setq bufferlo-ibuffer-bind-local-buffer-filter t)
    (setq bufferlo-ibuffer-bind-keys t)
    :config
    (setq bufferlo-mode-line-prefix "🐃") ; "🐮"
    (setq bufferlo-mode-line-set-active-prefix "Ⓢ")
    (setq bufferlo-mode-line-frame-prefix "Ⓕ")
    (setq bufferlo-mode-line-tab-prefix "Ⓣ")
    (setq bufferlo-mode-line-left-prefix nil)
    (setq bufferlo-mode-line-right-suffix nil)
    (setq switch-to-prev-buffer-skip-regexp
          (concat "\\` *"
                  "\\(\\*\\(" ; earmuffs
                  (mapconcat #'identity
                             '("Messages"
                               "Buffer List"
                               "Ibuffer"
                               "Local Buffer List" ; bufferlo
                               "scratch"
                               "Occur"
                               "Completions"
                               "Help"
                               "Warnings"
                               "Apropos"
                               "Bookmark List"
                               "Async-native-compile-log"
                               "Flymake log"
                               "ruff-format errors"
                               "vc-diff")
                             "\\|")
                  "\\)\\*\\)"
                  "\\|" (rx "*" (1+ anything) " Ibuffer*")
                  "\\|" (rx "*helpful " (1+ anything) "*")
                  "\\|" (rx "*tramp" (1+ anything) "*")
                  "\\|" (rx "magit" (* anything) ": " (1+ anything))
                  "\\'"))
    (setq bufferlo-kill-buffers-prompt t)
    (setq bufferlo-kill-modified-buffers-policy 'retain-modified-kill-without-file-name) ; nil 'retain-modified 'retain-modified-kill-without-file-name 'kill-modified
    (setq bufferlo-bookmark-inhibit-bookmark-point t)
    (setq bufferlo-delete-frame-kill-buffers-prompt t)
    (setq bufferlo-bookmark-frame-save-on-delete 'when-bookmarked)
    (setq bufferlo-bookmark-tab-save-on-close 'when-bookmarked)
    (setq bufferlo-close-tab-kill-buffers-prompt t)
    (setq bufferlo-bookmark-frame-load-make-frame 'restore-geometry)
    (setq bufferlo-bookmark-frame-load-policy 'prompt)
    (setq bufferlo-bookmark-frame-duplicate-policy 'prompt)
    (setq bufferlo-bookmark-frame-persist-frame-name nil)
    (setq bufferlo-bookmark-tab-replace-policy 'new)
    (setq bufferlo-bookmark-tab-duplicate-policy 'prompt)
    (setq bufferlo-bookmark-tab-in-bookmarked-frame-policy 'prompt)
    (setq bufferlo-bookmark-tab-failed-buffer-policy 'placeholder)
    (setq bufferlo-bookmarks-save-duplicates-policy 'prompt)
    (setq bufferlo-bookmarks-save-frame-policy 'all)
    (setq bufferlo-bookmarks-load-tabs-make-frame t)
    (setq bufferlo-bookmarks-save-at-emacs-exit 'all)
    (setq bufferlo-bookmarks-load-at-emacs-startup 'pred)
    (setq bufferlo-bookmarks-load-at-emacs-startup-tabs-make-frame nil)
    (setopt bufferlo-bookmarks-auto-save-interval (* 60 5)) ; 5 minutes
    (setq bufferlo-bookmarks-auto-save-messages 'saved)
    (setq bufferlo-set-restore-geometry-policy 'all)
    (setq bufferlo-set-restore-tabs-reuse-init-frame 'reuse) ; nil 'reuse 'reuse-reset-geometry
    (setq bufferlo-set-restore-ignore-already-active 'prompt) ; nil 'prompt 'ignore
    (setq bufferlo-frameset-restore-geometry 'bufferlo)
    (setq bufferlo-frame-geometry-function #'bufferlo-frame-geometry-default)
    (setq bufferlo-frame-sleep-for 0.3)

    (setq bookmark-bmenu-type-column-width 12) ; supported in Emacs 31 (innocuous on earlier versions)

    (setq bufferlo-bookmark-buffers-exclude-filters
          (list
           (rx bos " " (1+ anything)) ; ignores "invisible" buffers; e.g., " *Minibuf...", " markdown-code-fontification:..."
           (rx bos "*" (1+ anything) "*") ; ignores "special" buffers; e.g;, "*Messages*", "*scratch*", "*occur*"
           ))

    (setq bufferlo-bookmark-buffers-include-filters
          (list
           (rx bos "*shell*") ; comment out shells if you do not have bookmark support
           (rx bos "*" (1+ anything) "-shell*") ; project.el shell buffers
           (rx bos "*eshell*")
           (rx bos "*" (1+ anything) "-eshell*") ; project.el eshell buffers
           ))

    (defun my/bufferlo-bookmarks-save-p (bookmark-name)
      (string-match-p (rx "=as") bookmark-name))
    (setq bufferlo-bookmarks-save-predicate-functions nil) ; clear the save-all predicate
    (add-hook 'bufferlo-bookmarks-save-predicate-functions #'my/bufferlo-bookmarks-save-p)

    (defun my/bufferlo-bookmarks-load-p (bookmark-name)
      (string-match-p (rx "=al") bookmark-name))
    (add-hook 'bufferlo-bookmarks-load-predicate-functions #'my/bufferlo-bookmarks-load-p)

    (defvar my:bufferlo-consult--source-local-buffers
      (list :name "Bufferlo Local Buffers"
            :narrow   ?l
            :category 'buffer
            :face     'consult-buffer
            :history  'buffer-name-history
            :state    #'consult--buffer-state
            :default  t
            :items    (lambda () (consult--buffer-query
                                  :predicate #'bufferlo-local-buffer-p
                                  :sort 'visibility
                                  :as #'buffer-name)))
      "Local Bufferlo buffer candidate source for `consult-buffer'.")

    (defvar my:bufferlo-consult--source-other-buffers
      (list :name "Bufferlo Other Buffers"
            :narrow   ?o
            :category 'buffer
            :face     'consult-buffer
            :history  'buffer-name-history
            :state    #'consult--buffer-state
            :items    (lambda () (consult--buffer-query
                                  :predicate #'bufferlo-non-local-buffer-p
                                  :sort 'visibility
                                  :as #'buffer-name)))
      "Non-local Bufferlo buffer candidate source for `consult-buffer'.")

    (defvar my:bufferlo-consult--source-all-buffers
      (list :name "Bufferlo All Buffers"
            :narrow   ?a
            :hidden   t
            :category 'buffer
            :face     'consult-buffer
            :history  'buffer-name-history
            :state    #'consult--buffer-state
            :items    (lambda () (consult--buffer-query
                                  :sort 'visibility
                                  :as #'buffer-name)))
      "All Bufferlo buffer candidate source for `consult-buffer'.")

    ;; add in the reverse order of display preference
    (add-to-list 'consult-buffer-sources 'my:bufferlo-consult--source-all-buffers)
    (add-to-list 'consult-buffer-sources 'my:bufferlo-consult--source-other-buffers)
    (add-to-list 'consult-buffer-sources 'my:bufferlo-consult--source-local-buffers)

    (bufferlo-mode)
    (bufferlo-anywhere-mode))
#+end_src

*** CRM prompt enhancement

Bufferlo uses ~completing-read-multiple~ for the prompts where you can
specify more than one input selection; e.g., when opening multiple
bookmarks at once using ~bufferlo-bookmarks-load-interactive~. Emacs
31 will be getting a proper CRM prompt that displays the CRM separator
character as a reminder hint. Note: The default separator key is a comma.

Per [[https://github.com/minad/vertico#completing-read-multiple][vertico#completing-read-multiple]] from the author of the Emacs CRM
patch, we recommend adding the following snippet to your Emacs
configuration.

#+begin_src emacs-lisp
;; Prompt indicator for `completing-read-multiple'.
(when (< emacs-major-version 31)
  (advice-add #'completing-read-multiple :filter-args
              (lambda (args)
                (cons (format "[CRM%s] %s"
                              (string-replace "[ \t]*" "" crm-separator)
                              (car args))
                      (cdr args))))))
#+end_src

* Related Packages

** desktop.el

The built-in ~desktop.el~ package provides the possibility to persist the
state of the current Emacs session for future recall. Bufferlo is fully
compatible with ~desktop.el~. Bufferlo's local buffer lists for frames/tabs
are saved and restored when you use ~desktop.el~, and is less granular than
using bufferlo's own bookmarks.

With its bookmark feature, bufferlo offers an alternative session
persistence solution. In contrast to ~desktop.el~'s all-or-nothing
solution, bufferlo's ability to bookmark tabs, frames, and sets are
more fine-grained and lightweight. They are particularly suited for
long-lived Emacs sessions with a large number of buffers that belong
to different contexts.

Unlike ~desktop.el~, bufferlo does not persist each buffer's enabled
major or minor modes, instead relying on your Emacs configuration to
establish modes, same as when you establish the buffer manually. As
your configuration evolves, so too will your preferred major and minor
modes evolve rather than assuming the desktop file will always
represent your preferences. One typical example of an optional minor
mode is ~treesit-explore-mode~ which you might use to understand
treesitter behaviors. This minor mode will not be reenabled by
bufferlo. If you want this behavior automatically, add
~treesit-explore-mode~ to your major-mode hook.

Bufferlo bookmarks are still compatible with ~desktop.el~. It is even
possible to use both features together.

** Similar packages

There are other Emacs packages that provide functionality with varying
degrees of similarity to bufferlo. These packages offer some form of
frame or tab-based buffer-list isolation and/or session management.

- [[https://github.com/protesilaos/beframe][Beframe]]: frame-based buffer isolation
- [[https://github.com/mclear-tools/tabspaces][Tabspaces]]: project-based buffer isolation for tabs and frames
- [[https://github.com/alphapapa/activities.el][activities.el]]: purpose-based sessions on frame/tab level
- [[https://github.com/alphapapa/bufler.el][Bufler]]: rule-based workspace management and buffer grouping
- [[https://github.com/alphapapa/burly.el][Burly]]: save and restore window configurations for single or multiple frames
- [[https://github.com/bmag/emacs-purpose][purpose]]: manage windows and buffers according to purposes
- [[https://github.com/alphapapa/frame-purpose.el][frame-purpose]]: specialize frames to only display certain buffers
- [[https://github.com/overideal/perject][perject]]: purpose/project-based buffer isolation and session management based
  on ~desktop.el~
- [[https://github.com/nex3/perspective-el][Perspective]]: frame-based workspace isolation and persistence
- [[https://github.com/Bad-ptr/persp-mode.el][persp-mode]]: based on perspective; allows multiple frames per workspace
- [[https://github.com/jamescherti/easysession.el][easysession.el]]: session management
- [[https://github.com/minad/bookmark-view][bookmark-view.el]]: save and restore window configurations
- [[https://github.com/minad/tab-bookmark][tab-bookmark.el]]: save and restore window configurations for tabs
- [[https://github.com/ajrosen/tab-bar-buffers][tab-bar-buffers]]: isolate selected buffers and show them in the tab bar
- [[https://github.com/localauthor/tab-sets][tab-sets.el]]: save and restore tabs
- [[https://codeberg.org/akib/emacs-workroom][Workroom]]: buffer isolation
- [[https://github.com/thierryvolpiatto/psession][psession]]: session management
- [[https://github.com/chumpage/chumpy-windows][Chumpy-windows (Spaces)]]: switch between named window configurations
- [[https://github.com/thisirs/state][state]]: switch between window configurations
- [[https://emacs-session.sourceforge.net][Emacs-session]]: session management
- [[https://github.com/vspinu/sesman][Sesman]]: session management
- [[https://github.com/petergardfjall/emacs-wsp][wsp]]: session management
- [[https://github.com/vijumathew/windwow][Windwow]]: session management
- [[https://github.com/alpaker/frame-bufs][frame-bufs]]: frame-based buffer isolation
- [[https://github.com/ffevotte/desktop-plus/blob/master/desktop%2B.el][desktop+.el]]: extended ~desktop.el~ session management
- [[https://github.com/tlh/workgroups.el][Workgroups]]: session management
- [[https://github.com/iqbalansari/restart-emacs][restart-emacs]]: reatart Emacs; can restore session via ~desktop.el~
- [[https://github.com/noctuid/framegroups.el][framegroups.el]]: session management via ~desktop.el~

# END
