From e4db05933e1f801fc4ed4015fd392e32ac801fcd Mon Sep 17 00:00:00 2001 From: Gregory Heytings Date: Tue, 26 Oct 2021 14:59:37 +0000 Subject: [PATCH] Options to automatically stop the Emacs server. * lisp/server.el (server-stop-automatically): New function. (server-stop-automatically): New auxiliary variable. (server-stop-automatically--maybe-kill-emacs, server-stop-automatically--handle-delete-frame): New auxiliary functions. (server-save-buffers-kill-terminal): Call the new auxiliary function when necessary. * doc/emacs/misc.texi (Emacs Server): Document the new function. Also mention that an Emacs server can be started with emacsclient. * etc/NEWS: Describe the new function. --- doc/emacs/misc.texi | 31 ++++++++++++ etc/NEWS | 9 ++++ lisp/server.el | 119 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 138 insertions(+), 21 deletions(-) diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi index 5123a716dc..f94e48f57b 100644 --- a/doc/emacs/misc.texi +++ b/doc/emacs/misc.texi @@ -1697,6 +1697,11 @@ Emacs Server calls @code{server-start} after initialization and does not open an initial frame. It then waits for edit requests from clients. +@item +Run the command @code{emacsclient} with the @samp{--alternate-editor=""} +command-line option. This starts an Emacs daemon only if no Emacs daemon +is already running. + @cindex systemd unit file @item If your operating system uses @command{systemd} to manage startup, @@ -1763,6 +1768,32 @@ Emacs Server emacs --daemon=foo @end example +@findex server-stop-automatically + The Emacs server can optionally be stopped automatically when +certain conditions are met. To do this, call the function +@code{server-stop-automatically} in your init file (@pxref{Init +File}), with one of the following arguments: + +@itemize +@item +With the argument @code{'empty}, the server is stopped when it has no +clients, no unsaved file-visiting buffers and no running processes +anymore. + +@item +With the argument @code{'delete-frame}, when the last client frame is +being closed, you are asked whether each unsaved file-visiting buffer +must be saved and each unfinished process can be stopped, and if so, +the server is stopped. + +@item +With the argument @code{'kill-terminal}, when the last client frame is +being closed with @kbd{C-x C-c} (@code{save-buffers-kill-terminal}), +you are asked whether each unsaved file-visiting buffer must be saved +and each unfinished process can be stopped, and if so, the server is +stopped. +@end itemize + @findex server-eval-at If you have defined a server by a unique server name, it is possible to connect to the server from another Emacs instance and evaluate Lisp diff --git a/etc/NEWS b/etc/NEWS index 6d3256959e..03aa81042d 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -73,6 +73,15 @@ Image specifiers can now use ':type webp'. *** 'display-buffer' now can set up the body size of the chosen window. For example, an alist entry as '(window-width . (body-columns . 40))' will make the body of the chosen window 40 columns wide. + +** Emacs server and client changes + ++++ +*** 'server-stop-automatically' can be used to automatically stop the server. +The Emacs server will be automatically stopped when certain conditions +are met. The conditions are given by the argument, which can be +'empty, 'delete-frame or 'kill-terminal. + * Editing Changes in Emacs 29.1 diff --git a/lisp/server.el b/lisp/server.el index 5306a54776..0c53c58465 100644 --- a/lisp/server.el +++ b/lisp/server.el @@ -1716,6 +1716,9 @@ server-switch-buffer (when server-raise-frame (select-frame-set-input-focus (window-frame))))) +(defvar server-stop-automatically nil + "Internal status variable for `server-stop-automatically'.") + ;;;###autoload (defun server-save-buffers-kill-terminal (arg) ;; Called from save-buffers-kill-terminal in files.el. @@ -1724,27 +1727,101 @@ server-save-buffers-kill-terminal If emacsclient was started with a list of filenames to edit, then only these files will be asked to be saved." - (let ((proc (frame-parameter nil 'client))) - (cond ((eq proc 'nowait) - ;; Nowait frames have no client buffer list. - (if (cdr (frame-list)) - (progn (save-some-buffers arg) - (delete-frame)) - ;; 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))) - (t (error "Invalid client frame"))))) + (if server-stop-automatically + (server-stop-automatically--handle-delete-frame (selected-frame)) + (let ((proc (frame-parameter nil 'client))) + (cond ((eq proc 'nowait) + ;; Nowait frames have no client buffer list. + (if (cdr (frame-list)) + (progn (save-some-buffers arg) + (delete-frame)) + ;; 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))) + (t (error "Invalid client frame")))))) + +(defun server-stop-automatically--handle-delete-frame (frame) + "Handle deletion of FRAME when `server-stop-automatically' is used." + (when server-stop-automatically + (if (if (and (processp (frame-parameter frame 'client)) + (eq this-command 'save-buffers-kill-terminal)) + (progn + (dolist (f (frame-list)) + (when (and (eq (frame-parameter frame 'client) + (frame-parameter f 'client)) + (not (eq frame f))) + (set-frame-parameter f 'client nil) + (let ((server-stop-automatically nil)) + (delete-frame f)))) + (if (cddr (frame-list)) + (let ((server-stop-automatically nil)) + (delete-frame frame) + nil) + t)) + (null (cddr (frame-list)))) + (let ((server-stop-automatically nil)) + (save-buffers-kill-emacs) + (delete-frame frame))))) + +(defun server-stop-automatically--maybe-kill-emacs () + "Handle closing of Emacs daemon when `server-stop-automatically' is used." + (unless (cdr (frame-list)) + (when (and + (not (memq t (mapcar (lambda (b) + (and (buffer-file-name b) + (buffer-modified-p b))) + (buffer-list)))) + (not (memq t (mapcar (lambda (p) + (and (memq (process-status p) + '(run stop open listen)) + (process-query-on-exit-flag p))) + (process-list))))) + (kill-emacs)))) + +;;;###autoload +(defun server-stop-automatically (arg) + "Automatically stop server when possible. + +When ARG is 'empty, the server is stopped when it has no remaining +clients, no remaining unsaved file-visiting buffers, and no +running processes with a query-on-exit flag. + +When ARG is 'delete-frame, the user is asked when the last frame is +being closed whether each unsaved file-visiting buffer must be +saved and each running process with a query-on-exit flag can be +stopped, and if so, the server itself is stopped. + +When ARG is 'kill-terminal, the user is asked when the last frame +is being close with \\[save-buffers-kill-terminal] \ +whether each unsaved file-visiting +buffer must be saved and each running process with a query-on-exit +flag can be stopped, and if so, the server itself is stopped. + +This function is meant to be put in init files." + (when (daemonp) + (setq server-stop-automatically arg) + (cond + ((eq arg 'empty) + (setq server-stop-automatically nil) + (run-with-timer 10 2 + #'server-stop-automatically--maybe-kill-emacs)) + ((eq arg 'delete-frame) + (add-hook 'delete-frame-functions + #'server-stop-automatically--handle-delete-frame)) + ((eq arg 'kill-terminal)) + (t + (error "Unexpected argument"))))) (define-key ctl-x-map "#" 'server-edit) -- 2.33.0