More tweaks of skeleton documentation wrt \n behavior at bol/eol.
[bpt/emacs.git] / lisp / frameset.el
index e334b4c..9a47451 100644 (file)
@@ -1,6 +1,6 @@
 ;;; frameset.el --- save and restore frame and window setup -*- lexical-binding: t -*-
 
-;; Copyright (C) 2013 Free Software Foundation, Inc.
+;; Copyright (C) 2013-2014 Free Software Foundation, Inc.
 
 ;; Author: Juanma Barranquero <lekktu@gmail.com>
 ;; Keywords: convenience
@@ -156,7 +156,13 @@ FRAMESET is copied with `copy-tree'."
        (let ((states (frameset-states object)))
          (and (listp states)
               (cl-every #'consp (frameset-states object))))
-       (frameset-version object)))                 ; And VERSION is non-nil.
+       (frameset-version object)))        ; And VERSION is non-nil.
+
+(defun frameset--prop-setter (frameset property value)
+  "Setter function for `frameset-prop'.  Internal use only."
+  (setf (frameset-properties frameset)
+       (plist-put (frameset-properties frameset) property value))
+  value)
 
 ;; A setf'able accessor to the frameset's properties
 (defun frameset-prop (frameset property)
@@ -165,15 +171,9 @@ FRAMESET is copied with `copy-tree'."
 Properties can be set with
 
   (setf (frameset-prop FRAMESET PROPERTY) NEW-VALUE)"
+  (declare (gv-setter frameset--prop-setter))
   (plist-get (frameset-properties frameset) property))
 
-(gv-define-setter frameset-prop (val fs prop)
-  (macroexp-let2 nil v val
-    `(progn
-       (setf (frameset-properties ,fs)
-            (plist-put (frameset-properties ,fs) ,prop ,v))
-       ,v)))
-
 \f
 ;; Filtering
 
@@ -182,7 +182,7 @@ Properties can be set with
 ;; Let's say that Emacs' frame parameters were never designed as a tool to
 ;; precisely record (or restore) a frame's state.  They grew organically,
 ;; and their uses and behaviors reflect their history.  In using them to
-;; implement framesets, the unwary implementor, or the prospective package
+;; implement framesets, the unwary implementer, or the prospective package
 ;; writer willing to use framesets in their code, might fall victim of some
 ;; unexpected... oddities.
 ;;
@@ -417,11 +417,11 @@ Properties can be set with
 ;; `frameset-filter-params' can be useful, even if you're not using
 ;; framesets.  The interface of `frameset-filter-params' is generic
 ;; and does not depend of global state, with one exception: it uses
-;; the internal variable `frameset--target-display' to decide if, and
-;; how, to modify the `display' parameter of FILTERED.  But that
-;; should not represent any problem, because it's only meaningful
-;; when restoring, and customized uses of `frameset-filter-params'
-;; are likely to use their own filter alist and just call
+;; the dynamically bound variable `frameset--target-display' to decide
+;; if, and how, to modify the `display' parameter of FILTERED.  That
+;; should not represent a problem, because it's only meaningful when
+;; restoring, and customized uses of `frameset-filter-params' are
+;; likely to use their own filter alist and just call
 ;;
 ;;   (setq my-filtered (frameset-filter-params my-params my-filters t))
 ;;
@@ -522,14 +522,13 @@ It must return:
 Frame parameters not on this alist are passed intact, as if they were
 defined with ACTION = nil.")
 
-
-(defvar frameset--target-display nil
-  ;; Either (minibuffer . VALUE) or nil.
-  ;; This refers to the current frame config being processed inside
-  ;; `frameset-restore' and its auxiliary functions (like filtering).
-  ;; If nil, there is no need to change the display.
-  ;; If non-nil, display parameter to use when creating the frame.
-  "Internal use only.")
+;; Dynamically bound in `frameset-save', `frameset-restore'.
+(defvar frameset--target-display)
+;; Either (display . VALUE) or nil.
+;; This refers to the current frame config being processed with
+;; `frameset-filter-params' and its auxiliary filtering functions.
+;; If nil, there is no need to change the display.
+;; If non-nil, display parameter to use when creating the frame.
 
 (defun frameset-switch-to-gui-p (parameters)
   "True when switching to a graphic display.
@@ -570,15 +569,22 @@ see `frameset-filter-alist'."
       (not (stringp (cdr current)))
       (not (string-match-p "^unspecified-[fb]g$" (cdr current)))))
 
