1; Lisp Interface code between GNU Emacs and gnuserv.
3; This file is part of GNU Emacs.
5; Copying is permitted under those conditions described by the GNU
6; General Public License.
8; Copyright (C) 1989-1994 Free Software Foundation, Inc.
10; Author: Andy Norman (ange@hplb.hpl.hp.com) based on
11; 'lisp/server.el' from the 18.52 GNU Emacs distribution.
13; Please mail bugs and suggestions to the author at the above address.
15; Updated for XEmacs, GNU Emacs 19 and Epoch V4 to use multiple frames
16; by Bob Weiner, <weiner@mot.com>, 1/20/94. (Still works with Emacs V18, too.)
17; Modified 'server-process-filter' to use \^D as end of request terminator
18; as gnuclient and gnudoit have been modified to send. This permits
20; Modified 'server-make-window-visible' to work with multiple frames.
21; Modified 'server-find-file' to display in a separate frame when possible.
22; Modified 'server-edit' to delete newly created frame when used to
23; terminate an edit and to signal an error if called within a
24; non-server-edit buffer.
25; Bob Weiner, <weiner@mot.com>, 5/9/94.
26; Added 'server-done-function' variable. Made default value 'kill-buffer'
27; instead of 'bury-buffer' as in original gnuserv.el.
29; Darrell Kindred <dkindred+@cmu.edu> May/1994
30; Updated to allow multi-line return values:
31; - output to gnuserv is "m/n:xxx" where m is the client number,
32; n is the length of the data, and xxx is the data itself, followed
35; Arup Mukherjee <arup+@cmu.edu> May/1994
36; Updated for XEmacs 19.10, and others:
37; - use find-file-other-screen if present
38; - new variable gnuserv-frame can be set to a frame or screen which is
39; is used for all edited files.
40; - check to see if server.el is already loaded and complain if it is, since
41; gnuserv.el can't coexist with server.el
42; - rename server-start to gnuserv-start, although server-start remains as
43; an alias. This allows gnuserv-start to be autoloaded from gnuserv.el
44; - changed server-get-buffer to take into account that in newer emacsen,
45; get buffer returns nil on deleted buffers.
46; - only try to create/delete frames or screens if window-system is non-nil
47; (otherwise things don't work w/ emacs19 on a dumb terminal)
52(defconst gnuserv-rcs-header-id "$Header: gnuserv.el,v 2.1 95/02/16 12:00:16 arup alpha $")
55;; server.el and gnuserv.el can't coexist because of conflicting defvar's and
58(if (and (boundp 'server-buffer-clients)
59 (not (featurep 'gnuserv)))
60 (error "Can't run gnuserv because server.el appears to be loaded already"))
62(defvar gnuserv-frame nil
63 "*If non-nil, the frame to be used to display all edited files.
64If nil, then a new frame is created for each file edited.
65This variable has no effect in XEmacs versions older than 19.9.")
67(defvar server-done-function 'kill-buffer
68 "*A function of one argument, a buffer, which removes the buffer after editing.
69Functions such as 'kill-buffer' and 'bury-buffer' are good values.")
71(defvar server-program "gnuserv"
72 "*The program to use as the edit server")
74(defvar server-process nil
75 "The current server process")
77(defvar server-string ""
78 "The last input string from the server")
80(defvar current-client nil
81 "The client we are currently talking to")
83(defvar server-clients nil
84 "List of current server clients.
85Each element is (CLIENTID BUFFER...) where CLIENTID is an integer
86that can be given to the server process to identify a client.
87When a buffer is killed, it is removed from this list.")
89(defvar server-buffer-clients nil
90 "List of client ids for clients requesting editing of the current buffer.")
92(make-variable-buffer-local 'server-buffer-clients)
93(setq-default server-buffer-clients nil)
94(or (assq 'server-buffer-clients minor-mode-alist)
95 (setq minor-mode-alist (cons '(server-buffer-clients " Server")
98(defun server-log (string)
99 "If a *server* buffer exists, write STRING to it for logging purposes."
100 (if (get-buffer "*server*")
102 (set-buffer "*server*")
103 (goto-char (point-max))
105 (or (bolp) (newline)))))
108(defun server-sentinel (proc msg)
109 (cond ((eq (process-status proc) 'exit)
110 (server-log (message "Server subprocess exited")))
111 ((eq (process-status proc) 'signal)
112 (server-log (message "Server subprocess killed")))))
115(defun server-process-display-error (string)
116 "Whenever a gnuserv error is reported, display it in a pop-up window."
117 (let ((cur (selected-window))
119 (pop-to-buffer (get-buffer-create "*server*"))
120 (set-window-start (selected-window) (point))
122 (select-window cur)))
125(defun server-process-filter (proc string)
126 "Process client gnuserv requests to execute Emacs commands."
127 (setq server-string (concat server-string string))
128 (if (string-match "\^D$" server-string) ; requests end with ctrl-D
129 (if (string-match "^[0-9]+" server-string) ; client request id
131 (server-log server-string)
132 (let ((header (read-from-string server-string)))
133 (setq current-client (car header))
135 (eval (car (read-from-string server-string
137 (error (setq server-string "")
138 (server-write-to-client current-client oops)
139 (setq current-client nil)
140 (signal (car oops) (cdr oops)))
141 (quit (setq server-string "")
142 (server-write-to-client current-client oops)
143 (setq current-client nil)
145 (setq server-string "")))
146 (progn ;error string from server
147 (server-process-display-error server-string)
148 (setq server-string "")))))
151(defun server-release-outstanding-buffers ()
152 "Release all buffers that have clients waiting for them to be finished."
154 (while server-clients
155 (let ((buffer (nth 1 (car server-clients)))) ; for all buffers...
156 (server-buffer-done buffer)))) ; destructively modifies server-clients
159(defun gnuserv-start (&optional leave-dead)
160 "Allow this Emacs process to be a server for client processes.
161This starts a server communications subprocess through which
162client \"editors\" (gnuclient and gnudoit) can send editing commands to
163this Emacs job. See the gnuserv(1) manual page for more details.
165Prefix arg means just kill any existing server communications subprocess."
170 (server-release-outstanding-buffers)
171 (set-process-sentinel server-process nil)
173 (delete-process server-process)
175 ;; If we already had a server, clear out associated status.
179 (server-log (message "Restarting server")))
180 (setq server-string "")
181 (setq current-client nil)
182 (let ((process-connection-type t))
184 (start-process "server" nil server-program)))
185 (set-process-sentinel server-process 'server-sentinel)
186 (set-process-filter server-process 'server-process-filter)
187 (process-kill-without-query server-process)))
189;; make gnuserv-start an alias to server-start, for backward compatibility
190(fset 'server-start (function gnuserv-start))
193(defun server-write-to-client (client form)
194 "Write the given form to the given client via the server process."
196 (eq (process-status server-process) 'run))
197 (let* ((result (format "%s" form))
198 (s (format "%s/%d:%s\n" client (length result) result)))
199 (process-send-string server-process s)
202(defun server-eval (form)
203 "Evaluate form and return result to client."
204 (server-write-to-client current-client (eval form))
205 (setq current-client nil))
208(defun server-eval-quickly (form)
209 "Let client know that we've received the request, but eval the form
210afterwards in order to not keep the client waiting."
211 (server-write-to-client current-client nil)
212 (setq current-client nil)
216(defun server-make-window-visible ()
217 "Try to make this window even more visible."
218 (and (boundp 'window-system)
219 (boundp 'window-system-version)
220 (eq window-system 'x)
221 (eq window-system-version 11)
222 (cond ((fboundp 'raise-frame)
223 (raise-frame (selected-frame)))
224 ((fboundp 'deiconify-screen)
225 (deiconify-screen (selected-screen))
226 (raise-screen (selected-screen)))
227 ((fboundp 'mapraised-screen)
229 ((fboundp 'x-remap-window)
231 ;; give window chance to re-display text
232 (accept-process-output)))))
235(defun server-find-file (file)
237Switch to a buffer visiting file FILENAME,
238creating one if none already exists."
239 (let ((obuf (get-file-buffer file)))
240 (if (and obuf (set-buffer obuf))
241 (if (file-exists-p file)
242 (if (or (not (verify-visited-file-modtime obuf))
243 (buffer-modified-p obuf))
244 (revert-buffer t nil))
246 (concat "File no longer exists: "
248 ", write buffer to file? "))
250 (cond ((and window-system
251 gnuserv-frame (fboundp 'frame-live-p) ;; v19 & Xemacs 19.12+
252 (frame-live-p gnuserv-frame))
253 (select-frame gnuserv-frame)
257 gnuserv-frame (fboundp 'live-screen-p) ;; XEmacs 19.9+
258 (live-screen-p gnuserv-frame))
259 (select-screen gnuserv-frame)
263 (fboundp 'select-frame)) ;; v19 & XEmacs 19.12+
264 (select-frame (make-frame))
268 (fboundp 'select-screen) ;; XEmacs 19.10+
269 (fboundp 'make-screen))
270 (select-screen (make-screen))
273 ((and (eq window-system 'x) ;; XEmacs 19.9-
274 (fboundp 'select-screen)
275 (fboundp 'x-create-screen))
276 (select-screen (x-create-screen nil))
280 (fboundp 'create-screen)) ;; epoch
281 (if (screenp gnuserv-frame)
282 (progn (select-screen gnuserv-frame)
284 (select-screen (create-screen (find-file-noselect file)))))
286 (t (find-file file))))) ;; emacs18+
289(defun server-edit-files-quickly (list)
290 "For each (line-number . file) pair in LIST, edit the file at line-number.
291Unlike (server-edit-files), no information is saved about clients waiting on
292edits to this buffer."
293 (server-write-to-client current-client nil)
294 (setq current-client nil)
296 (let ((line (car (car list)))
297 (path (cdr (car list))))
298 (server-find-file path)
299 (server-make-window-visible)
301 (setq list (cdr list))))
304(defun server-edit-files (list)
305 "For each (line-number . file) pair in LIST, edit the file at line-number.
306Save enough information for (server-kill-buffer) to inform the client when
307the edit is finished."
309 (let ((line (car (car list)))
310 (path (cdr (car list))))
311 (server-find-file path)
312 (server-make-window-visible)
313 (let ((old-clients (assq current-client server-clients))
314 (buffer (current-buffer)))
316 (setq server-buffer-clients
317 (cons current-client server-buffer-clients))
318 (if old-clients ;client already waiting for buffers?
319 (nconc old-clients (list buffer)) ;yes -- append this one as well
320 (setq server-clients ;nope -- make a new record
321 (cons (list current-client buffer)
323 (setq list (cdr list)))
324 (message (substitute-command-keys
325 (if (and (boundp 'infodock-version) window-system)
326 "Type {\\[server-edit]} or select Frame/Delete to finish edit."
327 "When done with a buffer, type \\[server-edit]."))))
330(defun server-get-buffer (buffer)
331 "One arg, a BUFFER or a buffer name. Return the buffer object even if killed.
332Signal an error if there is no record of BUFFER."
335 (let ((buf (get-buffer buffer)))
340 (error "No buffer named %s" buffer)
341 (error "Invalid buffer argument")))
344(defun server-kill-buffer (buffer)
345 "Kill the BUFFER. The argument may be a buffer object or buffer name.
346NOTE: This function has been enhanced to allow for remote editing
349If the buffer is waited upon by one or more clients, and a client is
350not waiting for other buffers to be killed, then the client is told
351that the buffer has been killed."
352 (interactive "bKill buffer ")
353 (setq buffer (server-get-buffer buffer))
354 (if (buffer-name buffer)
357 (let ((old-clients server-clients))
358 (server-real-kill-buffer buffer) ;try to kill it
359 (if (buffer-name buffer) ;succeeded in killing?
362 (let ((client (car old-clients)))
364 (if (cdr client) ;pending buffers?
366 (server-write-to-client (car client) nil) ;nope, tell client
367 (setq server-clients (delq client server-clients))))
368 (setq old-clients (cdr old-clients))))))))
371(defun server-kill-all-local-variables ()
372 "Eliminate all the buffer-local variable values of the current buffer.
373This buffer will then see the default values of all variables.
374NOTE: This function has been modified to ignore the variable
375server-buffer-clients."
376 (let ((clients server-buffer-clients))
377 (server-real-kill-all-local-variables)
379 (setq server-buffer-clients clients))))
382(or (fboundp 'server-real-kill-buffer)
383 (fset 'server-real-kill-buffer (symbol-function 'kill-buffer)))
385(fset 'kill-buffer 'server-kill-buffer)
387(or (fboundp 'server-real-kill-all-local-variables)
388 (fset 'server-real-kill-all-local-variables
389 (symbol-function 'kill-all-local-variables)))
391(fset 'kill-all-local-variables 'server-kill-all-local-variables)
394(defun server-buffer-done (buffer)
395 "Mark BUFFER as \"done\" for its client(s).
396Buries the buffer, and returns another server buffer as a suggestion for the
398 (let ((next-buffer nil)
399 (old-clients server-clients))
401 (let ((client (car old-clients)))
403 (setq next-buffer (nth 1 (memq buffer client))))
405 ;; If client now has no pending buffers,
406 ;; tell it that it is done, and forget it entirely.
409 (server-write-to-client (car client) nil)
410 (setq server-clients (delq client server-clients))))
411 (setq old-clients (cdr old-clients)))
412 (if (buffer-name buffer)
415 (setq server-buffer-clients nil)))
416 (funcall server-done-function buffer)
420(defun mh-draft-p (buffer)
421 "Return non-nil if this BUFFER is an mh <draft> file. Since MH deletes
422draft *BEFORE* it is edited, the server treats them specially."
423 ;; This may not be appropriately robust for all cases.
424 (string= (buffer-name buffer) "draft"))
428 "Offer to save current buffer and mark it as \"done\" for clients.
429Also bury it, and return a suggested new current buffer."
430 (let ((buffer (current-buffer)))
431 (if server-buffer-clients
433 (if (mh-draft-p buffer)
435 (write-region (point-min) (point-max)
436 (concat buffer-file-name "~"))
437 (kill-buffer buffer))
438 (if (and (buffer-modified-p)
439 (y-or-n-p (concat "Save file " buffer-file-name "? ")))
440 (save-buffer buffer)))
441 (server-buffer-done buffer)))))
444(defun server-edit (&optional arg)
445 "Switch to next server editing buffer and mark current one as \"done\".
446If a server buffer is current, it is marked \"done\" and optionally saved.
447MH <draft> files are always saved and backed up, no questions asked.
448When all of a client's buffers are marked as \"done\", the client is notified.
450If invoked with a prefix argument, or if there is no server process running,
451starts server process and that is all. Invoked by \\[server-edit]."
455 (memq (process-status server-process) '(signal exit)))
457 (if server-buffer-clients
458 (progn (server-switch-buffer (server-done))
459 (cond ((or (not window-system)
461 (or (and (fboundp 'frame-live-p)
462 (frame-live-p gnuserv-frame))
463 (and (fboundp 'live-screen-p)
464 (live-screen-p gnuserv-frame))
465 (and (fboundp 'create-screen)
466 (screenp gnuserv-frame)))))
468 ((fboundp 'delete-frame)
469 (delete-frame (selected-frame) t))
470 ((fboundp 'delete-screen)
473 "(server-edit): Use only on buffers created by external programs.")
476(defun server-switch-buffer (next-buffer)
477 "Switch to NEXT-BUFFER if a live buffer, otherwise switch to another buffer
478with gnuserv clients. If no such buffer is available, simply choose another
481 (if (and (bufferp next-buffer)
482 (buffer-name next-buffer))
483 (switch-to-buffer next-buffer)
484 ;; If NEXT-BUFFER is a dead buffer,
485 ;; remove the server records for it
486 ;; and try the next surviving server buffer.
487 (server-switch-buffer
488 (server-buffer-done next-buffer)))
490 (server-switch-buffer (nth 1 (car server-clients)))
491 (switch-to-buffer (other-buffer)))))
493(global-set-key "\C-x#" 'server-edit)