Subject: bug#58909: 29.0.50; [WIP PATCH] Deleting the last frame of an emacsclient doesn't ask to save

From: Jim Porter <[email-masked]>
Date: Sun, 30 Oct 2022 15:29:30 -0700

(Note: this is tangentially related to bug#51993, but it happens 
regardless of whether 'server-stop-automatically' is enabled.)

In most cases when you do something to exit an Emacs client, it prompts 
you to save buffers associated with that client. For example:

   $ emacs -Q -f server-start
   $ emacsclient -c foo.txt

   ;; type some random characters

   C-x #
   ;; or...
   C-x C-c

In both cases, Emacs will ask to save foo.txt, though the prompts will 
be slightly different. That's good, since whatever invoked "emacsclient 
-c foo.txt" is likely waiting for the user to have saved that file.

However, if you use 'C-x 5 0' instead, that terminates the Emacs client, 
but *doesn't* prompt to save foo.txt. I think it should prompt in this 
case too: all three of 'C-x #', 'C-x C-c', and 'C-x 5 0' have the effect 
(in this simple case) of deleting the Emacs client and returning to the 
calling process. (A user who wanted to bail out of an Emacs client 
without saving should use 'sever-edit-abort' instead.)

Attached is a WIP patch to do this. It's WIP because it will need to 
have some special handling for the 'server-stop-automatically' case so 
that it doesn't prompt twice in some cases. I also changed how 
'delete-frame' calls 'delete-frame-functions'. Hopefully the change I 
made is correct; I'm hesitant to change low-level code like that, but I 
think it's the right thing to do for this case at least. (Maybe that 
change should be called out in NEWS?)

Ultimately, it might make sense to merge this bug with bug#51993, but 
the discussion in that bug is already pretty long, and I think it would 
just confuse matters to add even more tangents to that discussion.

From 870d99540483d39174860190872d7e61f2205fad Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Sat, 29 Oct 2022 20:40:49 -0700
Subject: [PATCH] When deleting the last frame of an Emacs client, ask to save

* src/frame.c (delete_frame): Call 'delete-frame-functions' with
'call2' instead of 'safe_call2' to allow hooks to quit, cancelling the
frame's deletion.

* lisp/server.el (server-save-some-buffers): New function...
(server-handle-delete-frame, server-save-buffers-kill-terminal):
... use it.
---
 lisp/server.el | 31 +++++++++++++++++++------------
 src/frame.c    |  2 +-
 2 files changed, 20 insertions(+), 13 deletions(-)

diff --git a/lisp/server.el b/lisp/server.el
index 90d97c1538..de4c637caa 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -479,6 +479,20 @@ server-unselect-display
       (when (buffer-live-p buffer)
 	(kill-buffer buffer)))))
 
+(defun server-save-some-buffers (proc &optional arg)
+  "Save modified file-visiting buffers from the emacsclient file list.
+PROC is the emacsclient process.  If ARG is non-nil, save all
+with no questions."
+  (let ((buffers (process-get proc 'buffers)))
+    (save-some-buffers
+     arg (if buffers
+             ;; Only files from emacsclient file list.
+             (lambda () (memq (current-buffer) buffers))
+           ;; If there's no emacsclient file list: don't override
+           ;; `save-some-buffers-default-predicate' (unless ARG is
+           ;; non-nil).
+           (and arg t)))))
+
 (defun server-handle-delete-frame (frame)
   "Delete the client connection when the emacsclient frame is deleted.
 \(To be used from `delete-frame-functions'.)"
@@ -492,7 +506,9 @@ server-handle-delete-frame
                             (eq proc (frame-parameter f 'client))))
                      (frame-list))))
       (server-log (format "server-handle-delete-frame, frame %s" frame) proc)
-      (server-delete-client proc 'noframe)))) ; Let delete-frame delete the frame later.
+      (server-save-some-buffers proc))
+      ;; Let delete-frame delete the frame later.
+      (server-delete-client proc 'noframe)))
 
 (defun server-handle-suspend-tty (terminal)
   "Notify the client process that its tty device is suspended."
@@ -1752,17 +1768,8 @@ server-save-buffers-kill-terminal
 	       ;; If we're the last frame standing, kill Emacs.
 	       (save-buffers-kill-emacs arg)))
 	    ((processp proc)
-	     (let ((buffers (process-get proc 'buffers)))
-	       (save-some-buffers
-	        arg (if buffers
-                        ;; Only files from emacsclient file list.
-		        (lambda () (memq (current-buffer) buffers))
-                      ;; No emacsclient file list: don't override
-                      ;; `save-some-buffers-default-predicate' (unless
-                      ;; ARG is non-nil), since we're not killing
-                      ;; Emacs (unlike `save-buffers-kill-emacs').
-		      (and arg t)))
-	       (server-delete-client proc)))
+             (server-save-some-buffers proc arg)
+	     (server-delete-client proc))
 	    (t (error "Invalid client frame"))))))
 
 (defun server-stop-automatically--handle-delete-frame (frame)
diff --git a/src/frame.c b/src/frame.c
index f076a5ba54..8a85d5a400 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -2037,7 +2037,7 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
       x_clipboard_manager_save_frame (frame);
 #endif
 
-      safe_call2 (Qrun_hook_with_args, Qdelete_frame_functions, frame);
+      call2 (Qrun_hook_with_args, Qdelete_frame_functions, frame);
     }
 
   /* delete_frame_functions may have deleted any frame, including this
-- 
2.25.1