comparison lisp/packages/gnuserv.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children ac2d302a0011
comparison
equal deleted inserted replaced
-1:000000000000 0:376386a54a3c
1 ; Lisp Interface code between GNU Emacs and gnuserv.
2 ;
3 ; This file is part of GNU Emacs.
4 ;
5 ; Copying is permitted under those conditions described by the GNU
6 ; General Public License.
7 ;
8 ; Copyright (C) 1989-1994 Free Software Foundation, Inc.
9 ;
10 ; Author: Andy Norman (ange@hplb.hpl.hp.com) based on
11 ; 'lisp/server.el' from the 18.52 GNU Emacs distribution.
12 ;
13 ; Please mail bugs and suggestions to the author at the above address.
14 ;
15 ;;; Synched up with: Not in FSF.
16
17 ; Updated for XEmacs, GNU Emacs 19 and Epoch V4 to use multiple frames
18 ; by Bob Weiner, <weiner@mot.com>, 1/20/94. (Still works with Emacs V18, too.)
19 ; Modified 'server-process-filter' to use \^D as end of request terminator
20 ; as gnuclient and gnudoit have been modified to send. This permits
21 ; multi-line requests.
22 ; Modified 'server-make-window-visible' to work with multiple frames.
23 ; Modified 'server-find-file' to display in a separate frame when possible.
24 ; Modified 'server-edit' to delete newly created frame when used to
25 ; terminate an edit and to signal an error if called within a
26 ; non-server-edit buffer.
27 ; Bob Weiner, <weiner@mot.com>, 5/9/94.
28 ; Added 'server-done-function' variable. Made default value 'kill-buffer'
29 ; instead of 'bury-buffer' as in original gnuserv.el.
30 ;
31 ; Darrell Kindred <dkindred+@cmu.edu> May/1994
32 ; Updated to allow multi-line return values:
33 ; - output to gnuserv is "m/n:xxx" where m is the client number,
34 ; n is the length of the data, and xxx is the data itself, followed
35 ; by newline
36 ;
37 ; Arup Mukherjee <arup+@cmu.edu> May/1994
38 ; Updated for XEmacs 19.10, and others:
39 ; - use find-file-other-screen if present
40 ; - new variable gnuserv-frame can be set to a frame or screen which is
41 ; is used for all edited files.
42 ; - check to see if server.el is already loaded and complain if it is, since
43 ; gnuserv.el can't coexist with server.el
44 ; - rename server-start to gnuserv-start, although server-start remains as
45 ; an alias. This allows gnuserv-start to be autoloaded from gnuserv.el
46 ; - changed server-get-buffer to take into account that in newer emacsen,
47 ; get buffer returns nil on deleted buffers.
48 ; - only try to create/delete frames or screens if window-system is non-nil
49 ; (otherwise things don't work w/ emacs19 on a dumb terminal)
50 ;
51 ; Ben Wing <wing@666.com> sometime in 1995
52 ; Updated to allow `gnuattach'-type connections to the existing TTY
53 ;
54 ; Ben Wing <wing@666.com> May/1996
55 ; patch to get TTY terminal type correct.
56
57
58
59 (defconst gnuserv-rcs-header-id "!Header: gnuserv.el,v 2.1 95/02/16 12:00:16 arup alpha !")
60
61
62 ;; server.el and gnuserv.el can't coexist because of conflicting defvar's and
63 ;; function names.
64
65 (if (and (boundp 'server-buffer-clients)
66 (not (featurep 'gnuserv)))
67 (error "Can't run gnuserv because server.el appears to be loaded already"))
68
69 (defvar gnuserv-frame nil
70 "*If non-nil, the frame to be used to display all edited files.
71 If nil, then a new frame is created for each file edited.
72 This variable has no effect in XEmacs versions older than 19.9.")
73
74 (defvar server-done-function 'kill-buffer
75 "*A function of one argument, a buffer, which removes the buffer after editing.
76 Functions such as 'kill-buffer' and 'bury-buffer' are good values.")
77
78 (defvar server-program "gnuserv"
79 "*The program to use as the edit server")
80
81 (defvar server-process nil
82 "The current server process")
83
84 (defvar server-string ""
85 "The last input string from the server")
86
87 (defvar current-client nil
88 "The client we are currently talking to")
89
90 (defvar server-clients nil
91 "List of current server clients.
92 Each element is (CLIENTID BUFFER...) where CLIENTID is an integer
93 that can be given to the server process to identify a client.
94 When a buffer is killed, it is removed from this list.")
95
96 (defvar server-buffer-clients nil
97 "List of client ids for clients requesting editing of the current buffer.")
98
99 (make-variable-buffer-local 'server-buffer-clients)
100 (setq-default server-buffer-clients nil)
101 (or (assq 'server-buffer-clients minor-mode-alist)
102 (setq minor-mode-alist (cons '(server-buffer-clients " Server")
103 minor-mode-alist)))
104
105 (defun server-log (string)
106 "If a *server* buffer exists, write STRING to it for logging purposes."
107 (if (get-buffer "*server*")
108 (save-excursion
109 (set-buffer "*server*")
110 (goto-char (point-max))
111 (insert string)
112 (or (bolp) (newline)))))
113
114
115 (defun server-sentinel (proc msg)
116 (cond ((eq (process-status proc) 'exit)
117 (server-log (message "Server subprocess exited")))
118 ((eq (process-status proc) 'signal)
119 (server-log (message "Server subprocess killed")))))
120
121
122 (defun server-process-display-error (string)
123 "Whenever a gnuserv error is reported, display it in a pop-up window."
124 (let ((cur (selected-window))
125 (pop-up-windows t))
126 (pop-to-buffer (get-buffer-create "*server*"))
127 (set-window-start (selected-window) (point))
128 (server-log string)
129 (select-window cur)))
130
131
132 (defun server-process-filter (proc string)
133 "Process client gnuserv requests to execute Emacs commands."
134 (setq server-string (concat server-string string))
135 (if (string-match "\^D$" server-string) ; requests end with ctrl-D
136 (if (string-match "^[0-9]+" server-string) ; client request id
137 (progn
138 (server-log server-string)
139 (let ((header (read-from-string server-string)))
140 (setq current-client (car header))
141 (condition-case oops
142 (eval (car (read-from-string server-string
143 (cdr header))))
144 (error (setq server-string "")
145 (server-write-to-client current-client oops)
146 (setq current-client nil)
147 (signal (car oops) (cdr oops)))
148 (quit (setq server-string "")
149 (server-write-to-client current-client oops)
150 (setq current-client nil)
151 (signal 'quit nil)))
152 (setq server-string "")))
153 (progn ;error string from server
154 (server-process-display-error server-string)
155 (setq server-string "")))))
156
157
158 (defun server-release-outstanding-buffers ()
159 "Release all buffers that have clients waiting for them to be finished."
160 (interactive)
161 (while server-clients
162 (let ((buffer (nth 1 (car server-clients)))) ; for all buffers...
163 (server-buffer-done buffer)))) ; destructively modifies server-clients
164
165 ;;;###autoload
166 (defun gnuserv-start (&optional leave-dead)
167 "Allow this Emacs process to be a server for client processes.
168 This starts a server communications subprocess through which
169 client \"editors\" (gnuclient and gnudoit) can send editing commands to
170 this Emacs job. See the gnuserv(1) manual page for more details.
171
172 Prefix arg means just kill any existing server communications subprocess."
173 (interactive "P")
174 ;; kill it dead!
175 (if server-process
176 (progn
177 (server-release-outstanding-buffers)
178 (set-process-sentinel server-process nil)
179 (condition-case ()
180 (delete-process server-process)
181 (error nil))))
182 ;; If we already had a server, clear out associated status.
183 (if leave-dead
184 nil
185 (if server-process
186 (server-log (message "Restarting server")))
187 (setq server-string "")
188 (setq current-client nil)
189 (let ((process-connection-type t))
190 (setq server-process
191 (start-process "server" nil server-program)))
192 (set-process-sentinel server-process 'server-sentinel)
193 (set-process-filter server-process 'server-process-filter)
194 (process-kill-without-query server-process)))
195
196 ;; make gnuserv-start an alias to server-start, for backward compatibility
197 (fset 'server-start (function gnuserv-start))
198
199
200 (defun server-write-to-client (client form)
201 "Write the given form to the given client via the server process."
202 (if (and client
203 (eq (process-status server-process) 'run))
204 (let* ((result (format "%s" form))
205 (s (format "%s/%d:%s\n" client (length result) result)))
206 (process-send-string server-process s)
207 (server-log s))))
208
209 (defun server-eval (form)
210 "Evaluate form and return result to client."
211 (server-write-to-client current-client (eval form))
212 (setq current-client nil))
213
214
215 (defun server-eval-quickly (form)
216 "Let client know that we've received the request, but eval the form
217 afterwards in order to not keep the client waiting."
218 (server-write-to-client current-client nil)
219 (setq current-client nil)
220 (eval form))
221
222
223 (defun server-make-window-visible ()
224 "Try to make this window even more visible."
225 (and (boundp 'window-system)
226 (boundp 'window-system-version)
227 (eq window-system 'x)
228 (eq window-system-version 11)
229 (cond ((fboundp 'raise-frame)
230 (raise-frame (selected-frame)))
231 ((fboundp 'deiconify-screen)
232 (deiconify-screen (selected-screen))
233 (raise-screen (selected-screen)))
234 ((fboundp 'mapraised-screen)
235 (mapraised-screen))
236 ((fboundp 'x-remap-window)
237 (x-remap-window)
238 ;; give window chance to re-display text
239 (accept-process-output)))))
240
241 (defun server-tty-find-file (tty termtype file)
242 (let ((device (make-tty-device tty termtype)))
243 (select-frame (make-frame nil device))
244 (if (not file)
245 (switch-to-buffer (get-buffer-create "*scratch*"))
246 (find-file file))))
247
248 (defun server-find-file (file)
249 "Edit file FILENAME.
250 Switch to a buffer visiting file FILENAME,
251 creating one if none already exists."
252 (let ((obuf (get-file-buffer file))
253 ;; XEmacs addition.
254 (force-dialog-box-use t))
255 (if (and obuf (set-buffer obuf))
256 (if (file-exists-p file)
257 (if (or (not (verify-visited-file-modtime obuf))
258 (buffer-modified-p obuf))
259 (revert-buffer t nil))
260 (if (y-or-n-p
261 (concat "File no longer exists: "
262 file
263 ", write buffer to file? "))
264 (write-file file))))
265 (cond ((and window-system
266 gnuserv-frame (fboundp 'frame-live-p) ;; v19 & XEmacs 19.12+
267 (frame-live-p gnuserv-frame))
268 (select-frame gnuserv-frame)
269 (find-file file))
270
271 ((and window-system
272 gnuserv-frame (fboundp 'live-screen-p) ;; XEmacs 19.9+
273 (live-screen-p gnuserv-frame))
274 (select-screen gnuserv-frame)
275 (find-file file))
276
277 ((and window-system
278 (fboundp 'select-frame)) ;; v19 & XEmacs 19.12+
279 (select-frame (make-frame))
280 (find-file file))
281
282 ((and window-system
283 (fboundp 'select-screen) ;; XEmacs 19.10+
284 (fboundp 'make-screen))
285 (select-screen (make-screen))
286 (find-file file))
287
288 ((and (eq window-system 'x) ;; XEmacs 19.9-
289 (fboundp 'select-screen)
290 (fboundp 'x-create-screen))
291 (select-screen (x-create-screen nil))
292 (find-file file))
293
294 ((and window-system
295 (fboundp 'create-screen)) ;; epoch
296 (if (screenp gnuserv-frame)
297 (progn (select-screen gnuserv-frame)
298 (find-file file))
299 (select-screen (create-screen (find-file-noselect file)))))
300
301 (t (find-file file))))) ;; emacs18+
302
303
304 (defun server-edit-files-quickly (list)
305 "For each (line-number . file) pair in LIST, edit the file at line-number.
306 Unlike (server-edit-files), no information is saved about clients waiting on
307 edits to this buffer."
308 (server-write-to-client current-client nil)
309 (setq current-client nil)
310 (while list
311 (let ((line (car (car list)))
312 (path (cdr (car list))))
313 (server-find-file path)
314 (server-make-window-visible)
315 (goto-line line))
316 (setq list (cdr list))))
317
318
319 (defun server-edit-files (list)
320 "For each (line-number . file) pair in LIST, edit the file at line-number.
321 Save enough information for (server-kill-buffer) to inform the client when
322 the edit is finished."
323 (while list
324 (let ((line (car (car list)))
325 (path (cdr (car list))))
326 (server-find-file path)
327 (server-make-window-visible)
328 (let ((old-clients (assq current-client server-clients))
329 (buffer (current-buffer)))
330 (goto-line line)
331 (setq server-buffer-clients
332 (cons current-client server-buffer-clients))
333 (if old-clients ;client already waiting for buffers?
334 (nconc old-clients (list buffer)) ;yes -- append this one as well
335 (setq server-clients ;nope -- make a new record
336 (cons (list current-client buffer)
337 server-clients)))))
338 (setq list (cdr list)))
339 (message (substitute-command-keys
340 (if (and (boundp 'infodock-version) window-system)
341 "Type {\\[server-edit]} or select Frame/Delete to finish edit."
342 "When done with a buffer, type \\[server-edit]."))))
343
344 (defun server-tty-edit-files (tty termtype list)
345 "For each (line-number . file) pair in LIST, edit the file at line-number.
346 Save enough information for (server-kill-buffer) to inform the client when
347 the edit is finished."
348 (or list (setq list '((1 . nil))))
349 (while list
350 (let ((line (car (car list)))
351 (path (cdr (car list))))
352 (server-tty-find-file tty termtype path)
353 (server-make-window-visible)
354 (let ((old-clients (assq current-client server-clients))
355 (buffer (current-buffer)))
356 (goto-line line)
357 (setq server-buffer-clients
358 (cons current-client server-buffer-clients))
359 (if old-clients ;client already waiting for buffers?
360 (nconc old-clients (list buffer)) ;yes -- append this one as well
361 (setq server-clients ;nope -- make a new record
362 (cons (list current-client buffer)
363 server-clients)))))
364 (setq list (cdr list)))
365 (message (substitute-command-keys
366 (if (and (boundp 'infodock-version) window-system)
367 "Type {\\[server-edit]} or select Frame/Delete to finish edit."
368 "When done with a buffer, type \\[server-edit]."))))
369
370 (defun server-get-buffer (buffer)
371 "One arg, a BUFFER or a buffer name. Return the buffer object even if killed.
372 Signal an error if there is no record of BUFFER."
373 (if (null buffer)
374 (current-buffer)
375 (let ((buf (get-buffer buffer)))
376 (if (null buf)
377 (if (bufferp buffer)
378 buffer
379 (if (stringp buffer)
380 (error "No buffer named %s" buffer)
381 (error "Invalid buffer argument")))
382 buf))))
383
384 (defun server-kill-buffer (buffer)
385 "Kill the BUFFER. The argument may be a buffer object or buffer name.
386 NOTE: This function has been enhanced to allow for remote editing
387 in the following way:
388
389 If the buffer is waited upon by one or more clients, and a client is
390 not waiting for other buffers to be killed, then the client is told
391 that the buffer has been killed."
392 (interactive "bKill buffer ")
393 (setq buffer (server-get-buffer buffer))
394 (if (buffer-name buffer)
395 (save-excursion
396 (set-buffer buffer)
397 (let ((old-clients server-clients))
398 (server-real-kill-buffer buffer) ;try to kill it
399 (if (buffer-name buffer) ;succeeded in killing?
400 nil ;nope
401 (while old-clients
402 (let ((client (car old-clients)))
403 (delq buffer client)
404 (if (cdr client) ;pending buffers?
405 nil ;yep
406 (server-write-to-client (car client) nil) ;nope, tell client
407 (setq server-clients (delq client server-clients))))
408 (setq old-clients (cdr old-clients))))))))
409
410
411 (defun server-kill-all-local-variables ()
412 "Eliminate all the buffer-local variable values of the current buffer.
413 This buffer will then see the default values of all variables.
414 NOTE: This function has been modified to ignore the variable
415 server-buffer-clients."
416 (let ((clients server-buffer-clients))
417 (server-real-kill-all-local-variables)
418 (if clients
419 (setq server-buffer-clients clients))))
420
421
422 (or (fboundp 'server-real-kill-buffer)
423 (fset 'server-real-kill-buffer (symbol-function 'kill-buffer)))
424
425 (fset 'kill-buffer 'server-kill-buffer)
426
427 (or (fboundp 'server-real-kill-all-local-variables)
428 (fset 'server-real-kill-all-local-variables
429 (symbol-function 'kill-all-local-variables)))
430
431 (fset 'kill-all-local-variables 'server-kill-all-local-variables)
432
433
434 (defun server-buffer-done (buffer)
435 "Mark BUFFER as \"done\" for its client(s).
436 Buries the buffer, and returns another server buffer as a suggestion for the
437 new current buffer."
438 (let ((next-buffer nil)
439 (old-clients server-clients))
440 (while old-clients
441 (let ((client (car old-clients)))
442 (or next-buffer
443 (setq next-buffer (nth 1 (memq buffer client))))
444 (delq buffer client)
445 ;; If client now has no pending buffers,
446 ;; tell it that it is done, and forget it entirely.
447 (if (cdr client)
448 nil
449 (server-write-to-client (car client) nil)
450 (setq server-clients (delq client server-clients))))
451 (setq old-clients (cdr old-clients)))
452 (if (buffer-name buffer)
453 (save-excursion
454 (set-buffer buffer)
455 (setq server-buffer-clients nil)))
456 (funcall server-done-function buffer)
457 next-buffer))
458
459
460 (defun mh-draft-p (buffer)
461 "Return non-nil if this BUFFER is an mh <draft> file. Since MH deletes
462 draft *BEFORE* it is edited, the server treats them specially."
463 ;; This may not be appropriately robust for all cases.
464 (string= (buffer-name buffer) "draft"))
465
466
467 (defun server-done ()
468 "Offer to save current buffer and mark it as \"done\" for clients.
469 Also bury it, and return a suggested new current buffer."
470 (let ((buffer (current-buffer)))
471 (if server-buffer-clients
472 (progn
473 (if (mh-draft-p buffer)
474 (progn (save-buffer)
475 (write-region (point-min) (point-max)
476 (concat buffer-file-name "~"))
477 (kill-buffer buffer))
478 (if (and (buffer-modified-p)
479 (y-or-n-p (concat "Save file " buffer-file-name "? ")))
480 (save-buffer buffer)))
481 (server-buffer-done buffer)))))
482
483
484 (defun server-edit (&optional arg)
485 "Switch to next server editing buffer and mark current one as \"done\".
486 If a server buffer is current, it is marked \"done\" and optionally saved.
487 MH <draft> files are always saved and backed up, no questions asked.
488 When all of a client's buffers are marked as \"done\", the client is notified.
489
490 If invoked with a prefix argument, or if there is no server process running,
491 starts server process and that is all. Invoked by \\[server-edit]."
492 (interactive "P")
493 (if (or arg
494 (not server-process)
495 (memq (process-status server-process) '(signal exit)))
496 (server-start nil)
497 (if server-buffer-clients
498 (progn (server-switch-buffer (server-done))
499 (cond ((or ;(not window-system) #### someone examine!
500 (and gnuserv-frame
501 (or (and (fboundp 'frame-live-p)
502 (frame-live-p gnuserv-frame))
503 (and (fboundp 'live-screen-p)
504 (live-screen-p gnuserv-frame))
505 (and (fboundp 'create-screen)
506 (screenp gnuserv-frame)))))
507 ()) ;; do nothing
508 ((fboundp 'delete-frame)
509 (delete-frame (selected-frame) t))
510 ((fboundp 'delete-screen)
511 (delete-screen))))
512 (error
513 "(server-edit): Use only on buffers created by external programs.")
514 )))
515
516 (defun server-switch-buffer (next-buffer)
517 "Switch to NEXT-BUFFER if a live buffer, otherwise switch to another buffer
518 with gnuserv clients. If no such buffer is available, simply choose another
519 one."
520 (if next-buffer
521 (if (and (bufferp next-buffer)
522 (buffer-name next-buffer))
523 (switch-to-buffer next-buffer)
524 ;; If NEXT-BUFFER is a dead buffer,
525 ;; remove the server records for it
526 ;; and try the next surviving server buffer.
527 (server-switch-buffer
528 (server-buffer-done next-buffer)))
529 (if server-clients
530 (server-switch-buffer (nth 1 (car server-clients)))
531 (switch-to-buffer (other-buffer)))))
532
533 (global-set-key "\C-x#" 'server-edit)
534
535 (provide 'gnuserv)
536