1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137
|
;;; $Id: cookie.el,v 1.32 1995/12/11 00:11:04 ceder 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 know 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
|