branch: externals/bufferlo commit db2db11fe2bd6a00dbdd05efeae9ccb372630a53 Author: shipmints <shipmi...@gmail.com> Commit: shipmints <shipmi...@gmail.com>
First draft of updated documentation. --- README.org | 1064 ++++++++++++++++++++++++++++++++++++++++++++---------- img/bufferlo.jpg | Bin 0 -> 115794 bytes 2 files changed, 870 insertions(+), 194 deletions(-) diff --git a/README.org b/README.org index a0c526b199..a82ca18956 100644 --- a/README.org +++ b/README.org @@ -1,104 +1,645 @@ -#+TITLE: bufferlo.el - Manage frame/tab-local buffer lists -#+AUTHOR: Florian Rommel -#+LANGUAGE: en - -This gives you separate buffer lists per frame and per (tab-bar) tab. - -Bufferlo is a lightweight wrapper around Emacs's buffer-list frame -parameter. In contrast to similar solutions, it integrates seamlessly -with the standard frame and tab management facilities, including -undeletion of frames and tabs, tab duplication and moving, frame -cloning, and persisting sessions (via desktop.el). - -With bufferlo, every frame or tab (if you use tab-bar tabs) 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). Bufferlo provides extensive management functions for -the local list and frame/tab-local variants of the switch-buffer -function, buffer menu, and Ibuffer. In addition, you can configure -any command that selects a buffer to use the local buffer list -(bufferlo anywhere). Bufferlo also allows you to bookmark and persist -the state of individual frames or tabs. +:PROPERTIES: +:TOC: :include all :depth 3 :force (depth) :ignore (this) :local (depth) +:END: +#+title: bufferlo.el - Frame/Tab Local Buffer Lists with Persistence +#+author: Florian Rommel, Stephane Marks +#+email: m...@florommel.de, shipmi...@gmail.com +#+language: en +#+startup: indent +#+options: num:nil +#+options: toc:nil + +# Uncomment below for decent local preview (would be nicer to have local GitHub rendering). +# +options: html-style:nil +# +html_head: <link rel="stylesheet" type="text/css" href="https://fniessen.github.io/org-html-themes/src/readtheorg_theme/css/htmlize.css"/> +# +html_head: <link rel="stylesheet" type="text/css" href="https://fniessen.github.io/org-html-themes/src/readtheorg_theme/css/readtheorg.css"/> +# +html_head: <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> +# +html_head: <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script> +# +html_head: <script type="text/javascript" src="https://fniessen.github.io/org-html-themes/src/lib/js/jquery.stickytableheaders.min.js"></script> +# +html_head: <script type="text/javascript" src="https://fniessen.github.io/org-html-themes/src/readtheorg_theme/js/readtheorg.js"></script> + +# toc below works for local rendering but not github boo +#+toc: headlines 3 local + +* bufferlo +# M-x org-make-toc to refresh the embedded toc for github use +# below inserted using M-x org-make-toc-insert +:PROPERTIES: +:TOC: :include descendants :force (ignore) :ignore (this) :local (nothing) +:END: + +Easy-to-use buffer management and workspace persistence tools for +Emacs workflow management. Headbutt your way to productivity and moove +ahead with bufferlo. + +#+html: <img src="img/bufferlo.jpg" style="width: 35vw; min-width: 300px;" align="right"> +:CONTENTS: +- [[#introduction][Introduction]] +- [[#installation][Installation]] +- [[#usage][Usage]] + - [[#buffer-selection][Buffer selection]] + - [[#manage-local-buffer-lists][Manage local buffer lists]] + - [[#bookmark-management-for-frames-and-tabs][Bookmark management for frames and tabs]] + - [[#general-bookmark-commands][General bookmark commands]] + - [[#frame-bookmark-commands][Frame bookmark commands]] + - [[#tab-bookmark-commands][Tab bookmark commands]] + - [[#automatic-bookmark-saving][Automatic bookmark saving]] + - [[#automatic-bookmark-loading][Automatic bookmark loading]] + - [[#filter-saved-bookmark-buffers][Filter saved bookmark buffers]] + - [[#bookmark-duplicates][Bookmark duplicates]] + - [[#save-current-other-or-all-frame-bookmarks][Save current, other, or all frame bookmarks]] + - [[#frame-bookmark-options][Frame bookmark options]] + - [[#tab-bookmark-options][Tab bookmark options]] + - [[#bookmark-addenda][Bookmark addenda]] + - [[#mode-line][mode-line]] + - [[#initial-buffer][Initial buffer]] + - [[#bufferlo-anywhere][Bufferlo anywhere]] +- [[#package-integration][Package integration]] + - [[#consult][Consult]] + - [[#ivy][Ivy]] + - [[#shell-mode-bookmarks][shell-mode bookmarks]] +- [[#complete-configuration-sample][Complete configuration sample]] +- [[#recommended-packages][Recommended packages]] +- [[#alternatives][Alternatives]] + - [[#desktopel][desktop.el]] + - [[#other-emacs-packages][Other Emacs packages]] +:END: + + +** Introduction + +Bufferlo maintains separate buffer lists per frame and/or +~tab-bar-mode~ tab by curating what buffers are used in each context +vs. using the default global buffer-list. This helps ease the burden +associated with a long-lived Emacs session with potentially hundreds +of buffers, many unrelated to the current frame or tab. + +Using Emacs's built-in buffer-list frame parameter, bufferlo +integrates seamlessly with standard frame and tab management +facilities, including undeletion of frames and tabs, tab duplication +and moving, frame cloning, and, if you use it, persisting sessions via +desktop.el, though bufferlo offers a more efficient 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~. + +Bufferlo has lightweight Emacs bookmarks-based persistence for frames +and 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 for interactive bookmark selection. + +Bufferlo's mode-line indicator shows the currently active frame and/or +tab bookmark name. + +Note: Code examples use ~setq~ to customize options. You may also use +~M-x customize-group bufferlo~. Emacs 29 introduced ~setopt~ which is +works correctly in the presence of ~defcustom~ setters. Currently the +only bufferlo option with a setter is +~bufferlo-bookmarks-auto-save-idle-interval~ so be sure to set that +interval timer in advance of enabling ~bufferlo-mode~. + +** Installation -The packages [[https://github.com/alpaker/frame-bufs][frame-bufs]] (unmaintained) and [[https://protesilaos.com/emacs/beframe][beframe]] provide similar -functionality, but only at the frame level, without support for tabs. -You may also have a look at (more different) workspace-oriented solutions -like [[https://github.com/alphapapa/bufler.el][bufler]] (rule-based workspace management and buffer grouping), -[[https://github.com/nex3/perspective-el][perspective]] (comprehensive workspace isolation and persistence), -or [[https://github.com/alphapapa/activities.el][activities.el]] (purpose-based session management on frame/tab level). +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~ +#+begin_src emacs-lisp + (use-package bufferlo + :ensure t + :config + ;; To install ibuffer filters, set the below in advance of enabling + ;; bufferlo-mode + (setq bufferlo-ibuffer-bind-local-buffer-filter t) + ;; To narrow previous-buffer and next-buffer candidates to local + ;; frame or tab buffer lists, pick one of: + (setq bufferlo-prefer-local-buffers t) ; frame locals + (setq bufferlo-prefer-local-buffers 'tabs) ; frame + tab locals + (setq switch-to-prev-buffer-skip-regexp ; set this to filter out buffers in previous/next-buffer + (concat "\\` *" ; ignore hidden buffers + "\\(\\*\\(Messages\\|Ibuffer\\|scratch\\|Completions\\|Help\\|Warnings\\|Apropos\\|vc-diff\\)\\*\\)" + "\\|" (rx "*helpful " (1+ anything) "*") + "\\'")) + (bufferlo-mode)) +#+end_src -* Installation +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. -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 1) -#+END_SRC - -Or use ~use-package~: -#+BEGIN_SRC emacs-lisp -(use-package bufferlo - :ensure t - :config - (bufferlo-mode 1)) -#+END_SRC - - -* Usage - -Use the bufferlo buffer-list commands as an alternative to the -respective global commands: -- ~bufferlo-switch-to-buffer~: - The ~switch-to-buffer~ command filtered for local buffers. - Call it with the prefix argument to get the global list (all buffers). -- ~bufferlo-ibuffer~: - ~ibuffer~ filtered for local buffers. - Alternatively, use "/ l" in ibuffer. -- ~bufferlo-ibuffer-orphans~: - ~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. - -The functions ~previous-buffer~ and ~next-buffer~ are automatically aware -of the local buffer list when ~bufferlo-mode~ is enabled. - -Bufferlo provides functions to manage the local buffer lists: -- ~bufferlo-clear~: - Clear the frame/tab's buffer list. -- ~bufferlo-remove~: - Remove a buffer from the frame/tab's buffer list. -- ~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 from the local list. -- ~bufferlo-kill-orphan-buffers~: - Kill all buffers that are not in any 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 all its local buffers. -- ~bufferlo-isolate-project~: - Isolate a project in the frame or tab. -- ~bufferlo-find-buffer~: - Switch to (one of) the frame/tab that contains the buffer in its local list. -- ~bufferlo-find-buffer-switch~: - Switch to (one of) the frame/tab that contains the buffer in its local list, - and select the buffer. - - -** Consult Integration - -You can integrate bufferlo with consult-buffer. +If you're not a tab-bar user, you likely have this set: +#+begin_src emacs-lisp + (setq tab-bar-show nil) ; or use the following method + (setopt tab-bar-show nil) ; this has a defcusto setter +#+end_src + +Note: The most recent version of bufferlo has had many features added +since last published. An attempt has been made to maintain backward +compatibility for existing users. Some of the defaults may now seem +cumbersome. Please review your configuration in light of these new +features. + +** 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. + #+begin_src emacs-lisp + (setq bufferlo-kill-buffers-prompt t) ; confirm before killing buffers or orphans + #+end_src + +- ~bufferlo-delete-frame-kill-buffers~: Delete the frame and kill all its local buffers. + #+begin_src emacs-lisp + (setq bufferlo-delete-frame-kill-buffers-save-bookmark-prompt t) ; if bookmarked, offer to save before killing + + (setq bufferlo-delete-frame-kill-buffers-prompt t) ; confirm before killing the frame + #+end_src + +- ~bufferlo-tab-close-kill-buffers~: Close the tab and kill its local buffers. + #+begin_src emacs-lisp + (setq bufferlo-close-tab-kill-buffers-save-bookmark-prompt t) ; if bookmarked, offer to save before killing + + (setq bufferlo-close-tab-kill-buffers-prompt t) ; confirm before killing the tab + #+end_src + +- ~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 and tabs + +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. 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 the 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. + +**** 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. + +- ~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" and tab bookmarks as "B-Tab". + +- ~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~. + +**** 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). + +**** 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-idle-interval 120) ; do this in advance of enabling `bufferlo-mode' + (setopt bufferlo-bookmarks-auto-save-idle-interval 120) ; use setopt, there is a 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 (this is the 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 at Emacs exit (set in advance of +enabling ~bufferlo-mode~): +#+begin_src emacs-lisp + (setq bufferlo-bookmarks-save-at-emacs-exit-policy 'nosave) ; inhibit saving at exit (this is the default) + (setq bufferlo-bookmarks-save-at-emacs-exit-policy 'pred) ; save active bookmark names that match your predicates + (setq bufferlo-bookmarks-save-at-emacs-exit-policy 'all) ; save all active bookmarks +#+end_src + +**** Automatic bookmark loading + +To automatically load some or all bufferlo bookmarks at Emacs startup +time: + +#+begin_src emacs-lisp + (setq bufferlo-bookmarks-load-at-emacs-startup 'noload) ; inhibit loading at startup (this is the 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 + +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 + +You can inhibit bufferlo bookmarks from loading at Emacs startup +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. Using them 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 is 'allow for backward compatibility + (setq bufferlo-bookmarks-save-duplicates-policy 'disallow) ; even better +#+end_src + +**** 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) ; this is the 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 + +What follows is a good, basic set of frame bookmark policies. Refine +them 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 to hold loaded frame bookmarks (default is nil for backward compatibility) + (setq bufferlo-bookmark-frame-load-make-frame t) +#+end_src +#+begin_src emacs-lisp + ;; policy when loading onto an already bookmarked frame (default is 'replace-frame-retain-current-bookmark for backward compatibility) + (setq bufferlo-bookmark-frame-load-policy 'prompt) +#+end_src +#+begin_src emacs-lisp + ;; allow duplicate active frame bookmarks in the Emacs session (default is 'allow for backward compatibility) + (setq bufferlo-bookmark-frame-duplicate-policy 'prompt) +#+end_src +#+begin_src emacs-lisp + ;; retain the bookmark when cloning a bookmarked frame via `clone-frame' or C-x 5 c (default is 'allow for backward compatibility) + (setq bufferlo-bookmark-frame-clone-policy 'prompt) +#+end_src + +**** Tab bookmark options + +What follows is a good, basic set of tab bookmark policies. Refine +them 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 (default is nil for backward compatibility and will use the current frame) + (setq bufferlo-bookmarks-load-tabs-make-frame t) +#+end_src +#+begin_src emacs-lisp + ;; load a tab bookmark replacing the current tab or making a new tab (default is 'replace for backward compatibility) + (setq bufferlo-bookmark-tab-replace-policy 'new) +#+end_src +#+begin_src emacs-lisp + ;; allow duplicate active tab bookmarks in the Emacs session (default is 'allow for backward compatibility) + (setq bufferlo-bookmark-tab-duplicate-policy 'prompt) +#+end_src +#+begin_src emacs-lisp + ;; allow inferior tab bookmark on a bookmarked frame which will supersede the tab when saving (default is 'allow for backward compatibility) + (setq bufferlo-bookmark-tab-load-into-bookmarked-frame-policy 'prompt) +#+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. At this time, Emacs does not support ~*shell*~ 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 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 tab: Could not restore emacs-todo.md (error (bookmark-error-no-filename stringp ~/.emacs/emacs-todo.md))~ + +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 + +- If you prefer iconic lighter prefixes, set one like this: +#+begin_src emacs-lisp + (setq bufferlo-mode-line-lighter-prefix " 🐮") ; bufferlos are cows + (setq bufferlo-mode-line-lighter-prefix " 🐃") ; some are water bufferlos +#+end_src +- To disable bufferlo's mode-line or provide your own custom mode-line function: +#+begin_src emacs-lisp + (setq bufferlo-mode-line-lighter nil) ; disable the bufferlo mode-line + (setq bufferlo-mode-line-lighter #'my/bufferlo-mode-line-lighter) ; use your own +#+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~. This is an example configuration: #+begin_src emacs-lisp @@ -139,7 +680,7 @@ This is an example configuration: [[./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: +You can also configure ~consult-buffer~ to hide the non-local buffers by default: #+begin_src emacs-lisp (defvar my-consult--source-buffer `(:name "All Buffers" @@ -178,15 +719,15 @@ You can also configure consult-buffer to hide the non-local buffers by default: [[./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, space 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 ?.~). - +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 ?.~). -** Ivy Integration +*** Ivy -You can also integrate bufferlo with ivy. +You can also integrate bufferlo with ~ivy~. #+begin_src emacs-lisp (defun ivy-bufferlo-switch-buffer () @@ -204,105 +745,240 @@ You can also integrate bufferlo with ivy. :caller 'ivy-switch-buffer))) #+end_src +*** shell-mode bookmarks -** Bookmarking Tabs and Frames - -Bufferlo lets you bookmark the current editing state of individual -frames and tabs (windows and local buffers, i.e., the "session"). -This allows persisting and restoring the state of a frame or tab -within or between Emacs sessions. - -This feature has similarities to [[https://github.com/alphapapa/activities.el][activities.el]] and [[https://github.com/minad/bookmark-view][bookmark-view]] -but with awareness of bufferlo's local buffer list. - -A tab bookmark includes the tab's window configuration, the local -buffer list, and the state (not the contents) of all bookmarkable -local buffers. A frame bookmark saves the entire frame with all its -tabs and their states. +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. -The state of non-bookmarkable buffers is not saved. However, when -still open, they are included in the restored tab/frame. +** Complete configuration sample -Tab bookmark functions: -- ~bufferlo-bookmark-tab-save~: - Save the current tab as a bookmark. -- ~bufferlo-bookmark-tab-load~: - Load a tab bookmark. This replaces the current tab. -- ~bufferlo-bookmark-tab-save-current~: - Save the current tab to its associated bookmark (*). -- ~bufferlo-bookmark-tab-load-current~: - Load the current tab from its associated bookmark (*). +#+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) + :bind + ( + ;; ibuffer + ("C-z i i" . bufferlo-ibuffer) + ("C-z i b" . bufferlo-ibuffer) + ("C-z i B" . bufferlo-ibuffer-orphans) + ;; interactive + ("C-z i l" . bufferlo-bms-load) + ("C-z i s" . bufferlo-bms-save) + ("C-z i c" . bufferlo-bms-close) + ("C-z i -" . bufferlo-remove) + ("C-z i r" . bufferlo-bm-raise) + ;; 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-tab-close-kill-buffers) ; 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-delete-frame-kill-buffers) ; kill + ) + :init + ;; these must be set before the bufferlo package is loaded + (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-lighter-prefix " 🐮") + (setq switch-to-prev-buffer-skip-regexp + (concat "\\` *" + "\\(\\*\\(Messages\\|Ibuffer\\|scratch\\|Completions\\|Help\\|Warnings\\|Apropos\\|vc-diff\\)\\*\\)" + "\\|" (rx "*helpful " (1+ anything) "*") + "\\'")) + (setq bufferlo-kill-buffers-prompt t) + (setq bufferlo-bookmark-prefer-saveplace-point t) + (setq bufferlo-delete-frame-kill-buffers-save-bookmark-prompt t) + (setq bufferlo-delete-frame-kill-buffers-prompt t) + (setq bufferlo-close-tab-kill-buffers-save-bookmark-prompt t) + (setq bufferlo-close-tab-kill-buffers-prompt t) + (setq bufferlo-bookmark-frame-load-make-frame t) + (setq bufferlo-bookmark-frame-load-policy 'prompt) + (setq bufferlo-bookmark-frame-duplicate-policy 'prompt) + (setq bufferlo-bookmark-frame-clone-policy 'prompt) + (setq bufferlo-bookmark-tab-replace-policy 'new) + (setq bufferlo-bookmark-tab-duplicate-policy 'prompt) + (setq bufferlo-bookmark-tab-load-into-bookmarked-frame-policy 'prompt) + (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-policy 'all) + (setq bufferlo-bookmarks-load-at-emacs-startup 'pred) + (setopt bufferlo-bookmarks-auto-save-idle-interval (* 60 5)) ; 5 minutes + (setq bufferlo-bookmarks-auto-save-messages 'saved) + + (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-all-buffers + `(:name "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 buffer candidate source for `consult-buffer'.") + + (defvar my:bufferlo-consult--source-other-buffers + `(:name "Other Buffers" + :narrow ?b + :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 buffer candidate source for `consult-buffer'.") -Frame bookmark functions: -- ~bufferlo-bookmark-frame-save~: - Save the current frame as a bookmark. -- ~bufferlo-bookmark-frame-load~: - Load a frame bookmark. This replaces the current frame. -- ~bufferlo-bookmark-frame-save-current~: - Save the current frame to its associated bookmark (*). -- ~bufferlo-bookmark-frame-load-current~: - Load the current frame from its associated bookmark (*). + (defvar my:bufferlo-consult--source-local-buffers + `(:name "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 buffer candidate source for `consult-buffer'.") -Restoring bookmarks correctly handles renamed buffers with unchanged -file association (e.g., when Emacs had to "uniquify" buffer names). + (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) -(*) The associated bookmark is the bookmark from which the frame/tab -was loaded or to which it was saved. + (bufferlo-mode) + (bufferlo-anywhere-mode) + ) +#+end_src +** Recommended packages -** Initial Buffer +In general, we recommend using these additional Emacs features which +help remember state between Emacs sessions: ~recentf~, ~savehist~, +~saveplace~, and ~uniquify~ which helps with buffer naming conflicts. -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 list. -You can change the initial buffer by customizing ~tab-bar-new-tab-choice~: +If you use ~saveplace~, and prefer to use its buffer-position history +and want to ignore bookmark positions, put this in your bufferlo +configuration: #+begin_src emacs-lisp - (setq tab-bar-new-tab-choice "*scratch*") + (setq bufferlo-bookmark-prefer-saveplace-point t) #+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~ accordingly. - -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 +There are several mature packages that enhance Emacs completion +functions in the ~minibuffer~ and "in-region." Working with Emacs +under enhanced completion is almost a must-have over the defaults. + +Some people prefer [[https://company-mode.github.io/][company]], some [[https://github.com/abo-abo/swiper][ivy]], and some [[https://emacs-helm.github.io/helm/][helm]]. One bufferlo +author uses the following combination of completion packages which are +built using native shared Emacs completion APIs for more universal +operation: [[https://github.com/oantolin/orderless][orderless]] which provides regexp among other completion +styles, [[https://github.com/minad/vertico][vertico]] which enhances ~minibuffer~ completion candidate +selection into vertical or tabular lists, [[https://github.com/minad/corfu][corfu]] which offers rich +"in-region" completion for GUI or tty, and [[https://github.com/minad/consult][consult]] which, along with +[[https://github.com/minad/marginalia/][marginalia]] make ~minibuffer~ completions even richer. + +** Alternatives + +*** desktop.el + +In contrast to ~desktop.el~, Emacs's built-in persistence feature, +bufferlo's persistence is lightweight. ~desktop.el~ is an +all-or-nothing solution saving your entire Emacs environment for +future recall. When you have a long-lived Emacs session that may +include hundreds of buffers that may not relate to one another or are +not relevant to your current tasks, ~desktop.el~ is cumbersome and +slow to restore an entire context. Bufferlo gives you finer-grained +control over what collections of frames and tabs to save and load. + +Also in contrast to ~desktop.el~, Bufferlo does not store "framesets" +(though we may concoct a lightweight "session" persistence feature in +the future), instead relying on your Emacs configuration to create +frames as you prefer them when restoring bufferlo-managed content. +This can be more convenient than ~desktop.el~ when you use multiple +Emacs sessions; e.g., GUI and tty sessions where your frames and tabs +will have different geometries. + +*** Other Emacs packages -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 - -Of course, 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. +The packages [[https://github.com/alpaker/frame-bufs][frame-bufs]] (unmaintained) and [[https://protesilaos.com/emacs/beframe][beframe]] provide similar +functionality, but only at the frame level, and without support for +tabs. + +You may also have a look at workspace-oriented solutions like [[https://github.com/alphapapa/bufler.el][bufler]] +(rule-based workspace management and buffer grouping) and its related +package [[https://github.com/alphapapa/activities.el][activities.el]] (purpose-based session management on frame/tab +level), [[https://github.com/minad/bookmark-view][bookmark-view]], or [[https://github.com/nex3/perspective-el][perspective]] (comprehensive workspace +isolation and persistence). + +- https://github.com/iqbalansari/restart-emacs/blob/master/restart-emacs.el +- https://github.com/alphapapa/bufler.el +- https://github.com/alphapapa/activities.el +- https://github.com/alphapapa/burly.el +- https://github.com/alphapapa/frame-purpose.el +- https://github.com/overideal/perject +- https://github.com/nex3/perspective-el +- https://github.com/Bad-ptr/persp-mode.el +- https://github.com/protesilaos/beframe +- https://github.com/jamescherti/easysession.el +- https://github.com/minad/bookmark-view +- https://github.com/minad/tab-bookmark +- https://github.com/ajrosen/tab-bar-buffers +- https://github.com/localauthor/tab-sets +- https://github.com/mclear-tools/tabspaces +- https://github.com/chumpage/chumpy-windows +- https://github.com/thisirs/state +- https://emacs-session.sourceforge.net +- https://github.com/vspinu/sesman +- https://codeberg.org/akib/emacs-workroom +- https://github.com/thierryvolpiatto/psession +- https://github.com/noctuid/framegroups.el +- https://github.com/petergardfjall/emacs-wsp +- https://github.com/vijumathew/windwow +- https://github.com/alpaker/frame-bufs +- https://github.com/jdtsmith/mac-tab-desktop/blob/main/mac-tab-desktop.el +- https://github.com/ffevotte/desktop-plus/blob/master/desktop%2B.el +- https://www.emacswiki.org/emacs/BookmarkPlus +- https://github.com/emacsmirror/bookmark-plus +- https://github.com/tlh/workgroups.el +- https://github.com/emacsmirror/winring + +# END diff --git a/img/bufferlo.jpg b/img/bufferlo.jpg new file mode 100644 index 0000000000..b6e25f8c71 Binary files /dev/null and b/img/bufferlo.jpg differ