From: Steve Haflich

Subject: Re: xemacs lisp listener buffer max size

Date: 1997-10-17 1:16

I was backlogged and didn't get a chance to respond to this promptly.
Perhaps it still isn't too late for these remarks to be of use.

   Date: 07 Oct 1997 22:25:05 UT
   From: Erik Naggum <naggum.no at erik>
   
   | This proposal is simple, provided one is willing to do a little elisp
   | programming, but consider this thought experiment:
   | 
   | (defun tailify-buffer (lines)
   |   "Truncate the current buffer to the last n LINES."
   |   (interactive  "nNumber of lines: ")
   |   (let (beg end)
   |     (save-excursion
   |       (end-of-buffer)
   |       (forward-line (- lines))
   |       (setq end (point))
   |       (beginning-of-buffer)
   |       (setq beg (point)))
   |     (delete-region beg end)))
   
   as an Emacs maintainer, it pains me to see this code.  Hans Chalupsky's
   advice (pun intended) is much to be preferred, but see below.
   
   both `end-of-buffer' and `beginning-of-buffer' have a strong warning in
   their documentation not to use them in Lisp programs.  ("Don't use this
   command in Lisp programs!")  instead, use (goto-char (point-min)) and
   (goto-char (point-max)), respectively.  (but this is also unnecessary in
   this case.)

Right -- very sloppy of me.  I don't write elisp code every day.
   
   we need to be careful about a few things when deleting text from the start
   of a buffer.  first, when deleting text, Emacs moves the "gap" to the
   deletion point and then it is moved again when the next insertion arrives
   at the end.  the "gap" is just that, a gap between two halves of a buffer
   where all editing operations are performed.  moving it or expanding it
   unnecessarily is the major source of CPU usage in badly written Emacs Lisp
   code.  (not that anybody gives any guidance to Emacs Lisp programmers about
   it, however.)  it's therefore prudent to do something like Hans Chalupsky's
   90%-full solution.  there is no guarantee that the buffer will actually
   shrink from this exercise, either, and many small deletions may yet cause
   Emacs to grow without bound over time.

   second, the undo list is a user feature, and should not have stuff like
   this added to it.  ideally, the undo list should not affect process output
   to a buffer, but this is on our to-do list.

This seems like an unsolvable algorithm to me.

   until an Emacs-wide solution
   is made available, programmers of functions that insert text into buffers
   from inferior processes must take care to bind `buffer-undo-list' to t
   dynamically over their insertion.

This doesn't seem right.  See below.
   
   third, a user may have narrowed the buffer by the time these functions are
   called.  (this problem also applies to Hans Chalupsky's code.)
   
   this code, replacing the meat of Hans' code, takes care of these problems
   
       (save-excursion
         (set-buffer buffer)
         (if (< max-acl-buffer-size (buffer-size))
   	(save-restriction
   	  (widen)

Corrrection noted, again.

   	  (let ((buffer-undo-list t))
   	    (delete-region
   	     (point-min)
   	     (- (point-max) (truncate max-acl-buffer-size 1.1111111)))))))
   
   | Emacs is very reticent to throw anything away.
   
   well.  `undo-limit' (20000) and `undo-strong-limit' (30000) are the upper
   limits to how much undo information is kept, in bytes.  see their
   documentation.

I tried a quick test, and was surprised by the results.  I wrote a
couple megabytes into a cl lisp listener buffer, then used
delete-region to remove most of it.  I then made about ten small
additional interactions with the lisp listener.  The theory was that
the huge delete-region would have been flushed by the size limit on
buffer-undo memory.  I then used the undo command repeatedly to undo
back through the huge delete-region.  To my surprise Emacs 19.34.2 was
quite willing to reinstantiate all the deleted text, quite in excess
of undo-strong-limit.

I didn't investigate further (i.e. look at the Emacs source code) and
I don't know whether the undo information would eventually have been
flushed, perhaps the next time Emacs needed to allocate more memory.
You should be able to duplicate this experiment if you are so
inclined.  (If you're really interested I'll try to repeart it and
record the steps.)  Meanwhile, it behooves anyone who runs processes
lasting several months and which will depend upon Emacs not growing
without bounds to investigate and verify that the actual behavior will
be reasonable.
   
   | You can control this, assuming you don't really need undo
   | to be available in this buffer.  See buffer-disable-undo.
   
   hm.  the FI package should not introduce process output to the undo list to
   begin with.  continuous output to the Lisp process buffer will be made into
   one, giant undo item.  once past `undo-strong-limit' in length, it will be
   nuked during garbage collection.  in any case, (buffer-disable-undo) is
   exactly like (setq buffer-undo-list t).  process filters have a nasty habit
   of changing this value if not written super-correctly, so it wouldn't hurt
   to "reinforce" the binding.

I'm unsure whether you are actually suggesting that undo not be
supported at all in a listener buffer.  I think this would be
unfortunate, since I often think and revise while typing at a
listener.  It might be reasonable to flush the undo information each
time immediately after the listener prints to the buffer, so undo
information is repeatedly bounded each time lisp prints a prompt.
This idea would pose some difficulties for those of us who like to
type ahead while the previous top-level form is still executing, but
of course the current sublisp mode also misbehaves in that regard.
   
   | I believe to buffer-disable-undo appearently flushes all undo information
   | (but check -- undo informtion is kept in a buffer-local variable -- see
   | buffer-local-variables).  It need be called just once, but is probably
   | harmless to call repeatedly.
   
   the undo information is kept in `buffer-undo-list'.  undo information is
   added to the head of the list in this variable, unless it is t, in which
   case undo information is discarded.
   
   | See the emacs-lisp source file fi-subproc.el.  There is an undocumented
   | hook variable fi::subprocess-filter-insert-output-hook which I believe is
   | unused except by the "presenting listener" in Composer.  It could
   | probably be used in the initial lisp listener to call tailify-buffer,
   | something like this, after the ACL has started in the buffer:
   
   this is cool.  this is even better than Hans Chalupsky's advice.
   
   | (save-excursion
   |   (set-buffer "*common-lisp*")
   |   (buffer-disable-undo)
   |   ;; This will be called each time CL output is inserted into the buffer,
   |   ;; just before the output is inserted.
   |   (setq fi::subprocess-filter-insert-output-hook 'tailify-1000))
   
   but _please_ don't nuke hooks.  hooks are magic, and should be added to
   with `add-hook' and removed from with `remove-hook', which keeps magic
   working.

Right.
   
   | None of this is checked, and anyone who uses it would have to take the
   | responsibility for debugging it, supporting it, and keeping up with
   | future versions.
   
   new releases of "comint" in the standard Emacs distribution obey a variable
   `comint-buffer-maximum-size' if `comint-truncate-buffer' is on the filter
   functions list.  I would suggest that the FI package adopt the newer
   "comint" version and only add to it where absolutely necessary.

Alas, I suspect this is impractical without significant work.  Comint
is nice, but the initial version of the acl emacs-lisp interface was
coded in 1987 and would be hard to glue together the subsequent decade
of independent developments.  I don't think anyone is prepared to do a
full rewrite right now...