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