Source

XEmacs / lisp / gnuserv.el

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
;;; gnuserv.el --- Lisp interface code between Emacs and gnuserv
;; Copyright (C) 1989-1997 Free Software Foundation, Inc.

;; Version: 3.11
;; Author: Andy Norman (ange@hplb.hpl.hp.com), originally based on server.el
;;         Hrvoje Niksic <hniksic@srce.hr>
;; Maintainer: Jan Vroonhof <vroonhof@math.ethz.ch>,
;;             Hrvoje Niksic <hniksic@srce.hr>
;; Keywords: environment, processes, terminals

;; This file is part of XEmacs.

;; XEmacs is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; XEmacs is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with XEmacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Synched up with: Not in FSF.

;;; Commentary:

;; Gnuserv is run when Emacs needs to operate as a server for other
;; processes.  Specifically, any number of files can be attached for
;; editing to a running XEmacs process using the `gnuclient' program.

;; Use `M-x gnuserv-start' to start the server and `gnuclient files'
;; to load them to XEmacs.  When you are done with a buffer, press
;; `C-x #' (`M-x gnuserv-edit').  You can put (gnuserv-start) to your
;; .emacs, and enable `gnuclient' as your Unix "editor".  When all the
;; buffers for a client have been edited and exited with
;; `gnuserv-edit', the client "editor" will return to the program that
;; invoked it.

;; Your editing commands and Emacs' display output go to and from the
;; terminal or X display in the usual way.  If you are running under
;; X, a new X frame will be open for each gnuclient.  If you are on a
;; TTY, this TTY will be attached as a new device to the running
;; XEmacs, and will be removed once you are done with the buffer.

;; To evaluate a Lisp form in a running Emacs, use the `-eval'
;; argument of gnuclient.  To simplify this, we provide the `gnudoit'
;; shell script.  For example `gnudoit "(+ 2 3)"' will print `5',
;; whereas `gnudoit "(gnus)"' will fire up your favorite newsreader.
;; Like gnuclient, `gnudoit' requires the server to be started prior
;; to using it.

;; For more information you can refer to man pages of gnuclient,
;; gnudoit and gnuserv, distributed with XEmacs.

;; gnuserv.el was originally written by Andy Norman as an improvement
;; over William Sommerfeld's server.el.  Since then, a number of
;; people have worked on it, including Bob Weiner, Darell Kindred,
;; Arup Mukherjee, Ben Wing and Jan Vroonhof.  It was completely
;; rewritten (labeled as version 3) by Hrvoje Niksic in May 1997.  The
;; new code will not run on GNU Emacs.

;; Jan Vroonhof <vroonhof@math.ethz.ch> July/1996
;; ported the server-temp-file-regexp feature from server.el
;; ported server hooks from server.el
;; ported kill-*-query functions from server.el (and made it optional)
;; synced other behavior with server.el
;;
;; Jan Vroonhof
;;     Customized.
;;
;; Hrvoje Niksic <hniksic@srce.hr> May/1997
;;     Completely rewritten.  Now uses `defstruct' and other CL stuff
;;     to define clients cleanly.  Many thanks to Dave Gillespie!
;;
;; Mike Scheidler <c23mts@eng.delcoelect.com> July, 1997
;;     Added 'Done' button to the menubar.


;;; Code:

(defgroup gnuserv nil
  "The gnuserv suite of programs to talk to Emacs from outside."
  :group 'environment
  :group 'processes
  :group 'terminals)


;; Provide the old variables as aliases, to avoid breaking .emacs
;; files.  However, they are obsolete and should be converted to the
;; new forms.  This ugly crock must be before the variable
;; declaration, or the scheme fails.

(define-obsolete-variable-alias 'server-frame 'gnuserv-frame)
(define-obsolete-variable-alias 'server-done-function
  'gnuserv-done-function)
(define-obsolete-variable-alias 'server-done-temp-file-function
  'gnuserv-done-temp-file-function)
(define-obsolete-variable-alias 'server-find-file-function
  'gnuserv-find-file-function)
(define-obsolete-variable-alias 'server-program
  'gnuserv-program)
(define-obsolete-variable-alias 'server-visit-hook
  'gnuserv-visit-hook)
(define-obsolete-variable-alias 'server-done-hook
  'gnuserv-done-hook)
(define-obsolete-variable-alias 'server-kill-quietly
  'gnuserv-kill-quietly)
(define-obsolete-variable-alias 'server-temp-file-regexp
  'gnuserv-temp-file-regexp)
(define-obsolete-variable-alias 'server-make-temp-file-backup
  'gnuserv-make-temp-file-backup)

;;;###autoload
(defcustom gnuserv-frame nil
  "*The frame to be used to display all edited files.
If nil, then a new frame is created for each file edited.
If t, then the currently selected frame will be used.
If a function, then this will be called with a symbol `x' or `tty' as the
only argument, and its return value will be interpreted as above."
  :tag "Gnuserv Frame"
  :type '(radio (const :tag "Create new frame each time" nil)
		(const :tag "Use selected frame" t)
	        (function-item :tag "Use main Emacs frame"
			       gnuserv-main-frame-function)
		(function-item :tag "Use visible frame, otherwise create new"
			       gnuserv-visible-frame-function)
		(function-item :tag "Create special Gnuserv frame and use it"
			       gnuserv-special-frame-function)
		(function :tag "Other"))
  :group 'gnuserv
  :group 'frames)

