Initial Commit
[packages] / xemacs-packages / bbdb / lisp / bbdb.el
1 ;;; -*- Mode:Emacs-Lisp -*-
2 ;;; This file is the core of the Insidious Big Brother Database (aka BBDB),
3 ;;; copyright (c) 1991, 1992, 1993, 1994 Jamie Zawinski <jwz@netscape.com>.
4 ;;; See the file bbdb.texinfo for documentation.
5 ;;;
6 ;;; The Insidious Big Brother Database is free software; you can redistribute
7 ;;; it and/or modify it under the terms of the GNU General Public License as
8 ;;; published by the Free Software Foundation; either version 2, or (at your
9 ;;; option) any later version.
10 ;;;
11 ;;; BBDB is distributed in the hope that it will be useful, but WITHOUT ANY
12 ;;; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 ;;; FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
14 ;;; details.
15 ;;;
16 ;;; You should have received a copy of the GNU General Public License
17 ;;; along with GNU Emacs; see the file COPYING.  If not, write to
18 ;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
19 ;;;
20 ;;;  ------------------------------------------------------------------------
21 ;;; |  There is a mailing list for discussion of BBDB:                       |
22 ;;; |         bbdb-info@lists.sourceforge.net                                |
23 ;;; |  To join, send mail to bbdb-info-request@lists.sourceforge.net         |
24 ;;; |  (don't forget the "-request" part or you'll look silly in front of    |
25 ;;; |  lots of people who 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@lists.sourceforge.net.  This is a very low      |
30 ;;; |  volume list, 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. The preferred method of reporting bugs is to use    |
34 ;;; |  bbdb-submit-bug-report, which will include all useful version         |
35 ;;; |  information plus state information about how you have BBDB set up.    |
36 ;;;  ------------------------------------------------------------------------
37 ;;;
38 ;;; $Id: bbdb.el,v 1.10 2007-02-23 20:24:09 fenk Exp $
39
40 (require 'timezone)
41 (eval-when-compile (require 'cl))
42
43 (eval-when-compile              ; pacify the compiler.
44  (autoload 'widget-group-match "wid-edit")
45  (autoload 'Electric-pop-up-window "electric")
46  (autoload 'Electric-command-loop "electric")
47  (autoload 'bbdb-migration-query "bbdb-migrate")
48  (autoload 'bbdb-migrate "bbdb-migrate")
49  (autoload 'bbdb-migrate-rewrite-all "bbdb-migrate")
50  (autoload 'bbdb-migrate-update-file-version "bbdb-migrate")
51  (autoload 'bbdb-unmigrate-record "bbdb-migrate")
52  (autoload 'bbdb-create-internal "bbdb-com")
53  (autoload 'bbdb-append-records-p "bbdb-com")
54  (autoload 'bbdb-redisplay-records "bbdb-com")
55  (autoload 'y-or-n-p-with-timeout "timer")
56  (autoload 'mail-position-on-field "sendmail")
57  (autoload 'bbdb-fontify-buffer "bbdb-gui")
58  (autoload 'vm-select-folder-buffer "vm-folder")
59
60  ;; can't use autoload for variables...
61  (defvar bbdb-define-all-aliases-needs-rebuilt) ;; bbdb-com
62  (defvar message-mode-map) ;; message.el
63  (defvar mail-mode-map) ;; sendmail.el
64  (defvar gnus-article-buffer) ;; gnus-art.el
65  )
66
67 (defconst bbdb-version "2.35")
68 (defconst bbdb-version-date "$Date: 2007-02-23 20:24:09 $")
69
70 (defcustom bbdb-gui (if (fboundp 'display-color-p) ; Emacs 21
71                         (display-color-p)
72                       (not (null window-system))) ; wrong for XEmacs?
73   "*Non-nil means fontify the *BBDB* buffer."
74   :group 'bbdb
75   :type 'boolean)
76
77 ;; File format
78 (defconst bbdb-file-format 6)
79 (defvar bbdb-file-format-migration nil
80   "A cons of two elements: the version read, and the version to write.
81 nil if the database was read in and is to be written in the current
82 version.")
83
84 (defvar bbdb-no-duplicates-p nil
85   "Should BBDB allow entries with duplicate names.
86 This may lead to confusion when doing completion.  If non-nil, it will
87 prompt the users on how to merge records when duplicates are detected.")
88
89 ;; Definitions for things that aren't in all Emacsen and that I really
90 ;; would prefer not to live without.
91 (eval-and-compile
92   (if (fboundp 'unless) nil
93     (defmacro unless (bool &rest forms) `(if ,bool nil ,@forms))
94     (defmacro when (bool &rest forms) `(if ,bool (progn ,@forms))))
95   (unless (fboundp 'save-current-buffer)
96     (defalias 'save-current-buffer 'save-excursion))
97   (if (fboundp 'mapc)
98       (defalias 'bbdb-mapc 'mapc)
99     (defalias 'bbdb-mapc 'mapcar))
100   )
101
102 (unless (fboundp 'with-current-buffer)
103   (defmacro with-current-buffer (buf &rest body)
104     `(save-current-buffer (set-buffer ,buf) ,@body)))
105
106 (unless (fboundp 'defvaralias)
107   (defun defvaralias (&rest args)))
108
109 (defmacro string> (a b) (list 'not (list 'or (list 'string= a b)
110                                          (list 'string< a b))))
111
112 (eval-and-compile
113   (or (fboundp 'set-keymap-prompt)
114       (fset 'set-keymap-prompt 'ignore)))
115
116 ;; this should really be in bbdb-com
117 ;;;###autoload
118 (defun bbdb-submit-bug-report ()
119   "Submit a bug report, with pertinent information to the BBDB info list."
120   (interactive)
121   (require 'reporter)
122   (delete-other-windows)
123   (reporter-submit-bug-report
124    "bbdb-info@lists.sourceforge.net"
125    (concat "BBDB " bbdb-version)
126    (append
127     ;; non user variables
128     '(emacs-version
129       bbdb-version-date
130       bbdb-file-format
131       bbdb-no-duplicates-p)
132     ;; user variables
133     (sort (apropos-internal "^bbdb"
134                             'user-variable-p)
135           (lambda (v1 v2) (string-lessp (format "%s" v1) (format "%s" v2))))
136     ;; see what the user had loaded
137     (list 'features)
138     )
139    nil
140    nil
141    "Please change the Subject header to a concise bug description.\nIn this report, remember to cover the basics, that is, what you expected to\nhappen and what in fact did happen.  Please remove these\ninstructions from your message.")
142
143   ;; insert the backtrace buffer content if present
144   (let ((backtrace (get-buffer-create "*Backtrace*")))
145     (when backtrace
146       (goto-char (point-max))
147       (insert "\n\n")
148       (insert-buffer-substring backtrace)))
149
150   (goto-char (point-min))
151   (mail-position-on-field "Subject"))
152
153 ;; Make custom stuff work even without customize
154 ;;   Courtesy of Hrvoje Niksic <hniksic@srce.hr>
155 (eval-and-compile
156   (condition-case ()
157       (require 'custom)
158     (error nil))
159   (unless (and (featurep 'custom) (fboundp 'custom-declare-variable))
160     ;; We have the old custom-library, hack around it!
161     (defmacro defgroup (&rest args)
162       nil)
163     (defmacro defcustom (var value doc &rest args)
164       `(defvar ,var ,value ,doc))
165     (defmacro defface (var value doc &rest args)
166       `(make-face ,var))
167     (defmacro define-widget (&rest args)
168       nil)))
169
170 (defconst bbdb-have-re-char-classes (string-match "[[:alpha:]]" "x")
171     "Non-nil if this Emacs supports regexp character classes.
172 E.g. `[[:alnum:]]'.")
173
174 ;; Custom groups
175
176 (defgroup bbdb nil
177   "The Insidious Big Brother Database."
178   :group 'news
179   :group 'mail)
180
181 (put 'bbdb 'custom-loads '("bbdb-hooks" "bbdb-com"))
182
183 (defgroup bbdb-hooks nil
184   "Hooks run at various times by the BBDB"
185   :group 'bbdb)
186
187 (defgroup bbdb-record-display nil
188   "Variables that affect the display of BBDB records"
189   :group 'bbdb)
190
191 (defgroup bbdb-record-creation nil
192   "Variables that affect the creation of BBDB records"
193   :group 'bbdb)
194
195 (defgroup bbdb-noticing-records nil
196   "Variables that affect the noticing of new authors"
197   :group 'bbdb-record-creation)
198 (put 'bbdb-noticing-records 'custom-loads '("bbdb-hooks"))
199
200 (defgroup bbdb-record-use nil
201   "Variables that affect the use of BBDB records"
202   :group 'bbdb)
203
204 (defgroup bbdb-database nil
205   "Variables that affect the database as a whole"
206   :group 'bbdb)
207
208 (defgroup bbdb-saving nil
209   "Variables that affect saving of the BBDB"
210   :group 'bbdb-database)
211
212 (defgroup bbdb-mua-specific nil
213   "MUA-specific customizations"
214   :group 'bbdb)
215
216 (defgroup bbdb-mua-specific-gnus nil
217   "Gnus-specific BBDB customizations"
218   :group 'bbdb-mua-specific)
219
220 (put 'bbdb-mua-specific-gnus 'custom-loads '("bbdb-gnus"))
221
222 (defgroup bbdb-mua-specific-gnus-scoring nil
223   "Gnus-specific scoring BBDB customizations"
224   :group 'bbdb-mua-specific-gnus)
225
226 (put 'bbdb-mua-specific-gnus-scoring 'custom-loads '("bbdb-gnus"))
227
228 (defgroup bbdb-mua-specific-gnus-splitting nil
229   "Gnus-specific splitting BBDB customizations"
230   :group 'bbdb-mua-specific-gnus)
231
232 (put 'bbdb-mua-specific-gnus-splitting 'custom-loads '("bbdb-gnus"))
233
234 (defgroup bbdb-mua-specific-vm nil
235   "VM-specific BBDB customizations"
236   :group 'bbdb-mua-specific)
237
238 (put 'bbdb-mua-specific-vm 'custom-loads '("bbdb-vm"))
239
240 (defgroup bbdb-phone-dialing nil
241   "Customizations for phone number dialing"
242   :group 'bbdb)
243 (put 'bbdb-phone-dialing 'custom-loads '("bbdb-com"))
244
245 (defgroup bbdb-utilities nil
246   "Customize BBDB Utilities"
247   :group 'bbdb)
248
249 (defgroup bbdb-utilities-finger nil
250   "Customizations for fingering from within the BBDB"
251   :group 'bbdb-utilities
252   :prefix "bbdb-finger")
253 (put 'bbdb-utilities-finger 'custom-loads '("bbdb-com"))
254
255 (defgroup bbdb-utilities-ftp nil
256   "Customizations for using FTP sites stored in BBDB records."
257   :group 'bbdb-utilities)
258 (put 'bbdb-utilities-ftp 'custom-loads '("bbdb-ftp"))
259
260 (defgroup bbdb-utilities-print nil
261   "Customizations for printing the BBDB."
262   :group 'bbdb-utilities
263   :prefix "bbdb-print")
264 (put 'bbdb-utilities-print 'custom-loads '("bbdb-print"))
265
266 (defgroup bbdb-utilities-supercite nil
267   "Customizations for using Supercite with the BBDB."
268   :group 'bbdb-utilities
269   :prefix "bbdb/sc")
270 (if (or (featurep 'supercite)
271         (locate-library "supercite"))
272     (put 'bbdb-utilities-supercite 'custom-loads '("bbdb-sc")))
273
274 (defgroup bbdb-utilities-server nil
275   "Customizations for interfacing with the BBDB from external programs."
276   :group 'bbdb-utilities
277   :prefix "bbdb/srv")
278 (if (and (or (featurep 'gnuserv) (locate-library "gnuserv"))
279          (or (featurep 'itimer)  (locate-library "itimer")))
280     (put 'bbdb-utilities-server 'custom-loads '("bbdb-srv")))
281
282 ;; BBDB custom widgets
283
284 (define-widget 'bbdb-alist-with-header 'group
285   "My group"
286   :match 'bbdb-alist-with-header-match
287   :value-to-internal (lambda (widget value)
288                        (if value (list (car value) (cdr value))))
289   :value-to-external (lambda (widget value)
290                        (if value (append (list (car value)) (cadr value)))))
291
292 (defun bbdb-alist-with-header-match (widget value)
293   (widget-group-match widget
294                       (widget-apply widget :value-to-internal value)))
295
296 ;; Customizable variables
297
298 (defcustom bbdb-file "~/.bbdb"
299   "*The name of the Insidious Big Brother Database file."
300   :group 'bbdb-database
301   :type 'file)
302
303 ;; this should be removed, and the following put in place:
304 ;; a hierarchical structure of bbdb files, some perhaps read-only,
305 ;; perhaps caching in the local bbdb. This way you could have, e.g. a
306 ;; company address book, with each person having access to it, and
307 ;; then a local address book with personal stuff in it.
308 (defcustom bbdb-file-remote nil
309   "*The remote file to save the database to.
310 When this is non-nil, it should be a file name.
311 When BBDB reads `bbdb-file', it checks this file,
312  and if it is newer, downloads it.
313 When BBDB writes `bbdb-file', it also writes this file.
314
315 This feature allows one to keep the database in one place while using
316 different computers, thus reducing the need for merging different files."
317   :group 'bbdb-database
318   :type '(choice (const :tag "none" nil)
319                  (file :tag "remote file name")))
320
321 (defcustom bbdb-file-remote-save-always t
322   "*Should the `bbdb-file-remote' file be saved whenever the database is saved?
323 When nil, you will be asked."
324   :group 'bbdb-database
325   :type 'boolean)
326
327 (unless (fboundp 'primep)
328   (defun primep (num)
329     "Return t if NUM is a prime number."
330     (and (numberp num) (> num 1) (= num (floor num))
331          (let ((lim (sqrt num)) (nu 2) (prime t))
332            (while (and prime (<= nu lim))
333              (setq prime (/= 0 (mod num nu))
334                    nu (1+ nu)))
335            prime))))
336
337 (defcustom bbdb-hashtable-size 1021
338   "*The size of the bbdb hashtable.
339 BBDB hashtable is an obarray, so this must be a prime integer.
340 Set this to a prime number (much) larger than the size of your database
341 before loading it.
342 If you change this variable outside `customize',
343 you should reload `bbdb-file'."
344   :group 'bbdb-database
345   :type 'integer
346   :set (lambda (symb val)
347          (unless (primep val)
348            (error "`%s' must be prime, not %s" symb val))
349          (set symb val)
350          (when (fboundp 'bbdb-records)
351            (bbdb-records))
352          val))
353
354 (defcustom bbdb-default-area-code nil
355   "*The default area code to use when prompting for a new phone number.
356 This variable also affects dialing."
357   :group 'bbdb-record-creation
358   :type '(choice (const :tag "none" nil)
359                  (integer :tag "Default Area Code"))
360   :set (lambda( symb val )
361          (if (or (and (stringp val)
362                       (string-match "^[0-9]+$" val))
363                  (integerp val)
364                  (null val))
365              (set symb val)
366            (error "%s must contain digits only." symb))))
367
368 (defcustom bbdb-lastname-prefixes
369  '("von" "Von" "de" "De")
370   "*List of lastname prefixes recognized in name fields. Used to
371 enhance dividing name strings into firstname and lastname parts."
372   :group 'bbdb-record-creation
373   :type '(repeat string))
374
375 (defcustom bbdb-default-domain nil
376   "*The default domain to append when prompting for a new net address.
377 If the address entered does not contain `[@%!]', `@bbdb-default-domain'
378 will be appended to it.
379
380 The address will not be altered if bbdb-default-domain remains at its
381 default value of nil, or if one provides a prefix argument to the
382 bbdb-insert-new-field command."
383   :group 'bbdb-record-creation
384   :type '(choice (const :tag "none" nil)
385                  (string :tag "Domain" :value nil)))
386
387 (defcustom bbdb-north-american-phone-numbers-p t
388   "*Set this to nil if you want to enter phone numbers that aren't the same
389 syntax as those in North America (that is, [[1] nnn] nnn nnnn ['x' n*]).
390 If this is true, then some error checking is done so that you can't enter
391 incorrect phone numbers, and all phone numbers are pretty-printed the same
392 way.  European phone numbers don't have as strict a syntax, however, so
393 this is a harder problem for them (on which I am punting).
394
395 You can have both styles of phone number in your database by providing a
396 prefix argument to the bbdb-insert-new-field command."
397   :group 'bbdb-record-creation
398   :type 'boolean)
399
400 (defcustom bbdb-electric-p nil
401   "*Whether bbdb mode should be `electric' like `electric-buffer-list'."
402   :group 'bbdb-record-display
403   :type 'boolean)
404
405 (defcustom bbdb-case-fold-search (default-value 'case-fold-search)
406   "*This is the value of `case-fold-search' used by `bbdb' and friends.
407 This variable lets the case-sensitivity of ^S and of the bbdb
408 commands be different."
409   :group 'bbdb
410   :type 'boolean)
411
412 ;; these variables both need to be enabled for gnus mailreading to
413 ;; work right. that's probably a bug, or something.
414 (defcustom bbdb/mail-auto-create-p t
415   "*If this is t, then Gnus, MH, RMAIL, and VM will automatically
416 create new bbdb records for people you receive mail from. If this
417 is a function name or lambda, then it is called with no arguments
418 to decide whether an entry should be automatically created. You
419 can use this to, for example, not create records for messages
420 which have reached you through a particular mailing list, or to
421 only create records automatically if the mail has a particular
422 subject."
423   :group 'bbdb-noticing-records
424   :type '(choice (const :tag "Automatically create" t)
425                  (const :tag "Prompt before creating" prompt)
426                  (const :tag "Do not automatically create" nil)
427                  (function :tag "Create with function" bbdb-)))
428
429 (defcustom bbdb/news-auto-create-p nil
430   "*If this is t, then Gnus will automatically create new bbdb
431 records for people you receive mail from.  If this is a function name
432 or lambda, then it is called with no arguments to decide whether an
433 entry should be automatically created.  You can use this to, for
434 example, create or not create messages which have a particular
435 subject.  If you want to autocreate messages based on the current
436 newsgroup, it's probably a better idea to set this variable to t or
437 nil from your `gnus-select-group-hook' instead."
438   :group 'bbdb-noticing-records
439   :type '(choice (const :tag "Automatically create" t)
440                  (const :tag "Prompt before creating" prompt)
441                  (const :tag "Do not automatically create" nil)
442                  (function :tag "Create with function" bbdb-)))
443
444 (defcustom bbdb-quiet-about-name-mismatches nil
445   "*If this is true, then BBDB will not prompt you when it notices a
446 name change, that is, when the \"real name\" in a message doesn't correspond
447 to a record already in the database with the same network address.  As in,
448 \"John Smith <jqs@frob.com>\" versus \"John Q. Smith <jqs@frob.com>\".
449 Normally you will be asked if you want to change it.
450 If set to a number it is the number of seconds to sit for while
451 displaying the mismatch message."
452   :group 'bbdb-noticing-records
453   :type '(choice (const :tag "Prompt for name changes" nil)
454                  (const :tag "Do not prompt for name changes" t)
455          (integer :tag
456               "Instead of prompting, warn for this many seconds")))
457
458 (defcustom bbdb-use-alternate-names t
459   "*If this is true, then when bbdb notices a name change, it will ask you
460 if you want both names to map to the same record."
461   :group 'bbdb-noticing-records
462   :type '(choice (const :tag "Ask to use alternate names field" t)
463                  (const :tag "Use alternate names field without asking" nil)))
464
465 (defcustom bbdb-readonly-p nil
466   "*If this is true, then nothing will attempt to change the bbdb database
467 implicitly, and you will be prevented from doing it explicitly.  If you have
468 more than one emacs running at the same time, you might want to arrange for
469 this to be set to t in all but one of them."
470   :group 'bbdb-database
471   :type '(choice (const :tag "Database is read-only" t)
472                  (const :tag "Database is writable" nil)))
473
474 (defcustom bbdb-continental-zip-regexp "^\\s *[A-Z][A-Z]?\\s *-\\s *[0-9][0-9][0-9]"
475   "Regexp matching continental zip codes.
476 Addresses with zip codes matching the regexp will be formated using
477 `bbdb-format-address-continental'.  The regexp should match zip codes
478 of the form CH-8052, NL-2300RA, and SE-132 54."
479   :group 'bbdb-record-display
480   :type 'regexp)
481
482 (defcustom bbdb-auto-revert-p nil
483   "*If this variable is true and the BBDB file is noticed to have changed on
484 disk, it will be automatically reverted without prompting you first.  Otherwise
485 you will be asked. (But if the file has changed and you hae made changes in
486 memory as well, you will always be asked.)"
487   :group 'bbdb-saving
488   :type '(choice (const :tag "Revert unchanged database without prompting" t)
489                  (const :tag "Ask before reverting database")))
490
491 (defcustom bbdb-notice-auto-save-file nil
492   "*If this is true, then the BBDB will notice when its auto-save file is
493 newer than the file is was read from, and will offer to revert."
494   :group 'bbdb-saving
495   :type '(choice (const :tag "Check auto-save file" t)
496                  (const :tag "Do not check auto-save file" nil)))
497
498 (defcustom bbdb-use-pop-up t
499   "If true, display a continuously-updating bbdb window while in VM, MH,
500 RMAIL, or Gnus.  If 'horiz, stack the window horizontally if there is room."
501   :group 'bbdb-record-display
502   :type '(choice (const :tag "Automatic BBDB window, stacked vertically" t)
503                  (const :tag "Automatic BBDB window, stacked horizontally" 'horiz)
504                  (const :tag "No Automatic BBDB window" nil)))
505
506 (defcustom bbdb-pop-up-target-lines 5
507   "*Desired number of lines in a VM/MH/RMAIL/Gnus pop-up bbdb window."
508   :group 'bbdb-record-display
509   :type 'integer)
510
511 (defcustom bbdb-completion-type nil
512   "*Controls the behaviour of `bbdb-complete-name'.  If nil, completion is
513 done across the set of all full-names and user-ids in the bbdb-database;
514 if the symbol 'name, completion is done on names only; if the symbol 'net,
515 completion is done on network addresses only; if it is 'primary, then
516 completion is done only across the set of primary network addresses (the
517 first address in the list of addresses for a given user).  If it is
518 'primary-or-name, completion is done across primaries and real names."
519   :group 'bbdb-record-use
520   :type '(choice (const :tag "Complete across names and net addresses" nil)
521                  (const :tag "Complete across names" name)
522                  (const :tag "Complete across net addresses" net)
523                  (const :tag "Complete across primary net addresses" primary)
524                  (const :tag "Complete across names and primary net addresses"
525                         primary-or-name)))
526
527 (defcustom bbdb-completion-display-record t
528   "*Whether `bbdb-complete-name' (\\<mail-mode-map>\\[bbdb-complete-name]
529 in mail-mode) will update the *BBDB* buffer
530 to display the record whose email address has just been inserted."
531   :group 'bbdb-record-use
532   :type '(choice (const :tag "Update the BBDB buffer" t)
533                  (const :tag "Don't update the BBDB buffer" nil)))
534
535 (defcustom bbdb-user-mail-names nil
536   "*A regular expression identifying the addresses that belong to you.
537 If a message from an address matching this is seen, the BBDB record for
538 the To: line will be shown instead of the one for the From: line.  If
539 this is nil, it will default to the value of (user-login-name)."
540   :group 'bbdb-noticing-records
541   :type (list 'choice '(const :tag "Use value of (user-login-name)" nil)
542               (list 'regexp :tag "Pattern matching your addresses"
543                          (or (user-login-name) "address"))))
544
545 (defcustom bbdb-always-add-addresses 'ask
546   "*If this is true, then when the Insidious Big Brother Database notices
547 a new email address for a person, it will automatically add it to the list of
548 addresses.  If it is 'ask, you will be asked whether to add it.  If it is nil
549 then new network addresses will never be automatically added nor the user will
550 be asked.
551
552 When set to a function name the function should return one of these values.
553
554 See also the variable `bbdb-new-nets-always-primary' for control of whether
555 the addresses go at the front of the list or the back."
556   :group 'bbdb-noticing-records
557   :type '(choice (const :tag "Automatically add new addresses" t)
558                  (const :tag "Ask before adding new addresses" ask)
559                  (const :tag "Never add new addresses" nil)
560                  (const bbdb-ignore-some-messages-hook)
561                  (const bbdb-ignore-most-messages-hook)))
562
563 (defcustom bbdb-new-nets-always-primary nil
564   "*If this is true, then when the Insidious Big Brother Database adds a new
565 address to a record, it will always add it to the front of the list of
566 addresses, making it the primary address.  If this is nil, you will be asked.
567 If it is the symbol 'never (really, if it is any non-t, non-nil value) then
568 new network addresses will always be added at the end of the list."
569   :group 'bbdb-noticing-records
570   :type '(choice (const :tag "New address automatically made primary" t)
571                 (const :tag "Ask before making new address primary" nil)
572                 (const :tag "Never make new address primary" never)))
573
574 (defcustom bbdb-send-mail-style nil
575   "*Specifies which package should be used to send mail.
576 Should be 'vm, 'mh, 'mail, 'message, or 'gnus (or nil, meaning guess.)"
577   :group 'bbdb-record-use
578   :type '(choice (const :tag "Use VM to send mail" vm)
579                  (const :tag "Use MH-E to send mail" mh)
580                  (const :tag "Use send-mail mode to send mail" mail)
581                  (const :tag "Use Message to send mail" message)
582                  (const :tag "Use Mew to send mail" mew)
583                  (const :tag "Use compose-mail to send mail" compose-mail)
584                  (const :tag "Use gnus to send mail" gnus)
585                  (const :tag "Guess which package to use" nil)))
586
587 (defcustom bbdb-offer-save t
588   "*If t, then certain actions will cause the BBDB to ask you whether
589 you wish to save the database.  If nil, then the offer to save will never
590 be made.  If not t and not nil, then any time it would ask you, it will
591 just save it without asking."
592   :group 'bbdb-saving
593   :type '(choice (const :tag "Offer to save the database" t)
594                  (const :tag "Never offer to save the database" nil)
595                  (const :tag "Save database without asking" savenoprompt)))
596
597 (defcustom bbdb-message-caching-enabled t
598   "*Whether caching of the message->bbdb-record association should be used
599 for the interfaces which support it (VM, MH, and RMAIL).  This can speed
600 things up a lot.  One implication of this variable being true is that the
601 `bbdb-notice-hook' will not be called each time a message is selected, but
602 only the first time.  Likewise, if selecting a message would generate a
603 question (whether to add an address, change the name, etc) you will only
604 be asked that question the very first time the message is selected."
605   :group 'bbdb
606   :type '(choice (const :tag "Enable caching" t)
607                  (const :tag "Disable caching" nil)))
608
609 (defcustom bbdb-silent-running nil
610   "*If this is true, bbdb will suppress all its informational messages and
611 queries. Be very very certain you want to set this, because it will suppress
612 prompting to alter record names, assign names to addresses, etc."
613   :group 'bbdb
614   :type '(choice (const :tag "Run silently" t)
615                  (const :tag "Disable silent running" nil)))
616
617 (defcustom bbdb-mode-hook nil
618   "*Hook or hooks invoked when the *BBDB* buffer is created."
619   :group 'bbdb-hooks
620   :type 'hook)
621
622 (defcustom bbdb-list-hook nil
623   "*Hook or hooks invoked after the `bbdb-list-buffer' is filled in.
624 Invoked with no arguments."
625   :group 'bbdb-hooks
626   :type 'hook)
627
628 (defcustom bbdb-create-hook 'bbdb-creation-date-hook
629   "*Hook or hooks invoked each time a new BBDB record is created.  Invoked
630 with one argument, the new record.  This is called *before* the record is
631 added to the database.  Note that `bbdb-change-hook' will be called as well.
632
633 Hook functions can use the variable `bbdb-update-address-class' to determine
634 the class of an email address according to `bbdb-get-addresses-headers' and
635 the variable `bbdb-update-address-header' is set to the header the email
636 address was extracted from."
637   :group 'bbdb-hooks
638   :type 'hook)
639
640 (defcustom bbdb-change-hook 'bbdb-timestamp-hook
641   "*Hook or hooks invoked each time a BBDB record is altered.  Invoked with
642 one argument, the record.  This is called *before* the bbdb-database buffer
643 is modified.  Note that if a new bbdb record is created, both this hook and
644 `bbdb-create-hook' will be called."
645   :group 'bbdb-hooks
646   :type 'hook)
647
648 (defcustom bbdb-after-change-hook nil
649   "*Hook or hooks invoked each time a BBDB record is altered.  Invoked with
650 one argument, the record.  This is called *after* the bbdb-database buffer
651 is modified, so if you want to modify the record each time it is changed,
652 you should use the `bbdb-change-hook' instead.  Note that if a new bbdb
653 record is created, both this hook and `bbdb-create-hook' will be called."
654   :group 'bbdb-hooks
655   :type 'hook)
656
657 (defcustom bbdb-canonicalize-net-hook nil
658   "*If this is non-nil, it should be a function of one arg: a network address
659 string.  Whenever the Insidious Big Brother Database \"notices\" a message,
660 the corresponding network address will be passed to this function first, as
661 a kind of \"filter\" to do whatever transformations upon it you like before
662 it is compared against or added to the database.  For example: it is the case
663 that CS.CMU.EDU is a valid return address for all mail originating at a
664 machine in the .CS.CMU.EDU domain.  So, if you wanted all such addresses to
665 be canonically hashed as user@CS.CMU.EDU, instead of as user@host.CS.CMU.EDU,
666 you might set this variable to a function like this:
667
668  (setq bbdb-canonicalize-net-hook
669        '(lambda (addr)
670           (cond ((string-match \"\\\\`\\\\([^@]+@\\\\).*\\\\.\\\\(CS\\\\.CMU\\\\.EDU\\\\)\\\\'\"
671                                addr)
672                  (concat (substring addr (match-beginning 1) (match-end 1))
673                          (substring addr (match-beginning 2) (match-end 2))))
674                 (t addr))))
675
676 You could also use this function to rewrite UUCP-style addresses into domain-
677 style addresses, or any number of things.
678
679 This function will be called repeatedly until it returns a value EQ to the
680 value passed in.  So multiple rewrite rules might apply to a single address."
681   :group 'bbdb-hooks
682   :type 'function)
683
684 (defcustom bbdb-canonicalize-redundant-nets-p t
685   "*If this is non-nil, redundant network addresses will be ignored.
686 If a record has an address of the form foo@baz.com, setting this to t
687 will cause subsequently-noticed addresses like foo@bar.baz.com to be
688 ignored (since we already have a more general form of that address.)
689 This is similar in function to one of the possible uses of the variable
690 `bbdb-canonicalize-net-hook' but is somewhat more automatic.  (This
691 can't quite be implemented in terms of the canonicalize-net-hook because
692 it needs access to the database to determine whether an address is
693 redundant, and the canonicalize-net-hook is purely a textual manipulation
694 which is performed before any database access.)"
695   :group 'bbdb-noticing-records
696   :type '(choice (const :tag "Ignore redundant addresses" t)
697                  (const :tag "Don't ignore redundant addresses" nil)))
698
699 (defcustom bbdb-notice-hook nil
700   "*Hook or hooks invoked each time a BBDB record is \"noticed\", that is,
701 each time it is displayed by the news or mail interfaces.  Invoked with
702 one argument, the new record.  The record need not have been modified for
703 this to be called - use `bbdb-change-hook' for that.  You can use this to,
704 for example, add something to the notes field based on the subject of the
705 current message.  It is up to your hook to determine whether it is running
706 in Gnus, VM, MH, or RMAIL, and to act appropriately.
707
708 Also note that `bbdb-change-hook' will NOT be called as a result of any
709 modifications you may make to the record inside this hook.
710
711 Hook functions can use the variable `bbdb-update-address-class' to determine
712 the class of an email address according to `bbdb-get-addresses-headers' and
713 the variable `bbdb-update-address-header' is set to the header the email
714 address was extracted from.
715
716 Beware that if the variable `bbdb-message-caching-enabled' is true (a good
717 idea) then when you are using VM, MH, or RMAIL, this hook will be called only
718 the first time that message is selected.  (The Gnus interface does not use
719 caching.)  When debugging the value of this hook, it is a good idea to set
720 caching-enabled to nil."
721   :group 'bbdb-hooks
722   :type 'hook)
723
724 (defcustom bbdb-after-read-db-hook nil
725   "*Hook or hooks invoked (with no arguments) just after the Insidious Big
726 Brother Database is read in.  Note that this can be called more than once if
727 the BBDB is reverted."
728   :group 'bbdb-hooks
729   :type 'hook)
730
731 (defcustom bbdb-load-hook nil
732   "*Hook or hooks invoked when the BBDB code is first loaded.
733
734 WARNING:  This hook will be run the first time you traverse the Custom menus
735           for the BBDB.  As a result, nothing slow should be added to
736           this hook."
737   :group 'bbdb-hooks
738   :type 'hook)
739
740 (defcustom bbdb-initialize-hook nil
741   "*Hook or hooks invoked (with no arguments) when the Insidious Big Brother
742 Database initialization function `bbdb-initialize' is run."
743   :group 'bbdb-hooks
744   :type 'hook)
745
746 ;;;###autoload
747 (defcustom bbdb-multiple-buffers nil
748   "When non-nil we create a new buffer of every buffer causing pop-ups.
749 You can also set this to a function returning a buffer name."
750   :group 'bbdb-record-display
751   :type '(choice (const :tag "Disabled" nil)
752                  (function :tag "Enabled" bbdb-multiple-buffers-default)
753                  (function :tag "User defined function")))
754
755 (defvar bbdb-mode-map nil
756   "Keymap for Insidious Big Brother Database listings.")
757 (defvar bbdb-mode-search-map nil
758   "Keymap for Insidious Big Brother Database searching")
759
760 ;; iso-2022-7bit should be OK (but not optimal for Emacs, at least --
761 ;; emacs-mule would be better) with both Emacs 21 and XEmacs.
762 (defconst bbdb-file-coding-system
763   (if (fboundp 'coding-system-p)
764       (cond ((coding-system-p 'utf-8-emacs)
765              'utf-8-emacs)
766             (t 'iso-2022-7bit)))
767   "Coding system used for reading and writing `bbdb-file'.
768 This should not be changed by users.")
769
770 (defvar bbdb-suppress-changed-records-recording nil
771   "Whether to record changed records in variable `bbdb-changed-records'.
772
773 If this is false, the BBDB will cease to remember which records are changed
774 as the change happens.  It will still remember that records have been changed,
775 so the file will still be saved, but the changed records list, and the `!!'
776 in the *BBDB* buffer modeline that it depends on, will no longer be updated.
777
778 You should bind this variable, not set it; the `!!' is a useful user-
779 interface feature, and should only be suppressed when changes need to be
780 automatically made to BBDB records which the user will not care directly
781 about.")
782
783 \f
784 ;;; These are the buffer-local variables we use.
785 ;;; They are mentioned here so that the compiler doesn't warn about them
786 ;;; when byte-compile-warn-about-free-variables is on.
787
788 (defvar bbdb-records nil)
789 (defvar bbdb-changed-records nil)
790 (defvar bbdb-end-marker nil)
791 (defvar bbdb-hashtable nil)
792 (defvar bbdb-propnames nil)
793 (defvar bbdb-message-cache nil)
794 (defvar bbdb-showing-changed-ones nil)
795 (defvar bbdb-modified-p nil)
796 (defvar bbdb-address-print-formatting-alist) ; "bbdb-print"
797
798 (defvar bbdb-debug t)
799 (defmacro bbdb-debug (&rest body)
800   ;; ## comment out the next line to turn off debugging.
801   ;; ## You really shouldn't do this!  But it will speed things up.
802   (list 'and 'bbdb-debug (list 'let '((debug-on-error t)) (cons 'progn body)))
803   )
804
805
806 ;;; internal kludge to force queries to always happen with the mouse rather
807 ;;; than basing the decision on the last-input-event; bind this, don't set it.
808 (defvar bbdb-force-dialog-boxes nil)
809
810 (defun bbdb-y-or-n-p (prompt)
811   (prog1
812       (funcall
813        (cond ((and bbdb-force-dialog-boxes
814                    (fboundp 'yes-or-no-p-dialog-box))
815               (when (and (fboundp 'raise-frame)
816                          (not (frame-visible-p (selected-frame))))
817                 (raise-frame (selected-frame)))
818               'yes-or-no-p-dialog-box)
819              (t 'y-or-n-p))
820        prompt)
821     (message " ")))
822
823 (defun bbdb-yes-or-no-p (prompt)
824   (prog1
825       (funcall (if (and bbdb-force-dialog-boxes
826                         (fboundp 'yes-or-no-p-dialog-box))
827                    'yes-or-no-p-dialog-box
828                  'yes-or-no-p)
829                prompt)
830     (message " ")))
831
832 (defun bbdb-invoke-hook (hook arg)
833   "Like `invoke-hooks', but invokes the given hook with one argument."
834   (if (and (boundp hook) (setq hook (symbol-value hook)))
835       (if (and (consp hook) (not (eq (car hook) 'lambda)))
836           (while hook
837             (funcall (car hook) arg)
838             (setq hook (cdr hook)))
839           (funcall hook arg))))
840
841 (defun bbdb-invoke-hook-for-value (hook &rest args)
842   "If HOOK is a function, invoke it with ARGS. Otherwise return it as-is."
843   (cond ((eq hook nil) nil)
844         ((eq hook t) t)
845         ((functionp hook) (apply hook args))
846         (t hook)))
847
848 (defmacro bbdb-defstruct (conc-name &rest slots)
849   "Make two functions, one for each slot.  The functions are:
850         CONC-NAME + SLOT     and   CONC-NAME + `set-' + SLOT
851 The first one is to be used to read the element named in SLOT, and the
852 second is used to set it.  Also make a constant
853         CONC-NAME + `length'
854 that holds the number of slots."
855   (setq conc-name (symbol-name conc-name))
856   (let ((body '())
857         (i 0)
858         (L (length slots)))
859     (while slots
860       (setq body
861         (nconc body
862           (let ((readname (intern (concat conc-name (symbol-name (car slots)))))
863                 (setname (intern (concat conc-name "set-" (symbol-name (car slots))))))
864             (list
865               (list 'defmacro readname '(vector)
866                     (list 'list ''aref 'vector i))
867               (list 'defmacro setname '(vector value)
868                     (if (string= setname "bbdb-record-set-net")
869                         (list 'setq
870                               'bbdb-define-all-aliases-needs-rebuilt t))
871                     (list 'list ''aset 'vector i 'value))
872               ;(list 'put (list 'quote readname) ''edebug-form-hook ''(form))
873               ;(list 'put (list 'quote setname) ''edebug-form-hook ''(form form))
874               ))))
875       (setq slots (cdr slots) i (1+ i)))
876     (setq body (nconc body (list (list 'defconst
877                                        (intern (concat conc-name "length"))
878                                        L))))
879     (cons 'progn body)))
880
881 ;;; When reading this code, beware that "cache" refers to two things.
882 ;;; It refers to the cache slot of bbdb-record structures, which is
883 ;;; used for computed properties of the records; and it also refers
884 ;;; to a message-id --> bbdb-record association list which speeds up
885 ;;; the RMAIL, VM, and MH interfaces.
886
887 ;; Build reading and setting functions for firstname, lastname, aka,
888 ;; company, phones, addresses, net, raw-notes, and cache.  These are
889 ;; for accessing the high-level forms for the record.
890 (bbdb-defstruct bbdb-record-
891   firstname lastname aka company
892   phones addresses net raw-notes
893   cache
894   )
895
896 ;; HACKHACK
897 ;;(defmacro bbdb-record-set-net (vector value)
898 ;;  "We redefine the set-binding for 'net to detect changes"
899 ;;  (list 'progn
900 ;;        (list 'aset vector 6 value)
901 ;;        (list 'setq 'bbdb-define-all-aliases-needs-rebuilt t)))
902
903 (put 'company 'field-separator "; ")
904 (put 'notes 'field-separator "\n")
905
906 ;; Build reading and setting functions for location, area, exchange,
907 ;; suffix, and extension.  These are for accessing the elements of the
908 ;; individual phone number forms.
909 (bbdb-defstruct bbdb-phone-
910   location area exchange suffix extension
911   )
912
913 ;; Build reading and setting functions for location, street, city,
914 ;; state, zip and country.  These are for accessing the elements of
915 ;; the individual address forms.
916 (bbdb-defstruct bbdb-address-
917   location streets city state zip country
918   )
919
920 ;; Build reading and setting functions for namecache (the full name of
921 ;; the person referred to by the record), sortkey (the concatenation
922 ;; of the elements used for sorting the record), marker, and
923 ;; deleted-p.  These are for accessing the elements of the cache form,
924 ;; and are generally concatenations of data existing in separate parts
925 ;; of the record, stored here prebuilt for speed.
926 (bbdb-defstruct bbdb-cache-
927   namecache sortkey marker deleted-p
928   )
929
930 ;; Build the namecache for a record
931 (defsubst bbdb-record-name-1 (record)
932   (bbdb-cache-set-namecache (bbdb-record-cache record)
933     (let ((fname (bbdb-record-firstname record))
934           (lname (bbdb-record-lastname record)))
935       (if (> (length fname) 0)
936           (if (> (length lname) 0)
937               (concat fname " " lname)
938             fname)
939         lname))))
940
941 ;; Return the full name from a record.  If the name is not available
942 ;; in the namecache, the namecache value is generated (and stored).
943 (defun bbdb-record-name (record)
944   (or (bbdb-cache-namecache (bbdb-record-cache record))
945       (bbdb-record-name-1 record)))
946
947 ;; Return the sortkey for a record, building (and storing) it if
948 ;; necessary.
949 (defun bbdb-record-sortkey (record)
950   (or (bbdb-cache-sortkey (bbdb-record-cache record))
951       (bbdb-cache-set-sortkey (bbdb-record-cache record)
952         (downcase
953           (concat (bbdb-record-lastname record)
954                   (bbdb-record-firstname record)
955                   (bbdb-record-company record))))))
956
957 (defmacro bbdb-record-marker (record)
958   (list 'bbdb-cache-marker (list 'bbdb-record-cache record)))
959
960 (defmacro bbdb-record-deleted-p (record)
961   (list 'bbdb-cache-deleted-p (list 'bbdb-record-cache record)))
962
963 (defmacro bbdb-record-set-deleted-p (record val)
964   (list 'bbdb-cache-set-deleted-p (list 'bbdb-record-cache record) val))
965
966 (defmacro bbdb-record-set-namecache (record newval)
967   (list 'bbdb-cache-set-namecache (list 'bbdb-record-cache record) newval))
968
969 (defmacro bbdb-record-set-sortkey (record newval)
970   (list 'bbdb-cache-set-sortkey (list 'bbdb-record-cache record) newval))
971
972 (defmacro bbdb-record-set-marker (record newval)
973   (list 'bbdb-cache-set-marker (list 'bbdb-record-cache record) newval))
974
975
976 ;; The "notes" and "properties" accessors don't need to be fast.
977
978 (defun bbdb-record-notes (record)
979   (if (consp (bbdb-record-raw-notes record))
980       (cdr (assq 'notes (bbdb-record-raw-notes record)))
981       (bbdb-record-raw-notes record)))
982
983 ;; this works on the 'company field as well.
984 (defun bbdb-record-getprop (record property)
985   (if (memq property '(name address addresses phone phones net aka AKA))
986       (error "bbdb: cannot access the %s field this way" property))
987   (if (eq property 'company)
988       (bbdb-record-company record)
989     (if (consp (bbdb-record-raw-notes record))
990         (cdr (assq property (bbdb-record-raw-notes record)))
991       (if (and (eq property 'notes)
992                (stringp (bbdb-record-raw-notes record)))
993           (bbdb-record-raw-notes record)
994         nil))))
995
996 (defun bbdb-get-field (rec field &optional nn)
997   "Get the N-th element (or all if nil) of the notes FIELD of the REC.
998 If the note is absent, returns a zero length string."
999   (let ((note (or (bbdb-record-getprop rec field) "")))
1000     (if nn
1001         (nth nn (split-string note " ,;\t\n\f\r\v"))
1002         note)))
1003
1004 ;; this works on the 'company field as well.
1005 (defun bbdb-record-putprop (record property newval)
1006   (if (memq property '(name address addresses phone phones net aka AKA))
1007       (error "bbdb: cannot annotate the %s field this way" property))
1008   (if (eq property 'company)
1009       (bbdb-record-set-company record
1010         (bbdb-record-set-company record newval))
1011     (if (and (eq property 'notes)
1012              (not (consp (bbdb-record-raw-notes record))))
1013         (bbdb-record-set-raw-notes record newval)
1014       (or (listp (bbdb-record-raw-notes record))
1015           (bbdb-record-set-raw-notes record
1016             (list (cons 'notes (bbdb-record-raw-notes record)))))
1017       (let ((old (assq property (bbdb-record-raw-notes record))))
1018         (if old
1019             (if newval
1020                 (setcdr old newval)
1021               (bbdb-record-set-raw-notes record
1022                 (delq old (bbdb-record-raw-notes record))))
1023           (and newval
1024                (bbdb-record-set-raw-notes record
1025                  (append (bbdb-record-raw-notes record)
1026                          (list (cons property newval))))))))
1027     ;; save some file space: if we ever end up with ((notes . "...")),
1028     ;; replace it with the string.
1029     (if (and (consp (bbdb-record-raw-notes record))
1030              (null (cdr (bbdb-record-raw-notes record)))
1031              (eq 'notes (car (car (bbdb-record-raw-notes record)))))
1032         (bbdb-record-set-raw-notes record
1033           (cdr (car (bbdb-record-raw-notes record)))))
1034     )
1035   ;; If we're changing the company, then we need to sort, since the company
1036   ;; is the sortkey for nameless records.  This should almost never matter...
1037   (bbdb-change-record record (eq property 'company))
1038   newval)
1039
1040 (defun bbdb-record-set-notes (record newval)
1041   (if (consp (bbdb-record-raw-notes record))
1042       (bbdb-record-putprop record 'notes newval)
1043     (bbdb-record-set-raw-notes record newval)
1044     (bbdb-change-record record nil)))
1045
1046 (defun bbdb-phone-string (phone)
1047   (if (= 2 (length phone)) ; euronumbers....
1048       (aref phone 1)
1049     ;; numbers should come in two forms:
1050     ;; ["where" 415 555 1212 99] or ["where" "the number"]
1051     (if (stringp (aref phone 1))
1052         (error "doubleplus ungood: euronumbers unwork"))
1053     (concat (if (/= 0 (bbdb-phone-area phone))
1054                 (format "(%03d) " (bbdb-phone-area phone))
1055                 "")
1056             (if (/= 0 (bbdb-phone-exchange phone))
1057                 (format "%03d-%04d"
1058                         (bbdb-phone-exchange phone) (bbdb-phone-suffix phone))
1059                 "")
1060             (if (and (bbdb-phone-extension phone)
1061                      (/= 0 (bbdb-phone-extension phone)))
1062                 (format " x%d" (bbdb-phone-extension phone))
1063                 ""))))
1064
1065 ;; Legacy function.  Used to convert a zip datastructure string into a
1066 ;; formated string.  As zip codes are plain strings now, use
1067 ;; `bbdb-address-zip' instead.
1068 (defalias 'bbdb-address-zip-string 'bbdb-address-zip)
1069
1070 (defmacro bbdb-record-lessp (record1 record2)
1071   (list 'string< (list 'bbdb-record-sortkey record1)
1072                  (list 'bbdb-record-sortkey record2)))
1073
1074 (defmacro bbdb-subint (string match-number)
1075   (list 'string-to-number
1076         (list 'substring string
1077               (list 'match-beginning match-number)
1078               (list 'match-end match-number))))
1079
1080 (eval-and-compile
1081   (if (fboundp 'display-error)
1082       (fset 'bbdb-display-error 'display-error)
1083     (defun bbdb-display-error(msg stream)
1084       (message "Error: %s" (nth 1 msg)))))
1085
1086 (defmacro bbdb-error-retry (form)
1087   (list 'catch ''--bbdb-error-retry--
1088         (list 'while ''t
1089               (list 'condition-case '--c--
1090                     (list 'throw ''--bbdb-error-retry-- form)
1091                     '(error
1092                       (ding)
1093                       (let ((cursor-in-echo-area t))
1094                         (bbdb-display-error --c-- nil)
1095                         (sit-for 2)))))))
1096
1097 ;;; Completion on labels and field data
1098
1099 ;;; Realistically speaking, it doesn't make sense to offer minibuffer
1100 ;;; completion for some fields - like ones that don't have labels!
1101 ;;;
1102 ;;; Also, I could probably do this with macros similar to the
1103 ;;; def-struct stuff.
1104 (defcustom bbdb-default-label-list
1105  '("Home" "Office" "Mobile" "Other")
1106   "*Default list of labels for Address and Phone fields."
1107   :group 'bbdb-record-creation
1108   :type '(repeat string))
1109
1110 (defcustom bbdb-phones-label-list
1111   bbdb-default-label-list
1112   "*List of labels for Phone field.
1113 The default value is `bbdb-default-label-list'."
1114   :group 'bbdb-record-creation
1115   :type '(repeat string))
1116
1117 (defcustom bbdb-addresses-label-list
1118   bbdb-default-label-list
1119   "*List of labels for Address field.
1120 The default value is `bbdb-default-label-list'."
1121   :group 'bbdb-record-creation
1122   :type '(repeat string))
1123
1124 (defun bbdb-label-completion-list (field)
1125   "Figure out a completion list for the specified FIELD label.
1126 This evaluates the variable bbdb-FIELD-label-list, such
1127 as `bbdb-phones-label-list'."
1128   (if (boundp (intern (format "bbdb-%s-label-list" field)))
1129       (eval (intern (format "bbdb-%s-label-list" field)))
1130     ;; special-case out the ones it doesn't make sense for here?
1131     bbdb-default-label-list))
1132
1133 (defun bbdb-label-completion-default (field)
1134   "Figure out a default label from the completion list for FIELD.
1135 This evaluates the variable bbdb-default-FIELD-label, such
1136 as `bbdb-default-phones-label', if it exists, or it takes
1137 the first item from the list of completions for FIELD as
1138 returned by `bbdb-label-completion-list'."
1139   (if (boundp (intern (format "bbdb-default-%s-label" field)))
1140       (eval (intern (format "bbdb-default-%s-label" field)))
1141     (nth 0 (bbdb-label-completion-list field))))
1142
1143 ;; These are so you can accumulate e.g. mail aliases or company names
1144 ;; and have BBDB offer completion on them.
1145 (defun bbdb-data-completion-list (field)
1146   "Figure out a completion list for the specified FIELD value.
1147 This evaluates the variable bbdb-FIELD-data-list, such
1148 as `bbdb-mail-alias-data-list', if it exists, or it uses
1149 `bbdb-default-label-list'."
1150   (if (boundp (intern (format "bbdb-%s-data-list" field)))
1151       (eval (intern (format "bbdb-%s-data-list" field)))
1152     ;; special-case out the ones it doesn't make sense for here?
1153     bbdb-default-label-list))
1154
1155 (defun bbdb-data-completion-default (field)
1156   "Figure out a default value from the completion list for FIELD.
1157 This evaluates the variable bbdb-default-FIELD-data, such
1158 as `bbdb-default-mail-alias-data', if it exists, or it takes
1159 the first item from the list of completions for FIELD as
1160 returned by `bbdb-data-completion-list'."
1161   (if (boundp (intern (format "bbdb-default-%s-data" field)))
1162       (eval (intern (format "bbdb-default-%s-data" field)))
1163     (nth 0 (bbdb-label-completion-list field))))
1164
1165 ;;;
1166 (defvar bbdb-buffer nil)
1167 (defun bbdb-buffer ()
1168   (if (and bbdb-buffer (buffer-live-p bbdb-buffer))
1169       bbdb-buffer
1170     (when (and bbdb-file-remote
1171                (file-newer-than-file-p bbdb-file-remote bbdb-file))
1172       (let ((coding-system-for-write bbdb-file-coding-system))
1173     (copy-file bbdb-file-remote bbdb-file t t)))
1174     (setq bbdb-buffer (find-file-noselect bbdb-file 'nowarn))))
1175
1176 (defmacro bbdb-with-db-buffer (&rest body)
1177   (cons 'with-current-buffer
1178         (cons '(bbdb-buffer)
1179               (if (and (boundp 'bbdb-debug) bbdb-debug)
1180                   ;; if we're debugging, and the .bbdb buffer is visible in
1181                   ;; a window, temporarilly switch to that window so that
1182                   ;; when we come out, that window has been scrolled to the
1183                   ;; record we've just modified.  (make w-point = b-point)
1184                   (list
1185                     (list 'let '((w (and bbdb-debug
1186                                          (get-buffer-window
1187                                           (buffer-name
1188                                            (get-buffer bbdb-file))))))
1189                           (list 'save-excursion
1190                             (cons 'save-window-excursion
1191                                   (cons '(and w (select-window w))
1192                                         body)))))
1193                   body))))
1194
1195 (defsubst bbdb-string-trim (string)
1196   "Lose leading and trailing whitespace.  Also remove all properties
1197 from string."
1198   (if (string-match "\\`[ \t\n]+" string)
1199       (setq string (substring string (match-end 0))))
1200   (if (string-match "[ \t\n]+\\'" string)
1201       (setq string (substring string 0 (match-beginning 0))))
1202   ;; This is not ideologically blasphemous.  It is a bad function to
1203   ;; use on regions of a buffer, but since this is our string, we can
1204   ;; do whatever we want with it. --Colin
1205   (set-text-properties 0 (length string) nil string)
1206   string)
1207
1208 (defun bbdb-read-string (prompt &optional default completions)
1209   "Reads a string, trimming whitespace and text properties."
1210   (bbdb-string-trim
1211    (if completions
1212        (completing-read prompt completions nil nil (cons default 0))
1213      (bbdb-string-trim (read-string prompt default)))))
1214
1215 ;;; Address formatting.
1216
1217 (defcustom bbdb-time-display-format "%d %b %Y"
1218   "The format for the timestamp to be used in the creation-date and
1219 timestamp fields.  See the documentation for `format-time-string'."
1220   :group 'bbdb :type 'string)
1221
1222 (defun bbdb-time-convert (date &optional format)
1223   "Convert a date from the BBDB internal format to the format
1224 determined by FORMAT (or `bbdb-time-display-format' if FORMAT not
1225 present).  Returns a string containing the date in the new format."
1226   (let ((parts (bbdb-split date "-")))
1227     (format-time-string (or format bbdb-time-display-format)
1228                         (encode-time 0 0 0 (string-to-number (caddr parts))
1229                                      (string-to-number (cadr parts))
1230                                      (string-to-number (car parts))))))
1231
1232 (defalias 'bbdb-format-record-timestamp 'bbdb-time-convert)
1233 (defalias 'bbdb-format-record-creation-date 'bbdb-time-convert)
1234
1235 (defconst bbdb-gag-messages nil
1236   "Bind this to t to quiet things down - do not set it!")
1237
1238 (defconst bbdb-buffer-name "*BBDB*")
1239
1240 (defcustom bbdb-display-layout-alist
1241   '((one-line   (order     . (phones mail-alias net notes))
1242                 (name-end  . 24)
1243                 (toggle    . t))
1244     (multi-line (omit      . (creation-date timestamp))
1245                 (toggle    . t))
1246     (pop-up-multi-line)
1247     (full-multi-line))
1248   "*An alist describing each display layout.
1249 The format of an element is (LAYOUT-NAME OPTION-ALIST).
1250
1251 By default there are four different layout types used by BBDB, which are
1252 `one-line', `multi-line', `pop-up-multi-line' (used for pop-ups) and
1253 `full-multi-line' (showing all fields of a record).
1254
1255 OPTION-ALIST specifies the options for the layout.  Valid options are:
1256
1257                            ------- Availability --------
1258     Format                  one-line        multi-line        default if unset
1259 ------------------------------------------------------------------------------
1260  (toggle . BOOL)                 +               +              nil
1261  (order . FIELD-LIST)            +               +              '(phones ...)
1262  (omit . FIELD-LIST)             +               +              nil
1263  (name-end . INTEGER)            +               -              40
1264  (indentation . INTEGER)         -               +              14
1265  (primary . BOOL)                -               +              nil
1266  (test . SEXP)                   +               +              nil
1267
1268 - toggle: controls if this layout is included when toggeling the display layout
1269 - order: defines a user specific order for the fields, where `t' is a place
1270   holder for all remaining fields
1271 - omit: is a list of fields which should not be displayed or `t' to exclude all
1272   fields except those listed in the order option
1273 - name-end: sets the column where the name should end in one-line layout.
1274 - indentation: sets the level of indentation for multi-line display.
1275 - primary: controls wether only the primary net is shown or all are shown.
1276 - test: a lisp expression controlling wether the record is to be displayed.
1277
1278 When you add a new layout FOO, you can write a corresponding layout
1279 function bbdb-format-record-layout-FOO.  If you do not write your own
1280 layout function, the multi-line layout will be used."
1281   :group 'bbdb
1282   :type
1283   `(repeat
1284     (cons :tag "Layout Definition"
1285           (choice :tag "Layout type"
1286                   (const one-line)
1287                   (const multi-line)
1288           (const pop-up-multi-line)
1289                   (const full-multi-line)
1290                   (symbol))
1291           (set :tag "Properties"
1292                (cons :tag "Order"
1293                      (const :tag "List of fields to order by" order)
1294                      (repeat (choice (const phones)
1295                                      (const addresses)
1296                                      (const net)
1297                                      (const AKA)
1298                                      (const notes)
1299                                      (symbol :tag "other")
1300                                      (const :tag "Remaining fields" t))))
1301                (choice :tag "Omit"
1302                        :value (omit . nil)
1303                        (cons :tag "List of fields to omit"
1304                              (const :tag "Fields not to display" omit)
1305                              (repeat (choice (const phones)
1306                                              (const addresses)
1307                                              (const net)
1308                                              (const AKA)
1309                                              (const notes)
1310                                              (symbol :tag "other"))))
1311                        (const :tag "Exclude all fields except those listed in the order property" t))
1312                (cons :tag "Indentation"
1313                      :value (indentation . 14)
1314                      (const :tag "Level of indentation for multi-line layout"
1315                             indentation)
1316                      (number :tag "Column"))
1317                (cons :tag "End of name field"
1318                      :value (name-end . 24)
1319                      (const :tag "The column where the name should end in one-line layout"
1320                             name-end)
1321                      (number :tag "Column"))
1322                (cons :tag "Toggle"
1323                      (const :tag "The layout is included when toggling display layout" toggle)
1324                      boolean)
1325            (cons :tag "Primary Net Only"
1326              (const :tag "Only the primary net address is included" primary)
1327              boolean)
1328            (cons :tag "Test"
1329              (const :tag "Show only records passing this test" test)
1330              (choice (const :tag "No test" nil)
1331                  (cons :tag "List of required fields"
1332                    (const :tag "Choose from the attributes in the following set:" and)
1333                    (set
1334                     (const name)
1335                     (const company)
1336                     (const net)
1337                     (const phones)
1338                     (const addresses)
1339                     (const notes)))
1340                  (sexp :tag "Lisp expression")))))))
1341
1342
1343 (defcustom bbdb-display-layout 'multi-line
1344   "*The default display layout."
1345   :group 'bbdb
1346   :type '(choice (const one-line)
1347                  (const multi-line)
1348                  (const full-multi-line)
1349                  (symbol)))
1350
1351 (defcustom bbdb-pop-up-display-layout 'pop-up-multi-line
1352   "*The default display layout pop-up BBDB buffers, i.e. mail, news."
1353   :group 'bbdb
1354   :type '(choice (const one-line)
1355                  (const multi-line)
1356                  (const full-multi-line)
1357                  (symbol)))
1358
1359 (defun bbdb-display-layout-get-option (layout option)
1360   (let ((layout-spec (if (listp layout)
1361                          layout
1362                        (assoc layout bbdb-display-layout-alist)))
1363         option-value)
1364     (and layout-spec
1365          (setq option-value (assoc option layout-spec))
1366          (cdr option-value))))
1367
1368 (defcustom bbdb-address-formatting-alist
1369   '((bbdb-address-is-continental . bbdb-format-address-continental)
1370     (nil . bbdb-format-address-default))
1371   "Alist of address identifying and address formatting functions.
1372 The key is an identifying function which accepts an address.  The
1373 associated value is a formatting function which inserts the formatted
1374 address in the current buffer.  If the identifying function returns
1375 non-nil, the formatting function is called.  When nil is used as the
1376 car, then the associated formatting function will always be called.
1377 Therefore you should always have (nil . bbdb-format-address-default) as
1378 the last element in the alist.
1379
1380 All functions should take two arguments, the address and an indentation.
1381 The indentation argument may be optional.
1382
1383 This alist is used in `bbdb-format-address'.
1384
1385 See also `bbdb-address-print-formatting-alist'."
1386   :group 'bbdb-record-display
1387   :type '(repeat (cons function function)))
1388
1389 (defvar bbdb-address-print-formatting-alist) ; "bbdb-print"
1390
1391 (defun bbdb-address-is-continental (addr)
1392   "Return non-nil if the address ADDR is a continental address.
1393 This is done by comparing the zip code to `bbdb-continental-zip-regexp'.
1394
1395 This is a possible identifying function for
1396 `bbdb-address-formatting-alist' and
1397 `bbdb-address-print-formatting-alist'."
1398   (string-match bbdb-continental-zip-regexp (bbdb-address-zip addr)))
1399
1400 (defun bbdb-format-streets (addr indent)
1401   "Insert street subfields of address ADDR in current buffer.
1402 This may be used by formatting functions listed in
1403 `bbdb-address-formatting-alist'."
1404   (bbdb-mapc (lambda(str)
1405            (indent-to indent)
1406            (insert str "\n"))
1407          (bbdb-address-streets addr)))
1408
1409 (defun bbdb-format-address-continental (addr &optional indent)
1410   "Insert formated continental address ADDR in current buffer.
1411 This format is used in western Europe, for example.
1412
1413 This function is a possible formatting function for
1414 `bbdb-address-formatting-alist'.
1415
1416 The result looks like this:
1417        location: street
1418                  street
1419                  ...
1420                  zip city, state
1421                  country"
1422   (setq indent (or indent 14))
1423   (let (;(fmt (format " %%%ds: " indent))
1424         (indent (+ 3 indent)))
1425     ;(insert (format fmt (bbdb-address-location addr)))
1426     (bbdb-format-streets addr indent)
1427     (let ((c (bbdb-address-city addr))
1428           (s (bbdb-address-state addr))
1429           (z (bbdb-address-zip addr)))
1430       (if (or (> (length c) 0)
1431               (> (length z) 0)
1432               (> (length s) 0))
1433           (progn
1434             (indent-to indent)
1435             (insert z (if (and (> (length z) 0)
1436                                (> (length c) 0)) " " "")
1437                     c (if (and (or (> (length z) 0)
1438                                    (> (length c) 0))
1439                                (> (length s) 0)) ", " "")
1440                     s "\n"))))
1441     (let ((str (bbdb-address-country addr)))
1442       (if (= 0 (length str)) nil
1443         (indent-to indent) (insert str "\n")))))
1444
1445 (defun bbdb-format-address-default (addr &optional indent)
1446   "Insert formated address ADDR in current buffer.
1447 This is the default format; it is used in the US, for example.
1448
1449 This function is a possible formatting function for
1450 `bbdb-address-formatting-alist'.
1451
1452 The result looks like this:
1453        location: street
1454                  street
1455                  ...
1456                  city, state  zip
1457                  country"
1458   (setq indent (or indent 14))
1459   (let (;(fmt (format " %%%ds: " indent))
1460         (indent (+ 3 indent)))
1461 ;    (insert (format fmt (bbdb-address-location addr)))
1462     (bbdb-format-streets addr indent)
1463     (let ((c (bbdb-address-city addr))
1464           (s (bbdb-address-state addr))
1465           (z (bbdb-address-zip addr)))
1466       (if (or (> (length c) 0)
1467               (> (length z) 0)
1468               (> (length s) 0))
1469           (progn
1470             (indent-to indent)
1471             (insert c (if (and (> (length c) 0)
1472                                (> (length s) 0)) ", " "")
1473                     s (if (and (or (> (length c) 0)
1474                                    (> (length s) 0))
1475                                (> (length z) 0)) "  " "")
1476                     z "\n"))))
1477     (let ((str (bbdb-address-country addr)))
1478       (if (= 0 (length str)) nil
1479         (indent-to indent) (insert str "\n")))))
1480
1481 (defun bbdb-format-address (addr &optional printing indent)
1482   "Call appropriate formatting function for address ADDR.
1483
1484 If optional second argument PRINTING is non-nil, this uses the alist
1485 `bbdb-address-print-formatting-alist' to determine how the address is to
1486 formatted and inserted into the current buffer. This is used by
1487 `bbdb-print-format-record'.
1488
1489 If second argument PRINTING is nil, this uses the alist
1490 `bbdb-address-formatting-alist' to determine how the address is to
1491 formatted and inserted into the current buffer.  This is used by
1492 `bbdb-format-record'."
1493   ;; alist contains functions ((ident1 . format1) (ident2 . format2) ...)
1494   ;; the first identifying-function is (caar alist)
1495   ;; the first formatting-function is  (cdar alist)
1496   (let ((alist (if printing bbdb-address-print-formatting-alist
1497                  bbdb-address-formatting-alist)))
1498     ;; while there a functions left and the current function does not
1499     ;; identify the address, try the next function.
1500     (while (and (caar alist)
1501                 (null (funcall (caar alist) addr)))
1502       (setq alist (cdr alist)))
1503     ;; if we haven't reached the end of functions, we got a hit.
1504     (when alist
1505       (if printing
1506       (funcall (cdar alist) addr)
1507     (funcall (cdar alist) addr indent)))))
1508
1509 (defun bbdb-format-record-name-company (record)
1510   (let ((name (or (bbdb-record-name record) "???"))
1511         (company (bbdb-record-company record))
1512         (start (point)))
1513
1514     (insert name)
1515     (put-text-property start (point) 'bbdb-field '(name))
1516
1517     (when company
1518       (insert " - ")
1519       (setq start (point))
1520       (insert company)
1521       (put-text-property start (point) 'bbdb-field '(company)))))
1522
1523 (defun bbdb-format-record-one-line-phones (layout record phone)
1524   "Insert a formatted phone number for one-line display."
1525   (let ((start (point)))
1526     (insert (format "%s " (aref phone 1)))
1527     (put-text-property start (point) 'bbdb-field
1528                        (list 'phone phone (aref phone 0)))
1529     (setq start (point))
1530     (insert (format "(%s)" (aref phone 0)))
1531     (put-text-property start (point) 'bbdb-field
1532                        (list 'phone phone 'field-name))))
1533
1534 (defun bbdb-format-record-one-line-net (layout record net)
1535   "Insert a formatted list of nets for one-line display."
1536   (let ((start (point)))
1537     (insert net)
1538     (put-text-property start (point) 'bbdb-field (list 'net net))))
1539
1540 (defun bbdb-format-record-one-line-notes (layout record notes)
1541   "Insert formatted notes for one-line display.
1542 Line breaks will be removed and white space trimmed."
1543   (let ((start (point)))
1544     (insert (bbdb-replace-in-string notes "[\r\n\t ]+" " "))
1545     (put-text-property start (point) 'bbdb-field (list 'notes notes))))
1546
1547 (defun bbdb-format-record-layout-one-line (layout record field-list)
1548   "Record formatting function for the one-line layout.
1549 See `bbdb-display-layout-alist' for more."
1550   ;; name and company
1551   (bbdb-format-record-name-company record)
1552   (let ((name-end (or (bbdb-display-layout-get-option layout 'name-end)
1553                       40))
1554         start end)
1555     (save-excursion
1556       (setq end (point))
1557       (beginning-of-line)
1558       (setq start (point)))
1559     (when (> (- end start -1) name-end)
1560       (put-text-property (+ start name-end -4) end 'invisible t)
1561       (insert "..."))
1562     ;; guarantee one space after name - company
1563     (insert " ")
1564     (indent-to name-end))
1565   ;; rest of the fields
1566   (let (start field contentfun formatfun values value)
1567     (while field-list
1568       (setq field (car field-list)
1569             contentfun (intern (concat "bbdb-record-"
1570                                        (symbol-name field))))
1571       (if (fboundp contentfun)
1572           (setq values (eval (list contentfun record)))
1573         (setq values (bbdb-record-getprop record field)))
1574       (when (and (eq field 'net)
1575          (bbdb-display-layout-get-option layout 'primary))
1576     (setq values (list (car values))))
1577       (when values
1578         (if (not (listp values)) (setq values (list values)))
1579         (setq formatfun (intern (format "bbdb-format-record-%s-%s"
1580                                         layout field)))
1581         (while values
1582           (setq start (point)
1583                 value (car values))
1584           (if (fboundp formatfun)
1585               (funcall formatfun layout record value)
1586             (insert (format "%s" value))
1587             (cond ((eq field 'addresses)
1588                    (put-text-property start (point) 'bbdb-field
1589                                       (list 'address value)))
1590                   ((eq field 'phones)
1591                    (put-text-property start (point) 'bbdb-field
1592                                       (list 'phone value)))
1593                   ((memq field '(name net aka))
1594                    (put-text-property start (point) 'bbdb-field
1595                                       (list field value )))
1596                   (t
1597                    (put-text-property start (point) 'bbdb-field
1598                                       (list 'property (list field value))))))
1599           (setq values (cdr values))
1600           (if values (insert ", ")))
1601         (insert "; "))
1602       (setq field-list (cdr field-list))))
1603   ;; delete the trailing "; "
1604   (backward-delete-char 2)
1605   (insert "\n"))
1606
1607 (defun bbdb-format-record-layout-multi-line (layout record field-list)
1608   "Record formatting function for the multi-line layout.
1609 See `bbdb-display-layout-alist' for more."
1610   (bbdb-format-record-name-company record)
1611   (insert "\n")
1612   (let* ((notes (bbdb-record-raw-notes record))
1613          (indent (or (bbdb-display-layout-get-option layout 'indentation) 14))
1614          (fmt (format " %%%ds: " indent))
1615          start field)
1616     (if (stringp notes)
1617         (setq notes (list (cons 'notes notes))))
1618     (while field-list
1619       (setq field (car field-list)
1620             start (point))
1621       (cond ((eq field 'phones)
1622              (let ((phones (bbdb-record-phones record))
1623                    loc phone)
1624                (while phones
1625                  (setq phone (car phones)
1626                        start (point))
1627                  (setq loc (format fmt (bbdb-phone-location phone)))
1628                  (insert loc)
1629                  (put-text-property start (point) 'bbdb-field
1630                                     (list 'phone phone 'field-name))
1631                  (setq start (point))
1632                  (insert (bbdb-phone-string phone) "\n")
1633                  (put-text-property start (point) 'bbdb-field
1634                                     (list 'phone phone
1635                                           (bbdb-phone-location phone)))
1636                  (setq phones (cdr phones))))
1637              (setq start nil))
1638             ((eq field 'addresses)
1639              (let ((addrs (bbdb-record-addresses record))
1640                    loc addr)
1641                (while addrs
1642                  (setq addr (car addrs)
1643                        start (point))
1644                  (setq loc (format fmt (bbdb-address-location addr)))
1645                  (insert loc)
1646                  (put-text-property start (point) 'bbdb-field
1647                                     (list 'address addr 'field-name))
1648                  (setq start (point))
1649                  (bbdb-format-address addr nil indent)
1650                  (put-text-property start (point) 'bbdb-field
1651                                     (list 'address addr
1652                                           (bbdb-address-location addr)))
1653                  (setq addrs (cdr addrs))))
1654              (setq start nil))
1655             ((eq field 'net)
1656              (let ((net (bbdb-record-net record)))
1657                (when net
1658                  (insert (format fmt "net"))
1659                  (put-text-property start (point) 'bbdb-field
1660                                     '(net field-name))
1661                  (setq start (point))
1662          (if (bbdb-display-layout-get-option layout 'primary)
1663              (insert (car net) "\n")
1664            (insert (mapconcat (function identity) net ", ") "\n"))
1665                  (put-text-property start (point) 'bbdb-field '(net)))))
1666             ((eq field 'aka)
1667              (let ((aka (bbdb-record-aka record)))
1668                (when aka
1669                  (insert (format fmt "AKA"))
1670                  (put-text-property start (point) 'bbdb-field
1671                                     '(aka field-name))
1672                  (insert (mapconcat (function identity) aka ", ") "\n")
1673                  (setq start (point))
1674                  (put-text-property start (point) 'bbdb-field '(aka)))))
1675             (t
1676              (let ((note (assoc field notes))
1677                    (indent (length (format fmt "")))
1678                    p notefun)
1679                (when note
1680                  (insert (format fmt field))
1681                  (put-text-property start (point) 'bbdb-field
1682                                     (list 'property note 'field-name))
1683                  (setq start (point))
1684                  (setq p (point)
1685                        notefun (intern (format "bbdb-format-record-%s" field)))
1686                  (if (fboundp notefun)
1687                      (insert (funcall notefun (cdr note)))
1688                    (insert (cdr note)))
1689                  (save-excursion
1690                    (save-restriction
1691                      (narrow-to-region p (1- (point)))
1692                      (goto-char (1+ p))
1693                      (while (search-forward "\n" nil t)
1694                        (insert (make-string indent ?\ )))))
1695                  (insert "\n"))
1696                (put-text-property start (point) 'bbdb-field
1697                                   (list 'property note)))))
1698       (setq field-list (cdr field-list)))))
1699
1700 (defalias 'bbdb-format-record-layout-full-multi-line
1701   'bbdb-format-record-layout-multi-line)
1702
1703 (defalias 'bbdb-format-record-layout-pop-up-multi-line
1704   'bbdb-format-record-layout-multi-line)
1705
1706 (defun bbdb-format-record (record &optional layout)
1707   "Insert a formatted version of RECORD into the current buffer.
1708
1709 LAYOUT can be a symbol describing a layout in
1710 `bbdb-display-layout-alist'.  For compatibility reasons, LAYOUT can
1711 also be nil or t, where t stands for the one-line, and nil for the
1712 multi-line layout."
1713   (bbdb-debug (if (bbdb-record-deleted-p record)
1714                   (error "plus ungood: formatting deleted record")))
1715   (setq layout (cond ((eq nil layout)
1716                       'multi-line)
1717                      ((eq t layout)
1718                       'one-line)
1719                      ((symbolp layout)
1720                       layout)
1721                      (t
1722                       (error "Unknown layout `%s'" layout))))
1723   (let* ((layout-spec (assoc layout bbdb-display-layout-alist))
1724      (test        (bbdb-display-layout-get-option layout-spec 'test))
1725          (omit-list   (bbdb-display-layout-get-option layout-spec 'omit))
1726          (order-list  (bbdb-display-layout-get-option layout-spec 'order))
1727          (all-fields  (append '(phones addresses net aka)
1728                               (let ((raw-notes (bbdb-record-raw-notes record)))
1729                                 (if (stringp raw-notes)
1730                                     '(notes)
1731                                   (mapcar (lambda (r) (car r)) raw-notes)))))
1732          format-function field-list)
1733     (when (or (not test)
1734           ;; bind some variables for the test
1735           (let ((name (bbdb-record-name record))
1736             (company (bbdb-record-company record))
1737             (net (bbdb-record-net record))
1738             (phones (bbdb-record-phones record))
1739             (addresses (bbdb-record-addresses record))
1740             (notes (bbdb-record-raw-notes record)))
1741         ;; this must evaluate to non-nil if the record is to be shown
1742         (eval test)))
1743       (if (functionp omit-list)
1744       (setq omit-list (funcall omit-list record layout)))
1745       (if (functionp order-list)
1746       (setq order-list (funcall order-list record layout)))
1747       ;; first omit unwanted fields
1748       (when (and omit-list (or (not order-list) (memq t order-list)))
1749     (if (not (listp omit-list))
1750         ;; t => show nothing
1751         (setq all-fields nil)
1752       ;; listp => show all fields except those listed here
1753       (while omit-list
1754         (setq all-fields (delete (car omit-list) all-fields)
1755           omit-list (cdr omit-list)))))
1756       ;; then order them
1757       (if (not order-list)
1758       (setq field-list all-fields)
1759     (if (not (memq t order-list))
1760         (setq field-list order-list)
1761       (setq order-list (reverse order-list))
1762       (setq all-fields (delete nil (mapcar (lambda (f)
1763                          (if (memq f order-list)
1764                              nil
1765                            f))
1766                            all-fields)))
1767       (while order-list
1768         (if (eq t (car order-list))
1769         (setq field-list (append all-fields field-list))
1770           (setq field-list (cons (car order-list) field-list)))
1771         (setq order-list (cdr order-list)))))
1772       ;; call the actual format function
1773       (setq format-function
1774         (intern (format "bbdb-format-record-layout-%s" layout)))
1775       (if (functionp format-function)
1776       (funcall format-function layout record field-list)
1777     (bbdb-format-record-layout-multi-line layout record field-list)))))
1778
1779 (defun bbdb-frob-mode-line (n)
1780   (setq
1781    ;; identification
1782    mode-line-buffer-identification
1783    (if (> n 0)
1784        (list 24 (buffer-name) ": "
1785              (list 10 (format "%d/%d" n (length (bbdb-records))))
1786              '(bbdb-showing-changed-ones " !!" "   "))
1787      (list (buffer-name) ": Insidious Big Brother Database v" bbdb-version " "
1788            mode-line-modified "-"))
1789    ;; modified indicator
1790    mode-line-modified
1791    '(bbdb-readonly-p "--%%%%-" (bbdb-modified-p "--**-" "-----"))))
1792
1793 (defun bbdb-display-records-1 (records &optional append layout)
1794   (setq append (or append (bbdb-append-records-p)))
1795
1796   (if (or (null records)
1797           (consp (car records)))
1798       nil
1799
1800     ;; add layout and a marker to the local list of records
1801     (setq layout (or layout bbdb-display-layout))
1802     (setq records (mapcar (lambda (x)
1803                             (list x layout (make-marker)))
1804                           records)))
1805
1806   (let ((b (current-buffer))
1807         (temp-buffer-setup-hook nil)
1808         (temp-buffer-show-hook nil)
1809         (first (car (car records))))
1810
1811     (if bbdb-multiple-buffers (bbdb-pop-up-bbdb-buffer))
1812
1813     (with-output-to-temp-buffer bbdb-buffer-name
1814       (set-buffer bbdb-buffer-name)
1815
1816       ;; If append is set, clear the buffer, otherwise do clean up.
1817       (unless append (bbdb-undisplay-records))
1818
1819       ;; If we're appending these records to the ones already displayed,
1820       ;; then first remove any duplicates, and then sort them.
1821       (if append
1822           (let ((rest records))
1823             (while rest
1824               (if (assq (car (car rest)) bbdb-records)
1825                   (setq records (delq (car rest) records)))
1826               (setq rest (cdr rest)))
1827             (setq records (append bbdb-records records))
1828             (setq records
1829                   (sort records
1830                         (lambda (x y) (bbdb-record-lessp (car x) (car y)))))))
1831       (make-local-variable 'mode-line-buffer-identification)
1832       (make-local-variable 'mode-line-modified)
1833       (set (make-local-variable 'bbdb-showing-changed-ones) nil)
1834       (let ((done nil)
1835             (rest records)
1836             (changed (bbdb-changed-records)))
1837         (while (and rest (not done))
1838           (setq done (memq (car (car rest)) changed)
1839                 rest (cdr rest)))
1840         (setq bbdb-showing-changed-ones done))
1841       (bbdb-frob-mode-line (length records))
1842       (and (not bbdb-gag-messages)
1843            (not bbdb-silent-running)
1844            (message "Formatting..."))
1845       (bbdb-mode)
1846       ;; this in in the *BBDB* buffer, remember, not the .bbdb buffer.
1847       (set (make-local-variable 'bbdb-records) nil)
1848       (setq bbdb-records records)
1849       (let ((buffer-read-only nil)
1850             prs)
1851         (bbdb-debug (setq prs (bbdb-records)))
1852         (setq truncate-lines t)
1853         (while records
1854           (bbdb-debug (if (not (memq (car (car records)) prs))
1855                           (error "record doubleplus unpresent!")))
1856           (set-marker (nth 2 (car records)) (point))
1857           (bbdb-format-record (nth 0 (car records))
1858                               (nth 1 (car records)))
1859           (setq records (cdr records))))
1860       (and (not bbdb-gag-messages)
1861            (not bbdb-silent-running)
1862            (message "Formatting...done.")))
1863     (set-buffer bbdb-buffer-name)
1864     (if (and append first)
1865         (let ((cons (assq first bbdb-records))
1866               (window (get-buffer-window (current-buffer))))
1867           (if window (set-window-start window (nth 2 cons)))))
1868     (bbdbq)
1869     ;; this doesn't really belong here, but it's convenient ... and when
1870     ;; using electric display it would not be called otherwise.
1871     (save-excursion (run-hooks 'bbdb-list-hook))
1872     (if bbdb-gui (bbdb-fontify-buffer))
1873     (set-buffer-modified-p nil)
1874     (setq buffer-read-only t)
1875     (set-buffer b)))
1876
1877 (defun bbdb-undisplay-records ()
1878   (let ((bbdb-display-buffer (get-buffer bbdb-buffer-name)))
1879     (if (bufferp bbdb-display-buffer)
1880         (save-excursion
1881           (set-buffer bbdb-display-buffer)
1882           (setq bbdb-showing-changed-ones nil
1883                 mode-line-modified nil
1884                 bbdb-records nil
1885                 buffer-read-only nil)
1886           (erase-buffer)
1887           (setq buffer-read-only t)
1888           (set-buffer-modified-p nil)))))
1889
1890 \f;;; Electric display stuff
1891
1892 (defconst bbdb-inside-electric-display nil)
1893 ;; hack hack: a couple of specials that the electric stuff uses for state.
1894 (defvar bbdb-electric-execute-me)
1895 (defvar bbdb-electric-completed-normally)
1896
1897 (defun electric-bbdb-display-records (records)
1898   (require 'electric)
1899   (let ((bbdb-electric-execute-me nil))   ; Hack alert!  throw-to-execute sets this!
1900    (let ((bbdb-inside-electric-display t)
1901          buffer
1902          bbdb-electric-completed-normally ; Hack alert!  throw-to-execute sets this!
1903          )
1904     (save-excursion
1905      (save-window-excursion
1906       (save-window-excursion (bbdb-display-records-1 records))
1907       (setq buffer (window-buffer (Electric-pop-up-window bbdb-buffer-name)))
1908       (set-buffer buffer)
1909       (if (not bbdb-gag-messages)
1910           (message "<<< Press Space to bury the Insidious Big Brother Database list >>>"))
1911       (catch 'Done
1912         (while t
1913           (catch 'Blow-off-the-error
1914             (setq bbdb-electric-completed-normally nil)
1915             (unwind-protect
1916                 (progn
1917                    (catch 'electric-bbdb-list-select
1918                      (Electric-command-loop 'electric-bbdb-list-select
1919                                             "-> " t))
1920                    (setq bbdb-electric-completed-normally t))
1921               ;; protected
1922               (if bbdb-electric-completed-normally
1923                   (throw 'Done t)
1924                 (ding)
1925                 (message "BBDB-Quit")
1926                 (throw 'Blow-off-the-error t)
1927                 )))))
1928       (bury-buffer buffer))))
1929    (message " ")
1930    (if bbdb-electric-execute-me
1931        (eval bbdb-electric-execute-me)))
1932   nil)
1933
1934 (defun bbdb-electric-throw-to-execute (form-to-execute)
1935   "Exit the `electric-command-loop' and evaluate the given form."
1936   ;; Hack alert!  These variables are bound only within the scope of
1937   ;; bbdb-electric-display-records!
1938   (if (not (boundp 'bbdb-electric-execute-me))
1939       (error "plusungood: electrical short"))
1940   (setq bbdb-electric-execute-me form-to-execute
1941         bbdb-electric-completed-normally t)
1942   (throw 'electric-bbdb-list-select t))
1943
1944
1945 (defun bbdb-done-command () (interactive)
1946   (throw 'electric-bbdb-list-select t))
1947
1948 (defun bbdb-bury-buffer ()
1949   (interactive)
1950   (if bbdb-inside-electric-display
1951       (bbdb-done-command)
1952     (bury-buffer)))
1953
1954 (defun bbdb-display-records (records &optional layout append)
1955   (let ((bbdb-window (get-buffer-window bbdb-buffer-name)))
1956     (if (and bbdb-electric-p
1957              ;; never be electric if the buffer is already on screen.
1958              (not bbdb-window))
1959         (progn
1960           (define-key bbdb-mode-map " " 'bbdb-done-command)
1961           (electric-bbdb-display-records records))
1962       (bbdb-display-records-1 records append layout)
1963       ;; don't smash keybinding if they invoked `bbdb-display'
1964       ;; from inside an electric loop.
1965       (unless bbdb-inside-electric-display
1966         (define-key bbdb-mode-map " " 'undefined))
1967       (if (and (not bbdb-gag-messages)
1968                (not bbdb-window))
1969           (message
1970            (substitute-command-keys
1971             (if (one-window-p t)
1972                 (if pop-up-windows
1973                     "Type \\[delete-other-windows] to unshow the bbdb-list window."
1974                     "Type \\[switch-to-buffer] RET to unshow the bbdb-list window.")
1975                 "Type \\[switch-to-buffer-other-window] RET to restore old contents of the bbdb-list window.")))))))
1976
1977 (defun bbdbq ()
1978   (if (not (zerop (logand (random) 31))) nil
1979     (let ((v '["\104\157\156\47\164\40\163\165\163\160\145\143\164\40\171\157\
1980 \165\162\40\156\145\151\147\150\142\157\162\72\40\162\145\160\157\162\164\40\
1981 \150\151\155\41" "\146\156\157\162\144" "\103\157\156\163\165\155\145\40\55\55\
1982 \40\102\145\40\123\151\154\145\156\164\40\55\55\40\104\151\145" "\114\157\166\
1983 \145\40\102\151\147\40\102\162\157\164\150\145\162" "\114\145\145\40\110\141\
1984 \162\166\145\171\40\117\163\167\141\154\144\40\141\143\164\145\144\40\141\154\
1985 \157\156\145" "\101\114\114\40\131\117\125\122\40\102\101\123\105\40\101\122\
1986 \105\40\102\105\114\117\116\107\40\124\117\40\125\123" "\127\141\162\40\151\
1987 \163\40\120\145\141\143\145" "\106\162\145\145\144\157\155\40\151\163\40\123\
1988 \154\141\166\145\162\171" "\111\147\156\157\162\141\156\143\145\40\151\163\40\
1989 \123\164\162\145\156\147\164\150" "\120\162\157\154\145\163\40\141\156\144\40\
1990 \141\156\151\155\141\154\163\40\141\162\145\40\146\162\145\145"]))
1991       (message (aref v (% (logand 255 (random)) (length v))))
1992       (message " "))))
1993
1994 (defmacro bbdb-hashtable ()
1995   '(bbdb-with-db-buffer (bbdb-records nil t) bbdb-hashtable))
1996
1997 (defun bbdb-changed-records ()
1998   (bbdb-with-db-buffer (bbdb-records nil t) bbdb-changed-records))
1999
2000 (defmacro bbdb-build-name (f l)
2001   (list 'downcase
2002         (list 'if (list '= (list 'length f) 0) l
2003               (list 'if (list '= (list 'length l) 0) f
2004                     (list 'concat f " " l)))))
2005
2006 (defun bbdb-remove! (e l)
2007   (if (null l) l
2008     (let ((ret l)
2009           (n   (cdr l)))
2010       (while n
2011         (if (eq e (car n))
2012             (setcdr l (cdr n)) ; skip n
2013           (setq l n))          ; keep n
2014         (setq n (cdr n)))
2015       (if (eq e (car ret)) (cdr ret)
2016         ret))))
2017
2018 (defun bbdb-remove-memq-duplicates (l)
2019   (let (ret tail)
2020     (setq ret (cons '() '())
2021           tail ret)
2022     (while l
2023       (if (not (memq (car l) ret))
2024           (setq tail (setcdr tail (cons (car l) '()))))
2025       (setq l (cdr l)))
2026     (cdr ret)))
2027
2028 (defmacro bbdb-gethash (name &optional ht)
2029   (list 'symbol-value
2030         (list 'intern-soft name
2031               (or ht '(bbdb-hashtable)))))
2032
2033 (defmacro bbdb-puthash (name record &optional ht)
2034   (list 'let (list (list 'sym (list 'intern name (or ht '(bbdb-hashtable)))))
2035         (list 'set 'sym (list 'cons record
2036                               '(and (boundp sym) (symbol-value sym))))))
2037
2038 (defmacro bbdb-remhash (name record &optional ht)
2039   (list 'let (list (list 's (list 'intern-soft name
2040                                   (or ht '(bbdb-hashtable)))))
2041         (list 'and 's (list 'set 's (list 'bbdb-remove! record
2042                                           (list 'symbol-value 's))))))
2043
2044 (defsubst bbdb-search-intertwingle (name net)
2045   "Find bbdb records matching NAME and NET.
2046
2047 This is a more stringent version of bbdb-search-simple, which I am
2048 not inclined to modify for fear of damaging other code that currently
2049 relies on it. BBDB internals should be migrated to use this function
2050 to identify which record is referred to by a name/net combination,
2051 since search-simple has been overloaded with other functionality.
2052
2053 The name comes from
2054 http://www.mozilla.org/blue-sky/misc/199805/intertwingle.html, which
2055 any budding BBDB hacker should be at least vaguely familiar with."
2056   (bbdb-records t)
2057   (if name (setq name (downcase name)))
2058   (if net (setq net (downcase net))
2059     (setq net ""))
2060   (let ((net-recs (bbdb-gethash (downcase net)))
2061         recs)
2062     (while net-recs
2063       (if (or (and (not name) net)
2064               (string= name (downcase (bbdb-record-name (car net-recs)))))
2065           (add-to-list 'recs (car net-recs)))
2066       (setq net-recs (cdr net-recs)))
2067     recs))
2068
2069 (defsubst bbdb-search-simple (name net)
2070   "name is a string; net is a string or list of strings."
2071   (if (eq 0 (length name)) (setq name nil))
2072   (if (eq 0 (length net)) (setq net nil))
2073   (bbdb-records t) ; make sure db is parsed; don't check disk (faster)
2074   (let ((name-recs (if name ;; filter out companies from hash
2075                        (let ((recs (bbdb-gethash (downcase name)))
2076                              answer)
2077                          (while recs
2078                            (let ((n-rec (car recs)))
2079                              (if (string= (downcase name)
2080                                           (downcase
2081                                            (or (bbdb-record-name
2082                                                 n-rec)
2083                                                (bbdb-record-company
2084                                                 n-rec)
2085                                                "")))
2086                                  (setq answer (append recs (list n-rec))))
2087                              (setq recs (cdr recs))))
2088                          answer)))
2089         (net-recs  (if (stringp net) (bbdb-gethash (downcase net))
2090                      (let (answer)
2091                        (while (and net (null answer))
2092                          (setq answer (bbdb-gethash (downcase (car net)))
2093                                net (cdr net)))
2094                        answer)))
2095         ret)
2096     (if (not (and name-recs net-recs))
2097         (or (and name-recs (car name-recs))
2098             (and net-recs (car net-recs)))
2099
2100       (while name-recs
2101         (let ((name-rec (car name-recs))
2102               (nets     net-recs))
2103           (while nets
2104             (if (eq (car nets) name-rec)
2105                 (setq nets      '()
2106                       name-recs '()
2107                       ret name-rec)
2108               (setq nets (cdr nets))))
2109           (if name-recs (setq name-recs (cdr name-recs))
2110               name-rec)))
2111       ret)))
2112
2113 (defun bbdb-net-convert (record)
2114   "Given a record whose net field is a comma-separated string, convert it to
2115 a list of strings (the new way of doing things.)  Returns the new list."
2116   (bbdb-record-set-net record (bbdb-split (bbdb-record-net record) ",")))
2117
2118 (defun bbdb-split (string separators)
2119   "Return a list by splitting STRING at SEPARATORS.
2120 The inverse function of `bbdb-join'."
2121   (let (result
2122         (not-separators (concat "^" separators)))
2123     (save-excursion
2124       (set-buffer (get-buffer-create " *split*"))
2125       (erase-buffer)
2126       (insert string)
2127       (goto-char (point-min))
2128       (while (progn
2129                (skip-chars-forward separators)
2130                (skip-chars-forward " \t\n\r")
2131                (not (eobp)))
2132         (let ((begin (point))
2133               p)
2134           (skip-chars-forward not-separators)
2135           (setq p (point))
2136           (skip-chars-backward " \t\n\r")
2137           (setq result (cons (buffer-substring begin (point)) result))
2138           (goto-char p)))
2139       (erase-buffer))
2140     (nreverse result)))
2141
2142 (defun bbdb-join (list separator)
2143   "Join a LIST to a string where the list elements are separated by SEPARATOR.
2144 The inverse function of `bbdb-split'."
2145   (when list
2146     (mapconcat 'identity list separator)))
2147
2148 (defsubst bbdb-hash-record (record)
2149   "Insert the record in the appropriate hashtables.  This must be called
2150 while the .bbdb buffer is selected."
2151   (let ((name    (bbdb-record-name-1  record))  ; faster version
2152         (lastname (bbdb-record-lastname record))
2153         (company (bbdb-record-company record))
2154         (aka     (bbdb-record-aka     record))
2155         (net     (bbdb-record-net     record)))
2156     (if (> (length name) 0)
2157         (bbdb-puthash (downcase name)    record bbdb-hashtable))
2158     (if (> (length lastname) 0)
2159         (bbdb-puthash (downcase lastname)    record bbdb-hashtable))
2160     (if (> (length company) 0)
2161         (bbdb-puthash (downcase company) record bbdb-hashtable))
2162     (while aka
2163       (bbdb-puthash (downcase (car aka)) record bbdb-hashtable)
2164       (setq aka (cdr aka)))
2165     (while net
2166       (bbdb-puthash (downcase (car net)) record bbdb-hashtable)
2167       (setq net (cdr net)))))
2168
2169 \f
2170 ;;; Reading the BBDB
2171
2172 (defvar inside-bbdb-records nil
2173   "Internal variable.  Do not touch.")
2174
2175 (defvar bbdb-write-file-hooks '(bbdb-write-file-hook-fn)
2176   "*The list of functions added to `local-write-file-hooks' in `bbdb-file'.")
2177
2178 (defun bbdb-records (&optional dont-check-disk already-in-db-buffer)
2179   "Return a list of all bbdb records; read in and parse the db if necessary.
2180 This also notices if the disk file has changed out from under us, unless
2181 optional arg DONT-CHECK-DISK is non-nil (which is faster, but hazardous.)"
2182   (if inside-bbdb-records
2183       (let ((debug-on-error t))
2184         (error "catastrophic: bbdb-records recursed")))
2185   (let ((inside-bbdb-records t)
2186         (buf (if already-in-db-buffer (current-buffer) (bbdb-buffer)))
2187         shut-up)
2188     (with-current-buffer buf
2189       ;; make sure the BBDB in memory is not out of synch with disk.
2190       (cond (dont-check-disk nil)
2191             ((verify-visited-file-modtime buf) nil)
2192             ((and bbdb-auto-revert-p (not (buffer-modified-p buf)))
2193              (message "BBDB has changed on disk, reverting...")
2194              (setq shut-up t)
2195              (revert-buffer t t))
2196             ;; hassle the user
2197             ((bbdb-yes-or-no-p
2198               (if (buffer-modified-p buf)
2199                   "BBDB has changed on disk; flush your changes and revert? "
2200                   "BBDB has changed on disk; revert? "))
2201              (or (file-exists-p bbdb-file)
2202                  (error "bbdb: file %s no longer exists!!" bbdb-file))
2203              (revert-buffer t t))
2204             ;; this is the case where the .bbdb file has changed; the buffer
2205             ;; has changed as well; and the user has answered "no" to the
2206             ;; "flush your changes and revert" question.  The only other
2207             ;; alternative is to save the file right now.  If they answer
2208             ;; no to the following question, they will be asked the
2209             ;; preceeding question again and again some large (but finite)
2210             ;; number of times.  `bbdb-records' is called a lot, you see...
2211             ((buffer-modified-p buf)
2212              ;; this prompts
2213              (bbdb-save-db t t))
2214             ;; otherwise, the buffer and file are inconsistent, but we let
2215             ;; them stay that way.
2216             )
2217       (unless (assq 'bbdb-records (buffer-local-variables))
2218         (set (make-local-variable 'bbdb-records) nil)
2219         (set (make-local-variable 'bbdb-changed-records) nil)
2220         (set (make-local-variable 'bbdb-end-marker) nil)
2221         (set (make-local-variable 'bbdb-hashtable) nil)
2222         (set (make-local-variable 'bbdb-propnames) nil)
2223         (set (make-local-variable 'revert-buffer-function)
2224              'bbdb-revert-buffer)
2225         (bbdb-mapc (lambda (ff) (add-hook 'local-write-file-hooks ff))
2226               bbdb-write-file-hooks)
2227         (setq bbdb-hashtable (make-vector bbdb-hashtable-size 0)))
2228       (setq bbdb-modified-p (buffer-modified-p)
2229             buffer-read-only bbdb-readonly-p)
2230       (or bbdb-records
2231       (cond ((= (point-min) (point-max)) ; special-case empty db
2232              ;; this doesn't need to be insert-before-markers because
2233              ;; there are no db-markers in this buffer.
2234              (insert (format ";; -*-coding: %s;-*-\n;;; file-version: %d\n"
2235                  bbdb-file-coding-system bbdb-file-format))
2236              (bbdb-flush-all-caches)
2237              (setq bbdb-end-marker (point-marker))
2238              ;;(run-hooks 'bbdb-after-read-db-hook) ; run this?
2239              nil)
2240             (t
2241              (or shut-up bbdb-silent-running (message "Parsing BBDB..."))
2242              (bbdb-flush-all-caches)
2243              (cond ((and bbdb-notice-auto-save-file
2244                          (file-newer-than-file-p (make-auto-save-file-name)
2245                                                  buffer-file-name))
2246                     (if (bbdb-yes-or-no-p "BBDB auto-save file is newer; recover it? ")
2247                         (progn
2248                           (recover-file buffer-file-name)
2249                           (bury-buffer (current-buffer)) ; recover-file selects it
2250                           (auto-save-mode 1) ; turn autosave back on
2251                           (delete-file (make-auto-save-file-name))
2252                           (message "Auto-save mode is ON in BBDB buffer.  Suggest you save it soon.")
2253                           (sleep-for 2))
2254                       ;; delete auto-save anyway, so we don't keep asking.
2255                       (condition-case nil
2256                           (delete-file (make-auto-save-file-name))
2257                         (file-error nil)))
2258                     ;; tail-recurse and try again
2259                     (let ((inside-bbdb-records nil))
2260                       (bbdb-records)))
2261                    (t
2262                     ;; normal case
2263                     (fillarray bbdb-hashtable 0)
2264                     (parse-bbdb-internal)))))))))
2265
2266 (defun bbdb-revert-buffer (arg noconfirm)
2267   ;; The .bbdb file's revert-buffer-function.
2268   ;; Don't even think of calling this.
2269   (kill-all-local-variables)            ; clear db and caches.
2270   (if (get-buffer bbdb-buffer-name)     ; now contains invalid records; nukem.
2271       (bbdb-undisplay-records))
2272   (let ((revert-buffer-function nil))   ; don't loop.
2273     (revert-buffer arg noconfirm)))
2274
2275 (defun parse-bbdb-internal ()
2276   (bbdb-debug (message "Parsing BBDB... (reading...)"))
2277   (widen)
2278   (goto-char (point-min))
2279   ;; go to the point at which the first record begins
2280   (cond ((eq (following-char) ?\[) nil)
2281         ((search-forward "\n[" nil 0) (forward-char -1))
2282         (t nil)) ;; no records
2283   ;; look backwards for user-defined field names (for completion purposes.)
2284   (save-excursion
2285     (if (re-search-backward "^;+[ \t]*user-fields:[ \t]*\(" nil t)
2286         (progn
2287           (goto-char (1- (match-end 0)))
2288           (setq bbdb-propnames
2289                 (mapcar (lambda (x) (list (symbol-name x)))
2290                         (read (point-marker)))))))
2291   ;; look backwards for file version, and convert if necessary.
2292   ;; (at least, I'll write this code if I ever change the file format again...)
2293   (let ((v (save-excursion
2294              (if (re-search-backward
2295                   "^;+[ \t]*file-version:[ \t]*\\([0-9]+\\)[ \t]*$" nil t)
2296                  (car (read-from-string
2297                        (buffer-substring
2298                         (match-beginning 1) (match-end 1))))))))
2299     (if (null v) ; current version, but no file-version: line. Bootstrap it.
2300         (let ((modp (buffer-modified-p)))
2301           ;; This should never happen (not any more, anyway...)
2302           (bbdb-debug (error "bbdb corrupted: no file-version line"))
2303           (setq v 2)
2304           (save-excursion
2305             (if (re-search-backward "^;" nil t)
2306                 (forward-line 1)
2307                 (goto-char 1))
2308             ;; remember, this goes before the begin-marker of the first
2309             ;; record in the database!
2310             (insert-before-markers
2311          (format ";; -*-coding: %s;-*-\n;;; file-version: %d\n"
2312              bbdb-file-coding-system bbdb-file-format)))
2313           (set-buffer-modified-p modp)))
2314     (cond ((< v bbdb-file-format)
2315            (if bbdb-file-format-migration
2316                ;; Sanity checking.
2317                (if (/= (car bbdb-file-format-migration) v)
2318                    (error
2319                     (format
2320                      "BBDB file format has changed on disk from %d to %d!"
2321                      (car bbdb-file-format-migration) v)))
2322                (setq bbdb-file-format-migration
2323                      (cons v (bbdb-migration-query v)))))
2324           ((> v bbdb-file-format)
2325            (error "BBDB version %s doesn't understand file format version %s."
2326                   bbdb-version v))
2327           (t (setq bbdb-file-format-migration (cons bbdb-file-format
2328                                                     bbdb-file-format)))))
2329   ;; A trap to catch a bug
2330   ;;(assert (not (null (car bbdb-file-format-migration))))
2331
2332   (bbdb-debug
2333    (or (eobp) (looking-at "[\[]")
2334        (error "no following bracket: bbdb corrupted"))
2335    (if (save-excursion
2336          (save-restriction
2337            (widen)
2338            (save-excursion (search-backward "\n[" nil t))))
2339        (error "bbdb corrupted: records before point")))
2340
2341   ;; Migrate only if we need to.  Change the .bbdb buffer only if it
2342   ;; is not to be saved in the newest version.
2343   (if (= (car bbdb-file-format-migration) bbdb-file-format)
2344       (parse-bbdb-frobnicate (parse-bbdb-read))
2345     (let ((newrecs (parse-bbdb-frobnicate (bbdb-migrate (parse-bbdb-read)))))
2346       (cond ((= (cdr bbdb-file-format-migration) bbdb-file-format)
2347              (bbdb-migrate-rewrite-all nil newrecs)
2348              (bbdb-migrate-update-file-version
2349               (car bbdb-file-format-migration)
2350               (cdr bbdb-file-format-migration))))
2351       newrecs)))
2352
2353 (defun parse-bbdb-read ()
2354   ;; narrow the buffer to skip over the rubbish before the first record.
2355   (narrow-to-region (point) (point-max))
2356   (let ((records nil))
2357     ;; insert parens so we can read the db in one fell swoop (down in C).
2358     (let ((buffer-read-only nil)
2359           (modp (buffer-modified-p))
2360           ;; Make sure those parens get cleaned up.
2361           ;; This code had better stay simple!
2362           (inhibit-quit t))
2363       (goto-char (point-min)) (insert "(\n")
2364       (goto-char (point-max)) (insert "\n)")
2365       (goto-char (point-min))
2366       (setq records (read (current-buffer)))
2367       (goto-char (point-min)) (delete-char 2)
2368       (goto-char (point-max)) (delete-char -2)
2369       (set-buffer-modified-p modp))
2370     records))
2371
2372 (defun parse-bbdb-frobnicate (records)
2373   ;; now we have to come up with a marker for each record.  Rather than
2374   ;; calling read for each record, we read them at once (already done) and
2375   ;; assume that the markers are at each newline.  If this isn't the case,
2376   ;; things can go *very* wrong.
2377   (goto-char (point-min))
2378   (while (looking-at "[ \t\n\f]*;")
2379     (forward-line 1))
2380   (widen)
2381   (bbdb-debug (message "Parsing BBDB... (frobnicating...)"))
2382   (setq bbdb-records records)
2383   (let* ((head (cons '() records))
2384          (rest head)
2385         record)
2386     (while (cdr rest)
2387       (setq record (car (cdr rest)))
2388       ;; yow, are we stack-driven yet??  Damn byte-compiler...
2389       ;; Make a cache.  Put it in the record.  Put a marker in the cache.
2390       ;; Add record to hash tables.
2391       (bbdb-cache-set-marker
2392        (bbdb-record-set-cache record (make-vector bbdb-cache-length nil))
2393        (point-marker))
2394       (forward-line 1)
2395
2396       ;; frob the label completion lists (and data completion when I,
2397       ;; uh, get around to it, maybe. this stuff should probably be
2398       ;; conditional, in case you're not running a 42GHz Pentium 69
2399       ;; with chrome tailpipes)
2400       (let ((ps (bbdb-record-phones record))
2401             (pl (bbdb-label-completion-list "phones"))
2402             (as (bbdb-record-addresses record))
2403             (al (bbdb-label-completion-list "addresses")))
2404         (while ps
2405           (let ((l (bbdb-phone-location (car ps))))
2406             (or (member l pl)
2407                 (setq bbdb-phones-label-list
2408                       (append (or bbdb-phones-label-list
2409                                   bbdb-default-label-list)
2410                               (list l))
2411                       pl bbdb-phones-label-list)))
2412           (setq ps (cdr ps)))
2413         ;; Yes, I cut and pasted.
2414         (while as
2415           (let ((l (bbdb-address-location (car as))))
2416             (or (member l al)
2417                 (setq bbdb-addresses-label-list
2418                       (append (or bbdb-addresses-label-list
2419                                   bbdb-default-label-list)
2420                               (list l))
2421                       al bbdb-addresses-label-list)))
2422           (setq as (cdr as))))
2423
2424       (if bbdb-no-duplicates-p
2425           ;; warn the user that there is a duplicate...
2426           (let* ((name (bbdb-record-name record))
2427                  (tmp  (and name (bbdb-gethash (downcase name)
2428                                                bbdb-hashtable))))
2429             (if tmp (message "Duplicate BBDB record encountered: %s" name))))
2430
2431         (bbdb-hash-record record)
2432         (setq rest (cdr rest))
2433
2434       (bbdb-debug
2435        (if (and (cdr rest) (not (looking-at "[\[]")))
2436            (error "bbdb corrupted: junk between records at %s" (point)))))
2437     ;; In case we removed some of the leading entries...
2438     (setq bbdb-records (cdr head)))
2439   ;; all done.
2440   (setq bbdb-end-marker (point-marker))
2441   (run-hooks 'bbdb-after-read-db-hook)
2442   (bbdb-debug (message "Parsing BBDB... (frobnicating...done)"))
2443   bbdb-records)
2444
2445 (defmacro bbdb-user-mail-names ()
2446   "Returns a regexp matching the address of the logged-in user."
2447   '(or bbdb-user-mail-names
2448     (setq bbdb-user-mail-names
2449      (concat "\\b" (regexp-quote (user-login-name)) "\\b"))))
2450
2451 (defun bbdb-write-file-hook-fn ()
2452   "This is just for `bbdb-write-file-hooks'.  Keep it there."
2453   ;; this is premature as the file isn't actually written yet; but it's just
2454   ;; for the benefit of the mode-line of the *BBDB* buffer, and there isn't
2455   ;; an after-write-file-hook, so it'll do.
2456   (save-restriction
2457     (widen)
2458     (goto-char (point-min))
2459
2460     ;; this always rewrites the coding cookie, which is a bit
2461     ;; wasteful, but safer than alternatives
2462     (if (looking-at ";; *-\\*-coding:")
2463         (delete-region (point) (progn (forward-line) (point))))
2464     (insert-before-markers (format ";; -*-coding: %s;-*-\n"
2465                                    bbdb-file-coding-system)))
2466   (setq bbdb-modified-p nil
2467         bbdb-changed-records nil
2468         buffer-file-coding-system bbdb-file-coding-system)
2469   (let ((buf (get-buffer bbdb-buffer-name)))
2470     (when buf
2471       (with-current-buffer buf
2472         (setq bbdb-showing-changed-ones nil)
2473         (set-buffer-modified-p nil))))
2474   (when (and bbdb-file-remote
2475              (or bbdb-file-remote-save-always
2476                  (y-or-n-p (format "Save the remote BBDB file %s too? "
2477                                    bbdb-file-remote))))
2478     ;; write the current buffer, which is `bbdb-file' (since this is called
2479     ;; from its `local-write-file-hooks'), into the `bbdb-file-remote'.
2480     (let ((coding-system-for-write bbdb-file-coding-system))
2481       (write-region (point-min) (point-max) bbdb-file-remote))))
2482
2483 (defun bbdb-delete-record-internal (record)
2484   (if (null (bbdb-record-marker record)) (error "bbdb: marker unpresent"))
2485   (bbdb-with-db-buffer
2486     (if (or bbdb-suppress-changed-records-recording
2487             (memq record bbdb-changed-records))
2488         nil
2489       (setq bbdb-changed-records (cons record bbdb-changed-records)))
2490     (let ((tail (memq record bbdb-records)))
2491       (if (null tail) (error "bbdb: unfound %s" record))
2492       (setq bbdb-records (delq record bbdb-records))
2493       (delete-region (bbdb-record-marker record)
2494                      (if (cdr tail)
2495                          (bbdb-record-marker (car (cdr tail)))
2496                          bbdb-end-marker))
2497       (let ((name    (bbdb-record-name    record))
2498             (lastname (bbdb-record-lastname    record))
2499             (company (bbdb-record-company record))
2500             (aka     (bbdb-record-aka     record))
2501             (nets    (bbdb-record-net     record)))
2502         (if (> (length name) 0)
2503             (bbdb-remhash (downcase name) record bbdb-hashtable))
2504         (if (> (length company) 0)
2505             (bbdb-remhash (downcase company) record bbdb-hashtable))
2506         (if (> (length lastname) 0)
2507             (bbdb-remhash (downcase lastname) record bbdb-hashtable))
2508         (while nets
2509           (bbdb-remhash (downcase (car nets)) record bbdb-hashtable)
2510           (setq nets (cdr nets)))
2511         (while aka
2512           (bbdb-remhash (downcase (car aka)) record bbdb-hashtable)
2513           (setq aka (cdr aka)))
2514         )
2515       (bbdb-record-set-sortkey record nil)
2516       (setq bbdb-modified-p t))))
2517
2518 (defun bbdb-insert-sorted (record records)
2519   "Inserts the RECORD into the list of RECORDS, in order.
2520 Assumes the list is already sorted.  Returns the new head."
2521   (bbdb-debug (if (memq record records)
2522                   (error "doubleplus ununique: - %s" record)))
2523   (let* ((rest (cons nil records))
2524          (top rest))
2525     (while (and (cdr rest)
2526                 (bbdb-record-lessp (nth 1 rest) record))
2527       (setq rest (cdr rest)))
2528     (setcdr rest (cons record (cdr rest)))
2529     (cdr top)))
2530
2531 (defun bbdb-insert-record-internal (record unmigrated)
2532   (if (null (bbdb-record-marker record))
2533       (bbdb-record-set-marker record (make-marker)))
2534   (bbdb-with-db-buffer
2535     (if (or bbdb-suppress-changed-records-recording
2536             (memq record bbdb-changed-records))
2537         nil
2538       (setq bbdb-changed-records (cons record bbdb-changed-records)))
2539     (let ((print-escape-newlines t))
2540       (bbdb-record-set-sortkey record nil) ; just in case...
2541       (setq bbdb-records
2542             (bbdb-insert-sorted record bbdb-records))
2543       (let ((next (car (cdr (memq record bbdb-records)))))
2544         (goto-char (if next
2545                        (bbdb-record-marker next)
2546                        bbdb-end-marker))
2547         ;; before printing the record, remove the cache \(we don't want that
2548         ;; written to the file.\)  Ater writing, put the cache back and update
2549         ;; the cache's marker.
2550         (let ((cache (bbdb-record-cache record))
2551               (point (point)))
2552           (bbdb-debug
2553            (if (= point (point-min))
2554                (error "doubleplus ungood: inserting at point-min (%s)" point))
2555            (if (and (/= point bbdb-end-marker)
2556                     (not (looking-at "[\[]")))
2557                (error "doubleplus ungood: not inserting before a record (%s)"
2558                       point)))
2559           (bbdb-record-set-cache record nil)
2560           (if unmigrated (bbdb-record-set-cache unmigrated nil))
2561           (insert-before-markers (prin1-to-string (or unmigrated record)) "\n")
2562           (set-marker (bbdb-cache-marker cache) point)
2563           (bbdb-record-set-cache record cache)
2564 ;;        (if (bbdb-record-name record)
2565 ;;            (bbdb-puthash (downcase (bbdb-record-name record)) record bbdb-hashtable))
2566 ;;        (let ((nets (bbdb-record-net record)))
2567 ;;          (while nets
2568 ;;            (bbdb-puthash (downcase (car nets)) record bbdb-hashtable)
2569 ;;            (setq nets (cdr nets))))
2570           ;; This is marginally slower because it rebuilds the namecache,
2571           ;; but it makes jbw's life easier. :-\)
2572           (bbdb-hash-record record))
2573         record))
2574     (setq bbdb-modified-p t)))
2575
2576 (defun bbdb-overwrite-record-internal (record unmigrated)
2577   (bbdb-with-db-buffer
2578     (if (or bbdb-suppress-changed-records-recording
2579             (memq record bbdb-changed-records))
2580         nil
2581       (setq bbdb-changed-records (cons record bbdb-changed-records)))
2582     (let ((print-escape-newlines t)
2583           (tail bbdb-records))
2584       ;; Look for record after RECORD in the database.  Use the
2585       ;; beginning marker of this record (or the marker for the end of
2586       ;; the database if no next record) to determine where to stop
2587       ;; deleting old copy of record
2588       (while (and tail (not (eq record (car tail))))
2589         (setq tail (cdr tail)))
2590       (if (null tail) (error "bbdb: unfound %s" record))
2591       (let ((cache (bbdb-record-cache record)))
2592
2593         (bbdb-debug
2594          (if (<= (bbdb-cache-marker cache) (point-min))
2595              (error "doubleplus ungood: cache marker is %s"
2596                     (bbdb-cache-marker cache)))
2597          (goto-char (bbdb-cache-marker cache))
2598          (if (and (/= (point) bbdb-end-marker)
2599                   (not (looking-at "[\[]")))
2600              (error "doubleplus ungood: not inserting before a record (%s)"
2601                     (point))))
2602
2603         (goto-char (bbdb-cache-marker cache))
2604         (bbdb-record-set-cache record nil)
2605         (if unmigrated (bbdb-record-set-cache unmigrated nil))
2606
2607         (insert (prin1-to-string (or unmigrated record)) "\n")
2608         (delete-region (point)
2609                        (if (cdr tail)
2610                            (bbdb-record-marker (car (cdr tail)))
2611                          bbdb-end-marker))
2612         (bbdb-record-set-cache record cache)
2613
2614         (bbdb-debug
2615          (if (<= (if (cdr tail)
2616                      (bbdb-record-marker (car (cdr tail)))
2617                    bbdb-end-marker)
2618                  (bbdb-record-marker record))
2619              (error "doubleplus ungood: overwrite unworks")))
2620
2621         (setq bbdb-modified-p t)
2622         record))))
2623
2624 (defvar inside-bbdb-change-record nil "hands off")
2625 (defvar inside-bbdb-notice-hook nil
2626   "Internal variable; hands off.
2627 Set to t by the BBDB when inside the `bbdb-notice-hook'.
2628
2629 Calls to the `bbdb-change-hook' are suppressed when this is non-nil.")
2630
2631 (defun bbdb-change-record (record need-to-sort)
2632   "Update the database after a change to the given record.  Second arg
2633 NEED-TO-SORT is whether the name has changed.  You still need to worry
2634 about updating the name hash-table."
2635   (if inside-bbdb-change-record
2636       record
2637     (let ((inside-bbdb-change-record t)
2638           unmigrated)
2639       (or inside-bbdb-notice-hook
2640           (bbdb-invoke-hook 'bbdb-change-hook record))
2641       (bbdb-debug (if (bbdb-record-deleted-p record)
2642                       (error "bbdb: changing deleted record")))
2643       (if (/= (cdr bbdb-file-format-migration) bbdb-file-format)
2644              (bbdb-unmigrate-record (setq unmigrated (bbdb-copy-thing record))))
2645       ;; Do the changing
2646       (if (memq record (bbdb-records)) ; checks file synchronization too.
2647           (if (not need-to-sort) ;; If we don't need to sort, overwrite it.
2648               (progn
2649                 (bbdb-overwrite-record-internal record unmigrated)
2650                 (bbdb-debug
2651                  (if (not (memq record (bbdb-records)))
2652                      (error "Overwrite in change doesn't work"))))
2653             ;; Since we do need to sort, delete then insert
2654             (bbdb-delete-record-internal record)
2655             (bbdb-debug
2656              (if (memq record (bbdb-records))
2657                  (error "Delete in need-sort change doesn't work")))
2658             (bbdb-insert-record-internal record unmigrated)
2659             (bbdb-debug
2660              (if (not (memq record (bbdb-records)))
2661                  (error "Insert in need-sort change doesn't work"))))
2662         ;; Record isn't in database so add it.
2663         (bbdb-insert-record-internal record unmigrated)
2664         (bbdb-debug (if (not (memq record (bbdb-records)))
2665                         (error "Insert in change doesn't work"))))
2666       (setq bbdb-modified-p t)
2667       (bbdb-invoke-hook 'bbdb-after-change-hook record)
2668       record)))
2669
2670 (defun bbdb-copy-thing (thing)
2671   "Copy a thing.  Handles vectors, strings, markers, numbers, conses,
2672 lists, symbols, and nil.  Raises an error if it finds something it
2673 doesn't know how to deal with."
2674   (cond ((vectorp thing)
2675          (let ((i 0)
2676                (newvec (make-vector (length thing) nil)))
2677            (while (< i (length thing))
2678              (aset newvec i (bbdb-copy-thing (aref thing i)))
2679              (setq i (1+ i)))
2680            newvec))
2681         ((stringp thing)
2682          (copy-sequence thing))
2683         ((markerp thing)
2684          (copy-marker thing))
2685         ((numberp thing)
2686          thing)
2687         ((consp thing)
2688          (cons (bbdb-copy-thing (car thing))
2689                (bbdb-copy-thing (cdr thing))))
2690         ((listp thing)
2691          (let ((i 0) newlist)
2692            (while (< i (length thing))
2693              (setq newlist (append newlist (list (bbdb-copy-thing
2694                                                   (nth i thing))))
2695                    i (1+ i)))
2696            newlist))
2697         ((symbolp thing)
2698          thing)
2699         ((eq nil thing)
2700          nil)
2701         (t
2702          (error "Don't know how to copy %s" (prin1-to-string thing)))))
2703
2704 (defmacro bbdb-propnames ()
2705   '(bbdb-with-db-buffer bbdb-propnames))
2706
2707 (defun bbdb-set-propnames (newval)
2708   (bbdb-with-db-buffer
2709     (setq bbdb-propnames newval)
2710     (widen)
2711     (goto-char (point-min))
2712     (and (not (eq (following-char) ?\[))
2713          (search-forward "\n[" nil 0))
2714     (if (re-search-backward "^[ \t]*;+[ \t]*user-fields:[ \t]*\(" nil t)
2715         (progn
2716           (goto-char (1- (match-end 0)))
2717           (delete-region (point) (progn (end-of-line) (point))))
2718       (and (re-search-backward "^[ \t]*;.*\n" nil t)
2719            (goto-char (match-end 0)))
2720       ;; remember, this goes before the begin-marker of the first
2721       ;; record in the database!
2722       (insert-before-markers ";;; user-fields: \n")
2723       (forward-char -1))
2724     (prin1 (mapcar (lambda (x) (intern (car x)))
2725                    bbdb-propnames)
2726            (current-buffer))
2727     bbdb-propnames))
2728
2729 \f
2730 ;;; BBDB mode
2731
2732 (defun bbdb-mode ()
2733   "Major mode for viewing and editing the Insidious Big Brother Database.
2734 Letters no longer insert themselves.  Numbers are prefix arguments.
2735 You can move around using the usual cursor motion commands.
2736 \\<bbdb-mode-map>
2737 \\[bbdb-add-or-remove-mail-alias]\t Add new mail alias to visible records or \
2738 remove it.
2739 \\[bbdb-edit-current-field]\t Edit the field on the current line.
2740 \\[bbdb-record-edit-notes]\t Edit the `notes' field for the current record.
2741 \\[bbdb-delete-current-field-or-record]\t Delete the field on the \
2742 current line.  If the current line is the\n\t first line of a record, then \
2743 delete the entire record.
2744 \\[bbdb-insert-new-field]\t Insert a new field into the current record.  \
2745 Note that this\n\t will let you add new fields of your own as well.
2746 \\[bbdb-transpose-fields]\t Swap the field on the current line with the \
2747 previous field.
2748 \\[bbdb-dial]\t Dial the current phone field.
2749 \\[bbdb-next-record], \\[bbdb-prev-record]\t Move to the next or the previous \
2750 displayed record, respectively.
2751 \\[bbdb-create]\t Create a new record.
2752 \\[bbdb-toggle-records-display-layout]\t Toggle whether the current record is displayed in a \
2753 one-line\n\t listing, or a full multi-line listing.
2754 \\[bbdb-apply-next-command-to-all-records]\\[bbdb-toggle-records-display-layout]\t Do that \
2755 for all displayed records.
2756 \\[bbdb-refile-record]\t Merge the contents of the current record with \
2757 some other, and then\n\t delete the current record.  See this command's \
2758 documentation.
2759 \\[bbdb-omit-record]\t Remove the current record from the display without \
2760 deleting it from\n\t the database.  This is often a useful thing to do \
2761 before using one\n\t of the `*' commands.
2762 \\[bbdb]\t Search for records in the database (on all fields).
2763 \\[bbdb-net]\t Search for records by net address.
2764 \\[bbdb-company]\t Search for records by company.
2765 \\[bbdb-notes]\t Search for records by note.
2766 \\[bbdb-name]\t Search for records by name.
2767 \\[bbdb-changed]\t Display records that have changed since the database \
2768 was saved.
2769 \\[bbdb-send-mail]\t Compose mail to the person represented by the \
2770 current record.
2771 \\[bbdb-apply-next-command-to-all-records]\\[bbdb-send-mail]\t Compose mail \
2772 to everyone whose record is displayed.
2773 \\[bbdb-finger]\t Finger the net address of the current record.
2774 \\[bbdb-ftp]\t FTP to the curent records's `ftp' field.
2775 \\[bbdb-apply-next-command-to-all-records]\\[bbdb-finger]\t Finger the \
2776 net address of all displayed records.
2777 \\[bbdb-save-db]\t Save the BBDB file to disk.
2778 \\[bbdb-print]\t Create a TeX file containing a pretty-printed version \
2779 of all the\n\t records in the database.
2780 \\[bbdb-apply-next-command-to-all-records]\\[bbdb-print]\t Do that for the \
2781 displayed records only.
2782 \\[other-window]\t Move to another window.
2783 \\[bbdb-info]\t Read the Info documentation for BBDB.
2784 \\[bbdb-help]\t Display a one line command summary in the echo area.
2785 \\[bbdb-www]\t Visit Web sites listed in the `www' field(s) of the current \
2786 record.
2787 \\[bbdb-whois]\t run whois on the current record.
2788
2789 For address completion using the names and net addresses in the database:
2790 \t in Sendmail mode, type \\<mail-mode-map>\\[bbdb-complete-name].
2791 \t in Message mode, type \\<message-mode-map>\\[bbdb-complete-name].
2792
2793 Variables of note:
2794 \t bbdb-always-add-addresses
2795 \t bbdb-auto-revert-p
2796 \t bbdb-canonicalize-redundant-nets-p
2797 \t bbdb-case-fold-search
2798 \t bbdb-completion-type
2799 \t bbdb-default-area-code
2800 \t bbdb-default-domain
2801 \t bbdb-electric-p
2802 \t bbdb-display-layout
2803 \t bbdb-file
2804 \t bbdb-message-caching-enabled
2805 \t bbdb-new-nets-always-primary
2806 \t bbdb-north-american-phone-numbers-p
2807 \t bbdb-notice-auto-save-file
2808 \t bbdb-offer-save
2809 \t bbdb-pop-up-display-layout
2810 \t bbdb-pop-up-target-lines
2811 \t bbdb-quiet-about-name-mismatches
2812 \t bbdb-readonly-p
2813 \t bbdb-use-alternate-names
2814 \t bbdb-use-pop-up
2815 \t bbdb-user-mail-names
2816 \t bbdb/mail-auto-create-p
2817 \t bbdb/news-auto-create-p
2818
2819 There are numerous hooks.  M-x apropos ^bbdb.*hook RET
2820
2821 The keybindings, more precisely:
2822 \\{bbdb-mode-map}"
2823   (setq major-mode 'bbdb-mode)
2824   (setq mode-name "BBDB")
2825   (use-local-map bbdb-mode-map)
2826   (run-hooks 'bbdb-mode-hook))
2827
2828 ;;; these should be in bbdb-com.el but they're so simple, why load it all.
2829
2830 (defun bbdb-next-record (p)
2831   "Move the cursor to the first line of the next BBDB record."
2832   (interactive "p")
2833   (if (< p 0)
2834       (bbdb-prev-record (- p))
2835     (forward-char)
2836     (while (> p 0)
2837       (or (re-search-forward "^[^ \t\n]" nil t)
2838           (progn (beginning-of-line)
2839                  (error "no next record")))
2840       (setq p (1- p)))
2841     (beginning-of-line)))
2842
2843 (defun bbdb-prev-record (p)
2844   "Move the cursor to the first line of the previous BBDB record."
2845   (interactive "p")
2846   (if (< p 0)
2847       (bbdb-next-record (- p))
2848     (while (> p 0)
2849       (or (re-search-backward "^[^ \t\n]" nil t)
2850           (error "no previous record"))
2851       (setq p (1- p)))))
2852
2853
2854 (defun bbdb-maybe-update-display (bbdb-record)
2855   (save-excursion
2856     (save-window-excursion
2857       (let ((w (get-buffer-window bbdb-buffer-name))
2858             (b (current-buffer)))
2859         (if w
2860             (unwind-protect
2861                 (progn (set-buffer bbdb-buffer-name)
2862                        (save-restriction
2863                          (if (assq bbdb-record bbdb-records)
2864                              (bbdb-redisplay-records))))
2865               (set-buffer b)))))))
2866
2867 (defcustom bbdb-notes-default-separator ", "
2868   "*The default separator inserted by `bbdb-annotate-notes'.
2869 This is used for notes which do not have `field-separator' property set.
2870 E.g., if you want URLs to be separated by newlines, you can put
2871   (put 'www 'field-separator \"\\n\")
2872 into your .emacs."
2873   :group 'bbdb-noticing-records
2874   :type 'string)
2875
2876 (defun bbdb-annotate-notes (bbdb-record annotation &optional fieldname replace)
2877   "Add an annotation to a record.
2878 Adds (or replaces, when the fourth argument REPLACE is non-nil)
2879 an ANNOTATION to the note FIELDNAME in BBDB-RECORD.
2880 Called by `bbdb-auto-notes-hook'."
2881   (or bbdb-record (error "unperson"))
2882   (setq annotation (bbdb-string-trim annotation))
2883   (if (memq fieldname '(name address addresses phone phones net aka AKA))
2884       (error "bbdb: cannot annotate the %s field this way" fieldname))
2885   (or fieldname (setq fieldname 'notes))
2886   (or (memq fieldname '(notes company))
2887       (assoc (symbol-name fieldname) (bbdb-propnames))
2888       (bbdb-set-propnames (append (bbdb-propnames)
2889                                   (list (list (symbol-name fieldname))))))
2890   (let ((notes (bbdb-string-trim
2891                 (or (bbdb-record-getprop bbdb-record fieldname) ""))))
2892     (unless (or (string= "" annotation)
2893                 (string-match (regexp-quote annotation) notes))
2894       (bbdb-record-putprop bbdb-record fieldname
2895                            (if (or replace (string= notes ""))
2896                                annotation
2897                                (concat notes
2898                                        (or (get fieldname 'field-separator)
2899                                            bbdb-notes-default-separator)
2900                                        annotation)))
2901       (bbdb-maybe-update-display bbdb-record))))
2902
2903 (defun bbdb-offer-save ()
2904   "Offer to save the Insidious Big Brother Database if it is modified."
2905   (if bbdb-offer-save
2906       (bbdb-save-db (eq bbdb-offer-save t))))
2907
2908 (defcustom bbdb-save-db-timeout nil
2909   "*If non-nil, then when `bbdb-save-db' is asking you whether to save the db,
2910 it will time out to `yes' after this many seconds.  This only works if the
2911 function `y-or-n-p-with-timeout' is defined."
2912   :group 'bbdb-save
2913   :type '(choice (const :tag "Don't time out" nil)
2914                  (integer :tag "Time out after this many seconds" 5)))
2915
2916 (defun bbdb-save-db (&optional prompt-first mention-if-not-saved)
2917   "Save the DB if it is modified."
2918   (interactive (list nil t))
2919   (bbdb-with-db-buffer
2920     (if (and (buffer-modified-p)
2921              (or (null prompt-first)
2922                  (if bbdb-readonly-p
2923                      (bbdb-y-or-n-p
2924                       "Save the BBDB, even though it's supposedly read-only? ")
2925                    (if (and bbdb-save-db-timeout
2926                             (fboundp 'y-or-n-p-with-timeout))
2927                        (y-or-n-p-with-timeout
2928                         "Save the BBDB now? " bbdb-save-db-timeout t)
2929                      (bbdb-y-or-n-p "Save the BBDB now? ")))))
2930         (save-buffer)
2931       (if mention-if-not-saved (message "BBDB not saved")))))
2932
2933 \f
2934 ;;; mail and news interface
2935
2936 (defun bbdb-clean-username (string)
2937   "Strips garbage from the user full name string."
2938   ;; This function is called a lot, and should be fast.  But I'm loathe to
2939   ;; remove any of the functionality in it.
2940   (if (string-match "[@%!]" string)  ; ain't no user name!  It's an address!
2941       (bbdb-string-trim string)
2942    (let ((case-fold-search t))
2943      ;; Take off leading and trailing non-alpha chars \(quotes, parens,
2944      ;; digits, etc) and things which look like phone extensions \(like
2945      ;; "x1234" and "ext. 1234". \)
2946      ;; This doesn't work all the time because some of our friends in
2947      ;; northern europe have brackets in their names...
2948      (if (string-match (if bbdb-have-re-char-classes
2949                "\\`[^[:alpha:]]+"
2950              "\\`[^a-z]+")
2951                string)
2952          (setq string (substring string (match-end 0))))
2953      (while (string-match
2954              "\\(\\W+\\([Xx]\\|[Ee]xt\\.?\\)\\W*[-0-9]+\\|[^a-z]+\\)\\'"
2955              string)
2956        (setq string (substring string 0 (match-beginning 0))))
2957      ;; replace tabs, multiple spaces, dots, and underscores with a single
2958      ;; space, but don't replace ". " with " " because that could be an
2959      ;; initial.
2960      (while (string-match "\\(\t\\|  +\\|\\(\\.\\)[^ \t_]\\|_+\\)" string)
2961        (setq string (concat (substring string 0
2962                                        (or (match-beginning 2)
2963                                            (match-beginning 1)))
2964                             " "
2965                             (substring string (or (match-end 2)
2966                                                   (match-end 1))))))
2967      ;; If the string contains trailing parenthesized comments, nuke 'em.
2968      (if (string-match "[^ \t]\\([ \t]*\\((\\| -\\| #\\)\\)" string)
2969          (progn
2970            (setq string (substring string 0 (match-beginning 1)))
2971            ;; lose rubbish this may have exposed.
2972            (while
2973                (string-match
2974                 "\\(\\W+\\([Xx]\\|[Ee]xt\\.?\\)\\W*[-0-9]+\\|[^a-z]+\\)\\'"
2975                 string)
2976                (setq string (substring string 0 (match-beginning 0))))))
2977      string)))
2978
2979 ;;; message-caching, to speed up the the mail interfaces
2980
2981 (defvar bbdb-buffers-with-message-caches '()
2982   "A list of all the buffers which have stuff on their `bbdb-message-cache'
2983 local variable.  When we re-parse the `bbdb-file', we need to flush all of
2984 these caches.")
2985
2986 (defun notice-buffer-with-cache (buffer)
2987   (or (memq buffer bbdb-buffers-with-message-caches)
2988       (progn
2989         ;; First remove any deleted buffers which may have accumulated.
2990         ;; This happens only when a buffer is added to the list, so it
2991         ;; ought not happen that frequently (each time you read mail, say.)
2992         (let ((rest bbdb-buffers-with-message-caches))
2993           (while rest
2994             (if (null (buffer-name (car rest)))
2995                 (setq bbdb-buffers-with-message-caches
2996                       (delq (car rest) bbdb-buffers-with-message-caches)))
2997             (setq rest (cdr rest))))
2998         ;; now add this buffer.
2999         (setq bbdb-buffers-with-message-caches
3000               (cons buffer bbdb-buffers-with-message-caches)))))
3001
3002 (defvar bbdb-message-cache nil
3003   "alist of (MESSAGE-KEY BBDB-RECORDS) cached in order to avoid updating
3004 messages each time they are visited.  This is used by all MUAs, while the
3005 MESSAGE-KEY is specific to the MUA and the cache is local for each MUA or MUA
3006 folder.")
3007
3008 (make-variable-buffer-local 'bbdb-message-cache)
3009
3010 (defun bbdb-message-cache-lookup (message-key)
3011   "Return cached BBDB records for MESSAGE-KEY.
3012 If not present or when the records have been modified return nil."
3013   (bbdb-records)
3014   (if bbdb-message-caching-enabled
3015       (let ((records (assq message-key bbdb-message-cache))
3016             (invalid nil))
3017         (when records
3018           (setq records (cdr records))
3019           (bbdb-mapc (lambda (record)
3020                   (if (bbdb-record-deleted-p record)
3021                       (setq invalid t)))
3022                 records))
3023         (if invalid nil records))))
3024
3025 (defun bbdb-encache-message (message-key bbdb-records)
3026   "Cache the BBDB-RECORDS for a message identified by MESSAGE-KEY and
3027 return them."
3028   (and bbdb-message-caching-enabled
3029        (car bbdb-records)
3030        (add-to-list 'bbdb-message-cache (cons message-key bbdb-records))
3031        (notice-buffer-with-cache (current-buffer)))
3032   bbdb-records)
3033
3034 (defun bbdb-decache-message (message-key)
3035   "Remove an element form the cache."
3036   (and bbdb-message-caching-enabled
3037        (delq (assoc message-key bbdb-message-cache) bbdb-message-cache)))
3038
3039 (defun bbdb-flush-all-caches ()
3040   (bbdb-debug
3041     (and bbdb-buffers-with-message-caches
3042          (message "Flushing BBDB caches")))
3043   (save-excursion
3044     (while bbdb-buffers-with-message-caches
3045       (if (buffer-name (car bbdb-buffers-with-message-caches))
3046           (progn
3047             (set-buffer (car bbdb-buffers-with-message-caches))
3048             (setq bbdb-message-cache nil)))
3049       (setq bbdb-buffers-with-message-caches
3050             (cdr bbdb-buffers-with-message-caches)))))
3051
3052
3053 (defconst bbdb-name-gubbish
3054   (concat "[-,. \t/\\]+\\("
3055           "[JjSs]r\\.?"
3056           "\\|V?\\(I\\.?\\)+V?"
3057           (concat "\\|"
3058                   (regexp-opt bbdb-lastname-prefixes))
3059           "\\)\\W*\\'"))
3060
3061 (defun bbdb-divide-name (string)
3062   "divide the string into a first name and a last name, cleverly."
3063   ;; ## This shouldn't be here.
3064   (if (string-match "\\W+\\([Xx]\\|[Ee]xt\\.?\\)\\W*[-0-9]+\\'" string)
3065       (setq string (substring string 0 (match-beginning 0))))
3066   (let* ((case-fold-search nil)
3067          (str string)
3068          (gubbish (string-match bbdb-name-gubbish string)))
3069     (if gubbish
3070         (setq gubbish (substring str gubbish)
3071               str (substring string 0 (match-beginning 0))))
3072     (if (string-match
3073      (concat " +\\("
3074          ;; start recognize some prefixes to lastnames
3075          (if bbdb-lastname-prefixes
3076              (concat "\\("
3077                  (regexp-opt bbdb-lastname-prefixes t)
3078                  "[ ]+\\)?"))
3079          ;; end recognize some prefixes to lastnames
3080          "\\([^ ]+ *- *\\)?[^ ]+\\)\\'") str)
3081         (list (substring str 0 (match-beginning 0))
3082               (concat
3083                (substring str (match-beginning 1))
3084                (or gubbish "")))
3085       (list string ""))))
3086
3087 (defun bbdb-check-alternate-name (possible-name record)
3088   (let (aka)
3089     (if (setq aka (bbdb-record-aka record))
3090         (let ((down-name (downcase possible-name))
3091               match)
3092           (while aka
3093             (if (equal down-name (downcase (car aka)))
3094                 (setq match (car aka)
3095                       aka nil)
3096                 (setq aka (cdr aka))))
3097           match))))
3098
3099
3100 (defun bbdb-canonicalize-address (net)
3101   ;; call the bbdb-canonicalize-net-hook repeatedly until it returns a
3102   ;; value eq to the value passed in.  This implies that it can't
3103   ;; destructively modify the string.
3104
3105   ;; Hysterical Raisins: This is a function, not a hook. In order to
3106   ;; make this hook a hook, we'll quietly convert a single function
3107   ;; into a hook list.  We should really warn the user that we're
3108   ;; doing this, and advise them to update their configuration
3109   ;; accordingly. For the release, maybe.
3110   (if (functionp bbdb-canonicalize-net-hook)
3111       (setq bbdb-canonicalize-net-hook (list bbdb-canonicalize-net-hook)))
3112
3113   ;; Now, do the hook run. Note, if you mess up, it's possible that
3114   ;; BBDB will get stuck here oscillating between various definitions
3115   ;; of the canonical address.
3116   (while (not (equal net (setq net (run-hook-with-args
3117                                     'bbdb-canonicalize-net-hook net)))))
3118
3119   net)
3120
3121 ;; Mostly written by Rod Whitby.
3122 (defun bbdb-net-redundant-p (net old-nets)
3123   "Returns non-nil if NET represents a sub-domain of one of the OLD-NETS.
3124 The returned value is the address which makes this one redundant.
3125 For example, \"foo@bar.baz.com\" is redundant w.r.t. \"foo@baz.com\",
3126 and \"foo@quux.bar.baz.com\" is redundant w.r.t. \"foo@bar.baz.com\"."
3127   (let ((redundant-addr nil))
3128     (while (and (not redundant-addr) old-nets)
3129       ;; Calculate a host-regexp for each address in OLD-NETS
3130       (let* ((old (car old-nets))
3131              (host-index (string-match "@" old))
3132              (name (and host-index (substring old 0 host-index)))
3133              (host (and host-index (substring old (1+ host-index))))
3134              ;; host-regexp is "^<name>@.*\.<host>$"
3135              (host-regexp (and name host
3136                                (concat "\\`" (regexp-quote name)
3137                                        "@.*\\." (regexp-quote host)
3138                                        "\\'"))))
3139         ;; If NET matches host-regexp, then it is redundant
3140         (if (and host-regexp net
3141                  (string-match host-regexp net))
3142             (setq redundant-addr old)))
3143       (setq old-nets (cdr old-nets)))
3144     redundant-addr))
3145
3146 \f
3147 (defun bbdb-annotate-message-sender (from &optional loudly create-p
3148                                           prompt-to-create-p)
3149   "Fills the record corresponding to the sender with as much info as possible.
3150 A record may be created by this; a record or nil is returned.
3151 If `bbdb-readonly-p' is true, then a record will never be created.
3152 If CREATE-P is true, then a record may be created, otherwise it won't.
3153 If PROMPT-TO-CREATE-P is true, then the user will be asked for confirmation
3154 before the record is created, otherwise it is created without confirmation
3155 \(assuming that CREATE-P is true\).  "
3156   (let* ((data (if (consp from)
3157                    from ; if from is a cons, it's pre-parsed (hack hack)
3158                  (mail-extract-address-components from)))
3159          (name (car data))
3160          (net (car (cdr data))))
3161     (if (equal name net) (setq name nil))
3162     (bbdb-debug
3163      (if (equal name "") (error "mail-extr returned \"\" as name"))
3164      (if (equal net "") (error "mail-extr returned \"\" as net")))
3165
3166     (if (and net bbdb-canonicalize-net-hook)
3167         (setq net (bbdb-canonicalize-address net)))
3168
3169     (let ((change-p nil)
3170           (record (or (bbdb-search-simple nil net)
3171                       (bbdb-search-simple name nil)))
3172           (created-p nil)
3173           (fname name)
3174           (lname nil)
3175           old-name
3176           bogon-mode)
3177       (and record (setq old-name (bbdb-record-name record)))
3178
3179       ;; This is to prevent having losers like "John <blat@foop>" match
3180       ;; against existing records like "Someone Else <john>".
3181       ;;
3182       ;; The solution implemented here is to never create or show records
3183       ;; corresponding to a person who has a real-name which is the same
3184       ;; as the network-address of someone in the db already.  This is not
3185       ;; a good solution.
3186       (let (down-name old-net)
3187         (if (and record name
3188                  (not (equal (setq down-name (downcase name))
3189                              (and old-name (downcase old-name)))))
3190             (progn
3191               (setq old-net (bbdb-record-net record))
3192               (while old-net
3193                 (if (equal down-name (downcase (car old-net)))
3194                     (progn
3195                       (setq bogon-mode t
3196                             old-net nil)
3197                       (message
3198                        "Ignoring bogon %s's name \"%s\" to avoid name-clash with \"%s\""
3199                        net name old-name)
3200                       (sit-for 2))
3201                   (setq old-net (cdr old-net)))))))
3202
3203       (if (or record
3204               bbdb-readonly-p
3205               (not create-p)
3206               (not (or name net))
3207               bogon-mode)
3208           ;; no further action required
3209           nil
3210         ;; otherwise, the db is writable, and we may create a record.
3211         ;; first try to get a reasonable default name if not given
3212         ;; often I get things like <firstname>.<surname>@ ...
3213         (if (or (null name) (and (stringp name) (string= "" name)))
3214             (if (string-match "^[^@]+" net)
3215                 (setq name (bbdb-clean-username (match-string 0 net)))))
3216         (setq record (if (or (null prompt-to-create-p)
3217                              (eq create-p t) ;; don't skip if it's 'prompt!
3218                              (if (functionp prompt-to-create-p)
3219                                  (bbdb-invoke-hook-for-value
3220                                   prompt-to-create-p)
3221                                (bbdb-y-or-n-p
3222                                 (format "%s is not in the db.  Add? "
3223                                         (or name net)))))
3224                          (make-vector bbdb-record-length nil))
3225               created-p (not (null record)))
3226         (if record
3227             (bbdb-record-set-cache record (make-vector bbdb-cache-length nil)))
3228         )
3229       (if (or bogon-mode (null record))
3230           nil
3231         (bbdb-debug (if (bbdb-record-deleted-p record)
3232                         (error "nasty nasty deleted record nasty.")))
3233         (if (and name
3234                  (not (equal (and name (downcase name))
3235                              (and old-name (downcase old-name))))
3236                  (or (null bbdb-use-alternate-names)
3237                      (not (bbdb-check-alternate-name name record)))
3238                  (let ((fullname (bbdb-divide-name name))
3239                        tmp)
3240                    (setq fname (car fullname)
3241                          lname (nth 1 fullname))
3242                    (not (and (equal (downcase fname)
3243                                     (and (setq tmp
3244                                                (bbdb-record-firstname record))
3245                                          (downcase tmp)))
3246                              (equal (downcase lname)
3247                                     (and (setq tmp
3248                                                (bbdb-record-lastname record))
3249                                          (downcase tmp)))))))
3250
3251             ;; have a message-name, not the same as old name.
3252             (cond (bbdb-readonly-p nil);; skip if readonly
3253
3254                   ;; ignore name mismatches?
3255                   ;; NB 'quiet' means 'don't ask', not 'don't mention'
3256                   ((and bbdb-quiet-about-name-mismatches old-name)
3257                    (let ((sit-for-secs
3258                           (if (numberp bbdb-quiet-about-name-mismatches)
3259                               bbdb-quiet-about-name-mismatches
3260                             2)))
3261                      (if (or bbdb-silent-running (= 0 sit-for-secs)) nil
3262                        (message "name mismatch: \"%s\" changed to \"%s\""
3263                                 (bbdb-record-name record) name)
3264                        (sit-for sit-for-secs))))
3265                   ((or created-p
3266                        (if bbdb-silent-running t
3267                          (if (null old-name)
3268                              (bbdb-y-or-n-p
3269                               (format "Assign name \"%s\" to address \"%s\"? "
3270                                       name (car (bbdb-record-net record))))
3271                            (bbdb-y-or-n-p
3272                             (format "Change name \"%s\" to \"%s\"? "
3273                                     old-name name)))))
3274                    (setq change-p 'sort)
3275
3276                    ;; Keep old name?
3277                    (and old-name bbdb-use-alternate-names
3278                         (not (member old-name (bbdb-record-aka record)))
3279                         ;; Silent mode: just add it.
3280                         (if bbdb-silent-running
3281                             (bbdb-record-set-aka record
3282                                                  (cons old-name
3283                                                        (bbdb-record-aka
3284                                                         record)))
3285                           ;; prompt user otherwise.
3286                           (if (bbdb-y-or-n-p
3287                                (format "Keep name \"%s\" as an AKA? "
3288                                        old-name))
3289                               (bbdb-record-set-aka record
3290                                                    (cons old-name
3291                                                          (bbdb-record-aka
3292                                                           record)))
3293                             (bbdb-remhash (downcase old-name) record))))
3294
3295                    (bbdb-record-set-namecache record nil)
3296                    (bbdb-record-set-firstname record fname)
3297                    (bbdb-record-set-lastname record lname)
3298                    (bbdb-debug (or fname lname
3299                                    (error "bbdb: should have a name by now")))
3300                    (bbdb-puthash (downcase (bbdb-record-name record)) record))
3301
3302                   ;; not quiet about mismatches
3303                   ((and old-name bbdb-use-alternate-names
3304                         ;; dedupe
3305                         (not (member old-name (bbdb-record-aka record)))
3306                         (if (not bbdb-silent-running)
3307                             (bbdb-y-or-n-p
3308                              (format "Make \"%s\" an alternate for \"%s\"? "
3309                                      name old-name))))
3310                    (setq change-p 'sort)
3311                    (bbdb-record-set-aka
3312                     record (cons name (bbdb-record-aka record)))
3313                    (bbdb-puthash (downcase name) record))))
3314
3315         ;; It's kind of a kludge that the "redundancy" concept is built in.
3316         ;; Maybe I should just add a new hook here...  The problem is that the
3317         ;; canonicalize-net-hook is run before database lookup, and thus can't
3318         ;; refer to the database to determine whether a net is redundant.
3319         (if bbdb-canonicalize-redundant-nets-p
3320             (setq net (or (bbdb-net-redundant-p net (bbdb-record-net record))
3321                           net)))
3322
3323         (if (and net (not bbdb-readonly-p))
3324             (if (null (bbdb-record-net record))
3325                 ;; names are always a sure match, so don't bother prompting
3326                 ;; here.
3327                 (progn (bbdb-record-set-net record (list net))
3328                        (bbdb-puthash (downcase net) record) ; important!
3329                        (or change-p (setq change-p t)))
3330               ;; new address; ask before adding.
3331               (if (let ((rest-net (bbdb-record-net record))
3332                         (new (downcase net))
3333                         (match nil))
3334                     (while (and rest-net (null match))
3335                       (setq match (string= new (downcase (car rest-net)))
3336                             rest-net (cdr rest-net)))
3337                     match)
3338                   nil
3339                 (if (let ((bbdb-always-add-addresses
3340                            bbdb-always-add-addresses))
3341                       (if (functionp bbdb-always-add-addresses)
3342                           (setq bbdb-always-add-addresses
3343                                 (funcall bbdb-always-add-addresses)))
3344                       (cond
3345                        ;; add it automatically
3346                        ((eq bbdb-always-add-addresses t)
3347                         t)
3348                        ;; do not add it
3349                        ((null bbdb-always-add-addresses)
3350                         nil)
3351                        ;; ask the user if it should be added
3352                        (t
3353                         (and
3354                          (not (equal net "???"))
3355                          (let ((the-first-bit
3356                                 (format "Add address \"%s\" to \"" net))
3357                                ;; this groveling is to prevent the "(y or n)"
3358                                ;; from falling off the right edge of the
3359                                ;; screen.
3360                                (the-next-bit (mapconcat 'identity
3361                                                         (bbdb-record-net
3362                                                          record)
3363                                                         ", "))
3364                                (w (window-width (minibuffer-window))))
3365                            (if (> (+ (length the-first-bit)
3366                                      (length the-next-bit) 15) w)
3367                                (setq the-next-bit
3368                                      (concat
3369                                       (substring
3370                                        the-next-bit
3371                                        0 (max 0 (- w (length the-first-bit)
3372                                                    20)))
3373                                       "...")))
3374                            (bbdb-display-records (list record))
3375                            (if (bbdb-y-or-n-p (concat the-first-bit
3376                                                       the-next-bit
3377                                                       "\"? "))
3378                                ;; then add the new net
3379                                t
3380                              ;; else add a new record with the same name
3381                              (if (and create-p
3382                                       (or (null prompt-to-create-p)
3383                                           (if (functionp prompt-to-create-p)
3384                                               (bbdb-invoke-hook-for-value
3385                                                prompt-to-create-p)
3386                                             (bbdb-y-or-n-p
3387                                              (format
3388                                               "Create a new record for %s? "
3389                                               (bbdb-record-name record))))))
3390                                  (setq record
3391                                        (bbdb-create-internal name nil net
3392                                                              nil nil nil)))
3393                              nil))))))
3394                     ;; then modify an existing record
3395                     (let ((front-p (cond ((null bbdb-new-nets-always-primary)
3396                                           (bbdb-y-or-n-p
3397                                            (format
3398                                             "Make \"%s\" the primary address? "
3399                                             net)))
3400                                          ((eq bbdb-new-nets-always-primary t)
3401                                           t)
3402                                          (t nil))))
3403                       (bbdb-record-set-net record
3404                                            (if front-p
3405                                                (cons net (bbdb-record-net
3406                                                           record))
3407                                              (nconc (bbdb-record-net record)
3408                                                     (list net))))
3409                       (bbdb-puthash (downcase net) record) ; important!
3410                       (or change-p (setq change-p t)))))))
3411
3412         (bbdb-debug
3413          (if (and change-p bbdb-readonly-p)
3414              (error
3415               "doubleplus ungood: how did we change anything in readonly mode?"
3416               )))
3417         (if (and loudly change-p (not bbdb-silent-running))
3418             (if (eq change-p 'sort)
3419                 (message "noticed \"%s\"" (bbdb-record-name record))
3420               (if (bbdb-record-name record)
3421                   (message "noticed %s's address \"%s\""
3422                            (bbdb-record-name record) net)
3423                 (message "noticed naked address \"%s\"" net))))
3424
3425         (if created-p
3426             (bbdb-invoke-hook 'bbdb-create-hook record))
3427
3428         (if change-p
3429             (bbdb-change-record record (eq change-p 'sort)))
3430
3431         ;; only invoke bbdb-notice-hook if we actually noticed something
3432         (if record
3433             (let ((inside-bbdb-notice-hook t))
3434               (bbdb-invoke-hook 'bbdb-notice-hook record)))
3435
3436         record))))
3437
3438 \f
3439 ;;; window configuration hackery
3440 (defun bbdb-multiple-buffers-default ()
3441   "Default function for guessing a better name for new *BBDB* buffers."
3442   (cond ((memq major-mode '(vm-mode vm-summary-mode
3443                                     vm-presentation-mode
3444                                     vm-virtual-mode))
3445          (vm-select-folder-buffer)
3446          (buffer-name))
3447         ((memq major-mode '(gnus-summary-mode gnus-group-mode))
3448          (set-buffer gnus-article-buffer)
3449          (buffer-name))
3450         ((memq major-mode '(mail-mode vm-mail-mode message-mode))
3451          "message composition")))
3452
3453 (defun bbdb-multiple-buffers-set-name (&optional buffer-list new-name)
3454   (setq new-name (or new-name
3455                      (concat " *BBDB " (funcall bbdb-multiple-buffers) "*"))
3456         buffer-list (append (list (current-buffer)
3457                                   (get-buffer-create new-name))
3458                             buffer-list))
3459
3460   (save-excursion
3461     (while buffer-list
3462       (set-buffer (car buffer-list))
3463       (make-local-variable 'bbdb-buffer-name)
3464       (setq bbdb-buffer-name new-name)
3465       (setq buffer-list (cdr buffer-list)))))
3466
3467 (defun bbdb-pop-up-bbdb-buffer (&optional horiz-predicate)
3468   "Find the largest window on the screen, and split it, displaying the
3469 *BBDB* buffer in the bottom 'bbdb-pop-up-target-lines' lines (unless
3470 the *BBDB* buffer is already visible, in which case do nothing.)
3471
3472 If 'bbdb-use-pop-up' is the symbol 'horiz, and the first window
3473 matching HORIZ-PREDICATE is sufficiently wide (> 100 columns) then
3474 the window will be split vertically rather than horizontally.
3475
3476 If `bbdb-multiple-buffers' is set we create a new BBDB buffer when not
3477 already within one.  The new buffer-name starts with a space, i.e. it does
3478 not clutter the buffer-list."
3479
3480   (let ((b (current-buffer))
3481         new-bbdb-buffer-name)
3482
3483     ;; create new BBDB buffer if multiple buffers are desired.
3484     (when (and bbdb-multiple-buffers (not (eq major-mode 'bbdb-mode)))
3485       (bbdb-multiple-buffers-set-name (list b)))
3486     (setq new-bbdb-buffer-name bbdb-buffer-name)
3487
3488     ;; now get the pop-up
3489     (if (get-buffer-window new-bbdb-buffer-name)
3490         nil
3491       (if (and (eq bbdb-use-pop-up 'horiz)
3492                horiz-predicate
3493                (bbdb-pop-up-bbdb-buffer-horizontally horiz-predicate))
3494           nil
3495         (let* ((first-window (selected-window))
3496                (tallest-window first-window)
3497                (window first-window))
3498           ;; find the tallest window...
3499           (while (not (eq (setq window (previous-window window)) first-window))
3500             (if (> (window-height window) (window-height tallest-window))
3501                 (setq tallest-window window)))
3502           ;; select it and split it...
3503           (select-window tallest-window)
3504           (let ((size (min
3505                        (- (window-height tallest-window)
3506                           window-min-height 1)
3507                        (- (window-height tallest-window)
3508                           (max window-min-height
3509                                (1+ bbdb-pop-up-target-lines))))))
3510             (split-window tallest-window
3511                           (if (> size 0) size window-min-height)))
3512           (if (memq major-mode
3513                     '(gnus-Group-mode gnus-Subject-mode gnus-Article-mode))
3514               (goto-char (point-min)))  ; make gnus happy...
3515           ;; goto the bottom of the two...
3516           (select-window (next-window))
3517           ;; make it display *BBDB*...
3518           (let ((pop-up-windows nil))
3519             (switch-to-buffer (get-buffer-create new-bbdb-buffer-name)))
3520           ;; select the original window we were in...
3521           (select-window first-window)))
3522       ;; and make sure the current buffer is correct as well.
3523       (set-buffer b)
3524       nil)))
3525
3526 (defun bbdb-pop-up-bbdb-buffer-horizontally (predicate)
3527   (if (<= (frame-width) 112)
3528       nil
3529     (let* ((first-window (selected-window))
3530            (got-it nil)
3531            (window first-window))
3532       (while (and (not (setq got-it (funcall predicate window)))
3533                   (not (eq first-window (setq window (next-window window)))))
3534         )
3535       (if (or (null got-it)
3536               (<= (window-width window) 112))
3537           nil
3538         (let ((b (current-buffer)))
3539           (select-window window)
3540           (split-window-horizontally 80)
3541           (select-window (next-window window))
3542           (let ((pop-up-windows nil))
3543             (switch-to-buffer (get-buffer-create bbdb-buffer-name)))
3544           (select-window first-window)
3545           (set-buffer b)
3546           t)))))
3547
3548 (defun bbdb-version (&optional arg)
3549   "Return string describing the version of the BBDB that is running.
3550 When called interactively with a prefix argument, insert string at point."
3551   (interactive "P")
3552   (let ((version-string (format "BBDB version %s (%s)"
3553                                bbdb-version bbdb-version-date)))
3554     (cond
3555      (arg
3556       (insert (message version-string)))
3557      ((interactive-p)
3558       (message version-string))
3559      (t version-string))))
3560
3561 \f;;; resorting, which really shouldn't be necesary...
3562
3563 (defun bbdb-record-lessp-fn (record1 record2) ; for use as a funarg
3564   (bbdb-record-lessp record1 record2))
3565
3566 (defun bbdb-resort-database ()
3567   "*Resort BBDB database as a last resort.
3568 This is not be needed when using BBDB itself.  It might be necessary
3569 after having used inferior software to add entries to the BBDB, however."
3570   (interactive)
3571   (let* ((records (copy-sequence (bbdb-records))))
3572     (bbdb-with-db-buffer
3573      (setq bbdb-records (sort bbdb-records 'bbdb-record-lessp-fn))
3574      (if (equal records bbdb-records)
3575          nil
3576        (message "DANGER!  BBDB was mis-sorted; it's being fixed...")
3577        (goto-char (point-min))
3578        (cond ((eq (following-char) ?\[) nil)
3579              ((search-forward "\n[" nil 0) (forward-char -1)))
3580        (delete-region (point) bbdb-end-marker)
3581        (let ((print-escape-newlines t)
3582              (standard-output (current-buffer))
3583              (inhibit-quit t) ; really, don't fuck with this
3584              record cache)
3585          (setq records bbdb-records)
3586          (while records
3587            (setq record (car records)
3588                  cache (bbdb-record-cache record))
3589            (bbdb-record-set-cache record nil)
3590            (prin1 (car records))
3591            (bbdb-record-set-cache record cache)
3592            (insert ?\n)
3593            (setq records (cdr records))))
3594        (kill-all-local-variables)
3595        (error "the BBDB was mis-sorted: it has been repaired.")))))
3596
3597 (defvar bbdb-init-forms
3598   '((gnus                       ; gnus 3.15 or newer
3599      (add-hook 'gnus-startup-hook 'bbdb-insinuate-gnus))
3600     (mh-e                       ; MH-E
3601      (add-hook 'mh-folder-mode-hook 'bbdb-insinuate-mh))
3602     (rmail                      ; RMAIL
3603      (add-hook 'rmail-mode-hook 'bbdb-insinuate-rmail))
3604     (sendmail                   ; the standard mail user agent
3605      (add-hook 'mail-setup-hook 'bbdb-insinuate-sendmail))
3606     (vm-old                     ; the alternative mail reader
3607      (add-hook 'vm-load-hook 'bbdb-insinuate-vm))
3608     (vm                        ; newer versions don't have vm-load-hook
3609      (progn (eval-after-load "vm" '(bbdb-insinuate-vm))))
3610     (message                    ; the gnus mail user agent
3611      (add-hook 'message-setup-hook 'bbdb-insinuate-message))
3612     (reportmail                 ; mail notification
3613      (add-hook 'reportmail-load-hook 'bbdb-insinuate-reportmail))
3614     (sc                         ; message citation
3615      (add-hook 'sc-load-hook 'bbdb-insinuate-sc))
3616     (supercite                  ; same
3617      (add-hook 'sc-load-hook 'bbdb-insinuate-sc))
3618     (w3                         ; WWW browser
3619      (add-hook 'w3-load-hook 'bbdb-insinuate-w3)))
3620   "The alist which maps features to insinuation forms.")
3621
3622 ;;;###autoload
3623 (defun bbdb-initialize (&rest to-insinuate)
3624 "*Initialize the BBDB.  One or more of the following symbols can be
3625 passed as arguments to initiate the appropriate insinuations.
3626
3627  Initialization of mail/news readers:
3628
3629    gnus       Initialize BBDB support for the gnus mail/news reader
3630               version 3.15 or newer.  If you pass the `gnus' symbol,
3631               you should probably also pass the `message' symbol.
3632    mh-e       Initialize BBDB support for the MH-E mail reader.
3633    rmail      Initialize BBDB support for the RMAIL mail reader.
3634    sendmail   Initialize BBDB support for sendmail (M-x mail).
3635    vm         Initialize BBDB support for the VM mail reader.
3636               NOTE: For the VM insinuation to work properly, you must
3637               either call `bbdb-initialize' with the `vm' symbol from
3638               within your VM initialization file (\"~/.vm\") or you
3639               must call `bbdb-insinuate-vm' manually from within your
3640               VM initialization file.
3641
3642  Initialization of miscellaneous package:
3643
3644    message    Initialize BBDB support for Message mode.
3645    reportmail Initialize BBDB support for the Reportmail mail
3646               notification package.
3647    sc or      Initialize BBDB support for the Supercite message
3648    supercite  citation package.
3649    w3         Initialize BBDB support for Web browsers."
3650
3651   (defalias 'advertized-bbdb-delete-current-field-or-record
3652     'bbdb-delete-current-field-or-record)
3653
3654   (require 'bbdb-autoloads)
3655
3656   (while to-insinuate
3657     (let* ((feature (car to-insinuate))
3658            (init (assq feature bbdb-init-forms)))
3659       (setq to-insinuate (cdr to-insinuate))
3660       (if init
3661           (if (or (featurep feature) (locate-library (symbol-name feature)))
3662               (eval (cadr init))
3663             (bbdb-warn "cannot locate feature `%s'" feature))
3664           (bbdb-warn "don't know how to insinuate `%s'" feature))))
3665
3666   ;; RMAIL, MHE, and VM interfaces might need these.
3667   (autoload 'mail-strip-quoted-names "mail-utils")
3668   (autoload 'mail-fetch-field "mail-utils")
3669   ;; All of the interfaces need this.
3670   (autoload 'mail-extract-address-components "mail-extr")
3671
3672   (run-hooks 'bbdb-initialize-hook))
3673
3674 ;; Initialize keymaps
3675 (unless bbdb-mode-search-map
3676   (define-prefix-command 'bbdb-mode-search-map)
3677   (if (fboundp 'set-keymap-prompt)
3678       (set-keymap-prompt
3679        bbdb-mode-search-map
3680        "(Search [n]ame, [c]ompany, net [a]ddress, n[o]tes)?"))
3681
3682   (define-key bbdb-mode-search-map [(n)] 'bbdb-name)
3683   (define-key bbdb-mode-search-map [(c)] 'bbdb-company)
3684   (define-key bbdb-mode-search-map [(a)] 'bbdb-net)
3685   (define-key bbdb-mode-search-map [(o)] 'bbdb-notes))
3686
3687 (unless bbdb-mode-map
3688   (setq bbdb-mode-map (make-keymap))
3689   (suppress-keymap bbdb-mode-map)
3690
3691   (define-key bbdb-mode-map [(S)]          'bbdb-mode-search-map)
3692
3693   (define-key bbdb-mode-map [(*)]          'bbdb-apply-next-command-to-all-records)
3694   (define-key bbdb-mode-map [(+)]          'bbdb-append-records)
3695   (define-key bbdb-mode-map [(!)]          'bbdb-search-invert-set)
3696   (define-key bbdb-mode-map [(a)]          'bbdb-add-or-remove-mail-alias)
3697   (define-key bbdb-mode-map [(e)]          'bbdb-edit-current-field)
3698   (define-key bbdb-mode-map [(n)]          'bbdb-next-record)
3699   (define-key bbdb-mode-map [(p)]          'bbdb-prev-record)
3700   (define-key bbdb-mode-map [(d)]          'bbdb-delete-current-field-or-record)
3701   (define-key bbdb-mode-map [(control k)]  'bbdb-delete-current-field-or-record)
3702   (define-key bbdb-mode-map [(control o)]  'bbdb-insert-new-field)
3703   (define-key bbdb-mode-map [(s)]          'bbdb-save-db)
3704   (define-key bbdb-mode-map [(control x) (control s)]
3705                                            'bbdb-save-db)
3706   (define-key bbdb-mode-map [(r)]          'bbdb-refile-record)
3707   (define-key bbdb-mode-map [(t)]          'bbdb-toggle-records-display-layout)
3708   (define-key bbdb-mode-map [(T)]          'bbdb-display-record-completely)
3709   (define-key bbdb-mode-map [(o)]          'bbdb-omit-record)
3710   (define-key bbdb-mode-map [(?\;)]        'bbdb-record-edit-notes)
3711   (define-key bbdb-mode-map [(m)]          'bbdb-send-mail)
3712   (define-key bbdb-mode-map "\M-d"         'bbdb-dial)
3713   (define-key bbdb-mode-map [(f)]          'bbdb-finger)
3714   (define-key bbdb-mode-map [(F)]          'bbdb-ftp)
3715   (define-key bbdb-mode-map [(i)]          'bbdb-info)
3716   (define-key bbdb-mode-map [(??)]         'bbdb-help)
3717   (define-key bbdb-mode-map [(q)]          'bbdb-bury-buffer)
3718   (define-key bbdb-mode-map [(control x) (control t)]
3719                                            'bbdb-transpose-fields)
3720   (define-key bbdb-mode-map [(w)]          'bbdb-www)
3721   (define-key bbdb-mode-map [(W)]         'bbdb-whois)
3722   (define-key bbdb-mode-map [(P)]          'bbdb-print)
3723   (define-key bbdb-mode-map [(h)]          'other-window)
3724   (define-key bbdb-mode-map [(=)]          'delete-other-windows)
3725   (define-key bbdb-mode-map [(c)]          'bbdb-create)
3726   (define-key bbdb-mode-map [(C)]          'bbdb-changed)
3727   (define-key bbdb-mode-map [(b)]          'bbdb)
3728
3729   (define-key bbdb-mode-map [delete]       'scroll-down)
3730   (define-key bbdb-mode-map " "            'scroll-up)
3731   )
3732
3733
3734 ;;; Support for the various Emacsen.  This is for features that the
3735 ;;; BBDB adds to itself for different Emacsen.  For definitions of
3736 ;;; functions that aren't present in various Emacsen (for example,
3737 ;;; cadr for Emacs 19.34), see below
3738 (when (string-match "XEmacs\\|Lucid" emacs-version)
3739   ;; Above
3740   (fset 'bbdb-warn 'warn)
3741
3742   ;; bbdb-com.el
3743   (fset 'bbdb-display-completion-list 'bbdb-xemacs-display-completion-list))
3744
3745 (defun bbdb-insinuate-sendmail ()
3746   "Call this function to hook BBDB into sendmail (that is, M-x mail)."
3747   (define-key mail-mode-map "\M-\t" 'bbdb-complete-name))
3748
3749 ;;;###autoload
3750 (defun bbdb-insinuate-message ()
3751   "Call this function to hook BBDB into `message-mode'."
3752   (define-key message-mode-map "\M-\t" 'bbdb-complete-name))
3753
3754 ;;; Erm. says here that (require...) can take a noerror flag; why do
3755 ;;; we have this function?
3756 (defmacro safe-require (thing)
3757   (list 'condition-case nil (list 'require thing) '(file-error nil)))
3758
3759 ;; Wrappers for things that change for different Emacsen.  Note: This
3760 ;; is for things that get redefined that don't belong elsewhere.  Some
3761 ;; functions that get redefined live elsewhere in the source because
3762 ;; it makes sense to put them there.
3763
3764 (defun bbdb-warn (&rest args)
3765   (beep 1)
3766   (apply 'message args))
3767
3768 \f
3769 (provide 'bbdb)  ; provide before loading things which might require
3770
3771 (run-hooks 'bbdb-load-hook)