1 ;;; -*- Mode:Emacs-Lisp -*-
3 ;;; This file is part of the Insidious Big Brother Database (aka BBDB),
4 ;;; copyright (c) 1991, 1992, 1993 Jamie Zawinski <jwz@netscape.com>.
5 ;;; It contains most of the user-level interactive commands for BBDB.
8 ;;; The Insidious Big Brother Database is free software; you can redistribute
9 ;;; it and/or modify it under the terms of the GNU General Public License as
10 ;;; published by the Free Software Foundation; either version 2, or (at your
11 ;;; option) any later version.
13 ;;; BBDB is distributed in the hope that it will be useful, but WITHOUT ANY
14 ;;; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 ;;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18 ;;; You should have received a copy of the GNU General Public License
19 ;;; along with GNU Emacs; see the file COPYING. If not, write to
20 ;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 ;; $Id: bbdb-com.el,v 1.10 2007-02-23 20:24:06 fenk Exp $
28 ;;(require 'bbdb-snarf) causes recursive compile, which I should fix.
33 (if (locate-library "mailabbrev")
35 (quote mail-abbrevs))))
37 ;; compiler placating.
38 ;; not sure BBDB runs on anything old enough to use auto-fill-hook, mind.
40 (if (boundp 'auto-fill-function)
41 (fset 'bbdb-auto-fill-function 'auto-fill-function)
42 (fset 'bbdb-auto-fill-function 'auto-fill-hook))
44 (autoload 'mh-send "mh-e")
45 (autoload 'vm-session-initialization "vm-startup")
46 (autoload 'vm-mail-internal "vm-reply")
47 (autoload 'mew-send "mew")
48 (autoload 'bbdb-header-start "bbdb-hooks")
49 (autoload 'bbdb-extract-field-value "bbdb-hooks")
50 (autoload 'bbdb-fontify-buffer "bbdb-gui")
51 (autoload 'Info-goto-node "info")
52 ;; this is very unpleasant, but saves me doing a lot of rewriting
53 ;; for now. a big cleanup will happen for the next release, maybe.
54 ;; NB if emacs 21 or older emacsen or even things you bolt on have
55 ;; any of these functions, bad things will happen. Again, FITNR.
56 (if (featurep 'xemacs)
58 (fset 'bbdb-extent-string 'extent-string)
59 (fset 'bbdb-display-message 'display-message)
60 (fset 'bbdb-event-to-character 'event-to-character))
61 (fset 'bbdb-extent-string 'ignore)
62 (fset 'bbdb-display-message 'ignore)
63 (fset 'bbdb-event-to-character 'ignore)))
65 (defvar bbdb-define-all-aliases-needs-rebuilt nil)
67 (defcustom bbdb-extract-address-components-func
68 'bbdb-rfc822-addresses
69 "Function called to parse one or more email addresses.
70 See bbdb-extract-address-components for an example."
71 :group 'bbdb-noticing-records
74 (defcustom bbdb-default-country
75 '"Emacs";; what do you mean, it's not a country?
76 "*Default country to use if none is specified."
77 :group 'bbdb-record-creation
78 :type 'string) ;; wonder if there's a smart place to get this? TZ, maybe?
80 (defmacro bbdb-grovel-elide-arg (arg)
82 (list 'not (list 'eq arg 0))
83 'bbdb-display-layout))
85 (defvar bbdb-search-invert nil
86 "Bind this variable to t in order to invert the result of `bbdb-search'.
88 \(let ((bbdb-search-invert t))
89 \(bbdb-search records foo foo))")
91 (defun bbdb-search-invert-p ()
92 "Return `bbdb-search-invert' and set it to nil.
93 To set it on again, use `bbdb-search-invert-set'."
94 (let ((result bbdb-search-invert))
95 (setq bbdb-search-invert nil)
99 (defun bbdb-search-invert-set ()
100 "Typing \\<bbdb-mode-map>\\[bbdb-search-invert-set] inverts the meaning of the next search command.
101 Sets `bbdb-search-invert' to t.
102 You will have to call this function again, if you want to
103 do repeated inverted searches."
105 (setq bbdb-search-invert t)
106 (message (substitute-command-keys
107 "\\<bbdb-mode-map>\\[bbdb-search-invert-set] - ")))
109 (defmacro bbdb-search (records &optional name company net notes phone)
110 "Search RECORDS for optional arguments NAME, COMPANY, NET, NOTES, PHONE.
111 This macro only emits code for those things being searched for;
112 literal nils at compile-time cause no code to be emitted.
114 If you want to reverse the search, bind `bbdb-search-invert' to t."
116 ;; I didn't protect these vars from multiple evaluation because that
117 ;; actually generates *less efficient code* in elisp, because the extra
118 ;; bindings can't easily be optimized away without lexical scope. fmh.
119 (or (stringp name) (symbolp name) (error "name must be atomic"))
120 (or (stringp company) (symbolp company) (error "company must be atomic"))
121 (or (stringp net) (symbolp net) (error "net must be atomic"))
122 (or (stringp notes) (symbolp notes) (error "notes must be atomic"))
123 (or (stringp phone) (symbolp phone) (error "phone must be atomic"))
127 (` (let ((rest-of-phones (bbdb-record-phones record))
130 (while (and rest-of-phones (not done))
131 (setq done (string-match (, phone)
132 ;; way way wasteful...
134 (car rest-of-phones)))
135 rest-of-phones (cdr rest-of-phones)))
136 ;; so that "^$" can be used to find entries that
138 (setq done (string-match (, phone) "")))
144 (` (if (stringp (, notes))
145 (string-match (, notes)
146 (or (bbdb-record-notes record) ""))
147 (if (eq (car (, notes)) '*)
148 (let ((fields all-fields) done tmp)
149 (if (bbdb-record-raw-notes record)
150 (while (and (not done) fields)
151 (setq tmp (bbdb-record-getprop
153 done (and tmp (string-match
156 fields (cdr fields)))
157 ;; so that "^$" can be used to find entries that
159 (setq done (string-match (cdr (, notes)) "")))
161 (string-match (cdr (, notes))
162 (or (bbdb-record-getprop
163 record (car (, notes))) "")))))
168 (` ((string-match (, name) (or (bbdb-record-name record) ""))
169 (let ((rest-of-aka (bbdb-record-aka record))
171 (while (and rest-of-aka (not done))
172 (setq done (string-match (, name) (car rest-of-aka))
173 rest-of-aka (cdr rest-of-aka)))
179 (` (let ((rest-of-nets (bbdb-record-net record))
182 (while (and rest-of-nets (not done))
183 (setq done (string-match (, net) (car rest-of-nets))
184 rest-of-nets (cdr rest-of-nets)))
185 ;; so that "^$" can be used to find entries that
186 ;; have no net addresses.
187 (setq done (string-match (, net) "")))
193 (` (string-match (, company)
194 (or (bbdb-record-company record) "")))
197 (` (let ((matches '())
199 '((all-fields (cons 'notes
200 (mapcar (lambda (x) (intern (car x)))
203 (case-fold-search bbdb-case-fold-search)
204 (records (, records))
205 (invert (bbdb-search-invert-p))
208 (setq record (car records))
210 (not (or (,@ clauses))))
213 (setq matches (cons record matches)))
214 (setq records (cdr records)))
215 (nreverse matches)))))
217 (defun bbdb-search-prompt (prompt &rest rest)
218 (if (string-match "%m" prompt)
219 (setq prompt (replace-match (if bbdb-search-invert
223 (read-string (apply 'format prompt rest)))
226 (defun bbdb (string elidep)
227 "Display all entries in the BBDB matching the regexp STRING
228 in either the name(s), company, network address, or notes."
230 (list (bbdb-search-prompt "Search records %m regexp: ")
232 (let* ((bbdb-display-layout (bbdb-grovel-elide-arg elidep))
233 (notes (cons '* string))
235 (bbdb-search (bbdb-records) string string string notes
238 (bbdb-display-records records)
239 ;; we could use error here, but it's not really an error.
240 (message "No records matching '%s'" string))))
243 (defun bbdb-name (string elidep)
244 "Display all entries in the BBDB matching the regexp STRING in the name
245 \(or ``alternate'' names\)."
247 (list (bbdb-search-prompt "Search records with names %m regexp: ")
249 (let ((bbdb-display-layout (bbdb-grovel-elide-arg elidep)))
250 (bbdb-display-records (bbdb-search (bbdb-records) string))))
253 (defun bbdb-company (string elidep)
254 "Display all entries in BBDB matching STRING in the company field."
256 (list (bbdb-search-prompt "Search records with company %m regexp: ")
258 (let ((bbdb-display-layout (bbdb-grovel-elide-arg elidep)))
259 (bbdb-display-records (bbdb-search (bbdb-records) nil string))))
262 (defun bbdb-net (string elidep)
263 "Display all entries in BBDB matching regexp STRING in the network address."
265 (list (bbdb-search-prompt "Search records with net address %m regexp: ")
267 (let ((bbdb-display-layout (bbdb-grovel-elide-arg elidep)))
268 (bbdb-display-records (bbdb-search (bbdb-records) nil nil string))))
271 (defun bbdb-notes (which string elidep)
272 "Display all entries in BBDB matching STRING in the named notes field."
275 (list (setq field (completing-read "Notes field to search (RET for all): "
276 (append '(("notes")) (bbdb-propnames))
278 (if (featurep 'gmhist)
279 (read-with-history-in 'bbdb-notes-field "Regular expression: ")
280 (bbdb-search-prompt "Search records with %s %m regexp: "
281 (if (string= field "")
284 current-prefix-arg)))
285 (let ((bbdb-display-layout (bbdb-grovel-elide-arg elidep))
286 (notes (if (string= which "")
288 (cons (intern which) string))))
289 (bbdb-display-records (bbdb-search (bbdb-records) nil nil nil notes))))
291 (defun bbdb-phones (string elidep)
292 "Display all entries in BBDB matching the regexp STRING in the phones field."
294 (list (bbdb-search-prompt "Search records with phone %m regexp: ")
296 (let ((bbdb-display-layout (bbdb-grovel-elide-arg elidep)))
297 (bbdb-display-records
298 (bbdb-search (bbdb-records) nil nil nil nil string))))
301 (defun bbdb-changed (elidep)
302 "Display all entries in the bbdb database which have been changed since
303 the database was last saved."
305 (let ((bbdb-display-layout (bbdb-grovel-elide-arg elidep))
306 (changed-records (bbdb-with-db-buffer bbdb-changed-records)))
307 (if (bbdb-search-invert-p)
308 (let ((recs (bbdb-records))
314 (when (not (member r changed-records))
315 (setq changed-records (delete r changed-records)
316 unchanged-records (cons r unchanged-records))))
317 (bbdb-display-records unchanged-records))
318 (bbdb-display-records changed-records))))
320 (defun bbdb-display (records)
321 "Prompts for and displays a single record (this is faster than searching.)"
322 (interactive (list (bbdb-completing-read-record "Display record of: ")))
323 (bbdb-display-records records))
325 (defun bbdb-display-some (function)
326 "Display records according to FUNCTION. FUNCTION is called with one
327 argument, the record, and should return nil if the record is not to be
328 displayed. If the record is to be displayed, it (the record) should
330 (bbdb-display-records (delq nil (mapcar function (bbdb-records)))))
335 (defun bbdb-redisplay-records ()
336 "Regrinds the contents of the *BBDB* buffer, without scrolling.
337 If possible, you should call `bbdb-redisplay-one-record' instead."
339 (m (condition-case nil (mark) (error nil))))
340 (goto-char (window-start))
342 (bbdb-display-records-1 bbdb-records)
348 (run-hooks 'bbdb-list-hook))))
350 (defun bbdb-redisplay-one-record (record &optional record-cons next-record-cons
352 "Regrind one record. The *BBDB* buffer must be current when this is called."
353 (bbdb-debug (if (not (eq (not (not delete-p))
354 (not (not (bbdb-record-deleted-p record)))))
356 (if (null record-cons) (setq record-cons (assq record bbdb-records)))
357 (if (null next-record-cons)
358 (setq next-record-cons (car (cdr (memq record-cons bbdb-records)))))
359 (if (null record-cons)
360 (bbdb-display-records (list record) nil t)
361 (let ((position (point))
362 (marker (nth 2 record-cons))
364 (buffer-read-only nil))
366 (if (null record-cons) (error "doubleplus ungood: record unexists!"))
367 (if (null marker) (error "doubleplus ungood: marker unexists!")))
370 (remove-text-properties marker (or (nth 2 next-record-cons) (point-max))
373 (bbdb-format-record (car record-cons) (car (cdr record-cons))))
374 (setq next-marker (or (nth 2 next-record-cons) (point-max)))
375 (delete-region (point) next-marker)
376 (if (< position next-marker)
377 (goto-char position))
379 (if (and bbdb-gui (not delete-p))
380 (bbdb-fontify-buffer (list record-cons
381 ;; the record ends here
382 (list nil nil next-marker))))
384 (run-hooks 'bbdb-list-hook)))))
386 ;;; Parsing phone numbers
387 ;;; XXX this needs expansion to handle international prefixes properly
388 ;;; i.e. +353-number without discarding the +353 part. Problem being
389 ;;; that this will necessitate yet another change in the database
390 ;;; format for people who are using north american numbers.
393 (defconst bbdb-phone-area-regexp "(?[ \t]*\\+?1?[ \t]*[-\(]?[ \t]*[-\(]?[ \t]*\\([2-9][0-9][0-9]\\)[ \t]*)?[-./ \t]*")
394 (defconst bbdb-phone-main-regexp "\\([1-9][0-9][0-9]\\)[ \t]*[-.]?[ \t]*\\([0-9][0-9][0-9][0-9]\\)[ \t]*")
396 (defconst bbdb-phone-ext-regexp "x?[ \t]*\\([0-9]+\\)[ \t]*")
398 (defconst bbdb-phone-regexp-1 (concat "^[ \t]*" bbdb-phone-area-regexp bbdb-phone-main-regexp bbdb-phone-ext-regexp "$"))
399 (defconst bbdb-phone-regexp-2 (concat "^[ \t]*" bbdb-phone-area-regexp bbdb-phone-main-regexp "$"))
400 (defconst bbdb-phone-regexp-3 (concat "^[ \t]*" bbdb-phone-main-regexp bbdb-phone-ext-regexp "$"))
401 (defconst bbdb-phone-regexp-4 (concat "^[ \t]*" bbdb-phone-main-regexp "$"))
402 (defconst bbdb-phone-regexp-5 (concat "^[ \t]*" bbdb-phone-ext-regexp "$"))
404 (defun bbdb-parse-phone-number (string &optional number-type)
405 "Parse a phone number from STRING and return a list of integers the form
406 \(area-code exchange number) or (area-code exchange number extension).
407 This is both lenient and strict in what it will parse - whitespace may
408 appear (or not) between any of the groups of digits, parentheses around the
409 area code are optional, as is a dash between the exchange and number, and
410 a '1' preceeding the area code; but there must be three digits in the area
411 code and exchange, and four in the number (if they are present). An error
412 will be signalled if unparsable. All of these are unambigously parsable:
414 ( 415 ) 555 - 1212 x123 -> (415 555 1212 123)
415 (415)555-1212 123 -> (415 555 1212 123)
416 (1-415) 555-1212 123 -> (415 555 1212 123)
417 1 (415)-555-1212 123 -> (415 555 1212 123)
418 555-1212 123 -> (0 555 1212 123)
419 555 1212 -> (0 555 1212)
420 415 555 1212 -> (415 555 1212)
421 1 415 555 1212 -> (415 555 1212)
422 5551212 -> (0 555 1212)
423 4155551212 -> (415 555 1212)
424 4155551212123 -> (415 555 1212 123)
425 5551212x123 -> (0 555 1212 123)
428 Note that \"4151212123\" is ambiguous; it could be interpreted either as
429 \"(415) 121-2123\" or as \"415-1212 x123\".
431 \(And uh, oh yeah, this does little if `bbdb-north-american-phone-numbers-p'
434 (cond ((if number-type
435 (eq number-type 'euro)
436 (not bbdb-north-american-phone-numbers-p))
437 (list (bbdb-string-trim string)))
438 ((string-match bbdb-phone-regexp-1 string)
439 ;; (415) 555-1212 x123
440 (list (bbdb-subint string 1) (bbdb-subint string 2)
441 (bbdb-subint string 3) (bbdb-subint string 4)))
442 ((string-match bbdb-phone-regexp-2 string)
444 (list (bbdb-subint string 1) (bbdb-subint string 2)
445 (bbdb-subint string 3)))
446 ((string-match bbdb-phone-regexp-3 string)
448 (list 0 (bbdb-subint string 1) (bbdb-subint string 2)
449 (bbdb-subint string 3)))
450 ((string-match bbdb-phone-regexp-4 string)
452 (list 0 (bbdb-subint string 1) (bbdb-subint string 2)))
453 ((string-match bbdb-phone-regexp-5 string)
455 (list 0 0 0 (bbdb-subint string 1)))
456 (t (error "phone number unparsable."))))
458 ;;; Parsing other things
460 (defcustom bbdb-expand-mail-aliases t
461 "If non-nil, expand mail aliases in `bbdb-complete-name'."
462 :group 'bbdb-record-use
465 (defcustom bbdb-check-zip-codes-p t
466 "If non-nil, require legal zip codes when entering an address.
467 The format of legal zip codes is determined by the variable
468 `bbdb-legal-zip-codes'."
469 :group 'bbdb-record-creation
472 (defcustom bbdb-legal-zip-codes
475 ;; Matches 1 to 6 digits.
476 "^[ \t\n]*[0-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[ \t\n]*$"
477 ;; Matches 5 digits and 3 or 4 digits.
478 "^[ \t\n]*\\([0-9][0-9][0-9][0-9][0-9]\\)[ \t\n]*-?[ \t\n]*\\([0-9][0-9][0-9][0-9]?\\)[ \t\n]*$"
479 ;; Match zip codes for Canada, UK, etc. (result is ("LL47" "U4B")).
480 "^[ \t\n]*\\([A-Za-z0-9]+\\)[ \t\n]+\\([A-Za-z0-9]+\\)[ \t\n]*$"
481 ;; Match zip codes for continental Europe. Examples "CH-8057"
482 ;; or "F - 83320" (result is ("CH" "8057") or ("F" "83320")).
483 ;; Support for "NL-2300RA" added at request from Carsten Dominik
484 ;; <dominik@astro.uva.nl>
485 "^[ \t\n]*\\([A-Z]+\\)[ \t\n]*-?[ \t\n]*\\([0-9]+ ?[A-Z]*\\)[ \t\n]*$"
486 ;; Match zip codes from Sweden where the five digits are grouped 3+2
487 ;; at the request from Mats Lofdahl <MLofdahl@solar.stanford.edu>.
488 ;; (result is ("SE" (133 36)))
489 "^[ \t\n]*\\([A-Z]+\\)[ \t\n]*-?[ \t\n]*\\([0-9]+\\)[ \t\n]+\\([0-9]+\\)[ \t\n]*$")
490 "List of regexps that match legal zip codes.
491 Whether this is used at all depends on the variable `bbdb-check-zip-codes-p'."
492 :group 'bbdb-record-creation
493 :type '(repeat regexp))
495 (defun bbdb-parse-zip-string (string)
496 "Check whether STRING is a legal zip code.
497 Do this only if `bbdb-check-zip-codes-p' is non-nil."
498 (if (and bbdb-check-zip-codes-p
499 (not (memq t (mapcar (lambda (regexp)
500 ;; if it matches, (not (not index-of-match)) returns t
501 (not (not (string-match regexp string))))
502 bbdb-legal-zip-codes))))
503 (error "not a valid zip code.")
506 (defun bbdb-read-new-record ()
507 "Prompt for and return a completely new BBDB record.
508 Doesn't insert it in to the database or update the hashtables, but does
509 ensure that there will not be name collisions."
510 (bbdb-records) ; make sure database is loaded
512 (error "The Insidious Big Brother Database is read-only."))
513 (let (firstname lastname)
516 (if current-prefix-arg
517 (setq firstname (bbdb-read-string "First Name: ")
518 lastname (bbdb-read-string "Last Name: "))
519 (let ((names (bbdb-divide-name (bbdb-read-string "Name: "))))
520 (setq firstname (car names)
521 lastname (nth 1 names))))
522 (if (string= firstname "") (setq firstname nil))
523 (if (string= lastname "") (setq lastname nil))
524 (if (and bbdb-no-duplicates-p
525 (bbdb-gethash (bbdb-build-name firstname lastname)))
526 (error "%s %s is already in the database"
527 (or firstname "") (or lastname "")))))
528 (let ((company (bbdb-read-string "Company: "))
529 (net (bbdb-split (bbdb-read-string "Network Address: ") ","))
531 (let (L L-tail str addr)
536 "Address Description [RET when no more]: "
538 (mapcar (function (lambda(x) (list x)))
539 (bbdb-label-completion-list
541 (setq addr (make-vector bbdb-address-length nil))
542 (bbdb-record-edit-address addr str)
544 (progn (setcdr L-tail (cons addr nil))
545 (setq L-tail (cdr L-tail)))
546 (setq L (cons addr nil)
555 "Phone Location [RET when no more]: "
557 (mapcar (function (lambda(x) (list x)))
558 (bbdb-label-completion-list
562 (bbdb-parse-phone-number
563 (read-string "Phone: "
564 (and (integerp bbdb-default-area-code)
566 bbdb-default-area-code))))))
567 (phone (apply 'vector str
568 (if (= 3 (length phonelist))
569 (nconc phonelist '(0))
572 (progn (setcdr L-tail (cons phone nil))
573 (setq L-tail (cdr L-tail)))
574 (setq L (cons phone nil)
577 (notes (bbdb-read-string "Additional Comments: ")))
578 (if (string= company "") (setq company nil))
579 (if (string= notes "") (setq notes nil))
581 (vector firstname lastname nil company phones addrs net notes
582 (make-vector bbdb-cache-length nil))))
586 (defun bbdb-create (record)
587 "Add a new entry to the bbdb database ; prompts for all relevant info
588 using the echo area, inserts the new record in the db, sorted alphabetically,
589 and offers to save the db file. DO NOT call this from a program. Call
590 bbdb-create-internal instead."
591 (interactive (list (bbdb-read-new-record)))
592 (bbdb-invoke-hook 'bbdb-create-hook record)
593 (bbdb-change-record record t)
594 (bbdb-display-records (list record)))
597 (defmacro bbdb-check-type (place predicate)
598 (list 'while (list 'not (list predicate place))
599 (nconc (cond ((eq (car-safe place) 'aref)
600 (list 'aset (nth 1 place) (nth 2 place)))
601 ((eq (car-safe place) 'car)
602 (list 'setcar (nth 1 place)))
603 ((eq (car-safe place) 'cdr)
604 (list 'setcdr (nth 1 place)))
605 (t (list 'setq place)))
607 (list 'signal ''wrong-type-argument
608 (list 'list (list 'quote predicate) place))))))
610 (defun bbdb-create-internal (name company net addrs phones notes)
611 "Adds a record to the database; this function does a fair amount of
612 error-checking on the passed in values, so it's safe to call this from
615 NAME is a string, the name of the person to add. An error is signalled
616 if that name is already in use and `bbdb-no-duplicates-p' is t.
617 COMPANY is a string or nil.
618 NET is a comma-separated list of email addresses, or a list of strings.
619 An error is signalled if that name is already in use.
620 ADDRS is a list of address objects. An address is a vector of the form
621 [\"location\" (\"line1\" \"line2\" ... ) \"City\" \"State\" \"Zip\" \"Country\"].
622 PHONES is a list of phone-number objects. A phone-number is a vector of
624 [\"location\" areacode prefix suffix extension-or-nil]
626 [\"location\" \"phone-number\"]
627 NOTES is a string, or an alist associating symbols with strings."
628 (let (firstname lastname aka)
630 (setq name (and name (bbdb-divide-name name))
632 lastname (nth 1 name))
633 (bbdb-gethash (bbdb-build-name firstname lastname)))
634 bbdb-no-duplicates-p)
635 (setq name (signal 'error
636 (list (format "%s %s is already in the database"
637 (or firstname "") (or lastname ""))))))
638 (and company (bbdb-check-type company stringp))
640 (setq net (bbdb-split net ",")))
641 (if bbdb-no-duplicates-p
644 (while (bbdb-gethash (downcase (car rest)))
646 (signal 'error (list (format
647 "%s is already in the database"
649 (setq rest (cdr rest)))))
653 (while (or (not (vectorp addr))
654 (/= (length addr) bbdb-address-length))
655 (setq addr (signal 'wrong-type-argument (list 'vectorp addr))))
656 (bbdb-check-type (aref addr 0) stringp) ;;; XXX use bbdb-addresses
657 (bbdb-check-type (aref addr 1) listp)
658 (bbdb-check-type (aref addr 2) stringp)
659 (bbdb-check-type (aref addr 3) stringp)
660 (bbdb-check-type (aref addr 4) stringp)
661 (bbdb-check-type (aref addr 5) stringp)
667 (while (or (not (vectorp phone))
668 (and (/= (length phone) 2)
669 (/= (length phone) bbdb-phone-length)))
671 (signal 'wrong-type-argument (list 'vectorp phone))))
672 (bbdb-check-type (aref phone 0) stringp)
673 (if (= 2 (length phone))
674 (bbdb-check-type (aref phone 1) stringp)
675 (bbdb-check-type (aref phone 1) integerp)
676 (bbdb-check-type (aref phone 2) integerp)
677 (bbdb-check-type (aref phone 3) integerp)
678 (and (aref phone 4) (bbdb-check-type (aref phone 4) integerp))
679 (if (eq 0 (aref phone 4)) (aset phone 4 nil)))
684 (mapcar (lambda (note)
685 (bbdb-check-type note consp)
686 (bbdb-check-type (car note) symbolp)
687 (if (consp (cdr note))
688 (setq note (cons (car note) (car (cdr note)))))
689 (bbdb-check-type (cdr note) stringp)
693 (vector firstname lastname aka company phones addrs net notes
694 (make-vector bbdb-cache-length nil))))
695 (bbdb-invoke-hook 'bbdb-create-hook record)
696 (bbdb-change-record record t)
702 (defun bbdb-current-record (&optional planning-on-modifying)
703 "Returns the record which the point is point at. In linear time, man..."
704 (if (and planning-on-modifying bbdb-readonly-p)
705 (error "The Insidious Big Brother Database is read-only."))
706 (if (not (equal bbdb-buffer-name (buffer-name (current-buffer))))
707 (error "this command only works while in the \"%s\" buffer."
712 (while (and (cdr rest) (not rec))
713 (if (> (nth 2 (car (cdr rest))) p)
714 (setq rec (car (car rest))))
715 (setq rest (cdr rest)))
716 (or rec (car (car rest)))))
719 ;; yow, are we object oriented yet?
720 (defun bbdb-record-get-field-internal (record field)
721 (cond ((eq field 'name) (bbdb-record-name record))
722 ((eq field 'net) (bbdb-record-net record))
723 ((eq field 'aka) (bbdb-record-aka record))
724 ((eq field 'phone) (bbdb-record-phones record))
725 ((eq field 'address) (bbdb-record-addresses record))
726 ((eq field 'property) (bbdb-record-raw-notes record))
727 (t (error "doubleplus ungood: unknown field type %s" field))))
729 (defun bbdb-record-store-field-internal (record field value)
730 (cond ((eq field 'name) (error "doesn't work on names"))
731 ((eq field 'net) (bbdb-record-set-net record value))
732 ((eq field 'aka) (bbdb-record-set-aka record value))
733 ((eq field 'phone) (bbdb-record-set-phones record value))
734 ((eq field 'address) (bbdb-record-set-addresses record value))
735 ((eq field 'property) (bbdb-record-set-raw-notes record value))
736 (t (error "doubleplus ungood: unknown field type %s" field))))
738 (defun bbdb-record-edit-field-internal (record field &optional which location)
739 (cond ((eq field 'name) (bbdb-record-edit-name record))
740 ((eq field 'company) (bbdb-record-edit-company record))
741 ((eq field 'net) (bbdb-record-edit-net record))
742 ((eq field 'aka) (bbdb-record-edit-aka record))
743 ((eq field 'phone) (bbdb-record-edit-phone which location))
744 ((eq field 'address) (bbdb-record-edit-address which location))
745 ((eq field 'property) (bbdb-record-edit-property record (car which)))
746 (t (error "doubleplus ungood: unknown field type %s" field))))
749 (defun bbdb-current-field (&optional planning-on-modifying)
750 (or (bbdb-current-record planning-on-modifying)
752 (delete 'field-name (get-text-property (point) 'bbdb-field)))
755 (defun bbdb-apply-next-command-to-all-records ()
756 "Typing \\<bbdb-mode-map>\\[bbdb-apply-next-command-to-all-records] \
757 in the *BBDB* buffer makes the next command operate on all
758 of the records currently displayed. \(Note that this only works for
761 (message (substitute-command-keys
762 "\\<bbdb-mode-map>\\[bbdb-apply-next-command-to-all-records] - "))
763 (setq prefix-arg current-prefix-arg
764 last-command this-command)
767 (defmacro bbdb-do-all-records-p ()
768 "Whether the last command was `bbdb-apply-next-command-to-all-records'."
769 '(eq last-command 'bbdb-apply-next-command-to-all-records))
772 (defvar bbdb-append-records nil)
775 (defun bbdb-append-records-p ()
776 (cond ((eq t bbdb-append-records))
777 ((numberp bbdb-append-records)
778 (setq bbdb-append-records
779 (1- bbdb-append-records))
780 (when (= 0 bbdb-append-records)
781 (when (not bbdb-silent-running)
782 (message "No further search results will be appended.")
784 (setq bbdb-append-records nil))
787 (setq bbdb-append-records nil)
792 (defun bbdb-append-records (arg)
793 "Typing \\<bbdb-mode-map>\\[bbdb-append-records] \
794 in the *BBDB* buffer makes the next search/display command to append
795 new records to those in the *BBDB* buffer.
797 With an prefix arg (C-u) toggle between always append and no append.
798 With an prefix arg that is a positive number append will be enabled for that
800 With any other argument append will be enabled once."
802 (message (substitute-command-keys
803 "\\<bbdb-mode-map>\\[bbdb-append-records] - "))
804 (setq bbdb-append-records
805 (cond ((and arg (listp arg))
806 (if (not bbdb-silent-running)
807 (if (not bbdb-append-records)
808 (message "Always append records.")
809 (message "Do not append records.")))
810 (not bbdb-append-records))
811 ((and (numberp arg) (< 1 arg))
812 (if (not bbdb-silent-running)
813 (message "Append records for the next %d times." arg))
818 (defun bbdb-insert-new-field (record name contents)
819 "Add a new field to the current record; the field type and contents
820 are prompted for if not supplied.
822 If you are inserting a new phone-number field, you can control whether
823 it is a north american or european phone number by providing a prefix
824 argument. A prefix arg of ^U means it's to be a euronumber, and any
825 other prefix arg means it's to be a a structured north american number.
826 Otherwise, which style is used is controlled by the variable
827 `bbdb-north-american-phone-numbers-p'.
829 If you are inserting a new net address, you can have BBDB append a
830 default domain to any net address that does not contain one. Set
831 `bbdb-default-domain' to a string such as \"mycompany.com\" (or,
832 depending on your environment, (getenv \"DOMAINNAME\")), and
833 \"@mycompany.com\" will be appended to an address that is entered as
834 just a username. A prefix arg of ^U (or a `bbdb-default-domain'
835 value of \"\", the default) means do not alter the address."
836 (interactive (let ((record (or (bbdb-current-record t)
837 (error "current record unexists!")))
839 (completion-ignore-case t))
840 (while (string= name "")
843 (completing-read "Insert Field: "
844 (append '(("phone") ("address")
845 ("net") ("AKA") ("notes"))
850 (setq name (intern name))
851 (list record name (bbdb-prompt-for-new-field-value name))))
853 (setq contents (bbdb-prompt-for-new-field-value name)))
855 (cond ((eq name 'phone)
856 (bbdb-record-set-phones record
857 (nconc (bbdb-record-phones record)
860 (bbdb-record-set-addresses record
861 (nconc (bbdb-record-addresses record)
864 (if (bbdb-record-net record)
865 (error "There already are net addresses!"))
866 (if (stringp contents)
867 (setq contents (bbdb-split contents ",")))
868 ;; first detect any conflicts....
869 (if bbdb-no-duplicates-p
870 (let ((nets contents))
872 (let ((old (bbdb-gethash (downcase (car nets)))))
873 (if (and old (not (eq old record)))
874 (error "net address \"%s\" is used by \"%s\""
876 (or (bbdb-record-name old)
877 (car (bbdb-record-net old))))))
878 (setq nets (cdr nets)))))
880 (let ((nets contents))
882 (bbdb-puthash (downcase (car nets)) record)
883 (setq nets (cdr nets))))
884 (bbdb-record-set-net record contents))
886 (if (bbdb-record-aka record)
887 (error "there already are alternate names!"))
888 (if (stringp contents)
889 (setq contents (bbdb-split contents ";")))
890 ;; first detect any conflicts....
891 (if bbdb-no-duplicates-p
892 (let ((aka contents))
894 (let ((old (bbdb-gethash (downcase (car aka)))))
895 (if (and old (not (eq old record)))
896 (error "alternate name \"%s\" is used by \"%s\""
898 (or (bbdb-record-name old)
899 (car (bbdb-record-net old))))))
900 (setq aka (cdr aka)))))
902 (let ((aka contents))
904 (bbdb-puthash (downcase (car aka)) record)
905 (setq aka (cdr aka))))
906 (bbdb-record-set-aka record contents))
908 (if (bbdb-record-notes record) (error "there already are notes!"))
909 (bbdb-record-set-notes record contents))
910 ((assoc (symbol-name name) (bbdb-propnames))
911 (if (and (consp (bbdb-record-raw-notes record))
912 (assq name (bbdb-record-raw-notes record)))
913 (error "there is already a \"%s\" note!" name))
914 (bbdb-record-putprop record name contents))
915 (t (error "doubleplus ungood: unknow how to set slot %s" name)))
916 (bbdb-change-record record nil)
918 (let ((bbdb-display-layout nil))
919 (bbdb-redisplay-one-record record)))
921 (defun bbdb-prompt-for-new-field-value (name)
922 (cond ((eq name 'net)
924 ((n (bbdb-read-string "Net: ")))
925 (if (string-match "^mailto:" n)
926 (setq n (substring n (match-end 0))))
927 (if (or (eq nil bbdb-default-domain)
928 current-prefix-arg (string-match "[@%!]" n))
930 (concat n "@" bbdb-default-domain))))
931 ((eq name 'aka) (bbdb-read-string "Alternate Names: "))
933 (let ((p (make-vector
934 (if (if current-prefix-arg
935 (numberp current-prefix-arg)
936 bbdb-north-american-phone-numbers-p)
942 (if (= bbdb-phone-length (length p))
943 (if (integerp bbdb-default-area-code)
944 bbdb-default-area-code
947 (bbdb-record-edit-phone p)
950 (let ((a (make-vector bbdb-address-length nil)))
951 (bbdb-record-edit-address a)
953 ((eq name 'notes) (bbdb-read-string "Notes: "))
954 ((assoc (symbol-name name) (bbdb-propnames))
955 (bbdb-read-string (format "%s: " name)))
958 (format "\"%s\" is an unknown field name. Define it? " name))
960 (append (bbdb-propnames) (list (list (symbol-name name)))))
961 (error "unknown field \"%s\"" name))
962 (bbdb-read-string (format "%s: " name)))))
964 (defun bbdb-add-new-field (name)
965 "Programmatically add a new field called NAME. Returns the list of propnames."
966 ;; check that we don't have one already; if we do, return quietly.
967 (if (assoc (symbol-name name) (append '(("phone") ("address") ("net")
971 (bbdb-set-propnames (append (bbdb-propnames)
972 (list (list (symbol-name name)))))))
975 (defun bbdb-edit-current-field ()
976 "Edit the contents of the Insidious Big Brother Database field displayed on
977 the current line (this is only meaningful in the \"*BBDB*\" buffer.) If the
978 cursor is in the middle of a multi-line field, such as an address or comments
979 section, then the entire field is edited, not just the current line."
981 ;; when at the end of the line take care of it
982 (if (and (eolp) (not (bobp)) (not (bbdb-current-field t)))
985 (let* ((record (bbdb-current-record t))
986 (field (bbdb-current-field t))
988 (or field (error "on an unfield"))
990 (apply 'bbdb-record-edit-field-internal record field))
991 (bbdb-change-record record need-to-sort)
992 (bbdb-redisplay-one-record record)
994 (if (and (eq 'property (car field))
995 (or (eq 'mail-alias (caadr field))
996 (eq 'net (caadr field))))
997 (setq bbdb-define-all-aliases-needs-rebuilt 'edit))
1000 (defun bbdb-record-edit-name (bbdb-record)
1001 (let (fn ln co need-to-sort new-name old-name)
1004 (if current-prefix-arg
1005 (setq fn (bbdb-read-string "First Name: "
1006 (bbdb-record-firstname bbdb-record))
1007 ln (bbdb-read-string "Last Name: "
1008 (bbdb-record-lastname bbdb-record)))
1009 (let ((names (bbdb-divide-name
1010 (bbdb-read-string "Name: "
1011 (bbdb-record-name bbdb-record)))))
1012 (setq fn (car names)
1015 (or (not (string= fn
1016 (or (bbdb-record-firstname bbdb-record) "")))
1018 (or (bbdb-record-lastname bbdb-record) "")))))
1019 (if (string= "" fn) (setq fn nil))
1020 (if (string= "" ln) (setq ln nil))
1021 ;; check for collisions
1022 (setq new-name (if (and fn ln) (concat fn " " ln)
1024 old-name (bbdb-record-name bbdb-record))
1025 (if (and bbdb-no-duplicates-p
1027 (not (and old-name (string= (downcase new-name)
1028 (downcase old-name))))
1029 (bbdb-gethash (downcase new-name)))
1030 (error "%s is already in the database!" new-name))))
1031 (setq co (bbdb-read-string "Company: "
1032 (bbdb-record-company bbdb-record)))
1033 (if (string= "" co) (setq co nil))
1036 (not (equal (if co (downcase co) "")
1037 (downcase (or (bbdb-record-company bbdb-record)
1040 ;; delete the old hash entry
1041 (let ((name (bbdb-record-name bbdb-record))
1042 (lastname (bbdb-record-lastname bbdb-record))
1043 (company (bbdb-record-company bbdb-record)))
1044 (if (> (length name) 0)
1045 (bbdb-remhash (downcase name) bbdb-record))
1046 (if (> (length lastname) 0)
1047 (bbdb-remhash (downcase lastname) bbdb-record))
1048 (if (> (length company) 0)
1049 (bbdb-remhash (downcase company) bbdb-record)))
1050 (bbdb-record-set-namecache bbdb-record nil)
1051 (bbdb-record-set-firstname bbdb-record fn)
1052 (bbdb-record-set-lastname bbdb-record ln)
1053 (bbdb-record-set-company bbdb-record co)
1054 ;; add a new hash entry
1056 (bbdb-puthash (downcase (bbdb-record-name bbdb-record))
1060 (defun bbdb-record-edit-company (bbdb-record)
1061 (let ((co (bbdb-read-string "Company: " (bbdb-record-company bbdb-record)))
1064 (if (string= "" co) (setq co nil))
1067 (not (equal (if co (downcase co) "")
1068 (downcase (or (bbdb-record-company bbdb-record)
1071 ;; delete the old hash entry
1072 (let ((company (bbdb-record-company bbdb-record)))
1073 (if (> (length company) 0)
1074 (bbdb-remhash (downcase company) bbdb-record)))
1076 (bbdb-record-set-company bbdb-record co)
1077 ;; add a new hash entry
1078 (bbdb-puthash (downcase (bbdb-record-name bbdb-record))
1083 (defun bbdb-address-edit-default (addr)
1084 "Function to use for address editing.
1085 The sub-fields are queried using the default order and using the
1086 default names. Set `bbdb-address-editing-function' to an alternate
1087 address editing function if you don't like this function. It is
1088 mostly used for US style addresses.
1090 The sub-fields and the prompts used are:
1091 Street, line n: (nth n street)
1096 (let* ((str (let ((l) (s) (n 0))
1097 (while (not (string= "" (setq s (bbdb-read-string
1098 (format "Street, line %d: " (+ 1 n))
1099 (nth n (bbdb-address-streets addr))))))
1100 (setq l (append l (list s)))
1103 (cty (bbdb-read-string "City: " (bbdb-address-city addr)))
1104 (ste (bbdb-read-string "State: " (bbdb-address-state addr)))
1105 (zip (bbdb-error-retry
1106 (bbdb-parse-zip-string
1107 (bbdb-read-string "Zip Code: " (bbdb-address-zip-string addr)))))
1108 (country (bbdb-read-string "Country: " (or (bbdb-address-country addr)
1109 bbdb-default-country))))
1110 (bbdb-address-set-streets addr str)
1111 (bbdb-address-set-city addr cty)
1112 (bbdb-address-set-state addr ste)
1113 (bbdb-address-set-zip addr zip)
1114 (if (string= "" (concat cty ste zip country (mapconcat 'identity str "")))
1115 ;; user didn't enter anything. this causes a display bug. this
1116 ;; is a temporary fix. Ideally, we'd simply discard the entire
1117 ;; address entry, but that's going to require bigger hacking.
1118 (bbdb-address-set-country addr "Emacs")
1119 (bbdb-address-set-country addr country))
1122 (defun bbdb-address-edit-continental (addr)
1123 "Function to use for address editing.
1124 The sub-fields are queried using the default order and using the
1125 default names. Set `bbdb-address-editing-function' to an alternate
1126 address editing function if you don't like this function. It is
1127 mostly used for US style addresses.
1129 The sub-fields and the prompts used are:
1130 Street, line n: (nth n street)
1135 (let* ((str (let ((l) (s) (n 0))
1136 (while (not (string= "" (setq s (bbdb-read-string
1137 (format "Street, line %d: " (+ 1 n))
1138 (nth n (bbdb-address-streets addr))))))
1139 (setq l (append l (list s)))
1142 (zip (bbdb-error-retry
1143 (bbdb-parse-zip-string
1144 (bbdb-read-string "Zip Code: " (bbdb-address-zip-string addr)))))
1145 (cty (bbdb-read-string "City: " (bbdb-address-city addr)))
1147 (country (bbdb-read-string "Country: " (or (bbdb-address-country addr)
1148 bbdb-default-country))))
1149 (bbdb-address-set-streets addr str)
1150 (bbdb-address-set-city addr cty)
1151 (bbdb-address-set-state addr ste)
1152 (bbdb-address-set-zip addr zip)
1153 (if (string= "" (concat cty ste zip country (mapconcat 'identity str "")))
1154 ;; user didn't enter anything. this causes a display bug. this
1155 ;; is a temporary fix. Ideally, we'd simply discard the entire
1156 ;; address entry, but that's going to require bigger hacking.
1157 (bbdb-address-set-country addr "Emacs")
1158 (bbdb-address-set-country addr country))
1161 (defcustom bbdb-address-editing-function 'bbdb-address-edit-default
1162 "Function to use for address editing.
1163 The function must accept a BBDB address as parameter and allow the
1164 user to edit it. This variable is called from `bbdb-record-edit-address'.
1165 The default value is the symbol `bbdb-address-edit-default'."
1166 :group 'bbdb-record-creation
1169 (defun bbdb-record-edit-address (addr &optional location)
1170 "Edit an address ADDR.
1171 If optional parameter LOCATION is nil, edit the location sub-field
1172 of the address as well. The address itself is edited using the editing
1173 function in `bbdb-address-editing-function'."
1175 (or location (bbdb-read-string "Location: "
1176 (or (bbdb-address-location addr)
1177 (bbdb-label-completion-default
1179 (mapcar (function (lambda(x) (list x)))
1180 (bbdb-label-completion-list
1182 (bbdb-address-set-location addr loc))
1183 (if current-prefix-arg
1184 (bbdb-address-edit-default addr)
1185 (funcall bbdb-address-editing-function addr)))
1187 (defun bbdb-record-edit-phone (phone-number &optional location)
1188 (let ((newl (or location
1189 (bbdb-read-string "Location: "
1190 (or (bbdb-phone-location phone-number)
1191 (bbdb-label-completion-default
1193 (mapcar (function (lambda(x) (list x)))
1194 (bbdb-label-completion-list
1196 (newp (let ((bbdb-north-american-phone-numbers-p
1197 (= (length phone-number) bbdb-phone-length)))
1199 (bbdb-parse-phone-number
1200 (read-string "Phone: " (bbdb-phone-string phone-number)))))))
1201 (bbdb-phone-set-location phone-number newl)
1202 (bbdb-phone-set-area phone-number (nth 0 newp)) ; euronumbers too.
1203 (if (= (length phone-number) 2)
1205 (bbdb-phone-set-exchange phone-number (nth 1 newp))
1206 (bbdb-phone-set-suffix phone-number (nth 2 newp))
1207 (bbdb-phone-set-extension phone-number (or (nth 3 newp) 0))))
1210 (defun bbdb-record-edit-net (bbdb-record)
1211 (let ((str (bbdb-read-string "Net: "
1212 (mapconcat (function identity)
1213 (bbdb-record-net bbdb-record)
1215 (let ((oldnets (bbdb-record-net bbdb-record))
1216 (newnets (bbdb-split str ",")))
1217 ;; first check for any conflicts...
1218 (if bbdb-no-duplicates-p
1219 (let ((rest newnets))
1221 (let ((old (delete bbdb-record (bbdb-gethash (downcase (car rest))))))
1223 (error "net address \"%s\" is used by \"%s\""
1224 (car rest) (mapconcat (lambda (r) (bbdb-record-name r))
1226 (setq rest (cdr rest)))))
1228 (let ((rest oldnets))
1230 (bbdb-remhash (downcase (car rest)) bbdb-record)
1231 (setq rest (cdr rest))))
1232 (let ((nets newnets))
1234 (bbdb-puthash (downcase (car nets)) bbdb-record)
1235 (setq nets (cdr nets))))
1236 (bbdb-record-set-net bbdb-record newnets)))
1239 (defun bbdb-record-edit-aka (bbdb-record)
1240 (let ((str (bbdb-read-string "AKA: "
1241 (mapconcat (function identity)
1242 (bbdb-record-aka bbdb-record)
1244 (let ((oldaka (bbdb-record-aka bbdb-record))
1245 (newaka (bbdb-split str ";")))
1246 ;; first check for any conflicts...
1247 (if bbdb-no-duplicates-p
1248 (let ((rest newaka))
1250 (let ((old (bbdb-gethash (downcase (car rest)))))
1251 (if (and old (not (eq old bbdb-record)))
1252 (error "alternate name address \"%s\" is used by \"%s\""
1253 (car rest) (bbdb-record-name old))))
1254 (setq rest (cdr rest)))))
1256 (let ((rest oldaka))
1258 (bbdb-remhash (downcase (car rest)) bbdb-record)
1259 (setq rest (cdr rest))))
1262 (bbdb-puthash (downcase (car aka)) bbdb-record)
1263 (setq aka (cdr aka))))
1264 (bbdb-record-set-aka bbdb-record newaka)))
1268 (defun bbdb-record-edit-notes (bbdb-record &optional regrind)
1269 (interactive (list (bbdb-current-record t) t))
1270 (let ((notes (bbdb-read-string "Notes: " (bbdb-record-notes bbdb-record))))
1271 (bbdb-record-set-notes bbdb-record (if (string= "" notes) nil notes)))
1274 (set-buffer bbdb-buffer-name)
1275 (bbdb-redisplay-one-record bbdb-record)))
1279 (defun bbdb-record-edit-property (bbdb-record &optional prop regrind)
1280 (interactive (list (bbdb-current-record t) nil t))
1281 (let* ((propnames (bbdb-propnames))
1282 (propname (if prop (symbol-name prop)
1284 (format "Edit property of %s: "
1285 (bbdb-record-name bbdb-record))
1286 (cons '("notes") propnames))))
1287 (propsym (or prop (if (equal "" propname) 'notes (intern propname))))
1288 (string (bbdb-read-string (format "%s: " propname)
1289 (bbdb-record-getprop bbdb-record propsym))))
1290 (bbdb-record-putprop bbdb-record propsym
1291 (if (string= "" string) nil string)))
1294 (set-buffer bbdb-buffer-name)
1295 (bbdb-redisplay-one-record bbdb-record)))
1299 (defsubst bbdb-field-equal (x y)
1300 (if (and (consp x) (consp y))
1301 (and (eq (car x) (car y))
1302 (eq (car (cdr x)) (car (cdr y)))
1303 (eq (car (cdr (cdr x))) (car (cdr (cdr y)))))
1306 (defun bbdb-next-field (&optional count planning-on-modifying)
1307 (or count (setq count 1))
1309 (let* ((record (bbdb-current-record planning-on-modifying))
1310 (field (bbdb-current-field planning-on-modifying))
1311 (next-record record)
1313 (signum (if (< count 0) -1 1))
1315 (if (< count 0) (setq count (- count)))
1317 (while (and next-field (< i count))
1318 (while (bbdb-field-equal next-field field)
1319 (forward-line signum)
1320 (setq next-record (bbdb-current-record planning-on-modifying)
1321 next-field (bbdb-current-field planning-on-modifying))
1322 (or (eq next-record record)
1323 (setq next-field nil)))
1325 (setq field next-field)))
1329 (defun bbdb-transpose-fields (&optional arg)
1330 "This is like the `transpose-lines' command, but it is for BBDB fields.
1331 If the cursor is on a field of a BBDB record, that field and the previous
1332 field will be transposed.
1334 With argument ARG, takes previous line and moves it past ARG fields.
1335 With argument 0, interchanges field point is in with field mark is in.
1337 Both fields must be in the same record, and must be of the same basic type
1338 \(that is, you can use this command to change the order in which phone-number
1339 fields are listed, but you can't use it to make an address appear before a
1340 phone number; the order of field types is fixed.\)"
1342 (let ((record (bbdb-current-record t))
1343 moving-field position-after position-before
1346 (setq moving-field (or (bbdb-next-field -1 t)
1347 (error "no previous field"))
1348 position-after (bbdb-next-field arg t)
1349 position-before (bbdb-next-field (if (< arg 0) -1 1) t))
1350 ;; if arg is 0, swap fields at point and mark
1352 (setq position-after (bbdb-current-field))
1355 (setq moving-field (bbdb-current-field))
1356 (or (eq record (bbdb-current-record)) (error "not in the same record"))))
1358 (let ((x position-after))
1359 (setq position-after position-before
1362 (setq type (car moving-field))
1363 (or position-after position-before
1364 (error "that would be out of the record!"))
1365 (or (eq type (car position-after))
1366 (eq type (car position-before))
1367 (error "can't transpose fields of different types (%s and %s)"
1368 type (if (eq type (car position-after))
1369 (car position-before) (car position-after))))
1370 (or (eq type (car position-after)) (setq position-after nil))
1371 (or (eq type (car position-before)) (setq position-before nil))
1372 (setq moving-field (nth 1 moving-field)
1373 position-after (nth 1 position-after)
1374 position-before (nth 1 position-before))
1375 (cond ((memq type '(name aka net))
1376 (error "there is only one %s field, so you can't transpose it"
1378 ((memq type '(phone address property))
1379 (setq list (bbdb-record-get-field-internal record type)))
1380 (t (error "doubleplus ungood: unknown field %s" type)))
1384 (cond ((eq (car rest) moving-field) (setcar rest position-after))
1385 ((eq (car rest) position-after) (setcar rest moving-field)))
1386 (setq rest (cdr rest))))
1387 (if (eq position-before (car list))
1388 (setq list (cons moving-field (delq moving-field list)))
1390 (while (and rest (not (eq position-after (car rest))))
1391 (setq rest (cdr rest)))
1392 (or rest (error "doubleplus ungood: couldn't reorder list"))
1393 (let ((inhibit-quit t))
1394 (setq list (delq moving-field list))
1395 (setcdr rest (cons moving-field (cdr rest)))))))
1396 (bbdb-record-store-field-internal record type list)
1397 (bbdb-change-record record nil)
1398 (bbdb-redisplay-one-record record)))
1402 (defun bbdb-delete-current-field-or-record (&optional records noprompt)
1403 "Delete the line which the cursor is on; actually, delete the field which
1404 that line represents from the database. If the cursor is on the first line
1405 of a database entry (the name/company line) then the entire entry will be
1407 (interactive (list (if (bbdb-do-all-records-p)
1408 (mapcar 'car bbdb-records)
1409 (list (bbdb-current-record)))
1410 current-prefix-arg))
1411 (let* ((field (bbdb-current-field t))
1414 (name (cond ((null field) (error "on an unfield"))
1415 ((eq type 'property) (symbol-name (car (nth 1 field))))
1416 (t (symbol-name type)))))
1418 (setq record (car records))
1420 (bbdb-delete-current-record record noprompt)
1421 (if (not (or noprompt
1422 (bbdb-y-or-n-p (format "delete this %s field (of %s)? "
1424 (bbdb-record-name record)))))
1426 (cond ((memq type '(phone address))
1427 (bbdb-record-store-field-internal
1430 (bbdb-record-get-field-internal record type))))
1431 ((memq type '(net aka))
1432 (let ((rest (bbdb-record-get-field-internal record type)))
1434 (bbdb-remhash (downcase (car rest)) record)
1435 (setq rest (cdr rest))))
1436 (bbdb-record-store-field-internal record type nil))
1437 ((eq type 'property)
1438 (bbdb-record-putprop record (car (nth 1 field)) nil))
1439 (t (error "doubleplus ungood: unknown field type")))
1440 (bbdb-change-record record nil)
1441 (bbdb-redisplay-one-record record)))
1442 (setq records (cdr records)))))
1445 (defun bbdb-delete-current-record (recs &optional noprompt)
1446 "Delete the entire bbdb database entry which the cursor is within.
1447 Pressing \\<bbdb-mode-map>\\[bbdb-apply-next-command-to-all-records] will
1448 delete all records listed in the BBDB buffer."
1449 (interactive (list (if (bbdb-do-all-records-p)
1450 (mapcar 'car bbdb-records)
1451 (list (bbdb-current-record t)))
1452 current-prefix-arg))
1453 (if (not (listp recs))
1454 (setq recs (list recs)))
1456 (let ((r (car recs)))
1457 (setq recs (cdr recs))
1458 (bbdb-debug (if (bbdb-record-deleted-p r)
1459 (error "deleting deleted record")))
1461 (bbdb-y-or-n-p (format "delete the entire db entry of %s? "
1462 (or (bbdb-record-name r)
1463 (bbdb-record-company r)
1464 (car (bbdb-record-net r))))))
1465 (let* ((record-cons (assq r bbdb-records))
1466 (next-record-cons (car (cdr (memq record-cons
1468 (bbdb-debug (if (bbdb-record-deleted-p r)
1469 (error "deleting deleted record")))
1470 (bbdb-record-set-deleted-p r t)
1471 (bbdb-delete-record-internal r)
1472 (if (eq record-cons (car bbdb-records))
1473 (setq bbdb-records (cdr bbdb-records))
1474 (let ((rest bbdb-records))
1476 (if (eq record-cons (car (cdr rest)))
1478 (setcdr rest (cdr (cdr rest)))
1480 (setq rest (cdr rest)))))
1481 (bbdb-redisplay-one-record r record-cons next-record-cons t)
1482 (bbdb-with-db-buffer
1483 (setq bbdb-changed-records (delq r bbdb-changed-records)))
1484 ;; (bbdb-offer-save)
1487 (defun bbdb-change-records-state-and-redisplay (desired-state records)
1490 (setq rec (car records))
1491 (unless (eq desired-state (nth 1 rec))
1492 (setcar (cdr rec) desired-state)
1493 (bbdb-redisplay-one-record (car rec) rec))
1494 (setq records (cdr records)))))
1497 (defun bbdb-toggle-all-records-display-layout (arg &optional records)
1498 "Show all the fields of all visible records.
1499 Like `bbdb-toggle-records-display-layout' but for all visible records."
1502 (setq records bbdb-records))
1503 (let* ((record (bbdb-current-record))
1504 (cons (assq record bbdb-records))
1505 (current-state (nth 1 cons))
1507 (or (delete nil (mapcar (lambda (l)
1508 (if (and (assoc 'toggle l)
1509 (cdr (assoc 'toggle l)))
1511 bbdb-display-layout-alist))
1512 bbdb-display-layout-alist))
1513 (desired-state (assoc current-state layout-alist)))
1517 ((null current-state)
1519 ((null (cdr (memq desired-state layout-alist)))
1520 (caar layout-alist))
1522 (caadr (memq desired-state layout-alist)))))
1523 (message "Using %S layout" desired-state)
1524 (bbdb-change-records-state-and-redisplay desired-state records)))
1527 (defun bbdb-toggle-records-display-layout (arg)
1528 "Toggle whether the current record is displayed expanded or elided
1529 \(multi-line or one-line display.\) With a numeric argument of 0, the
1530 current record will unconditionally be made elided; with any other argument,
1531 the current record will unconditionally be shown expanded.
1533 If \"\\[bbdb-apply-next-command-to-all-records]\\[bbdb-toggle-records-display-layout]\" is \
1534 used instead of simply \"\\[bbdb-toggle-records-display-layout]\", then the state of all \
1536 be changed instead of just the one at point. In this case, an argument
1537 of 0 means that all records will unconditionally be made elided; any other
1538 numeric argument means that all of the records will unconditionally be shown
1539 expanded; and no numeric argument means that the records are made to be in
1540 the opposite state of the record under point."
1542 (bbdb-toggle-all-records-display-layout
1544 (if (not (bbdb-do-all-records-p))
1545 (list (assq (bbdb-current-record) bbdb-records)))))
1548 (defun bbdb-display-all-records-completely
1549 (arg &optional records)
1550 "Show all the fields of all currently displayed records.
1551 The display layout `full-multi-line' is used for this."
1554 (setq records bbdb-records))
1555 (let* ((record (bbdb-current-record))
1556 (cons (assq record bbdb-records))
1557 (current-state (nth 1 cons))
1559 (cond ((not (eq current-state 'full-multi-line))
1563 (bbdb-change-records-state-and-redisplay desired-state records)))
1566 (defun bbdb-display-record-completely (arg)
1567 "Show all the fields of the current record.
1568 The display layout `full-multi-line' is used for this."
1570 (bbdb-display-all-records-completely
1572 (if (not (bbdb-do-all-records-p))
1573 (list (assq (bbdb-current-record) bbdb-records)))))
1576 (defun bbdb-display-record-with-layout (layout &optional records)
1577 "Show all the fields of the current record using LAYOUT."
1578 (interactive (list (completing-read "Layout: "
1580 (list (symbol-name (car i))))
1581 bbdb-display-layout-alist))))
1582 (when (stringp layout)
1583 (setq layout (intern layout)))
1584 (when (null records)
1585 (setq records bbdb-records))
1586 (bbdb-change-records-state-and-redisplay layout records))
1589 (defun bbdb-omit-record (n)
1590 "Remove the current record from the display without deleting it from the
1591 database. With a prefix argument, omit the next N records. If negative,
1594 (while (not (= n 0))
1595 (if (< n 0) (bbdb-prev-record 1))
1596 (let* ((record (or (bbdb-current-record) (error "no records")))
1598 cons next prev-tail)
1600 (if (eq (car (car rest)) record)
1601 (setq cons (car rest)
1602 next (car (cdr rest))
1604 (setq prev-tail rest
1606 (or record (error "can't find current record"))
1607 (let ((buffer-read-only nil))
1608 (delete-region (nth 2 cons) (if next (nth 2 next) (point-max))))
1610 (setcdr prev-tail (cdr (cdr prev-tail)))
1611 (setq bbdb-records (cdr bbdb-records)))
1612 (setq n (if (> n 0) (1- n) (1+ n)))))
1613 (bbdb-frob-mode-line (length bbdb-records)))
1615 ;;; Fixing up bogus entries
1617 (defcustom bbdb-refile-notes-generate-alist '((creation-date . bbdb-refile-notes-string-least) (timestamp . bbdb-refile-notes-string-most))
1618 "*An alist defining specific merging function, based on notes field."
1619 :group 'bbdb-noticing-records
1620 :type '(repeat (cons
1621 (symbol :tag "Notes filed")
1622 (hook :tag "Generating function"))))
1624 (defcustom bbdb-refile-notes-default-merge-function 'bbdb-refile-notes-default-merge-function
1625 "*Default function to use for merging BBDB notes records.
1627 If the note field has an entry in `bbdb-refile-notes-generate-alist',
1628 that function will be used instead."
1629 :group 'bbdb-noticing-records
1633 (defun bbdb-refile-notes-default-merge-function (string1 string2)
1634 "Returns the concatenation of STRING1 and STRING2"
1635 (concat string1 "\n" string2))
1637 (defun bbdb-refile-notes-remove-duplicates (string1 string2)
1638 "Concatenate STRING1 and STRING2, but remove duplicate lines."
1639 (let ((note1 (split-string string1 "\n"))
1640 (note2 (split-string string2 "\n")))
1642 (if (not (member (car note2) note1))
1643 (setq note1 (cons (car note2) note1)))
1644 (setq note2 (cdr note2)))
1645 (mapconcat 'identity note1 "\n")))
1647 (defun bbdb-refile-notes-string-least (string1 string2)
1648 "Returns the string that is lessp."
1649 (if (string-lessp string1 string2)
1653 (defun bbdb-refile-notes-string-most (string1 string2)
1654 "Returns the string that is not lessp."
1655 (if (string-lessp string1 string2)
1659 (defun bbdb-merge-lists! (l1 l2 cmp &optional mod)
1660 "Merge two lists l1 l2 (modifies l1) only adds elements from l2
1661 if cmp returns false for all elements of l1. If optional mod
1662 is provided it is applied to each element of l1 and l2 prior to cmp"
1665 (let ((end (last l1))
1667 (chk (if mod (mapcar mod l1) (append l1 '()))))
1671 (val (if mod (apply mod (car src2) '()) (car src2))))
1673 (if (apply cmp (car src1) val '())
1676 (setq src1 (cdr src1))))
1678 (setcdr end (cons (car src2) '()))
1679 (setq end (cdr end)))
1680 (setq src2 (cdr src2))))
1683 (defun bbdb-merge-records (old-record new-record)
1684 "Merge the contents of old-record into new-record, old-record
1685 remains unchanged. For name and company it queries about which to use
1686 if they differ. All other fields are concatenated. Idealy this would
1687 be better about checking for duplicate entries in other fields, as
1688 well as possibly querying about differing values.
1690 This function does nothing to ensure the integrity of the rest of the
1691 database, that is somebody elses problem (something like
1692 `bbdb-refile-record')."
1693 (if (or (null new-record) (eq old-record new-record))
1694 (error "those are the same"))
1695 (let ((new-name (bbdb-record-name new-record))
1696 (new-co (bbdb-record-company new-record))
1697 (old-name (bbdb-record-name old-record))
1698 (old-co (bbdb-record-company old-record))
1699 (old-nets (bbdb-record-net old-record))
1700 (old-aka (bbdb-record-aka old-record))
1703 (cond ((= 0 (length old-name))
1704 (cons (bbdb-record-firstname new-record)
1705 (bbdb-record-lastname new-record)))
1706 ((= 0 (length new-name))
1707 (cons (bbdb-record-firstname old-record)
1708 (bbdb-record-lastname old-record)))
1709 ((string-equal (downcase old-name) (downcase new-name))
1710 (cons (bbdb-record-firstname new-record)
1711 (bbdb-record-lastname new-record)))
1714 (format "Use name \"%s\" instead of \"%s\"? "
1717 (setq extra-name new-record)
1718 (cons (bbdb-record-firstname old-record)
1719 (bbdb-record-lastname old-record)))
1720 (setq extra-name old-record)
1721 (cons (bbdb-record-firstname new-record)
1722 (bbdb-record-lastname new-record)))
1723 (or (and bbdb-use-alternate-names
1725 (format "Keep \"%s\" as an alternate name? "
1726 (bbdb-record-name extra-name))))
1727 (setq extra-name nil))))))
1728 (comp (cond ((= 0 (length old-co)) new-co)
1729 ((= 0 (length new-co)) old-co)
1730 ((string-equal old-co new-co) new-co)
1731 (t (if (bbdb-y-or-n-p
1732 (format "Use company \"%s\" instead of \"%s\"? "
1737 (setq old-aka (cons (bbdb-record-name extra-name) old-aka)))
1739 (bbdb-record-set-phones new-record
1741 (bbdb-record-phones new-record)
1742 (bbdb-record-phones old-record)
1744 (bbdb-record-set-addresses new-record
1746 (bbdb-record-addresses new-record)
1747 (bbdb-record-addresses old-record)
1749 (bbdb-record-set-company new-record comp)
1751 (let ((n1 (bbdb-record-raw-notes new-record))
1752 (n2 (bbdb-record-raw-notes old-record))
1756 (or (listp n1) (setq n1 (list (cons 'notes n1))))
1757 (or (listp n2) (setq n2 (list (cons 'notes n2))))
1759 (if (setq tmp (assq (car (car n2)) n1))
1762 (or (cdr (assq (car (car n2))
1763 bbdb-refile-notes-generate-alist))
1764 bbdb-refile-notes-default-merge-function)
1765 (cdr tmp) (cdr (car n2))))
1766 (setq n1 (nconc n1 (list (car n2)))))
1768 (bbdb-record-set-raw-notes new-record n1))))
1770 (bbdb-record-set-firstname new-record (car name))
1771 (bbdb-record-set-lastname new-record (cdr name))
1772 (bbdb-record-set-namecache new-record nil)
1774 (bbdb-record-set-net new-record
1776 (bbdb-record-net new-record) old-nets
1777 'string= 'downcase))
1778 (bbdb-record-set-aka new-record
1780 (bbdb-record-aka new-record) old-aka
1781 'string= 'downcase))
1785 (defun bbdb-refile-record (old-record new-record)
1786 "Merge the current record into some other record; that is, delete the
1787 record under point after copying all of the data within it into some other
1788 record. this is useful if you realize that somehow a redundant record has
1789 gotten into the database, and you want to merge it with another.
1791 If both records have names and/or companies, you are asked which to use.
1792 Phone numbers, addresses, and network addresses are simply concatenated.
1793 The first record is the record under the point; the second is prompted for.
1794 Completion behaviour is as dictated by the variable `bbdb-completion-type'."
1796 (let ((r (bbdb-current-record))
1798 (setq name (bbdb-record-name r))
1800 (if current-prefix-arg
1801 (car (delq r (bbdb-search (bbdb-records) name nil)))
1802 (bbdb-completing-read-one-record
1803 (format "merge record \"%s\" into: "
1804 (or (bbdb-record-name r) (car (bbdb-record-net r))
1805 "???")) (list r))))))
1807 (if (or (null new-record) (eq old-record new-record))
1808 (error "those are the same"))
1809 (setq new-record (bbdb-merge-records old-record new-record))
1811 (bbdb-delete-current-record old-record 'noprompt)
1812 (bbdb-change-record new-record t) ; don't always need-to-sort...
1813 (let ((bbdb-display-layout nil))
1814 (if (assq new-record bbdb-records)
1815 (bbdb-redisplay-one-record new-record))
1816 (bbdb-with-db-buffer
1817 (if (not (memq new-record bbdb-changed-records))
1818 (setq bbdb-changed-records
1819 (cons new-record bbdb-changed-records))))
1820 (if (null bbdb-records) ; nothing displayed, display something.
1821 (bbdb-display-records (list new-record))))
1822 (message "records merged."))
1825 (defcustom bbdb-notes-sort-order
1826 '((notes . 0) (www . 1) (ftp . 2) (gopher . 3) (telnet . 4) (mail-alias . 5)
1827 (mail-folder . 6) (lpr . 7) (creation-date . 1000) (timestamp . 1001))
1828 "*The order for sorting the notes.
1829 If a note is not in the alist, it is assigned weight 100, so all notes
1830 with weights less then 100 will be in the beginning, and all notes with
1831 weights more than 100 will be in the end."
1832 :group 'bbdb-noticing-records
1836 (defun bbdb-sort-notes (rec)
1837 "Sort the notes in the record according to `bbdb-notes-sort-order'.
1838 Can be used in `bbdb-change-hook'."
1839 (flet ((kk (nt) (or (cdr (assq (car nt) bbdb-notes-sort-order)) 100)))
1840 (bbdb-record-set-raw-notes
1841 rec (sort (bbdb-record-raw-notes rec)
1842 (lambda (aa bb) (< (kk aa) (kk bb)))))))
1845 (defun bbdb-sort-phones (rec)
1846 "Sort the phones in the record according to the location.
1847 Can be used in `bbdb-change-hook'."
1848 (bbdb-record-set-phones
1849 rec (sort (bbdb-record-phones rec)
1850 (lambda (xx yy) (string< (aref xx 0) (aref yy 0))))))
1853 (defun bbdb-sort-addresses (rec)
1854 "Sort the addresses in the record according to the location.
1855 Can be used in `bbdb-change-hook'."
1856 (bbdb-record-set-addresses
1857 rec (sort (bbdb-record-addresses rec)
1858 (lambda (xx yy) (string< (aref xx 0) (aref yy 0))))))
1861 ;;; Send-Mail interface
1863 (defcustom bbdb-dwim-net-address-allow-redundancy nil
1864 "*Non-nil means always use full name when sending mail, even if same as net."
1866 :type '(choice (const :tag "Disallow redundancy" nil)
1867 (const :tag "Return only the net" 'netonly)
1868 (const :tag "Allow redundancy" t)))
1871 (defun bbdb-dwim-net-address (record &optional net)
1872 "Returns a string to use as the email address of the given record. The
1873 given address is the address the mail is destined to; this is formatted like
1874 \"Firstname Lastname <addr>\" unless both the first name and last name are
1875 constituents of the address, as in John.Doe@SomeHost, or the address is
1876 already in the form \"Name <foo>\" or \"foo (Name)\", in which case the
1877 address is used as-is. If `bbdb-dwim-net-address-allow-redundancy' is non-nil,
1878 the name is always included. If `bbdb-dwim-net-address-allow-redundancy' is
1879 'netonly the name is never included!"
1880 (or net (setq net (car (bbdb-record-net record))))
1881 (or net (error "record unhas network addresses"))
1882 (let* ((override (bbdb-record-getprop record 'mail-name))
1883 (name (or override (bbdb-record-name record)))
1886 (let ((both (bbdb-divide-name override)))
1888 ln (car (cdr both)))
1889 (if (equal fn "") (setq fn nil))
1890 (if (equal ln "") (setq ln nil)))
1891 (setq fn (bbdb-record-firstname record)
1892 ln (bbdb-record-lastname record)))
1893 ;; if the name contains backslashes or double-quotes, backslash them.
1895 (while (setq i (string-match "[\\\"]" name i))
1896 (setq name (concat (substring name 0 i) "\\" (substring name i))
1898 (cond ((eq 'netonly bbdb-dwim-net-address-allow-redundancy)
1901 (if (not bbdb-dwim-net-address-allow-redundancy)
1904 (concat "\\`[^!@%]*\\b" (regexp-quote fn)
1905 "\\b[^!%@]+\\b" (regexp-quote ln) "\\b")
1908 (concat "\\`[^!@%]*\\b" (regexp-quote ln)
1909 "\\b[^!%@]+\\b" (regexp-quote fn) "\\b")
1913 (concat "\\`[^!@%]*\\b" (regexp-quote (or fn ln)) "\\b")
1915 ;; already in "foo <bar>" or "bar <foo>" format.
1916 (string-match "\\`[ \t]*[^<]+[ \t]*<" net)
1917 (string-match "\\`[ \t]*[^(]+[ \t]*(" net))
1919 ;; if the name contains control chars or RFC822 specials, it needs
1920 ;; to be enclosed in quotes. Double-quotes and backslashes have
1921 ;; already been escaped. This quotes a few extra characters as
1922 ;; well (!,%, and $) just for common sense.
1923 ((string-match "[][\000-\037\177()<>@,;:.!$%]" name)
1924 (format "\"%s\" <%s>" name net))
1926 (format "%s <%s>" name net)))))
1929 (defun bbdb-send-mail-internal (&optional to subj records)
1930 (let ((type (or bbdb-send-mail-style
1931 ;; In Emacs, `compose-mail' gets whatever you've
1932 ;; customized as your preferred `mail-user-agent'.
1933 (cond ((fboundp 'compose-mail) 'compose-mail)
1934 ((featurep 'mh-e) 'mh)
1935 ((featurep 'vm) 'vm)
1936 ((featurep 'message) 'message)
1937 ((featurep 'mew) 'mew)
1938 ((featurep 'gnus) 'gnus)
1942 (or (fboundp 'mh-send) (autoload 'mh-send "mh-e"))
1943 (mh-send to "" (or subj "")))
1945 (cond ((not (fboundp 'vm-mail-internal))
1946 (load-library "vm") ; 5.32 or later
1947 (or (fboundp 'vm-mail-internal)
1948 (load-library "vm-reply")))) ; 5.31 or earlier
1949 (vm-session-initialization)
1952 (vm-mail-internal nil to subj)
1953 (run-hooks 'vm-mail-hook)
1954 (run-hooks 'vm-mail-mode-hook)))
1956 (or (fboundp 'message-mail) (autoload 'message-mail "message"))
1957 (message-mail to subj))
1958 ((or (eq type 'mail) (eq type 'rmail))
1961 (or (fboundp 'mew-send) (load-library "mew"))
1962 (mew-send to nil subj))
1963 ((eq type 'compose-mail)
1964 (compose-mail to subj))
1966 (gnus-msg-mail to subj))
1968 (error "bbdb-send-mail-style must be vm, mh, message, compose-mail, or rmail")))))
1971 (defun bbdb-send-mail (bbdb-record &optional subject)
1972 "Compose a mail message to the person indicated by the current bbdb record.
1973 The first (most-recently-added) address is used if there are more than one.
1975 If \"\\[bbdb-apply-next-command-to-all-records]\\[bbdb-send-mail]\" is \
1976 used instead of simply \"\\[bbdb-send-mail]\", then mail will be sent to \
1978 folks listed in the *BBDB* buffer instead of just the person at point."
1979 (interactive (list (if (bbdb-do-all-records-p)
1980 (mapcar 'car bbdb-records)
1981 (bbdb-current-record))))
1982 (if (consp bbdb-record)
1983 (bbdb-send-mail-many bbdb-record subject)
1984 (bbdb-send-mail-1 bbdb-record subject)))
1987 (defun bbdb-send-mail-1 (bbdb-record &optional subject)
1988 (if bbdb-inside-electric-display
1989 (bbdb-electric-throw-to-execute
1990 (list 'bbdb-send-mail bbdb-record subject)))
1993 (cond ((null bbdb-record) (error "record unexists"))
1994 ((null (bbdb-record-net bbdb-record))
1995 (error "Current record unhas a network addresses."))
1996 (t (bbdb-send-mail-internal (bbdb-dwim-net-address bbdb-record)
1997 subject (list bbdb-record))
1998 (if (re-search-backward "^Subject: $" nil t) (end-of-line)))))
2001 (defun bbdb-send-mail-many (records &optional subject)
2002 (if bbdb-inside-electric-display
2003 (bbdb-electric-throw-to-execute
2004 (list 'bbdb-send-mail (list 'quote records) subject)))
2007 (let ((good '()) (bad '())
2010 (if (bbdb-record-net (car records))
2011 (setq good (cons (car records) good))
2012 (setq bad (cons (car records) bad)))
2013 (setq records (cdr records)))
2014 (bbdb-send-mail-internal
2015 (mapconcat (lambda (x) (bbdb-dwim-net-address x))
2016 (nreverse good) ",\n ")
2019 (goto-char (point-max))
2023 (insert "*** Warning: No net addresses for "
2024 (mapconcat (lambda (x) (bbdb-record-name x))
2025 (nreverse bad) ", ") ".")
2026 (fill-region-as-paragraph p (point))
2028 (if (re-search-backward "^Subject: $" nil t) (end-of-line)))
2031 (defun bbdb-yank-addresses ()
2032 "CC the people displayed in the *BBDB* buffer on this message.
2033 The primary net-address of each of the records currently listed in the
2034 *BBDB* buffer (whether it is visible or not) will be appended to the
2035 CC: field of the current buffer (assuming the current buffer is a mail
2036 composition buffer.)"
2038 (let ((addrs (save-excursion
2039 (set-buffer bbdb-buffer-name)
2042 (if (bbdb-record-net (car x))
2043 (bbdb-dwim-net-address (car x))
2046 (goto-char (point-min))
2047 ;; If there's a CC field, move to the end of it, inserting a comma if
2048 ;; there are already addresses present.
2049 ;; Otherwise, if there's an empty To: field, move to the end of it.
2050 ;; Otherwise, insert an empty CC: field.
2051 (if (re-search-forward "^CC:[ \t]*" nil t)
2055 (while (looking-at "\n[ \t]")
2056 (forward-char) (end-of-line))
2059 (re-search-forward "^To:[ \t]*")
2063 (while (looking-at "\n[ \t]")
2064 (forward-char) (end-of-line))
2070 (while (looking-at "\n[ \t]")
2071 (forward-char) (end-of-line))
2074 ;; Now insert each of the addresses on its own line.
2076 (insert (car addrs))
2077 (if (cdr addrs) (progn (insert ",\n") (indent-relative)))
2078 (setq addrs (cdr addrs)))))
2081 (defun bbdb-show-all-recipients ()
2082 "*Display BBDB records for all recipients of the message in this buffer."
2084 (let ((marker (bbdb-header-start))
2085 (fields '("from" "sender" "to" "cc" "bcc"
2086 "resent-from" "resent-to" "resent-cc" "resent-bcc"))
2088 (message "Searching...")
2090 (set-buffer (marker-buffer marker))
2093 (setq addrs (append (bbdb-split (or (bbdb-extract-field-value
2098 fields (cdr fields))))
2103 (setq record (bbdb-annotate-message-sender (car rest) t t t))
2104 (if record (setq records (cons record records)))
2105 (setq rest (cdr rest)))
2106 (message "Sorting...")
2107 (setq records (sort records (lambda (x y) (bbdb-record-lessp x y))))
2108 (bbdb-display-records records))))
2114 (defun bbdb-completion-check-record (sym rec)
2115 (let ((name (or (bbdb-record-name rec)
2116 (bbdb-record-company rec)
2118 (nets (bbdb-record-net rec))
2121 (if (null bbdb-completion-type)
2124 (if (memq bbdb-completion-type
2125 '(name primary-or-name name-or-primary))
2126 (setq ok (string= sym (downcase name))))
2128 ;; #### handle AKA, mail-name or mail-alias here?
2130 (when (eq bbdb-completion-type 'net)
2131 (while (and nets (not ok))
2132 (setq ok (string= sym (downcase (car nets)))
2134 (when (and nets (memq bbdb-completion-type
2135 '(primary primary-or-name name-or-primary)))
2136 (setq ok (string= sym (downcase (car nets)))))))
2141 (defun bbdb-completion-predicate (symbol)
2142 "For use as the third argument to `completing-read'.
2143 Obey the semantics of `bbdb-completion-type'."
2144 (cond ((null bbdb-completion-type)
2146 ((not (boundp symbol))
2149 (let ((sym (symbol-name symbol))
2150 (recs (symbol-value symbol))
2152 (while (and recs (not ok))
2153 (setq ok (bbdb-completion-check-record sym (car recs))
2157 (defun bbdb-completing-read-record (prompt &optional omit-records)
2158 "Prompt for and return a record from the bbdb.
2159 Completion is done according to `bbdb-completion-type'. If the user
2160 just hits return, nil is returned. Otherwise, a valid response is forced."
2161 (let* ((ht (bbdb-hashtable))
2162 (completion-ignore-case 't)
2163 (string (completing-read prompt ht 'bbdb-completion-predicate t))
2164 (symbol (and (not (= 0 (length string)))
2165 (intern-soft string ht))))
2167 (if (and (boundp symbol) (symbol-value symbol))
2168 (let ((recs (symbol-value symbol)) ret)
2170 (if (and (not (memq (car recs) omit-records))
2171 (bbdb-completion-check-record (symbol-name symbol)
2173 (setq ret (cons (car recs) ret)))
2174 (setq recs (cdr recs)))
2176 (error "selecting deleted (unhashed) record \"%s\"!" symbol))
2179 (defun bbdb-completing-read-one-record (prompt &optional omit-records)
2180 "Prompt for and return a single record from the bbdb;
2181 completion is done according to `bbdb-completion-type'. If the user
2182 just hits return, nil is returned. Otherwise, a valid response is forced.
2183 if omit-records is non-nil it should be a list of records to dis-allow
2185 (let ((records (bbdb-remove-memq-duplicates
2186 (bbdb-completing-read-record prompt omit-records))))
2188 ((eq (length records) 1)
2190 ((> (length records) 1)
2191 (let ((count (length records))
2193 (bbdb-display-records records)
2195 (setq prompts (cons (list (number-to-string count) count) prompts)
2198 (completing-read (format "Which duplicate record (1-%s): "
2201 (nth (1- (string-to-number result)) records)))
2205 (defvar bbdb-read-addresses-with-completion-map
2206 (let ((map (copy-keymap minibuffer-local-completion-map)))
2207 (define-key map " " 'self-insert-command)
2208 (define-key map "\t" 'bbdb-complete-name)
2209 (define-key map "\M-\t" 'bbdb-complete-name)
2213 (defun bbdb-read-addresses-with-completion (prompt &optional default)
2214 "Like `read-string', but allows `bbdb-complete-name' style completion."
2215 (read-from-minibuffer prompt default
2216 bbdb-read-addresses-with-completion-map))
2219 ;; Internal use. Store the window configuration before we pop up the
2220 ;; completion buffer.
2221 (defvar bbdb-complete-name-saved-window-config nil)
2223 ;; Restore the saved window configuration
2224 (defun bbdb-complete-name-cleanup ()
2225 (if bbdb-complete-name-saved-window-config
2227 (if (get-buffer-window "*Completions*")
2229 (set-window-configuration
2230 bbdb-complete-name-saved-window-config)
2231 (bury-buffer "*Completions*"))
2233 (setq bbdb-complete-name-saved-window-config nil))))
2235 (defvar bbdb-complete-name-callback-data nil
2236 "Stores the buffer and region start and end of the completed string.
2237 This is set in the *Completions* buffer.
2238 It is set in `bbdb-display-completion-list' and used in the advice
2239 `choose-completion-string'.")
2241 (make-variable-buffer-local 'bbdb-complete-name-callback-data)
2243 (defun bbdb-display-completion-list (list &optional callback data)
2244 "Wrapper for `display-completion-list'.
2245 GNU Emacs requires DATA to be in a specific format, viz. (nth 1 data) should
2246 be a marker for the start of the region being completed."
2247 ;; disgusting hack to make GNU Emacs nuke the bit you've typed
2248 ;; when it inserts the completion.
2249 (setq bbdb-complete-name-callback-data data)
2250 (if (featurep 'xemacs)
2251 (display-completion-list list :activate-callback callback
2253 (display-completion-list list)))
2255 (defadvice choose-completion-string (before bbdb-complete-fix activate)
2256 "Deletes the completed string before replacing.
2257 We need to do this as we are abusing completion and it was not meant to work
2258 in buffer other than the mini buffer."
2259 (when bbdb-complete-name-callback-data
2261 (set-buffer (car bbdb-complete-name-callback-data))
2262 (apply 'delete-region (cdr bbdb-complete-name-callback-data)))))
2264 (defun bbdb-complete-clicked-name (event extent user-data)
2265 "Find the record for a name clicked in a completion buffer.
2266 Currently only used by XEmacs."
2267 (let ((buffer (nth 0 user-data))
2268 (bbdb-complete-name-allow-cycling nil)
2269 (beg (nth 1 user-data))
2270 (end (nth 2 user-data)))
2271 (bbdb-complete-name-cleanup)
2274 (delete-region beg end)
2275 (insert (bbdb-extent-string extent))
2276 (bbdb-complete-name beg)))
2279 (defun bbdb-list-overlap (l1 l2)
2281 (while (and (not ok) l1)
2282 (if (memq (car l1) l2) (setq ok t l1 '())
2283 (setq l1 (cdr l1))))
2286 (defun bbdb-remove-assoc-duplicates (l)
2288 (if (assoc (car (car l)) (cdr l))
2289 (bbdb-remove-assoc-duplicates (cdr l))
2290 (cons (car l) (bbdb-remove-assoc-duplicates (cdr l))))))
2292 (defcustom bbdb-complete-name-allow-cycling nil
2293 "Whether to allow cycling of email addresses when calling
2294 `bbdb-complete-name' on a completed address in a composition buffer."
2295 :group 'bbdb-mua-specific
2298 (defcustom bbdb-complete-name-hooks nil
2299 "List of functions called after a sucessful completion."
2300 :group 'bbdb-mua-specific
2303 (eval-when-compile (defvar auto-fill-hook))
2306 (defun bbdb-complete-name (&optional start-pos)
2307 "Complete the user full-name or net-address before point (up to the
2308 preceeding newline, colon, or comma, or the value of START-POS). If
2309 what has been typed is unique, insert an entry of the form \"User Name
2310 <net-addr>\" (although see documentation for
2311 bbdb-dwim-net-address-allow-redundancy). If it is a valid completion
2312 but not unique, a list of completions is displayed.
2314 If the completion is done and `bbdb-complete-name-allow-cycling' is
2315 true then cycle through the nets for the matching record.
2317 When called with a prefix arg then display a list of all nets.
2319 Completion behaviour can be controlled with `bbdb-completion-type'."
2322 (let* ((end (point))
2325 (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*")
2326 (goto-char (match-end 0))
2328 (orig (buffer-substring beg end))
2329 (typed (downcase orig))
2330 (pattern (bbdb-string-trim typed))
2331 (ht (bbdb-hashtable))
2332 ;; make a list of possible completion strings
2333 ;; (all-the-completions), and a flag to indicate if there's a
2334 ;; single matching record or not (only-one-p)
2336 (all-the-completions nil)
2339 (when (bbdb-completion-predicate sym)
2343 ;; not sure about this. more than one record
2344 ;; attached to the symbol? does that happen?
2345 (> (length (symbol-value sym)) 1)
2346 ;; this is the doozy, though. multiple syms
2347 ;; which all match the same record
2348 (delete t (mapcar (lambda(x)
2349 (equal (symbol-value x)
2350 (symbol-value sym)))
2351 all-the-completions))))
2352 (setq only-one-p nil))
2353 (if (not (memq sym all-the-completions))
2354 (setq all-the-completions (cons sym all-the-completions))))))
2355 (completion (progn (all-completions pattern ht pred) (try-completion pattern ht)))
2356 (exact-match (eq completion t)))
2359 ;; No matches found OR you're trying completion on an
2360 ;; already-completed record. In the latter case, we might have to
2361 ;; cycle through the nets for that record.
2362 ((or (null completion)
2363 (and bbdb-complete-name-allow-cycling
2364 exact-match ;; which is a net of the record
2367 (car (symbol-value (intern-soft pattern ht)))))))
2368 ;; Clean up the completion buffer, if it exists
2369 (bbdb-complete-name-cleanup)
2370 ;; Check for cycling
2371 (or (catch 'bbdb-cycling-exit
2372 ;; jump straight out if we're not cycling
2373 (or bbdb-complete-name-allow-cycling
2374 (throw 'bbdb-cycling-exit nil))
2376 ;; find the record we're working on.
2377 (let* ((addr (funcall bbdb-extract-address-components-func orig))
2380 ;; for now, we're ignoring the case where this
2381 ;; returns more than one record. Ideally, the
2382 ;; last expansion would be stored in a
2383 ;; buffer-local variable, perhaps.
2384 (car (bbdb-search-intertwingle (caar addr)
2388 (throw 'bbdb-cycling-exit nil))
2390 (if current-prefix-arg
2391 ;; use completion buffer
2392 (let ((standard-output (get-buffer-create "*Completions*")))
2393 ;; a previously existing buffer has to be cleaned first
2394 (save-excursion (set-buffer standard-output)
2395 (setq buffer-read-only nil)
2397 (display-completion-list
2398 (mapcar (lambda (n) (bbdb-dwim-net-address rec n))
2399 (bbdb-record-net rec)))
2400 (delete-region beg end)
2401 (switch-to-buffer standard-output))
2403 (let* ((addrs (bbdb-record-net rec))
2404 (this-addr (or (cadr (member (car (cdar addr)) addrs))
2406 (if (= (length addrs) 1)
2407 ;; no alternatives. don't signal an error.
2408 (throw 'bbdb-cycling-exit t)
2409 ;; replace with new mail address
2410 (delete-region beg end)
2411 (insert (bbdb-dwim-net-address rec this-addr))
2412 (run-hooks 'bbdb-complete-name-hooks)
2413 (throw 'bbdb-cycling-exit t))))))
2416 ;; Check mail aliases
2417 (if (and bbdb-expand-mail-aliases (expand-abbrev))
2419 (when bbdb-complete-name-hooks
2420 (message "completion for \"%s\" unfound." pattern)
2421 (ding)))));; no matches, sorry!
2423 ;; Match for a single record. If cycling is enabled then we don't
2424 ;; care too much about the exact-match part.
2425 ((and only-one-p (or exact-match bbdb-complete-name-allow-cycling))
2426 (let* ((sym (if exact-match (intern-soft pattern ht) (car all-the-completions)))
2427 (recs (symbol-value sym))
2428 the-net match-recs lst primary matched)
2431 (when (bbdb-record-net (car recs))
2433 ;; Did we match on name?
2434 (let ((b-r-name (or (bbdb-record-name (car recs)) "")))
2435 (if (string= pattern
2436 (substring (downcase b-r-name) 0
2437 (min (length b-r-name)
2439 (setq match-recs (cons (car recs) match-recs)
2442 ;; Did we match on lastname?
2443 (let ((b-r-name (or (bbdb-record-lastname (car recs)) "")))
2444 (if (string= pattern
2445 (substring (downcase b-r-name) 0
2446 (min (length b-r-name)
2448 (setq match-recs (cons (car recs) match-recs)
2451 ;; Did we match on aka?
2453 (setq lst (bbdb-record-aka (car recs)))
2455 (if (string= pattern (substring (downcase (car lst)) 0
2456 (min (length (downcase
2460 (setq match-recs (append match-recs (list (car recs)))
2463 (setq lst (cdr lst)))))
2465 ;; Name didn't match name so check net matching
2467 (setq lst (bbdb-record-net (car recs)))
2468 (setq primary t) ;; primary wins over secondary...
2470 (if (string= pattern (substring (downcase (car lst))
2475 (setq the-net (car lst)
2478 (if primary (cons (car recs) match-recs)
2479 (append match-recs (list (car recs))))))
2484 (setq recs (cdr recs)
2488 (error "only exact matching record unhas net field"))
2490 ;; now replace the text with the expansion
2491 (delete-region beg end)
2492 (insert (bbdb-dwim-net-address (car match-recs) the-net))
2494 ;; if we're past fill-column, wrap at the previous comma.
2496 (bbdb-auto-fill-function)
2497 (>= (current-column) fill-column))
2504 (if (search-backward "," bol t)
2509 ;; Update the *BBDB* buffer if desired.
2510 (if bbdb-completion-display-record
2511 (let ((bbdb-gag-messages t))
2512 (bbdb-pop-up-bbdb-buffer)
2513 (bbdb-display-records-1 match-recs t)))
2514 (bbdb-complete-name-cleanup)
2516 ;; call the exact-completion hook
2517 (run-hooks 'bbdb-complete-name-hooks)))
2520 ;; note, we can't use the trimmed version of the pattern here or
2521 ;; we'll recurse infinitely on e.g. common first names
2522 ((and (stringp completion) (not (string= typed completion)))
2523 (delete-region beg end)
2527 (bbdb-complete-name-allow-cycling nil))
2528 (while (and (stringp completion)
2529 (not (string= completion last))
2530 (setq last completion
2531 pattern (downcase orig)
2532 completion (progn (all-completions pattern ht pred) (try-completion pattern ht))))
2533 (if (stringp completion)
2534 (progn (delete-region beg end)
2535 (insert completion))))
2536 (bbdb-complete-name beg)))
2538 ;; Exact match, but more than one record
2540 (or (eq (selected-window) (minibuffer-window))
2541 (message "Making completion list..."))
2543 (let (dwim-completions
2544 uniq nets net name akas)
2545 ;; Now collect all the dwim-addresses for each completion, but only
2546 ;; once for each record! Add it if the net is part of the completions
2551 (when (not (member rec uniq))
2552 (setq uniq (cons rec uniq)
2553 nets (bbdb-record-net rec)
2554 name (downcase (or (bbdb-record-name rec) ""))
2555 akas (mapcar 'downcase (bbdb-record-aka rec)))
2557 (setq net (car nets))
2560 ((and (member bbdb-completion-type
2561 '(primary primary-or-name))
2562 (member (intern-soft (downcase net) ht)
2563 all-the-completions))
2567 ((and name (member bbdb-completion-type
2568 '(nil name primary-or-name))
2569 (let ((cname (symbol-name sym)))
2570 (or (string= cname name)
2571 (member cname akas))))
2575 ((and (member bbdb-completion-type
2577 (member (intern-soft (downcase net) ht)
2578 all-the-completions)))
2579 ;; (name-or-)primary
2580 ((and (member bbdb-completion-type
2582 (let ((cname (symbol-name sym)))
2583 (or (string= cname name)
2584 (member cname akas))))
2588 (setq dwim-completions
2589 (cons (bbdb-dwim-net-address rec net)
2591 (if exact-match (setq nets nil)))
2592 (setq nets (cdr nets)))))
2593 (symbol-value sym)))
2594 all-the-completions)
2596 ;; if, after all that, we've only got one matching record...
2597 (if (and dwim-completions (null (cdr dwim-completions)))
2599 (delete-region beg end)
2600 (insert (car dwim-completions))
2602 ;; otherwise, pop up a completions window
2603 (if (not (get-buffer-window "*Completions*"))
2604 (setq bbdb-complete-name-saved-window-config
2605 (current-window-configuration)))
2606 (let ((arg (list (current-buffer)
2607 (set-marker (make-marker) beg)
2608 (set-marker (make-marker) end))))
2609 (with-output-to-temp-buffer "*Completions*"
2610 (bbdb-display-completion-list
2612 'bbdb-complete-clicked-name
2614 (or (eq (selected-window) (minibuffer-window))
2615 (message "Making completion list...done"))))))))
2619 "Insert the current contents of the *BBDB* buffer at point."
2621 (insert (let ((b (current-buffer)))
2622 (set-buffer bbdb-buffer-name)
2623 (prog1 (buffer-string) (set-buffer b)))))
2626 ;;; interface to mail-abbrevs.el.
2628 (defcustom bbdb-define-all-aliases-field 'mail-alias
2629 "*The field which `bbdb-define-all-aliases' searches for."
2633 (defcustom bbdb-define-all-aliases-mode 'first
2634 "*The type of alias which are created.
2635 first: Default is to generate an abbrev which is \"alias\" and expands to the
2637 star: Generate an extra alias \"<alias>*\" which expands to all nets of an
2639 all: Generate an alias all nets (as for 'star) and an alias for each net
2640 as \"<alias>n\" where n is the position of the net in the nets of the
2643 :type '(choice (symbol :tag "Only first" first)
2644 (symbol :tag "<alias>* for all nets" star)
2645 (symbol :tag "All aliases" all)))
2648 (defun bbdb-define-all-aliases ()
2649 "Define mail aliases for some of the records in the database.
2650 Every record which has a `mail-alias' field will have a mail alias
2651 defined for it which is the contents of that field. If there are
2652 multiple comma-separated words in the `mail-alias' field, then all
2653 of those words will be defined as aliases for that person.
2655 If multiple entries in the database have the same mail alias, then
2656 that alias expands to a comma-separated list of the network addresses
2657 of all of those people."
2659 (let* ((target (cons bbdb-define-all-aliases-field "."))
2660 (use-abbrev-p (fboundp 'define-mail-abbrev))
2661 (mail-alias-separator-string (if (boundp 'mail-alias-separator-string)
2662 mail-alias-separator-string
2664 (records (bbdb-search (bbdb-records) nil nil nil target))
2665 result record aliases match)
2669 ;; clear abbrev-table
2670 (setq mail-aliases nil)
2671 ;; arrange rebuilt if necessary, this should be done by
2672 ;; mail-pre-abbrev-expand-hook, but there is none!
2673 (defadvice sendmail-pre-abbrev-expand-hook
2674 (before bbdb-rebuilt-all-aliases activate)
2675 (bbdb-rebuilt-all-aliases)))
2677 ;; collect an alist of (alias rec1 [rec2 ...])
2679 (setq record (car records))
2680 (if (bbdb-record-net record)
2681 (setq aliases (bbdb-split
2682 (bbdb-record-getprop record
2683 bbdb-define-all-aliases-field)
2685 (if (not bbdb-silent-running)
2686 (bbdb-warn "record %S has no network address, but the aliases: %s"
2687 (bbdb-record-name record)
2688 (bbdb-record-getprop record
2689 bbdb-define-all-aliases-field)))
2693 (if (setq match (assoc (car aliases) result))
2694 (nconc match (cons record nil))
2695 (setq result (cons (list (car aliases) record) result)))
2696 (setq aliases (cdr aliases)))
2697 (setq records (cdr records)))
2699 ;; iterate over the results and create the aliases
2701 (let* ((aliasstem (caar result))
2702 (rec (cadar result))
2703 (group-alias-p (cddar result))
2704 (nets (if (not group-alias-p) (bbdb-record-net rec)))
2707 (mapcar (lambda (r) (bbdb-dwim-net-address r)) (cdar result))
2708 (mapcar (lambda (net) (bbdb-dwim-net-address rec net))
2709 (if (eq 'all bbdb-define-all-aliases-mode)
2711 (list (car nets))))))
2716 ;; for group aliases we just take all the primary nets and define
2717 ;; just one expansion!
2718 (setq expansions (list (mapconcat 'identity expansions
2719 mail-alias-separator-string)))
2720 ;; this is an alias for a single person so deal with it according to
2721 ;; the bbdb-define-all-aliases-mode
2722 (when (or (not (eq 'first bbdb-define-all-aliases-mode))
2724 (cons (mapconcat 'identity
2725 (mapcar (lambda (net)
2726 (bbdb-dwim-net-address
2729 mail-alias-separator-string)
2733 ;; create the aliases for each expansion
2735 (cond ((= count 0);; all the nets of a record
2736 (setq alias (concat aliasstem "*")))
2737 ((= count 1);; expansion as usual
2738 (setq alias aliasstem))
2739 (t;; alias# for each net of a record
2740 (setq alias (format "%s%s" aliasstem count))))
2741 (setq count (1+ count))
2742 (setq expansion (car expansions))
2745 (define-mail-abbrev alias expansion)
2746 (define-mail-alias alias expansion))
2747 (setq alias (or (intern-soft (downcase alias)
2749 mail-abbrevs mail-aliases))
2750 (error "couldn't find the alias we just defined!")))
2752 (or (eq (symbol-function alias) 'mail-abbrev-expand-hook)
2753 (error "mail-aliases contains unexpected hook %s"
2754 (symbol-function alias)))
2755 ;; The abbrev-hook is called with network addresses instead of bbdb
2756 ;; records to avoid keeping pointers to records, which would lose if
2757 ;; the database was reverted. It uses -search-simple to convert
2758 ;; these to records, which is plenty fast.
2759 (fset alias (list 'lambda '()
2760 (list 'bbdb-mail-abbrev-expand-hook
2764 (car (bbdb-record-net x)))
2765 (cdr (car result)))))))
2766 (setq expansions (cdr expansions))))
2767 (setq result (cdr result)))
2769 (when (not use-abbrev-p)
2770 (if (boundp 'mail-mode-header-syntax-table)
2771 (modify-syntax-entry ?* "w" mail-mode-header-syntax-table))
2772 (sendmail-pre-abbrev-expand-hook))))
2774 ;; We should be cleverer here and instead of rebuilding all aliases we should
2775 ;; just do what's necessary, i.e. remove deleted records and add new records
2776 (defun bbdb-rebuilt-all-aliases ()
2777 (let ((needs-rebuilt bbdb-define-all-aliases-needs-rebuilt))
2779 (if (not bbdb-silent-running)
2780 (message "Rebuilding aliases due to %s aliases." needs-rebuilt))
2781 (setq bbdb-define-all-aliases-needs-rebuilt nil)
2782 (bbdb-define-all-aliases))))
2784 (defcustom bbdb-mail-abbrev-expand-hook nil
2785 "*Hook or hooks invoked each time an alias is expanded.
2786 The hook is called with two arguments the alias and the list of records."
2790 (defun bbdb-mail-abbrev-expand-hook (alias records)
2791 (run-hook-with-args 'bbdb-mail-abbrev-expand-hook alias records)
2792 (mail-abbrev-expand-hook)
2793 (when bbdb-completion-display-record
2795 (bbdb-pop-up-bbdb-buffer))
2796 (let ((bbdb-gag-messages t))
2797 (bbdb-display-records-1
2798 (mapcar (lambda (x) (bbdb-search-simple nil x)) records)
2801 (defun bbdb-get-mail-aliases ()
2802 "Return a list of mail aliases used in the BBDB.
2803 The format is suitable for `completing-read'."
2804 (let* ((target (cons bbdb-define-all-aliases-field "."))
2805 (records (bbdb-search (bbdb-records) nil nil nil target))
2808 (setq aliases (bbdb-split
2809 (bbdb-record-getprop (car records)
2810 bbdb-define-all-aliases-field)
2813 (add-to-list 'result (list (car aliases)))
2814 (setq aliases (cdr aliases)))
2815 (setq records (cdr records)))
2819 (defun bbdb-add-or-remove-mail-alias (&optional records newalias delete)
2820 "Add NEWALIAS in all RECORDS or remove it if DELETE it t.
2821 When called with prefix argument it will remove the alias.
2822 We honor `bbdb-apply-next-command-to-all-records'!
2823 The new alias will only be added if it isn't there yet."
2824 (interactive (list (if (bbdb-do-all-records-p) 'all 'one)
2826 (format "%s mail alias: " (if current-prefix-arg "Remove" "Add"))
2827 (bbdb-get-mail-aliases))
2828 current-prefix-arg))
2829 (setq newalias (bbdb-string-trim newalias))
2830 (setq newalias (if (string= "" newalias) nil newalias))
2831 (let* ((propsym bbdb-define-all-aliases-field)
2832 (do-all-p (if (equal records 'one) nil t))
2833 (records (cond ((equal records 'all) (mapcar 'car bbdb-records))
2834 ((equal records 'one) (list (bbdb-current-record t)))
2837 (let* ((record (car records))
2838 (oldaliases (bbdb-record-getprop record propsym)))
2839 (if oldaliases (setq oldaliases (bbdb-split oldaliases ",")))
2840 (if delete (setq oldaliases (delete newalias oldaliases))
2841 (add-to-list 'oldaliases newalias))
2842 (setq oldaliases (bbdb-join oldaliases ", "))
2843 (bbdb-record-putprop record propsym oldaliases))
2844 (setq records (cdr records)))
2846 (bbdb-redisplay-records)
2847 (bbdb-redisplay-one-record (bbdb-current-record))))
2848 (setq bbdb-define-all-aliases-needs-rebuilt
2851 (if (bbdb-record-net (bbdb-current-record))
2855 ;;; Dialing numbers from BBDB
2856 (defcustom bbdb-dial-local-prefix-alist
2857 '(((if (integerp bbdb-default-area-code)
2858 (format "(%03d)" bbdb-default-area-code)
2859 (or bbdb-default-area-code ""))
2861 "Mapping to remove local prefixes from numbers.
2862 If this is non-nil, it should be an alist of
2863 (PREFIX REPLACEMENT) elements. The first part of a phone number
2864 matching the regexp returned by evaluating PREFIX will be replaced by
2865 the corresponding REPLACEMENT when dialing."
2866 :group 'bbdb-phone-dialing
2869 (defcustom bbdb-dial-local-prefix nil
2870 "Local prefix digits.
2871 If this is non-nil, it should be a string of digits which your phone
2872 system requires before making local calls (for example, if your phone system
2873 requires you to dial 9 before making outside calls.) In BBDB's
2874 opinion, you're dialing a local number if it starts with a 0 after
2875 processing bbdb-dial-local-prefix-alist."
2876 :group 'bbdb-phone-dialing
2877 :type '(choice (const :tag "No digits required" nil)
2878 (string :tag "Dial this first" "9")))
2880 (defcustom bbdb-dial-long-distance-prefix nil
2881 "Long distance prefix digits.
2882 If this is non-nil, it should be a string of digits which your phone
2883 system requires before making a long distance call (one not in your local
2884 area code). For example, in some areas you must dial 1 before an area
2885 code. Note that this is used to replace the + sign in phone numbers
2886 when dialling (international dialing prefix.)"
2887 :group 'bbdb-phone-dialing
2888 :type '(choice (const :tag "No digits required" nil)
2889 (string :tag "Dial this first" "1")))
2891 (defcustom bbdb-sound-player nil
2892 "The program to be used to play the sounds for the touch-tone digits."
2893 :group 'bbdb-phone-dialing
2894 :type '(choice (const :tag "No External Player" nil)
2895 (file :tag "Sound Player" "/usr/local/bin/play")))
2897 (defcustom bbdb-sound-files
2898 '["/usr/demo/SOUND/sounds/touchtone.0.au"
2899 "/usr/demo/SOUND/sounds/touchtone.1.au"
2900 "/usr/demo/SOUND/sounds/touchtone.2.au"
2901 "/usr/demo/SOUND/sounds/touchtone.3.au"
2902 "/usr/demo/SOUND/sounds/touchtone.4.au"
2903 "/usr/demo/SOUND/sounds/touchtone.5.au"
2904 "/usr/demo/SOUND/sounds/touchtone.6.au"
2905 "/usr/demo/SOUND/sounds/touchtone.7.au"
2906 "/usr/demo/SOUND/sounds/touchtone.8.au"
2907 "/usr/demo/SOUND/sounds/touchtone.9.au"
2908 "/usr/demo/SOUND/sounds/touchtone.pound.au"
2909 "/usr/demo/SOUND/sounds/touchtone.star.au"]
2910 "A vector of ten sound files to be used for dialing. They
2911 correspond to the 0, 1, 2, ... 9 digits, pound and star, respectively."
2912 :group 'bbdb-phone-dialing
2915 (defcustom bbdb-modem-dial nil
2916 "Type of dialing to use.
2917 If this value is nil, the audio device is used for dialing. Otherwise,
2918 this string is fed to the modem before the phone number digits."
2919 :group 'bbdb-phone-dialing
2920 :type '(choice (const :tag "audio" nil)
2921 (string :tag "tone dialing" "ATDT ")
2922 (string :tag "pulse dialing" "ATDP ")))
2924 (defcustom bbdb-modem-device "/dev/modem"
2925 "The name of the modem device.
2926 This is only used if bbdb-modem-dial is set to something other than nil."
2927 :group 'bbdb-phone-dialing
2930 (defcustom bbdb-sound-volume 50
2931 "The volume to play back dial tones at. The range is 0 to 100.
2932 This is only used if bbdb-modem-dial is set to nil."
2933 :group 'bbdb-phone-dialing
2936 (defun bbdb-play-sound (num &optional volume)
2937 "Play the specified touchtone number NUM at VOLUME.
2938 Uses external program `bbdb-sound-player' if set, otherwise
2939 try to use internal sound if available."
2940 (if (and (not bbdb-sound-player) (featurep 'native-sound))
2941 ;; This requires the sound files to be loaded via bbdb-xemacs.
2942 (funcall 'play-sound (intern (format "touchtone%d" num))
2944 (if (and (not (featurep 'xemacs))
2945 ;; We can't tell a priori if Emacs 21 facility will
2948 (play-sound (list 'sound
2949 :file (aref bbdb-sound-files
2950 (string-to-number num))
2951 :volume (or volume bbdb-sound-volume)))
2953 (if (and bbdb-sound-player
2954 (file-exists-p bbdb-sound-player))
2955 (call-process bbdb-sound-player nil nil nil
2956 (aref bbdb-sound-files num))
2957 (error "BBDB has no means of playing sound.")))))
2960 (if (fboundp 'next-event)
2961 (fset 'bbdb-next-event 'next-event)
2962 (fset 'bbdb-next-event 'read-event)))
2964 (defun bbdb-dial-number (phone-string)
2965 "Dial the number specified by PHONE-STRING.
2966 The number is dialed either by playing touchtones through the audio
2967 device using bbdb-sound-player, or by sending a dial sequence to
2968 bbdb-modem-device. # and * are dialed as-is, and a space is treated as
2969 a pause in the dial sequence."
2970 (interactive "sDial number: ")
2977 (cond ((eq ? d) ",")
2978 ((memq d '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?* ?#))
2983 (bbdb-play-sound 10))
2985 (bbdb-play-sound 11))
2987 ;; if we use sit-for, the user can interrupt!
2988 (sleep-for 1)) ;; configurable?
2989 ((memq d '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9))
2990 (bbdb-play-sound (- (char-int d) (char-int ?0))))
2991 (t)))) phone-string)
2993 ;; tell the user that we're dialed, if we're using the modem
2996 (insert bbdb-modem-dial dialed ";\r\n")
2997 (write-region (point-min) (point-max) bbdb-modem-device t)
2998 (message "%s dialed. Pick up the phone now and hit any key ..."
3003 (write-region (point-min) (point-max) bbdb-modem-device t)))))
3006 (defun bbdb-dial (phone force-area-code)
3007 "Dial the number at point.
3008 If the point is at the beginning of a record, dial the first
3009 phone number. Does not dial the extension. Does not apply the
3010 transformations from bbdb-dial-local-prefix-alist if a prefix arg
3012 (interactive (list (bbdb-current-field)
3013 current-prefix-arg))
3014 (if (eq (car-safe phone) 'name)
3015 (setq phone (car (bbdb-record-phones (bbdb-current-record)))))
3016 (if (eq (car-safe phone) 'phone)
3017 (setq phone (car (cdr phone))))
3018 (or (vectorp phone) (error "not on a phone field"))
3020 (let* ((number (bbdb-phone-string phone)) shortnumber)
3021 (when (not force-area-code)
3022 (let ((alist bbdb-dial-local-prefix-alist))
3024 (if (string-match (concat "^" (eval (caar alist))) number)
3025 (setq shortnumber (concat (car (cdar alist))
3026 (substring number (match-end 0)))
3028 (setq alist (cdr alist)))))
3030 ;; cut off the extension
3031 (if (string-match "x[0-9]+$" number)
3032 (setq number (substring number 0 (match-beginning 0))))
3034 ;; This is terrifically Americanized...
3035 ;; Leading 0 => local number (?)
3036 (if (and (not shortnumber) bbdb-dial-local-prefix
3037 (string-match "^0" number))
3038 (setq number (concat bbdb-dial-local-prefix number)))
3040 ;; Leading + => long distance/international number
3041 (if (and (not shortnumber) bbdb-dial-long-distance-prefix
3042 (string-match "^\+" number))
3043 (setq number (concat bbdb-dial-long-distance-prefix " "
3044 (substring number 1))))
3046 ;; use the short number if it's available
3047 (setq number (or shortnumber number))
3048 (if (not bbdb-silent-running)
3049 (message "Dialing %s" number))
3050 (bbdb-dial-number number)))
3053 ;; not sure what this is doing here...
3054 (defun bbdb-get-record (prompt)
3055 "Get the current record or ask the user.
3056 To be used in `interactive' like this:
3057 (interactive (list (bbdb-get-record \"look up ...\")))"
3058 (if (and (boundp 'bbdb-buffer-name)
3059 (string= bbdb-buffer-name (buffer-name)))
3060 (bbdb-current-record)
3063 (setq re (bbdb-completing-read-record (concat pr prompt)))
3064 (unless re (ding)) (setq pr "Invalid response! ")) re)))
3066 ;;; Finger, based on code by Sam Cramer <cramer@sun.com>.
3067 ;;; Note that process-death bugs in 18.57 may make this eat up all the cpu...
3069 (defcustom bbdb-finger-buffer-name "*finger*"
3070 "The buffer into which finger output should be directed."
3071 :group 'bbdb-utilities-finger
3074 (defun bbdb-finger-internal (address)
3075 (message "Fingering %s..." address)
3076 (condition-case condition
3077 (let* ((@ (string-match "@" address))
3078 (stream (open-network-stream
3079 "finger" bbdb-finger-buffer-name
3080 (if @ (substring address (1+ @)) "localhost")
3082 (set-process-sentinel stream 'bbdb-finger-process-sentinel)
3083 (princ (concat "finger " address "\n"))
3084 (process-send-string
3085 stream (concat;;"/W " ; cs.stanford.edu doesn't like this...
3086 (if @ (substring address 0 @) address) "\n"))
3087 (process-send-eof stream))
3089 (princ (format "error fingering %s: %s\n" address
3090 (if (stringp condition) condition
3091 (concat "\n" (nth 1 condition)
3092 (if (cdr (cdr condition)) ": ")
3093 (mapconcat '(lambda (x)
3095 (prin1-to-string x)))
3096 (cdr (cdr condition)) ", ")))))
3097 (bbdb-finger-process-sentinel nil nil)))) ; hackaroonie
3099 (defvar bbdb-remaining-addrs-to-finger)
3100 (defun bbdb-finger-process-sentinel (process s)
3102 (set-buffer bbdb-finger-buffer-name)
3103 (goto-char (point-min))
3104 (while (search-forward "\r" nil t)
3106 (if (and (boundp 'bbdb-remaining-addrs-to-finger)
3107 bbdb-remaining-addrs-to-finger)
3108 (let ((addr (car bbdb-remaining-addrs-to-finger)))
3109 (setq bbdb-remaining-addrs-to-finger
3110 (cdr bbdb-remaining-addrs-to-finger))
3111 (goto-char (point-max))
3112 (let ((standard-output (current-buffer)))
3114 (bbdb-finger-internal addr)))
3115 (goto-char (point-max))
3116 (message "Finger done."))))
3118 (defcustom bbdb-finger-host-field 'finger-host
3119 "*The field for special net addresses used by \"\\[bbdb-finger]\"."
3120 :group 'bbdb-utilities-finger
3123 (defun bbdb-record-finger-host (record)
3124 (let ((finger-host (and bbdb-finger-host-field
3125 (bbdb-record-getprop record bbdb-finger-host-field))))
3127 (bbdb-split finger-host ",")
3128 (bbdb-record-net record))))
3131 (defun bbdb-finger (record &optional which-address)
3132 "Finger the network address of a BBDB record.
3133 If this command is executed from the *BBDB* buffer, finger the network
3134 address of the record at point; otherwise, it prompts for a user.
3135 With a numeric prefix argument, finger the Nth network address of the
3136 current record\; with a prefix argument of ^U, finger all of them.
3137 The *finger* buffer is filled asynchronously, meaning that you don't
3138 have to wait around for it to finish\; but fingering another user before
3139 the first finger has finished could have unpredictable results.
3141 If this command is executed from the *BBDB* buffer, it may be prefixed
3142 with \"\\[bbdb-apply-next-command-to-all-records]\" \(as in \
3143 \"\\[bbdb-apply-next-command-to-all-records]\\[bbdb-finger]\" instead of \
3144 simply \"\\[bbdb-finger]\"\), meaning to finger all of
3145 the users currently listed in the *BBDB* buffer instead of just the one
3146 at point. The numeric prefix argument has the same interpretation.
3148 You can define a special network address to \"finger\" by defining a
3149 field `finger-host' (default value of `bbdb-finger-host-field')."
3150 (interactive (list (bbdb-get-record "BBDB Finger: ")
3151 current-prefix-arg))
3152 (if (not (consp record)) (setq record (list record)))
3155 (cond ((null which-address)
3158 (list (car (bbdb-record-finger-host (car record)))))))
3159 ((stringp which-address)
3160 (setq addrs (nconc addrs (list which-address))))
3161 ((numberp which-address)
3164 (list (nth which-address
3165 (bbdb-record-finger-host (car record)))))))
3169 (copy-sequence (bbdb-record-finger-host
3171 (setq record (cdr record)))
3174 (with-output-to-temp-buffer bbdb-finger-buffer-name
3175 (set-buffer bbdb-finger-buffer-name)
3176 (make-local-variable 'bbdb-remaining-addrs-to-finger)
3177 (setq bbdb-remaining-addrs-to-finger (cdr addrs))
3178 (bbdb-finger-internal (car addrs))))
3179 (error "Nothing to finger!"))))
3182 (defun bbdb-remove-duplicate-nets (records)
3183 "*Remove duplicate nets from a record."
3184 (interactive (if (bbdb-do-all-records-p)
3185 (mapcar 'car bbdb-records)
3186 (bbdb-current-record)))
3189 (setq nets (bbdb-record-net (car records))
3192 (add-to-list 'cnets (car nets))
3193 (setq nets (cdr nets)))
3194 (bbdb-record-set-net (car records) cnets)
3195 (setq records (cdr records)))))
3197 (defun bbdb-find-duplicates (&optional fields)
3198 "Find all records that have duplicate entries for given FIELDS.
3199 FIELDS should be a list of the symbols `name', `net', and/or `aka'.
3200 Note that overlap between these fields is noted if either is selected,
3201 most common case `aka' and `name'. If FIELDS is not given it
3202 defaults to all of them.
3204 The results of the search is returned as a list of records."
3205 (setq fields (or fields '(name net aka)))
3206 (let ((records (bbdb-records))
3209 (setq rec (car records))
3211 (when (and (memq 'name fields)
3212 (bbdb-record-name rec)
3213 (setq hash (bbdb-gethash (downcase (bbdb-record-name rec))))
3214 (> (length hash) 1))
3215 (setq ret (append hash ret))
3216 (message "BBDB record `%s' causes duplicates, maybe it is equal to a company name."
3217 (bbdb-record-name rec))
3220 (if (memq 'net fields)
3221 (let ((nets (bbdb-record-net rec)))
3223 (setq hash (bbdb-gethash (downcase (car nets))))
3224 (when (> (length hash) 1)
3225 (setq ret (append hash ret))
3226 (message "BBDB record `%s' has duplicate net `%s'."
3227 (bbdb-record-name rec) (car nets))
3229 (setq nets (cdr nets)))))
3231 (if (memq 'aka fields)
3232 (let ((aka (bbdb-record-aka rec)))
3234 (setq hash (bbdb-gethash (downcase (car aka))))
3235 (when (> (length hash) 1)
3236 (setq ret (append hash ret))
3237 (message "BBDB record `%s' has duplicate aka `%s'"
3238 (bbdb-record-name rec) (car aka))
3240 (setq aka (cdr aka)))))
3242 (setq records (cdr records)))
3243 (reverse (bbdb-remove-memq-duplicates ret))))
3245 (defun bbdb-show-duplicates (&optional fields)
3246 "*Find all records that have duplicate entries for given FIELDS.
3247 FIELDS should be a list of the symbols `name', `net', and/or `aka'.
3248 Note that overlap between these fields is noted if either is selected
3249 (most common case `aka' and `name'). If FIELDS is not given it
3250 defaults to all of them.
3252 The results are displayed in the bbdb buffer."
3254 (setq fields (or fields '(name net aka)))
3255 (bbdb-display-records (bbdb-find-duplicates fields)))
3257 ;;; Time-based functions
3258 (defun bbdb-kill-older (date &optional compare function)
3259 "*Apply FUNCTION to all records with timestamps older than DATE.
3260 The comparison is done with COMPARE. If FUNCTION is not specified, the
3261 selected records are deleted. If COMPARE is not specified,
3262 `string-lessp' is used.
3265 (bbdb-kill-older \"1997-01-01\")
3266 will delete all records with timestamps older than Jan 1 1997.
3268 Notes: 1. Records without timestamp fields will be ignored
3269 2. DATE must be in yyyy-mm-dd format."
3270 (interactive "sKill records with timestamp older than (yyyy-mm-dd): \n")
3271 (let ((records (bbdb-records)) timestamp
3272 (fun (or function 'bbdb-delete-record-internal))
3273 (cmp (or compare 'string-lessp)))
3275 (if (and (setq timestamp (bbdb-record-getprop (car records) 'timestamp))
3276 (funcall cmp timestamp date))
3277 (funcall fun (car records)))
3278 (setq records (cdr records)))))
3280 (defmacro bbdb-compare-records (cmpval field compare)
3281 "Builds a lambda comparison function that takes one argument, REC.
3283 (COMPARE VALUE CMPVAL)
3284 is true, where VALUE is the value of the FIELD field of REC."
3286 (let ((val (bbdb-record-getprop rec ,field)))
3287 (if (and val (,compare val ,cmpval))
3291 (defun bbdb-timestamp-older (date)
3292 "*Display records with timestamp older than DATE.
3293 DATE must be in yyyy-mm-dd format."
3294 (interactive "sOlder than date (yyyy-mm-dd): ")
3295 (bbdb-display-some (bbdb-compare-records date 'timestamp string<)))
3298 (defun bbdb-timestamp-newer (date)
3299 "*Display records with timestamp newer than DATE.
3300 DATE must be in yyyy-mm-dd format."
3301 (interactive "sNewer than date (yyyy-mm-dd): ")
3302 (bbdb-display-some (bbdb-compare-records date 'timestamp string>)))
3305 (defun bbdb-creation-older (date)
3306 "*Display records with creation-date older than DATE.
3307 DATE must be in yyyy-mm-dd format."
3308 (interactive "sOlder than date (yyyy-mm-dd): ")
3309 (bbdb-display-some (bbdb-compare-records date 'creation-date string<)))
3312 (defun bbdb-creation-newer (date)
3313 "*Display records with creation-date newer than DATE.
3314 DATE must be in yyyy-mm-dd format."
3315 (interactive "sNewer than date (yyyy-mm-dd): ")
3316 (bbdb-display-some (bbdb-compare-records date 'creation-date string>)))
3319 (defun bbdb-creation-no-change ()
3320 "*Display records that have the same timestamp and creation-date."
3323 (bbdb-compare-records (bbdb-record-getprop rec 'timestamp)
3324 'creation-date string=)))
3326 ;;; Help and documentation
3328 (defcustom bbdb-info-file nil
3329 "*Set this to the location of the bbdb info file, if it's not in the
3332 :type '(choice (const :tag "Standard location" nil)
3333 (file :tag "New location")))
3339 (if bbdb-inside-electric-display
3340 (bbdb-electric-throw-to-execute '(bbdb-info))
3341 (let ((file (or bbdb-info-file "bbdb")))
3342 (Info-goto-node (format "(%s)Top" file)))))
3347 (message (substitute-command-keys "\\<bbdb-mode-map>\
3348 new field: \\[bbdb-insert-new-field]; \
3349 edit field: \\[bbdb-edit-current-field]; \
3350 delete field: \\[bbdb-delete-current-field-or-record]; \
3351 mode help: \\[describe-mode]; \
3352 info: \\[bbdb-info]")))
3355 (or (fboundp 'member);; v18 lossage
3356 (defun member (item list)
3357 (while (and list (not (equal item (car list)))) (setq list (cdr list)))
3361 ;;; If Sebastian Kremer's minibuffer history package is around, use it.
3362 (if (and (fboundp 'gmhist-make-magic)
3363 (string-lessp emacs-version "19")) ; v19 has history built in
3364 (mapcar 'gmhist-make-magic
3365 '(bbdb bbdb-name bbdb-company bbdb-net bbdb-changed)))
3368 (defcustom bbdb-update-records-mode 'annotating
3369 "Controls how `bbdb-update-records' processes email addresses.
3370 Set this to an expression which evaluates either to 'searching or
3371 'annotating. When set to 'annotating email addresses will be fed to
3372 `bbdb-annotate-message-sender' in order to update existing records or create
3373 new ones. A value of 'searching will search just for existing records having
3376 There is a version of this variable for each MUA, which overrides this variable
3379 This variable is also used for inter-function communication between the
3380 functions `bbdb-update-records' and `bbdb-prompt-for-create'."
3381 :group 'bbdb-mua-specific
3382 :type '(choice (const :tag "annotating all messages"
3384 (const :tag "annotating no messages"
3386 (sexp :tag "user defined")))
3388 (defvar bbdb-offer-to-create nil
3389 "Used for inter-function communication between the functions
3390 `bbdb-update-records' and `bbdb-prompt-for-create'.")
3391 (defvar bbdb-address nil
3392 "Used for inter-function communication between the functions
3393 `bbdb-update-records' and `bbdb-prompt-for-create'.")
3395 (defvar bbdb-update-address-class nil
3396 "Class of currently processed address as in `bbdb-get-addresses-headers'.
3397 The `bbdb-notice-hook' and `bbdb-create-hook' functions may utilize this to
3398 treat updates in the right way.")
3400 (defvar bbdb-update-address-header nil
3401 "Header the currently processed address was extracted from.
3402 The `bbdb-notice-hook' and `bbdb-create-hook' functions may utilize this to
3403 treat updates in the right way.")
3406 (defun bbdb-update-records (addrs auto-create-p offer-to-create)
3407 "Returns the records corresponding to the list of addresses ADDRS,
3408 creating or modifying them as necessary. A record will be created if
3409 AUTO-CREATE-P is non-nil or if OFFER-TO-CREATE is true and the user
3410 confirms the creation.
3412 The variable `bbdb/gnus-update-records-mode' controls what actions
3413 are performed and it might override `bbdb-update-records-mode'.
3415 When hitting C-g once you will not be asked any more for new people listed
3416 in this message, but it will search only for existing records. When hitting
3417 C-g again it will stop scanning."
3418 (setq auto-create-p (bbdb-invoke-hook-for-value auto-create-p))
3420 (let ((bbdb-records (bbdb-records))
3421 (processed-addresses 0)
3422 (bbdb-offer-to-create (or offer-to-create (eq 'prompt auto-create-p)))
3423 (bbdb-update-records-mode
3424 (if offer-to-create 'annotating
3425 (if (listp bbdb-update-records-mode)
3426 (eval bbdb-update-records-mode)
3427 bbdb-update-records-mode)))
3428 (addrslen (length addrs))
3429 (bbdb-update-address-class nil)
3430 (bbdb-update-address-header nil)
3435 (setq bbdb-address (car addrs)
3436 bbdb-update-address-class (car bbdb-address)
3437 bbdb-update-address-header (cadr bbdb-address)
3438 bbdb-address (caddr bbdb-address))
3443 (cond ((null (cadr bbdb-address))
3444 ;; ignore emtpy addrs, e.g. (??? nil)
3446 ((eq bbdb-update-records-mode 'annotating)
3447 (list;; search might return a list
3448 (bbdb-annotate-message-sender
3450 (or offer-to-create;; force create
3452 'bbdb-prompt-for-create)))
3453 ((eq bbdb-update-records-mode 'searching)
3454 ;; search for records having this net
3455 (let ((net (concat "^"
3457 (cadr bbdb-address))
3459 ;; there is no case for nets
3460 (bbdb-case-fold-search t))
3461 (bbdb-search bbdb-records nil nil net))))
3462 processed-addresses (+ processed-addresses 1))
3464 (when (and (not bbdb-silent-running)
3465 (not bbdb-gag-messages)
3466 (not (eq bbdb-offer-to-create 'quit))
3467 (= 0 (% processed-addresses 5)))
3468 (let ((mess (format "Hit C-g to stop BBDB from %s. %d of %d addresses processed."
3469 bbdb-update-records-mode processed-addresses addrslen)))
3470 (if (featurep 'xemacs)
3471 (bbdb-display-message 'progress mess)
3475 ;; o.k. there was a quit signal so how should we proceed now?
3476 (quit (cond ((eq bbdb-update-records-mode 'annotating)
3477 (setq bbdb-update-records-mode 'searching))
3478 ((eq bbdb-update-records-mode 'searching)
3480 ((eq bbdb-update-records-mode 'next)
3481 (setq bbdb-update-records-mode 'annotating))
3483 (setq bbdb-update-records-mode 'quit)))
3487 ;; people should be listed only once so we use add-to-list
3488 (if (car hits) (add-to-list 'records (car hits)))
3489 (setq hits (cdr hits)))
3491 (setq addrs (cdr addrs)))
3493 ;; add-to-list adds at the front so we have to reverse the list in order
3494 ;; to reflect the order of the records as they appear in the headers.
3495 (setq records (nreverse records))
3499 (defun bbdb-get-help-window (message)
3500 "Display MESSAGE in a new window which is the last one in the current frame."
3501 (let ((b (get-buffer-create " *BBDB Help*"))
3502 (w (or (get-buffer-window " *BBDB Help*")
3504 (lines (let ((l 2) (s 0))
3505 (while (setq s (string-match "\n" message s))
3506 (setq s (1+ s) l (1+ l)))
3509 (setq w (split-window w))
3511 (switch-to-buffer b)
3514 (goto-char (point-min))
3515 (let ((window-min-height 1))
3516 (enlarge-window (- lines (window-height w))))
3519 (defun bbdb-kill-help-window (window)
3520 "Kill the buffer corresponding to WINDOW and delete the WINDOW."
3521 (kill-buffer (window-buffer window))
3522 (delete-window window))
3524 ;; This is a hack. The function is called by bbdb-annotate-message-sender and
3525 ;; uses the above variable in order to manipulate bbdb-update-records.
3526 ;; Some cases are handled with signals in order to keep the changes in
3527 ;; bbdb-annotate-message-sender as minimal as possible.
3529 (defun bbdb-prompt-for-create ()
3530 "This function is used by `bbdb-update-records' to ask the user how to
3531 proceed the processing of records.
3533 It is called from `bbdb-annotate-message-sender' (PROMPT-FOR-CREATE arg) and
3534 returns `t' if the record should be created or `nil' otherwise. It honors a
3535 previous answer, e.g. \"!\" add all ..."
3536 (let ((old-offer-to-create bbdb-offer-to-create)
3538 (when bbdb-offer-to-create
3539 (when (not (integerp bbdb-offer-to-create))
3540 (setq prompt (format "%s is not in the db; add? (y,!,n,s,q,?) "
3541 (or (car bbdb-address) (cadr bbdb-address))))
3543 (setq event (read-key-sequence prompt))
3544 (if (featurep 'xemacs)
3545 (setq event (bbdb-event-to-character (aref event 0)))
3546 (setq event (if (stringp event) (aref event 0)))))
3548 (setq bbdb-offer-to-create event))
3549 (message "");; clear the message buffer
3551 (cond ((eq bbdb-offer-to-create ?y)
3552 (setq bbdb-offer-to-create old-offer-to-create)
3554 ((eq bbdb-offer-to-create ?!)
3556 ((or (eq bbdb-offer-to-create ?n)
3557 (eq bbdb-offer-to-create ? ))
3558 (setq bbdb-update-records-mode 'next
3559 bbdb-offer-to-create old-offer-to-create)
3561 ((eq bbdb-offer-to-create ?q)
3562 (setq bbdb-update-records-mode 'quit)
3564 ((eq bbdb-offer-to-create ?s)
3565 (setq bbdb-update-records-mode 'searching)
3568 (let ((w (bbdb-get-help-window
3569 "Your answer controls how BBDB updates/searches for records.
3571 Type ? for this help.
3572 Type y to add the current record.
3573 Type ! to add all remaining records.
3574 Type n to skip the current record. (You might also type space)
3575 Type s to switch from annotate to search mode.
3576 Type q to quit updating records. No more search or annotation is done.")))
3577 (setq bbdb-offer-to-create nil)
3578 (condition-case error
3579 (progn (bbdb-prompt-for-create)
3580 (bbdb-kill-help-window w))
3582 (bbdb-kill-help-window w)
3583 (apply 'signal error)))))))))
3586 (defcustom bbdb-get-addresses-headers
3587 '((authors . ("From" "Resent-From" "Reply-To"))
3588 (recipients . ("Resent-To" "Resent-CC" "To" "CC" "BCC")))
3589 "*List of headers to search for senders and recipients email addresses.
3590 The headers are grouped into two classes, the authors and the senders headers."
3591 :group 'bbdb-mua-specific
3595 (defcustom bbdb-get-only-first-address-p
3597 "*If t `bbdb-update-records' will return only the first one.
3598 Changing this variable will show its effect only after clearing the
3599 `bbdb-message-cache' of a folder or closing and visiting it again."
3600 :group 'bbdb-mua-specific
3603 (defun bbdb-get-addresses (only-first-address
3604 uninteresting-senders
3605 get-header-content-function
3606 &rest get-header-content-function-args)
3608 (let ((headers bbdb-get-addresses-headers)
3609 (ignore-senders (or bbdb-user-mail-names uninteresting-senders))
3610 addrlist adlist fn ad
3611 header-type header-fields header-content)
3613 (setq header-type (caar headers)
3614 header-fields (cdar headers))
3615 (while header-fields
3616 (setq header-content (apply get-header-content-function
3618 get-header-content-function-args))
3619 (when header-content
3620 (setq adlist (funcall bbdb-extract-address-components-func
3623 (setq fn (caar adlist)
3624 ad (car (cdar adlist)))
3626 ;; ignore uninteresting addresses, this is kinda gross!
3627 (if (or (not (stringp ignore-senders))
3628 (not (or (and fn (string-match ignore-senders fn))
3629 (and ad (string-match ignore-senders ad)))))
3630 (add-to-list 'addrlist
3635 (if (and only-first-address addrlist)
3636 (setq adlist nil headers nil)
3637 (setq adlist (cdr adlist)))))
3638 (setq header-fields (cdr header-fields)))
3639 (setq headers (cdr headers)))
3640 (nreverse addrlist)))