Mercurial > hg > xemacs-beta
diff lisp/packages/gnuserv.el @ 151:59463afc5666 r20-3b2
Import from CVS: tag r20-3b2
author | cvs |
---|---|
date | Mon, 13 Aug 2007 09:37:19 +0200 |
parents | 538048ae2ab8 |
children | 25f70ba0133c |
line wrap: on
line diff
--- a/lisp/packages/gnuserv.el Mon Aug 13 09:36:20 2007 +0200 +++ b/lisp/packages/gnuserv.el Mon Aug 13 09:37:19 2007 +0200 @@ -1,7 +1,7 @@ ;;; gnuserv.el --- Lisp interface code between Emacs and gnuserv ;; Copyright (C) 1989-1997 Free Software Foundation, Inc. -;; Version: 3.1 +;; Version: 3.2 ;; Author: Andy Norman (ange@hplb.hpl.hp.com), originally based on server.el ;; Hrvoje Niksic <hniksic@srce.hr> ;; Keywords: environment, processes, terminals @@ -77,7 +77,7 @@ ;;; Code: (defconst gnuserv-rcs-version - "$Id: gnuserv.el,v 1.8 1997/05/18 03:40:06 steve Exp $") + "$Id: gnuserv.el,v 1.9 1997/05/23 01:36:30 steve Exp $") (defgroup gnuserv nil "The gnuserv suite of programs to talk to Emacs from outside." @@ -154,6 +154,16 @@ :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 @@ -230,7 +240,8 @@ ;; 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) +(pushnew '(gnuserv-minor-mode " Server") minor-mode-alist + :test 'equal) ;; Sample gnuserv-frame functions @@ -263,14 +274,45 @@ ;;; 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) (case (process-status proc) - (exit (message "Gnuserv subprocess exited; restarting") - ;; This will also kill all the existing clients. - (gnuserv-start-1)) - (closed (message "Gnuserv subprocess closed")) - (signal (message "Gnuserv subprocess killed")))) + (exit + (message + (substitute-command-keys + "Gnuserv subprocess exited; restart with `\\[gnuserv-start]'")) + (gnuserv-prepare-shutdown)) + (signal + (message + (substitute-command-keys + "Gnuserv subprocess killed; restart with `\\[gnuserv-start]'")) + (gnuserv-prepare-shutdown)) + (closed + (message + (substitute-command-keys + "Gnuserv subprocess closed; restart with `\\[gnuserv-start]'")) + (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)) @@ -298,6 +340,16 @@ (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) @@ -306,7 +358,6 @@ (length result) result))) (process-send-string gnuserv-process s)))) - ;; The following two functions are helper functions, used by ;; gnuclient. @@ -325,82 +376,90 @@ ;; "Execute" a client connection, called by gnuclient. This is the ;; backbone of gnuserv.el. -(defun gnuserv-edit-files (type list &optional flags) +(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 FLAGS is `quick', just edit the files in Emacs. -If FLAGS is `view', view the files read-only." - (or (not flags) - (memq flags '(quick view)) - (error "Invalid flag %s" 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. - (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 nil 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)) - ;; Visit all the listed files. - (while list - (let ((line (caar list)) (path (cdar list))) - (select-frame frame) - ;; Visit the file. - (funcall (if (eq flags 'view) - gnuserv-view-file-function - gnuserv-find-file-function) - path) - (goto-line line) - (run-hooks 'gnuserv-visit-hook) - ;; Don't memorize the quick and view buffers. - (when (null flags) - (pushnew (current-buffer) (gnuclient-buffers client)) - (setq gnuserv-minor-mode t)) - (pop list))) - (cond ((and flags (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 (null flags) - (gnuclient-buffers client)) - (message (substitute-command-keys - "Type `\\[gnuserv-edit]' to finish editing")) - (or dest-frame - (message (substitute-command-keys - "Type `\\[delete-frame]' to finish editing")))))))) +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. + (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 nil 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) + (run-hooks 'gnuserv-visit-hook) + ;; Don't memorize the quick and view buffers. + (unless (or quick view) + (pushnew (current-buffer) (gnuclient-buffers client)) + (setq gnuserv-minor-mode t)) + (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 (substitute-command-keys + "Type `\\[gnuserv-edit]' to finish editing")) + (or dest-frame + (message (substitute-command-keys + "Type `\\[delete-frame]' to finish editing"))))))))) ;;; Functions that hook into Emacs in various way to enable operation @@ -408,7 +467,9 @@ ;; Defined later. (add-hook 'kill-emacs-hook 'gnuserv-kill-all-clients t) -;; A helper function; used by others. +;; 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 ((client gnuserv-clients) @@ -419,6 +480,13 @@ (pop client)) 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. ;; @@ -443,7 +511,7 @@ ;; living client. (defun gnuserv-kill-buffer-query-function () (or gnuserv-kill-quietly - (not (gnuserv-buffer-clients (current-buffer))) + (not (gnuserv-buffer-p (current-buffer))) (yes-or-no-p (format "Buffer %s belongs to gnuserv client(s); kill anyway? " (current-buffer))))) @@ -538,7 +606,7 @@ ;;; Higher-level functions ;; Choose a `next' server buffer, according to several criteria, and -;; return it. If none appropriate are found, return nil. +;; return it. If none are found, return nil. (defun gnuserv-next-buffer () (let* ((frame (selected-frame)) (device (selected-device)) @@ -555,18 +623,20 @@ (setq client (car (member* device gnuserv-clients :key 'gnuclient-device)))) (car (gnuclient-buffers client))) - ;; Else, try to find just any client, and return its first buffer. - (gnuserv-clients - (car (gnuclient-buffers (car gnuserv-clients)))) - ;; Oh, give up. + ;; 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 'gnuserv-buffers))) + (car (gnuclient-buffers client))) + ;; Oh, give up. (t nil)))) (defun gnuserv-buffer-done (buffer) "Mark BUFFER as \"done\" for its client(s). -Calls `gnuserv-done-function' and returns another gnuserv buffer as a -suggestion for the new current buffer." +Does the save/backup queries first, and calls `gnuserv-done-function'." ;; Check whether this is the real thing. - (unless (gnuserv-buffer-clients buffer) + (unless (gnuserv-buffer-p buffer) (error "%s does not belong to a gnuserv client" buffer)) ;; Backup/ask query. (if (gnuserv-temp-file-p buffer) @@ -578,8 +648,7 @@ (if (and (buffer-modified-p) (y-or-n-p (concat "Save file " buffer-file-name "? "))) (save-buffer buffer))) - (gnuserv-buffer-done-1 buffer) - (gnuserv-next-buffer)) + (gnuserv-buffer-done-1 buffer)) ;; Called by `gnuserv-start-1' to clean everything. Hooked into ;; `kill-emacs-hook', too. @@ -587,29 +656,53 @@ "Kill all the gnuserv clients. Ruthlessly." (mapc 'gnuserv-kill-client gnuserv-clients)) -;; Actually start the process. Kills all the clients before-hand. -(defun gnuserv-start-1 (&optional leave-dead) +;; 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))) + (error nil)) + (setq gnuserv-process nil) + (message "Killed server"))) + +;; 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 "") - (setq gnuserv-current-client nil) + (setq gnuserv-string "" + gnuserv-current-client nil) (let ((process-connection-type t)) - (setq gnuserv-process + (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))) + (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 @@ -618,35 +711,44 @@ Prefix arg means just kill any existing server communications subprocess." (interactive "P") - ;; kill it dead! (and gnuserv-process (not leave-dead) (message "Restarting gnuserv")) (gnuserv-start-1 leave-dead)) -;;;###autoload -(defun gnuserv-edit (&optional arg) +(defun gnuserv-edit (&optional count) "Mark the current gnuserv editing buffer as \"done\", and switch to next one. -The `gnuserv-done-function' is used to dispose of the buffer after marking it -as done; it is `kill-buffer' by default. +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'. +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. - -If invoked with a prefix argument, or if there is no gnuserv process -running, only starts server process. Invoked with \\[gnuserv-edit]." +When all of a client's buffers are marked as \"done\", the client is notified." (interactive "P") - (if (or arg (not gnuserv-process) - (memq (process-status gnuserv-process) '(signal exit))) - (gnuserv-start) - (switch-to-buffer (or (gnuserv-buffer-done (current-buffer)) - (current-buffer))))) + (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)))))) -;;;###autoload (global-set-key "\C-x#" 'gnuserv-edit) (provide 'gnuserv)