unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#51883: 29.0.50; Command to get accidentally deleted frames back
@ 2021-11-15 23:38 Michael Heerdegen
  2021-11-16  7:53 ` Juri Linkov
                   ` (3 more replies)
  0 siblings, 4 replies; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-15 23:38 UTC (permalink / raw)
  To: 51883


Hello,

seems we don't have a command to restore a frame that the user has
killed by accident (note: we have `restore-frame', but that's something
different [related to maximizing commands]).  This is a feature request
to add such a command.

It seems this feature can't be implemented using window configurations
(AFAIU they die with their frame), and also not using frame
configurations (they don't work per frame) - but using one element
framesets seems to work:

#+begin_src emacs-lisp
(require 'frameset)
(require 'cl-lib)

(defvar my-killed-frame-ring-size 10)
(defvar my-killed-frames (make-ring my-killed-frame-ring-size))

(advice-add 'delete-frame :before #'my-remember-deleted-frame)

(defun my-remember-deleted-frame (&optional frame _force)
  (ring-insert my-killed-frames
               (frameset-save (list (or frame (selected-frame))))))

(defun my-restore-killed-frame (&optional n)
  (interactive "p")
  (let ((frames-before (frame-list)))
    (frameset-restore (ring-ref my-killed-frames (- (or n 1) 1)))
    (let ((restored (cl-set-difference (frame-list) frames-before)))
      (when (and restored (not (cdr restored)))
        (select-frame-set-input-focus (car restored))))))

(global-set-key [?\C-x ?5 ?t] #'my-restore-killed-frame)
#+end_src

Maybe having it in C would be better - I don't know.  A more convenient
access to frames killed earlier than the last one, instead of using the
prefix arg like above, might be appropriate (making the command
repeatable, maybe?)  Apart from these details the above is a start and
could just go to frameset.el.  Opinions?


TIA,

Michael.


In GNU Emacs 29.0.50 (build 25, x86_64-pc-linux-gnu, GTK+ Version 3.24.24, cairo version 1.16.0)
 of 2021-11-15 built on drachen
Repository revision: 6b79b03c81f77ca00adeb80170653d4c0b53d46a
Repository branch: master
Windowing system distributor 'The X.Org Foundation', version 11.0.12011000
System Description: Debian GNU/Linux 11 (bullseye)






^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-15 23:38 bug#51883: 29.0.50; Command to get accidentally deleted frames back Michael Heerdegen
@ 2021-11-16  7:53 ` Juri Linkov
  2021-11-16  8:14   ` Lars Ingebrigtsen
  2021-11-16 20:46   ` Juri Linkov
  2021-11-16  8:49 ` Visuwesh via Bug reports for GNU Emacs, the Swiss army knife of text editors
                   ` (2 subsequent siblings)
  3 siblings, 2 replies; 51+ messages in thread
From: Juri Linkov @ 2021-11-16  7:53 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: 51883

> Maybe having it in C would be better - I don't know.

To avoid implementing the whole function in C,
'delete-frame' could call a hook with the frame as its arg,
then you can add your code to the hook.

> A more convenient access to frames killed earlier than the last one,
> instead of using the prefix arg like above, might be appropriate (making
> the command repeatable, maybe?)  Apart from these details the above is
> a start and could just go to frameset.el.  Opinions?

Very useful feature.  And like tab-undo is bound to 'C-x t u',
the frame closing undo could be bound to 'C-x 5 u'.

I haven't yet tested whether your implementation restores the tab-bar
as well, or maybe it would require more handling.  I suspect that
instead of framesets you might need to use window-state-get/window-state-put
like it's used in 'clone-frame'.  This means that code could go to frame.el.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-16  7:53 ` Juri Linkov
@ 2021-11-16  8:14   ` Lars Ingebrigtsen
  2021-11-16 20:46   ` Juri Linkov
  1 sibling, 0 replies; 51+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-16  8:14 UTC (permalink / raw)
  To: Juri Linkov; +Cc: Michael Heerdegen, 51883

Juri Linkov <juri@linkov.net> writes:

> To avoid implementing the whole function in C,
> 'delete-frame' could call a hook with the frame as its arg,
> then you can add your code to the hook.

Yes, that sounds generally useful for other things, too.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-15 23:38 bug#51883: 29.0.50; Command to get accidentally deleted frames back Michael Heerdegen
  2021-11-16  7:53 ` Juri Linkov
@ 2021-11-16  8:49 ` Visuwesh via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-16 20:20   ` Juri Linkov
  2021-11-16 15:17 ` Gregory Heytings
  2021-11-17  4:13 ` Richard Stallman
  3 siblings, 1 reply; 51+ messages in thread
From: Visuwesh via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-16  8:49 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51883

[ Please bear with me here as I try to use the web interface for the
mail archive.  I apologise if I messed something up and the message
doesn't get CC'd to the debbugs.  ]

Hi Juri,

>> Maybe having it in C would be better - I don't know.
> To avoid implementing the whole function in C,
> 'delete-frame' could call a hook with the frame as its arg,
> then you can add your code to the hook.

Doesn't Emacs already provide this?  See `delete-frame-functions'.






^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-15 23:38 bug#51883: 29.0.50; Command to get accidentally deleted frames back Michael Heerdegen
  2021-11-16  7:53 ` Juri Linkov
  2021-11-16  8:49 ` Visuwesh via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-16 15:17 ` Gregory Heytings
  2021-11-16 17:05   ` Gregory Heytings
  2021-11-16 20:30   ` Juri Linkov
  2021-11-17  4:13 ` Richard Stallman
  3 siblings, 2 replies; 51+ messages in thread
From: Gregory Heytings @ 2021-11-16 15:17 UTC (permalink / raw)
  To: 51883; +Cc: Michael Heerdegen

[-- Attachment #1: Type: text/plain, Size: 16 bytes --]


Patch attached.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-diff; name=Undelete-deleted-frames.patch, Size: 6468 bytes --]

From 7d5ffe8af8348bf1b3ff888816f2517ee4aa345e Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Tue, 16 Nov 2021 15:10:41 +0000
Subject: [PATCH] Undelete deleted frames.

* lisp/frame.el (undelete-frame): New command.
(undeleted-frame--save-deleted-frame): New auxiliary function.
(undelete-frame--deleted-frames): New auxiliary variable.
(make-frame-command): Add a prefix argument, and call the new
command.

* lisp/menu-bar.el (menu-bar-file-menu): Add an entry for the
new command.

* doc/emacs/frames.tex (Creating Frames, Frame Commands): Document
the new command.

* etc/NEWS: Document the new command.

See bug#51883.
---
 doc/emacs/frames.texi | 11 ++++++---
 etc/NEWS              | 10 ++++++++
 lisp/frame.el         | 56 +++++++++++++++++++++++++++++++++++++++----
 lisp/menu-bar.el      |  4 ++++
 4 files changed, 73 insertions(+), 8 deletions(-)

diff --git a/doc/emacs/frames.texi b/doc/emacs/frames.texi
index c14ada2957..f5ede6af5e 100644
--- a/doc/emacs/frames.texi
+++ b/doc/emacs/frames.texi
@@ -452,8 +452,11 @@ Creating Frames
 @item C-x 5 2
 @kindex C-x 5 2
 @findex make-frame-command
-Create a new frame using the default frame parameters
-(@code{make-frame-command}).
+Create a new frame using the default frame parameters, or, with a prefix
+argument, undelete one of the 16 most recently deleted frames
+(@code{make-frame-command}).  A prefix argument undeletes the last deleted
+frame, a numerical prefix argument between 0 and 15 undeletes the
+corresponding deleted frame, where 0 is the most recently deleted frame.
 
 @item C-x 5 c
 @kindex C-x 5 c
@@ -510,7 +513,9 @@ Frame Commands
 @kindex C-x 5 0
 @findex delete-frame
 Delete the selected frame (@code{delete-frame}).  This signals an
-error if there is only one frame.
+error if there is only one frame.  The 16 most recently deleted frames
+can be undeleted with the @kbd{C-x 5 2} command, when it is used with
+a prefix argument.
 
 @item C-z
 @kindex C-z @r{(X windows)}
diff --git a/etc/NEWS b/etc/NEWS
index 312fc18f4f..ab47f8cf5b 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -130,6 +130,16 @@ For example, a 'display-buffer-alist' entry of
 will make the body of the chosen window 40 columns wide.  For the
 height use 'window-height' in combination with 'body-lines'.
 
+---
+** Frames
+
++++
+*** With a prefix argument, the key 'C-x 5 2' undeletes deleted frames.
+The 16 most recently deleted frames can be undeleted.  A prefix argument
+undeletes the last deleted frame, a numerical prefix argument between 0
+and 15 undeletes the corresponding deleted frame, where 0 is the most
+recently deleted frame.
+
 ** Better detection of text suspiciously reordered on display.
 The function 'bidi-find-overridden-directionality' has been extended
 to detect reordering effects produced by embeddings and isolates
diff --git a/lisp/frame.el b/lisp/frame.el
index 2c73737a54..2085397b30 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -775,16 +775,23 @@ close-display-connection
       (mapc #'delete-frame frames)
       (x-close-connection display))))
 
-(defun make-frame-command ()
+(defun make-frame-command (&optional arg)
   "Make a new frame, on the same terminal as the selected frame.
 If the terminal is a text-only terminal, this also selects the
 new frame.
 
+With a prefix argument ARG, undelete the most recently deleted
+frame.
+With a numerical prefix argument ARG between 0 and 15, undelete
+the ARGth deleted frame, where 0 is most recently deleted frame.
+
 When called from Lisp, returns the new frame."
-  (interactive)
-  (if (display-graphic-p)
-      (make-frame)
-    (select-frame (make-frame))))
+  (interactive "P")
+  (if arg
+      (undelete-frame arg)
+    (if (display-graphic-p)
+        (make-frame)
+      (select-frame (make-frame)))))
 
 (defun clone-frame (&optional frame no-windows)
   "Make a new frame with the same parameters and windows as FRAME.
@@ -2484,6 +2491,45 @@ delete-other-frames
         (if iconify (iconify-frame this) (delete-frame this)))
       (setq this next))))
 
+(eval-when-compile (require 'frameset))
+
+(defvar undelete-frame--deleted-frames nil
+  "Internal variable used by `undelete-frame--save-deleted-frame'.")
+
+(defun undeleted-frame--save-deleted-frame (frame)
+  "Save the configuration of frames deleted with `delete-frame'.
+Only the 16 most recently deleted frames are saved."
+  (when (frame-live-p frame)
+    (setq undelete-frame--deleted-frames
+          (cons (cons
+                 (display-graphic-p)
+                 (frameset-save (list frame)))
+                undelete-frame--deleted-frames))
+    (if (> (length undelete-frame--deleted-frames) 16)
+        (setq undelete-frame--deleted-frames
+              (butlast undelete-frame--deleted-frames)))))
+
+(add-hook 'delete-frame-functions #'undeleted-frame--save-deleted-frame)
+
+(defun undelete-frame (&optional arg)
+  "Undelete a frame deleted with `delete-frame'.
+When ARG is nil or a list, the last deleted frame is undeleted.
+When ARG is a number between 0 and 15, the ARGth deleted frame is
+undeleted.
+When called from Lisp, returns the new frame."
+  (interactive "P")
+  (let* ((frames (frame-list))
+         (n (if (listp arg) 0 arg))
+         (frameset (nth n undelete-frame--deleted-frames)))
+    (when (and frameset
+               (eq (display-graphic-p) (car frameset)))
+      (setq undelete-frame--deleted-frames
+            (delq frameset undelete-frame--deleted-frames))
+      (frameset-restore (cdr frameset))
+      (let ((frame (car (seq-difference (frame-list) frames))))
+        (when frame
+          (select-frame-set-input-focus frame)
+          frame)))))
 \f
 ;;; Window dividers.
 (defgroup window-divider nil
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index 1a81f1a3d0..a1e0195bb5 100644
--- a/lisp/menu-bar.el
+++ b/lisp/menu-bar.el
@@ -109,6 +109,10 @@ menu-bar-file-menu
       (bindings--define-key menu [separator-tab]
         menu-bar-separator))
 
+    (bindings--define-key menu [undelete-last-deleted-frame]
+      '(menu-item "Undelete Frame" undelete-frame
+                  :help "Undelete last deleted frame"))
+
     ;; Don't use delete-frame as event name because that is a special
     ;; event.
     (bindings--define-key menu [delete-this-frame]
-- 
2.33.0


^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-16 15:17 ` Gregory Heytings
@ 2021-11-16 17:05   ` Gregory Heytings
  2021-11-16 17:40     ` Eli Zaretskii
  2021-11-16 20:30   ` Juri Linkov
  1 sibling, 1 reply; 51+ messages in thread
From: Gregory Heytings @ 2021-11-16 17:05 UTC (permalink / raw)
  To: 51883; +Cc: Michael Heerdegen

[-- Attachment #1: Type: text/plain, Size: 34 bytes --]


Slightly improved patch attached.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-diff; name=Undelete-deleted-frames.patch, Size: 7469 bytes --]

From fd68d27dde26788820ebdc3ad816cefb6a5c659b Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Tue, 16 Nov 2021 17:01:37 +0000
Subject: [PATCH] Undelete deleted frames.

* lisp/frame.el (undelete-frame): New command.
(undeleted-frame--save-deleted-frame): New auxiliary function.
(undelete-frame--deleted-frames): New auxiliary variable.
(make-frame-command): Add a prefix argument, and call the new
command.

* src/frame.c (Fdelete_frame): Update docstring.

* lisp/menu-bar.el (menu-bar-file-menu): Add an entry for the
new command.

* doc/emacs/frames.tex (Creating Frames, Frame Commands): Document
the new command.

* etc/NEWS: Document the new command.

See bug#51883.
---
 doc/emacs/frames.texi | 11 +++++---
 etc/NEWS              | 10 +++++++
 lisp/frame.el         | 61 +++++++++++++++++++++++++++++++++++++++----
 lisp/menu-bar.el      |  4 +++
 src/frame.c           |  3 +++
 5 files changed, 81 insertions(+), 8 deletions(-)

diff --git a/doc/emacs/frames.texi b/doc/emacs/frames.texi
index c14ada2957..f5ede6af5e 100644
--- a/doc/emacs/frames.texi
+++ b/doc/emacs/frames.texi
@@ -452,8 +452,11 @@ Creating Frames
 @item C-x 5 2
 @kindex C-x 5 2
 @findex make-frame-command
-Create a new frame using the default frame parameters
-(@code{make-frame-command}).
+Create a new frame using the default frame parameters, or, with a prefix
+argument, undelete one of the 16 most recently deleted frames
+(@code{make-frame-command}).  A prefix argument undeletes the last deleted
+frame, a numerical prefix argument between 0 and 15 undeletes the
+corresponding deleted frame, where 0 is the most recently deleted frame.
 
 @item C-x 5 c
 @kindex C-x 5 c
@@ -510,7 +513,9 @@ Frame Commands
 @kindex C-x 5 0
 @findex delete-frame
 Delete the selected frame (@code{delete-frame}).  This signals an
-error if there is only one frame.
+error if there is only one frame.  The 16 most recently deleted frames
+can be undeleted with the @kbd{C-x 5 2} command, when it is used with
+a prefix argument.
 
 @item C-z
 @kindex C-z @r{(X windows)}
diff --git a/etc/NEWS b/etc/NEWS
index 312fc18f4f..ab47f8cf5b 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -130,6 +130,16 @@ For example, a 'display-buffer-alist' entry of
 will make the body of the chosen window 40 columns wide.  For the
 height use 'window-height' in combination with 'body-lines'.
 
+---
+** Frames
+
++++
+*** With a prefix argument, the key 'C-x 5 2' undeletes deleted frames.
+The 16 most recently deleted frames can be undeleted.  A prefix argument
+undeletes the last deleted frame, a numerical prefix argument between 0
+and 15 undeletes the corresponding deleted frame, where 0 is the most
+recently deleted frame.
+
 ** Better detection of text suspiciously reordered on display.
 The function 'bidi-find-overridden-directionality' has been extended
 to detect reordering effects produced by embeddings and isolates
diff --git a/lisp/frame.el b/lisp/frame.el
index 2c73737a54..199e9e8005 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -775,16 +775,23 @@ close-display-connection
       (mapc #'delete-frame frames)
       (x-close-connection display))))
 
