Mercurial > hg > xemacs-beta
diff lisp/gnuserv.el @ 217:d44af0c54775 r20-4b7
Import from CVS: tag r20-4b7
author | cvs |
---|---|
date | Mon, 13 Aug 2007 10:08:34 +0200 |
parents | |
children | 262b8bb4a523 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/gnuserv.el Mon Aug 13 10:08:34 2007 +0200 @@ -0,0 +1,779 @@ +;;; gnuserv.el --- Lisp interface code between Emacs and gnuserv +;; Copyright (C) 1989-1997 Free Software Foundation, Inc. + +;; Version: 3.10 +;; 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 behaviour 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 '(repeat (group :inline t + (symbol :tag "Property") + (sexp :tag "Value"))) + :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 "^/tmp/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) + "Returns a sensible value for the main Emacs frame." + (if (eq type 'x) + (car (frame-list)) + nil)) + +(defun gnuserv-visible-frame-function (type) + "Returns 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 (eq type 'x) + (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) + "Creates a special frame for Gnuserv and returns 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))) + (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 t])) + )) + (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) + "Returns 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) + (let (next) + (while (natnump (decf count)) + (gnuserv-buffer-done (current-buffer)) + (setq 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