(defcustom gnuserv-frame-plist nil
  "*Plist of frame properties for creating a gnuserv frame."
  :type 'plist
  :group 'gnuserv
  :group 'frames)

(defcustom gnuserv-done-function 'kill-buffer
  "*Function used to remove a buffer after editing.
It is called with one BUFFER argument.  Functions such as `kill-buffer' and
`bury-buffer' are good values. See also `gnuserv-done-temp-file-function'."
  :type '(radio (function-item kill-buffer)
		(function-item bury-buffer)
		(function :tag "Other"))
  :group 'gnuserv)

(defcustom gnuserv-done-temp-file-function 'kill-buffer
  "*Function used to remove a temporary buffer after editing.
It is called with one BUFFER argument.  Functions such as `kill-buffer' and
`bury-buffer' are good values. See also `gnuserv-done-temp-file-function'."
  :type '(radio (function-item kill-buffer)
		(function-item bury-buffer)
		(function :tag "Other"))
  :group 'gnuserv)

(defcustom gnuserv-find-file-function 'find-file
  "*Function to visit a file with.
It takes one argument, a file name to visit."
  :type 'function
  :group 'gnuserv)

(defcustom gnuserv-view-file-function 'view-file
  "*Function to view a file with.
It takes one argument, a file name to view."
  :type '(radio (function-item view-file)
		(function-item find-file-read-only)
		(function :tag "Other"))
  :group 'gnuserv)

(defcustom gnuserv-program "gnuserv"
  "*Program to use as the editing server."
  :type 'string
  :group 'gnuserv)

(defcustom gnuserv-visit-hook nil
  "*Hook run after visiting a file."
  :type 'hook
  :group 'gnuserv)

(defcustom gnuserv-done-hook nil
  "*Hook run when done editing a buffer for the Emacs server.
The hook functions are called after the file has been visited, with the
current buffer set to the visiting buffer."
  :type 'hook
  :group 'gnuserv)

(defcustom gnuserv-init-hook nil
  "*Hook run after the server is started."
  :type 'hook
  :group 'gnuserv)

(defcustom gnuserv-shutdown-hook nil
  "*Hook run before the server exits."
  :type 'hook
  :group 'gnuserv)

(defcustom gnuserv-kill-quietly nil
  "*Non-nil means to kill buffers with clients attached without requiring confirmation."
  :type 'boolean
  :group 'gnuserv)

(defcustom gnuserv-temp-file-regexp
  (concat "^" (temp-directory) "/Re\\|/draft$")
  "*Regexp which should match filenames of temporary files deleted
and reused by the programs that invoke the Emacs server."
  :type 'regexp
  :group 'gnuserv)

(defcustom gnuserv-make-temp-file-backup nil
  "*Non-nil makes the server backup temporary files also."
  :type 'boolean
  :group 'gnuserv)


;;; Internal variables:

(defstruct gnuclient
  "An object that encompasses several buffers in one.
Normally, a client connecting to Emacs will be assigned an id, and
will request editing of several files.

ID       - Client id (integer).
BUFFERS  - List of buffers that \"belong\" to the client.
           NOTE: one buffer can belong to several clients.
DEVICE   - The device this client is on.  If the device was also created.
           by a client, it will be placed to `gnuserv-devices' list.
FRAME    - Frame created by the client, or nil if the client didn't
           create a frame.

All the slots default to nil."
  (id nil)
  (buffers nil)
  (device nil)
  (frame nil))

(defvar gnuserv-process nil
  "The current gnuserv process.")

(defvar gnuserv-string ""
  "The last input string from the server.")

(defvar gnuserv-current-client nil
  "The client we are currently talking to.")

(defvar gnuserv-clients nil
  "List of current gnuserv clients.
Each element is a gnuclient structure that identifies a client.")

(defvar gnuserv-devices nil
  "List of devices created by clients.")

(defvar gnuserv-special-frame nil
  "Frame created specially for Server.")

;; We want the client-infested buffers to have some modeline
;; identification, so we'll make a "minor mode".
(defvar gnuserv-minor-mode nil)
(make-variable-buffer-local 'gnuserv-mode)
(pushnew '(gnuserv-minor-mode " Server") minor-mode-alist
	  :test 'equal)


;; Sample gnuserv-frame functions

(defun gnuserv-main-frame-function (type)
  "Return a sensible value for the main Emacs frame."
  (if (or (eq type 'x)
	  (eq type 'mswindows))
      (car (frame-list))
    nil))

(defun gnuserv-visible-frame-function (type)
  "Return a frame if there is a frame that is truly visible, nil otherwise.
This is meant in the X sense, so it will not return frames that are on another
visual screen.  Totally visible frames are preferred.  If none found, return nil."
  (if (or (eq type 'x)
	  (eq type 'mswindows))
      (cond ((car (filtered-frame-list 'frame-totally-visible-p
				       (selected-device))))
	    ((car (filtered-frame-list (lambda (frame)
					 ;; eq t as in not 'hidden
					 (eq t (frame-visible-p frame)))
				       (selected-device)))))
    nil))

(defun gnuserv-special-frame-function (type)
  "Create a special frame for Gnuserv and return it on later invocations."
  (unless (frame-live-p gnuserv-special-frame)
    (setq gnuserv-special-frame (make-frame gnuserv-frame-plist)))
  gnuserv-special-frame)


;;; Communication functions

;; We used to restart the server here, but it's too risky -- if
;; something goes awry, it's too easy to wind up in a loop.
(defun gnuserv-sentinel (proc msg)
  (let ((msgstring (concat "Gnuserv process %s; restart with `%s'"))
	(keystring (substitute-command-keys "\\[gnuserv-start]")))
  (case (process-status proc)
    (exit
     (message msgstring "exited" keystring)
     (gnuserv-prepare-shutdown))
    (signal
     (message msgstring "killed" keystring)
     (gnuserv-prepare-shutdown))
    (closed
     (message msgstring "closed" keystring))
     (gnuserv-prepare-shutdown))))

;; This function reads client requests from our current server.  Every
;; client is identified by a unique ID within the server
;; (incidentally, the same ID is the file descriptor the server uses
;; to communicate to client).
;;
;; The request string can arrive in several chunks.  As the request
;; ends with \C-d, we check for that character at the end of string.
;; If not found, keep reading, and concatenating to former strings.
;; So, if at first read we receive "5 (gn", that text will be stored
;; to gnuserv-string.  If we then receive "us)\C-d", the two will be
;; concatenated, `current-client' will be set to 5, and `(gnus)' form
;; will be evaluated.
;;
;; Server will send the following:
;;
;; "ID <text>\C-d"  (no quotes)
;;
;;  ID    - file descriptor of the given client;
;; <text> - the actual contents of the request.
(defun gnuserv-process-filter (proc string)
  "Process gnuserv client requests to execute Emacs commands."
  (setq gnuserv-string (concat gnuserv-string string))
  ;; C-d means end of request.
  (when (string-match "\C-d\\'" gnuserv-string)
    (cond ((string-match "^[0-9]+" gnuserv-string) ; client request id
	   (let ((header (read-from-string gnuserv-string)))
	     ;; Set the client we are talking to.
	     (setq gnuserv-current-client (car header))
	     ;; Evaluate the expression
	     (condition-case oops
		 (eval (car (read-from-string gnuserv-string (cdr header))))
	       ;; In case of an error, write the description to the
	       ;; client, and then signal it.
	       (error (setq gnuserv-string "")
		      (gnuserv-write-to-client gnuserv-current-client oops)
		      (setq gnuserv-current-client nil)
		      (signal (car oops) (cdr oops)))
	       (quit (setq gnuserv-string "")
		     (gnuserv-write-to-client gnuserv-current-client oops)
		     (setq gnuserv-current-client nil)
		     (signal 'quit nil)))
	     (setq gnuserv-string "")))
	  (t
	   (error "%s: invalid response from gnuserv" gnuserv-string)
	   (setq gnuserv-string "")))))

;; This function is somewhat of a misnomer.  Actually, we write to the
;; server (using `process-send-string' to gnuserv-process), which
;; interprets what we say and forwards it to the client.  The
;; incantation server understands is (from gnuserv.c):
;;
;; "FD/LEN:<text>\n"  (no quotes)
;;    FD     - file descriptor of the given client (which we obtained from
;;             the server earlier);
;;    LEN    - length of the stuff we are about to send;
;;    <text> - the actual contents of the request.
(defun gnuserv-write-to-client (client-id form)
  "Write the given form to the given client via the gnuserv process."
  (when (eq (process-status gnuserv-process) 'run)
    (let* ((result (format "%s" form))
	   (s      (format "%s/%d:%s\n" client-id
			   (length result) result)))
      (process-send-string gnuserv-process s))))

;; The following two functions are helper functions, used by
;; gnuclient.

(defun gnuserv-eval (form)
  "Evaluate form and return result to client."
  (gnuserv-write-to-client gnuserv-current-client (eval form))
  (setq gnuserv-current-client nil))

(defun gnuserv-eval-quickly (form)
  "Let client know that we've received the request, and then eval the form.
This order is important as not to keep the client waiting."
  (gnuserv-write-to-client gnuserv-current-client nil)
  (setq gnuserv-current-client nil)
  (eval form))


;; "Execute" a client connection, called by gnuclient.  This is the
;; backbone of gnuserv.el.
(defun gnuserv-edit-files (type list &rest flags)
  "For each (line-number . file) pair in LIST, edit the file at line-number.
The visited buffers are memorized, so that when \\[gnuserv-edit] is invoked
in such a buffer, or when it is killed, or the client's device deleted, the
client will be invoked that the edit is finished.

TYPE should either be a (tty TTY TERM PID) list, or (x DISPLAY) list.
If a flag is `quick', just edit the files in Emacs.
If a flag is `view', view the files read-only."
  (let (quick view)
    (mapc (lambda (flag)
	    (case flag
	      (quick (setq quick t))
	      (view  (setq view t))
	      (t     (error "Invalid flag %s" flag))))
	  flags)
    (let* ((old-device-num (length (device-list)))
	   (new-frame nil)
	   (dest-frame (if (functionp gnuserv-frame)
			   (funcall gnuserv-frame (car type))
			 gnuserv-frame))
	   ;; The gnuserv-frame dependencies are ugly, but it's
	   ;; extremely hard to make that stuff cleaner without
	   ;; breaking everything in sight.
	   (device (cond ((frame-live-p dest-frame)
			  (frame-device dest-frame))
			 ((null dest-frame)
			  (case (car type)
			    (tty (apply 'make-tty-device (cdr type)))
			    (x   (make-x-device (cadr type)))
			    (mswindows   (make-mswindows-device))
			    (t   (error "Invalid device type"))))
			 (t
			  (selected-device))))
	   (frame (cond ((frame-live-p dest-frame)
			 dest-frame)
			((null dest-frame)
			 (setq new-frame (make-frame gnuserv-frame-plist
						     device))
			 new-frame)
			(t (selected-frame))))
	   (client (make-gnuclient :id gnuserv-current-client
				   :device device
				   :frame new-frame)))
      (setq gnuserv-current-client nil)
      ;; If the device was created by this client, push it to the list.
      (and (/= old-device-num (length (device-list)))
	   (push device gnuserv-devices))
      (and (frame-iconified-p frame)
	   (deiconify-frame frame))
      ;; Visit all the listed files.
      (while list
	(let ((line (caar list)) (path (cdar list)))
	  (select-frame frame)
	  ;; Visit the file.
	  (funcall (if view
		       gnuserv-view-file-function
		     gnuserv-find-file-function)
		   path)
	  (goto-line line)
	  ;; Don't memorize the quick and view buffers.
	  (unless (or quick view)
	    (pushnew (current-buffer) (gnuclient-buffers client))
	    (setq gnuserv-minor-mode t)
	    ;; Add the "Done" button to the menubar, only in this buffer.
	    (if (and (featurep 'menubar) current-menubar)
	      (progn (set-buffer-menubar current-menubar)
	      (add-menu-button nil ["Done" gnuserv-edit]))
	      ))
	  (run-hooks 'gnuserv-visit-hook)
	  (pop list)))
      (cond
       ((and (or quick view)
	     (device-on-window-system-p device))
	;; Exit if on X device, and quick or view.  NOTE: if the
	;; client is to finish now, it must absolutely /not/ be
	;; included to the list of clients.  This way the client-ids
	;; should be unique.
	(gnuserv-write-to-client (gnuclient-id client) nil))
       (t
	;; Else, the client gets a vote.
	(push client gnuserv-clients)
	;; Explain buffer exit options.  If dest-frame is nil, the
	;; user can exit via `delete-frame'.  OTOH, if FLAGS are nil
	;; and there are some buffers, the user can exit via
	;; `gnuserv-edit'.
	(if (and (not (or quick view))
		 (gnuclient-buffers client))
	    (message "%s"
		     (substitute-command-keys
		      "Type `\\[gnuserv-edit]' to finish editing"))
	  (or dest-frame
	      (message "%s"
		       (substitute-command-keys
			"Type `\\[delete-frame]' to finish editing")))))))))


;;; Functions that hook into Emacs in various way to enable operation

;; Defined later.
(add-hook 'kill-emacs-hook 'gnuserv-kill-all-clients t)

;; A helper function; used by others.  Try avoiding it whenever
;; possible, because it is slow, and conses a list.  Use
;; `gnuserv-buffer-p' when appropriate, for instance.
(defun gnuserv-buffer-clients (buffer)
  "Return a list of clients to which BUFFER belongs."
  (let (res)
    (dolist (client gnuserv-clients)
      (when (memq buffer (gnuclient-buffers client))
	(push client res)))
    res))

;; Like `gnuserv-buffer-clients', but returns a boolean; doesn't
;; collect a list.
(defun gnuserv-buffer-p (buffer)
  (member* buffer gnuserv-clients
	   :test 'memq
	   :key 'gnuclient-buffers))

;; This function makes sure that a killed buffer is deleted off the
;; list for the particular client.
;;
;; This hooks into `kill-buffer-hook'.  It is *not* a replacement for
;; `kill-buffer' (thanks God).
(defun gnuserv-kill-buffer-function ()
  "Remove the buffer from the buffer lists of all the clients it belongs to.
Any client that remains \"empty\" after the removal is informed that the
editing has ended."
  (let* ((buf (current-buffer)))
    (dolist (client (gnuserv-buffer-clients buf))
      (callf2 delq buf (gnuclient-buffers client))
      ;; If no more buffers, kill the client.
      (when (null (gnuclient-buffers client))
	(gnuserv-kill-client client)))))

(add-hook 'kill-buffer-hook 'gnuserv-kill-buffer-function)

;; Ask for confirmation before killing a buffer that belongs to a
;; living client.
(defun gnuserv-kill-buffer-query-function ()
  (or gnuserv-kill-quietly
      (not (gnuserv-buffer-p (current-buffer)))
      (yes-or-no-p
       (format "Buffer %s belongs to gnuserv client(s); kill anyway? "
	       (current-buffer)))))

(add-hook 'kill-buffer-query-functions
	  'gnuserv-kill-buffer-query-function)

(defun gnuserv-kill-emacs-query-function ()
  (or gnuserv-kill-quietly
      (not (some 'gnuclient-buffers gnuserv-clients))
      (yes-or-no-p "Gnuserv buffers still have clients; exit anyway? ")))

(add-hook 'kill-emacs-query-functions
	  'gnuserv-kill-emacs-query-function)

;; If the device of a client is to be deleted, the client should die
;; as well.  This is why we hook into `delete-device-hook'.
(defun gnuserv-check-device (device)
  (when (memq device gnuserv-devices)
    (dolist (client gnuserv-clients)
      (when (eq device (gnuclient-device client))
	;; we must make sure that the server kill doesn't result in
	;; killing the device, because it would cause a device-dead
	;; error when `delete-device' tries to do the job later.
	(gnuserv-kill-client client t))))
  (callf2 delq device gnuserv-devices))

(add-hook 'delete-device-hook 'gnuserv-check-device)

(defun gnuserv-temp-file-p (buffer)
  "Return non-nil if BUFFER contains a file considered temporary.
These are files whose names suggest they are repeatedly
reused to pass information to another program.

The variable `gnuserv-temp-file-regexp' controls which filenames
are considered temporary."
  (and (buffer-file-name buffer)
       (string-match gnuserv-temp-file-regexp (buffer-file-name buffer))))

(defun gnuserv-kill-client (client &optional leave-frame)
  "Kill the gnuclient CLIENT.
This will do away with all the associated buffers.  If LEAVE-FRAME,
the function will not remove the frames associated with the client."
  ;; Order is important: first delete client from gnuserv-clients, to
  ;; prevent gnuserv-buffer-done-1 calling us recursively.
  (callf2 delq client gnuserv-clients)
  ;; Process the buffers.
  (mapc 'gnuserv-buffer-done-1 (gnuclient-buffers client))
  (unless leave-frame
    (let ((device (gnuclient-device client)))
      ;; kill frame created by this client (if any), unless
      ;; specifically requested otherwise.
      ;;
      ;; note: last frame on a device will not be deleted here.
    (when (and (gnuclient-frame client)
	       (frame-live-p (gnuclient-frame client))
	       (second (device-frame-list device)))
      (delete-frame (gnuclient-frame client)))
    ;; If the device is live, created by a client, and no longer used
    ;; by any client, delete it.
    (when (and (device-live-p device)
	       (memq device gnuserv-devices)
	       (second (device-list))
	       (not (member* device gnuserv-clients
			     :key 'gnuclient-device)))
      ;; `gnuserv-check-device' will remove it from `gnuserv-devices'.
      (delete-device device))))
  ;; Notify the client.
  (gnuserv-write-to-client (gnuclient-id client) nil))

;; Do away with the buffer.
(defun gnuserv-buffer-done-1 (buffer)
  (dolist (client (gnuserv-buffer-clients buffer))
    (callf2 delq buffer (gnuclient-buffers client))
    (when (null (gnuclient-buffers client))
      (gnuserv-kill-client client)))
  ;; Get rid of the buffer.
  (save-excursion
    (set-buffer buffer)
    (run-hooks 'gnuserv-done-hook)
    (setq gnuserv-minor-mode nil)
    ;; Delete the menu button.
    (if (and (featurep 'menubar) current-menubar)
      (delete-menu-item '("Done")))
    (funcall (if (gnuserv-temp-file-p buffer)
		 gnuserv-done-temp-file-function
	       gnuserv-done-function)
	     buffer)))


;;; Higher-level functions

;; Choose a `next' server buffer, according to several criteria, and
;; return it.  If none are found, return nil.
(defun gnuserv-next-buffer ()
  (let* ((frame (selected-frame))
	 (device (selected-device))
	 client)
    (cond
     ;; If we have a client belonging to this frame, return
     ;; the first buffer from it.
     ((setq client
	    (car (member* frame gnuserv-clients :key 'gnuclient-frame)))
      (car (gnuclient-buffers client)))
     ;; Else, look for a device.
     ((and
       (memq (selected-device) gnuserv-devices)
       (setq client
	     (car (member* device gnuserv-clients :key 'gnuclient-device))))
      (car (gnuclient-buffers client)))
     ;; Else, try to find any client with at least one buffer, and
     ;; return its first buffer.
     ((setq client
	    (car (member-if-not #'null gnuserv-clients
				:key 'gnuclient-buffers)))
      (car (gnuclient-buffers client)))
     ;; Oh, give up.
     (t nil))))

(defun gnuserv-buffer-done (buffer)
  "Mark BUFFER as \"done\" for its client(s).
Does the save/backup queries first, and calls `gnuserv-done-function'."
  ;; Check whether this is the real thing.
  (unless (gnuserv-buffer-p buffer)
    (error "%s does not belong to a gnuserv client" buffer))
  ;; Backup/ask query.
  (if (gnuserv-temp-file-p buffer)
      ;; For a temp file, save, and do NOT make a non-numeric backup
      ;; Why does server.el explicitly back up temporary files?
      (let ((version-control nil)
	    (buffer-backed-up (not gnuserv-make-temp-file-backup)))
	(save-buffer))
    (if (and (buffer-modified-p)
	     (y-or-n-p (concat "Save file " buffer-file-name "? ")))
	(save-buffer buffer)))
  (gnuserv-buffer-done-1 buffer))

;; Called by `gnuserv-start-1' to clean everything.  Hooked into
;; `kill-emacs-hook', too.
(defun gnuserv-kill-all-clients ()
  "Kill all the gnuserv clients.  Ruthlessly."
  (mapc 'gnuserv-kill-client gnuserv-clients))

;; This serves to run the hook and reset
;; `allow-deletion-of-last-visible-frame'.
(defun gnuserv-prepare-shutdown ()
  (setq allow-deletion-of-last-visible-frame nil)
  (run-hooks 'gnuserv-shutdown-hook))

;; This is a user-callable function, too.
(defun gnuserv-shutdown ()
  "Shutdown the gnuserv server, if one is currently running.
All the clients will be disposed of via the normal methods."
  (interactive)
  (gnuserv-kill-all-clients)
  (when gnuserv-process
    (set-process-sentinel gnuserv-process nil)
    (gnuserv-prepare-shutdown)
    (condition-case ()
	(delete-process gnuserv-process)
      (error nil))
    (setq gnuserv-process nil)))

;; Actually start the process.  Kills all the clients before-hand.
(defun gnuserv-start-1 (&optional leave-dead)
  ;; Shutdown the existing server, if any.
  (gnuserv-shutdown)
  ;; If we already had a server, clear out associated status.
  (unless leave-dead
    (setq gnuserv-string ""
	  gnuserv-current-client nil)
    (let ((process-connection-type t))
      (setq gnuserv-process
	    (start-process "gnuserv" nil gnuserv-program)))
    (set-process-sentinel gnuserv-process 'gnuserv-sentinel)
    (set-process-filter gnuserv-process 'gnuserv-process-filter)
    (process-kill-without-query gnuserv-process)
    (setq allow-deletion-of-last-visible-frame t)
    (run-hooks 'gnuserv-init-hook)))


;;; User-callable functions:

;;;###autoload
(defun gnuserv-running-p ()
  "Return non-nil if a gnuserv process is running from this XEmacs session."
  (not (not gnuserv-process)))

;;;###autoload
(defun gnuserv-start (&optional leave-dead)
  "Allow this Emacs process to be a server for client processes.
This starts a gnuserv communications subprocess through which
client \"editors\" (gnuclient and gnudoit) can send editing commands to
this Emacs job.  See the gnuserv(1) manual page for more details.

Prefix arg means just kill any existing server communications subprocess."
  (interactive "P")
  (and gnuserv-process
       (not leave-dead)
       (message "Restarting gnuserv"))
  (gnuserv-start-1 leave-dead))

(defun gnuserv-edit (&optional count)
  "Mark the current gnuserv editing buffer as \"done\", and switch to next one.

Run with a numeric prefix argument, repeat the operation that number
of times.  If given a universal prefix argument, close all the buffers
of this buffer's clients.

The `gnuserv-done-function' (bound to `kill-buffer' by default) is
called to dispose of the buffer after marking it as done.

Files that match `gnuserv-temp-file-regexp' are considered temporary and
are saved unconditionally and backed up if `gnuserv-make-temp-file-backup'
is non-nil.  They are disposed of using `gnuserv-done-temp-file-function'
\(also bound to `kill-buffer' by default).

When all of a client's buffers are marked as \"done\", the client is notified."
  (interactive "P")
  (when (null count)
    (setq count 1))
  (cond ((numberp count)
	 (while (natnump (decf count))
	   (let ((frame (selected-frame)))
	     (gnuserv-buffer-done (current-buffer))
	     (when (eq frame (selected-frame))
	       ;; Switch to the next gnuserv buffer.  However, do this
	       ;; only if we remain in the same frame.
	       (let ((next (gnuserv-next-buffer)))
		 (when next
		   (switch-to-buffer next)))))))
	(count
	   (let* ((buf (current-buffer))
		  (clients (gnuserv-buffer-clients buf)))
	     (unless clients
	       (error "%s does not belong to a gnuserv client" buf))
	     (mapc 'gnuserv-kill-client (gnuserv-buffer-clients buf))))))

(global-set-key "\C-x#" 'gnuserv-edit)

(provide 'gnuserv)

;;; gnuserv.el ends here
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.