home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / elisp / packages / bbdb / bbdb.el < prev    next >
Encoding:
Text File  |  1992-09-10  |  81.7 KB  |  2,167 lines

  1. ;;; -*- Mode:Emacs-Lisp -*-
  2.  
  3. ;;; This file is the core of the Insidious Big Brother Database (aka BBDB),
  4. ;;; copyright (c) 1991, 1992 Jamie Zawinski <jwz@lucid.com>.
  5. ;;; See the file bbdb.texinfo for documentation.
  6. ;;;
  7. ;;; The Insidious Big Brother Database is free software; you can redistribute
  8. ;;; it and/or modify it under the terms of the GNU General Public License as
  9. ;;; published by the Free Software Foundation; either version 1, or (at your
  10. ;;; option) any later version.
  11. ;;;
  12. ;;; BBDB is distributed in the hope that it will be useful, but WITHOUT ANY
  13. ;;; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  14. ;;; FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  15. ;;; details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Emacs; see the file COPYING.  If not, write to
  19. ;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  20. ;;;
  21. ;;;  -----------------------------------------------------------------------
  22. ;;; |  There is a mailing list for discussion of BBDB: info-bbdb@lucid.com. |
  23. ;;; |  To join, send mail to info-bbdb-request@lucid.com (don't forget the  |
  24. ;;; |  -request part or you'll look silly in front of lots of people who    |
  25. ;;; |  have the ability to remember it indefinitely...)                     |
  26. ;;; |                                                                       |
  27. ;;; |  There is also a second mailing list, to which only bug fixes and     |
  28. ;;; |  new version announcements are sent; to be added to it, send mail to  |
  29. ;;; |  bbdb-announce-request@lucid.com.  This is a very low volume list,    |
  30. ;;; |  and if you're using BBDB, you really should be on it.                |
  31. ;;; |                                                                       |
  32. ;;; |  When joining these lists or reporting bugs, please mention which     |
  33. ;;; |  version you have.                                                    |
  34. ;;;  -----------------------------------------------------------------------
  35.  
  36. (defconst bbdb-version "1.47; 11-sep-92.")
  37. (defconst bbdb-file-format 2)
  38.  
  39. ;; This nonsense is to get the definition of defsubst loaded in when this file
  40. ;; is loaded,without necessarily forcing the compiler to be loaded if we're 
  41. ;; running in an emacs with bytecomp-runtime.el predumped.  We are using 
  42. ;; `require' as a way to get compile-time evaluation of this form so that this
  43. ;; works in the old compiler as well as the new one.
  44. ;;
  45. (require (progn
  46.        (provide 't) ; eeeewwww, gross!
  47.        (condition-case ()
  48.            (if (fboundp 'defsubst)
  49.            't
  50.          ;; If byte-optimize can be loaded, use that.
  51.          (require 'byte-optimize)
  52.          'byte-optimize)
  53.          ;; otherwise, use the boneheaded version of defsubst.
  54.          (error 'defsubst))))
  55.  
  56. (defvar bbdb-file "~/.bbdb"
  57.   "*The name of the Insidious Big Brother Database file.")
  58.  
  59. (defvar bbdb-default-area-code 415
  60.   "*The default area code to use when prompting for a new phone number.
  61. This must be a number, not a string.")
  62.  
  63. (defvar bbdb-north-american-phone-numbers-p t
  64.   "*Set this to nil if you want to enter phone numbers that aren't the same
  65. syntax as those in North America (that is, [[1] nnn] nnn nnnn ['x' n*]).
  66. If this is true, then some error checking is done so that you can't enter
  67. incorrect phone numbers, and all phone numbers are pretty-printed the same
  68. way.  European phone numbers don't have as strict a syntax, however, so
  69. this is a harder problem for them (on which I am punting).
  70.  
  71. You can have both styles of phone number in your database by providing a
  72. prefix argument to the bbdb-insert-new-field command.")
  73.  
  74. (defvar bbdb-electric-p t
  75.   "*Whether bbdb mode should be `electric' like electric-buffer-list.")
  76.  
  77. (defvar bbdb-case-fold-search case-fold-search
  78.   "*This is the value of case-fold-search used by Meta-X bbdb and related
  79. commands.  This variable lets the case-sensitivity of ^S and of the bbdb
  80. commands be different.")
  81.  
  82. (defvar bbdb/mail-auto-create-p t
  83.   "*If this is t, then VM, MH, and RMAIL will automatically create new bbdb
  84. records for people you receive mail from.  If this is a function name
  85. or lambda, then it is called with no arguments to decide whether an
  86. entry should be automatically created.  You can use this to, for example,
  87. not create records for messages which have reached you through a 
  88. particular mailing list, or to only create records automatically if
  89. the mail has a particular subject.")
  90.  
  91. (defvar bbdb/news-auto-create-p nil
  92.   "*If this is t, then GNUS will automatically create new bbdb 
  93. records for people you receive mail from.  If this is a function name
  94. or lambda, then it is called with no arguments to decide whether an
  95. entry should be automatically created.  You can use this to, for example,
  96. create or not create messages which have a particular subject.  If you
  97. want to autocreate messages based on the current newsgroup, it's 
  98. probably a better idea to set this variable to t or nil from your 
  99. gnus-Select-group-hook instead.")
  100.  
  101. (defvar bbdb-quiet-about-name-mismatches nil
  102.   "*If this is true, then BBDB will not prompt you when it notices a
  103. name change, that is, when the \"real name\" in a message doesn't correspond
  104. to a record already in the database with the same network address.  As in,
  105. \"John Smith <jqs@frob.com>\" versus \"John Q. Smith <jqs@frob.com>\".  
  106. Normally you will be asked if you want to change it.")
  107.  
  108. (defvar bbdb-use-alternate-names t
  109.   "*If this is true, then when bbdb notices a name change, it will ask you
  110. if you want both names to map to the same record.")
  111.  
  112. (defvar bbdb-readonly-p nil
  113.   "*If this is true, then nothing will attempt to change the bbdb database
  114. implicitly, and you will be prevented from doing it explicitly.  If you have 
  115. more than one emacs running at the same time, you might want to arrange for 
  116. this to be set to t in all but one of them.")
  117.  
  118. (defvar bbdb-auto-revert-p nil
  119.   "*If this variable is true and the BBDB file is noticed to have changed on 
  120. disk, it will be automatically reverted without prompting you first.  Otherwise
  121. you will be asked. (But if the file has changed and you hae made changes in 
  122. memory as well, you will always be asked.)")
  123.  
  124. (defvar bbdb-notice-auto-save-file nil
  125.   "*If this is true, then the BBDB will notice when its auto-save file is
  126. newer than the file is was read from, and will offer to revert.")
  127.  
  128. (defvar bbdb-use-pop-up 'horiz
  129.   "If true, display a continuously-updating bbdb window while in VM, MH,
  130. RMAIL, or GNUS.  If 'horiz, stack the window horizontally if there is room.")
  131.  
  132. (defvar bbdb-pop-up-target-lines 5
  133.   "*Desired number of lines in a VM/MH/RMAIL/GNUS pop-up bbdb window.")
  134.  
  135. (defvar bbdb-completion-type nil
  136.   "*Controls the behaviour of 'bbdb-complete-name'.  If nil, completion is 
  137. done across the set of all full-names and user-ids in the bbdb-database;
  138. if the symbol 'name, completion is done on names only; if the symbol 'net, 
  139. completion is done on network addresses only; if it is 'primary, then 
  140. completion is done only across the set of primary network addresses (the
  141. first address in the list of addresses for a given user).  If it is 
  142. 'primary-or-name, completion is done across primaries and real names.")
  143.  
  144. (defvar bbdb-completion-display-record t
  145.   "*Whether bbdb-complete-name (\\<mail-mode-map>\\[bbdb-complete-name] \
  146. in mail-mode) will update the *BBDB* buffer
  147. to display the record whose email address has just been inserted.")
  148.  
  149. (defvar bbdb-user-mail-names nil
  150.   "*A regular expression identifying the addresses that belong to you.
  151. If a message from an address matching this is seen, the BBDB record for
  152. the To: line will be shown instead of the one for the From: line.  If
  153. this is nil, it will default to the value of (user-login-name).")
  154.  
  155. (defvar bbdb-always-add-addresses nil
  156.   "*If this is true, then when the Insidious Big Brother Database notices
  157. a new email address for a person, it will automatically add it to the list
  158. of addresses.  If it is nil, you will be asked whether to add it.  If it is
  159. the symbol 'never (really, if it is any non-t, non-nil value) then new 
  160. network addresses will never be automatically added.
  161.  
  162. See also the variable `bbdb-new-nets-always-primary' for control of whether
  163. the addresses go at the front of the list or the back.")
  164.  
  165. (defvar bbdb-new-nets-always-primary nil
  166.   "*If this is true, then when the Insidious Big Brother Database adds a new 
  167. address to a record, it will always add it to the front of the list of 
  168. addresses, making it the primary address.  If this is nil, you will be asked.
  169. If it is the symbol 'never (really, if it is any non-t, non-nil value) then
  170. new network addresses will always be added at the end of the list.")
  171.  
  172. (defvar bbdb-send-mail-style nil
  173.   "*Specifies which package should be used to send mail.
  174. Should be 'vm, 'mh, 'mail (or nil, meaning guess.)")
  175.  
  176. (defvar bbdb-offer-save t
  177.   "*If t, then certain actions will cause the BBDB to ask you whether
  178. you wish to save the database.  If nil, then the offer to save will never
  179. be made.  If not t and not nil, then any time it would ask you, it will
  180. just save it without asking.")
  181.  
  182. (defvar bbdb-message-caching-enabled t
  183.   "*Whether caching of the message->bbdb-record association should be used
  184. for the interfaces which support it (VM, MH, and RMAIL).  This can speed
  185. things up a lot.  One implication of this variable being true is that the
  186. bbdb-notice-hook will not be called each time a message is selected, but
  187. only the first time.  Likewise, if selecting a message would generate a
  188. question (whether to add an address, change the name, etc) you will only
  189. be asked that question the very first time the message is selected.")
  190.  
  191. (defvar bbdb-list-hook nil
  192.   "*Hook or hooks invoked after the bbdb-list-buffer is filled in.  Invoked
  193. with no arguments.")
  194.  
  195. (defvar bbdb-create-hook nil
  196.   "*Hook or hooks invoked each time a new bbdb-record is created.  Invoked
  197. with one argument, the new record.  This is called *before* the record is 
  198. added to the database.  Note that bbdb-change-hook will be called as well.")
  199.  
  200. (defvar bbdb-change-hook nil
  201.   "*Hook or hooks invoked each time a bbdb-record is altered.  Invoked with
  202. one argument, the record.  This is called *before* the bbdb-database buffer
  203. is modified.  Note that if a new bbdb record is created, both this hook and
  204. bbdb-create-hook will be called.")
  205.  
  206. (defvar bbdb-after-change-hook nil
  207.   "*Hook or hooks invoked each time a bbdb-record is altered.  Invoked with
  208. one argument, the record.  This is called *after* the bbdb-database buffer
  209. is modified, so if you want to modify the record each time it is changed,
  210. you should use the `bbdb-change-hook' instead.  Note that if a new bbdb 
  211. record is created, both this hook and bbdb-create-hook will be called.")
  212.  
  213. (defvar bbdb-canonicalize-net-hook nil
  214.   "*If this is non-nil, it should be a function of one arg: a network address
  215. string.  Whenever the Insidious Big Brother Database \"notices\" a message,
  216. the corresponding network address will be passed to this function first, as
  217. a kind of \"filter\" to do whatever transformations upon it you like before
  218. it is compared against or added to the database.  For example: it is the case
  219. that CS.CMU.EDU is a valid return address for all mail originating at a 
  220. machine in the .CS.CMU.EDU domain.  So, if you wanted all such addresses to
  221. be canonically hashed as user@CS.CMU.EDU, instead of as user@host.CS.CMU.EDU,
  222. you might set this variable to a function like this:
  223.  
  224.  (setq bbdb-canonicalize-net-hook
  225.        '(lambda (addr)
  226.           (cond ((string-match \"\\\\`\\\\([^@]+@\\\\).*\\\\.\\\\(CS\\\\.CMU\\\\.EDU\\\\)\\\\'\"
  227.                                addr)
  228.                  (concat (substring addr (match-beginning 1) (match-end 1))
  229.                          (substring addr (match-beginning 2) (match-end 2))))
  230.                 (t addr))))
  231.  
  232. You could also use this function to rewrite UUCP-style addresses into domain-
  233. style addresses, or any number of things.
  234.  
  235. This function will be called repeatedly until it returns a value EQ to the
  236. value passed in.  So multiple rewrite rules might apply to a single address.")
  237.  
  238.  
  239. (defvar bbdb-notice-hook nil
  240.   "*Hook or hooks invoked each time a bbdb-record is \"noticed\", that is,
  241. each time it is displayed by the news or mail interfaces.  Invoked with
  242. one argument, the new record.  The record need not have been modified for 
  243. this to be called - use bbdb-change-hook for that.  You can use this to, 
  244. for example, add something to the notes field based on the subject of the 
  245. current message.  It is up to your hook to determine whether it is running
  246. in GNUS, VM, MH, or RMAIL, and to act appropriately.
  247.  
  248. Also note that bbdb-change-hook will NOT be called as a result of any
  249. modifications you may make to the record inside this hook.
  250.  
  251. Beware that if the variable `bbdb-message-caching-enabled' is true (a good
  252. idea) then when you are using VM, MH, or RMAIL, this hook will be called only 
  253. the first time that message is selected.  (The GNUS interface does not use
  254. caching.)  When debugging the value of this hook, it is a good idea to set 
  255. caching-enabled to nil.")
  256.  
  257. (defvar bbdb-after-read-db-hook nil
  258.   "*Hook or hooks invoked (with no arguments) just after the Insidious Big 
  259. Brother Database is read in.  Note that this can be called more than once if 
  260. the BBDB is reverted.")
  261.  
  262. (defvar bbdb-load-hook nil
  263.   "*Hook or hooks invoked (with no arguments) when the Insidious Big Brother 
  264. Database code is first loaded.")
  265.  
  266. (defvar bbdb-mode-map nil
  267.   "Keymap for Insidious Big Brother Database listings.")
  268.  
  269.  
  270. ;;; These are the buffer-local variables we use.
  271. ;;; They are mentioned here so that the compiler doesn't warn about them 
  272. ;;; when byte-compile-warn-about-free-variables is on.
  273.  
  274. (defvar bbdb-records nil)
  275. (defvar bbdb-changed-records nil)
  276. (defvar bbdb-end-marker nil)
  277. (defvar bbdb-hashtable nil)
  278. (defvar bbdb-propnames nil)
  279. (defvar bbdb-message-cache nil)
  280. (defvar bbdb-showing-changed-ones nil)
  281. (defvar bbdb-modified-p nil)
  282. (defvar bbdb-elided-display nil)
  283.  
  284. (defvar bbdb-debug t)
  285. (defmacro bbdb-debug (&rest body)
  286.   ;; ## comment out the next line to turn off debugging.
  287.   ;; ## You really shouldn't do this!  But it will speed things up.
  288.   (list 'and 'bbdb-debug (list 'let '((debug-on-error t)) (cons 'progn body)))
  289.   )
  290.  
  291. (defmacro bbdb-y-or-n-p (&rest args)
  292.   (list 'prog1
  293.     (cons 'y-or-n-p args)
  294.     '(message " ")))
  295.  
  296. (defun bbdb-invoke-hook (hook arg)
  297.   "Like invoke-hooks, but invokes the given hook with one argument."
  298.   (if (and (boundp hook) (setq hook (symbol-value hook)))
  299.       (if (and (consp hook) (not (eq (car hook) 'lambda)))
  300.       (while hook
  301.         (funcall (car hook) arg)
  302.         (setq hook (cdr hook)))
  303.       (funcall hook arg))))
  304.  
  305. (defun bbdb-invoke-hook-for-value (hook &rest args)
  306.   "If HOOK is nil, return nil.  If it is t, return t.  Otherwise,
  307. return the value of funcalling it with the rest of the arguments."
  308.   (cond ((eq hook nil) nil)
  309.     ((eq hook t) t)
  310.     (t (apply hook args))))
  311.  
  312. (defmacro bbdb-defstruct (conc-name &rest slots)
  313.   (setq conc-name (symbol-name conc-name))
  314.   (let ((body '())
  315.     (i 0)
  316.     (L (length slots)))
  317.     (while slots
  318.       (setq body
  319.     (nconc body
  320.       (let ((name1 (intern (concat conc-name (symbol-name (car slots)))))
  321.         (name2 (intern (concat conc-name "set-" (symbol-name (car slots))))))
  322.         (list
  323.           (list 'defmacro name1 '(vector)
  324.             (list 'list ''aref 'vector i))
  325.           (list 'defmacro name2 '(vector value)
  326.             (list 'list ''aset 'vector i 'value))
  327.           ;(list 'put (list 'quote name1) ''edebug-form-hook ''(form))
  328.           ;(list 'put (list 'quote name2) ''edebug-form-hook ''(form form))
  329.           ))))
  330.       (setq slots (cdr slots) i (1+ i)))
  331.     (setq body (nconc body (list (list 'defconst
  332.                        (intern (concat conc-name "length"))
  333.                        L))))
  334.     (cons 'progn body)))
  335.  
  336. ;;; When reading this code, beware that "cache" refers to two things.
  337. ;;; It refers to the cache slot of bbdb-record structures, which is
  338. ;;; used for computed properties of the records; and it also refers
  339. ;;; to a message-id --> bbdb-record association list which speeds up
  340. ;;; the RMAIL, VM, and MH interfaces.
  341.  
  342. (bbdb-defstruct bbdb-record-
  343.   firstname lastname aka company
  344.   phones addresses net raw-notes
  345.   cache
  346.   )
  347. (bbdb-defstruct bbdb-phone-
  348.   location area exchange suffix extension
  349.   )
  350. (bbdb-defstruct bbdb-address-
  351.   location street1 street2 street3 city state zip
  352.   )
  353. (bbdb-defstruct bbdb-cache-
  354.   namecache sortkey marker deleted-p
  355.   )
  356.  
  357. (defsubst bbdb-record-name-1 (record)
  358.   (bbdb-cache-set-namecache (bbdb-record-cache record)
  359.     (let ((fname (bbdb-record-firstname record))
  360.       (lname (bbdb-record-lastname record)))
  361.       (if (> (length fname) 0)
  362.       (if (> (length lname) 0)
  363.           (concat fname " " lname)
  364.         fname)
  365.     lname))))
  366.  
  367. (defun bbdb-record-name (record)
  368.   (or (bbdb-cache-namecache (bbdb-record-cache record))
  369.       (bbdb-record-name-1 record)))
  370.  
  371. (defun bbdb-record-sortkey (record)
  372.   (or (bbdb-cache-sortkey (bbdb-record-cache record))
  373.       (bbdb-cache-set-sortkey (bbdb-record-cache record)
  374.         (downcase
  375.           (concat (bbdb-record-lastname record)
  376.           (bbdb-record-firstname record)
  377.           (bbdb-record-company record))))))
  378.  
  379. (defmacro bbdb-record-marker (record)
  380.   (list 'bbdb-cache-marker (list 'bbdb-record-cache record)))
  381.  
  382. (defmacro bbdb-record-deleted-p (record)
  383.   (list 'bbdb-cache-deleted-p (list 'bbdb-record-cache record)))
  384.  
  385. (defmacro bbdb-record-set-deleted-p (record val)
  386.   (list 'bbdb-cache-set-deleted-p (list 'bbdb-record-cache record) val))
  387.  
  388. (defmacro bbdb-record-set-namecache (record newval)
  389.   (list 'bbdb-cache-set-namecache (list 'bbdb-record-cache record) newval))
  390.  
  391. (defmacro bbdb-record-set-sortkey (record newval)
  392.   (list 'bbdb-cache-set-sortkey (list 'bbdb-record-cache record) newval))
  393.  
  394. (defmacro bbdb-record-set-marker (record newval)
  395.   (list 'bbdb-cache-set-marker (list 'bbdb-record-cache record) newval))
  396.  
  397.  
  398. ;; The "notes" and "properties" accessors don't need to be fast.
  399.  
  400. (defun bbdb-record-notes (record)
  401.   (if (consp (bbdb-record-raw-notes record))
  402.       (cdr (assq 'notes (bbdb-record-raw-notes record)))
  403.       (bbdb-record-raw-notes record)))
  404.  
  405. ;; this works on the 'company field as well.
  406. (defun bbdb-record-getprop (record property)
  407.   (if (memq property '(name address addresses phone phones net aka AKA))
  408.       (error "bbdb: cannot access the %s field this way" property))
  409.   (if (eq property 'company)
  410.       (bbdb-record-company record)
  411.     (if (consp (bbdb-record-raw-notes record))
  412.     (cdr (assq property (bbdb-record-raw-notes record)))
  413.       (if (and (eq property 'notes)
  414.            (stringp (bbdb-record-raw-notes record)))
  415.       (bbdb-record-raw-notes record)
  416.     nil))))
  417.  
  418. ;; this works on the 'company field as well.
  419. (defun bbdb-record-putprop (record property newval)
  420.   (if (memq property '(name address addresses phone phones net aka AKA))
  421.       (error "bbdb: cannot annotate the %s field this way" property))
  422.   (if (eq property 'company)
  423.       (bbdb-record-set-company record
  424.     (bbdb-record-set-company record newval))
  425.     (if (and (eq property 'notes)
  426.          (not (consp (bbdb-record-raw-notes record))))
  427.     (bbdb-record-set-raw-notes record newval)
  428.       (or (listp (bbdb-record-raw-notes record))
  429.       (bbdb-record-set-raw-notes record
  430.         (list (cons 'notes (bbdb-record-raw-notes record)))))
  431.       (let ((old (assq property (bbdb-record-raw-notes record))))
  432.     (if old
  433.         (if newval
  434.         (setcdr old newval)
  435.           (bbdb-record-set-raw-notes record
  436.         (delq old (bbdb-record-raw-notes record))))
  437.       (and newval
  438.            (bbdb-record-set-raw-notes record
  439.          (append (bbdb-record-raw-notes record)
  440.              (list (cons property newval))))))))
  441.     ;; save some file space: if we ever end up with ((notes . "...")),
  442.     ;; replace it with the string.
  443.     (if (and (consp (bbdb-record-raw-notes record))
  444.          (null (cdr (bbdb-record-raw-notes record)))
  445.          (eq 'notes (car (car (bbdb-record-raw-notes record)))))
  446.     (bbdb-record-set-raw-notes record
  447.       (cdr (car (bbdb-record-raw-notes record)))))
  448.     )
  449.   ;; If we're changing the company, then we need to sort, since the company
  450.   ;; is the sortkey for nameless records.  This should almost never matter...
  451.   (bbdb-change-record record (eq property 'company))
  452.   newval)
  453.  
  454. (defun bbdb-record-set-notes (record newval)
  455.   (if (consp (bbdb-record-raw-notes record))
  456.       (bbdb-record-putprop record 'notes newval)
  457.     (bbdb-record-set-raw-notes record newval)
  458.     (bbdb-change-record record nil)))
  459.  
  460. (defun bbdb-phone-string (phone)
  461.   (if (= 2 (length phone)) ; euronumbers....
  462.       (aref phone 1)
  463.     ;; numbers should come in two forms:
  464.     ;; ["where" 415 555 1212 99] or ["where" "the number"]
  465.     (if (stringp (aref phone 1))
  466.     (error "doubleplus ungood: euronumbers unwork"))
  467.     (concat (if (/= 0 (bbdb-phone-area phone))
  468.         (format "(%03d) " (bbdb-phone-area phone))
  469.         "")
  470.         (if (/= 0 (bbdb-phone-exchange phone))
  471.         (format "%03d-%04d"
  472.             (bbdb-phone-exchange phone) (bbdb-phone-suffix phone))
  473.         "")
  474.         (if (and (bbdb-phone-extension phone)
  475.              (/= 0 (bbdb-phone-extension phone)))
  476.         (format " x%d" (bbdb-phone-extension phone))
  477.         ""))))
  478.  
  479. (defun bbdb-address-zip-string (addr)
  480.   (if (consp (bbdb-address-zip addr))
  481.       (if (stringp (car (bbdb-address-zip addr)))
  482.       (concat (car (bbdb-address-zip addr))
  483.           " "
  484.           (car (cdr (bbdb-address-zip addr))))
  485.     (format "%05d-%04d" (car (bbdb-address-zip addr))
  486.         (car (cdr (bbdb-address-zip addr)))))
  487.     (if (or (eq 0 (bbdb-address-zip addr))
  488.         (null (bbdb-address-zip addr)))
  489.     ""
  490.       (format "%05d" (bbdb-address-zip addr)))))
  491.  
  492. (defmacro bbdb-record-lessp (record1 record2)
  493.   (list 'string< (list 'bbdb-record-sortkey record1)
  494.              (list 'bbdb-record-sortkey record2)))
  495.  
  496. (defmacro bbdb-subint (string match-number)
  497.   (list 'string-to-int
  498.     (list 'substring string
  499.           (list 'match-beginning match-number)
  500.           (list 'match-end match-number))))
  501.  
  502. (defmacro bbdb-error-retry (form)
  503.   (list 'let '(--bbdb-error-retry--)
  504.     (list 'while
  505.       (list 'not (list 'condition-case '--c--
  506.                (list 'progn
  507.                  (list 'setq '--bbdb-error-retry-- form)
  508.                  't)
  509.            '(error (ding)
  510.              (let ((cursor-in-echo-area t))
  511.                (message "Error: %s" (nth 1 --c--))
  512.                (sit-for 2) nil)))))
  513.     '--bbdb-error-retry--))
  514.  
  515. ;;; I no longer remember why I felt this was necessary, but I think it 
  516. ;;; might have been because of the bug in the save-excursion of 18.55-57
  517. (defmacro bbdb-save-buffer-excursion (&rest body)
  518.   (list 'save-excursion
  519.     (list 'let '((--bbdb-obuf-- (current-buffer)))
  520.       (list 'unwind-protect (cons 'progn body)
  521.     '(set-buffer --bbdb-obuf--)))))
  522.  
  523. (defmacro bbdb-with-db-buffer (&rest body)
  524.   (cons 'bbdb-save-buffer-excursion
  525.     (cons '(set-buffer (find-file-noselect bbdb-file 'nowarn))
  526.           (if (and (boundp 'bbdb-debug) bbdb-debug)
  527.           ;; if we're debugging, and the .bbdb buffer is visible in
  528.           ;; a window, temporarilly switch to that window so that
  529.           ;; when we come out, that window has been scrolled to the
  530.           ;; record we've just modified.  (make w-point = b-point)
  531.           (list
  532.             (list 'let '((w (and bbdb-debug
  533.                      (get-buffer-window
  534.                       (buffer-name
  535.                        (get-buffer bbdb-file))))))
  536.               (list 'save-excursion
  537.                 (cons 'save-window-excursion
  538.                   (cons '(and w (select-window w))
  539.                     body)))))
  540.           body))))
  541.  
  542.  
  543. (defsubst bbdb-string-trim (string)
  544.   "lose leading and trailing whitespace"
  545.   (if (string-match "\\`[ \t\n]+" string)
  546.       (setq string (substring string (match-end 0))))
  547.   (if (string-match "[ \t\n]+\\'" string)
  548.       (substring string 0 (match-beginning 0))
  549.     string))
  550.  
  551.  
  552. (defun bbdb-read-string (prompt &optional default)
  553.   "Reads a string, trimming trailing whitespace.  If DEFAULT is multiple
  554. lines, then the minibuffer is enlarged to fit it while editing."
  555.   (let ((n 0)
  556.     (start 0)
  557.     (L (length default)))
  558.     (while (< start L)
  559.       (setq start (1+ (or (string-match "\n" default start) L))
  560.         n (1+ n)))
  561.     (save-excursion
  562.      (save-window-excursion
  563.       (if (and (boundp 'epoch::version) epoch::version)
  564.       nil  ; this breaks epoch...
  565.     (let ((w (selected-window)))
  566.       (select-window (minibuffer-window))
  567.       (enlarge-window (max 0 (- n (window-height))))
  568.       (sit-for 0) ; avoid redisplay glitch
  569.       (select-window w)))
  570.       (bbdb-string-trim
  571.     (read-string prompt default))))))
  572.  
  573.  
  574. (defsubst bbdb-field-shown-p (field)
  575.   (or (null bbdb-elided-display)
  576.       (eq field 'name)
  577.       (not (or (eq bbdb-elided-display t)
  578.            (memq field bbdb-elided-display)))))
  579.  
  580.  
  581. (defun bbdb-format-record (record &optional brief)
  582.   (bbdb-debug (if (bbdb-record-deleted-p record)
  583.           (error "plus ungood: formatting deleted record")))
  584.   (let ((name (bbdb-record-name record))
  585.     (comp (bbdb-record-company record)))
  586.     (cond ((and name comp) (insert name " - " comp))
  587.       ((or name comp) (insert (or name comp)))
  588.       (t (insert "???")))
  589.     (cond ((eq brief t)
  590.        (let ((p (point)))
  591.          (beginning-of-line)
  592.          (if (<= (- p (point)) 47)
  593.          (goto-char p)
  594.            (goto-char (+ (point) 44))
  595.            (setq p (point))
  596.            (end-of-line)
  597.            (delete-region p (point))
  598.            (insert "...")))
  599.        (let ((phone (car (bbdb-record-phones record)))
  600.          (net (car (bbdb-record-net record)))
  601.          (notes (bbdb-record-raw-notes record)))
  602.          (if (or phone net notes)
  603.          (progn (indent-to 48)
  604.             (insert (if notes ". " "  "))))
  605.          (cond (phone (insert (bbdb-phone-string phone))
  606.               (indent-to 70)
  607.               (insert " ("); don't ask, it compiles better
  608.               (insert (bbdb-phone-location phone))
  609.               (insert ")"))
  610.            (net   (insert net))))
  611.        (insert "\n"))
  612.       (t
  613.        (insert "\n")
  614.        (let* ((bbdb-elided-display brief) ;pfeh.
  615.           (aka (bbdb-record-aka record))
  616.           (phones (and (bbdb-field-shown-p 'phone)
  617.                    (bbdb-record-phones record)))
  618.           (addrs (and (bbdb-field-shown-p 'address)
  619.                   (bbdb-record-addresses record)))
  620.           phone)
  621.        (while phones
  622.          (setq phone (car phones))
  623.          (insert (format " %14s: " (bbdb-phone-location phone)))
  624.          (insert (bbdb-phone-string phone) "\n")
  625.          (setq phones (cdr phones)))
  626.        (let (addr c s)
  627.          (while addrs
  628.            (setq addr (car addrs))
  629.            (insert (format " %14s: " (bbdb-address-location addr)))
  630.            (if (= 0 (length (setq s (bbdb-address-street1 addr)))) nil
  631.          (indent-to 17) (insert s "\n"))
  632.            (if (= 0 (length (setq s (bbdb-address-street2 addr)))) nil
  633.          (indent-to 17) (insert s "\n"))
  634.            (if (= 0 (length (setq s (bbdb-address-street3 addr)))) nil
  635.          (indent-to 17) (insert s "\n"))
  636.            (indent-to 17)
  637.            (insert (setq c (bbdb-address-city addr)))
  638.            (setq s (bbdb-address-state addr))
  639.            (if (and (> (length c) 0) (> (length s) 0)) (insert ", "))
  640.            (insert s "  ")
  641.            (insert (bbdb-address-zip-string addr) "\n")
  642.            (setq addrs (cdr addrs))))
  643.        (if (and (bbdb-record-net record)
  644.             (bbdb-field-shown-p 'net))
  645.            (insert (format " %14s: %s\n" "net"
  646.                    (mapconcat (function identity)
  647.                       (bbdb-record-net record)
  648.                       ", "))))
  649.        (if (and aka
  650.             (bbdb-field-shown-p 'aka))
  651.            (insert (format " %14s: %s\n" "AKA"
  652.                    (mapconcat (function identity)
  653.                       aka ", "))))
  654.        (let ((notes (bbdb-record-raw-notes record)))
  655.          (if (stringp notes)
  656.          (setq notes (list (cons 'notes notes))))
  657.          (while notes
  658.            (if (bbdb-field-shown-p (car (car notes)))
  659.            (progn
  660.              (insert (format " %14s: " (car (car notes))))
  661.              (let ((p (point)))
  662.                (insert (cdr (car notes)))
  663.                (save-excursion
  664.              (save-restriction
  665.                (narrow-to-region p (1- (point)))
  666.                (goto-char (1+ p))
  667.                (while (search-forward "\n" nil t)
  668.                  (insert (make-string 17 ?\ )))))
  669.                (insert "\n"))))
  670.            (setq notes (cdr notes)))))
  671.        (insert "\n")))))
  672.  
  673. (defconst bbdb-gag-messages nil
  674.   "Bind this to t to quiet things down - do not set it!")
  675.  
  676. (defconst bbdb-buffer-name "*BBDB*")
  677.  
  678. (defvar bbdb-elided-display nil
  679.   "*Set this to t if to make the bbdb-display commands default to displaying
  680. one line per record instead of a full listing.  Set this to a list of some
  681. of the symbols '(address phone net notes) to select those fields to be left 
  682. out of the listing (you can't leave out the name field).
  683.  
  684. This is the default state for Meta-x bbdb and friends.  You can have a
  685. different default for when the BBDB buffer is automatically updated by the
  686. mail and news interfaces by setting the variable `bbdb-pop-up-elided-display'.
  687. If that variable is unbound, this variable will be consulted instead.")
  688.  
  689. (defvar bbdb-pop-up-elided-display) ; default unbound.
  690. (put 'bbdb-pop-up-elided-display
  691.      'variable-documentation
  692.      "*Set this to t if to make the pop-up BBDB buffer default to displaying
  693. one line per record instead of a full listing.  Set this to a list of some
  694. of the symbols '(address phone net notes) to select those fields to be left
  695. out of the listing (you can't leave out the name field).
  696.  
  697. The default state for Meta-x bbdb and friends is controlled by the variable
  698. `bbdb-elided-display'; this variable (`bbdb-pop-up-elided-display') is the
  699. default for when the BBDB buffer is automatically updated by the mail and
  700. news interfaces.  If bbdb-pop-up-elided-display is unbound, then 
  701. bbdb-elided-display will be consulted instead by mail and news.")
  702.  
  703.  
  704. (defmacro bbdb-pop-up-elided-display ()
  705.   '(if (boundp 'bbdb-pop-up-elided-display)
  706.        bbdb-pop-up-elided-display
  707.        bbdb-elided-display))
  708.  
  709. (defun bbdb-frob-mode-line (n)
  710.   (setq mode-line-buffer-identification
  711.     (if (> n 0)
  712.         (list 24 "BBDB: "
  713.           (list 10
  714.             (format "%d/%d" n (length (bbdb-records))))
  715.           '(bbdb-showing-changed-ones " !!" "   "))
  716.       '("- Insidious Big Brother Database " mode-line-modified "-"))
  717.     mode-line-modified
  718.     '(bbdb-readonly-p "--%%%%-" (bbdb-modified-p "--**-" "-----"))))
  719.  
  720. (defun bbdb-display-records-1 (records &optional append)
  721.   (if (or (null records)
  722.       (consp (car records)))
  723.       nil
  724.     (setq records (mapcar (function (lambda (x)
  725.                 (list x bbdb-elided-display (make-marker))))
  726.               records)))
  727.   (let ((b (current-buffer))
  728.     (first (car (car records))))
  729.    (save-window-excursion
  730.     (with-output-to-temp-buffer bbdb-buffer-name
  731.       (set-buffer bbdb-buffer-name)
  732.       ;; If we're appending these records to the ones already displayed,
  733.       ;; then first remove any duplicates, and then sort them.
  734.       (if append
  735.       (let ((rest records))
  736.         (while rest
  737.           (if (assq (car (car rest)) bbdb-records)
  738.           (setq records (delq (car rest) records)))
  739.           (setq rest (cdr rest)))
  740.         (setq records (append bbdb-records records))
  741.         (setq records
  742.           (sort records
  743.             (function (lambda (x y)
  744.                     (bbdb-record-lessp (car x) (car y))))))))
  745.       (make-local-variable 'mode-line-buffer-identification)
  746.       (make-local-variable 'mode-line-modified)
  747.       (set (make-local-variable 'bbdb-showing-changed-ones) nil)
  748.       (let ((done nil)
  749.         (rest records)
  750.         (changed (bbdb-changed-records)))
  751.     (while (and rest (not done))
  752.       (setq done (memq (car (car rest)) changed)
  753.         rest (cdr rest)))
  754.     (setq bbdb-showing-changed-ones done))
  755.       (bbdb-frob-mode-line (length records))
  756.       (if (not bbdb-gag-messages) (message "Formatting..."))
  757.       (bbdb-mode)
  758.       ;; this in in the *BBDB* buffer, remember, not the .bbdb buffer.
  759.       (set (make-local-variable 'bbdb-records) nil)
  760.       (setq bbdb-records records)
  761.       (let ((buffer-read-only nil)
  762.         prs)
  763.     (bbdb-debug (setq prs (bbdb-records)))
  764.     (setq truncate-lines t)
  765.     (while records
  766.       (bbdb-debug (if (not (memq (car (car records)) prs)) (error "record doubleplus unpresent!")))
  767.       (set-marker (nth 2 (car records)) (point))
  768.       (bbdb-format-record (nth 0 (car records))
  769.                   (nth 1 (car records)))
  770.       (setq records (cdr records))))
  771.       (if (not bbdb-gag-messages) (message "Formatting...done."))))
  772.     (set-buffer bbdb-buffer-name)
  773.     (if append
  774.     (let ((cons (assq first bbdb-records))
  775.           (window (get-buffer-window (current-buffer))))
  776.       (if window (set-window-start window (nth 2 cons)))
  777.       ;; this doesn't really belong here, but it's convenient...
  778.       (save-excursion (run-hooks 'bbdb-list-hook))))
  779.     (bbdbq)
  780.     (set-buffer-modified-p nil)
  781.     (setq buffer-read-only t)
  782.     (set-buffer b)))
  783.  
  784. (defun bbdb-undisplay-records ()
  785.   (save-excursion
  786.     (set-buffer bbdb-buffer-name)
  787.     (setq bbdb-showing-changed-ones nil
  788.       mode-line-modified nil
  789.       bbdb-records nil
  790.       buffer-read-only t)
  791.     (set-buffer-modified-p nil)))
  792.  
  793. ;;; Electric display stuff
  794.  
  795. (defconst bbdb-inside-electric-display nil)
  796. ;; hack hack: a couple of specials that the electric stuff uses for state.
  797. (defvar bbdb-electric-execute-me)
  798. (defvar bbdb-electric-completed-normally)
  799.  
  800. (defun electric-bbdb-display-records (records)
  801.   (require 'electric)
  802.   (let ((bbdb-electric-execute-me nil))   ; Hack alert!  throw-to-execute sets this!
  803.    (let ((bbdb-inside-electric-display t)
  804.      buffer
  805.      bbdb-electric-completed-normally ; Hack alert!  throw-to-execute sets this!
  806.      )
  807.     (save-excursion
  808.      (save-window-excursion
  809.       (save-window-excursion (bbdb-display-records-1 records))
  810.       (setq buffer (window-buffer (Electric-pop-up-window bbdb-buffer-name)))
  811.       (set-buffer buffer)
  812.       (if (not bbdb-gag-messages)
  813.       (message "<<< Press Space to bury the Insidious Big Brother Database list >>>"))
  814.       (catch 'Done
  815.     (while t
  816.       (catch 'Blow-off-the-error
  817.         (setq bbdb-electric-completed-normally nil)
  818.         (unwind-protect
  819.          (progn
  820.            (catch 'electric-bbdb-list-select
  821.              (Electric-command-loop 'electric-bbdb-list-select
  822.                         "-> " t))
  823.            (setq bbdb-electric-completed-normally t))
  824.           ;; protected
  825.           (if bbdb-electric-completed-normally
  826.           (throw 'Done t)
  827.         (ding)
  828.         (message "BBDB-Quit")
  829.         (throw 'Blow-off-the-error t)
  830.         )))))
  831.       (bury-buffer buffer))))
  832.    (message " ")
  833.    (if bbdb-electric-execute-me
  834.        (eval bbdb-electric-execute-me)))
  835.   nil)
  836.  
  837. (defun bbdb-electric-throw-to-execute (form-to-execute)
  838.   "Exit the electric-command-loop, and evaluate the given form once we're out."
  839.   ;; Hack alert!  These variables are bound only within the scope of
  840.   ;; bbdb-electric-display-records!
  841.   (if (not (boundp 'bbdb-electric-execute-me))
  842.       (error "plusungood: electrical short"))
  843.   (setq bbdb-electric-execute-me form-to-execute
  844.     bbdb-electric-completed-normally t)
  845.   (throw 'electric-bbdb-list-select t))
  846.  
  847.  
  848. (defun bbdb-done-command () (interactive)
  849.   (throw 'electric-bbdb-list-select t))
  850.  
  851. (defun bbdb-bury-buffer ()
  852.   (interactive)
  853.   (if bbdb-inside-electric-display
  854.       (bbdb-done-command)
  855.     (bury-buffer)))
  856.  
  857. (defun bbdb-display-records (records)
  858.   (let ((bbdb-window (get-buffer-window bbdb-buffer-name)))
  859.     (if (and bbdb-electric-p
  860.          ;; never be electric if the buffer is already on screen.
  861.          (not bbdb-window))
  862.     (progn
  863.       (define-key bbdb-mode-map " " 'bbdb-done-command)
  864.       (electric-bbdb-display-records records))
  865.       (bbdb-display-records-1 records)
  866.       (save-excursion (run-hooks 'bbdb-list-hook))
  867.       ;; don't smash keybinding if they invoked the bbdb-display
  868.       ;; from inside an electric loop.
  869.       (if bbdb-inside-electric-display
  870.       nil
  871.     (define-key bbdb-mode-map " " 'undefined))
  872.       (if (and (not bbdb-gag-messages)
  873.            (not bbdb-window))
  874.       (message
  875.         (substitute-command-keys
  876.           (if (one-window-p t)
  877.           (if pop-up-windows
  878.               "Type \\[delete-other-windows] to unshow the bbdb-list window."
  879.               "Type \\[switch-to-buffer] RET to unshow the bbdb-list window.")
  880.         "Type \\[switch-to-buffer-other-window] RET to restore old contents of the bbdb-list window.")))))))
  881.  
  882. (defun bbdbq ()
  883.   (if (not (zerop (logand (random) 31))) nil
  884.     (let ((v '["\104\157\156\47\164\40\163\165\163\160\145\143\164\40\171\157\
  885. \165\162\40\156\145\151\147\150\142\157\162\72\40\162\145\160\157\162\164\40\
  886. \150\151\155\41" "\146\156\157\162\144" "\103\157\156\163\165\155\145\40\55\55\
  887. \40\102\145\40\123\151\154\145\156\164\40\55\55\40\104\151\145" "\114\157\166\
  888. \145\40\102\151\147\40\102\162\157\164\150\145\162" "\114\145\145\40\110\141\
  889. \162\166\145\171\40\117\163\167\141\154\144\40\141\143\164\145\144\40\141\154\
  890. \157\156\145"]))
  891.       (message (aref v (% (logand 255 (random)) (length v))))
  892.       (message " "))))
  893.  
  894.  
  895. (defmacro bbdb-hashtable ()
  896.   '(bbdb-with-db-buffer (bbdb-records t) bbdb-hashtable))
  897.  
  898. (defun bbdb-changed-records ()
  899.   (bbdb-with-db-buffer (bbdb-records t) bbdb-changed-records))
  900.  
  901. (defmacro bbdb-gethash (name &optional ht)
  902.   (list 'symbol-value
  903.     (list 'intern-soft name
  904.           (or ht '(bbdb-hashtable)))))
  905.  
  906. (defmacro bbdb-puthash (name record &optional ht)
  907.   (list 'set (list 'intern name
  908.            (or ht '(bbdb-hashtable)))
  909.     record))
  910.  
  911. (defmacro bbdb-remhash (name &optional ht)
  912.   (list 'let (list (list 's (list 'intern-soft name
  913.                   (or ht '(bbdb-hashtable)))))
  914.     '(and s (set s nil))))
  915.  
  916.  
  917. (defsubst bbdb-search-simple (name net)
  918.   "name is a string; net is a string or list of strings."
  919.   (if (eq 0 (length name)) (setq name nil))
  920.   (if (eq 0 (length net)) (setq net nil))
  921.   (bbdb-records) ; make sure db is parsed
  922.   (or (and name (bbdb-gethash (downcase name)))
  923.       (and net
  924.        (if (stringp net)
  925.            (bbdb-gethash (downcase net))
  926.          (let ((answer nil))
  927.            (while (and net (null answer))
  928.          (setq answer (bbdb-gethash (downcase (car net)))
  929.                net (cdr net)))
  930.            answer)))))
  931.  
  932.  
  933. (defun bbdb-net-convert (record)
  934.   "Given a record whose net field is a comma-separated string, convert it to
  935. a list of strings (the new way of doing things.)  Returns the new list."
  936.   (bbdb-record-set-net record (bbdb-split (bbdb-record-net record) ",")))
  937.  
  938. (defun bbdb-split (string separators)
  939.   (let (result
  940.     (not-separators (concat "^" separators)))
  941.     (save-excursion
  942.       (set-buffer (get-buffer-create " *split*"))
  943.       (erase-buffer)
  944.       (insert string)
  945.       (goto-char (point-min))
  946.       (while (progn
  947.            (skip-chars-forward separators)
  948.            (skip-chars-forward " \t\n\r")
  949.            (not (eobp)))
  950.     (let ((begin (point))
  951.           p)
  952.       (skip-chars-forward not-separators)
  953.       (setq p (point))
  954.       (skip-chars-backward " \t\n\r")
  955.       (setq result (cons (buffer-substring begin (point)) result))
  956.       (goto-char p)))
  957.       (erase-buffer))
  958.     (nreverse result)))
  959.  
  960.  
  961. (defsubst bbdb-hash-record (record)
  962.   "Insert the record in the appropriate hashtables.  This must be called 
  963. while the .bbdb buffer is selected."
  964.   (let ((name (bbdb-record-name-1 record))  ; faster version
  965.     (aka (bbdb-record-aka record))
  966.     (net (bbdb-record-net record)))
  967.     (if (not (= 0 (length name))) ; could be nil or ""
  968.     (bbdb-puthash (downcase name) record bbdb-hashtable))
  969.     (while aka
  970.       (bbdb-puthash (downcase (car aka)) record bbdb-hashtable)
  971.       (setq aka (cdr aka)))
  972.     (while net
  973.       (bbdb-puthash (downcase (car net)) record bbdb-hashtable)
  974.       (setq net (cdr net)))))
  975.  
  976. (defvar inside-bbdb-records nil)
  977.  
  978. (defun bbdb-records (&optional already-in-db-buffer)
  979.   "Return a list of all bbdb records; read in and parse the db if necessary.
  980. This also notices if the disk file has changed out from under us."
  981.   (if inside-bbdb-records
  982.       (let ((debug-on-error t))
  983.     (error "catastrophic: bbdb-records recursed")))
  984.   (let ((inside-bbdb-records t)
  985.     shut-up)
  986.     (bbdb-save-buffer-excursion
  987.       ;; get the buffer, don't worry if it's out of synch with disk yet.
  988.       (let ((buf (if already-in-db-buffer
  989.              --bbdb-obuf--  ; hackorama; let's bum some cycles...
  990.              (set-buffer (find-file-noselect bbdb-file 'nowarn)))))
  991.     ;; make sure the BBDB in memory is not out of synch with disk.
  992.     (cond ((verify-visited-file-modtime buf) nil)
  993.           ((and bbdb-auto-revert-p
  994.             (not (buffer-modified-p buf)))
  995.            (message "BBDB has changed on disk, reverting...")
  996.            (setq shut-up t)
  997.            (revert-buffer t t))
  998.           ;; hassle the user
  999.           ((yes-or-no-p (if (buffer-modified-p buf)
  1000.                 "BBDB has changed on disk; flush your changes and revert? "
  1001.                 "BBDB has changed on disk; revert? "))
  1002.            (or (file-exists-p bbdb-file)
  1003.            (error "bbdb: file %s no longer exists!!" bbdb-file))
  1004.            (revert-buffer t t)
  1005.            )
  1006.           ;; this is the case where the .bbdb file has changed; the buffer
  1007.           ;; has changed as well; and the user has answered "no" to the
  1008.           ;; "flush your changes and revert" question.  The only other
  1009.           ;; alternative is to save the file right now.  If they answer
  1010.           ;; no to the following question, they will be asked the
  1011.           ;; preceeding question again and again some large (but finite)
  1012.           ;; number of times.  `bbdb-records' is called a lot, you see...
  1013.           ((buffer-modified-p buf)
  1014.            ;; this prompts
  1015.            (bbdb-save-db t t))
  1016.           ;; otherwise, the buffer and file are inconsistent, but we let
  1017.           ;; them stay that way.
  1018.           )
  1019.     (if (assq 'bbdb-records (buffer-local-variables))
  1020.         nil
  1021.       (set (make-local-variable 'bbdb-records) nil)
  1022.       (set (make-local-variable 'bbdb-changed-records) nil)
  1023.       (set (make-local-variable 'bbdb-end-marker) nil)
  1024.       (set (make-local-variable 'bbdb-hashtable) nil)
  1025.       (set (make-local-variable 'bbdb-propnames) nil)
  1026.       (set (make-local-variable 'revert-buffer-function)
  1027.            'bbdb-revert-buffer)
  1028.       (make-local-variable 'write-file-hooks)
  1029.       (setq write-file-hooks
  1030.         (append write-file-hooks '(bbdb-write-file-hook-fn))
  1031.         bbdb-hashtable (make-vector 1021 0)))
  1032.     (setq bbdb-modified-p (buffer-modified-p)
  1033.           buffer-read-only bbdb-readonly-p)
  1034.     (or bbdb-records
  1035.         (cond ((= (point-min) (point-max)) ; special-case empty db
  1036.            ;; this doesn't need to be insert-before-markers because
  1037.            ;; there are no db-markers in this buffer.
  1038.            (insert (format ";;; file-version: %d\n" bbdb-file-format))
  1039.            (bbdb-flush-all-caches)
  1040.            (setq bbdb-end-marker (point-marker))
  1041.            ;;(run-hooks 'bbdb-after-read-db-hook) ; run this?
  1042.            nil)
  1043.           (t
  1044.            (or shut-up (message "Parsing BBDB..."))
  1045.            (bbdb-flush-all-caches)
  1046.            (cond ((and bbdb-notice-auto-save-file
  1047.                    (file-newer-than-file-p (make-auto-save-file-name)
  1048.                                buffer-file-name))
  1049.               (if (yes-or-no-p "BBDB auto-save file is newer; recover it? ")
  1050.                   (progn
  1051.                 (recover-file buffer-file-name)
  1052.                 (bury-buffer (current-buffer)) ; recover-file selects it
  1053.                 (auto-save-mode 1) ; turn autosave back on
  1054.                 (delete-file (make-auto-save-file-name))
  1055.                 (message "Auto-save mode is ON in BBDB buffer.  Suggest you save it soon.")
  1056.                 (sleep-for 2))
  1057.                   ;; delete auto-save anyway, so we don't keep asking.
  1058.                 (condition-case c
  1059.                 (delete-file (make-auto-save-file-name))
  1060.                   (file-error nil)))
  1061.               ;; tail-recurse and try again
  1062.               (let ((inside-bbdb-records nil))
  1063.                 (bbdb-records)))
  1064.              (t
  1065.               ;; normal case
  1066.               (fillarray bbdb-hashtable 0)
  1067.               (parse-bbdb-internal))))))))))
  1068.  
  1069. (defun bbdb-revert-buffer (arg noconfirm)
  1070.   ;; The .bbdb file's revert-buffer-function.
  1071.   ;; Don't even think of calling this.
  1072.   (kill-all-local-variables)        ; clear db and caches.
  1073.   (if (get-buffer bbdb-buffer-name)    ; now contains invalid records; nukem.
  1074.       (bbdb-undisplay-records))
  1075.   (let ((revert-buffer-function nil))    ; don't loop.
  1076.     (revert-buffer arg noconfirm)))
  1077.  
  1078. (defun parse-bbdb-internal ()
  1079.   (bbdb-debug (message "Parsing BBDB... (reading...)"))
  1080.   (widen)
  1081.   (goto-char (point-min))
  1082.   ;; go to the point at which the first record begins
  1083.   (cond ((eq (following-char) ?\[) nil)
  1084.     ((search-forward "\n[" nil 0) (forward-char -1))
  1085.     (t nil)) ;; no records
  1086.   ;; look backwards for user-defined field names (for completion purposes.)
  1087.   (save-excursion
  1088.     (if (re-search-backward "^;+[ \t]*user-fields:[ \t]*\(" nil t)
  1089.     (progn
  1090.       (goto-char (1- (match-end 0)))
  1091.       (setq bbdb-propnames
  1092.         (mapcar (function (lambda (x) (list (symbol-name x))))
  1093.             (read (point-marker)))))))
  1094.   ;; look backwards for file version, and convert if necessary.
  1095.   ;; (at least, I'll write this code if I ever change the file format again...)
  1096.   (let (v)
  1097.     (save-excursion
  1098.        (if (re-search-backward
  1099.         "^;+[ \t]*file-version:[ \t]*\\([0-9]+\\)[ \t]*$" nil t)
  1100.        (setq v (car (read-from-string
  1101.              (buffer-substring
  1102.               (match-beginning 1) (match-end 1)))))))
  1103.      (if (null v) ; version 2, but no file-version: line. Bootstrap it.
  1104.      (let ((modp (buffer-modified-p)))
  1105.        ;; This should never happen (not any more, anyway...)
  1106.        (bbdb-debug (error "bbdb corrupted: no file-version line"))
  1107.        (setq v 2)
  1108.        (save-excursion
  1109.          (if (re-search-backward "^;" nil t)
  1110.          (forward-line 1)
  1111.            (goto-char 1))
  1112.          ;; remember, this goes before the begin-marker of the first
  1113.          ;; record in the database!
  1114.          (insert-before-markers ";;; file-version: 2\n"))
  1115.        (set-buffer-modified-p modp)))
  1116.      (cond ((< v bbdb-file-format)
  1117.         ;; add calls to appropriate conversion routines here.
  1118.         (error "bbdb: how can the file format version possibly be less than 2?"))
  1119.        ((> v bbdb-file-format)
  1120.         (error "BBDB version %s doesn't understand file format version %s."
  1121.            bbdb-version bbdb-file-format))))
  1122.   
  1123.   (bbdb-debug
  1124.    (or (eobp) (looking-at "[\[]")
  1125.        (error "no following bracket: bbdb corrupted"))
  1126.    (if (save-excursion
  1127.      (save-restriction
  1128.        (widen)
  1129.        (save-excursion (search-backward "\n[" nil t))))
  1130.        (error "bbdb corrupted: records before point")))
  1131.  
  1132.   ;; narrow the buffer to skip over the rubbish before the first record.
  1133.   (narrow-to-region (point) (point-max))
  1134.   (let ((records nil))
  1135.     ;; insert parens so we can read the db in one fell swoop (down in C).
  1136.     (let ((buffer-read-only nil)
  1137.       (modp (buffer-modified-p))
  1138.       ;; Make sure those parens get cleaned up.
  1139.       ;; This code had better stay simple!
  1140.       (inhibit-quit t))
  1141.       (goto-char (point-min)) (insert "(\n")
  1142.       (goto-char (point-max)) (insert "\n)")
  1143.       (goto-char (point-min))
  1144.       (setq records (read (current-buffer)))
  1145.       (goto-char (point-min)) (delete-char 2)
  1146.       (goto-char (point-max)) (delete-char -2)
  1147.       (set-buffer-modified-p modp))
  1148.     ;; now we have to come up with a marker for each record.  Rather than
  1149.     ;; calling read for each record, we read them at once (already done) and
  1150.     ;; assume that the markers are at each newline.  If this isn't the case,
  1151.     ;; things can go *very* wrong.
  1152.     (goto-char (point-min))
  1153.     (while (looking-at "[ \t\n\f]*;")
  1154.       (goto-char (match-end 0))
  1155.       (forward-line 1))
  1156.     (widen)
  1157.     (bbdb-debug (message "Parsing BBDB... (frobnicating...)"))
  1158.     (let ((rest records)
  1159.       record new L)
  1160.       (while rest
  1161.     (setq record (car rest))
  1162.     ;; yow, are we stack-driven yet??  Damn byte-compiler...
  1163.     ;; Make a cache.  Put it in the record.  Put a marker in the cache.
  1164.     (bbdb-cache-set-marker
  1165.       (bbdb-record-set-cache record 
  1166.         (make-vector bbdb-cache-length nil))
  1167.       (point-marker))
  1168.     (bbdb-debug
  1169.      (let ((name (bbdb-record-name record))
  1170.            tmp)
  1171.        (if (and name
  1172.             (setq tmp (bbdb-gethash (setq name (downcase name))
  1173.                         bbdb-hashtable)))
  1174.            (signal 'error (list "duplicate bbdb entries" record tmp)))))
  1175.     (bbdb-hash-record record)
  1176.     (forward-line 1)
  1177.     (setq rest (cdr rest))
  1178.     (bbdb-debug
  1179.      (if (and rest (not (looking-at "[\[]")))
  1180.          (error "bbdb corrupted: junk between records at %s" (point))))
  1181.     ))
  1182.     ;; all done.
  1183.     (setq bbdb-records records)
  1184.     (setq bbdb-end-marker (point-marker))
  1185.     (run-hooks 'bbdb-after-read-db-hook)
  1186.     (bbdb-debug (message "Parsing BBDB... (frobnicating...done)"))
  1187.     records))
  1188.  
  1189.  
  1190. (defmacro bbdb-user-mail-names ()
  1191.   "Returns a regexp matching the address of the logged-in user"
  1192.   '(or bbdb-user-mail-names
  1193.     (setq bbdb-user-mail-names
  1194.      (concat "\\b" (regexp-quote (user-login-name)) "\\b"))))
  1195.  
  1196. (defun bbdb-write-file-hook-fn ()
  1197.   "Added to write-file-hooks locally to the bbdb-file buffer."
  1198.   ;; this is premature as the file isn't actually written yet; but it's just
  1199.   ;; for the benefit of the mode-line of the *BBDB* buffer, and there isn't
  1200.   ;; an after-write-file-hook, so it'll do.
  1201.   (setq bbdb-modified-p nil
  1202.     bbdb-changed-records nil)
  1203.   (let ((b (get-buffer bbdb-buffer-name)))
  1204.     (if b
  1205.     (bbdb-save-buffer-excursion
  1206.       (set-buffer b)
  1207.       (setq bbdb-showing-changed-ones nil)
  1208.       (set-buffer-modified-p nil)))))
  1209.  
  1210.  
  1211. (defun bbdb-delete-record-internal (record)
  1212.   (if (null (bbdb-record-marker record)) (error "bbdb: marker unpresent"))
  1213.   (bbdb-with-db-buffer
  1214.     (if (memq record bbdb-changed-records) nil
  1215.     (setq bbdb-changed-records (cons record bbdb-changed-records)))
  1216.     (let ((tail (memq record bbdb-records)))
  1217.       (if (null tail) (error "bbdb: unfound %s" record))
  1218.       (setq bbdb-records (delq record bbdb-records))
  1219.       (delete-region (bbdb-record-marker record)
  1220.              (if (cdr tail)
  1221.              (bbdb-record-marker (car (cdr tail)))
  1222.              bbdb-end-marker))
  1223.       (if (bbdb-record-name record)
  1224.       (let ((name (downcase (bbdb-record-name record))))
  1225.         (bbdb-remhash name bbdb-hashtable)))
  1226.       (let ((nets (bbdb-record-net record)))
  1227.     (while nets
  1228.       (bbdb-remhash (downcase (car nets)) bbdb-hashtable)
  1229.       (setq nets (cdr nets))))
  1230.       (let ((aka (bbdb-record-aka record)))
  1231.     (while aka
  1232.       (bbdb-remhash (downcase (car aka)) bbdb-hashtable)
  1233.       (setq aka (cdr aka))))
  1234.       (bbdb-record-set-sortkey record nil)
  1235.       (setq bbdb-modified-p t))))
  1236.  
  1237. (defun bbdb-insert-sorted (record records)
  1238.   "Inserts the RECORD into the list of RECORDS, in order (assuming the list is
  1239. already sorted.)  Returns the new head."
  1240.   (bbdb-debug (if (memq record records) (error "doubleplus ununique: - %s" record)))
  1241.   (let* ((rest (cons nil records))
  1242.      (top rest))
  1243.     (while (and (cdr rest)
  1244.         (bbdb-record-lessp (nth 1 rest) record))
  1245.       (setq rest (cdr rest)))
  1246.     (setcdr rest (cons record (cdr rest)))
  1247.     (cdr top)))
  1248.  
  1249. (defun bbdb-insert-record-internal (record)
  1250.   (if (null (bbdb-record-marker record))
  1251.       (bbdb-record-set-marker record (make-marker)))
  1252.   (bbdb-with-db-buffer
  1253.     (if (memq record bbdb-changed-records) nil
  1254.     (setq bbdb-changed-records (cons record bbdb-changed-records)))
  1255.     (let ((print-escape-newlines t))
  1256.       (bbdb-record-set-sortkey record nil) ; just in case...
  1257.       (setq bbdb-records
  1258.         (bbdb-insert-sorted record bbdb-records))
  1259.       (let ((next (car (cdr (memq record bbdb-records)))))
  1260.     (goto-char (if next
  1261.                (bbdb-record-marker next)
  1262.                bbdb-end-marker))
  1263.     ;; before printing the record, remove the cache \(we don't want that
  1264.     ;; written to the file.\)  Ater writing, put the cache back and update
  1265.     ;; the cache's marker.
  1266.     (let ((cache (bbdb-record-cache record))
  1267.           (point (point)))
  1268.       (bbdb-debug
  1269.        (if (= point (point-min))
  1270.            (error "doubleplus ungood: inserting at point-min (%s)" point))
  1271.        (if (and (/= point bbdb-end-marker)
  1272.             (not (looking-at "[\[]")))
  1273.            (error "doubleplus ungood: not inserting before a record (%s)"
  1274.               point))
  1275.        )
  1276.       (bbdb-record-set-cache record nil)
  1277.       (insert-before-markers (prin1-to-string record) "\n")
  1278.       (set-marker (bbdb-cache-marker cache) point)
  1279.       (bbdb-record-set-cache record cache)
  1280. ;;      (if (bbdb-record-name record)
  1281. ;;          (bbdb-puthash (downcase (bbdb-record-name record)) record bbdb-hashtable))
  1282. ;;      (let ((nets (bbdb-record-net record)))
  1283. ;;        (while nets
  1284. ;;          (bbdb-puthash (downcase (car nets)) record bbdb-hashtable)
  1285. ;;          (setq nets (cdr nets))))
  1286.       ;; This is marginally slower because it rebuilds the namecache,
  1287.       ;; but it makes jbw's life easier. :-\)
  1288.       (bbdb-hash-record record)
  1289.       )
  1290.     record))
  1291.     (setq bbdb-modified-p t)))
  1292.  
  1293. (defun bbdb-overwrite-record-internal (record)
  1294.   (bbdb-with-db-buffer
  1295.     (if (memq record bbdb-changed-records) nil
  1296.     (setq bbdb-changed-records (cons record bbdb-changed-records)))
  1297.     (let ((print-escape-newlines t)
  1298.       (tail bbdb-records))
  1299.       (while (and tail (not (eq record (car tail))))
  1300.     (setq tail (cdr tail)))
  1301.       (if (null tail) (error "bbdb: unfound %s" record))
  1302.       (let ((cache (bbdb-record-cache record)))
  1303.  
  1304.     (bbdb-debug
  1305.      (if (<= (bbdb-cache-marker cache) (point-min))
  1306.          (error "doubleplus ungood: cache marker is %s"
  1307.             (bbdb-cache-marker cache)))
  1308.      (goto-char (bbdb-cache-marker cache))
  1309.      (if (and (/= (point) bbdb-end-marker)
  1310.           (not (looking-at "[\[]")))
  1311.          (error "doubleplus ungood: not inserting before a record (%s)"
  1312.             (point)))
  1313.      )
  1314.  
  1315.     (goto-char (bbdb-cache-marker cache))
  1316.     (bbdb-record-set-cache record nil)
  1317.  
  1318.     (insert (prin1-to-string record) "\n")
  1319.     (delete-region (point)
  1320.                (if (cdr tail)
  1321.                (bbdb-record-marker (car (cdr tail)))
  1322.              bbdb-end-marker))
  1323.     (bbdb-record-set-cache record cache)
  1324.  
  1325.     (bbdb-debug
  1326.      (if (<= (if (cdr tail)
  1327.              (bbdb-record-marker (car (cdr tail)))
  1328.            bbdb-end-marker)
  1329.          (bbdb-record-marker record))
  1330.          (error "doubleplus ungood: overwrite unworks")))
  1331.  
  1332.     (setq bbdb-modified-p t)
  1333.     record))))
  1334.  
  1335. (defvar inside-bbdb-change-record nil "hands off")
  1336.  
  1337. (defun bbdb-change-record (record need-to-sort)
  1338.   "Update the database after a change to the given record.  Second arg 
  1339. NEED-TO-SORT is whether the name has changed.  You still need to worry 
  1340. about updating the name hash-table."
  1341.   (if inside-bbdb-change-record
  1342.       record
  1343.     (let ((inside-bbdb-change-record t))
  1344.       (bbdb-invoke-hook 'bbdb-change-hook record)
  1345.       (bbdb-debug (if (bbdb-record-deleted-p record)
  1346.               (error "bbdb: changing deleted record")))
  1347.       (if (memq record (bbdb-records))    ; this checks file synchronization too.
  1348.       (if (not need-to-sort)
  1349.           (progn
  1350.         (bbdb-overwrite-record-internal record)
  1351.         (bbdb-debug
  1352.          (if (not (memq record (bbdb-records)))
  1353.              (error "doubleplus ungood: overwrite unworks"))))
  1354.         (bbdb-delete-record-internal record)
  1355.         (bbdb-debug
  1356.          (if (memq record (bbdb-records))
  1357.          (error "doubleplus ungood: delete unworks")))
  1358.         (bbdb-insert-record-internal record)
  1359.         (bbdb-debug
  1360.          (if (not (memq record (bbdb-records))) (error "insert unworks")))
  1361.         )
  1362.     ;; else it's deleted -- add it.
  1363.     (bbdb-insert-record-internal record)
  1364.     (bbdb-debug (if (not (memq record (bbdb-records)))
  1365.             (error "doubleplus ungood: insert unworks")))
  1366.     )
  1367.       (setq bbdb-modified-p t)
  1368.       (bbdb-invoke-hook 'bbdb-after-change-hook record)
  1369.       record)))
  1370.  
  1371. (defmacro bbdb-propnames ()
  1372.   '(bbdb-with-db-buffer bbdb-propnames))
  1373.  
  1374. (defun bbdb-set-propnames (newval)
  1375.   (bbdb-with-db-buffer
  1376.     (setq bbdb-propnames newval)
  1377.     (widen)
  1378.     (goto-char (point-min))
  1379.     (and (not (eq (following-char) ?\[))
  1380.      (search-forward "\n[" nil 0))
  1381.     (if (re-search-backward "^[ \t]*;+[ \t]*user-fields:[ \t]*\(" nil t)
  1382.     (progn
  1383.       (goto-char (1- (match-end 0)))
  1384.       (delete-region (point) (progn (end-of-line) (point))))
  1385.       (and (re-search-backward "^[ \t]*;.*\n" nil t)
  1386.        (goto-char (match-end 0)))
  1387.       ;; remember, this goes before the begin-marker of the first
  1388.       ;; record in the database!
  1389.       (insert-before-markers ";;; user-fields: \n")
  1390.       (forward-char -1))
  1391.     (prin1 (mapcar (function (lambda (x) (intern (car x))))
  1392.            bbdb-propnames)
  1393.        (current-buffer))
  1394.     bbdb-propnames))
  1395.  
  1396. (defun bbdb-modified-p ()
  1397.   (setq bbdb-modified-p
  1398.     (buffer-modified-p (find-file-noselect bbdb-file 'nowarn))))
  1399.  
  1400.  
  1401. ;;; BBDB mode
  1402.  
  1403. (defun bbdb-mode ()
  1404.   "Major mode for viewing and editing the Insidious Big Brother Database.
  1405. Letters no longer insert themselves.  Numbers are prefix arguments.
  1406. You can move around using the usual cursor motion commands.
  1407. \\<bbdb-mode-map>
  1408. \\[bbdb-edit-current-field]\t edit the field on the current line.
  1409. \\[bbdb-record-edit-notes]\t shortcut for editing the 'notes' field.
  1410. \\[advertized-bbdb-delete-current-field-or-record]\t delete the field on the \
  1411. current line.  If the current line is the\n\t first line of a record, then \
  1412. this deletes the entire record from\n\t the database.
  1413. \\[bbdb-insert-new-field]\t inserts a new field into the current record, as \
  1414. opposed to editing\n\t an existing one.  Note that this will let you add \
  1415. new fields of your\n\t own as well.
  1416. \\[bbdb-next-record], \\[bbdb-prev-record]\t move to the next and previous \
  1417. displayed record, respectively.
  1418. \\[bbdb-elide-record]\t toggles whether the current record is displayed in a \
  1419. one-line\n\t listing, or a full multi-line listing.
  1420. \\[bbdb-apply-next-command-to-all-records]\\[bbdb-elide-record]\t does that \
  1421. for all records.
  1422. \\[bbdb-send-mail]\t lets you compose mail to the person represented by the \
  1423. current record.
  1424. \\[bbdb-apply-next-command-to-all-records]\\[bbdb-send-mail]\t sends a mail \
  1425. message to everyone listed in the BBDB \
  1426. buffer.
  1427. \\[bbdb-save-db]\t saves the BBDB file to disk.
  1428. \\[bbdb-refile-record]\t merges the contents of this record with some other, \
  1429. and then deletes\n\t this one.  See this command's documentation.
  1430. \\[bbdb-finger]\t fingers the network address of the current record.
  1431. \\[bbdb-apply-next-command-to-all-records]\\[bbdb-finger]\t fingers the \
  1432. network address of all displayed records.
  1433. \\[bbdb-omit-record]\t removes the current record from the display without \
  1434. deleting it\n\t from the database.  This is often a useful thing to do before \
  1435. using\n\t one of the `*' commands.
  1436. \\[bbdb-info]\t enters the Info node (online documentation) for BBDB.
  1437. \\[bbdb-help]\t displays a one-line command-summary in the echo-area.
  1438.  
  1439. In send-mail mode, \\<mail-mode-map>\\[bbdb-complete-name] does completion \
  1440. across the set of names and network \naddresses in the database.
  1441.  
  1442. Variables of note:
  1443.     bbdb-file
  1444.     bbdb-electric-p
  1445.     bbdb-use-pop-up
  1446.     bbdb-pop-up-target-lines
  1447.     bbdb-readonly-p
  1448.     bbdb-notice-auto-save-file
  1449.     bbdb/mail-auto-create-p
  1450.     bbdb/news-auto-create-p
  1451.     bbdb-quiet-about-name-mismatches
  1452.     bbdb-completion-type
  1453.     bbdb-default-area-code
  1454.     bbdb-north-american-phone-numbers-p
  1455.  
  1456. There are numerous hooks.  M-x apropos ^bbdb.*hook RET
  1457.  
  1458. The keybindings, more precisely:
  1459. \\{bbdb-mode-map}"
  1460.   (setq major-mode 'bbdb-mode)
  1461.   (setq mode-name "BBDB")
  1462.   (use-local-map bbdb-mode-map)
  1463.   (run-hooks 'bbdb-mode-hook))
  1464.  
  1465. ;;; these should be in bbdb-com.el but they're so simple, why load it all.
  1466.  
  1467. (defun bbdb-next-record (p)
  1468.   "Move the cursor to the first line of the next bbdb-record."
  1469.   (interactive "p")
  1470.   (if (< p 0)
  1471.       (bbdb-prev-record (- p))
  1472.     (forward-char)
  1473.     (while (> p 0)
  1474.       (or (re-search-forward "^[^ \t\n]" nil t)
  1475.       (progn (beginning-of-line)
  1476.          (error "no next record")))
  1477.       (setq p (1- p)))
  1478.     (beginning-of-line)))
  1479.  
  1480. (defun bbdb-prev-record (p)
  1481.   "Move the cursor to the first line of the previous bbdb-record."
  1482.   (interactive "p")
  1483.   (if (< p 0)
  1484.       (bbdb-next-record (- p))
  1485.     (while (> p 0)
  1486.       (or (re-search-backward "^[^ \t\n]" nil t)
  1487.       (error "no previous record"))
  1488.       (setq p (1- p)))))
  1489.  
  1490.  
  1491. (defun bbdb-maybe-update-display (bbdb-record)
  1492.   (save-excursion
  1493.     (save-window-excursion
  1494.       (let ((w (get-buffer-window bbdb-buffer-name))
  1495.         (b (current-buffer)))
  1496.     (if w
  1497.         (unwind-protect
  1498.         (progn (set-buffer bbdb-buffer-name)
  1499.                (save-restriction
  1500.              (if (assq bbdb-record bbdb-records)
  1501.                  (bbdb-redisplay-records))))
  1502.           (set-buffer b)))))))
  1503.  
  1504. (defun bbdb-annotate-notes (bbdb-record annotation &optional fieldname)
  1505.   (or bbdb-record (error "unperson"))
  1506.   (setq annotation (bbdb-string-trim annotation))
  1507.   (if (memq fieldname '(name address addresses phone phones net aka AKA))
  1508.       (error "bbdb: cannot annotate the %s field this way" fieldname))
  1509.   (or fieldname (setq fieldname 'notes))
  1510.   (or (memq fieldname '(notes company))
  1511.       (assoc (symbol-name fieldname) (bbdb-propnames))
  1512.       (bbdb-set-propnames (append (bbdb-propnames)
  1513.                   (list (list (symbol-name fieldname))))))
  1514.   (if (string= "" annotation)
  1515.       nil
  1516.     (let ((notes (bbdb-string-trim
  1517.            (or (bbdb-record-getprop bbdb-record fieldname) ""))))
  1518.       (bbdb-record-putprop bbdb-record fieldname
  1519.                (if (string= notes "")
  1520.                    annotation
  1521.                    (concat notes
  1522.                        (if (eq fieldname 'company) "; "
  1523.                      (or (get fieldname 'field-separator)
  1524.                          "\n"))
  1525.                        annotation)))
  1526.       (bbdb-maybe-update-display bbdb-record))))
  1527.  
  1528.  
  1529. (defun bbdb-offer-save ()
  1530.   "Offer to save the Insidious Big Brother Database if it is modified."
  1531.   (if bbdb-offer-save
  1532.       (bbdb-save-db (eq bbdb-offer-save t))))
  1533.  
  1534. (defun bbdb-save-db (&optional prompt-first mention-if-not-saved)
  1535.   "save the db if it is modified."
  1536.   (interactive (list nil t))
  1537.   (bbdb-with-db-buffer
  1538.     (if (and (buffer-modified-p)
  1539.          (or (null prompt-first)
  1540.          (if bbdb-readonly-p
  1541.              (bbdb-y-or-n-p "Save the BBDB, even though it's supposedly read-only? ")
  1542.              (bbdb-y-or-n-p "Save the BBDB now? "))))
  1543.     (save-buffer)
  1544.       (if mention-if-not-saved (message "BBDB unsaved")))))
  1545.  
  1546. ;(defun bbdb-dry-heaves (sort-p message-p)
  1547. ;  "Rewrite each and every record in the bbdb file; this is necessary if we 
  1548. ;are updating an old file format.  SORT-P says whether we need to resort the
  1549. ;entire db (you should never need this).  MESSAGE-P says whether to sound
  1550. ;off for each record converted."
  1551. ;  (let ((records (bbdb-records))
  1552. ;    (i 0))
  1553. ;    (while records
  1554. ;      (bbdb-change-record (car records) sort-p)
  1555. ;      (setq records (cdr records))
  1556. ;      (if message-p (message "%d" (setq i (1+ i)))))))
  1557.  
  1558.  
  1559. (defun bbdb-add-hook (hook-var function)
  1560.   (if (not (boundp hook-var)) (set hook-var nil))
  1561.   (if (or (not (listp (symbol-value hook-var)))
  1562.       (eq (car (symbol-value hook-var)) 'lambda))
  1563.       (set hook-var (list (symbol-value hook-var))))
  1564.   (if (memq function (symbol-value hook-var))
  1565.       nil
  1566.       (set hook-var (cons function (symbol-value hook-var)))))
  1567.  
  1568.  
  1569. ;;; mail and news interface
  1570.  
  1571. (defun bbdb-clean-username (string)
  1572.   "Strips garbage from the user full name string."
  1573.   ;; This function is called a lot, and should be fast.  But I'm loathe to
  1574.   ;; remove any of the functionality in it.
  1575.   (if (string-match "[@%!]" string)  ; ain't no user name!  It's an address!
  1576.       (bbdb-string-trim string)
  1577.    (let ((case-fold-search t))
  1578.      ;; Take off leading and trailing non-alpha chars \(quotes, parens,
  1579.      ;; digits, etc) and things which look like phone extensions \(like
  1580.      ;; "x1234" and "ext. 1234". \)
  1581.      ;; This doesn't work all the time because some of our friends in
  1582.      ;; northern europe have brackets in their names...
  1583.      (if (string-match "\\`[^a-z]+" string)
  1584.      (setq string (substring string (match-end 0))))
  1585.      (while (string-match
  1586.          "\\(\\W+\\([Xx]\\|[Ee]xt\\.?\\)\\W*[-0-9]+\\|[^a-z]+\\)\\'"
  1587.          string)
  1588.        (setq string (substring string 0 (match-beginning 0))))
  1589.      ;; replace tabs, multiple spaces, dots, and underscores with a single space.
  1590.      ;; but don't replace ". " with " " because that could be an initial.
  1591.      (while (string-match "\\(\t\\|  +\\|\\(\\.\\)[^ \t_]\\|_+\\)" string)
  1592.        (setq string (concat (substring string 0
  1593.                        (or (match-beginning 2)
  1594.                        (match-beginning 1)))
  1595.                 " "
  1596.                 (substring string (or (match-end 2)
  1597.                           (match-end 1))))))
  1598.      ;; If the string contains trailing parenthesized comments, nuke 'em.
  1599.      (if (string-match "[^ \t]\\([ \t]*\\((\\| -\\| #\\)\\)" string)
  1600.      (progn
  1601.        (setq string (substring string 0 (match-beginning 1)))
  1602.        ;; lose rubbish this may have exposed.
  1603.        (while
  1604.            (string-match
  1605.         "\\(\\W+\\([Xx]\\|[Ee]xt\\.?\\)\\W*[-0-9]+\\|[^a-z]+\\)\\'"
  1606.         string)
  1607.            (setq string (substring string 0 (match-beginning 0))))
  1608.        ))
  1609.      string)))
  1610.  
  1611. ;;; message-caching, to speed up the the mail interfaces
  1612.  
  1613. (defvar bbdb-buffers-with-message-caches '()
  1614.   "A list of all the buffers which have stuff on their bbdb-message-cache 
  1615. local variable.  When we re-parse the .bbdb file, we need to flush all of
  1616. these caches.")
  1617.  
  1618. (defun notice-buffer-with-cache (buffer)
  1619.   (or (memq buffer bbdb-buffers-with-message-caches)
  1620.       (progn
  1621.     ;; First remove any deleted buffers which may have accumulated.
  1622.     ;; This happens only when a buffer is added to the list, so it
  1623.     ;; ought not happen that frequently (each time you read mail, say.)
  1624.     (let ((rest bbdb-buffers-with-message-caches))
  1625.       (while rest
  1626.         (if (null (buffer-name (car rest)))
  1627.         (setq bbdb-buffers-with-message-caches
  1628.               (delq (car rest) bbdb-buffers-with-message-caches)))
  1629.         (setq rest (cdr rest))))
  1630.     ;; now add this buffer.
  1631.     (setq bbdb-buffers-with-message-caches
  1632.           (cons buffer bbdb-buffers-with-message-caches)))))
  1633.  
  1634. (make-variable-buffer-local 'bbdb-message-cache)
  1635.  
  1636. (defmacro bbdb-message-cache-lookup (message-key
  1637.                      &optional message-sequence-buffer)
  1638.   (list 'progn '(bbdb-records)  ; yuck, this is to make auto-revert happen
  1639.                 ; in a convenient place.
  1640.   (list 'and 'bbdb-message-caching-enabled
  1641.     (let ((bod
  1642.            (list 'let (list (list '--cons--
  1643.                       (list 'assq message-key 'bbdb-message-cache)))
  1644.              '(if (and --cons-- (bbdb-record-deleted-p (cdr --cons--)))
  1645.                (progn
  1646.              (setq bbdb-message-cache (delq --cons-- bbdb-message-cache))
  1647.              nil)
  1648.                (cdr --cons--)))))
  1649.       (if message-sequence-buffer
  1650.           (list 'save-excursion
  1651.             (list 'set-buffer message-sequence-buffer)
  1652.             bod)
  1653.           bod))))
  1654.   )
  1655.  
  1656. (defmacro bbdb-encache-message (message-key bbdb-record &optional message-sequence-buffer)
  1657.   "Don't call this multiple times with the same args, it doesn't replace."
  1658.   (let ((bod (list 'let (list (list '--rec-- bbdb-record))
  1659.            (list 'if 'bbdb-message-caching-enabled
  1660.              (list 'and '--rec--
  1661.               (list 'progn
  1662.                '(notice-buffer-with-cache (current-buffer))
  1663.                (list 'cdr
  1664.                 (list 'car
  1665.                  (list 'setq 'bbdb-message-cache
  1666.                   (list 'cons (list 'cons message-key '--rec--)
  1667.                     'bbdb-message-cache))))))
  1668.              '--rec--))))
  1669.     (if message-sequence-buffer
  1670.     (cons 'save-excursion
  1671.           (list (list 'set-buffer message-sequence-buffer)
  1672.             bod))
  1673.     bod)))
  1674.  
  1675. (defun bbdb-flush-all-caches ()
  1676.   (bbdb-debug
  1677.     (and bbdb-buffers-with-message-caches
  1678.      (message "Flushing BBDB caches")))
  1679.   (save-excursion
  1680.     (while bbdb-buffers-with-message-caches
  1681.       (if (buffer-name (car bbdb-buffers-with-message-caches))
  1682.       (progn
  1683.         (set-buffer (car bbdb-buffers-with-message-caches))
  1684.         (setq bbdb-message-cache nil)))
  1685.       (setq bbdb-buffers-with-message-caches
  1686.         (cdr bbdb-buffers-with-message-caches)))))
  1687.  
  1688.  
  1689. (defconst bbdb-name-gubbish
  1690.   (concat "[-,. \t/\\]+\\("
  1691.       "[JjSs]r\\.?"
  1692.       "\\|V?\\(I\\.?\\)+V?"
  1693.       "\\)\\W*\\'"))
  1694.  
  1695. (defun bbdb-divide-name (string)
  1696.   "divide the string into a first name and a last name, cleverly."
  1697.   ;; ## This shouldn't be here.
  1698.   (if (string-match "\\W+\\([Xx]\\|[Ee]xt\\.?\\)\\W*[-0-9]+\\'" string)
  1699.       (setq string (substring string 0 (match-beginning 0))))
  1700.   (let* ((case-fold-search nil)
  1701.      (str string)
  1702.      (gubbish (string-match bbdb-name-gubbish string)))
  1703.     (if gubbish
  1704.     (setq gubbish (substring str gubbish)
  1705.           str (substring string 0 (match-beginning 0))))
  1706.     (if (string-match " +\\(\\([^ ]+ *- *\\)?[^ ]+\\)\\'" str)
  1707.     (list (substring str 0 (match-beginning 0))
  1708.           (concat
  1709.            (substring str (match-beginning 1))
  1710.            (or gubbish "")))
  1711.       (list string ""))))
  1712.  
  1713. (defun bbdb-check-alternate-name (possible-name record)
  1714.   (let (aka)
  1715.     (if (setq aka (bbdb-record-aka record))
  1716.     (let ((down-name (downcase possible-name))
  1717.           match)
  1718.       (while aka
  1719.         (if (equal down-name (downcase (car aka)))
  1720.         (setq match (car aka)
  1721.               aka nil)
  1722.         (setq aka (cdr aka))))
  1723.       match))))
  1724.  
  1725.  
  1726. (defun bbdb-canonicalize-address (net)
  1727.   ;; call the bbdb-canonicalize-net-hook repeatedly until it returns a
  1728.   ;; value eq to the value passed in.  This implies that it can't
  1729.   ;; destructively modify the string.
  1730.   (while (not (eq net (setq net (funcall bbdb-canonicalize-net-hook net)))))
  1731.   net)
  1732.  
  1733. (defun bbdb-annotate-message-sender (from &optional loudly create-p
  1734.                      prompt-to-create-p)
  1735.   "Fills the record corresponding to the sender with as much info as possible.
  1736. A record may be created by this; a record or nil is returned.
  1737. If bbdb-readonly-p is true, then a record will never be created.
  1738. If CREATE-P is true, then a record may be created, otherwise it won't.
  1739. If PROMPT-TO-CREATE-P is true, then the user will be asked for confirmation
  1740. before the record is created, otherwise it is created without confirmation 
  1741. \(assuming that CREATE-P is true\).  "
  1742.   (let* ((data (mail-extract-address-components from))
  1743.      (name (car data))
  1744.      (net (car (cdr data))))
  1745.     (if (equal name net) (setq name nil))
  1746.     (bbdb-debug
  1747.      (if (equal name "") (error "mail-extr returned \"\" as name"))
  1748.      (if (equal net "") (error "mail-extr returned \"\" as net")))
  1749.  
  1750.   (if (and net bbdb-canonicalize-net-hook)
  1751.       (setq net (bbdb-canonicalize-address net)))
  1752.  
  1753.   (let ((change-p nil)
  1754.     (record (bbdb-search-simple name net))
  1755.     (created-p nil)
  1756.     (fname name)
  1757.     (lname nil)
  1758.     old-name
  1759.     bogon-mode)
  1760.     (and record (setq old-name (bbdb-record-name record)))
  1761.  
  1762.     ;; This is to prevent having losers like "John <blat@foop>" match
  1763.     ;; against existing records like "Someone Else <john>".
  1764.     ;;
  1765.     ;; The solution implemented here is to never create or show records
  1766.     ;; corresponding to a person who has a real-name which is the same
  1767.     ;; as the network-address of someone in the db already.  This is not
  1768.     ;; a good solution.
  1769.     (let (down-name old-net)
  1770.       (if (and record name
  1771.            (not (equal (setq down-name (downcase name))
  1772.                (and old-name (downcase old-name)))))
  1773.       (progn
  1774.         (setq old-net (bbdb-record-net record))
  1775.         (while old-net
  1776.           (if (equal down-name (downcase (car old-net)))
  1777.           (progn
  1778.             (setq bogon-mode t
  1779.               old-net nil)
  1780.             (message
  1781.      "Ignoring bogon %s's name \"%s\" to avoid name-clash with \"%s\""
  1782.                  net name old-name)
  1783.             (sit-for 2))
  1784.         (setq old-net (cdr old-net)))))))
  1785.     
  1786.     (if (or record
  1787.         bbdb-readonly-p
  1788.         (not create-p)
  1789.         (not (or name net))
  1790.         bogon-mode)
  1791.     ;; no further action required
  1792.     nil
  1793.       ;; otherwise, the db is writable, and we may create a record.
  1794.       (setq record (if (or (null prompt-to-create-p)
  1795.                (bbdb-y-or-n-p (format "%s is not in the db; rectify? " name)))
  1796.                (make-vector bbdb-record-length nil))
  1797.         created-p (not (null record)))
  1798.       (if record
  1799.       (bbdb-record-set-cache record (make-vector bbdb-cache-length nil)))
  1800.       (if created-p (bbdb-invoke-hook 'bbdb-create-hook record)))
  1801.     (if (or bogon-mode (null record))
  1802.     nil
  1803.       (bbdb-debug (if (bbdb-record-deleted-p record)
  1804.               (error "nasty nasty deleted record nasty.")))
  1805.       (if (and name
  1806.            (not (equal (and name (downcase name))
  1807.                (and old-name (downcase old-name))))
  1808.            (or (null bbdb-use-alternate-names)
  1809.            (not (bbdb-check-alternate-name name record)))
  1810.            (let ((fullname (bbdb-divide-name name))
  1811.              tmp)
  1812.          (setq fname (car fullname)
  1813.                lname (nth 1 fullname))
  1814.          (not (and (equal (downcase fname)
  1815.                 (and (setq tmp (bbdb-record-firstname record))
  1816.                      (downcase tmp)))
  1817.                (equal (downcase lname)
  1818.                   (and (setq tmp (bbdb-record-lastname record))
  1819.                        (downcase tmp)))))))
  1820.       ;; have a message-name, not the same as old name.
  1821.       (cond (bbdb-readonly-p nil)
  1822.         ;;(created-p nil)
  1823.         ((and bbdb-quiet-about-name-mismatches old-name)
  1824.          (message "name mismatch: \"%s\" changed to \"%s\""
  1825.               (bbdb-record-name record) name)
  1826.          (sit-for 1))
  1827.         ((or created-p
  1828.              (if (null old-name)
  1829.              (bbdb-y-or-n-p (format "Assign name \"%s\" to address \"%s\"? "
  1830.                         name (car (bbdb-record-net record))))
  1831.                (bbdb-y-or-n-p (format "Change name \"%s\" to \"%s\"? "
  1832.                           old-name name))))
  1833.          (setq change-p 'sort)
  1834.          (and old-name bbdb-use-alternate-names
  1835.              (if (bbdb-y-or-n-p (format "Keep name \"%s\" as an AKA? " old-name))
  1836.              (bbdb-record-set-aka record
  1837.                (cons old-name (bbdb-record-aka record)))
  1838.                (bbdb-remhash (downcase old-name))))
  1839.          (bbdb-record-set-namecache record nil)
  1840.          (bbdb-record-set-firstname record fname)
  1841.          (bbdb-record-set-lastname record lname)
  1842.          (bbdb-debug (or fname lname
  1843.                  (error "bbdb: should have a name by now")))
  1844.          (bbdb-puthash (downcase (bbdb-record-name record))
  1845.                    record)
  1846.          )
  1847.         ((and old-name
  1848.               bbdb-use-alternate-names
  1849.               (bbdb-y-or-n-p
  1850.             (format "Make \"%s\" an alternate for \"%s\"? "
  1851.                 name old-name)))
  1852.          (setq change-p 'sort)
  1853.          (bbdb-record-set-aka
  1854.            record
  1855.            (cons name (bbdb-record-aka record)))
  1856.          (bbdb-puthash (downcase name) record)
  1857.          )))
  1858.       (if (and net (not bbdb-readonly-p))
  1859.       (if (null (bbdb-record-net record))
  1860.           ;; names are always a sure match, so don't bother prompting here.
  1861.           (progn (bbdb-record-set-net record (list net))
  1862.              (bbdb-puthash (downcase net) record) ; important!
  1863.              (or change-p (setq change-p t)))
  1864.         ;; new address; ask before adding.
  1865.         (if (let ((rest-net (bbdb-record-net record))
  1866.               (new (downcase net))
  1867.               (match nil))
  1868.           (while (and rest-net (null match))
  1869.             (setq match (string= new (downcase (car rest-net)))
  1870.               rest-net (cdr rest-net)))
  1871.           match)
  1872.         nil
  1873.           (if (cond
  1874.            ((eq bbdb-always-add-addresses t)
  1875.             t)
  1876.            (bbdb-always-add-addresses ; non-t and non-nil = never
  1877.             nil)
  1878.            (t
  1879.             (and
  1880.              (not (equal net "???"))
  1881.              (let ((the-first-bit
  1882.                 (format "add address \"%s\" to \"" net))
  1883.                ;; this groveling is to prevent the "(y or n)" from
  1884.                ;; falling off the right edge of the screen.
  1885.                (the-next-bit (mapconcat 'identity
  1886.                             (bbdb-record-net record)
  1887.                             ", "))
  1888.                (w (window-width (minibuffer-window))))
  1889.                (if (> (+ (length the-first-bit)
  1890.                  (length the-next-bit) 15) w)
  1891.                (setq the-next-bit
  1892.                  (concat
  1893.                   (substring the-next-bit
  1894.                     0 (max 0 (- w (length the-first-bit) 20)))
  1895.                   "...")))
  1896.                (bbdb-y-or-n-p (concat the-first-bit the-next-bit
  1897.                           "\"? "))))))
  1898.           (let ((front-p (cond ((null bbdb-new-nets-always-primary)
  1899.                     (bbdb-y-or-n-p
  1900.                      (format
  1901.                       "Make \"%s\" the primary address? "
  1902.                       net)))
  1903.                        ((eq bbdb-new-nets-always-primary t)
  1904.                     t)
  1905.                        (t nil))))
  1906.             (bbdb-record-set-net record
  1907.               (if front-p
  1908.               (cons net (bbdb-record-net record))
  1909.             (nconc (bbdb-record-net record) (list net))))
  1910.             (bbdb-puthash (downcase net) record)  ; important!
  1911.             (or change-p (setq change-p t)))))))
  1912.       (bbdb-debug
  1913.     (if (and change-p bbdb-readonly-p)
  1914.         (error
  1915.           "doubleplus ungood: how did we change anything in readonly mode?")))
  1916.       (if (and loudly change-p)
  1917.       (if (eq change-p 'sort)
  1918.           (message "noticed \"%s\"" (bbdb-record-name record))
  1919.           (if (bbdb-record-name record)
  1920.           (message "noticed %s's address \"%s\"" (bbdb-record-name record) net)
  1921.           (message "noticed naked address \"%s\"" net))))
  1922.       (if change-p
  1923.       (bbdb-change-record record (eq change-p 'sort)))
  1924.       (bbdb-invoke-hook 'bbdb-notice-hook record)
  1925.       record))))
  1926.  
  1927.  
  1928. ;;; window configuration hackery
  1929.  
  1930. (defun bbdb-pop-up-bbdb-buffer (&optional horiz-predicate)
  1931.   "Find the largest window on the screen, and split it, displaying the
  1932. *BBDB* buffer in the bottom 'bbdb-pop-up-target-lines' lines (unless
  1933. the *BBDB* buffer is already visible, in which case do nothing.)
  1934.  
  1935. If 'bbdb-use-pop-up' is the symbol 'horiz, and the first window 
  1936. matching HORIZ-PREDICATE is sufficiently wide (> 100 columns) then
  1937. the window will be split vertically rather than horizontally."
  1938.   (let ((b (current-buffer)))
  1939.    (if (get-buffer-window bbdb-buffer-name)
  1940.        nil
  1941.      (if (and (eq bbdb-use-pop-up 'horiz)
  1942.           horiz-predicate
  1943.           (bbdb-pop-up-bbdb-buffer-horizontally horiz-predicate))
  1944.      nil
  1945.       (let* ((first-window (selected-window))
  1946.          (tallest-window first-window)
  1947.          (window first-window))
  1948.     ;; find the tallest window...
  1949.     (while (not (eq (setq window (previous-window window)) first-window))
  1950.       (if (> (window-height window) (window-height tallest-window))
  1951.           (setq tallest-window window)))
  1952.     ;; select it and split it...
  1953.     (select-window tallest-window)
  1954.     (let ((size (min
  1955.               (- (window-height tallest-window)
  1956.              window-min-height 1)
  1957.               (- (window-height tallest-window)
  1958.              (max window-min-height
  1959.                   (1+ bbdb-pop-up-target-lines))))))
  1960.       (split-window tallest-window
  1961.             (if (> size 0) size window-min-height)))
  1962.     (if (memq major-mode
  1963.           '(gnus-Group-mode gnus-Subject-mode gnus-Article-mode))
  1964.         (goto-char (point-min))) ; make gnus happy...
  1965.     ;; goto the bottom of the two...
  1966.     (select-window (next-window))
  1967.     ;; make it display *BBDB*...
  1968.     (let ((pop-up-windows nil))
  1969.       (switch-to-buffer (get-buffer-create bbdb-buffer-name)))
  1970.     ;; select the original window we were in...
  1971.     (select-window first-window)))
  1972.     ;; and make sure the current buffer is correct as well.
  1973.     (set-buffer b)
  1974.     nil)))
  1975.  
  1976. (defun bbdb-pop-up-bbdb-buffer-horizontally (predicate)
  1977.   (if (<= (screen-width) 112)
  1978.       nil
  1979.     (let* ((first-window (selected-window))
  1980.        (got-it nil)
  1981.        (window first-window))
  1982.       (while (and (not (setq got-it (funcall predicate window)))
  1983.           (not (eq first-window (setq window (next-window window)))))
  1984.     )
  1985.       (if (or (null got-it)
  1986.           (<= (window-width window) 112))
  1987.       nil
  1988.     (let ((b (current-buffer)))
  1989.       (select-window window)
  1990.       (split-window-horizontally 80)
  1991.       (select-window (next-window window))
  1992.       (let ((pop-up-windows nil))
  1993.         (switch-to-buffer (get-buffer-create bbdb-buffer-name)))
  1994.       (select-window first-window)
  1995.       (set-buffer b)
  1996.       t)))))
  1997.  
  1998. (defun bbdb-version () (interactive) (message "BBDB version %s" bbdb-version))
  1999.  
  2000. ;;; resorting, which really shouldn't be necesary...
  2001.  
  2002. (defun bbdb-record-lessp-fn (record1 record2) ; for use as a funarg
  2003.   (bbdb-record-lessp record1 record2))
  2004.  
  2005. (defun bbdb-resort-database ()
  2006.   ;; only as a last resort, ha ha
  2007.   (let* ((records (copy-sequence (bbdb-records))))
  2008.     (bbdb-with-db-buffer
  2009.      (setq bbdb-records (sort bbdb-records 'bbdb-record-lessp-fn))
  2010.      (if (equal records bbdb-records)
  2011.      nil
  2012.        (message "DANGER!  BBDB was mis-sorted; it's being fixed...")
  2013.        (goto-char (point-min))
  2014.        (cond ((eq (following-char) ?\[) nil)
  2015.          ((search-forward "\n[" nil 0) (forward-char -1)))
  2016.        (delete-region (point) bbdb-end-marker)
  2017.        (let ((print-escape-newlines t)
  2018.          (standard-output (current-buffer))
  2019.          (inhibit-quit t) ; really, don't fuck with this
  2020.          record cache)
  2021.      (setq records bbdb-records)
  2022.      (while records
  2023.        (setq record (car records)
  2024.          cache (bbdb-record-cache record))
  2025.        (bbdb-record-set-cache record nil)
  2026.        (prin1 (car records))
  2027.        (bbdb-record-set-cache record cache)
  2028.        (insert ?\n)
  2029.        (setq records (cdr records))))
  2030.        (kill-all-local-variables)
  2031.        (error "the BBDB was mis-sorted: it has been repaired.")))))
  2032.  
  2033.  
  2034. (defmacro safe-require (thing)
  2035.   (list 'condition-case nil (list 'require thing) '(error nil)))
  2036.  
  2037. (if (featurep 'rmail) (safe-require 'bbdb-rmail))
  2038. (if (featurep 'gnus)  (safe-require 'bbdb-gnus))
  2039. (if (featurep 'vm)    (safe-require 'bbdb-vm))
  2040. (if (featurep 'mh-e)  (safe-require 'bbdb-mhe))
  2041.  
  2042. ;;; Keybindings
  2043.  
  2044. (fset 'advertized-bbdb-delete-current-field-or-record
  2045.       'bbdb-delete-current-field-or-record)
  2046.  
  2047. (if bbdb-mode-map
  2048.     nil
  2049.   (setq bbdb-mode-map (make-keymap))
  2050.   (suppress-keymap bbdb-mode-map)
  2051.   (define-key bbdb-mode-map "*"      'bbdb-apply-next-command-to-all-records)
  2052.   (define-key bbdb-mode-map "e"      'bbdb-edit-current-field)
  2053.   (define-key bbdb-mode-map "n"      'bbdb-next-record)
  2054.   (define-key bbdb-mode-map "p"      'bbdb-prev-record)
  2055.   (define-key bbdb-mode-map "d"      'advertized-bbdb-delete-current-field-or-record)
  2056.   (define-key bbdb-mode-map "\^K"    'bbdb-delete-current-field-or-record)
  2057.   (define-key bbdb-mode-map "\^O"    'bbdb-insert-new-field)
  2058.   (define-key bbdb-mode-map "s"      'bbdb-save-db)
  2059.   (define-key bbdb-mode-map "\^X\^S" 'bbdb-save-db)
  2060.   (define-key bbdb-mode-map "r"      'bbdb-refile-record)
  2061.   (define-key bbdb-mode-map "t"      'bbdb-elide-record)
  2062.   (define-key bbdb-mode-map "o"      'bbdb-omit-record)
  2063.   (define-key bbdb-mode-map ";"      'bbdb-record-edit-notes)
  2064.   (define-key bbdb-mode-map "m"      'bbdb-send-mail)
  2065.   (define-key bbdb-mode-map "\M-d"   'bbdb-dial)
  2066.   (define-key bbdb-mode-map "f"      'bbdb-finger)
  2067.   (define-key bbdb-mode-map "i"         'bbdb-info)
  2068.   (define-key bbdb-mode-map "?"         'bbdb-help)
  2069.   (define-key bbdb-mode-map "q"         'bbdb-bury-buffer)
  2070.   (define-key bbdb-mode-map "\^X\^T" 'bbdb-transpose-fields)
  2071.   )
  2072.  
  2073. (defvar bbdbid "Insidious Big Brother Database autoload")
  2074.  
  2075. ;; tie it all together...
  2076. ;;
  2077. (autoload 'bbdb        "bbdb-com" bbdbid t)
  2078. (autoload 'bbdb-name    "bbdb-com" bbdbid t)
  2079. (autoload 'bbdb-company    "bbdb-com" bbdbid t)
  2080. (autoload 'bbdb-net    "bbdb-com" bbdbid t)
  2081. (autoload 'bbdb-notes    "bbdb-com" bbdbid t)
  2082. (autoload 'bbdb-changed    "bbdb-com" bbdbid t)
  2083. (autoload 'bbdb-create    "bbdb-com" bbdbid t)
  2084. (autoload 'bbdb-dial    "bbdb-com" bbdbid t)
  2085. (autoload 'bbdb-finger    "bbdb-com" bbdbid t)
  2086. (autoload 'bbdb-info    "bbdb-com" bbdbid t)
  2087. (autoload 'bbdb-help    "bbdb-com" bbdbid t)
  2088.  
  2089. (autoload 'bbdb-insinuate-vm    "bbdb-vm"    "Hook BBDB into VM")
  2090. (autoload 'bbdb-insinuate-rmail "bbdb-rmail" "Hook BBDB into RMAIL")
  2091. (autoload 'bbdb-insinuate-mh    "bbdb-mhe"   "Hook BBDB into MH-E")
  2092. (autoload 'bbdb-insinuate-gnus  "bbdb-gnus"  "Hook BBDB into GNUS")
  2093.  
  2094. (autoload 'bbdb-apply-next-command-to-all-records "bbdb-com" bbdbid t)
  2095.  
  2096. (autoload 'bbdb-insert-new-field        "bbdb-com" bbdbid t)
  2097. (autoload 'bbdb-edit-current-field        "bbdb-com" bbdbid t)
  2098. (autoload 'bbdb-transpose-fields        "bbdb-com" bbdbid t)
  2099. (autoload 'bbdb-record-edit-notes        "bbdb-com" bbdbid t)
  2100. (autoload 'bbdb-delete-current-field-or-record    "bbdb-com" bbdbid t)
  2101. (autoload 'bbdb-delete-current-record        "bbdb-com" bbdbid t)
  2102. (autoload 'bbdb-refile-record            "bbdb-com" bbdbid t)
  2103. (autoload 'bbdb-elide-record            "bbdb-com" bbdbid t)
  2104. (autoload 'bbdb-omit-record            "bbdb-com" bbdbid t)
  2105. (autoload 'bbdb-send-mail            "bbdb-com" bbdbid t)
  2106. (autoload 'bbdb-complete-name            "bbdb-com" bbdbid t)
  2107. (autoload 'bbdb-yank                "bbdb-com" bbdbid t)
  2108. (autoload 'bbdb-completion-predicate            "bbdb-com" bbdbid)
  2109. (autoload 'bbdb-dwim-net-address                "bbdb-com" bbdbid)
  2110. (autoload 'bbdb-redisplay-records        "bbdb-com" bbdbid)
  2111. (autoload 'bbdb-define-all-aliases        "bbdb-com" bbdbid)
  2112. (autoload 'bbdb-read-addresses-with-completion    "bbdb-com" bbdbid)
  2113. (autoload 'bbdb-record-edit-property        "bbdb-com" bbdbid t)
  2114.  
  2115. (autoload 'bbdb/vm-show-sender        "bbdb-vm"    bbdbid t)
  2116. (autoload 'bbdb/vm-annotate-sender    "bbdb-vm"    bbdbid t)
  2117. (autoload 'bbdb/vm-update-record      "bbdb-vm"    bbdbid t)
  2118. (autoload 'bbdb/rmail-show-sender     "bbdb-rmail" bbdbid t)
  2119. (autoload 'bbdb/rmail-annotate-sender "bbdb-rmail" bbdbid t)
  2120. (autoload 'bbdb/rmail-update-record   "bbdb-rmail" bbdbid t)
  2121. (autoload 'bbdb/mh-show-sender        "bbdb-mhe"   bbdbid t)
  2122. (autoload 'bbdb/mh-annotate-sender    "bbdb-mhe"   bbdbid t)
  2123. (autoload 'bbdb/mh-update-record      "bbdb-mhe"   bbdbid t)
  2124. (autoload 'bbdb/gnus-show-sender      "bbdb-gnus"  bbdbid t)
  2125. (autoload 'bbdb/gnus-annotate-sender  "bbdb-gnus"  bbdbid t)
  2126. (autoload 'bbdb/gnus-update-record    "bbdb-gnus"  bbdbid t)
  2127. (autoload 'bbdb/gnus-lines-and-from   "bbdb-gnus"  bbdbid nil)
  2128.  
  2129. (autoload 'bbdb-extract-field-value          "bbdb-hooks" bbdbid nil)
  2130. (autoload 'bbdb-timestamp-hook               "bbdb-hooks" bbdbid nil)
  2131. (autoload 'bbdb-ignore-most-messages-hook    "bbdb-hooks" bbdbid nil)
  2132. (autoload 'bbdb-ignore-some-messages-hook    "bbdb-hooks" bbdbid nil)
  2133. (autoload 'bbdb-auto-notes-hook              "bbdb-hooks" bbdbid nil)
  2134. (autoload 'sample-bbdb-canonicalize-net-hook "bbdb-hooks" bbdbid nil)
  2135. (autoload 'bbdb-creation-date-hook         "bbdb-hooks" bbdbid nil)
  2136.  
  2137. (autoload 'bbdb-fontify-buffer    "bbdb-lucid" bbdbid nil)
  2138. (autoload 'bbdb-menu        "bbdb-lucid" bbdbid t)
  2139.  
  2140. (makunbound 'bbdbid)
  2141.  
  2142. ;;; RMAIL, MHE, and VM interfaces might need these.
  2143. (autoload 'mail-strip-quoted-names "mail-utils")
  2144. (autoload 'mail-fetch-field "mail-utils")
  2145.  
  2146. ;;; All of the interfaces need this.
  2147. (autoload 'mail-extract-address-components "mail-extr")
  2148.  
  2149.  
  2150. (defun bbdb-insinuate-sendmail ()
  2151.   "Call this function to hook BBDB into sendmail (that is, M-x mail)."
  2152.   (define-key mail-mode-map "\M-\t" 'bbdb-complete-name)
  2153.   )
  2154.  
  2155.  
  2156. ;;; elucidate
  2157.  
  2158. (cond ((and (string-match "Lucid" emacs-version)
  2159.         (not (string-lessp emacs-version "19.3")))
  2160.        (bbdb-add-hook 'bbdb-list-hook 'bbdb-fontify-buffer)
  2161.        (define-key bbdb-mode-map 'button3 'bbdb-menu)
  2162.        ))
  2163.  
  2164.  
  2165. (provide 'bbdb)  ; provide before running load-hooks.
  2166. (run-hooks 'bbdb-load-hook)
  2167.