diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/packages/gnuserv.el	Mon Aug 13 08:45:50 2007 +0200
@@ -0,0 +1,536 @@
+; Lisp Interface code between GNU Emacs and gnuserv.
+;
+; This file is part of GNU Emacs.
+;
+; Copying is permitted under those conditions described by the GNU
+; General Public License.
+;
+; Copyright (C) 1989-1994  Free Software Foundation, Inc.
+;
+; Author: Andy Norman (ange@hplb.hpl.hp.com) based on
+;         'lisp/server.el' from the 18.52 GNU Emacs distribution.
+;
+; Please mail bugs and suggestions to the author at the above address.
+;
+;;; Synched up with: Not in FSF.
+
+; Updated for XEmacs, GNU Emacs 19 and Epoch V4 to use multiple frames
+; by Bob Weiner, <weiner@mot.com>, 1/20/94.  (Still works with Emacs V18, too.)
+;    Modified 'server-process-filter' to use \^D as end of request terminator
+;      as gnuclient and gnudoit have been modified to send.  This permits
+;      multi-line requests.
+;    Modified 'server-make-window-visible' to work with multiple frames.
+;    Modified 'server-find-file' to display in a separate frame when possible.
+;    Modified 'server-edit' to delete newly created frame when used to
+;      terminate an edit and to signal an error if called within a
+;      non-server-edit buffer.
+; Bob Weiner, <weiner@mot.com>, 5/9/94.
+;    Added 'server-done-function' variable.  Made default value 'kill-buffer'
+;    instead of 'bury-buffer' as in original gnuserv.el.
+;
+; Darrell Kindred <dkindred+@cmu.edu> May/1994
+; Updated to allow multi-line return values:
+;    - output to gnuserv is "m/n:xxx" where m is the client number,
+;      n is the length of the data, and xxx is the data itself, followed 
+;      by newline
+;
+; Arup Mukherjee <arup+@cmu.edu> May/1994
+; Updated for XEmacs 19.10, and others:
+;    - use find-file-other-screen if present
+;    - new variable gnuserv-frame can be set to a frame or screen which is 
+;      is used for all edited files. 
+;    - check to see if server.el is already loaded and complain if it is, since
+;      gnuserv.el can't coexist with server.el
+;    - rename server-start to gnuserv-start, although server-start remains as
+;      an alias. This allows gnuserv-start to be autoloaded from gnuserv.el
+;    - changed server-get-buffer to take into account that in newer emacsen,
+;      get buffer returns nil on deleted buffers.
+;    - only try to create/delete frames or screens if window-system is non-nil 
+;      (otherwise things don't work w/ emacs19 on a dumb terminal)
+;
+; Ben Wing <wing@666.com> sometime in 1995
+; Updated to allow `gnuattach'-type connections to the existing TTY
+;
+; Ben Wing <wing@666.com> May/1996
+; patch to get TTY terminal type correct.
+
+
+
+(defconst gnuserv-rcs-header-id "!Header: gnuserv.el,v 2.1 95/02/16 12:00:16 arup alpha !")
+
+
+;; server.el and gnuserv.el can't coexist because of conflicting defvar's and
+;; function names. 
+
+(if (and (boundp 'server-buffer-clients)
+	 (not (featurep 'gnuserv)))
+    (error "Can't run gnuserv because server.el appears to be loaded already"))
+
+(defvar gnuserv-frame nil
+  "*If non-nil, the frame to be used to display all edited files.
+If nil, then a new frame is created for each file edited.
+This variable has no effect in XEmacs versions older than 19.9.")
+
+(defvar server-done-function 'kill-buffer 
+  "*A function of one argument, a buffer, which removes the buffer after editing.
+Functions such as 'kill-buffer' and 'bury-buffer' are good values.")
+
+(defvar server-program "gnuserv"
+  "*The program to use as the edit server")
+
+(defvar server-process nil 
+  "The current server process")
+
+(defvar server-string ""
+  "The last input string from the server")
+
+(defvar current-client nil
+  "The client we are currently talking to")
+
+(defvar server-clients nil
+  "List of current server clients.
+Each element is (CLIENTID BUFFER...) where CLIENTID is an integer
+that can be given to the server process to identify a client.
+When a buffer is killed, it is removed from this list.")
+
+(defvar server-buffer-clients nil
+  "List of client ids for clients requesting editing of the current buffer.")
+
+(make-variable-buffer-local 'server-buffer-clients)
+(setq-default server-buffer-clients nil)
+(or (assq 'server-buffer-clients minor-mode-alist)
+    (setq minor-mode-alist (cons '(server-buffer-clients " Server") 
+				 minor-mode-alist)))
+
+(defun server-log (string)
+  "If a *server* buffer exists, write STRING to it for logging purposes."
+  (if (get-buffer "*server*")
+      (save-excursion
+	(set-buffer "*server*")
+	(goto-char (point-max))
+	(insert string)
+	(or (bolp) (newline)))))
+
+
+(defun server-sentinel (proc msg)
+  (cond ((eq (process-status proc) 'exit)
+	 (server-log (message "Server subprocess exited")))
+	((eq (process-status proc) 'signal)
+	 (server-log (message "Server subprocess killed")))))
+
+
+(defun server-process-display-error (string)
+  "Whenever a gnuserv error is reported, display it in a pop-up window."
+  (let ((cur (selected-window))
+	(pop-up-windows t))
+    (pop-to-buffer (get-buffer-create "*server*"))
+    (set-window-start (selected-window) (point))
+    (server-log string)
+    (select-window cur)))
+
+
+(defun server-process-filter (proc string)
+  "Process client gnuserv requests to execute Emacs commands."
+  (setq server-string (concat server-string string))
+  (if (string-match "\^D$" server-string) ; requests end with ctrl-D
+      (if (string-match "^[0-9]+" server-string) ; client request id
+	(progn
+	  (server-log server-string)
+	  (let ((header (read-from-string server-string)))
+	    (setq current-client (car header))
+	    (condition-case oops
+		(eval (car (read-from-string server-string 
+					     (cdr header))))
+	      (error (setq server-string "")
+		     (server-write-to-client current-client oops)
+		     (setq current-client nil)
+		     (signal (car oops) (cdr oops)))
+	      (quit (setq server-string "")
+		    (server-write-to-client current-client oops)
+		    (setq current-client nil)
+		    (signal 'quit nil)))
+	    (setq server-string "")))
+	(progn				;error string from server
+	  (server-process-display-error server-string)
+	  (setq server-string "")))))
+
+
+(defun server-release-outstanding-buffers ()
+  "Release all buffers that have clients waiting for them to be finished."
+  (interactive)
+  (while server-clients
+    (let ((buffer (nth 1 (car server-clients)))) ; for all buffers...
+      (server-buffer-done buffer)))) ; destructively modifies server-clients
+
+;;;###autoload
+(defun gnuserv-start (&optional leave-dead)
+  "Allow this Emacs process to be a server for client processes.
+This starts a server 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")
+  ;; kill it dead!
+  (if server-process
+      (progn
+	(server-release-outstanding-buffers)
+	(set-process-sentinel server-process nil)
+	(condition-case ()
+	    (delete-process server-process)
+	  (error nil))))
+  ;; If we already had a server, clear out associated status.
+  (if leave-dead
+      nil
+    (if server-process
+	(server-log (message "Restarting server")))
+    (setq server-string "")
+    (setq current-client nil)
+    (let ((process-connection-type t))
+      (setq server-process 
+	    (start-process "server" nil server-program)))
+    (set-process-sentinel server-process 'server-sentinel)
+    (set-process-filter server-process 'server-process-filter)
+    (process-kill-without-query server-process)))
+
+;; make gnuserv-start an alias to server-start, for backward compatibility
+(fset 'server-start (function gnuserv-start))
+
+
+(defun server-write-to-client (client form)
+  "Write the given form to the given client via the server process."
+  (if (and client
+	   (eq (process-status server-process) 'run))
+      (let* ((result (format "%s" form))
+	     (s      (format "%s/%d:%s\n" client (length result) result)))
+	(process-send-string server-process s)
+	(server-log s))))
+
+(defun server-eval (form)
+  "Evaluate form and return result to client."
+  (server-write-to-client current-client (eval form))
+  (setq current-client nil))
+
+
+(defun server-eval-quickly (form)
+  "Let client know that we've received the request, but eval the form
+afterwards in order to not keep the client waiting."
+  (server-write-to-client current-client nil)
+  (setq current-client nil)
+  (eval form))
+
+
+(defun server-make-window-visible ()
+  "Try to make this window even more visible."
+  (and (boundp 'window-system)
+       (boundp 'window-system-version)
+       (eq window-system 'x)
+       (eq window-system-version 11)
+       (cond ((fboundp 'raise-frame)
+	      (raise-frame (selected-frame)))
+	     ((fboundp 'deiconify-screen)
+	      (deiconify-screen (selected-screen))
+	      (raise-screen (selected-screen)))
+	     ((fboundp 'mapraised-screen)
+	      (mapraised-screen))
+	     ((fboundp 'x-remap-window)
+	      (x-remap-window)
+	      ;; give window chance to re-display text
+	      (accept-process-output)))))
+
+(defun server-tty-find-file (tty termtype file)
+  (let ((device (make-tty-device tty termtype)))
+    (select-frame (make-frame nil device))
+    (if (not file)
+	(switch-to-buffer (get-buffer-create "*scratch*"))
+      (find-file file))))
+
+(defun server-find-file (file)
+  "Edit file FILENAME.
+Switch to a buffer visiting file FILENAME,
+creating one if none already exists."
+  (let ((obuf (get-file-buffer file))
+	;; XEmacs addition.
+	(force-dialog-box-use t))
+    (if (and obuf (set-buffer obuf))
+	(if (file-exists-p file)
+	    (if (or (not (verify-visited-file-modtime obuf))
+		    (buffer-modified-p obuf))
+		(revert-buffer t nil))
+	  (if (y-or-n-p
+	       (concat "File no longer exists: "
+		       file
+		       ", write buffer to file? "))
+	      (write-file file))))
+    (cond ((and window-system
+		gnuserv-frame (fboundp 'frame-live-p)    ;; v19 & XEmacs 19.12+
+		(frame-live-p gnuserv-frame))
+	   (select-frame gnuserv-frame)
+	   (find-file file))
+
+	  ((and window-system
+		gnuserv-frame (fboundp 'live-screen-p)   ;; XEmacs 19.9+
+		(live-screen-p gnuserv-frame))
+	   (select-screen gnuserv-frame)          
+	   (find-file file))
+	  
+	  ((and window-system
+		(fboundp 'select-frame))                 ;; v19 & XEmacs 19.12+
+	   (select-frame (make-frame))
+	   (find-file file))
+
+	  ((and window-system
+		(fboundp 'select-screen)                 ;; XEmacs 19.10+
+		(fboundp 'make-screen))
+	   (select-screen (make-screen))
+	   (find-file file))
+	  
+	  ((and (eq window-system 'x)                    ;; XEmacs 19.9-
+		(fboundp 'select-screen)
+		(fboundp 'x-create-screen))
+	   (select-screen (x-create-screen nil))
+	   (find-file file))
+
+	  ((and window-system
+		(fboundp 'create-screen))                ;; epoch
+	   (if (screenp gnuserv-frame)
+	       (progn (select-screen gnuserv-frame)
+		      (find-file file))
+	     (select-screen (create-screen (find-file-noselect file)))))
+
+	  (t (find-file file)))))                        ;; emacs18+
+
+
+(defun server-edit-files-quickly (list)
+  "For each (line-number . file) pair in LIST, edit the file at line-number.
+Unlike (server-edit-files), no information is saved about clients waiting on
+edits to this buffer."
+  (server-write-to-client current-client nil)
+  (setq current-client nil)
+  (while list
+    (let ((line (car (car list)))
+	  (path (cdr (car list))))
+      (server-find-file path)
+      (server-make-window-visible)
+      (goto-line line))
+    (setq list (cdr list))))
+
+
+(defun server-edit-files (list)
+  "For each (line-number . file) pair in LIST, edit the file at line-number.
+Save enough information for (server-kill-buffer) to inform the client when
+the edit is finished."
+  (while list
+    (let ((line (car (car list)))
+	  (path (cdr (car list))))
+      (server-find-file path)
+      (server-make-window-visible)
+      (let ((old-clients (assq current-client server-clients))
+	    (buffer (current-buffer)))
+	(goto-line line)
+	(setq server-buffer-clients
+	      (cons current-client server-buffer-clients))
+	(if old-clients			;client already waiting for buffers?
+	    (nconc old-clients (list buffer)) ;yes -- append this one as well
+	  (setq server-clients		;nope -- make a new record
+		(cons (list current-client buffer)
+		      server-clients)))))
+      (setq list (cdr list)))
+  (message (substitute-command-keys
+	    (if (and (boundp 'infodock-version) window-system)
+		"Type {\\[server-edit]} or select Frame/Delete to finish edit."
+	      "When done with a buffer, type \\[server-edit]."))))
+
+(defun server-tty-edit-files (tty termtype list)
+  "For each (line-number . file) pair in LIST, edit the file at line-number.
+Save enough information for (server-kill-buffer) to inform the client when
+the edit is finished."
+  (or list (setq list '((1 . nil))))
+  (while list
+    (let ((line (car (car list)))
+	  (path (cdr (car list))))
+      (server-tty-find-file tty termtype path)
+      (server-make-window-visible)
+      (let ((old-clients (assq current-client server-clients))
+	    (buffer (current-buffer)))
+	(goto-line line)
+	(setq server-buffer-clients
+	      (cons current-client server-buffer-clients))
+	(if old-clients			;client already waiting for buffers?
+	    (nconc old-clients (list buffer)) ;yes -- append this one as well
+	  (setq server-clients		;nope -- make a new record
+		(cons (list current-client buffer)
+		      server-clients)))))
+      (setq list (cdr list)))
+  (message (substitute-command-keys
+	    (if (and (boundp 'infodock-version) window-system)
+		"Type {\\[server-edit]} or select Frame/Delete to finish edit."
+	      "When done with a buffer, type \\[server-edit]."))))
+
+(defun server-get-buffer (buffer)
+  "One arg, a BUFFER or a buffer name.  Return the buffer object even if killed.
+Signal an error if there is no record of BUFFER."
+  (if (null buffer)
+      (current-buffer)
+    (let ((buf (get-buffer buffer)))
+      (if (null buf)
+	  (if (bufferp buffer)
+	      buffer
+	    (if (stringp buffer)
+		(error "No buffer named %s" buffer)
+	      (error "Invalid buffer argument")))
+	buf))))
+
+(defun server-kill-buffer (buffer)
+  "Kill the BUFFER.  The argument may be a buffer object or buffer name. 
+NOTE: This function has been enhanced to allow for remote editing
+in the following way:
+
+If the buffer is waited upon by one or more clients, and a client is
+not waiting for other buffers to be killed, then the client is told
+that the buffer has been killed."
+  (interactive "bKill buffer ")
+  (setq buffer (server-get-buffer buffer))
+  (if (buffer-name buffer)
+      (save-excursion
+	(set-buffer buffer)
+	(let ((old-clients server-clients))
+	  (server-real-kill-buffer buffer) ;try to kill it
+	  (if (buffer-name buffer)	;succeeded in killing?
+	      nil			;nope
+	    (while old-clients
+	      (let ((client (car old-clients)))
+		(delq buffer client)
+		(if (cdr client)	;pending buffers?
+		    nil			;yep
+		  (server-write-to-client (car client) nil) ;nope, tell client
+		  (setq server-clients (delq client server-clients))))
+	      (setq old-clients (cdr old-clients))))))))
+
+
+(defun server-kill-all-local-variables ()
+  "Eliminate all the buffer-local variable values of the current buffer.
+This buffer will then see the default values of all variables.
+NOTE: This function has been modified to ignore the variable 
+server-buffer-clients."
+  (let ((clients server-buffer-clients))
+    (server-real-kill-all-local-variables)
+    (if clients
+	(setq server-buffer-clients clients))))
+
+
+(or (fboundp 'server-real-kill-buffer)
+  (fset 'server-real-kill-buffer (symbol-function 'kill-buffer)))
+
+(fset 'kill-buffer 'server-kill-buffer)
+
+(or (fboundp 'server-real-kill-all-local-variables)
+    (fset 'server-real-kill-all-local-variables
+	  (symbol-function 'kill-all-local-variables)))
+
+(fset 'kill-all-local-variables 'server-kill-all-local-variables)
+
+
+(defun server-buffer-done (buffer)
+  "Mark BUFFER as \"done\" for its client(s).
+Buries the buffer, and returns another server buffer as a suggestion for the
+new current buffer."
+  (let ((next-buffer nil)
+	(old-clients server-clients))
+    (while old-clients
+      (let ((client (car old-clients)))
+	(or next-buffer 
+	    (setq next-buffer (nth 1 (memq buffer client))))
+	(delq buffer client)
+	;; If client now has no pending buffers,
+	;; tell it that it is done, and forget it entirely.
+	(if (cdr client)
+	    nil
+	  (server-write-to-client (car client) nil)
+	  (setq server-clients (delq client server-clients))))
+      (setq old-clients (cdr old-clients)))
+    (if (buffer-name buffer)
+	(save-excursion
+	  (set-buffer buffer)
+	  (setq server-buffer-clients nil)))
+   (funcall server-done-function buffer)
+    next-buffer))
+
+
+(defun mh-draft-p (buffer)
+  "Return non-nil if this BUFFER is an mh <draft> file. Since MH deletes 
+draft *BEFORE* it is edited, the server treats them specially."
+ ;; This may not be appropriately robust for all cases.
+  (string= (buffer-name buffer) "draft"))
+
+
+(defun server-done ()
+  "Offer to save current buffer and mark it as \"done\" for clients.
+Also bury it, and return a suggested new current buffer."
+  (let ((buffer (current-buffer)))
+    (if server-buffer-clients
+	(progn
+ 	  (if (mh-draft-p buffer)
+ 	      (progn (save-buffer)
+		     (write-region (point-min) (point-max)
+				   (concat buffer-file-name "~"))
+		     (kill-buffer buffer))
+	    (if (and (buffer-modified-p)
+		     (y-or-n-p (concat "Save file " buffer-file-name "? ")))
+		(save-buffer buffer)))
+	  (server-buffer-done buffer)))))
+
+
+(defun server-edit (&optional arg)
+  "Switch to next server editing buffer and mark current one as \"done\".
+If a server buffer is current, it is marked \"done\" and optionally saved.
+MH <draft> files are always saved and backed up, no questions asked.
+When all of a client's buffers are marked as \"done\", the client is notified.
+
+If invoked with a prefix argument, or if there is no server process running, 
+starts server process and that is all.  Invoked by \\[server-edit]."
+  (interactive "P")
+  (if (or arg
+	  (not server-process)
+	  (memq (process-status server-process) '(signal exit)))
+      (server-start nil)
+    (if server-buffer-clients
+	(progn (server-switch-buffer (server-done))
+	       (cond ((or ;(not window-system) #### someone examine!
+			  (and gnuserv-frame 
+			       (or (and (fboundp 'frame-live-p)
+					(frame-live-p gnuserv-frame))
+				   (and (fboundp 'live-screen-p)
+					(live-screen-p gnuserv-frame))
+				   (and (fboundp 'create-screen)
+					(screenp gnuserv-frame)))))
+		      ())                                   ;; do nothing
+		     ((fboundp 'delete-frame)
+		      (delete-frame (selected-frame) t))
+		     ((fboundp 'delete-screen)
+		      (delete-screen))))
+      (error 
+       "(server-edit): Use only on buffers created by external programs.")
+      )))
+
+(defun server-switch-buffer (next-buffer)
+  "Switch to NEXT-BUFFER if a live buffer, otherwise switch to another buffer
+with gnuserv clients. If no such buffer is available, simply choose another 
+one."
+  (if next-buffer
+      (if (and (bufferp next-buffer)
+	       (buffer-name next-buffer))
+	  (switch-to-buffer next-buffer)
+	;; If NEXT-BUFFER is a dead buffer,
+	;; remove the server records for it
+	;; and try the next surviving server buffer.
+	(server-switch-buffer
+	 (server-buffer-done next-buffer)))
+    (if server-clients
+	(server-switch-buffer (nth 1 (car server-clients)))
+      (switch-to-buffer (other-buffer)))))
+
+(global-set-key "\C-x#" 'server-edit)
+
+(provide 'gnuserv)
+