Options to automatically stop the Emacs server

* 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 (bug#51377).
* 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.
This commit is contained in:
Gregory Heytings 2021-11-11 06:43:10 +01:00 committed by Lars Ingebrigtsen
parent 0a93fb499b
commit 894dd18804
3 changed files with 135 additions and 21 deletions

View file

@ -1703,6 +1703,11 @@ options. @xref{Initial Options}. When Emacs is started this way, it
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,
@ -1769,6 +1774,32 @@ you can give each daemon its own server name like this:
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

View file

@ -139,6 +139,12 @@ suspicious and could be malicious.
With this command-line option, Emacs reuses an existing graphical client
frame if one exists; otherwise it creates a new frame.
+++
*** '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
---

View file

@ -1716,6 +1716,9 @@ be a cons cell (LINENUMBER . COLUMNNUMBER)."
(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 @@ With ARG non-nil, silently save all file-visiting buffers, then kill.
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)