Contents in 1999-06-04-13 of release-21-2.
[chise/xemacs-chise.git.1] / lisp / gnuserv.el
1 ;;; gnuserv.el --- Lisp interface code between Emacs and gnuserv
2 ;; Copyright (C) 1989-1997 Free Software Foundation, Inc.
3
4 ;; Version: 3.11
5 ;; Author: Andy Norman (ange@hplb.hpl.hp.com), originally based on server.el
6 ;;         Hrvoje Niksic <hniksic@srce.hr>
7 ;; Maintainer: Jan Vroonhof <vroonhof@math.ethz.ch>,
8 ;;             Hrvoje Niksic <hniksic@srce.hr>
9 ;; Keywords: environment, processes, terminals
10
11 ;; This file is part of XEmacs.
12
13 ;; XEmacs is free software; you can redistribute it and/or modify it
14 ;; under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 2, or (at your option)
16 ;; any later version.
17
18 ;; XEmacs is distributed in the hope that it will be useful, but
19 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 ;; General Public License for more details.
22
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with XEmacs; see the file COPYING.  If not, write to the
25 ;; Free Software Foundation, 59 Temple Place - Suite 330,
26 ;; Boston, MA 02111-1307, USA.
27
28 ;;; Synched up with: Not in FSF.
29
30 ;;; Commentary:
31
32 ;; Gnuserv is run when Emacs needs to operate as a server for other
33 ;; processes.  Specifically, any number of files can be attached for
34 ;; editing to a running XEmacs process using the `gnuclient' program.
35
36 ;; Use `M-x gnuserv-start' to start the server and `gnuclient files'
37 ;; to load them to XEmacs.  When you are done with a buffer, press
38 ;; `C-x #' (`M-x gnuserv-edit').  You can put (gnuserv-start) to your
39 ;; .emacs, and enable `gnuclient' as your Unix "editor".  When all the
40 ;; buffers for a client have been edited and exited with
41 ;; `gnuserv-edit', the client "editor" will return to the program that
42 ;; invoked it.
43
44 ;; Your editing commands and Emacs' display output go to and from the
45 ;; terminal or X display in the usual way.  If you are running under
46 ;; X, a new X frame will be open for each gnuclient.  If you are on a
47 ;; TTY, this TTY will be attached as a new device to the running
48 ;; XEmacs, and will be removed once you are done with the buffer.
49
50 ;; To evaluate a Lisp form in a running Emacs, use the `-eval'
51 ;; argument of gnuclient.  To simplify this, we provide the `gnudoit'
52 ;; shell script.  For example `gnudoit "(+ 2 3)"' will print `5',
53 ;; whereas `gnudoit "(gnus)"' will fire up your favorite newsreader.
54 ;; Like gnuclient, `gnudoit' requires the server to be started prior
55 ;; to using it.
56
57 ;; For more information you can refer to man pages of gnuclient,
58 ;; gnudoit and gnuserv, distributed with XEmacs.
59
60 ;; gnuserv.el was originally written by Andy Norman as an improvement
61 ;; over William Sommerfeld's server.el.  Since then, a number of
62 ;; people have worked on it, including Bob Weiner, Darell Kindred,
63 ;; Arup Mukherjee, Ben Wing and Jan Vroonhof.  It was completely
64 ;; rewritten (labeled as version 3) by Hrvoje Niksic in May 1997.  The
65 ;; new code will not run on GNU Emacs.
66
67 ;; Jan Vroonhof <vroonhof@math.ethz.ch> July/1996
68 ;; ported the server-temp-file-regexp feature from server.el
69 ;; ported server hooks from server.el
70 ;; ported kill-*-query functions from server.el (and made it optional)
71 ;; synced other behavior with server.el
72 ;;
73 ;; Jan Vroonhof
74 ;;     Customized.
75 ;;
76 ;; Hrvoje Niksic <hniksic@srce.hr> May/1997
77 ;;     Completely rewritten.  Now uses `defstruct' and other CL stuff
78 ;;     to define clients cleanly.  Many thanks to Dave Gillespie!
79 ;;
80 ;; Mike Scheidler <c23mts@eng.delcoelect.com> July, 1997
81 ;;     Added 'Done' button to the menubar.
82
83 \f
84 ;;; Code:
85
86 (defgroup gnuserv nil
87   "The gnuserv suite of programs to talk to Emacs from outside."
88   :group 'environment
89   :group 'processes
90   :group 'terminals)
91
92
93 ;; Provide the old variables as aliases, to avoid breaking .emacs
94 ;; files.  However, they are obsolete and should be converted to the
95 ;; new forms.  This ugly crock must be before the variable
96 ;; declaration, or the scheme fails.
97
98 (define-obsolete-variable-alias 'server-frame 'gnuserv-frame)
99 (define-obsolete-variable-alias 'server-done-function
100   'gnuserv-done-function)
101 (define-obsolete-variable-alias 'server-done-temp-file-function
102   'gnuserv-done-temp-file-function)
103 (define-obsolete-variable-alias 'server-find-file-function
104   'gnuserv-find-file-function)
105 (define-obsolete-variable-alias 'server-program
106   'gnuserv-program)
107 (define-obsolete-variable-alias 'server-visit-hook
108   'gnuserv-visit-hook)
109 (define-obsolete-variable-alias 'server-done-hook
110   'gnuserv-done-hook)
111 (define-obsolete-variable-alias 'server-kill-quietly
112   'gnuserv-kill-quietly)
113 (define-obsolete-variable-alias 'server-temp-file-regexp
114   'gnuserv-temp-file-regexp)
115 (define-obsolete-variable-alias 'server-make-temp-file-backup
116   'gnuserv-make-temp-file-backup)
117
118 ;;;###autoload
119 (defcustom gnuserv-frame nil
120   "*The frame to be used to display all edited files.
121 If nil, then a new frame is created for each file edited.
122 If t, then the currently selected frame will be used.
123 If a function, then this will be called with a symbol `x' or `tty' as the
124 only argument, and its return value will be interpreted as above."
125   :tag "Gnuserv Frame"
126   :type '(radio (const :tag "Create new frame each time" nil)
127                 (const :tag "Use selected frame" t)
128                 (function-item :tag "Use main Emacs frame"
129                                gnuserv-main-frame-function)
130                 (function-item :tag "Use visible frame, otherwise create new"
131                                gnuserv-visible-frame-function)
132                 (function-item :tag "Create special Gnuserv frame and use it"
133                                gnuserv-special-frame-function)
134                 (function :tag "Other"))
135   :group 'gnuserv
136   :group 'frames)
137
138 (defcustom gnuserv-frame-plist nil
139   "*Plist of frame properties for creating a gnuserv frame."
140   :type 'plist
141   :group 'gnuserv
142   :group 'frames)
143
144 (defcustom gnuserv-done-function 'kill-buffer
145   "*Function used to remove a buffer after editing.
146 It is called with one BUFFER argument.  Functions such as `kill-buffer' and
147 `bury-buffer' are good values. See also `gnuserv-done-temp-file-function'."
148   :type '(radio (function-item kill-buffer)
149                 (function-item bury-buffer)
150                 (function :tag "Other"))
151   :group 'gnuserv)
152
153 (defcustom gnuserv-done-temp-file-function 'kill-buffer
154   "*Function used to remove a temporary buffer after editing.
155 It is called with one BUFFER argument.  Functions such as `kill-buffer' and
156 `bury-buffer' are good values. See also `gnuserv-done-temp-file-function'."
157   :type '(radio (function-item kill-buffer)
158                 (function-item bury-buffer)
159                 (function :tag "Other"))
160   :group 'gnuserv)
161
162 (defcustom gnuserv-find-file-function 'find-file
163   "*Function to visit a file with.
164 It takes one argument, a file name to visit."
165   :type 'function
166   :group 'gnuserv)
167
168 (defcustom gnuserv-view-file-function 'view-file
169   "*Function to view a file with.
170 It takes one argument, a file name to view."
171   :type '(radio (function-item view-file)
172                 (function-item find-file-read-only)
173                 (function :tag "Other"))
174   :group 'gnuserv)
175
176 (defcustom gnuserv-program "gnuserv"
177   "*Program to use as the editing server."
178   :type 'string
179   :group 'gnuserv)
180
181 (defcustom gnuserv-visit-hook nil
182   "*Hook run after visiting a file."
183   :type 'hook
184   :group 'gnuserv)
185
186 (defcustom gnuserv-done-hook nil
187   "*Hook run when done editing a buffer for the Emacs server.
188 The hook functions are called after the file has been visited, with the
189 current buffer set to the visiting buffer."
190   :type 'hook
191   :group 'gnuserv)
192
193 (defcustom gnuserv-init-hook nil
194   "*Hook run after the server is started."
195   :type 'hook
196   :group 'gnuserv)
197
198 (defcustom gnuserv-shutdown-hook nil
199   "*Hook run before the server exits."
200   :type 'hook
201   :group 'gnuserv)
202
203 (defcustom gnuserv-kill-quietly nil
204   "*Non-nil means to kill buffers with clients attached without requiring confirmation."
205   :type 'boolean
206   :group 'gnuserv)
207
208 (defcustom gnuserv-temp-file-regexp
209   (concat "^" (temp-directory) "/Re\\|/draft$")
210   "*Regexp which should match filenames of temporary files deleted
211 and reused by the programs that invoke the Emacs server."
212   :type 'regexp
213   :group 'gnuserv)
214
215 (defcustom gnuserv-make-temp-file-backup nil
216   "*Non-nil makes the server backup temporary files also."
217   :type 'boolean
218   :group 'gnuserv)
219
220 \f
221 ;;; Internal variables:
222
223 (defstruct gnuclient
224   "An object that encompasses several buffers in one.
225 Normally, a client connecting to Emacs will be assigned an id, and
226 will request editing of several files.
227
228 ID       - Client id (integer).
229 BUFFERS  - List of buffers that \"belong\" to the client.
230            NOTE: one buffer can belong to several clients.
231 DEVICE   - The device this client is on.  If the device was also created.
232            by a client, it will be placed to `gnuserv-devices' list.
233 FRAME    - Frame created by the client, or nil if the client didn't
234            create a frame.
235
236 All the slots default to nil."
237   (id nil)
238   (buffers nil)
239   (device nil)
240   (frame nil))
241
242 (defvar gnuserv-process nil
243   "The current gnuserv process.")
244
245 (defvar gnuserv-string ""
246   "The last input string from the server.")
247
248 (defvar gnuserv-current-client nil
249   "The client we are currently talking to.")
250
251 (defvar gnuserv-clients nil
252   "List of current gnuserv clients.
253 Each element is a gnuclient structure that identifies a client.")
254
255 (defvar gnuserv-devices nil
256   "List of devices created by clients.")
257
258 (defvar gnuserv-special-frame nil
259   "Frame created specially for Server.")
260
261 ;; We want the client-infested buffers to have some modeline
262 ;; identification, so we'll make a "minor mode".
263 (defvar gnuserv-minor-mode nil)
264 (make-variable-buffer-local 'gnuserv-mode)
265 (pushnew '(gnuserv-minor-mode " Server") minor-mode-alist
266           :test 'equal)
267
268 \f
269 ;; Sample gnuserv-frame functions
270
271 (defun gnuserv-main-frame-function (type)
272   "Return a sensible value for the main Emacs frame."
273   (if (or (eq type 'x)
274           (eq type 'mswindows))
275       (car (frame-list))
276     nil))
277
278 (defun gnuserv-visible-frame-function (type)
279   "Return a frame if there is a frame that is truly visible, nil otherwise.
280 This is meant in the X sense, so it will not return frames that are on another
281 visual screen.  Totally visible frames are preferred.  If none found, return nil."
282   (if (or (eq type 'x)
283           (eq type 'mswindows))
284       (cond ((car (filtered-frame-list 'frame-totally-visible-p
285                                        (selected-device))))
286             ((car (filtered-frame-list (lambda (frame)
287                                          ;; eq t as in not 'hidden
288                                          (eq t (frame-visible-p frame)))
289                                        (selected-device)))))
290     nil))
291
292 (defun gnuserv-special-frame-function (type)
293   "Create a special frame for Gnuserv and return it on later invocations."
294   (unless (frame-live-p gnuserv-special-frame)
295     (setq gnuserv-special-frame (make-frame gnuserv-frame-plist)))
296   gnuserv-special-frame)
297
298 \f
299 ;;; Communication functions
300
301 ;; We used to restart the server here, but it's too risky -- if
302 ;; something goes awry, it's too easy to wind up in a loop.
303 (defun gnuserv-sentinel (proc msg)
304   (let ((msgstring (concat "Gnuserv process %s; restart with `%s'"))
305         (keystring (substitute-command-keys "\\[gnuserv-start]")))
306   (case (process-status proc)
307     (exit
308      (message msgstring "exited" keystring)
309      (gnuserv-prepare-shutdown))
310     (signal
311      (message msgstring "killed" keystring)
312      (gnuserv-prepare-shutdown))
313     (closed
314      (message msgstring "closed" keystring))
315      (gnuserv-prepare-shutdown))))
316
317 ;; This function reads client requests from our current server.  Every
318 ;; client is identified by a unique ID within the server
319 ;; (incidentally, the same ID is the file descriptor the server uses
320 ;; to communicate to client).
321 ;;
322 ;; The request string can arrive in several chunks.  As the request
323 ;; ends with \C-d, we check for that character at the end of string.
324 ;; If not found, keep reading, and concatenating to former strings.
325 ;; So, if at first read we receive "5 (gn", that text will be stored
326 ;; to gnuserv-string.  If we then receive "us)\C-d", the two will be
327 ;; concatenated, `current-client' will be set to 5, and `(gnus)' form
328 ;; will be evaluated.
329 ;;
330 ;; Server will send the following:
331 ;;
332 ;; "ID <text>\C-d"  (no quotes)
333 ;;
334 ;;  ID    - file descriptor of the given client;
335 ;; <text> - the actual contents of the request.
336 (defun gnuserv-process-filter (proc string)
337   "Process gnuserv client requests to execute Emacs commands."
338   (setq gnuserv-string (concat gnuserv-string string))
339   ;; C-d means end of request.
340   (when (string-match "\C-d\\'" gnuserv-string)
341     (cond ((string-match "^[0-9]+" gnuserv-string) ; client request id
342            (let ((header (read-from-string gnuserv-string)))
343              ;; Set the client we are talking to.
344              (setq gnuserv-current-client (car header))
345              ;; Evaluate the expression
346              (condition-case oops
347                  (eval (car (read-from-string gnuserv-string (cdr header))))
348                ;; In case of an error, write the description to the
349                ;; client, and then signal it.
350                (error (setq gnuserv-string "")
351                       (gnuserv-write-to-client gnuserv-current-client oops)
352                       (setq gnuserv-current-client nil)
353                       (signal (car oops) (cdr oops)))
354                (quit (setq gnuserv-string "")
355                      (gnuserv-write-to-client gnuserv-current-client oops)
356                      (setq gnuserv-current-client nil)
357                      (signal 'quit nil)))
358              (setq gnuserv-string "")))
359           (t
360            (error "%s: invalid response from gnuserv" gnuserv-string)
361            (setq gnuserv-string "")))))
362
363 ;; This function is somewhat of a misnomer.  Actually, we write to the
364 ;; server (using `process-send-string' to gnuserv-process), which
365 ;; interprets what we say and forwards it to the client.  The
366 ;; incantation server understands is (from gnuserv.c):
367 ;;
368 ;; "FD/LEN:<text>\n"  (no quotes)
369 ;;    FD     - file descriptor of the given client (which we obtained from
370 ;;             the server earlier);
371 ;;    LEN    - length of the stuff we are about to send;
372 ;;    <text> - the actual contents of the request.
373 (defun gnuserv-write-to-client (client-id form)
374   "Write the given form to the given client via the gnuserv process."
375   (when (eq (process-status gnuserv-process) 'run)
376     (let* ((result (format "%s" form))
377            (s      (format "%s/%d:%s\n" client-id
378                            (length result) result)))
379       (process-send-string gnuserv-process s))))
380
381 ;; The following two functions are helper functions, used by
382 ;; gnuclient.
383
384 (defun gnuserv-eval (form)
385   "Evaluate form and return result to client."
386   (gnuserv-write-to-client gnuserv-current-client (eval form))
387   (setq gnuserv-current-client nil))
388
389 (defun gnuserv-eval-quickly (form)
390   "Let client know that we've received the request, and then eval the form.
391 This order is important as not to keep the client waiting."
392   (gnuserv-write-to-client gnuserv-current-client nil)
393   (setq gnuserv-current-client nil)
394   (eval form))
395
396 \f
397 ;; "Execute" a client connection, called by gnuclient.  This is the
398 ;; backbone of gnuserv.el.
399 (defun gnuserv-edit-files (type list &rest flags)
400   "For each (line-number . file) pair in LIST, edit the file at line-number.
401 The visited buffers are memorized, so that when \\[gnuserv-edit] is invoked
402 in such a buffer, or when it is killed, or the client's device deleted, the
403 client will be invoked that the edit is finished.
404
405 TYPE should either be a (tty TTY TERM PID) list, or (x DISPLAY) list.
406 If a flag is `quick', just edit the files in Emacs.
407 If a flag is `view', view the files read-only."
408   (let (quick view)
409     (mapc (lambda (flag)
410             (case flag
411               (quick (setq quick t))
412               (view  (setq view t))
413               (t     (error "Invalid flag %s" flag))))
414           flags)
415     (let* ((old-device-num (length (device-list)))
416            (new-frame nil)
417            (dest-frame (if (functionp gnuserv-frame)
418                            (funcall gnuserv-frame (car type))
419                          gnuserv-frame))
420            ;; The gnuserv-frame dependencies are ugly, but it's
421            ;; extremely hard to make that stuff cleaner without
422            ;; breaking everything in sight.
423            (device (cond ((frame-live-p dest-frame)
424                           (frame-device dest-frame))
425                          ((null dest-frame)
426                           (case (car type)
427                             (tty (apply 'make-tty-device (cdr type)))
428                             (x   (make-x-device (cadr type)))
429                             (mswindows   (make-mswindows-device))
430                             (t   (error "Invalid device type"))))
431                          (t
432                           (selected-device))))
433            (frame (cond ((frame-live-p dest-frame)
434                          dest-frame)
435                         ((null dest-frame)
436                          (setq new-frame (make-frame gnuserv-frame-plist
437                                                      device))
438                          new-frame)
439                         (t (selected-frame))))
440            (client (make-gnuclient :id gnuserv-current-client
441                                    :device device
442                                    :frame new-frame)))
443       (setq gnuserv-current-client nil)
444       ;; If the device was created by this client, push it to the list.
445       (and (/= old-device-num (length (device-list)))
446            (push device gnuserv-devices))
447       (and (frame-iconified-p frame)
448            (deiconify-frame frame))
449       ;; Visit all the listed files.
450       (while list
451         (let ((line (caar list)) (path (cdar list)))
452           (select-frame frame)
453           ;; Visit the file.
454           (funcall (if view
455                        gnuserv-view-file-function
456                      gnuserv-find-file-function)
457                    path)
458           (goto-line line)
459           ;; Don't memorize the quick and view buffers.
460           (unless (or quick view)
461             (pushnew (current-buffer) (gnuclient-buffers client))
462             (setq gnuserv-minor-mode t)
463             ;; Add the "Done" button to the menubar, only in this buffer.
464             (if (and (featurep 'menubar) current-menubar)
465               (progn (set-buffer-menubar current-menubar)
466               (add-menu-button nil ["Done" gnuserv-edit]))
467               ))
468           (run-hooks 'gnuserv-visit-hook)
469           (pop list)))
470       (cond
471        ((and (or quick view)
472              (device-on-window-system-p device))
473         ;; Exit if on X device, and quick or view.  NOTE: if the
474         ;; client is to finish now, it must absolutely /not/ be
475         ;; included to the list of clients.  This way the client-ids
476         ;; should be unique.
477         (gnuserv-write-to-client (gnuclient-id client) nil))
478        (t
479         ;; Else, the client gets a vote.
480         (push client gnuserv-clients)
481         ;; Explain buffer exit options.  If dest-frame is nil, the
482         ;; user can exit via `delete-frame'.  OTOH, if FLAGS are nil
483         ;; and there are some buffers, the user can exit via
484         ;; `gnuserv-edit'.
485         (if (and (not (or quick view))
486                  (gnuclient-buffers client))
487             (message "%s"
488                      (substitute-command-keys
489                       "Type `\\[gnuserv-edit]' to finish editing"))
490           (or dest-frame
491               (message "%s"
492                        (substitute-command-keys
493                         "Type `\\[delete-frame]' to finish editing")))))))))
494
495 \f
496 ;;; Functions that hook into Emacs in various way to enable operation
497
498 ;; Defined later.
499 (add-hook 'kill-emacs-hook 'gnuserv-kill-all-clients t)
500
501 ;; A helper function; used by others.  Try avoiding it whenever
502 ;; possible, because it is slow, and conses a list.  Use
503 ;; `gnuserv-buffer-p' when appropriate, for instance.
504 (defun gnuserv-buffer-clients (buffer)
505   "Return a list of clients to which BUFFER belongs."
506   (let (res)
507     (dolist (client gnuserv-clients)
508       (when (memq buffer (gnuclient-buffers client))
509         (push client res)))
510     res))
511
512 ;; Like `gnuserv-buffer-clients', but returns a boolean; doesn't
513 ;; collect a list.
514 (defun gnuserv-buffer-p (buffer)
515   (member* buffer gnuserv-clients
516            :test 'memq
517            :key 'gnuclient-buffers))
518
519 ;; This function makes sure that a killed buffer is deleted off the
520 ;; list for the particular client.
521 ;;
522 ;; This hooks into `kill-buffer-hook'.  It is *not* a replacement for
523 ;; `kill-buffer' (thanks God).
524 (defun gnuserv-kill-buffer-function ()
525   "Remove the buffer from the buffer lists of all the clients it belongs to.
526 Any client that remains \"empty\" after the removal is informed that the
527 editing has ended."
528   (let* ((buf (current-buffer)))
529     (dolist (client (gnuserv-buffer-clients buf))
530       (callf2 delq buf (gnuclient-buffers client))
531       ;; If no more buffers, kill the client.
532       (when (null (gnuclient-buffers client))
533         (gnuserv-kill-client client)))))
534
535 (add-hook 'kill-buffer-hook 'gnuserv-kill-buffer-function)
536
537 ;; Ask for confirmation before killing a buffer that belongs to a
538 ;; living client.
539 (defun gnuserv-kill-buffer-query-function ()
540   (or gnuserv-kill-quietly
541       (not (gnuserv-buffer-p (current-buffer)))
542       (yes-or-no-p
543        (format "Buffer %s belongs to gnuserv client(s); kill anyway? "
544                (current-buffer)))))
545
546 (add-hook 'kill-buffer-query-functions
547           'gnuserv-kill-buffer-query-function)
548
549 (defun gnuserv-kill-emacs-query-function ()
550   (or gnuserv-kill-quietly
551       (not (some 'gnuclient-buffers gnuserv-clients))
552       (yes-or-no-p "Gnuserv buffers still have clients; exit anyway? ")))
553
554 (add-hook 'kill-emacs-query-functions
555           'gnuserv-kill-emacs-query-function)
556
557 ;; If the device of a client is to be deleted, the client should die
558 ;; as well.  This is why we hook into `delete-device-hook'.
559 (defun gnuserv-check-device (device)
560   (when (memq device gnuserv-devices)
561     (dolist (client gnuserv-clients)
562       (when (eq device (gnuclient-device client))
563         ;; we must make sure that the server kill doesn't result in
564         ;; killing the device, because it would cause a device-dead
565         ;; error when `delete-device' tries to do the job later.
566         (gnuserv-kill-client client t))))
567   (callf2 delq device gnuserv-devices))
568
569 (add-hook 'delete-device-hook 'gnuserv-check-device)
570
571 (defun gnuserv-temp-file-p (buffer)
572   "Return non-nil if BUFFER contains a file considered temporary.
573 These are files whose names suggest they are repeatedly
574 reused to pass information to another program.
575
576 The variable `gnuserv-temp-file-regexp' controls which filenames
577 are considered temporary."
578   (and (buffer-file-name buffer)
579        (string-match gnuserv-temp-file-regexp (buffer-file-name buffer))))
580
581 (defun gnuserv-kill-client (client &optional leave-frame)
582   "Kill the gnuclient CLIENT.
583 This will do away with all the associated buffers.  If LEAVE-FRAME,
584 the function will not remove the frames associated with the client."
585   ;; Order is important: first delete client from gnuserv-clients, to
586   ;; prevent gnuserv-buffer-done-1 calling us recursively.
587   (callf2 delq client gnuserv-clients)
588   ;; Process the buffers.
589   (mapc 'gnuserv-buffer-done-1 (gnuclient-buffers client))
590   (unless leave-frame
591     (let ((device (gnuclient-device client)))
592       ;; kill frame created by this client (if any), unless
593       ;; specifically requested otherwise.
594       ;;
595       ;; note: last frame on a device will not be deleted here.
596     (when (and (gnuclient-frame client)
597                (frame-live-p (gnuclient-frame client))
598                (second (device-frame-list device)))
599       (delete-frame (gnuclient-frame client)))
600     ;; If the device is live, created by a client, and no longer used
601     ;; by any client, delete it.
602     (when (and (device-live-p device)
603                (memq device gnuserv-devices)
604                (second (device-list))
605                (not (member* device gnuserv-clients
606                              :key 'gnuclient-device)))
607       ;; `gnuserv-check-device' will remove it from `gnuserv-devices'.
608       (delete-device device))))
609   ;; Notify the client.
610   (gnuserv-write-to-client (gnuclient-id client) nil))
611
612 ;; Do away with the buffer.
613 (defun gnuserv-buffer-done-1 (buffer)
614   (dolist (client (gnuserv-buffer-clients buffer))
615     (callf2 delq buffer (gnuclient-buffers client))
616     (when (null (gnuclient-buffers client))
617       (gnuserv-kill-client client)))
618   ;; Get rid of the buffer.
619   (save-excursion
620     (set-buffer buffer)
621     (run-hooks 'gnuserv-done-hook)
622     (setq gnuserv-minor-mode nil)
623     ;; Delete the menu button.
624     (if (and (featurep 'menubar) current-menubar)
625       (delete-menu-item '("Done")))
626     (funcall (if (gnuserv-temp-file-p buffer)
627                  gnuserv-done-temp-file-function
628                gnuserv-done-function)
629              buffer)))
630
631 \f
632 ;;; Higher-level functions
633
634 ;; Choose a `next' server buffer, according to several criteria, and
635 ;; return it.  If none are found, return nil.
636 (defun gnuserv-next-buffer ()
637   (let* ((frame (selected-frame))
638          (device (selected-device))
639          client)
640     (cond
641      ;; If we have a client belonging to this frame, return
642      ;; the first buffer from it.
643      ((setq client
644             (car (member* frame gnuserv-clients :key 'gnuclient-frame)))
645       (car (gnuclient-buffers client)))
646      ;; Else, look for a device.
647      ((and
648        (memq (selected-device) gnuserv-devices)
649        (setq client
650              (car (member* device gnuserv-clients :key 'gnuclient-device))))
651       (car (gnuclient-buffers client)))
652      ;; Else, try to find any client with at least one buffer, and
653      ;; return its first buffer.
654      ((setq client
655             (car (member-if-not #'null gnuserv-clients
656                                 :key 'gnuclient-buffers)))
657       (car (gnuclient-buffers client)))
658      ;; Oh, give up.
659      (t nil))))
660
661 (defun gnuserv-buffer-done (buffer)
662   "Mark BUFFER as \"done\" for its client(s).
663 Does the save/backup queries first, and calls `gnuserv-done-function'."
664   ;; Check whether this is the real thing.
665   (unless (gnuserv-buffer-p buffer)
666     (error "%s does not belong to a gnuserv client" buffer))
667   ;; Backup/ask query.
668   (if (gnuserv-temp-file-p buffer)
669       ;; For a temp file, save, and do NOT make a non-numeric backup
670       ;; Why does server.el explicitly back up temporary files?
671       (let ((version-control nil)
672             (buffer-backed-up (not gnuserv-make-temp-file-backup)))
673         (save-buffer))
674     (if (and (buffer-modified-p)
675              (y-or-n-p (concat "Save file " buffer-file-name "? ")))
676         (save-buffer buffer)))
677   (gnuserv-buffer-done-1 buffer))
678
679 ;; Called by `gnuserv-start-1' to clean everything.  Hooked into
680 ;; `kill-emacs-hook', too.
681 (defun gnuserv-kill-all-clients ()
682   "Kill all the gnuserv clients.  Ruthlessly."
683   (mapc 'gnuserv-kill-client gnuserv-clients))
684
685 ;; This serves to run the hook and reset
686 ;; `allow-deletion-of-last-visible-frame'.
687 (defun gnuserv-prepare-shutdown ()
688   (setq allow-deletion-of-last-visible-frame nil)
689   (run-hooks 'gnuserv-shutdown-hook))
690
691 ;; This is a user-callable function, too.
692 (defun gnuserv-shutdown ()
693   "Shutdown the gnuserv server, if one is currently running.
694 All the clients will be disposed of via the normal methods."
695   (interactive)
696   (gnuserv-kill-all-clients)
697   (when gnuserv-process
698     (set-process-sentinel gnuserv-process nil)
699     (gnuserv-prepare-shutdown)
700     (condition-case ()
701         (delete-process gnuserv-process)
702       (error nil))
703     (setq gnuserv-process nil)))
704
705 ;; Actually start the process.  Kills all the clients before-hand.
706 (defun gnuserv-start-1 (&optional leave-dead)
707   ;; Shutdown the existing server, if any.
708   (gnuserv-shutdown)
709   ;; If we already had a server, clear out associated status.
710   (unless leave-dead
711     (setq gnuserv-string ""
712           gnuserv-current-client nil)
713     (let ((process-connection-type t))
714       (setq gnuserv-process
715             (start-process "gnuserv" nil gnuserv-program)))
716     (set-process-sentinel gnuserv-process 'gnuserv-sentinel)
717     (set-process-filter gnuserv-process 'gnuserv-process-filter)
718     (process-kill-without-query gnuserv-process)
719     (setq allow-deletion-of-last-visible-frame t)
720     (run-hooks 'gnuserv-init-hook)))
721
722 \f
723 ;;; User-callable functions:
724
725 ;;;###autoload
726 (defun gnuserv-running-p ()
727   "Return non-nil if a gnuserv process is running from this XEmacs session."
728   (not (not gnuserv-process)))
729
730 ;;;###autoload
731 (defun gnuserv-start (&optional leave-dead)
732   "Allow this Emacs process to be a server for client processes.
733 This starts a gnuserv communications subprocess through which
734 client \"editors\" (gnuclient and gnudoit) can send editing commands to
735 this Emacs job.  See the gnuserv(1) manual page for more details.
736
737 Prefix arg means just kill any existing server communications subprocess."
738   (interactive "P")
739   (and gnuserv-process
740        (not leave-dead)
741        (message "Restarting gnuserv"))
742   (gnuserv-start-1 leave-dead))
743
744 (defun gnuserv-edit (&optional count)
745   "Mark the current gnuserv editing buffer as \"done\", and switch to next one.
746
747 Run with a numeric prefix argument, repeat the operation that number
748 of times.  If given a universal prefix argument, close all the buffers
749 of this buffer's clients.
750
751 The `gnuserv-done-function' (bound to `kill-buffer' by default) is
752 called to dispose of the buffer after marking it as done.
753
754 Files that match `gnuserv-temp-file-regexp' are considered temporary and
755 are saved unconditionally and backed up if `gnuserv-make-temp-file-backup'
756 is non-nil.  They are disposed of using `gnuserv-done-temp-file-function'
757 \(also bound to `kill-buffer' by default).
758
759 When all of a client's buffers are marked as \"done\", the client is notified."
760   (interactive "P")
761   (when (null count)
762     (setq count 1))
763   (cond ((numberp count)
764          (while (natnump (decf count))
765            (let ((frame (selected-frame)))
766              (gnuserv-buffer-done (current-buffer))
767              (when (eq frame (selected-frame))
768                ;; Switch to the next gnuserv buffer.  However, do this
769                ;; only if we remain in the same frame.
770                (let ((next (gnuserv-next-buffer)))
771                  (when next
772                    (switch-to-buffer next)))))))
773         (count
774            (let* ((buf (current-buffer))
775                   (clients (gnuserv-buffer-clients buf)))
776              (unless clients
777                (error "%s does not belong to a gnuserv client" buf))
778              (mapc 'gnuserv-kill-client (gnuserv-buffer-clients buf))))))
779
780 (global-set-key "\C-x#" 'gnuserv-edit)
781
782 (provide 'gnuserv)
783
784 ;;; gnuserv.el ends here