diff lisp/pcl-cvs/cookie.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/pcl-cvs/cookie.el	Mon Aug 13 08:45:50 2007 +0200
@@ -0,0 +1,1137 @@
+;;; $Id: cookie.el,v 1.1.1.1 1996/12/18 03:32:25 steve Exp $
+;;; cookie.el -- Utility to display cookies in buffers
+
+;; Copyright (C) 1991-1995   Free Software Foundation
+
+;; Author: Per Cederqvist <ceder@lysator.liu.se>
+;;	Inge Wallin <inge@lysator.liu.se>
+;; Maintainer: elib-maintainers@lysator.liu.se
+;; Created: 3 Aug 1992
+;; Keywords: extensions, lisp
+
+;;; This program 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 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; This program 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 GNU Elib; see the file COPYING.  If not, write to
+;;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;;; Boston, MA 02111-1307, USA
+;;;
+
+
+;;; Commentary:
+
+;;;     Introduction
+;;;     ============
+;;;
+;;; Cookie is a package that implements a connection between an
+;;; dll (a doubly linked list) and the contents of a buffer.
+;;; Possible uses are dired (have all files in a list, and show them),
+;;; buffer-list, kom-prioritize (in the LysKOM elisp client) and
+;;; others.  pcl-cvs.el uses cookie.el.
+;;;
+;;; A `cookie' can be any lisp object.  When you use the cookie
+;;; package you specify a pretty-printer, a function that inserts
+;;; a printable representation of the cookie in the buffer.  (The
+;;; pretty-printer should use "insert" and not
+;;; "insert-before-markers").
+;;;
+;;; A `collection' consists of a doubly linked list of cookies, a
+;;; header, a footer and a pretty-printer.  It is displayed at a
+;;; certain point in a certain buffer.  (The buffer and point are
+;;; fixed when the collection is created).  The header and the footer
+;;; are constant strings.  They appear before and after the cookies.
+;;; (Currently, once set, they can not be changed).
+;;;
+;;; Cookie does not affect the mode of the buffer in any way. It
+;;; merely makes it easy to connect an underlying data representation
+;;; to the buffer contents.
+;;;
+;;; A `tin' is an object that contains one cookie.  There are
+;;; functions in this package that given a tin extracts the cookie, or
+;;; gives the next or previous tin.  (All tins are linked together in
+;;; a doubly linked list.  The 'previous' tin is the one that appears
+;;; before the other in the buffer.)  You should not do anything with
+;;; a tin except pass it to the functions in this package.
+;;;
+;;; A collection is a very dynamic thing.  You can easily add or
+;;; delete cookies.  You can sort all cookies in a collection (you
+;;; have to supply a function that compares two cookies).  You can
+;;; apply a function to all cookies in a collection, et c, et c.
+;;;
+;;; Remember that a cookie can be anything.  Your imagination is the
+;;; limit!  It is even possible to have another collection as a
+;;; cookie.  In that way some kind of tree hierarchy can be created.
+;;;
+;;; Full documentation will, God willing, soon be available in a
+;;; Texinfo manual.
+
+
+
+;;;     Coding conventions
+;;;     ==================
+;;;
+;;; All functions that are intended for external use begin with one of
+;;; the prefixes "cookie-", "collection-" or "tin-".  The prefix
+;;; "elib-" is used for internal functions and macros.  There are
+;;; currently no global or buffer-local variables used.
+;;;
+;;; Many function operate on `tins' instead of `cookies'.  To avoid
+;;; confusion most of the function names include the string "cookie"
+;;; or "tin" to show this.
+;;;
+;;; Most doc-strings contains an "Args:" line that lists the
+;;; arguments.
+;;;
+;;; The internal functions don't contain any doc-strings.  RMS thinks
+;;; this is a good way to save space.
+
+
+
+;;; INTERNAL DOCUMENTATION (Your understanding of this package might
+;;; increase if you read it, but you should not exploit the knowledge
+;;; you gain. The internal details might change without notice).
+;;;
+;;; A collection is implemented as an dll (a doubly linked list).
+;;; The first and last element on the list are always the header and
+;;; footer (as strings). Any remaining entries are `wrappers'.
+;;;
+;;; At the implementation level a `tin' is really an elib-node that
+;;; consists of
+;;;      left        Pointer to previous tin
+;;;      right       Pointer to next tin
+;;;      data        Holder of a `wrapper'.
+;;; These internals of an elib-node are in fact unknown to cookie.el.
+;;; It uses dll.el to handle everything that deals with the
+;;; doubly linked list.
+;;;
+;;; The wrapper data type contains
+;;;      start-marker    Position of the printed representation of the
+;;;                      cookie in the buffer. 
+;;;      cookie          The user-supplied cookie.
+;;;
+;;; The wrapper is not accessible to the user of this package.
+
+;;; Code:
+
+(require 'dll)
+(provide 'cookie)
+
+
+;;; ================================================================
+;;;      Internal   macros   for use in the cookie package
+
+
+(put 'elib-set-buffer-bind-dll 'lisp-indent-hook 1)
+
+(defmacro elib-set-buffer-bind-dll (collection &rest forms)
+
+  ;; Execute FORMS with collection->buffer selected as current buffer,
+  ;; and dll bound to collection->dll.
+  ;; Return value of last form in FORMS.  INTERNAL USE ONLY.
+
+  (let ((old-buffer (make-symbol "old-buffer"))
+	(hnd (make-symbol "collection")))
+    (` (let* (((, old-buffer) (current-buffer))
+	      ((, hnd) (, collection))
+	      (dll (elib-collection->dll (, hnd))))
+	 (set-buffer (elib-collection->buffer (, hnd)))
+	 (unwind-protect
+	     (progn (,@ forms))
+	   (set-buffer (, old-buffer)))))))
+
+
+(put 'elib-set-buffer-bind-dll-let* 'lisp-indent-hook 2)
+
+(defmacro elib-set-buffer-bind-dll-let* (collection varlist &rest forms)
+
+  ;; Execute FORMS with collection->buffer selected as current buffer,
+  ;; dll bound to collection->dll, and VARLIST bound as in a let*.
+  ;; dll will be bound when VARLIST is initialized, but the current
+  ;; buffer will *not* have been changed.
+  ;; Return value of last form in FORMS.  INTERNAL USE ONLY.
+
+  (let ((old-buffer (make-symbol "old-buffer"))
+	(hnd (make-symbol "collection")))
+    (` (let* (((, old-buffer) (current-buffer))
+	      ((, hnd) (, collection))
+	      (dll (elib-collection->dll (, hnd)))
+	      (,@ varlist))
+	 (set-buffer (elib-collection->buffer (, hnd)))
+	 (unwind-protect
+	     (progn (,@ forms))
+	   (set-buffer (, old-buffer)))))))
+
+
+(defmacro elib-filter-hf (collection tin)
+
+  ;; Evaluate TIN once and return it. BUT if it is
+  ;; the header or the footer in COLLECTION return nil instead.
+  ;; Args: COLLECTION TIN
+  ;; INTERNAL USE ONLY.
+
+  (let ((tempvar (make-symbol "tin"))
+	(tmpcoll (make-symbol "tmpcollection")))
+    (` (let (((, tempvar) (, tin))
+	     ((, tmpcoll) (, collection)))
+	 (if (or (eq (, tempvar) (elib-collection->header (, tmpcoll)))
+		 (eq (, tempvar) (elib-collection->footer (, tmpcoll))))
+	     nil
+	   (, tempvar))))))
+
+
+
+;;; ================================================================
+;;;      Internal   data types   for use in the cookie package
+
+;;; Yes, I know about cl.el, but I don't like it.   /ceder
+
+;;; The wrapper data type.
+
+(defun elib-create-wrapper (start-marker cookie)
+  ;; Create a wrapper.   INTERNAL USE ONLY.
+  (cons 'WRAPPER (vector start-marker cookie)))
+
+(defun elib-wrapper->start-marker (wrapper)
+  ;; Get start-marker from wrapper.    INTERNAL USE ONLY.
+  (elt (cdr wrapper) 0))
+
+(defun elib-wrapper->cookie-safe (wrapper)
+  ;; Get cookie from wrapper.   INTERNAL USE ONLY.
+  ;; Returns nil if given nil as input.
+  ;; Since (elt nil 1) returns nil in emacs version 18.57 and 18.58
+  ;; this can be defined in this way. The documentation in the info
+  ;; file says that elt should signal an error in that case. I think
+  ;; it is the documentation that is buggy. (The bug is reported).
+  (elt (cdr wrapper) 1))
+
+(defun elib-wrapper->cookie (wrapper)
+  ;; Get cookie from wrapper.   INTERNAL USE ONLY.
+  (elt (cdr wrapper) 1))
+
+
+
+;;; The collection data type
+
+(defun elib-create-collection (buffer pretty-printer 
+					 header-wrapper footer-wrapper
+					 dll)
+  ;; Create a collection. INTERNAL USE ONLY.
+  (cons 'COLLECTION
+	;; The last element is a pointer to the last tin
+	;; the cursor was at, or nil if that is unknown.  
+	(vector buffer
+		pretty-printer 
+		header-wrapper footer-wrapper
+		dll nil)))
+
+
+(defun elib-collection->buffer (collection)
+  ;; Get buffer from COLLECTION.
+  (elt (cdr collection) 0))
+
+(defun elib-collection->pretty-printer (collection)
+  ;; Get pretty-printer from COLLECTION.
+  (elt (cdr collection) 1))
+
+(defun elib-collection->header (collection)
+  ;; Get header from COLLECTION.
+  (elt (cdr collection) 2))
+
+(defun elib-collection->footer (collection)
+  ;; Get footer from COLLECTION.
+  (elt (cdr collection) 3))
+
+(defun elib-collection->dll (collection)
+  ;; Get dll from COLLECTION.
+  (elt (cdr collection) 4))
+
+(defun elib-collection->last-tin (collection)
+  ;; Get last-tin from COLLECTION.
+  (elt (cdr collection) 5))
+
+
+
+(defun elib-set-collection->buffer (collection buffer)
+  ;; Change the buffer. Args: COLLECTION BUFFER.
+  (aset (cdr collection) 0 buffer))
+
+(defun elib-set-collection->pretty-printer (collection pretty-printer)
+  ;; Change the pretty-printer. Args: COLLECTION PRETTY-PRINTER.
+  (aset (cdr collection) 1 pretty-printer))
+
+(defun elib-set-collection->header (collection header)
+  ;; Change the header. Args: COLLECTION HEADER.
+  (aset (cdr collection) 2 header))
+
+(defun elib-set-collection->footer (collection footer)
+  ;; Change the footer. Args: COLLECTION FOOTER.
+  (aset (cdr collection) 3 footer))
+
+(defun elib-set-collection->dll (collection dll)
+  ;; Change the dll. Args: COLLECTION DLL.
+  (aset (cdr collection) 4 dll))
+
+(defun elib-set-collection->last-tin (collection last-tin)
+  ;; Change the last-tin. Args: COLLECTION LAST-TIN.
+  (aset (cdr collection) 5 last-tin))
+
+
+;;; ================================================================
+;;;      Internal   functions   for use in the cookie package
+
+(defun elib-abs (x)
+  ;; Return the absolute value of x
+  (max x (- x)))
+
+(defun elib-create-wrapper-and-insert (cookie string pos)
+  ;; Insert STRING at POS in current buffer. Remember the start
+  ;; position. Create a wrapper containing that start position and the
+  ;; COOKIE.
+  ;;    INTERNAL USE ONLY.
+
+  (save-excursion
+    (goto-char pos)
+    ;; Remember the position as a number so that it doesn't move
+    ;; when we insert the string.
+    (let ((start (if (markerp pos)
+		     (marker-position pos)
+		   pos))
+	  (buffer-read-only nil))
+      ;; Use insert-before-markers so that the marker for the
+      ;; next cookie is updated.
+      (insert-before-markers string)
+
+      ;; Always insert a newline. You want invisible cookies? You
+      ;; lose. (At least in this version). FIXME-someday. (It is
+      ;; harder to fix than it might seem. All markers have to point
+      ;; to the right place all the time...)
+      (insert-before-markers ?\n)
+      (elib-create-wrapper (copy-marker start) cookie))))
+
+
+(defun elib-create-wrapper-and-pretty-print (cookie
+						pretty-printer pos)
+  ;; Call PRETTY-PRINTER with point set at POS in current buffer.
+  ;; Remember the start position. Create a wrapper containing that
+  ;; start position and the COOKIE.
+  ;;    INTERNAL USE ONLY.
+
+  (save-excursion
+    (goto-char pos)
+    ;; Remember the position as a number so that it doesn't move
+    ;; when we insert the string.
+    (let ((start (if (markerp pos)
+		     (marker-position pos)
+		   pos))
+	  (buffer-read-only nil))
+      ;; Insert the trailing newline using insert-before-markers
+      ;; so that the start position for the next cookie is updated.
+      (insert-before-markers ?\n)
+      ;; Move back, and call the pretty-printer.
+      (backward-char 1)
+      (funcall pretty-printer cookie)
+      (elib-create-wrapper (copy-marker start) cookie))))
+
+
+(defun elib-delete-tin-internal (collection tin)
+  ;; Delete a cookie string from COLLECTION.  INTERNAL USE ONLY.
+  ;; Can not be used on the footer. Returns the wrapper that is deleted.
+  ;; The start-marker in the wrapper is set to nil, so that it doesn't
+  ;; consume any more resources.
+  (let ((dll (elib-collection->dll collection))
+	(buffer-read-only nil))
+    ;; If we are about to delete the tin pointed at by last-tin,
+    ;; set last-tin to nil.
+    (if (eq (elib-collection->last-tin collection) tin)
+	(elib-set-collection->last-tin collection nil))
+
+    (delete-region (elib-wrapper->start-marker (dll-element dll tin))
+		   (elib-wrapper->start-marker
+		    (dll-element dll (dll-next dll tin))))
+    (set-marker (elib-wrapper->start-marker (dll-element dll tin)) nil)
+    ;; Delete the tin, and return the wrapper.
+    (dll-delete dll tin)))
+
+(defun elib-refresh-tin (collection tin)
+  ;; Redisplay the cookie represented by TIN. INTERNAL USE ONLY.
+  ;; Args: COLLECTION TIN
+  ;; Can not be used on the footer. dll *must* be bound to
+  ;; (elib-collection->dll collection).
+
+  (let ((buffer-read-only nil))
+    (save-excursion
+      ;; First, remove the string from the buffer:
+      (delete-region (elib-wrapper->start-marker (dll-element dll tin))
+		     (1- (marker-position
+			  (elib-wrapper->start-marker
+			   (dll-element dll (dll-next dll tin))))))
+
+      ;; Calculate and insert the string.
+
+      (goto-char (elib-wrapper->start-marker (dll-element dll tin)))
+      (funcall (elib-collection->pretty-printer collection)
+	       (elib-wrapper->cookie (dll-element dll tin))))))
+
+
+(defun elib-pos-before-middle-p (collection pos tin1 tin2)
+
+  ;; Return true if for the cookies in COLLECTION, POS is in the first
+  ;; half of the region defined by TIN1 and TIN2.
+
+  (let ((dll (elib-collection->dll collection)))
+    (< pos (/ (+ (elib-wrapper->start-marker (dll-element dll tin1))
+		 (elib-wrapper->start-marker (dll-element dll tin2)))
+	      2))))
+
+
+;;; ===========================================================================
+;;;                  Public members of the cookie package
+
+
+(defun collection-create (buffer pretty-printer 
+			     &optional header footer pos)
+  "Create an empty collection of cookies.
+Args: BUFFER PRETTY-PRINTER &optional HEADER FOOTER POS.
+
+The collection will be inserted in BUFFER. BUFFER may be a
+buffer or a buffer name. It is created if it does not exist.
+
+PRETTY-PRINTER should be a function that takes one argument, a
+cookie, and inserts a string representing it in the buffer (at
+point). The string PRETTY-PRINTER inserts may be empty or span
+several linse. A trailing newline will always be inserted
+automatically. The PRETTY-PRINTER should use insert, and not
+insert-before-markers.
+
+Optional third argument HEADER is a string that will always be
+present at the top of the collection. HEADER should end with a
+newline.  Optionaly fourth argument FOOTER is similar, and will
+always be inserted at the bottom of the collection.
+
+Optional fifth argument POS is a buffer position, specifying
+where the collection will be inserted. It defaults to the
+begining of the buffer."
+
+  (let ((new-collection
+	 (elib-create-collection (get-buffer-create buffer)
+				    pretty-printer nil nil (dll-create))))
+
+    (elib-set-buffer-bind-dll new-collection
+      ;; Set default values
+      (if (not header)
+	  (setq header ""))
+      (if (not footer)
+	  (setq footer ""))
+      (if (not pos)
+	  (setq pos (point-min))
+	(if (markerp pos)
+	    (set pos (marker-position pos)))) ;Force header to be above footer.
+
+      (let ((foot (elib-create-wrapper-and-insert footer footer pos))
+	    (head (elib-create-wrapper-and-insert header header pos)))
+
+	(dll-enter-first dll head)
+	(dll-enter-last  dll foot)
+	(elib-set-collection->header new-collection (dll-nth dll 0))
+	(elib-set-collection->footer new-collection (dll-nth dll -1))))
+
+    ;; Return the collection
+    new-collection))
+
+
+(defun tin-cookie (collection tin)
+  "Get the cookie from a TIN. Args: COLLECTION TIN."
+  (elib-wrapper->cookie (dll-element (cookie->dll collection) tin)))
+
+(defun cookie-enter-first (collection cookie)
+  "Enter a COOKIE first in the cookie collection COLLECTION.
+Args: COLLECTION COOKIE."
+
+  (elib-set-buffer-bind-dll collection
+
+    ;; It is always safe to insert an element after the first element,
+    ;; because the header is always present. (dll-nth dll 0) should
+    ;; therefore never return nil.
+
+    (dll-enter-after
+     dll
+     (dll-nth dll 0)
+     (elib-create-wrapper-and-pretty-print
+      cookie
+      (elib-collection->pretty-printer collection)
+      (elib-wrapper->start-marker
+       (dll-element dll (dll-nth dll 1)))))))
+
+
+(defun cookie-enter-last (collection cookie)
+  "Enter a COOKIE last in the cookie-collection COLLECTION.
+Args: COLLECTION COOKIE."
+
+  (elib-set-buffer-bind-dll collection
+
+    ;; Remember that the header and footer are always present. There
+    ;; is no need to check if (dll-nth dll -1) returns nil - it never
+    ;; does.
+
+    (dll-enter-before
+     dll
+     (dll-nth dll -1)
+     (elib-create-wrapper-and-pretty-print
+      cookie
+      (elib-collection->pretty-printer collection)
+      (elib-wrapper->start-marker (dll-last dll))))))
+
+
+(defun cookie-enter-after-tin (collection tin cookie)
+  "Enter a new COOKIE after TIN.
+Args: COLLECTION TIN COOKIE."
+  (elib-set-buffer-bind-dll collection
+    (dll-enter-after
+     dll tin
+     (elib-create-wrapper-and-pretty-print
+      cookie
+      (elib-collection->pretty-printer collection)
+      (elib-wrapper->start-marker (dll-element dll (dll-next dll tin)))))))
+
+
+(defun cookie-enter-before-tin (collection tin cookie)
+  "Enter a new COOKIE before TIN.
+Args: COLLECTION TIN COOKIE."
+  (elib-set-buffer-bind-dll collection
+    (dll-enter-before
+     dll tin
+     (elib-create-wrapper-and-pretty-print
+      cookie
+      (elib-collection->pretty-printer collection)
+      (elib-wrapper->start-marker (dll-element dll tin))))))
+
+
+(defun tin-next (collection tin)
+  "Get the next tin. Args: COLLECTION TIN.
+Returns nil if TIN is nil or the last cookie."
+  (if tin
+      (elib-filter-hf
+       collection (dll-next (elib-collection->dll collection) tin))
+    nil))
+
+(defun tin-previous (collection tin)
+  "Get the previous tin. Args: COLLECTION TIN.
+Returns nil if TIN is nil or the first cookie."
+  (if tin
+      (elib-filter-hf
+       collection
+       (dll-previous (elib-collection->dll collection) tin))
+    nil))
+
+
+(defun tin-nth (collection n)
+  "Return the Nth tin. Args: COLLECTION N.
+N counts from zero. Nil is returned if there is less than N cookies.
+If N is negative, return the -(N+1)th last element.
+Thus, (tin-nth dll 0) returns the first node,
+and (tin-nth dll -1) returns the last node.
+
+Use tin-cookie to extract the cookie from the tin (or use
+cookie-nth instead)."
+
+    ;; Skip the header (or footer, if n is negative).
+    (if (< n 0)
+	(setq n (1- n))
+      (setq n (1+ n)))
+
+    (elib-filter-hf collection
+		       (dll-nth (elib-collection->dll collection) n)))
+
+(defun cookie-nth (collection n)
+  "Return the Nth cookie. Args: COLLECTION N.
+N counts from zero. Nil is returned if there is less than N cookies.
+If N is negative, return the -(N+1)th last element.
+Thus, (cookie-nth dll 0) returns the first cookie,
+and (cookie-nth dll -1) returns the last cookie."
+
+    ;; Skip the header (or footer, if n is negative).
+    (if (< n 0)
+	(setq n (1- n))
+      (setq n (1+ n)))
+
+    (let* ((dll (elib-collection->dll collection))
+	   (tin (elib-filter-hf collection (dll-nth dll n))))
+      (if tin
+	  (elib-wrapper->cookie (dll-element dll tin))
+	nil)))
+
+(defun tin-delete (collection tin)
+  "Delete a tin from a collection. Args: COLLECTION TIN.
+The cookie in the tin is returned."
+
+  (elib-set-buffer-bind-dll collection
+    (elib-wrapper->cookie
+     (elib-delete-tin-internal collection tin))))
+
+
+(defun cookie-delete-first (collection)
+  "Delete first cookie and return it. Args: COLLECTION.
+Returns nil if there are no cookies left in the collection."
+
+  (elib-set-buffer-bind-dll-let* collection
+      ((tin (dll-nth dll 1)))         ;Skip the header.
+
+    ;; We have to check that we do not try to delete the footer.
+    (if (eq tin (elib-collection->footer collection))
+	nil
+      (elib-wrapper->cookie (elib-delete-tin-internal collection tin)))))
+
+
+(defun cookie-delete-last (collection)
+  "Delete last cookie and return it. Args: COLLECTION.
+Returns nil if there is no cookie left in the collection."
+
+  (elib-set-buffer-bind-dll-let* collection
+      ((tin (dll-nth dll -2)))		;Skip the footer.
+    ;; We have to check that we do not try to delete the header.
+    (if (eq tin (elib-collection->header collection))
+	nil
+      (elib-wrapper->cookie (elib-delete-tin-internal collection tin)))))
+
+(defun cookie-first (collection)
+  "Return the first cookie in COLLECTION. The cookie is not removed."
+
+  (let* ((dll (elib-collection->dll collection))
+	 (tin (elib-filter-hf collection (dll-nth dll -1))))
+    (if tin
+	(elib-wrapper->cookie (dll-element dll tin)))))
+
+
+
+(defun cookie-last (collection)
+  "Return the last cookie in COLLECTION. The cookie is not removed."
+
+  (let* ((dll (elib-collection->dll collection))
+	 (tin (elib-filter-hf collection (dll-nth dll -2))))
+      (if tin
+	  (elib-wrapper->cookie (dll-element dll tin)))))
+
+
+(defun collection-empty (collection)
+  "Return true if there are no cookies in COLLECTION."
+
+  (eq (dll-nth (elib-collection->dll collection) 1) 
+      (elib-collection->footer collection)))
+
+
+(defun collection-length (collection)
+  "Return the number of cookies in COLLECTION."
+
+  ;; Don't count the footer and header.
+  (- (dll-length (elib-collection->dll collection)) 2))
+
+
+(defun collection-list-cookies (collection)
+  "Return a list of all cookies in COLLECTION."
+
+  (elib-set-buffer-bind-dll-let* collection
+      ((result nil)
+       (header (elib-collection->header collection))
+       (tin (dll-nth dll -2)))
+    (while (not (eq tin header))
+      (setq result (cons (elib-wrapper->cookie (dll-element dll tin))
+			 result))
+      (setq tin (dll-previous dll tin)))
+    result))
+
+
+(defun collection-clear (collection)
+  "Remove all cookies in COLLECTION."
+
+  (elib-set-buffer-bind-dll-let* collection
+      ((header (elib-collection->header collection))
+       (footer (elib-collection->footer collection)))
+
+    ;; We have to bind buffer-read-only separately, so that the
+    ;; current buffer is correct.
+    (let ((buffer-read-only nil))
+      (delete-region (elib-wrapper->start-marker
+		      (dll-element dll (dll-nth dll 1)))
+		     (elib-wrapper->start-marker
+		      (dll-element dll footer))))
+    (setq dll (dll-create-from-list (list (dll-element dll header)
+					  (dll-element dll footer))))
+    (elib-set-collection->dll collection dll)
+
+    ;; Re-set the header and footer, since they are now new objects.
+    ;; elib-filter-hf uses eq to compare objects to them...
+    (elib-set-collection->header collection (dll-nth dll 0))
+    (elib-set-collection->footer collection (dll-nth dll -1))))
+
+
+(defun cookie-map (map-function collection &rest map-args)
+  "Apply MAP-FUNCTION to all cookies in COLLECTION.
+MAP-FUNCTION is applied to the first element first.
+If MAP-FUNCTION returns non-nil the cookie will be refreshed (its
+pretty-printer will be called once again).
+
+Note that the buffer for COLLECTION will be current buffer when MAP-FUNCTION 
+is called.  MAP-FUNCTION must restore the current buffer to BUFFER before 
+it returns, if it changes it.
+
+If more than two arguments are given to cookie-map, remaining
+arguments will be passed to MAP-FUNCTION."
+
+  (elib-set-buffer-bind-dll-let* collection
+      ((footer (elib-collection->footer collection))
+       (tin (dll-nth dll 1)))
+
+    (while (not (eq tin footer))
+
+      (if (apply map-function
+		 (elib-wrapper->cookie (dll-element dll tin))
+		 map-args)
+	  (elib-refresh-tin collection tin))
+
+      (setq tin (dll-next dll tin)))))
+
+
+
+(defun cookie-map-reverse (map-function collection &rest map-args)
+  "Apply MAP-FUNCTION to all cookies in COLLECTION.
+MAP-FUNCTION is applied to the last cookie first.
+If MAP-FUNCTION returns non-nil the cookie will be refreshed.
+
+Note that the buffer for COLLECTION will be current buffer when MAP-FUNCTION 
+is called.  MAP-FUNCTION must restore the current buffer to BUFFER before 
+it returns, if it changes the current buffer.
+
+If more than two arguments are given to cookie-map, remaining
+arguments will be passed to MAP-FUNCTION."
+
+  (elib-set-buffer-bind-dll-let* collection
+      ((header (elib-collection->header collection))
+       (tin (dll-nth dll -2)))
+
+    (while (not (eq tin header))
+
+      (if (apply map-function
+		 (elib-wrapper->cookie (dll-element dll tin))
+		 map-args)
+	  (elib-refresh-tin collection tin))
+
+      (setq tin (dll-previous dll tin)))))
+
+
+
+(defun collection-append-cookies (collection cookie-list)
+  "Insert all cookies in the list COOKIE-LIST last in COLLECTION.
+Args: COLLECTION COOKIE-LIST."
+
+  (while cookie-list
+    (cookie-enter-last collection (car cookie-list))
+    (setq cookie-list (cdr cookie-list))))
+
+
+(defun collection-filter-cookies (collection predicate &rest extra-args)
+  "Remove all cookies in COLLECTION for which PREDICATE returns nil.
+Args: COLLECTION PREDICATE &rest EXTRA-ARGS.
+Note that the buffer for COLLECTION will be current-buffer when PREDICATE 
+is called. PREDICATE must restore the current buffer before it returns
+if it changes it.
+
+The PREDICATE is called with the cookie as its first argument. If any
+EXTRA-ARGS are given to collection-filter-cookies they will be passed to the
+PREDICATE."
+
+  (elib-set-buffer-bind-dll-let* collection
+      ((tin (dll-nth dll 1))
+       (footer (elib-collection->footer collection))
+       (next nil))
+    (while (not (eq tin footer))
+      (setq next (dll-next dll tin))
+      (if (apply predicate
+		 (elib-wrapper->cookie (dll-element dll tin))
+		 extra-args)
+	  nil
+	(elib-delete-tin-internal collection tin))
+      (setq tin next))))
+
+
+(defun collection-filter-tins (collection predicate &rest extra-args)
+  "Remove all cookies in COLLECTION for which PREDICATE returns nil.
+Note that the buffer for COLLECTION will be current-buffer when PREDICATE 
+is called. PREDICATE must restore the current buffer before it returns
+if it changes it.
+
+The PREDICATE is called with one argument, the tin. If any EXTRA-ARGS
+are given to collection-filter-cookies they will be passed to the PREDICATE."
+
+  (elib-set-buffer-bind-dll-let* collection
+      ((tin (dll-nth dll 1))
+       (footer (elib-collection->footer collection))
+       (next nil))
+    (while (not (eq tin footer))
+      (setq next (dll-next dll tin))
+      (if (apply predicate tin extra-args)
+	  nil
+	(elib-delete-tin-internal collection tin))
+      (setq tin next))))
+
+
+(defun tin-locate (collection pos &optional guess)
+  "Return the tin that POS (a buffer position) is within.
+Args: COLLECTION POS &optional GUESS.
+POS may be a marker or an integer.
+GUESS should be a tin that it is likely that POS is near.
+
+If POS points before the first cookie, the first cookie is returned.
+If POS points after the last cookie, the last cookie is returned.
+If the COLLECTION is empty, nil is returned."
+
+  (elib-set-buffer-bind-dll-let* collection
+      ((footer (elib-collection->footer collection)))
+
+    (cond
+     ;; No cookies present?
+     ((eq (dll-nth dll 1) (dll-nth dll -1))
+      nil)
+
+     ;; Before first cookie?
+     ((< pos (elib-wrapper->start-marker
+	      (dll-element dll (dll-nth dll 1))))
+      (dll-nth dll 1))
+
+     ;; After last cookie?
+     ((>= pos (elib-wrapper->start-marker (dll-last dll)))
+      (dll-nth dll -2))
+
+     ;; We now now that pos is within a cookie.
+     (t
+      ;; Make an educated guess about which of the three known
+      ;; cookies (the first, the last, or GUESS) is nearest.
+      (let* ((best-guess (dll-nth dll 1))
+	     (distance (elib-abs (- pos (elib-wrapper->start-marker
+					 (dll-element dll best-guess))))))
+	(if guess
+	    (let* ((g guess)		;Check the guess, if given.
+		   (d (elib-abs
+		       (- pos (elib-wrapper->start-marker
+			       (dll-element dll g))))))
+	      (cond
+	       ((< d distance)
+		(setq distance d)
+		(setq best-guess g)))))
+
+	(let* ((g (dll-nth dll -1))	;Check the last cookie
+	       (d (elib-abs
+		   (- pos (elib-wrapper->start-marker
+			   (dll-element dll g))))))
+	  (cond
+	   ((< d distance)
+	    (setq distance d)
+	    (setq best-guess g))))
+
+	(if (elib-collection->last-tin collection) ;Check "previous".
+	    (let* ((g (elib-collection->last-tin collection)) 
+		   (d (elib-abs
+		       (- pos (elib-wrapper->start-marker
+			       (dll-element dll g))))))
+	      (cond
+	       ((< d distance)
+		(setq distance d)
+		(setq best-guess g)))))
+
+	;; best-guess is now a "best guess".
+     
+	;; Find the correct cookie. First determine in which direction
+	;; it lies, and then move in that direction until it is found.
+    
+	(cond
+	 ;; Is pos after the guess?
+	 ((>= pos
+	      (elib-wrapper->start-marker (dll-element dll best-guess)))
+
+	  ;; Loop until we are exactly one cookie too far down...
+	  (while (>= pos (elib-wrapper->start-marker
+			  (dll-element dll best-guess)))
+	    (setq best-guess (dll-next dll best-guess)))
+
+	  ;; ...and return the previous cookie.
+	  (dll-previous dll best-guess))
+
+	 ;; Pos is before best-guess
+	 (t
+
+	  (while (< pos (elib-wrapper->start-marker
+			 (dll-element dll best-guess)))
+	    (setq best-guess (dll-previous dll best-guess)))
+
+	  best-guess)))))))
+
+
+;;(defun tin-start-marker (collection tin)
+;;  "Return start-position of a cookie in COLLECTION.
+;;Args: COLLECTION TIN.
+;;The marker that is returned should not be modified in any way,
+;;and is only valid until the contents of the cookie buffer changes."
+;;
+;;  (elib-wrapper->start-marker 
+;;   (dll-element (elib-collection->dll collection) tin)))
+
+
+;;(defun tin-end-marker (collection tin)
+;;  "Return end-position of a cookie in COLLECTION.
+;;Args: COLLECTION TIN.
+;;The marker that is returned should not be modified in any way,
+;;and is only valid until the contents of the cookie buffer changes."
+;;
+;;  (let ((dll (elib-collection->dll collection)))
+;;    (elib-wrapper->start-marker
+;;     (dll-element dll (dll-next dll tin)))))
+
+
+
+(defun collection-refresh (collection)
+  "Refresh all cookies in COLLECTION.
+
+The pretty-printer that was specified when the COLLECTION was created
+will be called for all cookies in COLLECTION.
+
+Note that tin-invalidate is more efficient if only a small
+number of cookies needs to be refreshed."
+
+  (elib-set-buffer-bind-dll-let* collection
+
+      ((header (elib-collection->header collection))
+       (footer (elib-collection->footer collection)))
+
+    (let ((buffer-read-only nil))
+      (delete-region (elib-wrapper->start-marker
+		      (dll-element dll (dll-nth dll 1)))
+		     (elib-wrapper->start-marker
+		      (dll-element dll footer)))
+
+      (goto-char (elib-wrapper->start-marker
+		  (dll-element dll footer)))
+    
+      (let ((tin (dll-nth dll 1)))
+	(while (not (eq tin footer))
+
+	  (set-marker (elib-wrapper->start-marker (dll-element dll tin))
+		      (point))
+	  (funcall (elib-collection->pretty-printer collection)
+		   (elib-wrapper->cookie (dll-element dll tin)))
+	  (insert "\n")
+	  (setq tin (dll-next dll tin)))))
+    
+    (set-marker (elib-wrapper->start-marker (dll-element dll footer))
+		(point))))
+
+
+(defun tin-invalidate (collection &rest tins)
+  "Refresh some cookies. Args: COLLECTION &rest TINS.
+The pretty-printer that for COLLECTION will be called for all TINS."
+
+  (elib-set-buffer-bind-dll collection
+    
+    (while tins
+      (elib-refresh-tin collection (car tins))
+      (setq tins (cdr tins)))))
+
+
+(defun collection-set-goal-column (collection goal)
+  "Set goal-column for COLLECTION.
+Args: COLLECTION GOAL.
+goal-column is made buffer-local.
+
+There will eventually be a better way to specify the cursor position."
+  (elib-set-buffer-bind-dll collection 
+    (make-local-variable 'goal-column)
+    (setq goal-column goal)))
+
+
+(defun tin-goto-previous (collection pos arg)
+  "Move point to the ARGth previous cookie.
+Don't move if we are at the first cookie, or if COLLECTION is empty.
+Args: COLLECTION POS ARG.
+Returns the tin we move to."
+
+  (elib-set-buffer-bind-dll-let* collection
+      ((tin (tin-locate
+	     collection pos (elib-collection->last-tin collection))))
+
+    (cond
+     (tin
+      (while (and tin (> arg 0))
+	(setq arg (1- arg))
+	(setq tin (dll-previous dll tin)))
+
+      ;; Never step above the first cookie.
+
+      (if (null (elib-filter-hf collection tin))
+	  (setq tin (dll-nth dll 1)))
+
+      (goto-char
+       (elib-wrapper->start-marker
+	(dll-element dll tin)))
+
+      (if goal-column
+	  (move-to-column goal-column))
+      (elib-set-collection->last-tin collection tin)
+      tin))))
+
+
+(defun tin-goto-next (collection pos arg)
+  "Move point to the ARGth next cookie.
+Don't move if we are at the last cookie.
+Args: COLLECTION POS ARG.
+Returns the tin."
+
+  ;;Need to do something clever with (current-buffer)...
+  ;;Previously, when the buffer was used instead of the collection, this line
+  ;;did the trick. No longer so... This is hard to do right! Remember that a
+  ;;cookie can contain a collection!
+  ;;(interactive (list (current-buffer) (point)
+  ;;		     (prefix-numeric-value current-prefix-arg)))
+
+  (elib-set-buffer-bind-dll-let* collection
+      ((tin (tin-locate
+	     collection pos (elib-collection->last-tin collection))))
+
+    (while (and tin (> arg 0))
+      (setq arg (1- arg))
+      (setq tin (dll-next dll tin)))
+
+    ;; Never step below the first cookie.
+
+    (if (null (elib-filter-hf collection tin))
+	(setq tin (dll-nth dll -2)))
+
+    (goto-char
+     (elib-wrapper->start-marker
+      (dll-element dll tin)))
+
+    (if goal-column
+	(move-to-column goal-column))
+
+    (elib-set-collection->last-tin collection tin)
+    tin))
+
+
+(defun tin-goto (collection tin)
+  "Move point to TIN.  Args: COLLECTION TIN."
+  (elib-set-buffer-bind-dll collection
+    (goto-char
+     (elib-wrapper->start-marker
+      (dll-element dll tin)))
+
+    (if goal-column
+	(move-to-column goal-column))
+
+    (elib-set-collection->last-tin collection tin)))
+
+
+(defun collection-collect-tin (collection predicate &rest predicate-args)
+  "Select cookies from COLLECTION using PREDICATE.
+Return a list of all selected tins.
+
+PREDICATE is a function that takes a cookie as its first argument.
+
+The tins on the returned list will appear in the same order as in the
+buffer.  You should not rely on in which order PREDICATE is called.
+
+Note that the buffer the COLLECTION is displayed in is current-buffer
+when PREDICATE is called.  If PREDICATE must restore current-buffer if
+it changes it.
+
+If more than two arguments are given to collection-collect-tin the remaining
+arguments will be passed to PREDICATE."
+
+  (elib-set-buffer-bind-dll-let* collection
+      ((header (elib-collection->header collection))
+       (tin (dll-nth dll -2))
+       (result nil))
+
+    ;; Collect the tins, starting at the last one, so that they
+    ;; appear in the correct order in the result (which is cons'ed
+    ;; together).
+
+    (while (not (eq tin header))
+
+      (if (apply predicate
+		 (elib-wrapper->cookie (dll-element dll tin))
+		 predicate-args)
+	  (setq result (cons tin result)))
+
+      (setq tin (dll-previous dll tin)))
+    result))
+
+
+(defun collection-collect-cookie (collection predicate &rest predicate-args)
+  "Select cookies from COLLECTION using PREDICATE.
+Return a list of all selected cookies.
+
+PREDICATE is a function that takes a cookie as its first argument.
+
+The cookies on the returned list will appear in the same order as in
+the buffer.  You should not rely on in which order PREDICATE is
+called.
+
+Note that the buffer the COLLECTION is displayed in is current-buffer
+when PREDICATE is called.  If PREDICATE must restore current-buffer if
+it changes it.
+
+If more than two arguments are given to collection-collect-cookie the
+remaining arguments will be passed to PREDICATE."
+
+  (elib-set-buffer-bind-dll-let* collection
+      ((header (elib-collection->header collection))
+       (tin (dll-nth dll -2))
+       result)
+
+    (while (not (eq tin header))
+
+      (if (apply predicate
+		 (elib-wrapper->cookie (dll-element dll tin))
+		 predicate-args)
+	  (setq result (cons (elib-wrapper->cookie (dll-element dll tin))
+			     result)))
+
+      (setq tin (dll-previous dll tin)))
+    result))
+
+
+(defun cookie-sort (collection predicate)
+  "Sort the cookies in COLLECTION, stably, comparing elements using PREDICATE.
+PREDICATE is called with two cookies, and should return T
+if the first cookie is \"less\" than the second.
+
+All cookies will be refreshed when the sort is complete."
+
+  (elib-set-collection->last-tin collection nil)
+
+  (collection-append-cookies
+   collection
+   (prog1 (sort (collection-list-cookies collection) predicate)
+     (collection-clear collection))))
+
+
+(defun collection-buffer (collection)
+  "Return the buffer that is associated with COLLECTION.
+Returns nil if the buffer has been deleted."
+  (let ((buf (elib-collection->buffer collection)))
+    (if (buffer-name buf)
+	buf
+      nil)))
+
+
+;;; Local Variables:
+;;; eval: (put 'elib-set-buffer-bind-dll 'lisp-indent-hook 1)
+;;; eval: (put 'elib-set-buffer-bind-dll-let* 'lisp-indent-hook 2)
+;;; End:
+
+;;; cookie.el ends here