-(defun frameset-filter-minibuffer (current _filtered _parameters saving)
-  "When saving, convert (minibuffer . #<window>) to (minibuffer . t).
+(defun frameset-filter-minibuffer (current filtered _parameters saving)
+  "Force the minibuffer parameter to have a sensible value.
+
+When saving, convert (minibuffer . #<window>) to (minibuffer . t).
+When restoring, if there are two copies, keep the one pointing to
+a live window.
 
 For the meaning of CURRENT, FILTERED, PARAMETERS and SAVING,
 see `frameset-filter-alist'."
-  (or (not saving)
-      (if (windowp (cdr current))
-         '(minibuffer . t)
-       t)))
+  (let ((value (cdr current)) mini)
+    (cond (saving
+          (if (windowp value) '(minibuffer . t) t))
+         ((setq mini (assq 'minibuffer filtered))
+          (when (windowp value) (setcdr mini value))
+          nil)
+         (t t))))
 
 (defun frameset-filter-shelve-param (current _filtered parameters saving
                                             &optional prefix)
@@ -676,6 +682,11 @@ Internal use only."
                         (mapconcat (lambda (n) (format "%04X" n))
                                    (cl-loop repeat 4 collect (random 65536))
                                    "-"))))
+
+(defun frameset-cfg-id (frame-cfg)
+  "Return the frame id for frame configuration FRAME-CFG."
+  (cdr (assq 'frameset--id frame-cfg)))
+
 ;;;###autoload
 (defun frameset-frame-id (frame)
   "Return the frame id of FRAME, if it has one; else, return nil.
@@ -721,16 +732,18 @@ FRAME-LIST is a list of frames.  Internal use only."
   (dolist (frame frame-list)
     (unless (frame-parameter frame 'frameset--mini)
       (frameset--set-id frame)
-      (let* ((mb-frame (window-frame (minibuffer-window frame)))
-            (id (and mb-frame (frameset-frame-id mb-frame))))
-       (if (null id)
-           (error "Minibuffer frame %S for %S is not being saved" mb-frame frame)
-         ;; For minibufferless frames, frameset--mini is a cons
-         ;; (nil . FRAME-ID), where FRAME-ID is the frameset--id
-         ;; of the frame containing its minibuffer window.
-         (set-frame-parameter frame
-                              'frameset--mini
-                              (cons nil id)))))))
+      (let ((mb-frame (window-frame (minibuffer-window frame))))
+       ;; For minibufferless frames, frameset--mini is a cons
+       ;; (nil . FRAME-ID), where FRAME-ID is the frameset--id of
+       ;; the frame containing its minibuffer window.
+       ;; FRAME-ID can be set to nil, if FRAME-LIST doesn't contain
+       ;; the minibuffer frame of a minibufferless frame; we allow
+       ;; it without trying to second-guess the user.
+       (set-frame-parameter frame
+                            'frameset--mini
+                            (cons nil
+                                  (and mb-frame
+                                       (frameset-frame-id mb-frame))))))))
 
 ;;;###autoload
 (cl-defun frameset-save (frame-list
@@ -747,6 +760,7 @@ PREDICATE is a predicate function, which must return non-nil for frames that
 should be saved; if PREDICATE is nil, all frames from FRAME-LIST are saved.
 PROPERTIES is a user-defined property list to add to the frameset."
   (let* ((list (or (copy-sequence frame-list) (frame-list)))
+        (frameset--target-display nil)
         (frames (cl-delete-if-not #'frame-live-p
                                   (if predicate
                                       (cl-delete-if-not predicate list)
@@ -773,10 +787,9 @@ PROPERTIES is a user-defined property list to add to the frameset."
 \f
 ;; Restoring framesets
 
-(defvar frameset--reuse-list nil
-  "The list of frames potentially reusable.
-Its value is only meaningful during execution of `frameset-restore'.
-Internal use only.")
+;; Dynamically bound in `frameset-restore'.
+(defvar frameset--reuse-list)
+(defvar frameset--action-map)
 
 (defun frameset-compute-pos (value left/top right/bottom)
   "Return an absolute positioning value for a frame.
@@ -858,7 +871,7 @@ NOTE: This only works for non-iconified frames."
          (modify-frame-parameters frame params))))))
 
 (defun frameset--find-frame-if (predicate display &rest args)
-  "Find a frame in `frameset--reuse-list' satisfying PREDICATE.
+  "Find a reusable frame satisfying PREDICATE.
 Look through available frames whose display property matches DISPLAY
 and return the first one for which (PREDICATE frame ARGS) returns t.
 If PREDICATE is nil, it is always satisfied.  Internal use only."
@@ -892,7 +905,7 @@ is the parameter alist of the frame being restored.  Internal use only."
           (setq frame (frameset--find-frame-if
                        (lambda (f id)
                          (frameset-frame-id-equal-p f id))
-                       display (cdr (assq 'frameset--id parameters))))
+                       display (frameset-cfg-id parameters)))
           ;; If it has not been loaded, and it is not a minibuffer-only frame,
           ;; let's look for an existing non-minibuffer-only frame to reuse.
           (unless (or frame (eq (cdr (assq 'minibuffer parameters)) 'only))
@@ -909,10 +922,11 @@ is the parameter alist of the frame being restored.  Internal use only."
           (setq frame (frameset--find-frame-if
                        (lambda (f id mini-id)
                          (and (frameset-frame-id-equal-p f id)
-                              (frameset-frame-id-equal-p (window-frame
-                                                          (minibuffer-window f))
-                                                         mini-id)))
-                       display (cdr (assq 'frameset--id parameters)) (cdr mini))))
+                              (or (null mini-id) ; minibuffer frame not saved
+                                  (frameset-frame-id-equal-p
+                                   (window-frame (minibuffer-window f))
+                                   mini-id))))
+                       display (frameset-cfg-id parameters) (cdr mini))))
          (t
           ;; Default to just finding a frame in the same display.
           (setq frame (frameset--find-frame-if nil display))))
@@ -926,8 +940,8 @@ is the parameter alist of the frame being restored.  Internal use only."
 Setting position and size parameters as soon as possible helps reducing
 flickering; other parameters, like `minibuffer' and `border-width', can
 not be changed once the frame has been created.  Internal use only."
-  (cl-loop for param in '(left top with height border-width minibuffer)
-          collect (assq param parameters)))
+  (cl-loop for param in '(left top width height border-width minibuffer)
+          when (assq param parameters) collect it))
 
 (defun frameset--restore-frame (parameters window-state filters force-onscreen)
   "Set up and return a frame according to its saved state.
@@ -936,15 +950,10 @@ PARAMETERS is the frame's parameter alist; WINDOW-STATE is its window state.
 For the meaning of FILTERS and FORCE-ONSCREEN, see `frameset-restore'.
 Internal use only."
   (let* ((fullscreen (cdr (assq 'fullscreen parameters)))
-        (lines (assq 'tool-bar-lines parameters))
         (filtered-cfg (frameset-filter-params parameters filters nil))
         (display (cdr (assq 'display filtered-cfg))) ;; post-filtering
         alt-cfg frame)
 
-    ;; This works around bug#14795 (or feature#14795, if not a bug :-)
-    (setq filtered-cfg (assq-delete-all 'tool-bar-lines filtered-cfg))
-    (push '(tool-bar-lines . 0) filtered-cfg)
-
     (when fullscreen
       ;; Currently Emacs has the limitation that it does not record the size
       ;; and position of a frame before maximizing it, so we cannot save &
@@ -968,16 +977,20 @@ Internal use only."
        (push visible alt-cfg)
        (push (cons 'fullscreen fullscreen) alt-cfg)))
 
-    ;; Time to find or create a frame an apply the big bunch of parameters.
-    ;; If a frame needs to be created and it falls partially or fully offscreen,
-    ;; sometimes it gets "pushed back" onscreen; however, moving it afterwards is
-    ;; allowed.  So we create the frame as invisible and then reapply the full
-    ;; parameter alist (including position and size parameters).
-    (setq frame (or (and frameset--reuse-list
-                        (frameset--reuse-frame display filtered-cfg))
-                   (make-frame-on-display display
-                                          (cons '(visibility)
-                                                (frameset--initial-params filtered-cfg)))))
+    ;; Time to find or create a frame and apply the big bunch of parameters.
+    (setq frame (and frameset--reuse-list
+                    (frameset--reuse-frame display filtered-cfg)))
+    (if frame
+       (puthash frame :reused frameset--action-map)
+      ;; If a frame needs to be created and it falls partially or fully offscreen,
+      ;; sometimes it gets "pushed back" onscreen; however, moving it afterwards is
+      ;; allowed.  So we create the frame as invisible and then reapply the full
+      ;; parameter alist (including position and size parameters).
+      (setq frame (make-frame-on-display display
+                                        (cons '(visibility)
+                                              (frameset--initial-params filtered-cfg))))
+      (puthash frame :created frameset--action-map))
+
     (modify-frame-parameters frame
                             (if (eq (frame-parameter frame 'fullscreen) fullscreen)
                                 ;; Workaround for bug#14949
@@ -991,8 +1004,7 @@ Internal use only."
               (not (eq (frame-parameter frame 'visibility) 'icon)))
       (frameset-move-onscreen frame force-onscreen))
 
-    ;; Let's give the finishing touches (visibility, tool-bar, maximization).
-    (when lines (push lines alt-cfg))
+    ;; Let's give the finishing touches (visibility, maximization).
     (when alt-cfg (modify-frame-parameters frame alt-cfg))
     ;; Now restore window state.
     (window-state-put window-state (frame-root-window frame) 'safe)
@@ -1007,7 +1019,7 @@ Internal use only."
     (cond ((eq id-def1 t) t)
          ((eq id-def2 t) nil)
          ((not (eq hasmini1 hasmini2)) (eq hasmini1 t))
-         ((eq hasmini1 nil) (string< id-def1 id-def2))
+         ((eq hasmini1 nil) (or id-def1 id-def2))
          (t t))))
 
 (defun frameset-keep-original-display-p (force-display)
@@ -1024,7 +1036,8 @@ For the meaning of FORCE-DISPLAY, see `frameset-restore'."
 ;;;###autoload
 (cl-defun frameset-restore (frameset
                            &key predicate filters reuse-frames
-                                force-display force-onscreen)
+                                force-display force-onscreen
+                                cleanup-frames)
   "Restore a FRAMESET into the current display(s).
 
 PREDICATE is a function called with two arguments, the parameter alist
@@ -1036,58 +1049,79 @@ and window-state is not restored.
 FILTERS is an alist of parameter filters; if nil, the value of
 `frameset-filter-alist' is used instead.
 
-REUSE-FRAMES selects the policy to use to reuse frames when restoring:
-  t        Reuse existing frames if possible, and delete those not reused.
-  nil      Restore frameset in new frames and delete existing frames.
-  :keep    Restore frameset in new frames and keep the existing ones.
-  LIST     A list of frames to reuse; only these are reused (if possible).
-            Remaining frames in this list are deleted; other frames not
-            included on the list are left untouched.
+REUSE-FRAMES selects the policy to reuse frames when restoring:
+  t        All existing frames can be reused.
+  nil      No existing frame can be reused.
+  match    Only frames with matching frame ids can be reused.
+  PRED     A predicate function; it receives as argument a live frame,
+             and must return non-nil to allow reusing it, nil otherwise.
 
 FORCE-DISPLAY can be:
   t        Frames are restored in the current display.
   nil      Frames are restored, if possible, in their original displays.
-  :delete  Frames in other displays are deleted instead of restored.
+  delete   Frames in other displays are deleted instead of restored.
   PRED     A function called with two arguments, the parameter alist and
             the window state (in that order).  It must return t, nil or
-            `:delete', as above but affecting only the frame that will
+            `delete', as above but affecting only the frame that will
             be created from that parameter alist.
 
 FORCE-ONSCREEN can be:
   t        Force onscreen only those frames that are fully offscreen.
   nil      Do not force any frame back onscreen.
-  :all     Force onscreen any frame fully or partially offscreen.
+  all      Force onscreen any frame fully or partially offscreen.
   PRED     A function called with three arguments,
           - the live frame just restored,
           - a list (LEFT TOP WIDTH HEIGHT), describing the frame,
           - a list (LEFT TOP WIDTH HEIGHT), describing the workarea.
           It must return non-nil to force the frame onscreen, nil otherwise.
 
+CLEANUP-FRAMES allows to \"clean up\" the frame list after restoring a frameset:
+  t        Delete all frames that were not created or restored upon.
+  nil      Keep all frames.
+  FUNC     A function called with two arguments:
+           - FRAME, a live frame.
+           - ACTION, which can be one of
+             :rejected  Frame existed, but was not a candidate for reuse.
+             :ignored   Frame existed, was a candidate, but wasn't reused.
+             :reused    Frame existed, was a candidate, and restored upon.
+             :created   Frame didn't exist, was created and restored upon.
+           Return value is ignored.
+
 Note the timing and scope of the operations described above: REUSE-FRAMES
 affects existing frames; PREDICATE, FILTERS and FORCE-DISPLAY affect the frame
-being restored before that happens; and FORCE-ONSCREEN affects the frame once
-it has been restored.
+being restored before that happens; FORCE-ONSCREEN affects the frame once
+it has been restored; and CLEANUP-FRAMES affects all frames alive after the
+restoration, including those that have been reused or created anew.
 
 All keyword parameters default to nil."
 
   (cl-assert (frameset-valid-p frameset))
 
-  (let (other-frames)
-
-    ;; frameset--reuse-list is a list of frames potentially reusable.  Later we
-    ;; will decide which ones can be reused, and how to deal with any leftover.
-    (pcase reuse-frames
-      ((or `nil `:keep)
-       (setq frameset--reuse-list nil
-            other-frames (frame-list)))
-      ((pred consp)
-       (setq frameset--reuse-list (copy-sequence reuse-frames)
-            other-frames (cl-delete-if (lambda (frame)
-                                         (memq frame frameset--reuse-list))
-                                       (frame-list))))
-      (_
-       (setq frameset--reuse-list (frame-list)
-            other-frames nil)))
+  (let* ((frames (frame-list))
+        (frameset--action-map (make-hash-table :test #'eq))
+        ;; frameset--reuse-list is a list of frames potentially reusable.  Later we
+        ;; will decide which ones can be reused, and how to deal with any leftover.
+        (frameset--reuse-list
+         (pcase reuse-frames
+           (`t
+            frames)
+           (`nil
+            nil)
+           (`match
+            (cl-loop for (state) in (frameset-states frameset)
+                     when (frameset-frame-with-id (frameset-cfg-id state) frames)
+                     collect it))
+           ((pred functionp)
+            (cl-remove-if-not reuse-frames frames))
+           (_
+            (error "Invalid arg :reuse-frames %s" reuse-frames)))))
+
+    ;; Mark existing frames in the map; candidates to reuse are marked as :ignored;
+    ;; they will be reassigned later, if chosen.
+    (dolist (frame frames)
+      (puthash frame
+              (if (memq frame frameset--reuse-list) :ignored :rejected)
+              frameset--action-map))
 
     ;; Sort saved states to guarantee that minibufferless frames will be created
     ;; after the frames that contain their minibuffer windows.
@@ -1098,38 +1132,33 @@ All keyword parameters default to nil."
          (condition-case-unless-debug err
              (let* ((d-mini (cdr (assq 'frameset--mini frame-cfg)))
                     (mb-id (cdr d-mini))
-                    (default (and (booleanp mb-id) mb-id))
+                    (default (and (car d-mini) mb-id))
                     (force-display (if (functionp force-display)
                                        (funcall force-display frame-cfg window-cfg)
                                      force-display))
-                    frame to-tty)
+                    (frameset--target-display nil)
+                    frame to-tty duplicate)
                ;; Only set target if forcing displays and the target display is different.
-               (cond ((frameset-keep-original-display-p force-display)
-                      (setq frameset--target-display nil))
-                     ((eq (frame-parameter nil 'display) (cdr (assq 'display frame-cfg)))
-                      (setq frameset--target-display nil))
-                     (t
-                      (setq frameset--target-display (cons 'display
-                                                           (frame-parameter nil 'display))
-                            to-tty (null (cdr frameset--target-display)))))
+               (unless (or (frameset-keep-original-display-p force-display)
+                           (equal (frame-parameter nil 'display)
+                                  (cdr (assq 'display frame-cfg))))
+                 (setq frameset--target-display (cons 'display
+                                                      (frame-parameter nil 'display))
+                       to-tty (null (cdr frameset--target-display))))
                ;; Time to restore frames and set up their minibuffers as they were.
                ;; We only skip a frame (thus deleting it) if either:
                ;; - we're switching displays, and the user chose the option to delete, or
                ;; - we're switching to tty, and the frame to restore is minibuffer-only.
                (unless (and frameset--target-display
-                            (or (eq force-display :delete)
+                            (or (eq force-display 'delete)
                                 (and to-tty
                                      (eq (cdr (assq 'minibuffer frame-cfg)) 'only))))
-                 ;; If keeping non-reusable frames, and the frameset--id of one of them
-                 ;; matches the id of a frame being restored (because, for example, the
-                 ;; frameset has already been read in the same session), remove the
-                 ;; frameset--id from the non-reusable frame, which is not useful anymore.
-                 (when (and other-frames
-                            (or (eq reuse-frames :keep) (consp reuse-frames)))
-                   (let ((dup (frameset-frame-with-id (cdr (assq 'frameset--id frame-cfg))
-                                                      other-frames)))
-                     (when dup
-                       (set-frame-parameter dup 'frameset--id nil))))
+                 ;; To avoid duplicating frame ids after restoration, we note any
+                 ;; existing frame whose id matches a frame configuration in the
+                 ;; frameset.  Once the frame config is properly restored, we can
+                 ;; reset the old frame's id to nil.
+                 (setq duplicate (frameset-frame-with-id (frameset-cfg-id frame-cfg)
+                                                         frames))
                  ;; Restore minibuffers.  Some of this stuff could be done in a filter
                  ;; function, but it would be messy because restoring minibuffers affects
                  ;; global state; it's best to do it here than add a bunch of global
@@ -1142,21 +1171,30 @@ All keyword parameters default to nil."
                      (setq frame-cfg (append '((tool-bar-lines . 0) (menu-bar-lines . 0))
                                              frame-cfg))))
                   (t ;; Frame depends on other frame's minibuffer window.
-                   (let* ((mb-frame (or (frameset-frame-with-id mb-id)
-                                        (error "Minibuffer frame %S not found" mb-id)))
-                          (mb-param (assq 'minibuffer frame-cfg))
-                          (mb-window (minibuffer-window mb-frame)))
-                     (unless (and (window-live-p mb-window)
-                                  (window-minibuffer-p mb-window))
-                       (error "Not a minibuffer window %s" mb-window))
-                     (if mb-param
-                         (setcdr mb-param mb-window)
-                       (push (cons 'minibuffer mb-window) frame-cfg)))))
+                   (when mb-id
+                     (let ((mb-frame (frameset-frame-with-id mb-id))
+                           (mb-window nil))
+                       (if (not mb-frame)
+                           (delay-warning 'frameset
+                                          (format "Minibuffer frame %S not found" mb-id)
+                                          :warning)
+                         (setq mb-window (minibuffer-window mb-frame))
+                         (unless (and (window-live-p mb-window)
+                                      (window-minibuffer-p mb-window))
+                           (delay-warning 'frameset
+                                          (format "Not a minibuffer window %s" mb-window)
+                                          :warning)
+                           (setq mb-window nil)))
+                       (when mb-window
+                         (push (cons 'minibuffer mb-window) frame-cfg))))))
                  ;; OK, we're ready at last to create (or reuse) a frame and
                  ;; restore the window config.
                  (setq frame (frameset--restore-frame frame-cfg window-cfg
                                                       (or filters frameset-filter-alist)
                                                       force-onscreen))
+                 ;; Now reset any duplicate frameset--id
+                 (when (and duplicate (not (eq frame duplicate)))
+                   (set-frame-parameter duplicate 'frameset--id nil))
                  ;; Set default-minibuffer if required.
                  (when default (setq default-minibuffer-frame frame))))
            (error
@@ -1166,48 +1204,75 @@ All keyword parameters default to nil."
     ;; other frames are already visible (discussed in thread for bug#14841).
     (sit-for 0 t)
 
-    ;; Delete remaining frames, but do not fail if some resist being deleted.
-    (unless (eq reuse-frames :keep)
-      (dolist (frame (sort (nconc (if (listp reuse-frames) nil other-frames)
-                                 frameset--reuse-list)
-                          ;; Minibufferless frames must go first to avoid
-                          ;; errors when attempting to delete a frame whose
-                          ;; minibuffer window is used by another frame.
-                          #'frameset-minibufferless-first-p))
-       (condition-case err
-           (delete-frame frame)
-         (error
-          (delay-warning 'frameset (error-message-string err))))))
-    (setq frameset--reuse-list nil
-         frameset--target-display nil)
+    ;; Clean up the frame list
+    (when cleanup-frames
+      (let ((map nil)
+           (cleanup (if (eq cleanup-frames t)
+                        (lambda (frame action)
+                          (when (memq action '(:rejected :ignored))
+                            (delete-frame frame)))
+                      cleanup-frames)))
+       (maphash (lambda (frame _action) (push frame map)) frameset--action-map)
+       (dolist (frame (sort map
+                            ;; Minibufferless frames must go first to avoid
+                            ;; errors when attempting to delete a frame whose
+                            ;; minibuffer window is used by another frame.
+                            #'frameset-minibufferless-first-p))
+         (condition-case-unless-debug err
+             (funcall cleanup frame (gethash frame frameset--action-map))
+           (error
+            (delay-warning 'frameset (error-message-string err) :warning))))))
 
     ;; Make sure there's at least one visible frame.
-    (unless (or (daemonp) (visible-frame-list))
-      (make-frame-visible (car (frame-list))))))
+    (unless (or (daemonp)
+               (catch 'visible
+                 (maphash (lambda (frame _)
+                            (and (frame-live-p frame) (frame-visible-p frame)
+                                 (throw 'visible t)))
+                          frameset--action-map)))
+      (make-frame-visible (selected-frame)))))
 
 \f
 ;; Register support
 
+;;;###autoload
 (defun frameset--jump-to-register (data)
   "Restore frameset from DATA stored in register.
 Called from `jump-to-register'.  Internal use only."
-  (let* ((delete (and current-prefix-arg t))
-        (iconify-list (if delete nil (frame-list))))
-    (frameset-restore (aref data 0)
-                     :filters frameset-session-filter-alist
-                     :reuse-frames (if delete t :keep))
-    (mapc #'iconify-frame iconify-list)
-    (let ((frame (frameset-frame-with-id (aref data 1))))
-      (when frame
-       (select-frame-set-input-focus frame)
-       (goto-char (aref data 2))))))
+  (frameset-restore
+   (aref data 0)
+   :filters frameset-session-filter-alist
+   :reuse-frames (if current-prefix-arg t 'match)
+   :cleanup-frames (if current-prefix-arg
+                      ;; delete frames
+                      nil
+                    ;; iconify frames
+                    (lambda (frame action)
+                      (pcase action
+                        (`rejected (iconify-frame frame))
+                        ;; In the unexpected case that a frame was a candidate
+                        ;; (matching frame id) and yet not restored, remove it
+                        ;; because it is in fact a duplicate.
+                        (`ignored (delete-frame frame))))))
+
+  ;; Restore selected frame, buffer and point.
+  (let ((frame (frameset-frame-with-id (aref data 1)))
+       buffer window)
+    (when frame
+      (select-frame-set-input-focus frame)
+      (when (and (buffer-live-p (setq buffer (marker-buffer (aref data 2))))
+                (window-live-p (setq window (get-buffer-window buffer frame))))
+       (set-frame-selected-window frame window)
+       (with-current-buffer buffer (goto-char (aref data 2)))))))
 
 ;;;###autoload
-(defun frameset-to-register (register &optional _arg)
+(defun frameset-to-register (register)
   "Store the current frameset in register REGISTER.
 Use \\[jump-to-register] to restore the frameset.
-Argument is a character, naming the register."
-  (interactive "cFrameset to register: \nP")
+Argument is a character, naming the register.
+
+Interactively, reads the register using `register-read-with-preview'."
+  (interactive (list (register-read-with-preview "Frameset to register: ")))
   (set-register register
                (registerv-make
                 (vector (frameset-save nil