-(defun make-frame-command ()
+(defun make-frame-command (&optional arg)
   "Make a new frame, on the same terminal as the selected frame.
 If the terminal is a text-only terminal, this also selects the
 new frame.
 
+With a prefix argument ARG, undelete the most recently deleted
+frame.
+With a numerical prefix argument ARG between 0 and 15, undelete
+the ARGth deleted frame, where 0 is most recently deleted frame.
+
 When called from Lisp, returns the new frame."
-  (interactive)
-  (if (display-graphic-p)
-      (make-frame)
-    (select-frame (make-frame))))
+  (interactive "P")
+  (if arg
+      (undelete-frame arg)
+    (if (display-graphic-p)
+        (make-frame)
+      (select-frame (make-frame)))))
 
 (defun clone-frame (&optional frame no-windows)
   "Make a new frame with the same parameters and windows as FRAME.
@@ -2484,6 +2491,50 @@ delete-other-frames
         (if iconify (iconify-frame this) (delete-frame this)))
       (setq this next))))
 
+(eval-when-compile (require 'frameset))
+
+(defvar undelete-frame--deleted-frames nil
+  "Internal variable used by `undelete-frame--save-deleted-frame'.")
+
+(defun undeleted-frame--save-deleted-frame (frame)
+  "Save the configuration of frames deleted with `delete-frame'.
+Only the 16 most recently deleted frames are saved."
+  (when (frame-live-p frame)
+    (setq undelete-frame--deleted-frames
+          (cons (cons
+                 (display-graphic-p)
+                 (frameset-save (list frame)))
+                undelete-frame--deleted-frames))
+    (if (> (length undelete-frame--deleted-frames) 16)
+        (setq undelete-frame--deleted-frames
+              (butlast undelete-frame--deleted-frames)))))
+
+(add-hook 'delete-frame-functions #'undeleted-frame--save-deleted-frame)
+
+(defun undelete-frame (&optional arg)
+  "Undelete a frame deleted with `delete-frame'.
+When ARG is nil or a list, the last deleted frame is undeleted.
+When ARG is a number between 0 and 15, the ARGth deleted frame is
+undeleted.
+When called from Lisp, returns the new frame."
+  (interactive "P")
+  (let* ((frames (frame-list))
+         (n (if (listp arg) 0 arg))
+         (frameset (nth n undelete-frame--deleted-frames)))
+    (if (not frameset)
+        (message "No deleted frame saved at position %d" n)
+      (if (not (eq (display-graphic-p) (car frameset)))
+          (message
+           "Cannot undelete %sgraphic display frame on a %sgraphic display"
+           (if (display-graphic-p) "non-" "")
+           (if (display-graphic-p) "" "non-"))
+        (setq undelete-frame--deleted-frames
+              (delq frameset undelete-frame--deleted-frames))
+        (frameset-restore (cdr frameset))
+        (let ((frame (car (seq-difference (frame-list) frames))))
+          (when frame
+            (select-frame-set-input-focus frame)
+            frame))))))
 \f
 ;;; Window dividers.
 (defgroup window-divider nil
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index 1a81f1a3d0..a1e0195bb5 100644
--- a/lisp/menu-bar.el
+++ b/lisp/menu-bar.el
@@ -109,6 +109,10 @@ menu-bar-file-menu
       (bindings--define-key menu [separator-tab]
         menu-bar-separator))
 
+    (bindings--define-key menu [undelete-last-deleted-frame]
+      '(menu-item "Undelete Frame" undelete-frame
+                  :help "Undelete last deleted frame"))
+
     ;; Don't use delete-frame as event name because that is a special
     ;; event.
     (bindings--define-key menu [delete-this-frame]
diff --git a/src/frame.c b/src/frame.c
index 79a7c89e0d..9e11bc93ed 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -2376,6 +2376,9 @@ DEFUN ("delete-frame", Fdelete_frame, Sdelete_frame, 0, 2, "",
        doc: /* Delete FRAME, permanently eliminating it from use.
 FRAME must be a live frame and defaults to the selected one.
 
+The 16 most recently deleted frames can however be undeleted with
+`undelete-frame', which see.
+
 A frame may not be deleted if its minibuffer serves as surrogate
 minibuffer for another frame.  Normally, you may not delete a frame if
 all other frames are invisible, but if the second optional argument
-- 
2.33.0


^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-16 17:05   ` Gregory Heytings
@ 2021-11-16 17:40     ` Eli Zaretskii
  2021-11-16 21:29       ` Gregory Heytings
  0 siblings, 1 reply; 51+ messages in thread
From: Eli Zaretskii @ 2021-11-16 17:40 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: michael_heerdegen, 51883

> Date: Tue, 16 Nov 2021 17:05:07 +0000
> From: Gregory Heytings <gregory@heytings.org>
> Cc: Michael Heerdegen <michael_heerdegen@web.de>
> 
> Slightly improved patch attached.

Thanks, I have some comments below.

> +(@code{make-frame-command}).  A prefix argument undeletes the last deleted
> +frame, a numerical prefix argument between 0 and 15 undeletes the
> +corresponding deleted frame, where 0 is the most recently deleted frame.

This is unusual meaning of prefix argument.  Why not use zero for the
last, 1 for the one before that, etc.?

> +---
> +** Frames
> +
> ++++
> +*** With a prefix argument, the key 'C-x 5 2' undeletes deleted frames.

I would make the heading shorter and more to the point:

  *** Deleted frames can now be undeleted.

> +(eval-when-compile (require 'frameset))
> +
> +(defvar undelete-frame--deleted-frames nil
> +  "Internal variable used by `undelete-frame--save-deleted-frame'.")
> +
> +(defun undeleted-frame--save-deleted-frame (frame)
> +  "Save the configuration of frames deleted with `delete-frame'.
> +Only the 16 most recently deleted frames are saved."
> +  (when (frame-live-p frame)
> +    (setq undelete-frame--deleted-frames
> +          (cons (cons
> +                 (display-graphic-p)
> +                 (frameset-save (list frame)))
> +                undelete-frame--deleted-frames))
> +    (if (> (length undelete-frame--deleted-frames) 16)
> +        (setq undelete-frame--deleted-frames
> +              (butlast undelete-frame--deleted-frames)))))
> +
> +(add-hook 'delete-frame-functions #'undeleted-frame--save-deleted-frame)

I'd rather we didn't do that by default.  Several reasons:

  . the startup code deletes the terminal frame, so the above means we
    will always load frameset, which is not a small package, at
    startup, even if the user has no use for this functionality
  . using add-hook in Emacs's own code _by_default_ is not a good
    style; hooks are for customizing the default behavior
  . saving configurations of 16 deleted frames _by_default_ means we
    again impose on all users something that only some of them will
    use

So I'd suggest instead making this an opt-in feature or maybe even
minor mode.  Only when turned on should we save away the deleted
frames.

(And did you consider wrapping this into some history-like feature,
where users could interactively select which past frame to restore?)

> +The 16 most recently deleted frames can however be undeleted with
> +`undelete-frame', which see.

The "however" part is "out of the blue" here; I'd drop it.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-16  8:49 ` Visuwesh via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-16 20:20   ` Juri Linkov
  0 siblings, 0 replies; 51+ messages in thread
From: Juri Linkov @ 2021-11-16 20:20 UTC (permalink / raw)
  To: Visuwesh; +Cc: 51883

>>> Maybe having it in C would be better - I don't know.
>> To avoid implementing the whole function in C,
>> 'delete-frame' could call a hook with the frame as its arg,
>> then you can add your code to the hook.
>
> Doesn't Emacs already provide this?  See `delete-frame-functions'.

Thanks for mentioning `delete-frame-functions', then
we have everything necessary to implement this feature.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-16 15:17 ` Gregory Heytings
  2021-11-16 17:05   ` Gregory Heytings
@ 2021-11-16 20:30   ` Juri Linkov
  1 sibling, 0 replies; 51+ messages in thread
From: Juri Linkov @ 2021-11-16 20:30 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: Michael Heerdegen, 51883

> +*** With a prefix argument, the key 'C-x 5 2' undeletes deleted frames.

Recently we had a dispute whether the prefix argument of 'C-x 5 2'
should be used to clone the frame, and now you propose another
meaning of its argument :)

But to avoid conflicts, like 'clone-frame' is bound to 'C-x 5 c',
'undelete-frame' could be bound to 'C-x 5 u'.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-16  7:53 ` Juri Linkov
  2021-11-16  8:14   ` Lars Ingebrigtsen
@ 2021-11-16 20:46   ` Juri Linkov
  1 sibling, 0 replies; 51+ messages in thread
From: Juri Linkov @ 2021-11-16 20:46 UTC (permalink / raw)
  To: 51883

> I haven't yet tested whether your implementation restores the tab-bar
> as well, or maybe it would require more handling.  I suspect that
> instead of framesets you might need to use window-state-get/window-state-put
> like it's used in 'clone-frame'.

Now I tested the recently added 'clone-frame' with the tab-bar,
and indeed cloning a frame with the tab-bar breaks the tabs
in the cloned frame, and breaks the tabs on the original frame.
The fix is pushed now in c25be3e7bb.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-16 17:40     ` Eli Zaretskii
@ 2021-11-16 21:29       ` Gregory Heytings
  2021-11-17 10:02         ` Gregory Heytings
  2021-11-17 13:11         ` Eli Zaretskii
  0 siblings, 2 replies; 51+ messages in thread
From: Gregory Heytings @ 2021-11-16 21:29 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: michael_heerdegen, 51883, Juri Linkov

[-- Attachment #1: Type: text/plain, Size: 2463 bytes --]


Thanks for your comments, and thanks to Juri for his suggestion.

Updated patch attached, which incorporates your comments and Juri's 
suggestion.

>> +(@code{make-frame-command}).  A prefix argument undeletes the last deleted
>> +frame, a numerical prefix argument between 0 and 15 undeletes the
>> +corresponding deleted frame, where 0 is the most recently deleted frame.
>
> This is unusual meaning of prefix argument.  Why not use zero for the 
> last, 1 for the one before that, etc.?
>

You mean: 0 for the least recently deleted one, and 15 for the most 
recently deleted one?  So to recover the frame you just deleted by 
accident, you'd have to type C-u 15 C-x 5 u?  That seems unnatural to me, 
but perhaps it's just me.

>
> +(add-hook 'delete-frame-functions #'undeleted-frame--save-deleted-frame)
>
> I'd rather we didn't do that by default.  Several reasons:
>
> . the startup code deletes the terminal frame, so the above means we will always load frameset, which is not a small package, at startup, even if the user has no use for this functionality
> . using add-hook in Emacs's own code _by_default_ is not a good style; hooks are for customizing the default behavior
> . saving configurations of 16 deleted frames _by_default_ means we again impose on all users something that only some of them will use
>
> So I'd suggest instead making this an opt-in feature or maybe even minor 
> mode.  Only when turned on should we save away the deleted frames.
>

A minor mode is another option, indeed.  My feeling is that this feature 
is something about everyone would find useful, and that the cost you 
mention is not that high.  And I solved the problem of the deletion of the 
terminal frame.  So I made it an opt-out minor-mode.

>
> (And did you consider wrapping this into some history-like feature, 
> where users could interactively select which past frame to restore?)
>

Yes, I did consider this, but did not really know if it would be worth 
doing that.  Apparently it is ;-)  It's not yet clear to me how one could 
select one of the frames in a meaningful way.  I'll try to do that later.

>> +The 16 most recently deleted frames can however be undeleted with
>> +`undelete-frame', which see.
>
> The "however" part is "out of the blue" here; I'd drop it.
>

I see what you mean, but it's not out of the blue, it's meant to balance 
the "permanently eliminating" in "Delete FRAME, permanently eliminating it 
from use." two lines above.

[-- Attachment #2: Type: text/x-diff, Size: 7256 bytes --]

From 6bd40494c6a1e6c27a8379146b77027e49048bbf Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Tue, 16 Nov 2021 21:19:18 +0000
Subject: [PATCH] Undelete deleted frames.

* lisp/frame.el (undelete-frame): New command.
(undeleted-frame--save-deleted-frame): New auxiliary function.
(undelete-frame--deleted-frames): New auxiliary variable.
(undelete-frame-mode): New minor mode.
(ctl-x-5-map): Bind the new command.

* src/frame.c (Fdelete_frame): Update docstring.

* lisp/menu-bar.el (menu-bar-file-menu): Add an entry for the
new command.

* doc/emacs/frames.tex (Frame Commands): Document the new command
and minor mode.

* etc/NEWS: Document the new command and minor mode.

See bug#51883.
---
 doc/emacs/frames.texi | 10 ++++++++
 etc/NEWS              | 11 ++++++++
 lisp/frame.el         | 60 +++++++++++++++++++++++++++++++++++++++++++
 lisp/menu-bar.el      |  5 ++++
 src/frame.c           |  3 +++
 5 files changed, 89 insertions(+)

diff --git a/doc/emacs/frames.texi b/doc/emacs/frames.texi
index c14ada2957..296b66aa56 100644
--- a/doc/emacs/frames.texi
+++ b/doc/emacs/frames.texi
@@ -512,6 +512,16 @@ Frame Commands
 Delete the selected frame (@code{delete-frame}).  This signals an
 error if there is only one frame.
 
+@item C-x 5 u
+@kindex C-x 5 u
+@findex undelete-frame
+@findex undelete-frame-mode
+Unless @code{undelete-frame-mode} is disabled, undelete one of the 16
+most recently deleted frames.  Without a prefix argument, the most
+recently deleted frame is undeleted.  With a numerical prefix argument
+between 0 and 15, where 0 is the most recently deleted frame, the
+corresponding deleted frame is undeleted.
+
 @item C-z
 @kindex C-z @r{(X windows)}
 Minimize (or iconify) the selected Emacs frame
diff --git a/etc/NEWS b/etc/NEWS
index 312fc18f4f..0217f1fe60 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -130,6 +130,17 @@ For example, a 'display-buffer-alist' entry of
 will make the body of the chosen window 40 columns wide.  For the
 height use 'window-height' in combination with 'body-lines'.
 
+---
+** Frames
+
++++
+*** Deleted frames can now be undeleted.
+The 16 most recently deleted frames can be undeleted with C-x 5 u,
+unless undelete-frame-mode is disabled.  Without a prefix argument,
+undelete the most recently deleted frame.  A numerical prefix argument
+between 0 and 15 undeletes the corresponding deleted frame, where 0 is
+the most recently deleted frame.
+
 ** Better detection of text suspiciously reordered on display.
 The function 'bidi-find-overridden-directionality' has been extended
 to detect reordering effects produced by embeddings and isolates
diff --git a/lisp/frame.el b/lisp/frame.el
index 2c73737a54..e854197008 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -2484,6 +2484,65 @@ delete-other-frames
         (if iconify (iconify-frame this) (delete-frame this)))
       (setq this next))))
 
+(eval-when-compile (require 'frameset))
+
+(defvar undelete-frame--deleted-frames nil
+  "Internal variable used by `undelete-frame--save-deleted-frame'.")
+
+(defun undeleted-frame--save-deleted-frame (frame)
+  "Save the configuration of frames deleted with `delete-frame'.
+Only the 16 most recently deleted frames are saved."
+  (when (and after-init-time
+             (frame-live-p frame))
+    (setq undelete-frame--deleted-frames
+          (cons (cons
+                 (display-graphic-p)
+                 (frameset-save (list frame)))
+                undelete-frame--deleted-frames))
+    (if (> (length undelete-frame--deleted-frames) 16)
+        (setq undelete-frame--deleted-frames
+              (butlast undelete-frame--deleted-frames)))))
+
+(define-minor-mode undelete-frame-mode
+  "Enable the `delete-frame' command."
+  :group 'frames
+  :global t
+  :initialize 'custom-initialize-delay
+  :init-value t
+  (if undelete-frame-mode
+      (add-hook 'delete-frame-functions
+                #'undeleted-frame--save-deleted-frame)
+    (remove-hook 'delete-frame-functions
+                 #'undeleted-frame--save-deleted-frame)
+    (setq undelete-frame--deleted-frames nil)))
+
+(defun undelete-frame (&optional arg)
+  "Undelete a frame deleted with `delete-frame'.
+With a prefix argument ARG, undelete the most recently deleted
+frame.
+With a numerical prefix argument ARG between 0 and 15, undelete
+the ARGth deleted frame, where 0 is most recently deleted frame.
+When called from Lisp, returns the new frame."
+  (interactive "P")
+  (if (not undelete-frame-mode)
+      (message "undelete-frame-mode is disabled")
+    (let* ((frames (frame-list))
+           (n (if (listp arg) 0 arg))
+           (frameset (nth n undelete-frame--deleted-frames)))
+      (if (not frameset)
+          (message "No deleted frame saved at position %d" n)
+        (if (not (eq (display-graphic-p) (car frameset)))
+            (message
+             "Cannot undelete %sgraphic display frame on a %sgraphic display"
+             (if (display-graphic-p) "non-" "")
+             (if (display-graphic-p) "" "non-"))
+          (setq undelete-frame--deleted-frames
+                (delq frameset undelete-frame--deleted-frames))
+          (frameset-restore (cdr frameset))
+          (let ((frame (car (seq-difference (frame-list) frames))))
+            (when frame
+              (select-frame-set-input-focus frame)
+              frame)))))))
 \f
 ;;; Window dividers.
 (defgroup window-divider nil
@@ -2828,6 +2887,7 @@ ctl-x-5-map
 (define-key ctl-x-5-map "o" #'other-frame)
 (define-key ctl-x-5-map "5" #'other-frame-prefix)
 (define-key ctl-x-5-map "c" #'clone-frame)
+(define-key ctl-x-5-map "u" #'undelete-frame)
 (define-key global-map [f11] #'toggle-frame-fullscreen)
 (define-key global-map [(meta f10)] #'toggle-frame-maximized)
 (define-key esc-map    [f10]        #'toggle-frame-maximized)
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index 1a81f1a3d0..a5f7169355 100644
--- a/lisp/menu-bar.el
+++ b/lisp/menu-bar.el
@@ -109,6 +109,11 @@ menu-bar-file-menu
       (bindings--define-key menu [separator-tab]
         menu-bar-separator))
 
+    (bindings--define-key menu [undelete-last-deleted-frame]
+      '(menu-item "Undelete Frame" undelete-frame
+                  :enable undelete-frame-mode
+                  :help "Undelete last deleted frame"))
+
     ;; Don't use delete-frame as event name because that is a special
     ;; event.
     (bindings--define-key menu [delete-this-frame]
diff --git a/src/frame.c b/src/frame.c
index 79a7c89e0d..9e11bc93ed 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -2376,6 +2376,9 @@ DEFUN ("delete-frame", Fdelete_frame, Sdelete_frame, 0, 2, "",
        doc: /* Delete FRAME, permanently eliminating it from use.
 FRAME must be a live frame and defaults to the selected one.
 
+The 16 most recently deleted frames can however be undeleted with
+`undelete-frame', which see.
+
 A frame may not be deleted if its minibuffer serves as surrogate
 minibuffer for another frame.  Normally, you may not delete a frame if
 all other frames are invisible, but if the second optional argument
-- 
2.33.0


^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-15 23:38 bug#51883: 29.0.50; Command to get accidentally deleted frames back Michael Heerdegen
                   ` (2 preceding siblings ...)
  2021-11-16 15:17 ` Gregory Heytings
@ 2021-11-17  4:13 ` Richard Stallman
  2021-11-17 10:07   ` Gregory Heytings
  2021-11-17 16:39   ` bug#51883: [External] : " Drew Adams
  3 siblings, 2 replies; 51+ messages in thread
From: Richard Stallman @ 2021-11-17  4:13 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: 51883

[[[ To any NSA and FBI agents reading my email: please consider    ]]]
[[[ whether defending the US Constitution against all enemies,     ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]

The command sounds useful, but I worry about one possible problem:
how much garbage will the to-be-restored frame hold onto?
Please investigate this and see whether it is a significant issue or not.

-- 
Dr Richard Stallman (https://stallman.org)
Chief GNUisance of the GNU Project (https://gnu.org)
Founder, Free Software Foundation (https://fsf.org)
Internet Hall-of-Famer (https://internethalloffame.org)







^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-16 21:29       ` Gregory Heytings
@ 2021-11-17 10:02         ` Gregory Heytings
  2021-11-17 13:11         ` Eli Zaretskii
  1 sibling, 0 replies; 51+ messages in thread
From: Gregory Heytings @ 2021-11-17 10:02 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: michael_heerdegen, 51883, Juri Linkov

[-- Attachment #1: Type: text/plain, Size: 240 bytes --]


>
> And I solved the problem of the deletion of the terminal frame.
>

That solution wasn't robust enough, it would have failed if someone for 
some reason did a (setq after-init-time nil).  So I implemented something 
that shouldn't fail.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-diff; name=Undelete-deleted-frames.patch, Size: 7684 bytes --]

From 2fc329b63b64f2e53c3040c8f5cfd0b1f4667e07 Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Wed, 17 Nov 2021 10:01:15 +0000
Subject: [PATCH] Undelete deleted frames.

* lisp/frame.el (undelete-frame): New command.
(undelete-frame--handle-delete-frame): New auxiliary function.
(undelete-frame--deleted-frames, undelete-frame--enabled): New
auxiliary variables.
(undelete-frame-mode): New minor mode.
(ctl-x-5-map): Bind the new command.

* src/frame.c (Fdelete_frame): Update docstring.

* lisp/menu-bar.el (menu-bar-file-menu): Add an entry for the
new command.

* doc/emacs/frames.tex (Frame Commands): Document the new command
and minor mode.

* etc/NEWS: Document the new command and minor mode.

See bug#51883.
---
 doc/emacs/frames.texi | 10 +++++++
 etc/NEWS              | 11 +++++++
 lisp/frame.el         | 68 +++++++++++++++++++++++++++++++++++++++++++
 lisp/menu-bar.el      |  5 ++++
 src/frame.c           |  3 ++
 5 files changed, 97 insertions(+)

diff --git a/doc/emacs/frames.texi b/doc/emacs/frames.texi
index c14ada2957..296b66aa56 100644
--- a/doc/emacs/frames.texi
+++ b/doc/emacs/frames.texi
@@ -512,6 +512,16 @@ Frame Commands
 Delete the selected frame (@code{delete-frame}).  This signals an
 error if there is only one frame.
 
+@item C-x 5 u
+@kindex C-x 5 u
+@findex undelete-frame
+@findex undelete-frame-mode
+Unless @code{undelete-frame-mode} is disabled, undelete one of the 16
+most recently deleted frames.  Without a prefix argument, the most
+recently deleted frame is undeleted.  With a numerical prefix argument
+between 0 and 15, where 0 is the most recently deleted frame, the
+corresponding deleted frame is undeleted.
+
 @item C-z
 @kindex C-z @r{(X windows)}
 Minimize (or iconify) the selected Emacs frame
diff --git a/etc/NEWS b/etc/NEWS
index 312fc18f4f..0217f1fe60 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -130,6 +130,17 @@ For example, a 'display-buffer-alist' entry of
 will make the body of the chosen window 40 columns wide.  For the
 height use 'window-height' in combination with 'body-lines'.
 
+---
+** Frames
+
++++
+*** Deleted frames can now be undeleted.
+The 16 most recently deleted frames can be undeleted with C-x 5 u,
+unless undelete-frame-mode is disabled.  Without a prefix argument,
+undelete the most recently deleted frame.  A numerical prefix argument
+between 0 and 15 undeletes the corresponding deleted frame, where 0 is
+the most recently deleted frame.
+
 ** Better detection of text suspiciously reordered on display.
 The function 'bidi-find-overridden-directionality' has been extended
 to detect reordering effects produced by embeddings and isolates
diff --git a/lisp/frame.el b/lisp/frame.el
index 2c73737a54..1601ed1c44 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -2484,6 +2484,73 @@ delete-other-frames
         (if iconify (iconify-frame this) (delete-frame this)))
       (setq this next))))
 
+(eval-when-compile (require 'frameset))
+
+(defvar undelete-frame--deleted-frames nil
+  "Internal variable used by `undelete-frame--handle-delete-frame'.")
+
+(defvar undelete-frame--enabled nil
+  "Internal variable used by `undelete-frame--handle-delete-frame'.")
+
+(defun undelete-frame--handle-delete-frame (frame)
+  "Save the configuration of frames deleted with `delete-frame'.
+Only the 16 most recently deleted frames are saved."
+  (when (and (or undelete-frame--enabled
+                 (and (not (display-graphic-p))
+                      (seq-every-p
+                       (lambda (f) (not (frame-parameter f 'display)))
+                       (frame-list))))
+             (frame-live-p frame))
+    (setq undelete-frame--deleted-frames
+          (cons (cons
+                 (display-graphic-p)
+                 (frameset-save (list frame)))
+                undelete-frame--deleted-frames))
+    (if (> (length undelete-frame--deleted-frames) 16)
+        (setq undelete-frame--deleted-frames
+              (butlast undelete-frame--deleted-frames))))
+  (setq undelete-frame--enabled t))
+
+(define-minor-mode undelete-frame-mode
+  "Enable the `delete-frame' command."
+  :group 'frames
+  :global t
+  :initialize 'custom-initialize-delay
+  :init-value t
+  (if undelete-frame-mode
+      (add-hook 'delete-frame-functions
+                #'undelete-frame--handle-delete-frame)
+    (remove-hook 'delete-frame-functions
+                 #'undelete-frame--handle-delete-frame)
+    (setq undelete-frame--deleted-frames nil)))
+
+(defun undelete-frame (&optional arg)
+  "Undelete a frame deleted with `delete-frame'.
+Without a prefix argument, or with a non-numerical prefix argument,
+undelete the most recently deleted frame.
+With a numerical prefix argument ARG between 0 and 15, where 0 is
+most recently deleted frame, undelete the ARGth deleted frame.
+When called from Lisp, returns the new frame."
+  (interactive "P")
+  (if (not undelete-frame-mode)
+      (message "undelete-frame-mode is disabled")
+    (let* ((frames (frame-list))
+           (n (if (listp arg) 0 arg))
+           (frameset (nth n undelete-frame--deleted-frames)))
+      (if (not frameset)
+          (message "No deleted frame saved at position %d" n)
+        (if (not (eq (display-graphic-p) (car frameset)))
+            (message
+             "Cannot undelete %sgraphic display frame on a %sgraphic display"
+             (if (display-graphic-p) "non-" "")
+             (if (display-graphic-p) "" "non-"))
+          (setq undelete-frame--deleted-frames
+                (delq frameset undelete-frame--deleted-frames))
+          (frameset-restore (cdr frameset))
+          (let ((frame (car (seq-difference (frame-list) frames))))
+            (when frame
+              (select-frame-set-input-focus frame)
+              frame)))))))
 \f
 ;;; Window dividers.
 (defgroup window-divider nil
@@ -2828,6 +2895,7 @@ ctl-x-5-map
 (define-key ctl-x-5-map "o" #'other-frame)
 (define-key ctl-x-5-map "5" #'other-frame-prefix)
 (define-key ctl-x-5-map "c" #'clone-frame)
+(define-key ctl-x-5-map "u" #'undelete-frame)
 (define-key global-map [f11] #'toggle-frame-fullscreen)
 (define-key global-map [(meta f10)] #'toggle-frame-maximized)
 (define-key esc-map    [f10]        #'toggle-frame-maximized)
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index 1a81f1a3d0..a5f7169355 100644
--- a/lisp/menu-bar.el
+++ b/lisp/menu-bar.el
@@ -109,6 +109,11 @@ menu-bar-file-menu
       (bindings--define-key menu [separator-tab]
         menu-bar-separator))
 
+    (bindings--define-key menu [undelete-last-deleted-frame]
+      '(menu-item "Undelete Frame" undelete-frame
+                  :enable undelete-frame-mode
+                  :help "Undelete last deleted frame"))
+
     ;; Don't use delete-frame as event name because that is a special
     ;; event.
     (bindings--define-key menu [delete-this-frame]
diff --git a/src/frame.c b/src/frame.c
index 79a7c89e0d..9e11bc93ed 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -2376,6 +2376,9 @@ DEFUN ("delete-frame", Fdelete_frame, Sdelete_frame, 0, 2, "",
        doc: /* Delete FRAME, permanently eliminating it from use.
 FRAME must be a live frame and defaults to the selected one.
 
+The 16 most recently deleted frames can however be undeleted with
+`undelete-frame', which see.
+
 A frame may not be deleted if its minibuffer serves as surrogate
 minibuffer for another frame.  Normally, you may not delete a frame if
 all other frames are invisible, but if the second optional argument
-- 
2.33.0


^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-17  4:13 ` Richard Stallman
@ 2021-11-17 10:07   ` Gregory Heytings
  2021-11-17 16:39   ` bug#51883: [External] : " Drew Adams
  1 sibling, 0 replies; 51+ messages in thread
From: Gregory Heytings @ 2021-11-17 10:07 UTC (permalink / raw)
  To: Richard Stallman; +Cc: 51883


>
> The command sounds useful, but I worry about one possible problem: how 
> much garbage will the to-be-restored frame hold onto? Please investigate 
> this and see whether it is a significant issue or not.
>

It's not significant.  The frames are not kept in memory for later 
possible reuse, only a description of their state (size, windows, which 
buffer is displayed in which window) is kept in memory, which occupies a 
few kilobytes for each deleted frame.  That's much less than the memory 
used by undo, for example.  Moreover that state would not be kept for all 
deleted frames, but only for a the most recently deleted ones.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-16 21:29       ` Gregory Heytings
  2021-11-17 10:02         ` Gregory Heytings
@ 2021-11-17 13:11         ` Eli Zaretskii
  2021-11-17 17:06           ` Juri Linkov
  2021-11-19  9:00           ` Gregory Heytings
  1 sibling, 2 replies; 51+ messages in thread
From: Eli Zaretskii @ 2021-11-17 13:11 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: michael_heerdegen, 51883, juri

> Date: Tue, 16 Nov 2021 21:29:35 +0000
> From: Gregory Heytings <gregory@heytings.org>
> cc: 51883@debbugs.gnu.org, Juri Linkov <juri@linkov.net>, 
>     michael_heerdegen@web.de
> 
> Updated patch attached, which incorporates your comments and Juri's 
> suggestion.
> 
> > This is unusual meaning of prefix argument.  Why not use zero for the 
> > last, 1 for the one before that, etc.?
> 
> You mean: 0 for the least recently deleted one, and 15 for the most 
> recently deleted one?  So to recover the frame you just deleted by 
> accident, you'd have to type C-u 15 C-x 5 u?  That seems unnatural to me, 
> but perhaps it's just me.

No, I mean "last" as in "the most recently deleted one".  Sorry for
being unclear.

Usually, commands that use both numeric and raw prefix arg do
something very different with the raw argument, which is not the case
here.

> A minor mode is another option, indeed.  My feeling is that this feature 
> is something about everyone would find useful, and that the cost you 
> mention is not that high.  And I solved the problem of the deletion of the 
> terminal frame.  So I made it an opt-out minor-mode.

I'd prefer to make it opt-in.  I see no reason to force on everyone a
new feature that doesn't sound like it's urgent or important enough to
justify the behavior change.  Even though the memory it uses is not
large, it's still memory, and it still increases consing each time a
frame is deleted.  For example, some people turn on all kinds of optional
features that pop up new frames in many situations, and who knows what
this will cause in those usage patterns.  Why risk such unintended
consequences on behalf of a minor feature?

As a nice bonus, making it opt-in will also allow to make the
implementation cleaner: no need for special handling of the initial
frame etc.

> > The "however" part is "out of the blue" here; I'd drop it.
> >
> 
> I see what you mean, but it's not out of the blue, it's meant to balance 
> the "permanently eliminating" in "Delete FRAME, permanently eliminating it 
> from use." two lines above.

> +                 (and (not (display-graphic-p))
> +                      (seq-every-p
> +                       (lambda (f) (not (frame-parameter f 'display)))
> +                       (frame-list))))

This should have a comment explaining what it does and why.  (And I
hope we will be able to avoid doing that in the first place.)

Also, what happens with the daemon frame if this function is invoked
from a GUI frame?

> +Without a prefix argument, or with a non-numerical prefix argument,

This is better rephrased as

  ... or with just \\[universal-argument], ...

since "non-numerical prefix argument" will not necessarily be clear to
everyone quickly enough.

> +    (bindings--define-key menu [undelete-last-deleted-frame]
> +      '(menu-item "Undelete Frame" undelete-frame
> +                  :enable undelete-frame-mode
> +                  :help "Undelete last deleted frame"))

How about using "restore" instead of "undelete", here and everywhere
else?  I think it's a tad more clear, and also easier to understand,
as it doesn't use negative tense.

>         doc: /* Delete FRAME, permanently eliminating it from use.
>  FRAME must be a live frame and defaults to the selected one.
>  
> +The 16 most recently deleted frames can however be undeleted with
> +`undelete-frame', which see.

If "however" is because of "permanently", I'd rather we lost both.  It
makes little sense to say something and then contradict ourselves 2
sentences later.

The doc string should also mention the minor mode, because without it
the added sentence is inaccurate.

Thanks.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: [External] : bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-17  4:13 ` Richard Stallman
  2021-11-17 10:07   ` Gregory Heytings
@ 2021-11-17 16:39   ` Drew Adams
  1 sibling, 0 replies; 51+ messages in thread
From: Drew Adams @ 2021-11-17 16:39 UTC (permalink / raw)
  To: rms, Michael Heerdegen; +Cc: 51883

> The command sounds useful, but I worry about one possible problem:
> how much garbage will the to-be-restored frame hold onto?
> Please investigate this and see whether it is a significant issue or not.

That was my first thought too.  Some people use
few frames over an Emacs session.  Some others
use many.

At least, IIUC, a user can set the ring size,
to control how many deleted frames to save.

That should be sufficient (including the
possibility of setting the size to zero).

^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-17 13:11         ` Eli Zaretskii
@ 2021-11-17 17:06           ` Juri Linkov
  2021-11-17 17:14             ` Eli Zaretskii
  2021-11-19  9:00           ` Gregory Heytings
  1 sibling, 1 reply; 51+ messages in thread
From: Juri Linkov @ 2021-11-17 17:06 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: michael_heerdegen, Gregory Heytings, 51883

>> +    (bindings--define-key menu [undelete-last-deleted-frame]
>> +      '(menu-item "Undelete Frame" undelete-frame
>> +                  :enable undelete-frame-mode
>> +                  :help "Undelete last deleted frame"))
>
> How about using "restore" instead of "undelete", here and everywhere
> else?  I think it's a tad more clear, and also easier to understand,
> as it doesn't use negative tense.

Michael pointed out that "restore" is a too confusing name
where "restore" is opposite to making a frame fullscreen.
OTOH, "undelete" is similar to "undo", so "undelete" better explains
what it does.  And the main advantage of the name "undelete-frame"
is that it's immediately clear that it's opposite to "delete-frame".





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-17 17:06           ` Juri Linkov
@ 2021-11-17 17:14             ` Eli Zaretskii
  0 siblings, 0 replies; 51+ messages in thread
From: Eli Zaretskii @ 2021-11-17 17:14 UTC (permalink / raw)
  To: Juri Linkov; +Cc: michael_heerdegen, gregory, 51883

> From: Juri Linkov <juri@linkov.net>
> Cc: Gregory Heytings <gregory@heytings.org>,  51883@debbugs.gnu.org,
>   michael_heerdegen@web.de
> Date: Wed, 17 Nov 2021 19:06:06 +0200
> 
> >> +    (bindings--define-key menu [undelete-last-deleted-frame]
> >> +      '(menu-item "Undelete Frame" undelete-frame
> >> +                  :enable undelete-frame-mode
> >> +                  :help "Undelete last deleted frame"))
> >
> > How about using "restore" instead of "undelete", here and everywhere
> > else?  I think it's a tad more clear, and also easier to understand,
> > as it doesn't use negative tense.
> 
> Michael pointed out that "restore" is a too confusing name
> where "restore" is opposite to making a frame fullscreen.

And I raised a brow when reading that.  "Restore" is a very far cry
from "fullscreen".  OTOH, browsers that keep history use "restore", so
I thought it will be a better terminology here.

> OTOH, "undelete" is similar to "undo", so "undelete" better explains
> what it does.

The difference is that "undo" is widely accepted terminology for what
that does.

> And the main advantage of the name "undelete-frame" is that it's
> immediately clear that it's opposite to "delete-frame".

But undelete-frame (the command) is NOT the opposite of delete-frame,
because it doesn't just delete the last frame you deleted.

Anyway, I don't intend to argue more; if you are unhappy with my
proposal, so be it.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-17 13:11         ` Eli Zaretskii
  2021-11-17 17:06           ` Juri Linkov
@ 2021-11-19  9:00           ` Gregory Heytings
  2021-11-19 12:17             ` Eli Zaretskii
  1 sibling, 1 reply; 51+ messages in thread
From: Gregory Heytings @ 2021-11-19  9:00 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: michael_heerdegen, 51883, juri

[-- Attachment #1: Type: text/plain, Size: 2325 bytes --]


Thanks again for your comments.

I attach an updated and improved patch.

>>> This is unusual meaning of prefix argument.  Why not use zero for the 
>>> last, 1 for the one before that, etc.?
>
> Usually, commands that use both numeric and raw prefix arg do something 
> very different with the raw argument, which is not the case here.
>

The last version of the patch now does that, too: no argument and a raw 
argument means "most recent", a numeric argument means "Nth".

>> A minor mode is another option, indeed.  My feeling is that this 
>> feature is something about everyone would find useful, and that the 
>> cost you mention is not that high.  And I solved the problem of the 
>> deletion of the terminal frame.  So I made it an opt-out minor-mode.
>
> I'd prefer to make it opt-in.  I see no reason to force on everyone a 
> new feature that doesn't sound like it's urgent or important enough to 
> justify the behavior change.  Even though the memory it uses is not 
> large, it's still memory, and it still increases consing each time a 
> frame is deleted.  For example, some people turn on all kinds of 
> optional features that pop up new frames in many situations, and who 
> knows what this will cause in those usage patterns.  Why risk such 
> unintended consequences on behalf of a minor feature?
>

I hear your arguments, but IMO that would be like making "undo" opt-in. 
I also wouldn't call that a "minor" feature, it's worth an entry in the 
File menu.  Indeed we don't know what this could cause with exotic usage 
patterns, so I suggest, given that the release of Emacs 29 is far away in 
the future, to make it opt-out on the trunk, and if someone protests 
because it breaks their usage pattern before Emacs 29 is released, to make 
it opt-in instead.

>
> How about using "restore" instead of "undelete", here and everywhere 
> else?  I think it's a tad more clear, and also easier to understand, as 
> it doesn't use negative tense.
>

I agree with Juri here.  In another app, I would have named this "Restore 
Window", but Emacs uses "Delete Frame" where other apps would use "Close 
Window", so using "Restore Frame" would be much less clear than "Undelete 
Frame".  In particular, it would not be clear that "Restore" creates a new 
frame and does not do something with the current frame.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-diff; name=Undelete-deleted-frames.patch, Size: 8744 bytes --]

From 35ae03480511c16fb9657e4c661278322d7ccdec Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Fri, 19 Nov 2021 08:50:49 +0000
Subject: [PATCH] Undelete deleted frames.

* lisp/frame.el (undelete-frame): New command.
(undelete-frame--handle-delete-frame): New auxiliary function.
(undelete-frame--deleted-frames, undelete-frame--enabled): New
auxiliary variables.
(undelete-frame-mode): New minor mode.
(ctl-x-5-map): Bind the new command.

* src/frame.c (Fdelete_frame): Update docstring, and mention the
minor mode.

* lisp/menu-bar.el (menu-bar-file-menu): Add an entry for the
new command.

* doc/emacs/frames.tex (Frame Commands): Document the new command
and minor mode.

* etc/NEWS: Document the new command and minor mode.

See bug#51883.
---
 doc/emacs/frames.texi | 10 +++++
 etc/NEWS              | 10 +++++
 lisp/frame.el         | 88 +++++++++++++++++++++++++++++++++++++++++++
 lisp/menu-bar.el      |  5 +++
 src/frame.c           |  5 ++-
 5 files changed, 117 insertions(+), 1 deletion(-)

diff --git a/doc/emacs/frames.texi b/doc/emacs/frames.texi
index c14ada2957..296b66aa56 100644
--- a/doc/emacs/frames.texi
+++ b/doc/emacs/frames.texi
@@ -512,6 +512,16 @@ Frame Commands
 Delete the selected frame (@code{delete-frame}).  This signals an
 error if there is only one frame.
 
+@item C-x 5 u
+@kindex C-x 5 u
+@findex undelete-frame
+@findex undelete-frame-mode
+Unless @code{undelete-frame-mode} is disabled, undelete one of the 16
+most recently deleted frames.  Without a prefix argument, the most
+recently deleted frame is undeleted.  With a numerical prefix argument
+between 0 and 15, where 0 is the most recently deleted frame, the
+corresponding deleted frame is undeleted.
+
 @item C-z
 @kindex C-z @r{(X windows)}
 Minimize (or iconify) the selected Emacs frame
diff --git a/etc/NEWS b/etc/NEWS
index 312fc18f4f..51cfe16677 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -130,6 +130,16 @@ For example, a 'display-buffer-alist' entry of
 will make the body of the chosen window 40 columns wide.  For the
 height use 'window-height' in combination with 'body-lines'.
 
+** Frames
+
++++
+*** Deleted frames can now be undeleted.
+The 16 most recently deleted frames can be undeleted with C-x 5 u,
+unless undelete-frame-mode is disabled.  Without a prefix argument,
+undelete the most recently deleted frame.  A numerical prefix argument
+between 0 and 15 undeletes the corresponding deleted frame, where 0 is
+the most recently deleted frame.
+
 ** Better detection of text suspiciously reordered on display.
 The function 'bidi-find-overridden-directionality' has been extended
 to detect reordering effects produced by embeddings and isolates
diff --git a/lisp/frame.el b/lisp/frame.el
index 2c73737a54..563391e7a1 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -2484,6 +2484,93 @@ delete-other-frames
         (if iconify (iconify-frame this) (delete-frame this)))
       (setq this next))))
 
+(eval-when-compile (require 'frameset))
+
+(defvar undelete-frame--deleted-frames nil
+  "Internal variable used by `undelete-frame--handle-delete-frame'.")
+
+(defvar undelete-frame--enabled nil
+  "Internal variable used by `undelete-frame--handle-delete-frame'.")
+
+(defun undelete-frame--handle-delete-frame (frame)
+  "Save the configuration of frames deleted with `delete-frame'.
+Only the 16 most recently deleted frames are saved."
+  (when (and
+         ;; Skip the deletion of the terminal frame in
+         ;; `frame-initialize', but do not skip the first deletion of
+         ;; a frame when the daemon is used, nor the first deletion of
+         ;; a frame in a TTY.
+         (or undelete-frame--enabled
+             (daemonp)
+             (and (not (display-graphic-p))
+                  (seq-every-p
+                   (lambda (f) (not (frame-parameter f 'display)))
+                   (frame-list))))
+         (frame-live-p frame))
+    (setq undelete-frame--deleted-frames
+          (cons
+           (cons
+            (display-graphic-p)
+            (frameset-save
+             (list frame)
+             ;; When the daemon is started from a graphical
+             ;; environment, TTY frames have a 'display' parameter set
+             ;; to the value of $DISPLAY (see the note in
+             ;; `server--on-display-p').  Do not store that parameter
+             ;; in the frameset, otherwise `frameset-restore' attempts
+             ;; to restore a graphical frame.
+             :filters (if (display-graphic-p)
+                          frameset-filter-alist
+                        (cons '(display . :never)
+                              frameset-filter-alist))))
+           undelete-frame--deleted-frames))
+    (if (> (length undelete-frame--deleted-frames) 16)
+        (setq undelete-frame--deleted-frames
+              (butlast undelete-frame--deleted-frames))))
+  (setq undelete-frame--enabled t))
+
+(define-minor-mode undelete-frame-mode
+  "Enable the `delete-frame' command."
+  :group 'frames
+  :global t
+  :initialize 'custom-initialize-delay
+  :init-value t
+  (if undelete-frame-mode
+      (add-hook 'delete-frame-functions
+                #'undelete-frame--handle-delete-frame -75)
+    (remove-hook 'delete-frame-functions
+                 #'undelete-frame--handle-delete-frame)
+    (setq undelete-frame--deleted-frames nil)))
+
+(defun undelete-frame (&optional arg)
+  "Undelete a frame deleted with `delete-frame'.
+Without a prefix argument, or with with just \\[universal-argument], \
+undelete the most
+recently deleted frame.
+With a numerical prefix argument ARG between 0 and 15, where 0 is
+most recently deleted frame, undelete the ARGth deleted frame.
+When called from Lisp, returns the new frame."
+  (interactive "P")
+  (if (not undelete-frame-mode)
+      (message "undelete-frame-mode is disabled")
+    (let* ((frames (frame-list))
+           (n (if (listp arg) 0 arg))
+           (frameset (nth n undelete-frame--deleted-frames))
+           (graphic (display-graphic-p)))
+      (if (not frameset)
+          (message "No deleted frame saved at position %d" n)
+        (if (not (eq graphic (car frameset)))
+            (message
+             "Cannot undelete %sgraphic display frame on a %sgraphic display"
+             (if graphic "non-" "")
+             (if graphic "" "non-"))
+          (setq undelete-frame--deleted-frames
+                (delq frameset undelete-frame--deleted-frames))
+          (frameset-restore (cdr frameset))
+          (let ((frame (car (seq-difference (frame-list) frames))))
+            (when frame
+              (select-frame-set-input-focus frame)
+              frame)))))))
 \f
 ;;; Window dividers.
 (defgroup window-divider nil
@@ -2828,6 +2915,7 @@ ctl-x-5-map
 (define-key ctl-x-5-map "o" #'other-frame)
 (define-key ctl-x-5-map "5" #'other-frame-prefix)
 (define-key ctl-x-5-map "c" #'clone-frame)
+(define-key ctl-x-5-map "u" #'undelete-frame)
 (define-key global-map [f11] #'toggle-frame-fullscreen)
 (define-key global-map [(meta f10)] #'toggle-frame-maximized)
 (define-key esc-map    [f10]        #'toggle-frame-maximized)
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index 1a81f1a3d0..a5f7169355 100644
--- a/lisp/menu-bar.el
+++ b/lisp/menu-bar.el
@@ -109,6 +109,11 @@ menu-bar-file-menu
       (bindings--define-key menu [separator-tab]
         menu-bar-separator))
 
+    (bindings--define-key menu [undelete-last-deleted-frame]
+      '(menu-item "Undelete Frame" undelete-frame
+                  :enable undelete-frame-mode
+                  :help "Undelete last deleted frame"))
+
     ;; Don't use delete-frame as event name because that is a special
     ;; event.
     (bindings--define-key menu [delete-this-frame]
diff --git a/src/frame.c b/src/frame.c
index 79a7c89e0d..763ac65717 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -2373,9 +2373,12 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
 }
 
 DEFUN ("delete-frame", Fdelete_frame, Sdelete_frame, 0, 2, "",
-       doc: /* Delete FRAME, permanently eliminating it from use.
+       doc: /* Delete FRAME, eliminating it from use.
 FRAME must be a live frame and defaults to the selected one.
 
+Unless `undelete-frame-mode' is disabled, the 16 most recently deleted
+frames can be undeleted with `undelete-frame', which see.
+
 A frame may not be deleted if its minibuffer serves as surrogate
 minibuffer for another frame.  Normally, you may not delete a frame if
 all other frames are invisible, but if the second optional argument
-- 
2.33.0


^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-19  9:00           ` Gregory Heytings
@ 2021-11-19 12:17             ` Eli Zaretskii
  2021-11-24  0:44               ` Gregory Heytings
  0 siblings, 1 reply; 51+ messages in thread
From: Eli Zaretskii @ 2021-11-19 12:17 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: michael_heerdegen, 51883, juri

> Date: Fri, 19 Nov 2021 09:00:39 +0000
> From: Gregory Heytings <gregory@heytings.org>
> cc: 51883@debbugs.gnu.org, juri@linkov.net, michael_heerdegen@web.de
> 
> > Usually, commands that use both numeric and raw prefix arg do something 
> > very different with the raw argument, which is not the case here.
> 
> The last version of the patch now does that, too: no argument and a raw 
> argument means "most recent", a numeric argument means "Nth".

That's not "very different" in my book.  So I think only numeric
arguments should do that, with 1 being the default and meaning the
most-recently deleted one.  Yes, we will lose one frame this way, but
I don't think it's important enough to justify such strange usage of
the prefix arg.

> > I'd prefer to make it opt-in.  I see no reason to force on everyone a 
> > new feature that doesn't sound like it's urgent or important enough to 
> > justify the behavior change.  Even though the memory it uses is not 
> > large, it's still memory, and it still increases consing each time a 
> > frame is deleted.  For example, some people turn on all kinds of 
> > optional features that pop up new frames in many situations, and who 
> > knows what this will cause in those usage patterns.  Why risk such 
> > unintended consequences on behalf of a minor feature?
> 
> I hear your arguments, but IMO that would be like making "undo" opt-in. 
> I also wouldn't call that a "minor" feature, it's worth an entry in the 
> File menu.  Indeed we don't know what this could cause with exotic usage 
> patterns, so I suggest, given that the release of Emacs 29 is far away in 
> the future, to make it opt-out on the trunk, and if someone protests 
> because it breaks their usage pattern before Emacs 29 is released, to make 
> it opt-in instead.

No, please make it opt-in from the get-go, which will also remove the
need for some of the code which messes with the initial frame.  If
many users will request it be on by default, we will then reconsider.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-19 12:17             ` Eli Zaretskii
@ 2021-11-24  0:44               ` Gregory Heytings
  2021-11-27 11:36                 ` Michael Heerdegen
  0 siblings, 1 reply; 51+ messages in thread
From: Gregory Heytings @ 2021-11-24  0:44 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: michael_heerdegen, 51883, juri

[-- Attachment #1: Type: text/plain, Size: 24 bytes --]


Updated patch attached.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-diff; name=Undelete-deleted-frames.patch, Size: 8339 bytes --]

From 1d969cd2850226baf967eb58619074b8d6e01ac4 Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Wed, 24 Nov 2021 00:38:33 +0000
Subject: [PATCH] Undelete deleted frames.

* lisp/frame.el (undelete-frame): New command.
(undelete-last-deleted-frame): New auxiliary command.
(undelete-frame--handle-delete-frame): New auxiliary function.
(undelete-frame--deleted-frames, undelete-frame--enabled): New
auxiliary variables.
(undelete-frame-mode): New minor mode.
(ctl-x-5-map): Bind the new command.

* src/frame.c (Fdelete_frame): Update docstring, and mention the
minor mode.

* lisp/menu-bar.el (menu-bar-file-menu): Add an entry for the
new command.

* doc/emacs/frames.tex (Frame Commands): Document the new command
and minor mode.

* etc/NEWS: Document the new command and minor mode.

See bug#51883.
---
 doc/emacs/frames.texi |  9 ++++++
 etc/NEWS              |  9 ++++++
 lisp/frame.el         | 74 +++++++++++++++++++++++++++++++++++++++++++
 lisp/menu-bar.el      |  9 ++++++
 src/frame.c           |  5 ++-
 5 files changed, 105 insertions(+), 1 deletion(-)

diff --git a/doc/emacs/frames.texi b/doc/emacs/frames.texi
index c14ada2957..2f6e0f8300 100644
--- a/doc/emacs/frames.texi
+++ b/doc/emacs/frames.texi
@@ -512,6 +512,15 @@ Frame Commands
 Delete the selected frame (@code{delete-frame}).  This signals an
 error if there is only one frame.
 
+@item C-x 5 u
+@kindex C-x 5 u
+@findex undelete-frame
+@findex undelete-frame-mode
+When @code{undelete-frame-mode} is enabled, undelete one of the 16
+most recently deleted frames.  A numerical prefix argument between 1
+and 16, where 1 is the most recently deleted frame, specifies which
+deleted frame should be undeleted.
+
 @item C-z
 @kindex C-z @r{(X windows)}
 Minimize (or iconify) the selected Emacs frame
diff --git a/etc/NEWS b/etc/NEWS
index bfea4da8b9..953c7ebdff 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -178,6 +178,15 @@ For example, a 'display-buffer-alist' entry of
 will make the body of the chosen window 40 columns wide.  For the
 height use 'window-height' in combination with 'body-lines'.
 
+** Frames
+
++++
+*** Deleted frames can now be undeleted.
+The 16 most recently deleted frames can be undeleted with C-x 5 u when
+undelete-frame-mode is enabled.  A numerical prefix argument between 1
+and 16, where 1 is the most recently deleted frame, specifies which
+deleted frame should be undeleted.
+
 ** Better detection of text suspiciously reordered on display.
 The function 'bidi-find-overridden-directionality' has been extended
 to detect reordering effects produced by embeddings and isolates
diff --git a/lisp/frame.el b/lisp/frame.el
index 1319759e74..a44ac4afaa 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -2499,6 +2499,79 @@ delete-other-frames
         (if iconify (iconify-frame this) (delete-frame this)))
       (setq this next))))
 
+(eval-when-compile (require 'frameset))
+
+(defvar undelete-frame--deleted-frames nil
+  "Internal variable used by `undelete-frame--handle-delete-frame'.")
+
+(defun undelete-frame--handle-delete-frame (frame)
+  "Save the configuration of frames deleted with `delete-frame'.
+Only the 16 most recently deleted frames are saved."
+  (when (frame-live-p frame)
+    (setq undelete-frame--deleted-frames
+          (cons
+           (cons
+            (display-graphic-p)
+            (frameset-save
+             (list frame)
+             ;; When the daemon is started from a graphical
+             ;; environment, TTY frames have a 'display' parameter set
+             ;; to the value of $DISPLAY (see the note in
+             ;; `server--on-display-p').  Do not store that parameter
+             ;; in the frameset, otherwise `frameset-restore' attempts
+             ;; to restore a graphical frame.
+             :filters (if (display-graphic-p)
+                          frameset-filter-alist
+                        (cons '(display . :never)
+                              frameset-filter-alist))))
+           undelete-frame--deleted-frames))
+    (if (> (length undelete-frame--deleted-frames) 16)
+        (setq undelete-frame--deleted-frames
+              (butlast undelete-frame--deleted-frames)))))
+
+(define-minor-mode undelete-frame-mode
+  "Enable the `undelete-frame' command."
+  :group 'frames
+  :global t
+  (if undelete-frame-mode
+      (add-hook 'delete-frame-functions
+                #'undelete-frame--handle-delete-frame -75)
+    (remove-hook 'delete-frame-functions
+                 #'undelete-frame--handle-delete-frame)
+    (setq undelete-frame--deleted-frames nil)))
+
+(defun undelete-last-deleted-frame ()
+  "Undeleted the last frame deleted with `delete-frame'."
+  (interactive)
+  (undelete-frame 1))
+
+(defun undelete-frame (&optional arg)
+  "Undelete a frame deleted with `delete-frame'.
+With a numerical prefix argument ARG between 1 and 16, where 1 is
+most recently deleted frame, undelete the ARGth deleted frame.
+When called from Lisp, returns the new frame."
+  (interactive "P")
+  (if (not undelete-frame-mode)
+      (message "undelete-frame-mode is disabled")
+    (if (listp arg)
+        (message "Missing deleted frame number argument")
+      (let* ((frames (frame-list))
+             (frameset (nth (1- arg) undelete-frame--deleted-frames))
+             (graphic (display-graphic-p)))
+        (if (not frameset)
+            (message "No deleted frame with number %d" arg)
+          (if (not (eq graphic (car frameset)))
+              (message
+               "Cannot undelete a %s display frame on a %s display"
+               (if graphic "non-graphic" "graphic")
+               (if graphic "graphic" "non-graphic"))
+            (setq undelete-frame--deleted-frames
+                  (delq frameset undelete-frame--deleted-frames))
+            (frameset-restore (cdr frameset))
+            (let ((frame (car (seq-difference (frame-list) frames))))
+              (when frame
+                (select-frame-set-input-focus frame)
+                frame))))))))
 \f
 ;;; Window dividers.
 (defgroup window-divider nil
@@ -2843,6 +2916,7 @@ ctl-x-5-map
 (define-key ctl-x-5-map "o" #'other-frame)
 (define-key ctl-x-5-map "5" #'other-frame-prefix)
 (define-key ctl-x-5-map "c" #'clone-frame)
+(define-key ctl-x-5-map "u" #'undelete-frame)
 (define-key global-map [f11] #'toggle-frame-fullscreen)
 (define-key global-map [(meta f10)] #'toggle-frame-maximized)
 (define-key esc-map    [f10]        #'toggle-frame-maximized)
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index 274f594f69..e696e6c4c0 100644
--- a/lisp/menu-bar.el
+++ b/lisp/menu-bar.el
@@ -109,6 +109,15 @@ menu-bar-file-menu
       (bindings--define-key menu [separator-tab]
         menu-bar-separator))
 
+    (bindings--define-key menu [enable-undelete-frame-mode]
+      '(menu-item "Enable Frame Undeletion" undelete-frame-mode
+                  :visible (null undelete-frame-mode)
+                  :help "Enable frame undeletion for this session"))
+    (bindings--define-key menu [undelete-last-deleted-frame]
+      '(menu-item "Undelete Frame" undelete-last-deleted-frame
+                  :visible undelete-frame-mode
+                  :help "Undelete last deleted frame"))
+
     ;; Don't use delete-frame as event name because that is a special
     ;; event.
     (bindings--define-key menu [delete-this-frame]
diff --git a/src/frame.c b/src/frame.c
index a21dd0d927..544fa0d02b 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -2376,9 +2376,12 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
 }
 
 DEFUN ("delete-frame", Fdelete_frame, Sdelete_frame, 0, 2, "",
-       doc: /* Delete FRAME, permanently eliminating it from use.
+       doc: /* Delete FRAME, eliminating it from use.
 FRAME must be a live frame and defaults to the selected one.
 
+When `undelete-frame-mode' is enabled, the 16 most recently deleted
+frames can be undeleted with `undelete-frame', which see.
+
 A frame may not be deleted if its minibuffer serves as surrogate
 minibuffer for another frame.  Normally, you may not delete a frame if
 all other frames are invisible, but if the second optional argument
-- 
2.33.0


^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-24  0:44               ` Gregory Heytings
@ 2021-11-27 11:36                 ` Michael Heerdegen
  2021-11-27 11:53                   ` Gregory Heytings
  2021-11-27 12:13                   ` Michael Heerdegen
  0 siblings, 2 replies; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-27 11:36 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: 51883, juri

Gregory Heytings <gregory@heytings.org> writes:

> Updated patch attached.

Thanks for your work so far.

I tested your patch quickly.  It works!

But I noticed that C-x 5 u without prefix arg errors.  This is probably
not intended.  I think you rather want to test the raw prefix arg with
`consp', not with `listp'.  But why not just use (interactive "p")?

Regards,

Michael.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 11:36                 ` Michael Heerdegen
@ 2021-11-27 11:53                   ` Gregory Heytings
  2021-11-27 12:05                     ` Eli Zaretskii
  2021-11-27 12:13                   ` Michael Heerdegen
  1 sibling, 1 reply; 51+ messages in thread
From: Gregory Heytings @ 2021-11-27 11:53 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: 51883, juri


>
> Thanks for your work so far.
>
> I tested your patch quickly.  It works!
>

Thanks for your feedback!

>
> But I noticed that C-x 5 u without prefix arg errors.  This is probably 
> not intended.  I think you rather want to test the raw prefix arg with 
> `consp', not with `listp'.  But why not just use (interactive "p")?
>

This is intended, it's what Eli wanted, unless I misunderstood what he 
meant.  In a previous version of the patch C-x 5 u without prefix arg 
undeleted the last deleted frame.  Now an explicit numerical prefix 
argument between 1 and 16 is required.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 11:53                   ` Gregory Heytings
@ 2021-11-27 12:05                     ` Eli Zaretskii
  2021-11-27 12:12                       ` Gregory Heytings
  2021-11-27 12:23                       ` Michael Heerdegen
  0 siblings, 2 replies; 51+ messages in thread
From: Eli Zaretskii @ 2021-11-27 12:05 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: michael_heerdegen, 51883, juri

> Date: Sat, 27 Nov 2021 11:53:10 +0000
> From: Gregory Heytings <gregory@heytings.org>
> cc: Eli Zaretskii <eliz@gnu.org>, 51883@debbugs.gnu.org, juri@linkov.net
> 
> > But I noticed that C-x 5 u without prefix arg errors.  This is probably 
> > not intended.  I think you rather want to test the raw prefix arg with 
> > `consp', not with `listp'.  But why not just use (interactive "p")?
> >
> 
> This is intended, it's what Eli wanted, unless I misunderstood what he 
> meant.  In a previous version of the patch C-x 5 u without prefix arg 
> undeleted the last deleted frame.  Now an explicit numerical prefix 
> argument between 1 and 16 is required.

No, that's a misunderstanding, sorry.  I meant "C-x 5 u" to undelete
the most recently deleted frame, and 1 to 16 to mean undelete the
penultimate frame etc.  Basically ARG of N means undelete the N+1st
previously deleted frame.  I just didn't want the ARG of zero to be
handled specially.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 12:05                     ` Eli Zaretskii
@ 2021-11-27 12:12                       ` Gregory Heytings
  2021-11-27 12:30                         ` Andreas Schwab
  2021-11-27 12:34                         ` Eli Zaretskii
  2021-11-27 12:23                       ` Michael Heerdegen
  1 sibling, 2 replies; 51+ messages in thread
From: Gregory Heytings @ 2021-11-27 12:12 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: michael_heerdegen, 51883, juri


>
> I meant "C-x 5 u" to undelete the most recently deleted frame, and 1 to 
> 16 to mean undelete the penultimate frame etc.  Basically ARG of N means 
> undelete the N+1st previously deleted frame.  I just didn't want the ARG 
> of zero to be handled specially.
>

Okay, I'll do that.

So, just to make this crystal clear:

C-x 5 u -> undelete most recently deleted frame
C-u 0 C-x 5 u -> undelete most recently deleted frame
C-u 1 C-x 5 u -> undelete second most recently deleted frame
...

and

C-u C-x 5 u -> error

?





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 11:36                 ` Michael Heerdegen
  2021-11-27 11:53                   ` Gregory Heytings
@ 2021-11-27 12:13                   ` Michael Heerdegen
  1 sibling, 0 replies; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-27 12:13 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: 51883, juri

Michael Heerdegen <michael_heerdegen@web.de> writes:

> I tested your patch quickly.  It works!

One thing you also might want to consider: if you use a ring instead of
a list to store the frames:

  (info "(elisp) Rings")

you don't have to care about removing old elements, it will be done
silently.  Apart from that you only have to exchange the insertion and
access functions, that's it.  We can also think about this detail
after installing your patch to master, though.

Apart from that detail, no more comments from my side, only a "thank
you".

Michael.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 12:05                     ` Eli Zaretskii
  2021-11-27 12:12                       ` Gregory Heytings
@ 2021-11-27 12:23                       ` Michael Heerdegen
  2021-11-27 12:40                         ` Eli Zaretskii
  1 sibling, 1 reply; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-27 12:23 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Gregory Heytings, 51883, juri

Eli Zaretskii <eliz@gnu.org> writes:

> No, that's a misunderstanding, sorry.  I meant "C-x 5 u" to undelete
> the most recently deleted frame, and 1 to 16 to mean undelete the
> penultimate frame etc.  Basically ARG of N means undelete the N+1st
> previously deleted frame.

1 should undelete the second previously deleted frame?  I think it's
already complicated to guess the correct N, and then I would also have
to remember that I have to add or subtract 1... so why N+1?

Michael.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 12:12                       ` Gregory Heytings
@ 2021-11-27 12:30                         ` Andreas Schwab
  2021-11-27 12:34                         ` Eli Zaretskii
  1 sibling, 0 replies; 51+ messages in thread
From: Andreas Schwab @ 2021-11-27 12:30 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: michael_heerdegen, 51883, juri

On Nov 27 2021, Gregory Heytings wrote:

> C-u C-x 5 u -> error

Often, a lone C-u stands for 4 (if only a numeric argument is expected).

Andreas.

-- 
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 7578 EB47 D4E5 4D69 2510  2552 DF73 E780 A9DA AEC1
"And now for something completely different."





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 12:12                       ` Gregory Heytings
  2021-11-27 12:30                         ` Andreas Schwab
@ 2021-11-27 12:34                         ` Eli Zaretskii
  1 sibling, 0 replies; 51+ messages in thread
From: Eli Zaretskii @ 2021-11-27 12:34 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: michael_heerdegen, 51883, juri

> Date: Sat, 27 Nov 2021 12:12:15 +0000
> From: Gregory Heytings <gregory@heytings.org>
> cc: michael_heerdegen@web.de, 51883@debbugs.gnu.org, juri@linkov.net
> 
> So, just to make this crystal clear:
> 
> C-x 5 u -> undelete most recently deleted frame
> C-u 0 C-x 5 u -> undelete most recently deleted frame
> C-u 1 C-x 5 u -> undelete second most recently deleted frame
> ...

Yes.

> and
> 
> C-u C-x 5 u -> error
> 
> ?

You could treat that as "C-u 4 C-x 5 u", perhaps, like C-f does.

Thanks, and again apologies for the misunderstanding I caused.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 12:23                       ` Michael Heerdegen
@ 2021-11-27 12:40                         ` Eli Zaretskii
  2021-11-27 13:22                           ` Michael Heerdegen
  0 siblings, 1 reply; 51+ messages in thread
From: Eli Zaretskii @ 2021-11-27 12:40 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: gregory, 51883, juri

> From: Michael Heerdegen <michael_heerdegen@web.de>
> Cc: Gregory Heytings <gregory@heytings.org>,  51883@debbugs.gnu.org,
>   juri@linkov.net
> Date: Sat, 27 Nov 2021 13:23:58 +0100
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > No, that's a misunderstanding, sorry.  I meant "C-x 5 u" to undelete
> > the most recently deleted frame, and 1 to 16 to mean undelete the
> > penultimate frame etc.  Basically ARG of N means undelete the N+1st
> > previously deleted frame.
> 
> 1 should undelete the second previously deleted frame?  I think it's
> already complicated to guess the correct N, and then I would also have
> to remember that I have to add or subtract 1... so why N+1?

You want to make "C-x 5 u" and "C-u 1 C-x 5 u" mean the same?  It's
possible, but we also have commands/functions that work like above,
no?





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 12:40                         ` Eli Zaretskii
@ 2021-11-27 13:22                           ` Michael Heerdegen
  2021-11-27 13:26                             ` Eli Zaretskii
  0 siblings, 1 reply; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-27 13:22 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gregory, 51883, juri

Eli Zaretskii <eliz@gnu.org> writes:

> You want to make "C-x 5 u" and "C-u 1 C-x 5 u" mean the same?

Yes, I think that would be the simplest solution.

>  It's possible, but we also have commands/functions that work like
> above, no?

I'm not sure.  I didn't find one quickly.

In my opinion in this case it would not make much sense conceptionally,
numbering the last killed frames starting with 1 is more natural than
counting zero based.  Making it zero based would be an annoying detail
one has to remember without much gain.  Just my personal opinion without
having meditated long over the matter, of course.

Michael.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 13:22                           ` Michael Heerdegen
@ 2021-11-27 13:26                             ` Eli Zaretskii
  2021-11-27 13:34                               ` Michael Heerdegen
  0 siblings, 1 reply; 51+ messages in thread
From: Eli Zaretskii @ 2021-11-27 13:26 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: gregory, 51883, juri

> From: Michael Heerdegen <michael_heerdegen@web.de>
> Cc: gregory@heytings.org,  51883@debbugs.gnu.org,  juri@linkov.net
> Date: Sat, 27 Nov 2021 14:22:11 +0100
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > You want to make "C-x 5 u" and "C-u 1 C-x 5 u" mean the same?
> 
> Yes, I think that would be the simplest solution.
> 
> >  It's possible, but we also have commands/functions that work like
> > above, no?
> 
> I'm not sure.  I didn't find one quickly.
> 
> In my opinion in this case it would not make much sense conceptionally,
> numbering the last killed frames starting with 1 is more natural than
> counting zero based.  Making it zero based would be an annoying detail
> one has to remember without much gain.  Just my personal opinion without
> having meditated long over the matter, of course.

Fine with me.

Gregory, please do it this way, to avoid future rework due to my own
idiosyncrasies.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 13:26                             ` Eli Zaretskii
@ 2021-11-27 13:34                               ` Michael Heerdegen
  2021-11-27 13:56                                 ` Eli Zaretskii
  2021-11-27 14:12                                 ` Gregory Heytings
  0 siblings, 2 replies; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-27 13:34 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gregory, 51883, juri

Eli Zaretskii <eliz@gnu.org> writes:

> Fine with me.
>
> Gregory, please do it this way, to avoid future rework due to my own
> idiosyncrasies.

Let's help him and be clear: this means using just (interactive "p")
without any distinction of cases, right?

Michael.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 13:34                               ` Michael Heerdegen
@ 2021-11-27 13:56                                 ` Eli Zaretskii
  2021-11-27 13:59                                   ` Michael Heerdegen
  2021-11-27 14:12                                 ` Gregory Heytings
  1 sibling, 1 reply; 51+ messages in thread
From: Eli Zaretskii @ 2021-11-27 13:56 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: gregory, 51883, juri

> From: Michael Heerdegen <michael_heerdegen@web.de>
> Cc: gregory@heytings.org,  51883@debbugs.gnu.org,  juri@linkov.net
> Date: Sat, 27 Nov 2021 14:34:56 +0100
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > Fine with me.
> >
> > Gregory, please do it this way, to avoid future rework due to my own
> > idiosyncrasies.
> 
> Let's help him and be clear: this means using just (interactive "p")
> without any distinction of cases, right?

Probably.  But that doesn't tell what to do with "C-u 0", does it?





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 13:56                                 ` Eli Zaretskii
@ 2021-11-27 13:59                                   ` Michael Heerdegen
  2021-11-27 14:02                                     ` Eli Zaretskii
  0 siblings, 1 reply; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-27 13:59 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gregory, 51883, juri

Eli Zaretskii <eliz@gnu.org> writes:

> Probably.  But that doesn't tell what to do with "C-u 0", does it?

I would want C-u 1 be like M-1 or no prefix arg and C-u 0 produce an
error because number 0 is invalid.

Michael.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 13:59                                   ` Michael Heerdegen
@ 2021-11-27 14:02                                     ` Eli Zaretskii
  2021-11-27 14:08                                       ` Michael Heerdegen
  0 siblings, 1 reply; 51+ messages in thread
From: Eli Zaretskii @ 2021-11-27 14:02 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: gregory, 51883, juri

> From: Michael Heerdegen <michael_heerdegen@web.de>
> Cc: gregory@heytings.org,  51883@debbugs.gnu.org,  juri@linkov.net
> Date: Sat, 27 Nov 2021 14:59:43 +0100
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > Probably.  But that doesn't tell what to do with "C-u 0", does it?
> 
> I would want C-u 1 be like M-1 or no prefix arg and C-u 0 produce an
> error because number 0 is invalid.

It is maybe better to ignore an argument of zero, i.e. behave as if
there was no argument.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 14:02                                     ` Eli Zaretskii
@ 2021-11-27 14:08                                       ` Michael Heerdegen
  2021-11-27 14:47                                         ` Eli Zaretskii
  0 siblings, 1 reply; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-27 14:08 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gregory, 51883, juri

Eli Zaretskii <eliz@gnu.org> writes:

> It is maybe better to ignore an argument of zero, i.e. behave as if
> there was no argument.

If we do that (and I don't object) - what should negative arguments do?
Should -N be equivalent to +N?

Michael.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 13:34                               ` Michael Heerdegen
  2021-11-27 13:56                                 ` Eli Zaretskii
@ 2021-11-27 14:12                                 ` Gregory Heytings
  2021-11-27 14:24                                   ` Michael Heerdegen
  2021-11-27 14:48                                   ` Eli Zaretskii
  1 sibling, 2 replies; 51+ messages in thread
From: Gregory Heytings @ 2021-11-27 14:12 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: 51883, juri


>
> Let's help him and be clear: this means using just (interactive "p") 
> without any distinction of cases, right?
>

No, because I do not want to see C-u C-x 5 u undelete the fourth most 
recently deleted frame.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 14:12                                 ` Gregory Heytings
@ 2021-11-27 14:24                                   ` Michael Heerdegen
  2021-11-27 14:26                                     ` Gregory Heytings
  2021-11-27 14:48                                   ` Eli Zaretskii
  1 sibling, 1 reply; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-27 14:24 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: 51883, juri

Gregory Heytings <gregory@heytings.org> writes:

> No, because I do not want to see C-u C-x 5 u undelete the fourth most
> recently deleted frame.

What do you want in this case?

Michael.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 14:24                                   ` Michael Heerdegen
@ 2021-11-27 14:26                                     ` Gregory Heytings
  2021-11-27 14:33                                       ` Michael Heerdegen
  0 siblings, 1 reply; 51+ messages in thread
From: Gregory Heytings @ 2021-11-27 14:26 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: 51883, juri


>>> Let's help him and be clear: this means using just (interactive "p") 
>>> without any distinction of cases, right?
>>
>> No, because I do not want to see C-u C-x 5 u undelete the fourth most 
>> recently deleted frame.
>
> What do you want in this case?
>

An error, something like "You didn't specify a frame number."





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 14:26                                     ` Gregory Heytings
@ 2021-11-27 14:33                                       ` Michael Heerdegen
  2021-11-27 14:42                                         ` Gregory Heytings
  0 siblings, 1 reply; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-27 14:33 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: 51883, juri

Gregory Heytings <gregory@heytings.org> writes:

> An error, something like "You didn't specify a frame number."

What's different in this case, compared to commands that do use
(interactive "p")?  Why is raising an error here better than doing what
at least some people expect?

Michael.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 14:33                                       ` Michael Heerdegen
@ 2021-11-27 14:42                                         ` Gregory Heytings
  2021-11-27 14:54                                           ` Michael Heerdegen
  0 siblings, 1 reply; 51+ messages in thread
From: Gregory Heytings @ 2021-11-27 14:42 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: 51883, juri


>> An error, something like "You didn't specify a frame number."
>
> What's different in this case, compared to commands that do use 
> (interactive "p")?  Why is raising an error here better than doing what 
> at least some people expect?
>

They will not expect it if the docstring (and the error message) mentions 
that an explicit numerical argument must be given.  And I don't think that 
"undelete the fourth most recently deleted frame with just C-u" is 
something anyone would expect, in fact I think it would be more confusing 
than helpful.

Moreover I want to keep the C-u prefix free for later use.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 14:08                                       ` Michael Heerdegen
@ 2021-11-27 14:47                                         ` Eli Zaretskii
  0 siblings, 0 replies; 51+ messages in thread
From: Eli Zaretskii @ 2021-11-27 14:47 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: gregory, 51883, juri

> From: Michael Heerdegen <michael_heerdegen@web.de>
> Cc: gregory@heytings.org,  51883@debbugs.gnu.org,  juri@linkov.net
> Date: Sat, 27 Nov 2021 15:08:34 +0100
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > It is maybe better to ignore an argument of zero, i.e. behave as if
> > there was no argument.
> 
> If we do that (and I don't object) - what should negative arguments do?
> Should -N be equivalent to +N?

Either that or ignored.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 14:12                                 ` Gregory Heytings
  2021-11-27 14:24                                   ` Michael Heerdegen
@ 2021-11-27 14:48                                   ` Eli Zaretskii
  1 sibling, 0 replies; 51+ messages in thread
From: Eli Zaretskii @ 2021-11-27 14:48 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: michael_heerdegen, 51883, juri

> Date: Sat, 27 Nov 2021 14:12:56 +0000
> From: Gregory Heytings <gregory@heytings.org>
> cc: Eli Zaretskii <eliz@gnu.org>, 51883@debbugs.gnu.org, juri@linkov.net
> 
> > Let's help him and be clear: this means using just (interactive "p") 
> > without any distinction of cases, right?
> 
> No, because I do not want to see C-u C-x 5 u undelete the fourth most 
> recently deleted frame.

Why not?  It's the natural meaning of a bare C-u.  E.g., C-f behaves
like that.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 14:42                                         ` Gregory Heytings
@ 2021-11-27 14:54                                           ` Michael Heerdegen
  2021-11-27 17:19                                             ` Gregory Heytings
  0 siblings, 1 reply; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-27 14:54 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: 51883, juri

Gregory Heytings <gregory@heytings.org> writes:

> Moreover I want to keep the C-u prefix free for later use.

I accept that argument.  Still, do we raise an error in similar cases?
But I won't argue about that detail...

Michael.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 14:54                                           ` Michael Heerdegen
@ 2021-11-27 17:19                                             ` Gregory Heytings
  2021-11-28 15:47                                               ` Michael Heerdegen
  0 siblings, 1 reply; 51+ messages in thread
From: Gregory Heytings @ 2021-11-27 17:19 UTC (permalink / raw)
  To: Michael Heerdegen, Eli Zaretskii; +Cc: 51883, juri

[-- Attachment #1: Type: text/plain, Size: 236 bytes --]


Updated patch attached.

(A note for Michael: I know about the ring.el library, but it would have 
been necessary to require it in frame.el, which is not necessary, given 
that it's easy to get the same effect with standard functions.)

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-diff; name=Undelete-deleted-frames.patch, Size: 8355 bytes --]

From 69657897097c986efcc859a070fbd850989c8343 Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Sat, 27 Nov 2021 16:59:51 +0000
Subject: [PATCH] Undelete deleted frames.

* lisp/frame.el (undelete-frame): New command.
(undelete-frame--handle-delete-frame): New auxiliary function.
(undelete-frame--deleted-frames): New auxiliary variables.
(undelete-frame-mode): New minor mode.
(ctl-x-5-map): Bind the new command.

* etc/NEWS: Document the new command and minor mode.

* src/frame.c (Fdelete_frame): Update docstring, and mention the
minor mode.

* lisp/menu-bar.el (menu-bar-file-menu): Add an entry for the
new command.

* doc/emacs/frames.tex (Frame Commands): Document the new command
and minor mode.

See bug#51883.
---
 doc/emacs/frames.texi | 10 ++++++
 etc/NEWS              | 10 ++++++
 lisp/frame.el         | 75 +++++++++++++++++++++++++++++++++++++++++++
 lisp/menu-bar.el      |  9 ++++++
 src/frame.c           |  5 ++-
 5 files changed, 108 insertions(+), 1 deletion(-)

diff --git a/doc/emacs/frames.texi b/doc/emacs/frames.texi
index c14ada2957..b0fa22660f 100644
--- a/doc/emacs/frames.texi
+++ b/doc/emacs/frames.texi
@@ -512,6 +512,16 @@ Frame Commands
 Delete the selected frame (@code{delete-frame}).  This signals an
 error if there is only one frame.
 
+@item C-x 5 u
+@kindex C-x 5 u
+@findex undelete-frame
+@findex undelete-frame-mode
+When @code{undelete-frame-mode} is enabled, undelete one of the 16
+most recently deleted frames.  Without a prefix argument, undelete the
+most recently deleted frame.  With a numerical prefix argument between
+1 and 16, where 1 is the most recently deleted frame, undelete the
+corresponding deleted frame.
+
 @item C-z
 @kindex C-z @r{(X windows)}
 Minimize (or iconify) the selected Emacs frame
diff --git a/etc/NEWS b/etc/NEWS
index 8b7c2f7850..45628b6a65 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -189,6 +189,16 @@ For example, a 'display-buffer-alist' entry of
 will make the body of the chosen window 40 columns wide.  For the
 height use 'window-height' in combination with 'body-lines'.
 
+** Frames
+
++++
+*** Deleted frames can now be undeleted.
+The 16 most recently deleted frames can be undeleted with C-x 5 u when
+undelete-frame-mode is enabled.  Without a prefix argument, undelete
+the most recently deleted frame.  With a numerical prefix argument
+between 1 and 16, where 1 is the most recently deleted frame, undelete
+the corresponding deleted frame.
+
 ** Tab Bars and Tab Lines
 
 ---
diff --git a/lisp/frame.el b/lisp/frame.el
index 1319759e74..22e373fa0e 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -2499,6 +2499,80 @@ delete-other-frames
         (if iconify (iconify-frame this) (delete-frame this)))
       (setq this next))))
 
+(eval-when-compile (require 'frameset))
+
+(defvar undelete-frame--deleted-frames nil
+  "Internal variable used by `undelete-frame--handle-delete-frame'.")
+
+(defun undelete-frame--handle-delete-frame (frame)
+  "Save the configuration of frames deleted with `delete-frame'.
+Only the 16 most recently deleted frames are saved."
+  (when (frame-live-p frame)
+    (setq undelete-frame--deleted-frames
+          (cons
+           (cons
+            (display-graphic-p)
+            (frameset-save
+             (list frame)
+             ;; When the daemon is started from a graphical
+             ;; environment, TTY frames have a 'display' parameter set
+             ;; to the value of $DISPLAY (see the note in
+             ;; `server--on-display-p').  Do not store that parameter
+             ;; in the frameset, otherwise `frameset-restore' attempts
+             ;; to restore a graphical frame.
+             :filters (if (display-graphic-p)
+                          frameset-filter-alist
+                        (cons '(display . :never)
+                              frameset-filter-alist))))
+           undelete-frame--deleted-frames))
+    (if (> (length undelete-frame--deleted-frames) 16)
+        (setq undelete-frame--deleted-frames
+              (butlast undelete-frame--deleted-frames)))))
+
+(define-minor-mode undelete-frame-mode
+  "Enable the `undelete-frame' command."
+  :group 'frames
+  :global t
+  (if undelete-frame-mode
+      (add-hook 'delete-frame-functions
+                #'undelete-frame--handle-delete-frame -75)
+    (remove-hook 'delete-frame-functions
+                 #'undelete-frame--handle-delete-frame)
+    (setq undelete-frame--deleted-frames nil)))
+
+(defun undelete-frame (&optional arg)
+  "Undelete a frame deleted with `delete-frame'.
+Without a prefix argument, undelete the most recently deleted
+frame.
+With a numerical prefix argument ARG between 1 and 16, where 1 is
+most recently deleted frame, undelete the ARGth deleted frame.
+When called from Lisp, returns the new frame."
+  (interactive "P")
+  (if (not undelete-frame-mode)
+      (error "Undelete-Frame mode is disabled")
+    (if (consp arg)
+        (error "Missing deleted frame number argument")
+      (let* ((number (or arg 1))
+             (frames (frame-list))
+             (frameset (nth (1- number) undelete-frame--deleted-frames))
+             (graphic (display-graphic-p)))
+        (if (not (<= 1 number 16))
+            (error "%d is not a valid deleted frame number argument"
+                   number)
+          (if (not frameset)
+              (error "No deleted frame with number %d" number)
+            (if (not (eq graphic (car frameset)))
+                (error
+                 "Cannot undelete a %s display frame on a %s display"
+                 (if graphic "non-graphic" "graphic")
+                 (if graphic "graphic" "non-graphic"))
+              (setq undelete-frame--deleted-frames
+                    (delq frameset undelete-frame--deleted-frames))
+              (frameset-restore (cdr frameset))
+              (let ((frame (car (seq-difference (frame-list) frames))))
+                (when frame
+                  (select-frame-set-input-focus frame)
+                  frame)))))))))
 \f
 ;;; Window dividers.
 (defgroup window-divider nil
@@ -2843,6 +2917,7 @@ ctl-x-5-map
 (define-key ctl-x-5-map "o" #'other-frame)
 (define-key ctl-x-5-map "5" #'other-frame-prefix)
 (define-key ctl-x-5-map "c" #'clone-frame)
+(define-key ctl-x-5-map "u" #'undelete-frame)
 (define-key global-map [f11] #'toggle-frame-fullscreen)
 (define-key global-map [(meta f10)] #'toggle-frame-maximized)
 (define-key esc-map    [f10]        #'toggle-frame-maximized)
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index 274f594f69..9500f577bc 100644
--- a/lisp/menu-bar.el
+++ b/lisp/menu-bar.el
@@ -109,6 +109,15 @@ menu-bar-file-menu
       (bindings--define-key menu [separator-tab]
         menu-bar-separator))
 
+    (bindings--define-key menu [enable-undelete-frame-mode]
+      '(menu-item "Enable Frame Undeletion" undelete-frame-mode
+                  :visible (null undelete-frame-mode)
+                  :help "Enable frame undeletion for this session"))
+    (bindings--define-key menu [undelete-last-deleted-frame]
+      '(menu-item "Undelete Frame" undelete-frame
+                  :visible undelete-frame-mode
+                  :help "Undelete the most recently deleted frame"))
+
     ;; Don't use delete-frame as event name because that is a special
     ;; event.
     (bindings--define-key menu [delete-this-frame]
diff --git a/src/frame.c b/src/frame.c
index 33e9606e41..04dd6e3550 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -2376,9 +2376,12 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
 }
 
 DEFUN ("delete-frame", Fdelete_frame, Sdelete_frame, 0, 2, "",
-       doc: /* Delete FRAME, permanently eliminating it from use.
+       doc: /* Delete FRAME, eliminating it from use.
 FRAME must be a live frame and defaults to the selected one.
 
+When `undelete-frame-mode' is enabled, the 16 most recently deleted
+frames can be undeleted with `undelete-frame', which see.
+
 A frame may not be deleted if its minibuffer serves as surrogate
 minibuffer for another frame.  Normally, you may not delete a frame if
 all other frames are invisible, but if the second optional argument
-- 
2.33.0


^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-27 17:19                                             ` Gregory Heytings
@ 2021-11-28 15:47                                               ` Michael Heerdegen
  2021-11-29 13:38                                                 ` Gregory Heytings
  0 siblings, 1 reply; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-28 15:47 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: 51883, juri

Gregory Heytings <gregory@heytings.org> writes:

> Updated patch attached.

Thanks.  Hmm - one kind of raw prefix arg you don't handle yet is `-`
which is synonymous for -1.  M-- C-x 5 u gives the not so nice error
message:

  Wrong type argument: number-or-marker-p, -

Oh, and, I think all explicit `error' calls in your patch could be made
`user-error's instead, WDYT?

> (A note for Michael: I know about the ring.el library, but it would
> have been necessary to require it in frame.el, which is not necessary,
> given that it's easy to get the same effect with standard functions.)

Ok, fine with me.


Regards,

Michael.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-28 15:47                                               ` Michael Heerdegen
@ 2021-11-29 13:38                                                 ` Gregory Heytings
  2021-11-29 18:18                                                   ` Michael Heerdegen
  0 siblings, 1 reply; 51+ messages in thread
From: Gregory Heytings @ 2021-11-29 13:38 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: 51883, juri

[-- Attachment #1: Type: text/plain, Size: 443 bytes --]


Thanks for your feedback.

>
> Thanks.  Hmm - one kind of raw prefix arg you don't handle yet is `-` 
> which is synonymous for -1.  M-- C-x 5 u gives the not so nice error 
> message:
>
> Wrong type argument: number-or-marker-p, -
>

Indeed, I forgot to handle that case.

>
> Oh, and, I think all explicit `error' calls in your patch could be made 
> `user-error's instead, WDYT?
>

That makes sense, indeed.  Done.

Updated patch attached.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-diff; name=Undelete-deleted-frames.patch, Size: 8411 bytes --]

From b84ef1881834fb0b25eb29005d158cf10712d8f3 Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Mon, 29 Nov 2021 10:25:50 +0000
Subject: [PATCH] Undelete deleted frames.

* lisp/frame.el (undelete-frame): New command.
(undelete-frame--handle-delete-frame): New auxiliary function.
(undelete-frame--deleted-frames): New auxiliary variables.
(undelete-frame-mode): New minor mode.
(ctl-x-5-map): Bind the new command.

* etc/NEWS: Document the new command and minor mode.

* src/frame.c (Fdelete_frame): Update docstring, and mention the
minor mode.

* lisp/menu-bar.el (menu-bar-file-menu): Add an entry for the
new command.

* doc/emacs/frames.tex (Frame Commands): Document the new command
and minor mode.

See bug#51883.
---
 doc/emacs/frames.texi | 10 ++++++
 etc/NEWS              | 10 ++++++
 lisp/frame.el         | 75 +++++++++++++++++++++++++++++++++++++++++++
 lisp/menu-bar.el      |  9 ++++++
 src/frame.c           |  5 ++-
 5 files changed, 108 insertions(+), 1 deletion(-)

diff --git a/doc/emacs/frames.texi b/doc/emacs/frames.texi
index c14ada2957..b0fa22660f 100644
--- a/doc/emacs/frames.texi
+++ b/doc/emacs/frames.texi
@@ -512,6 +512,16 @@ Frame Commands
 Delete the selected frame (@code{delete-frame}).  This signals an
 error if there is only one frame.
 
+@item C-x 5 u
+@kindex C-x 5 u
+@findex undelete-frame
+@findex undelete-frame-mode
+When @code{undelete-frame-mode} is enabled, undelete one of the 16
+most recently deleted frames.  Without a prefix argument, undelete the
+most recently deleted frame.  With a numerical prefix argument between
+1 and 16, where 1 is the most recently deleted frame, undelete the
+corresponding deleted frame.
+
 @item C-z
 @kindex C-z @r{(X windows)}
 Minimize (or iconify) the selected Emacs frame
diff --git a/etc/NEWS b/etc/NEWS
index 8b7c2f7850..45628b6a65 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -189,6 +189,16 @@ For example, a 'display-buffer-alist' entry of
 will make the body of the chosen window 40 columns wide.  For the
 height use 'window-height' in combination with 'body-lines'.
 
+** Frames
+
++++
+*** Deleted frames can now be undeleted.
+The 16 most recently deleted frames can be undeleted with C-x 5 u when
+undelete-frame-mode is enabled.  Without a prefix argument, undelete
+the most recently deleted frame.  With a numerical prefix argument
+between 1 and 16, where 1 is the most recently deleted frame, undelete
+the corresponding deleted frame.
+
 ** Tab Bars and Tab Lines
 
 ---
diff --git a/lisp/frame.el b/lisp/frame.el
index 1319759e74..45dade7410 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -2499,6 +2499,80 @@ delete-other-frames
         (if iconify (iconify-frame this) (delete-frame this)))
       (setq this next))))
 
+(eval-when-compile (require 'frameset))
+
+(defvar undelete-frame--deleted-frames nil
+  "Internal variable used by `undelete-frame--handle-delete-frame'.")
+
+(defun undelete-frame--handle-delete-frame (frame)
+  "Save the configuration of frames deleted with `delete-frame'.
+Only the 16 most recently deleted frames are saved."
+  (when (frame-live-p frame)
+    (setq undelete-frame--deleted-frames
+          (cons
+           (cons
+            (display-graphic-p)
+            (frameset-save
+             (list frame)
+             ;; When the daemon is started from a graphical
+             ;; environment, TTY frames have a 'display' parameter set
+             ;; to the value of $DISPLAY (see the note in
+             ;; `server--on-display-p').  Do not store that parameter
+             ;; in the frameset, otherwise `frameset-restore' attempts
+             ;; to restore a graphical frame.
+             :filters (if (display-graphic-p)
+                          frameset-filter-alist
+                        (cons '(display . :never)
+                              frameset-filter-alist))))
+           undelete-frame--deleted-frames))
+    (if (> (length undelete-frame--deleted-frames) 16)
+        (setq undelete-frame--deleted-frames
+              (butlast undelete-frame--deleted-frames)))))
+
+(define-minor-mode undelete-frame-mode
+  "Enable the `undelete-frame' command."
+  :group 'frames
+  :global t
+  (if undelete-frame-mode
+      (add-hook 'delete-frame-functions
+                #'undelete-frame--handle-delete-frame -75)
+    (remove-hook 'delete-frame-functions
+                 #'undelete-frame--handle-delete-frame)
+    (setq undelete-frame--deleted-frames nil)))
+
+(defun undelete-frame (&optional arg)
+  "Undelete a frame deleted with `delete-frame'.
+Without a prefix argument, undelete the most recently deleted
+frame.
+With a numerical prefix argument ARG between 1 and 16, where 1 is
+most recently deleted frame, undelete the ARGth deleted frame.
+When called from Lisp, returns the new frame."
+  (interactive "P")
+  (if (not undelete-frame-mode)
+      (user-error "Undelete-Frame mode is disabled")
+    (if (consp arg)
+        (user-error "Missing deleted frame number argument")
+      (let* ((number (pcase arg ('nil 1) ('- -1) (_ arg)))
+             (frames (frame-list))
+             (frameset (nth (1- number) undelete-frame--deleted-frames))
+             (graphic (display-graphic-p)))
+        (if (not (<= 1 number 16))
+            (user-error "%d is not a valid deleted frame number argument"
+                        number)
+          (if (not frameset)
+              (user-error "No deleted frame with number %d" number)
+            (if (not (eq graphic (car frameset)))
+                (user-error
+                 "Cannot undelete a %s display frame on a %s display"
+                 (if graphic "non-graphic" "graphic")
+                 (if graphic "graphic" "non-graphic"))
+              (setq undelete-frame--deleted-frames
+                    (delq frameset undelete-frame--deleted-frames))
+              (frameset-restore (cdr frameset))
+              (let ((frame (car (seq-difference (frame-list) frames))))
+                (when frame
+                  (select-frame-set-input-focus frame)
+                  frame)))))))))
 \f
 ;;; Window dividers.
 (defgroup window-divider nil
@@ -2843,6 +2917,7 @@ ctl-x-5-map
 (define-key ctl-x-5-map "o" #'other-frame)
 (define-key ctl-x-5-map "5" #'other-frame-prefix)
 (define-key ctl-x-5-map "c" #'clone-frame)
+(define-key ctl-x-5-map "u" #'undelete-frame)
 (define-key global-map [f11] #'toggle-frame-fullscreen)
 (define-key global-map [(meta f10)] #'toggle-frame-maximized)
 (define-key esc-map    [f10]        #'toggle-frame-maximized)
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index 274f594f69..9500f577bc 100644
--- a/lisp/menu-bar.el
+++ b/lisp/menu-bar.el
@@ -109,6 +109,15 @@ menu-bar-file-menu
       (bindings--define-key menu [separator-tab]
         menu-bar-separator))
 
+    (bindings--define-key menu [enable-undelete-frame-mode]
+      '(menu-item "Enable Frame Undeletion" undelete-frame-mode
+                  :visible (null undelete-frame-mode)
+                  :help "Enable frame undeletion for this session"))
+    (bindings--define-key menu [undelete-last-deleted-frame]
+      '(menu-item "Undelete Frame" undelete-frame
+                  :visible undelete-frame-mode
+                  :help "Undelete the most recently deleted frame"))
+
     ;; Don't use delete-frame as event name because that is a special
     ;; event.
     (bindings--define-key menu [delete-this-frame]
diff --git a/src/frame.c b/src/frame.c
index 33e9606e41..04dd6e3550 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -2376,9 +2376,12 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
 }
 
 DEFUN ("delete-frame", Fdelete_frame, Sdelete_frame, 0, 2, "",
-       doc: /* Delete FRAME, permanently eliminating it from use.
+       doc: /* Delete FRAME, eliminating it from use.
 FRAME must be a live frame and defaults to the selected one.
 
+When `undelete-frame-mode' is enabled, the 16 most recently deleted
+frames can be undeleted with `undelete-frame', which see.
+
 A frame may not be deleted if its minibuffer serves as surrogate
 minibuffer for another frame.  Normally, you may not delete a frame if
 all other frames are invisible, but if the second optional argument
-- 
2.33.0


^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-29 13:38                                                 ` Gregory Heytings
@ 2021-11-29 18:18                                                   ` Michael Heerdegen
  2021-11-29 19:07                                                     ` Michael Heerdegen
  0 siblings, 1 reply; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-29 18:18 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: 51883, juri

Gregory Heytings <gregory@heytings.org> writes:

> Thanks for your feedback.

Thanks for your work and patience!

Ok, then we are done with comments from my side.  I think you can wait
another two days or so for other comments, and then it's really time to
install the patch to master.

Thanks!

Michael.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-29 18:18                                                   ` Michael Heerdegen
@ 2021-11-29 19:07                                                     ` Michael Heerdegen
  2021-11-29 20:19                                                       ` Juri Linkov
  0 siblings, 1 reply; 51+ messages in thread
From: Michael Heerdegen @ 2021-11-29 19:07 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: 51883, juri

Michael Heerdegen <michael_heerdegen@web.de> writes:

> Thanks for your work and patience!

Oh - and the tab-bar - Juri had mentioned it - do we have to do anything
special about it?  In my tests your patch restored the tab-bar correctly
I think.

Michael.





^ permalink raw reply	[flat|nested] 51+ messages in thread

* bug#51883: 29.0.50; Command to get accidentally deleted frames back
  2021-11-29 19:07                                                     ` Michael Heerdegen
@ 2021-11-29 20:19                                                       ` Juri Linkov
  0 siblings, 0 replies; 51+ messages in thread
From: Juri Linkov @ 2021-11-29 20:19 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: Gregory Heytings, 51883

>> Thanks for your work and patience!
>
> Oh - and the tab-bar - Juri had mentioned it - do we have to do anything
> special about it?  In my tests your patch restored the tab-bar correctly
> I think.

I'm waiting when the Gregory's patch will be pushed to master
to start testing the tab-bar with it :-)





^ permalink raw reply	[flat|nested] 51+ messages in thread

end of thread, other threads:[~2021-11-29 20:19 UTC | newest]

Thread overview: 51+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-11-15 23:38 bug#51883: 29.0.50; Command to get accidentally deleted frames back Michael Heerdegen
2021-11-16  7:53 ` Juri Linkov
2021-11-16  8:14   ` Lars Ingebrigtsen
2021-11-16 20:46   ` Juri Linkov
2021-11-16  8:49 ` Visuwesh via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-16 20:20   ` Juri Linkov
2021-11-16 15:17 ` Gregory Heytings
2021-11-16 17:05   ` Gregory Heytings
2021-11-16 17:40     ` Eli Zaretskii
2021-11-16 21:29       ` Gregory Heytings
2021-11-17 10:02         ` Gregory Heytings
2021-11-17 13:11         ` Eli Zaretskii
2021-11-17 17:06           ` Juri Linkov
2021-11-17 17:14             ` Eli Zaretskii
2021-11-19  9:00           ` Gregory Heytings
2021-11-19 12:17             ` Eli Zaretskii
2021-11-24  0:44               ` Gregory Heytings
2021-11-27 11:36                 ` Michael Heerdegen
2021-11-27 11:53                   ` Gregory Heytings
2021-11-27 12:05                     ` Eli Zaretskii
2021-11-27 12:12                       ` Gregory Heytings
2021-11-27 12:30                         ` Andreas Schwab
2021-11-27 12:34                         ` Eli Zaretskii
2021-11-27 12:23                       ` Michael Heerdegen
2021-11-27 12:40                         ` Eli Zaretskii
2021-11-27 13:22                           ` Michael Heerdegen
2021-11-27 13:26                             ` Eli Zaretskii
2021-11-27 13:34                               ` Michael Heerdegen
2021-11-27 13:56                                 ` Eli Zaretskii
2021-11-27 13:59                                   ` Michael Heerdegen
2021-11-27 14:02                                     ` Eli Zaretskii
2021-11-27 14:08                                       ` Michael Heerdegen
2021-11-27 14:47                                         ` Eli Zaretskii
2021-11-27 14:12                                 ` Gregory Heytings
2021-11-27 14:24                                   ` Michael Heerdegen
2021-11-27 14:26                                     ` Gregory Heytings
2021-11-27 14:33                                       ` Michael Heerdegen
2021-11-27 14:42                                         ` Gregory Heytings
2021-11-27 14:54                                           ` Michael Heerdegen
2021-11-27 17:19                                             ` Gregory Heytings
2021-11-28 15:47                                               ` Michael Heerdegen
2021-11-29 13:38                                                 ` Gregory Heytings
2021-11-29 18:18                                                   ` Michael Heerdegen
2021-11-29 19:07                                                     ` Michael Heerdegen
2021-11-29 20:19                                                       ` Juri Linkov
2021-11-27 14:48                                   ` Eli Zaretskii
2021-11-27 12:13                   ` Michael Heerdegen
2021-11-16 20:30   ` Juri Linkov
2021-11-17  4:13 ` Richard Stallman
2021-11-17 10:07   ` Gregory Heytings
2021-11-17 16:39   ` bug#51883: [External] : " Drew Adams

Code repositories for project(s) associated with this inbox:

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).