[comp.emacs] Probably difficult elisp question

bjaspan@ATHENA.MIT.EDU ("Barr3y Jaspan") (11/14/90)

I have written a major mode for emacs that is a wrapper around an
asynchronous process (specifically, a wrapper around zwgc, the Zephyr
Windowgram Client, but that's not important).  I am having a problem
with the interaction between buffer updates and screen updates.

This is a long message, and will require a lot of thought since you
will have to understand the code I've written to see the problem.
And, you really can't you it for yourself unless you have an async.
process that you can make behave something like zwgc.  However, the
code is included at the end of the message.  I'm sending this question
out in hopes that someone has seen this problem before and will know
immediately what is going on.  (If I am really lucky, I am just being
stupid and the solution will be trivial.)  The first part describes
how the code works, and the last couple paragraphs actually describe
the impossible behavior I am seeing from emacs.

Here's how it works.  The filter function (zwgc-wakeup) takes in a
string that is the output of zwgc, which consists of messages (also
called notices, since this is for the Zephyr Notification system)
which are a header (^_\n), the text, and a terminator (^L\n).  The
header and terminator are there so that the filter function can tell
when a notice ends and a new one begins (since there are no guarantees
about when the filter is called).  So, zwgc-wakeup takes in the
string, breaks it up at the header/terminator boundaries, and passes
each individual notice part, together with the boolean values of "is
this string the beginning of a new notice" and "is this string the end
of the current notice" to zwgc-wakeup-2.

If zwgc-wakeup-2 gets the beginning of a new notice, it inserts the
text at the end of the *zwgc* buffer, puts two markers around it, and
adds those markers to a list.  (Later on, other commands (like
zwgc-next-notice, zwgc-prev-notice, etc) use that list to narrow the
region to a specified notice.)  If zwgc-wakeup-2 gets more of the
current notice (ie: beg-of-notice is nil) goes to (- (point-max) 1)
and inserts the text (the markers are updated automatically).

Now, pay attention: when zwgc-wakeup-2 gets an end-of-notice set to t,
it (1) pops up a small emacs window containing the *zwgc* buffer if it
is not already popped up, and (2) **re-narrows the buffer to the
region containing the first notice in the buffer** which it gets from
the list mentioned earlier.

Just to make sure everyone is with me, here's a (trivial) example:
zwgc-wakeup gets "HDR Hi there TERM"
zwgc-wakeup-2 gets "Hi there" t t  (ie: beg-of-notice and
					end-of-notice are t)

zwgc-wakeup-2 goes to the end of the buffer (which is currently
empty), inserts "Hi there", puts markers around it, adds them to the
list.  It then pops up the window, narrows to the first notice in the
list (there's only one), and returns.

zwgc-wakeup gets "HDR This is the next notice. TERM"
zwgc-wakeup-2 gets "This is the next" t t

zwgc-wakeup-2 widens the buffer, goes to the end, inserts the text,
puts markers around it (because this is a new notice), pops up the
window (it may already be up), re-narrows the buffer to the first
notice, and returns.  Now the notice that is displayed is "Hi there",
the mode line says (1/2) since we are looking at the first of two
notices.

(Things can get more complicated if the headers and terminators don't
always arrive in pairs, but I handle that properly and that isn't the
issue here (I don't think so, at least).)

-- end of example --

Now, here's the problem.  When end-of-notice is t inside
zwgc-wakeup-2, it pops up the window, renarrows the buffer to the
first notice in the buffer and returns.  HOWEVER, the entire window
"flashes" (which I assume means is erased and redrawn) every time this
happens, even if the contents of the buffer do not change.  (The mode
line changes, but that shouldn't cause it.)  This, of course, is a big
problem, because EMACS ISN'T SUPPOSED TO REDRAW ANYTHING DURING THE
RUN OF A LISP FUNCTION, or so I thought.

So I put debugging code into zwgc-wakeup-2 to try to figure out what
was causing it.  The debugging code changed the behavior -- depending
on where I put the debugging statements, the buffer no longer flashed
when a new notice was received.  Eventually, I boiled it down to this:
putting a (sleep-for 1) just before the code that does a pop-up when
end-of-notice is t prevents the buffer from flashing when a new notice
is received.  To me, this means that all of the "obvious" causes
(popping up the window causes a refresh, etc etc) aren't the problem.

I do not call sit-for or accept-process-input or anything else that
should allow emacs to redisplay the screen.  What is going on should
be completely impossible, which means it may be a bug in emacs.
(I've found three other bugs in emacs as the result of writing this
mode, so it is not statistically unlikely.)

Anyway, I hope someone has a clue.  Search for the string zwgc-wakeup
as a good place to start looking at the code.  My next step is to run
this thing with emacs running under a debugger... (oh, joy. :-)

Barr3y Jaspan, bjaspan@mit.edu

--- snip snip ---

;;; zwgc.el
;;;
;;; Emacs mode for running zwgc in a sub-process of emacs.  Invoke with
;;; M-x zwgc.  See mode help for more information.
;;;
;;; Written by: Barr3y Jaspan <bjaspan@athena.mit.edu>
;;;
;;; Based on original zwgc-mode by:
;;;		Mark W. Eichin <eichin@athena.mit.edu>
;;;             Theodore T'so <tytso@athena.mit.edu>
;;;
;;; $Id: zwgc.el,v 2.13 90/11/01 11:57:38 bjaspan Exp Locker: bjaspan $
;;; $Source: /afs/athena.mit.edu/user/b/bjaspan/elisp/RCS/zwgc.el,v $
;;;
;;; $Log:	zwgc.el,v $
; Revision 2.13  90/11/01  11:57:38  bjaspan
; minor fixes
; 
; Revision 2.12  90/10/30  17:22:40  bjaspan
; added zwgc-reply stuff
; 
; Revision 2.11  90/10/26  16:55:24  bjaspan
; fixed notice-terminator handling, fixed notice-regexp-alist handling,
; rewrote sentinel to remove dependency on 'compile, add zwgc-punt-regexp
; 
; Revision 2.10  90/10/24  16:49:02  bjaspan
; added zwgc-notifications-terminator, mostly fixed zwgc-notice-regexp-alist
; handling.
; 
; Revision 2.9  90/10/19  23:25:50  bjaspan
; added eichin's fixed zwgc-pop-from-buffer
; 
; Revision 2.8  90/09/17  14:46:11  bjaspan
; zwgc-wakeup --> zwgc-wakeup-2; now zwgc-wakeup splits the incoming string
; so multiple notices delivered in one block are handled correctly
; 
; Revision 2.7  90/09/16  23:19:25  bjaspan
; changed Special-pop-up-window so it uses zwgc-resize-window instead of
; calling enlarge-window; the previous version cause emacs to coredump
; under conditions I still don't understand
; 
; Revision 2.6  90/09/15  23:55:14  bjaspan
; fixed things so the minibuffer isn't screwed up (doesn't call message);
; stole kludgy silent-replace-regexp
; 
; Revision 2.5  90/09/15  22:48:36  bjaspan
; completely rewrote the display code; all zwgc functions can be called
; from anywhere
; 
; Revision 2.4  90/09/14  18:04:57  bjaspan
; added some useful variables for zwgc control and a temporary hack
; to zwgc-punt to it can be called from anywhere (to be fixed correctly)
; 
; Revision 2.3  90/09/12  23:59:13  bjaspan
; fixed bug in zwgc-wakeup caused by having the notice separator and
; all of the text of the notice come as two separate strings
; 
; Revision 2.2  90/09/12  23:11:22  bjaspan
; "h" and "q" commands, zwgc-description-file, made zwgc-mode-map
; only contain zwgc commands
; 
; Revision 2.1  90/09/12  17:02:48  bjaspan
; initial revision (checked in as 2.1)
; 

(defvar zwgc_el-RCS-id)
(setq zwgc_el-RCS-id "$Header: /afs/athena.mit.edu/user/b/bjaspan/elisp/RCS/zwgc.el,v 2.13 90/11/01 11:57:38 bjaspan Exp Locker: bjaspan $")

(provide 'zwgc)

(defvar zwgc-process nil)

(defvar zwgc-description-file (expand-file-name "~/.zwgc.desc")
  "*Filename passed after the \"-f\" argument to zwgc.")

(defvar zwgc-environment (list "EMACS=t" "DISPLAY=emacs" "TERM=emacs")
  "*List of strings to add to the environment for zwgc.")

(defvar zwgc-window-min-height 7
  "*Minimum size of the *zwgc* buffer when it is displayed.  The intention is
to minimze repainting as the buffer size gets bounced around.")

(defvar zwgc-window-max-height 10
  "*Maximum size of the *zwgc* buffer when it is displayed.")

(defvar zwgc-marker-list nil
  "List of cons cells containing the markers that surround each active
message in the *zwgc* buffer.")

(defvar zwgc-marker-tail nil
  "Pointer to the last element of zwgc-marker-list.")

(defvar zwgc-marker-index 1 
  "Index of the zwgc message current displayed in *zwgc*.")

(defvar zwgc-notices 0
  "The total number of zephyr messages currently in the *zwgc* buffer.")

(defvar zwgc-prog "/usr/etc/zwgc"
  "*Program to run as the zwgc process. Should set it for the machine type.")

(defvar zwgc-warp-to-new nil
  "*Toggles whether new messages are immediately displayed.")

(defvar zwgc-jump-on-new nil
  "*If non-nil, change the selected window when a new notice arrives if
the *zwgc* window is selected.")

(defvar zwgc-buffer-stack nil
  "Internal stack used to jump in and out of the *zwgc* buffer.")

(defvar zwgc-notifications-separator "\n"
  "*String used by zwgc mode to identify notice boundaries.  Each notice
ouput by the zwgc subprocess must begin with this string.")

(defvar zwgc-notifications-terminator "\n"
  "*String used by zwgc mode to identify the end of a notice.
Necessary for proper functioning of zwgc-got-notice-hook and
zwgc-notice-regexp-alist.")

(defvar zwgc-mode-map nil "*Keymap used by the zwgc major mode.  Binding a
key to this variable sets that key as a prefix for zwgc-mode functions
from outside the *zwgc* buffer.")

(defvar zwgc-notice-regexp-alist nil
  "*List of (REGEXP . function) pairs that are matched against each
notice as it is received.  If REGEXP matches a new notice, the
corresponding function is called with a single argument,
(match-data).")

(defvar zwgc-reply-cell nil "\\<zwgc-mode-map>
*Cons cell consisting of (REGEXP . FUNCTION).  REGEXP is matched
against the current notice when \\[zwgc-reply] (zwgc-reply) is
executed and the match data is passed as the only argument to
FUNCTION.")

;;; ------------------------------------------------------------------
;;; Utilities -- these functions have nothing to do with zwgc mode.
;;; They could (and should) be moved elsewhere.
;;; ------------------------------------------------------------------

(defun silent-replace-regexp (old new)
  (save-excursion
    (while (re-search-forward old (point-max) t)
      (replace-match new))))

(defun window-of-buffer (buf)
  "Returns the window lisp object currently displaying BUFFER or nil."
  (if (stringp buf)
      (setq buf (get-buffer buf)))
  (if (not (bufferp buf))
      (error "Not a buffer -- how do I singal the type error?"))
  (save-window-excursion
    (let ((start (selected-window)))
      (setq win start)
      (while (and (not (equal buf (window-buffer)))
		  (not (equal start (next-window win))))
	(setq win (select-window (next-window))))
      (if (equal buf (window-buffer))
	  win
	nil)
      )))

(defun narrow-to-string (string)
  "Insert STRING after point and narrow to the region containing it."
  (interactive "sNarrow to string: ")
  (narrow-to-region (point) (point))
  (insert string)
  (goto-char (point-min)))

(defun replace-regexp-on-string (from to string)
  "Replace occurences of regexp FROM with regexp TO in STRING; uses same
rules as replace-regexp."
  (save-excursion
    (let ((buf (generate-new-buffer "  *tmp")))
      (set-buffer buf)
      (insert string)
      (goto-char (point-min))
      ; (perform-replace from to nil t nil) ; (replace-regexp from to)
      (silent-replace-regexp from to)
      (setq new-string (buffer-substring (point-min) (point-max)))
      (kill-buffer buf)
      new-string)))

(defun split (string regexp)
  "Splits STRING into a list of strings by choosing breakpoints 
according to REGEXP.  If REGEXP does not occur the entire STRING
is contained in the first element of the returned list."
  (save-excursion
    (let ((buf (generate-new-buffer "  *tmp"))
	  split-list)
      (set-buffer buf)
      (insert string)
      (goto-char (point-min))
      (let ((p (point)))
	(while (re-search-forward regexp nil t)
	  (save-excursion
	    (re-search-backward regexp)
	    (setq split-list (cons (buffer-substring p (point)) split-list)))
	  (setq p (point))))
      (setq split-list (cons (buffer-substring (point) (point-max)) 
			     split-list))
      (kill-buffer buf)
      (reverse split-list))))

(defun last (lst)
  "Return the pointer to last element of LIST or nil is LIST is nil.
(setcdr (last l) (cons NEW-ELEMENT nil)) appends NEW-ELEMENT to the 
end of l via mutation."
  (cond ((eq lst nil) nil)
	((not (cdr lst)) lst)
	(t (last (cdr lst)))))

(defun filter (pred lst)
  (cond ((null lst) nil)
	((funcall pred (car lst))
	 (cons (car lst) (filter pred (cdr lst))))
	(t
	 (filter pred (cdr lst)))))

;;; ------------------------------------------------------------------
;;; End of utilities
;;; ------------------------------------------------------------------

(defun zwgc-log (&rest objs)
  (let ((buffer (current-buffer)))
    (set-buffer (get-buffer-create "*zwgc-log*"))
    (point-max)
    (mapcar (function (lambda (obj)
			(print obj (get-buffer "*zwgc-log*"))))
	    objs)
    (point-max)
    (set-buffer buffer)))

(defun zzzz (m s)
  (message m)
  (sleep-for s))

(defun zwgc-push-to-buffer ()
  "Save the current window and buffer and move into the *zwgc* window,
if it exists."
  (let ((buffer (current-buffer))
	(window (selected-window))
	(zwgc-buffer (get-buffer "*zwgc*"))
	(zwgc-window (window-of-buffer "*zwgc*")))
    
    (if (or (not zwgc-window) (not zwgc-buffer))
	(error "*zwgc* buffer not visible or does not exist")
      (setq zwgc-buffer-stack (cons (cons buffer window) zwgc-buffer-stack))
      (select-window zwgc-window)
      (set-buffer zwgc-buffer))))

(defun zwgc-pop-from-buffer ()
  "Restore the previously pushed (with zwgc-push-to-buffer) window and buffer."
  (if (not zwgc-buffer-stack)
      (error "zwgc buffer stack is empty.")
    (let ((p (car zwgc-buffer-stack)))
      (setq zwgc-buffer-stack (cdr zwgc-buffer-stack))
      (if (not (equal (cdr p) (next-window (previous-window (cdr p)))))
	  nil
	(select-window (cdr p))
	(set-buffer (car p))))))

(defun zwgc-pop-from-buffer-to-nowhere ()
  "Delete the top element from zwgc-buffer-stack."
  (if (not zwgc-buffer-stack)
      (error "zwgc buffer stack is empty.")
    (setq zwgc-buffer-stack (cdr zwgc-buffer-stack))))

(defun zwgc-apply-cell (cell)
  "CELL is (REGEXP . function).  Calls function with the argument
match-data if REGEXP matches the current notice."
  (save-excursion
    (goto-char (point-min))
    (let ((regexp (car cell)) (f (cdr cell)))
      (if (re-search-forward regexp nil t)
	  (condition-case err
	      (funcall f (match-data))
	    (error
	     (message (format "zwgc: received %s applying %s" err cell))
	     (ding 1)
	     )))
      )))

(defun zwgc-eonp (string)
  ;; Returns t if the string ends with zwgc-notifications-terminator,
  ;; nil otherwise.
  (let* ((znt zwgc-notifications-terminator)
	 (znt-len (length znt)))
    (and (>= (length string) znt-len)
	 (equal (substring string (- znt-len)) znt))))

(defun zwgc-wakeup (proc string)
  ;; After split, if the first element is "" then all elements are
  ;; complete notices.  Otherwise the first element is part of the 
  ;; previous notice and all other elements are complete notices.
  ;; (The last element might be part of a notice, but that will be
  ;; handled by a non-"" first element next time).

  ;; Does *NOT* use the proc argument.

  (let* ((string (replace-regexp-on-string "\r" "" string))
	 (split-list (split string zwgc-notifications-separator)))
    
    ;; First element might be special.  I think that the last argument
    ;; to zwgc-wakeup-2 here will always be t, but I'm not sure.
    (if (not (equal (car split-list) ""))
	(zwgc-wakeup-2 (car split-list) nil (zwgc-eonp (car split-list))))
    
    (setq split-list (cdr split-list))
    (while split-list
      (zwgc-wakeup-2 (car split-list) t (zwgc-eonp (car split-list)))
      (setq split-list (cdr split-list)))
    ))

(defun zwgc-wakeup-2 (string beg-of-notice end-of-notice)
  ;; beg-of-notice means this is the beginning of a new notice.
  ;; (not beg-of-notice) means this is a continuation of the previous
  ;; notice.  end-of-notice means this is the end of the current
  ;; notice.  Assumes that the notice-separator has been stripped but
  ;; the notice-terminator has not, all for ease of implementation.
  (save-excursion
    (set-buffer (get-buffer "*zwgc*"))
    (widen)
    (goto-char (point-max))

    ;; If this is the end of a notice, strip off the terminator.
    (if end-of-notice
	(setq string
	      (substring string 0 (- (length zwgc-notifications-terminator)))))
    
    ;; When we get a beginning-of-notice signal, or if we get notice
    ;; text when the *zwgc* buffer is empty (--> assume it's a new notice)
    (if (or beg-of-notice (not (< (point-min) (point-max))))
	(progn
	  (if (eq (length string) 0)
	      (setq string " "))
	  (narrow-to-string string)
	  (setq marker (cons (point-min-marker) (point-max-marker)))
	  (setcdr zwgc-marker-tail (list marker))
	  (setq zwgc-marker-tail (cdr zwgc-marker-tail))
	  (setq zwgc-notices (1+ zwgc-notices)))
      
      ;; When we get more data for the current notice
      ;; We're at point max, so back up and insert the text.
      (save-excursion
	(goto-char (1- (point)))
	(insert string)))
    
    ;; When we get a end-of-notice signal.  This is the only time that
    ;; hooks are called or the window is popped up.
    (if end-of-notice
	(progn
	  (save-restriction
	    (let ((zwgc-marker-index (1- (length zwgc-marker-list))))
	      (zwgc-nth-notice zwgc-marker-index t)
	      (mapcar 'zwgc-apply-cell zwgc-notice-regexp-alist)
	      (run-hooks 'zwgc-got-notice-hook)))))
    )

  ;; If the following line is commented out, the *zwgc* window flashes
  ;; when each notice is received; if it is not commented out, it doesn't.
  (sleep-for 1)
  (if (and end-of-notice (> zwgc-notices 0))
      (progn
	(Special-pop-up-window (get-buffer "*zwgc*")
			       zwgc-jump-on-new)
	(if zwgc-warp-to-new
	    (zwgc-last-notice)
	  (zwgc-current-notice))))
  )

(defun zwgc-resize-window ()
  "Resizes the zwgc window to the current notice (within the
constraints of zwgc-window-{min,max}-height).  The *zwgc* buffer must
be visible and selected."  
  (if (one-window-p) 
      nil 
    (setq lines (1+ (count-lines (point-min) (point-max))))
    (if (> lines zwgc-window-max-height)
	(setq lines zwgc-window-max-height)
      (if (< lines zwgc-window-min-height)
	  (setq lines zwgc-window-min-height))
      )
    (enlarge-window (- lines (window-height (selected-window))))
    ))

(defun zwgc-nth-notice (n &optional nodisplay)
  (if (< n 1)
      (error "No such notice."))
  (let ((marker (nth n zwgc-marker-list)))
    (if (eq marker nil)
	(error "No such notice."))
    (if (not nodisplay)
	(progn
	  (zwgc-push-to-buffer)
	  (setq zwgc-marker-index n)
	  (setq mode-line-process (format " %d/%d" zwgc-marker-index
					  zwgc-notices))))
    (widen)
    (narrow-to-region (car marker) (cdr marker))
    (set-marker (mark-marker) (point-max))
    (goto-char (point-min))
    (if (not nodisplay)
	(progn
	  (zwgc-resize-window)
	  (zwgc-pop-from-buffer)))
    ))

(defun zwgc-first-notice ()
  (interactive)
  (zwgc-nth-notice 1))

(defun zwgc-next-notice ()
  "Display the next notice in the *zwgc* buffer."
  (interactive)
  (zwgc-nth-notice (1+ zwgc-marker-index)))

(defun zwgc-prev-notice ()
  "Display the previous notice in the *zwgc* buffer."
  (interactive)
  (zwgc-nth-notice (1- zwgc-marker-index)))

(defun zwgc-current-notice ()
  "A peculiar command used to reset the narrowed region after a 
notice has been deleted."
  (interactive)
  (zwgc-nth-notice zwgc-marker-index))

(defun zwgc-last-notice ()
  (interactive)
  (zwgc-nth-notice (1- (length zwgc-marker-list))))

(defun zwgc-reply ()
  "Performs a regular-expression search on the current notice with the
car of zwgc-reply-cell as the regexp and then calls the cdr of
zwgc-reply-cell with the match data as an argument."
  (interactive)
  (if (not zwgc-reply-cell)
      (error "zwgc-reply-cell is not defined.")
    (zwgc-push-to-buffer)
    (zwgc-apply-cell zwgc-reply-cell)
    (zwgc-pop-from-buffer)))
  
(defun zwgc-punt (&optional nodisplay)
  "Delete the current Zephyr notice.  Optional argument NODISPLAY
means not to display the new current notice after punting."
  (interactive)

  ;; Sanity check
  (if (< zwgc-notices 1)
      (error "No notices to punt!"))

  (let ((window-min-height 1)
	(buffer (current-buffer))
	(marker (nth zwgc-marker-index zwgc-marker-list)))
    (set-buffer (get-buffer "*zwgc*"))
    (widen)
    
    (if (not (eq marker (car zwgc-marker-tail)))
	(setq zwgc-marker-list (delq marker zwgc-marker-list))
      (setq zwgc-marker-list (delq marker zwgc-marker-list))
      (setq zwgc-marker-tail (last zwgc-marker-list))
      (if (> zwgc-marker-index 1)
	  (setq zwgc-marker-index (1- zwgc-marker-index))))
    (setq zwgc-notices (1- zwgc-notices))
    
    (delete-region (car marker) (cdr marker))
    (set-marker (car marker) nil)
    (set-marker (cdr marker) nil)
    (if (not (< (point-min) (point-max)))
	(progn 
	  (delete-windows-on "*zwgc*")
	  (bury-buffer "*zwgc*"))
      (if (not nodisplay)
	  (zwgc-current-notice))
      )
    (run-hooks 'zwgc-killed-noticed-hook)
    (set-buffer buffer)
    ))

(defun zwgc-punt-regexp (regexp)
  "Cause zwgc to ignore all zwgc notices that match REGEXP.  With a
prefix, does the opposite operation."
  (interactive (list (if current-prefix-arg
			 (read-string "Regexp to unpunt: ")
		       (read-string "Regexp to punt: "))))
  (let ((cell (cons regexp 'zwgc-punt)))
    (if current-prefix-arg
	(setq zwgc-notice-regexp-alist
	      (filter (function (lambda (x) (not (equal cell x))))
		      zwgc-notice-regexp-alist))
      (setq zwgc-notice-regexp-alist (cons cell zwgc-notice-regexp-alist)))
    ))

(defun zwgc-hide ()
  (interactive)
  (bury-buffer "*zwgc*")
  (if (not (one-window-p t))
      (delete-window)))
  
(defun kill-zwgc nil
  "Kill the zwgc subprocess and the *zwgc* buffer."
  (interactive)
  (let* ((buf (get-buffer "*zwgc*"))
	(win (window-of-buffer buf))
	proc)
    (if (not (and buf
		  (setq proc (get-buffer-process buf))
		  (eq (process-status proc) 'run)))
	(error "No zwgc process currently running."))
    (interrupt-process proc)  
    (sit-for 1)
    (delete-process proc)
    (kill-buffer buf)
    (if (and win (not (one-window-p)))
	(delete-window win))
    ))

(defun start-zwgc ()
  "Start the zwgc process."
  (let ((buffer (get-buffer "*zwgc*")) status)
    (setq zwgc-process (get-buffer-process buffer))
    (if zwgc-process (setq status (process-status zwgc-process)))
    (save-excursion
      (set-buffer buffer)
      (if (memq status '(run stop))
          nil
        (if zwgc-process (delete-process zwgc-process))
        (setq zwgc-process 
	      (let ((process-environment (append  zwgc-environment
						  process-environment))
		    process-connection-type)
		(start-process "Zwgc" buffer zwgc-prog "-ttymode" "-nofork"
			       "-f" zwgc-description-file)))
	(set-process-sentinel zwgc-process 'zwgc-sentinel)
	(set-process-filter zwgc-process 'zwgc-wakeup))
      )))

(defun zwgc ()
  "Run zwgc as a subprocess."
  (interactive)
  (let ((buffer (get-buffer-create "*zwgc*")))
    (save-excursion
      (set-buffer buffer)
      (zwgc-mode)
      (start-zwgc))
    ))

(defun zwgc-mode ()
  "\\<zwgc-mode-map>Major mode for receiving and viewing Zephyr
notices.  Each time a notice is received it is displayed in the *zwgc*
window (which is created at the bottom of the screen if it does not
already exist).  When a notice is deleted with \\[zwgc-punt], the next
notice in the queue is displayed or, if there are no more notices, the
window disappears.
  
The following commands are defined (some commands have other
bindings.. use the force):
\\[zwgc-next-notice]		Move to next notice.
\\[zwgc-prev-notice]		Move to previous notice.
\\[zwgc-first-notice]		Move to first notice.
\\[zwgc-last-notice]		Move to last notice.
\\[zwgc-current-notice]		``Move'' to current notice.
\\[zwgc-punt]		Delete current notice.
\\[zwgc-hide]		Hide the *zwgc* buffer until a notice is received.
\\[kill-zwgc]		Kill the zwgc subprocess and *zwgc* buffer.
\\[write-region]		Write current notice to file.
\\[copy-region-as-kill]		Copy current notice as if killed.
\\[describe-mode]		This help message.

Each zephyr notice is expected to begin with the magic sequence
\"^_\\n\" (control-underscore followed by a newline)."
  (interactive)
  (kill-all-local-variables)
  (setq major-mode 'zwgc-mode)
  (setq mode-name "zwgc")
  (put 'zwgc-mode 'mode-class 'special)
  (setq mode-line-format ; The default modulo %n
	'("" mode-line-modified mode-line-buffer-identification "   " 
	  global-mode-string "   %[(" mode-name minor-mode-alist 
	  mode-line-process ")%]----" (-3 . "%p") "-%-"))

  (save-excursion
    (set-buffer (get-buffer "*zwgc*"))
    (delete-region (point-min) (point-max)))
  (setq zwgc-marker-list (list (cons "Marker" "List")))
  (setq zwgc-marker-tail zwgc-marker-list)
  (setq zwgc-marker-index 1)
  (setq zwgc-notices 0)
  (setq zwgc-mode-map nil)
  (if zwgc-mode-map
      nil
    (define-prefix-command 'zwgc-mode-map)
    (setq zwgc-mode-map (symbol-function 'zwgc-mode-map))
    (suppress-keymap zwgc-mode-map)
    (define-key zwgc-mode-map "h" 'describe-mode)
    (define-key zwgc-mode-map "?" 'describe-mode)
    (define-key zwgc-mode-map "<" 'zwgc-first-notice)
    (define-key zwgc-mode-map ">" 'zwgc-last-notice)
    (define-key zwgc-mode-map "n" 'zwgc-next-notice)
    (define-key zwgc-mode-map "p" 'zwgc-prev-notice)
    (define-key zwgc-mode-map "." 'zwgc-current-notice)
    (define-key zwgc-mode-map "r" 'zwgc-reply)
    (define-key zwgc-mode-map "\177" 'zwgc-punt)
    (define-key zwgc-mode-map "d" 'zwgc-punt)
    (define-key zwgc-mode-map "q" 'zwgc-hide)
    (define-key zwgc-mode-map "k" 'kill-zwgc)
    (define-key zwgc-mode-map "\M-w" 'copy-region-as-kill)
    (define-key zwgc-mode-map "w" 'write-region)
    (define-key zwgc-mode-map " " 'scroll-up)
    (define-key zwgc-mode-map "\M-p" 'zwgc-punt-regexp)
    )
  (use-local-map zwgc-mode-map)
  (run-hooks 'zwgc-mode-hook)
  )

;; Called when zwgc loses.  If it exited, restart it; if it was
;; killed, do nothing (let it die).
(defun zwgc-sentinel (process signal)
  (let ((buffer (process-buffer process))
	(status (process-status process)))
    (cond ((eq status 'exit)
	   (if (equal signal "finished\n")
	       nil
	     (ding)
	     (message "zwgc: %s, restarting" (substring signal 0 -1))
	     (start-zwgc)))
	  ((eq status 'signal)
	   nil)
	  (t
	   (ding)
	   (message "zwgc: sentinel called for unknown reason!"))
	  )))

(defun Special-pop-up-window (buffer &optional leave)
  (interactive "bBuffer to pop: ")
  (let* ((retwin (selected-window))
	 (pop-up-windows t)
	 (window-min-height 1)
	 (prob-window (previous-window (minibuffer-window))))
    (if (not (get-buffer-window buffer))
	(progn
	  (split-window prob-window)
	  (select-window prob-window)))
    (pop-to-buffer buffer)
    (if (not (one-window-p t))
	(progn
	  (zwgc-resize-window)
	  (other-window 1)))
    (select-window retwin)
    (if (and leave (equal (window-buffer retwin) buffer))
	(other-window 1))
    ))