branch: externals/bufferlo
commit 1cb860ff141fa0aafd413e6ca2c13eda19ab3b94
Author: Stéphane Marks <shipmi...@gmail.com>
Commit: Flo Rommel <m...@florommel.de>

    Handle bookmark type changes on saves
    
    Cover the cases:
    
    frame->tab
    frame->set
    tab->frame
    tab->set
    set->tab
    set->frame
    
    Warn of unreachable tab->set and frame->set where the tab or
    frame bookmark is contained in a set.
    
    - 'bufferlo--set-save' add msg in harmony with the other implementations.
    - 'bufferlo-set-save-interactive' handle frame->set tab->set.
    - 'bufferlo-bookmark-tab-save' handle frame->tab set->tab.
    - 'bufferlo-bookmark-frame-save' handle tab->frame set->frame.
    - 'bufferlo--sets-containing-bookmark' new function.
    - 'bufferlo--clear-set-bookmarks-by-name' new function.
    - A few related, miscellaneous code improvements.
    - Updated documentation.
---
 README.org  |  16 ++++
 bufferlo.el | 266 ++++++++++++++++++++++++++++++++++++++++++++++++++----------
 2 files changed, 241 insertions(+), 41 deletions(-)

diff --git a/README.org b/README.org
index e5c6dc641b..6dbe89a83e 100644
--- a/README.org
+++ b/README.org
@@ -511,6 +511,22 @@ but they are likely to be enabled by default in the future.
   (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
diff --git a/bufferlo.el b/bufferlo.el
index 9064e4f05a..450232ce28 100644
--- a/bufferlo.el
+++ b/bufferlo.el
@@ -2668,10 +2668,10 @@ Returns nil on success, non-nil on abort."
                             nil))
            (orig-bookmark-name bookmark-name)
            (abm (assoc bookmark-name (bufferlo--active-bookmarks)))
-           (disconnect-tbm-p)
-           (restored-buffer-names)
-           (skipped-buffer-names)
-           (msg))
+           disconnect-tbm-p
+           restored-buffer-names
+           skipped-buffer-names
+           msg)
       (cl-labels
           ((msg-append (s) (setq msg (concat msg "; " s))))
         ;; Bookmark already loaded in another tab?
@@ -2915,9 +2915,9 @@ Returns nil on success, non-nil on abort."
                              ;; make-frame implied by functions like
                              ;; `bookmark-jump-other-frame'
                              (not pop-up-frames)))
-           (duplicate-policy)
-           (load-policy)
-           (msg))
+           duplicate-policy
+           load-policy
+           msg)
       (cl-labels
           ((msg-append (s) (setq msg (concat msg "; " s))))
 
@@ -3454,7 +3454,8 @@ Returns nil on success, non-nil on abort."
 (put #'bufferlo--bookmark-set-handler 'bookmark-handler-type "B-Set")
 (put #'bufferlo--bookmark-set-handler 'bookmark-inhibit 'insert)
 
-(defun bufferlo--set-save (bookmark-name active-bookmark-names 
active-bookmarks &optional no-overwrite)
+(defun bufferlo--set-save (bookmark-name active-bookmark-names active-bookmarks
+                                         &optional no-overwrite no-message msg)
   "Save a bufferlo bookmark set for the specified active bookmarks.
 Store the set in BOOKMARK-NAME for the named bookmarks in
 ACTIVE-BOOKMARK-NAMES represented in ACTIVE-BOOKMARKS.
@@ -3467,9 +3468,9 @@ frame.  New frames will be created to hold tab bookmarks 
in the
 same grouping.  Order may not be preserved.  Tab frame geometry is
 stored for optional restoration.
 
-If NO-OVERWRITE is non-nil, record the new bookmark without
-throwing away the old one.  NO-MESSAGE inhibits the save status
-message."
+If NO-OVERWRITE is non-nil, record the new bookmark without throwing
+away the old one.  NO-MESSAGE inhibits the save status message.  If MSG
+is non-nil, it is added to the save message."
   (let* ((abms (seq-filter
                 (lambda (x) (member (car x) active-bookmark-names))
                 active-bookmarks))
@@ -3540,11 +3541,14 @@ message."
                      (bufferlo--bookmark-set-make
                       active-bookmark-names tabsets frameset))
                     no-overwrite)
-    (message "Saved bookmark set `%s' containing: %s"
-             bookmark-name
-             (mapconcat #'identity active-bookmark-names ", "))))
+    (unless no-message
+      (message "Saved bookmark set `%s' containing: %s%s"
+               bookmark-name
+               (mapconcat #'identity active-bookmark-names ", ")
+               (if msg msg "")))))
 
-(defun bufferlo-set-save-interactive (bookmark-name &optional no-overwrite)
+(defun bufferlo-set-save-interactive (bookmark-name
+                                      &optional no-overwrite no-message)
   "Save a bufferlo bookmark set for the specified active bookmarks.
 The bookmark set will be stored under BOOKMARK-NAME.
 
@@ -3554,23 +3558,94 @@ the frame's geometry.
 Frame bookmarks represent themselves.
 
 If NO-OVERWRITE is non-nil, record the new bookmark without
-throwing away the old one."
+throwing away the old one.  NO-MESSAGE inhibits the save status
+message.
+
+Warn if this bookmark will overwrite a tab or frame bookmark, and clear
+them, if active, when overwriting."
   (interactive
    (list (completing-read
           "Save bufferlo bookmark set as: "
           (bufferlo--bookmark-get-names #'bufferlo--bookmark-set-handler)
           nil nil nil 'bufferlo-bookmark-set-history nil)))
   (bufferlo--warn)
-  (let* ((abms (bufferlo--active-bookmarks))
-         (abm-names (mapcar #'car abms))
-         (comps (bufferlo--bookmark-completing-read-multiple
-                 (format "Add bookmark(s) to %s: " bookmark-name) abm-names)))
-    (bufferlo--set-save bookmark-name comps abms no-overwrite)
-    (setq bufferlo--active-sets
-          (assoc-delete-all bookmark-name bufferlo--active-sets #'equal))
-    (push
-     `(,bookmark-name (bufferlo-bookmark-names . ,comps))
-     bufferlo--active-sets)))
+  (catch :abort
+    (let* ((tab-bm-names (bufferlo--bookmark-get-names
+                          #'bufferlo--bookmark-tab-handler))
+           (frame-bm-names (bufferlo--bookmark-get-names
+                            #'bufferlo--bookmark-frame-handler))
+           (is-tab-bm (member bookmark-name tab-bm-names))
+           (is-frame-bm (member bookmark-name frame-bm-names))
+           clear-active-tab-bm
+           clear-active-frame-bm)
+
+      (cond
+       ;; Overwriting a tab bookmark of the same name?  If we proceed, and the
+       ;; tab bookmark is active, mark it to deactivate, leaving its buffers
+       ;; intact, and after we know the replacement bookmark will be saved.
+       (is-tab-bm
+        (if (y-or-n-p (format "Overwrite the bufferlo tab bookmark `%s'? "
+                              bookmark-name))
+            (setq clear-active-tab-bm
+                  (assoc bookmark-name (bufferlo--active-bookmarks nil 'tbm)))
+          (throw :abort t)))
+       ;; Overwriting a frame bookmark of the same name?  If so, and the frame
+       ;; bookmark is active, mark it to deactivate, leaving its buffers
+       ;; intact, and after we know the replacement bookmark will be saved.
+       (is-frame-bm
+        (if (y-or-n-p (format "Overwrite the bufferlo frame bookmark `%s'? "
+                              bookmark-name))
+            (setq clear-active-frame-bm
+                  (assoc bookmark-name (bufferlo--active-bookmarks nil 'fbm)))
+          (throw :abort t))))
+
+      ;; Confirm if the about-to-be set bookmark is contained in a set, as it
+      ;; will not be loadable once converted.  (If this is a set, it will not
+      ;; be in a set.)
+      (when-let* ((containing-sets (bufferlo--sets-containing-bookmark
+                                    bookmark-name)))
+        (unless (y-or-n-p (format
+                           "Warning: `%s' is contained in set(s) %s and will 
be unloadable; proceed? "
+                           bookmark-name (mapconcat #'identity containing-sets 
", ")))
+          (throw :abort t)))
+
+      (let* ((abms (bufferlo--active-bookmarks))
+             (abm-names (mapcar #'car abms))
+             ;; If we are about to overwrite a tab or frame bm, remove that
+             ;; name from the list of bookmarks available for the new set.
+             (abm-names (if (or is-tab-bm
+                                is-frame-bm)
+                            (remove bookmark-name abm-names)
+                          abm-names))
+             (comps (bufferlo--bookmark-completing-read-multiple
+                     (format "Add bookmark(s) to %s: " bookmark-name) 
abm-names))
+             msg)
+
+        ;; Do not create an empty bookmark set.
+        (when (not (length> comps 0))
+          (message "Select at least one bookmark to create the set")
+          (throw :abort t))
+
+        (cl-labels
+            ((msg-append (s) (setq msg (concat msg "; " s))))
+
+          (cond
+           ;; Clear the about-to-be overwritten tab bookmark.
+           (clear-active-tab-bm
+            (msg-append "cleared overwritten tab bookmark")
+            (bufferlo--clear-tab-bookmarks-by-name bookmark-name))
+           ;; Clear the about-to-be overwritten frame bookmark.
+           (clear-active-frame-bm
+            (msg-append "cleared overwritten frame bookmark")
+            (bufferlo--clear-frame-bookmarks-by-name bookmark-name)))
+
+          ;; Finally, save the bookmark, and replace the active bookmark.
+          (bufferlo--set-save bookmark-name comps abms no-overwrite no-message 
msg)
+          (setq bufferlo--active-sets
+                (assoc-delete-all bookmark-name bufferlo--active-sets #'equal))
+          (push
+           `(,bookmark-name (bufferlo-bookmark-names . ,comps))
+           bufferlo--active-sets))))))
 
 (defun bufferlo--set-get-constituents (bsets abms)
   "Get the constituents of the given `bookmark-sets' from the list of 
bookmarks.
@@ -3586,6 +3661,18 @@ consider (usually all active bookmarks)."
                      bsets)))
     (seq-uniq abm-names)))
 
+(defun bufferlo--sets-containing-bookmark (bookmark-name)
+  "Return a list of set bookmark names that contain BOOKMARK-NAME."
+  (let (sets)
+    (dolist (bookmark-set-name
+             (bufferlo--bookmark-get-names #'bufferlo--bookmark-set-handler))
+      (when-let* ((bookmark-record
+                   (bufferlo--bookmark-get-bookmark bookmark-set-name)))
+        (when (member bookmark-name
+                      (alist-get 'bufferlo-bookmark-names bookmark-record))
+          (push (car bookmark-record) sets))))
+    sets))
+
 (defun bufferlo-set-save-current-interactive ()
   "Save active constituents in selected `bookmark-sets'."
   (interactive)
@@ -3796,7 +3883,10 @@ buffer list.
 Use `bufferlo-bookmark-tab-in-bookmarked-frame-policy' to
 influence how this function handles setting a tab bookmark in the
 presence of a frame bookmark.  Using both together is allowed, but
-is not recommended."
+is not recommended.
+
+Warn if this bookmark will overwrite a set or frame bookmark, and clear
+them, if active, when overwriting."
   (interactive
    (list (completing-read
           "Save bufferlo tab bookmark: "
@@ -3805,15 +3895,48 @@ is not recommended."
           (alist-get 'bufferlo-bookmark-tab-name (bufferlo--current-tab)))))
   (bufferlo--warn)
   (catch :abort
-    (let ((abm (assoc name (bufferlo--active-bookmarks)))
-          (tbm (alist-get 'bufferlo-bookmark-tab-name
-                          (tab-bar--current-tab-find)))
-          (msg))
+    (let* ((abm (assoc name (bufferlo--active-bookmarks)))
+           (tbm (alist-get 'bufferlo-bookmark-tab-name
+                           (tab-bar--current-tab-find)))
+           (set-bm-names (bufferlo--bookmark-get-names
+                          #'bufferlo--bookmark-set-handler))
+           (frame-bm-names (bufferlo--bookmark-get-names
+                            #'bufferlo--bookmark-frame-handler))
+           (is-set-bm (member name set-bm-names))
+           (is-frame-bm (member name frame-bm-names))
+           clear-active-set-bm
+           clear-active-frame-bm
+           msg)
+
+      (cond
+       ;; Overwriting a set bookmark of the same name?  If so, and the set
+       ;; bookmark is active, mark it to deactivate, leaving its bookmarks and
+       ;; buffers intact, and after we know the replacement bookmark will be
+       ;; saved.
+       (is-set-bm
+        (if (y-or-n-p (format "Overwrite the bufferlo set bookmark `%s'? " 
name))
+            (setq clear-active-set-bm
+                  (assoc name bufferlo--active-sets))
+          (throw :abort t)))
+       ;; Overwriting a frame bookmark of the same name?  If so, and the frame
+       ;; bookmark is active, mark it to deactivate, leaving its buffers
+       ;; intact, and after we know the replacement bookmark will be saved.
+       (is-frame-bm
+        (if (y-or-n-p (format "Overwrite the bufferlo frame bookmark `%s'? " 
name))
+            (setq clear-active-frame-bm
+                  (assoc name (bufferlo--active-bookmarks nil 'fbm)))
+          (throw :abort t))))
+
       (cl-labels
           ((msg-append (s) (setq msg (concat msg "; " s))))
 
-        ;; Only check policies when the bm is not already associated with this 
tab
-        (unless (and tbm (equal tbm (car abm)))
+        ;; Only check policies when the bm is a tab bm, and not already
+        ;; associated with this tab.
+        (unless (or is-set-bm
+                    is-frame-bm
+                    (and
+                     tbm
+                     (equal tbm (car abm))))
 
           ;; Bookmark already loaded in another tab?
           (when abm
@@ -3842,6 +3965,16 @@ is not recommended."
                (msg-append "cleared frame bookmark"))
               (_ ))))
 
+        (cond
+         ;; Clear the about-to-be overwritten set bookmark.
+         (clear-active-set-bm
+          (msg-append "cleared overwritten set bookmark")
+          (bufferlo--clear-set-bookmarks-by-name name))
+         ;; Clear the about-to-be overwritten frame bookmark.
+         (clear-active-frame-bm
+          (msg-append "cleared overwritten frame bookmark")
+          (bufferlo--clear-frame-bookmarks-by-name name)))
+
         ;; Finally, save the bookmark
         (bufferlo--bookmark-tab-save name no-overwrite no-message msg)))))
 
@@ -3915,6 +4048,11 @@ This reuses the current tab even if
                  (frame-parameter frame 'bufferlo-bookmark-frame-name))
       (set-frame-parameter frame 'bufferlo-bookmark-frame-name nil))))
 
+(defun bufferlo--clear-set-bookmarks-by-name (bookmark-name)
+  "Clear BOOKMARK-NAME set bookmark."
+  (setq bufferlo--active-sets
+        (assoc-delete-all bookmark-name bufferlo--active-sets #'equal)))
+
 (defun bufferlo--bookmark-frame-save (name &optional no-overwrite no-message 
msg)
   "Save the current frame as a bookmark.
 NAME is the bookmark's name.  If NO-OVERWRITE is non-nil, record
@@ -3942,7 +4080,10 @@ the contents) of the bookmarkable buffers for each tab.
 Use `bufferlo-bookmark-tab-in-bookmarked-frame-policy' to
 influence how this function handles setting a frame bookmark in
 the presence of bookmarked tabs.  Using both together is allowed,
-but is not recommended."
+but is not recommended.
+
+Warn if this bookmark will overwrite a set or tab bookmark, and clear
+them, if active, when overwriting."
   (interactive
    (list (completing-read
           "Save bufferlo frame bookmark: "
@@ -3951,14 +4092,47 @@ but is not recommended."
           (frame-parameter nil 'bufferlo-bookmark-frame-name))))
   (bufferlo--warn)
   (catch :abort
-    (let ((abm (assoc name (bufferlo--active-bookmarks)))
-          (fbm (frame-parameter nil 'bufferlo-bookmark-frame-name))
-          (msg))
+    (let* ((abm (assoc name (bufferlo--active-bookmarks)))
+           (fbm (frame-parameter nil 'bufferlo-bookmark-frame-name))
+           (set-bm-names (bufferlo--bookmark-get-names
+                          #'bufferlo--bookmark-set-handler))
+           (tab-bm-names (bufferlo--bookmark-get-names
+                          #'bufferlo--bookmark-tab-handler))
+           (is-set-bm (member name set-bm-names))
+           (is-tab-bm (member name tab-bm-names))
+           clear-active-set-bm
+           clear-active-tab-bm
+           msg)
+
+      (cond
+       ;; Overwriting a set bookmark of the same name?  If so, and the set
+       ;; bookmark is active, mark it to deactivate, leaving its bookmarks and
+       ;; buffers intact, and after we know the replacement bookmark will be
+       ;; saved.
+       (is-set-bm
+        (if (y-or-n-p (format "Overwrite the bufferlo set bookmark `%s'? " 
name))
+            (setq clear-active-set-bm
+                  (assoc name bufferlo--active-sets))
+          (throw :abort t)))
+       ;; Overwriting a tab bookmark of the same name?  If so, and the tab
+       ;; bookmark is active, mark it to deactivate, leaving its buffers
+       ;; intact, and after we know the replacement bookmark will be saved.
+       (is-tab-bm
+        (if (y-or-n-p (format "Overwrite the bufferlo tab bookmark `%s'? " 
name))
+            (setq clear-active-tab-bm
+                  (assoc name (bufferlo--active-bookmarks nil 'tbm)))
+          (throw :abort t))))
+
       (cl-labels
           ((msg-append (s) (setq msg (concat msg "; " s))))
 
-        ;; Only check policies when bm is not already associated with this 
frame
-        (unless (and fbm (equal fbm (car abm)))
+        ;; Only check policies when bm is a frame bm, and is not already
+        ;; associated with this frame.
+        (unless (or is-set-bm
+                    is-tab-bm
+                    (and
+                     fbm
+                     (equal fbm (car abm))))
 
           ;; Bookmark already loaded in another frame?
           (when abm
@@ -3990,6 +4164,16 @@ but is not recommended."
                (msg-append "cleared tab bookmarks"))
               ('allow))))
 
+        (cond
+         ;; Clear the about-to-be overwritten set bookmark.
+         (clear-active-set-bm
+          (msg-append "cleared overwritten set bookmark")
+          (bufferlo--clear-set-bookmarks-by-name name))
+         ;; Clear the about-to-be overwritten tab bookmark.
+         (clear-active-tab-bm
+          (msg-append "cleared overwritten tab bookmark")
+          (bufferlo--clear-tab-bookmarks-by-name name)))
+
         ;; Finally, save the bookmark
         (bufferlo--bookmark-frame-save name no-overwrite no-message msg)))))
 
@@ -4598,8 +4782,8 @@ raised."
          (abm-names (mapcar #'car abms))
          (comps (bufferlo--bookmark-completing-read
                  "Select a bookmark to raise: " abm-names)))
-    (if (not (= (length comps) 1))
-        (message "Please select a single bookmark to raise")
+    (if (not (length= comps 1))
+        (message "Select a single bookmark to raise")
       (bufferlo--bookmark-raise-by-name (car comps) abms))))
 
 ;; DWIM convenience functions

Reply via email to