1 ;;; bbdb-com.el --- user-level commands of BBDB -*- lexical-binding: t -*-
3 ;; Copyright (C) 2010-2017 Free Software Foundation, Inc.
5 ;; This file is part of the Insidious Big Brother Database (aka BBDB),
7 ;; BBDB is free software: you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation, either version 3 of the License, or
10 ;; (at your option) any later version.
12 ;; BBDB is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with BBDB. If not, see <http://www.gnu.org/licenses/>.
21 ;; This file contains most of the user-level interactive commands for BBDB.
22 ;; See the BBDB info manual for documentation.
30 (autoload 'build-mail-aliases
"mailalias")
31 (autoload 'browse-url-url-at-point
"browse-url"))
34 (defvar bbdb-crm-local-completion-map
35 (let ((map (make-sparse-keymap)))
36 (set-keymap-parent map crm-local-completion-map
)
37 (define-key map
" " 'self-insert-command
)
39 "Keymap used for BBDB crm completions.")
41 (defun bbdb-get-records (prompt)
42 "If inside the *BBDB* buffer get the current records.
43 In other buffers ask the user."
44 (if (string= bbdb-buffer-name
(buffer-name))
46 (bbdb-completing-read-records prompt
)))
48 ;; Note about the arg RECORDS of various BBDB commands:
49 ;; - Usually, RECORDS is a list of records. (Interactively,
50 ;; this list of records is set up by `bbdb-do-records'.)
51 ;; - If these commands are used, e.g., in `bbdb-create-hook' or
52 ;; `bbdb-change-hook', they will be called with one arg, a single record.
53 ;; So depending on context the value of RECORDS will be a single record
54 ;; or a list of records, and we want to handle both cases.
55 ;; So we pass RECORDS to `bbdb-record-list' to handle both cases.
56 (defun bbdb-record-list (records &optional full
)
57 "Ensure that RECORDS is a list of records.
58 If RECORDS is a single record turn it into a list.
59 If FULL is non-nil, assume that RECORDS include display information."
62 (if (vectorp (car records
)) (list records
) records
)
63 (if (vectorp records
) (list records
) records
))))
65 ;; Note about BBDB prefix commands:
66 ;; `bbdb-do-all-records', `bbdb-append-display' and `bbdb-search-invert'
67 ;; are fake prefix commands. They need not precede the main commands.
68 ;; Also, `bbdb-append-display' can act on multiple commands.
70 (defun bbdb-prefix-message ()
71 "Display a message about selected BBDB prefix commands."
72 (let ((msg (bbdb-concat " " (elt bbdb-modeline-info
1)
73 (elt bbdb-modeline-info
3)
74 (elt bbdb-modeline-info
5))))
75 (unless (string= "" msg
) (message "%s" msg
))))
78 (defun bbdb-do-all-records (&optional arg
)
79 "Command prefix for operating on all records currently displayed.
80 With prefix ARG a positive number, operate on all records.
81 With prefix ARG a negative number, operate on current record only.
82 This only works for certain commands."
84 (setq bbdb-do-all-records
85 (or (and (numberp arg
) (< 0 arg
))
86 (and (not (numberp arg
)) (not bbdb-do-all-records
))))
87 (aset bbdb-modeline-info
4 (if bbdb-do-all-records
"all"))
88 (aset bbdb-modeline-info
5
89 (if bbdb-do-all-records
90 (substitute-command-keys
91 "\\<bbdb-mode-map>\\[bbdb-do-all-records]")))
92 (bbdb-prefix-message))
95 (defun bbdb-do-records (&optional full
)
96 "Return list of records to operate on.
97 Normally this list includes only the current record.
98 It includes all currently displayed records if the command prefix \
99 \\<bbdb-mode-map>\\[bbdb-do-all-records] is used.
100 If FULL is non-nil, the list of records includes display information."
101 (if bbdb-do-all-records
103 (setq bbdb-do-all-records nil
)
104 (aset bbdb-modeline-info
4 nil
)
105 (aset bbdb-modeline-info
5 nil
)
106 (if full bbdb-records
(mapcar 'car bbdb-records
)))
107 (list (bbdb-current-record full
))))
110 (defun bbdb-append-display-p ()
111 "Return variable `bbdb-append-display' and reset."
112 (let ((job (cond ((eq t bbdb-append-display
))
113 ((numberp bbdb-append-display
)
114 (setq bbdb-append-display
(1- bbdb-append-display
))
115 (if (zerop bbdb-append-display
)
116 (setq bbdb-append-display nil
))
119 (setq bbdb-append-display nil
)
121 (cond ((numberp bbdb-append-display
)
122 (aset bbdb-modeline-info
0
123 (format "(add %dx)" bbdb-append-display
)))
124 ((not bbdb-append-display
)
125 (aset bbdb-modeline-info
0 nil
)
126 (aset bbdb-modeline-info
1 nil
)))
130 (defun bbdb-append-display (&optional arg
)
131 "Toggle appending next searched records in the *BBDB* buffer.
132 With prefix ARG \\[universal-argument] always append.
133 With ARG a positive number append for that many times.
134 With ARG a negative number do not append."
136 (setq bbdb-append-display
137 (cond ((and arg
(listp arg
)) t
)
138 ((and (numberp arg
) (< 1 arg
)) arg
)
139 ((or (and (numberp arg
) (< arg
0)) bbdb-append-display
) nil
)
141 (aset bbdb-modeline-info
0
142 (cond ((numberp bbdb-append-display
)
143 (format "(add %dx)" bbdb-append-display
))
144 ((eq t bbdb-append-display
) "Add")
145 (bbdb-append-display "add")
147 (aset bbdb-modeline-info
1
148 (if bbdb-append-display
149 (substitute-command-keys
150 "\\<bbdb-mode-map>\\[bbdb-append-display]")))
151 (bbdb-prefix-message))
153 (defsubst bbdb-layout-prefix
()
154 "Set the LAYOUT arg interactively using the prefix arg."
155 (cond ((eq current-prefix-arg
0) 'one-line
)
156 (current-prefix-arg 'multi-line
)
159 (defun bbdb-search-invert-p ()
160 "Return variable `bbdb-search-invert' and set it to nil.
161 To set it again, use command `bbdb-search-invert'."
162 (let ((result bbdb-search-invert
))
163 (setq bbdb-search-invert nil
)
164 (aset bbdb-modeline-info
2 nil
)
165 (aset bbdb-modeline-info
3 nil
)
169 (defun bbdb-search-invert (&optional arg
)
170 "Toggle inversion of the next search command.
171 With prefix ARG a positive number, invert next search.
172 With prefix ARG a negative number, do not invert next search."
174 (setq bbdb-search-invert
175 (or (and (numberp arg
) (< 0 arg
))
176 (and (not (numberp arg
)) (not bbdb-search-invert
))))
177 (aset bbdb-modeline-info
2 (if bbdb-search-invert
"inv"))
178 (aset bbdb-modeline-info
3 (if bbdb-search-invert
179 (substitute-command-keys
180 "\\<bbdb-mode-map>\\[bbdb-search-invert]")))
181 (bbdb-prefix-message))
183 (defmacro bbdb-search
(records &rest spec
)
184 "Search RECORDS for fields matching SPEC.
185 The following keywords are supported in SPEC to search fields in RECORDS
186 matching the regexps RE:
188 :name RE Match RE against first-last name.
189 :name-fl RE Match RE against last-first name.
190 :all-names RE Match RE against first-last, last-first, and aka.
191 :affix RE Match RE against affixes.
192 :aka RE Match RE against akas.
193 :organization RE Match RE against organizations.
194 :mail RE Match RE against mail addresses.
195 :xfield RE Match RE against `bbdb-default-xfield'.
196 RE may also be a cons (LABEL . REGEXP).
197 Then REGEXP is matched against xfield LABEL.
198 If LABEL is '* then RE is matched against all xfields.
199 :creation-date RE Match RE against creation-date.
200 :timestamp RE Match RE against timestamp.
202 Each of these keywords may appear multiple times.
205 :bool BOOL Combine the search for multiple fields using BOOL.
206 BOOL may be either `or' (match either field)
207 or `and' (match all fields) with default `or'.
209 To reverse the search, bind `bbdb-search-invert' to t.
210 See also `bbdb-message-search' for fast searches using `bbdb-hashtable'
211 but not allowing for regexps.
213 For backward compatibility, SPEC may also consist of the optional args
214 NAME ORGANIZATION MAIL XFIELD PHONE ADDRESS
215 which is equivalent to
216 :all-names NAME :organization ORGANIZATION :mail MAIL
217 :xfield XFIELD :phone PHONE :address ADDRESS
218 This usage is discouraged."
219 (when (not (keywordp (car spec
)))
220 ;; Old format for backward compatibility
221 (unless (get 'bbdb-search
'bbdb-outdated
)
222 (put 'bbdb-search
'bbdb-outdated t
)
223 (message "Outdated usage of `bbdb-search'")
226 (dolist (key '(:all-names
:organization
:mail
:xfield
:phone
:address
))
227 (if (setq val
(pop spec
))
228 (push (list key val
) newspec
)))
229 (setq spec
(apply 'append newspec
))))
232 (sym-list (mapcar (lambda (_)
234 (format "bbdb-re-%d" (setq count
(1+ count
)))))
236 (bool (make-symbol "bool"))
237 (not-invert (make-symbol "not-invert"))
238 (matches (make-symbol "matches"))
239 keyw re-list clauses
)
240 (set bool
''or
) ; default
243 (while (keywordp (setq keyw
(car spec
)))
244 (setq spec
(cdr spec
))
247 (let ((sym (pop sym-list
)))
248 (push `(,sym
,(pop spec
)) re-list
)
249 (push `(string-match ,sym
(bbdb-record-name record
)) clauses
)))
252 (let ((sym (pop sym-list
)))
253 (push `(,sym
,(pop spec
)) re-list
)
254 (push `(string-match ,sym
(bbdb-record-name-lf record
)) clauses
)))
257 (let ((sym (pop sym-list
)))
258 (push `(,sym
,(pop spec
)) re-list
)
259 (push `(or (string-match ,sym
(bbdb-record-name record
))
260 (string-match ,sym
(bbdb-record-name-lf record
))
261 (let ((akas (bbdb-record-field record
'aka-all
))
263 (while (and (setq aka
(pop akas
)) (not done
))
264 (setq done
(string-match ,sym aka
)))
269 (let ((sym (pop sym-list
)))
270 (push `(,sym
,(pop spec
)) re-list
)
271 (push `(let ((affixs (bbdb-record-field record
'affix-all
))
274 (while (and (setq affix
(pop affixs
)) (not done
))
275 (setq done
(string-match ,sym affix
)))
276 ;; so that "^$" matches records without affix
277 (setq done
(string-match ,sym
"")))
282 (let ((sym (pop sym-list
)))
283 (push `(,sym
,(pop spec
)) re-list
)
284 (push `(let ((akas (bbdb-record-field record
'aka-all
))
287 (while (and (setq aka
(pop akas
)) (not done
))
288 (setq done
(string-match ,sym aka
)))
289 ;; so that "^$" matches records without aka
290 (setq done
(string-match ,sym
"")))
295 (let ((sym (pop sym-list
)))
296 (push `(,sym
,(pop spec
)) re-list
)
297 (push `(let ((organizations (bbdb-record-organization record
))
300 (while (and (setq org
(pop organizations
)) (not done
))
301 (setq done
(string-match ,sym org
)))
302 ;; so that "^$" matches records without organizations
303 (setq done
(string-match ,sym
"")))
308 (let ((sym (pop sym-list
)))
309 (push `(,sym
,(pop spec
)) re-list
)
310 (push `(let ((phones (bbdb-record-phone record
))
313 (while (and (setq ph
(pop phones
)) (not done
))
314 (setq done
(string-match ,sym
315 (bbdb-phone-string ph
))))
316 ;; so that "^$" matches records without phones
317 (setq done
(string-match ,sym
"")))
322 (let ((sym (pop sym-list
)))
323 (push `(,sym
,(pop spec
)) re-list
)
324 (push `(let ((addresses (bbdb-record-address record
))
327 (while (and (setq a
(pop addresses
)) (not done
))
328 (setq done
(string-match ,sym
329 (bbdb-format-address a
2))))
330 ;; so that "^$" matches records without addresses
331 (setq done
(string-match ,sym
"")))
336 (let ((sym (pop sym-list
)))
337 (push `(,sym
,(pop spec
)) re-list
)
338 (push `(let ((mails (bbdb-record-mail record
))
339 (bbdb-case-fold-search t
) ; there is no case for mails
342 (while (and (setq m
(pop mails
)) (not done
))
343 (setq done
(string-match ,sym m
)))
344 ;; so that "^$" matches records without mail
345 (setq done
(string-match ,sym
"")))
350 (let ((sym (pop sym-list
)))
351 (push `(,sym
,(pop spec
)) re-list
)
352 (push `(cond ((stringp ,sym
)
353 ;; check xfield `bbdb-default-xfield'
354 ;; "^$" matches records without notes field
356 (or (bbdb-record-xfield-string
357 record bbdb-default-xfield
) "")))
360 (let ((labels bbdb-xfield-label-list
) done tmp
)
361 (while (and (not done
) labels
)
362 (setq tmp
(bbdb-record-xfield-string record
(car labels
))
363 done
(and tmp
(string-match (cdr ,sym
)
365 labels
(cdr labels
)))
368 (string-match (cdr ,sym
)
369 (or (bbdb-record-xfield-string
370 record
(car ,sym
)) ""))))
374 (let ((sym (pop sym-list
)))
375 (push `(,sym
,(pop spec
)) re-list
)
376 (push `(string-match ,sym
(bbdb-record-creation-date record
))
380 (let ((sym (pop sym-list
)))
381 (push `(,sym
,(pop spec
)) re-list
)
382 (push `(string-match ,sym
(bbdb-record-timestamp record
))
386 (set bool
(pop spec
)))
388 ;; Do we need other keywords?
390 (_ (error "Keyword `%s' undefines" keyw
))))
392 `(let ((case-fold-search bbdb-case-fold-search
)
393 (,not-invert
(not (bbdb-search-invert-p)))
395 ;; Are there any use cases for `bbdb-search' where BOOL is only
396 ;; known at run time? A smart byte compiler will hopefully
397 ;; simplify the code below if we know BOOL already at compile time.
398 ;; Alternatively, BOOL could also be a user function that
399 ;; defines more complicated boolian expressions. Yet then we loose
400 ;; the efficiency of `and' and `or' that evaluate its arguments
401 ;; as needed. We would need instead boolian macros that the compiler
402 ;; can analyze at compile time.
403 (if (eq 'and
,(symbol-value bool
))
404 (dolist (record ,records
)
405 (unless (eq ,not-invert
(not (and ,@clauses
)))
406 (push record
,matches
)))
407 (dolist (record ,records
)
408 (unless (eq ,not-invert
(not (or ,@clauses
)))
409 (push record
,matches
))))
410 (nreverse ,matches
))))
412 (defun bbdb-search-read (&optional field
)
413 "Read regexp to search FIELD values of records."
414 (read-string (format "Search records%s %smatching regexp: "
415 (if field
(concat " with " field
) "")
416 (if bbdb-search-invert
"not " ""))))
419 (defun bbdb (regexp &optional layout
)
420 "Display all records in the BBDB matching REGEXP
421 in either the name(s), organization, address, phone, mail, or xfields."
422 (interactive (list (bbdb-search-read) (bbdb-layout-prefix)))
423 (let ((records (bbdb-search (bbdb-records) :all-names regexp
424 :organization regexp
:mail regexp
425 :xfield
(cons '* regexp
)
426 :phone regexp
:address regexp
:bool
'or
)))
428 (bbdb-display-records records layout nil t
)
429 (message "No records matching '%s'" regexp
))))
432 (defun bbdb-search-name (regexp &optional layout
)
433 "Display all records in the BBDB matching REGEXP in the name
434 \(or ``alternate'' names\)."
435 (interactive (list (bbdb-search-read "names") (bbdb-layout-prefix)))
436 (bbdb-display-records (bbdb-search (bbdb-records) :all-names regexp
) layout
))
439 (defun bbdb-search-organization (regexp &optional layout
)
440 "Display all records in the BBDB matching REGEXP in the organization field."
441 (interactive (list (bbdb-search-read "organization") (bbdb-layout-prefix)))
442 (bbdb-display-records (bbdb-search (bbdb-records) :organization regexp
)
446 (defun bbdb-search-address (regexp &optional layout
)
447 "Display all records in the BBDB matching REGEXP in the address fields."
448 (interactive (list (bbdb-search-read "address") (bbdb-layout-prefix)))
449 (bbdb-display-records (bbdb-search (bbdb-records) :address regexp
)
453 (defun bbdb-search-mail (regexp &optional layout
)
454 "Display all records in the BBDB matching REGEXP in the mail address."
455 (interactive (list (bbdb-search-read "mail address") (bbdb-layout-prefix)))
456 (bbdb-display-records (bbdb-search (bbdb-records) :mail regexp
) layout
))
459 (defun bbdb-search-phone (regexp &optional layout
)
460 "Display all records in the BBDB matching REGEXP in the phones field."
461 (interactive (list (bbdb-search-read "phone") (bbdb-layout-prefix)))
462 (bbdb-display-records
463 (bbdb-search (bbdb-records) :phone regexp
) layout
))
466 (defun bbdb-search-xfields (field regexp
&optional layout
)
467 "Display all BBDB records for which xfield FIELD matches REGEXP."
469 (let ((field (completing-read "Xfield to search (RET for all): "
470 (mapcar 'list bbdb-xfield-label-list
) nil t
)))
471 (list (if (string= field
"") '* (intern field
))
472 (bbdb-search-read (if (string= field
"")
475 (bbdb-layout-prefix))))
476 (bbdb-display-records (bbdb-search (bbdb-records) :xfield
(cons field regexp
))
478 (define-obsolete-function-alias 'bbdb-search-notes
'bbdb-search-xfields
"3.0")
481 (defun bbdb-search-changed (&optional layout
)
482 ;; FIXME: "changes" in BBDB lingo are often called "modifications"
484 "Display records which have been changed since BBDB was last saved."
485 (interactive (list (bbdb-layout-prefix)))
486 (if (bbdb-search-invert-p)
487 (let (unchanged-records)
488 (dolist (record (bbdb-records))
489 (unless (memq record bbdb-changed-records
)
490 (push record unchanged-records
)))
491 (bbdb-display-records unchanged-records layout
))
492 (bbdb-display-records bbdb-changed-records layout
)))
494 (defun bbdb-search-prog (fun &optional layout
)
495 "Search records using function FUN.
496 FUN is called with one argument, the record, and should return
497 the record to be displayed or nil otherwise."
498 (bbdb-display-records (delq nil
(mapcar fun
(bbdb-records))) layout
))
501 ;; clean-up functions
503 ;; Sometimes one gets mail from foo@bar.baz.com, and then later gets mail
504 ;; from foo@baz.com. At this point, one would like to delete the bar.baz.com
505 ;; address, since the baz.com address is obviously superior.
507 (defun bbdb-mail-redundant-re (mail)
508 "Return a regexp matching redundant variants of email address MAIL.
509 For example, \"foo@bar.baz.com\" is redundant w.r.t. \"foo@baz.com\".
510 Return nil if MAIL is not a valid plain email address.
511 In particular, ignore addresses \"Joe Smith <foo@baz.com>\"."
512 (let* ((match (string-match "\\`\\([^ ]+\\)@\\(.+\\)\\'" mail
))
513 (name (and match
(match-string 1 mail
)))
514 (host (and match
(match-string 2 mail
))))
516 (concat (regexp-quote name
) "@.*\\." (regexp-quote host
)))))
518 (defun bbdb-delete-redundant-mails (records &optional query update
)
519 "Delete redundant or duplicate mails from RECORDS.
520 For example, \"foo@bar.baz.com\" is redundant w.r.t. \"foo@baz.com\".
521 Duplicates may (but should not) occur if we feed BBDB automatically.
522 Interactively, use BBDB prefix \
523 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
524 If QUERY is non-nil (as in interactive calls, unless we use a prefix arg)
525 query before deleting the redundant mail addresses.
526 If UPDATE is non-nil (as in interactive calls) update the database.
527 Otherwise, this is the caller's responsiblity.
529 Noninteractively, this may be used as an element of `bbdb-notice-record-hook'
530 or `bbdb-change-hook'. However, see also `bbdb-ignore-redundant-mails',
531 which is probably more suited for your needs."
532 (interactive (list (bbdb-do-records) (not current-prefix-arg
) t
))
534 (dolist (record (bbdb-record-list records
))
535 (let (mails redundant okay
)
536 ;; We do not look at the canonicalized mail addresses of RECORD.
537 ;; An address "Joe Smith <foo@baz.com>" can only be entered manually
538 ;; into BBDB, and we assume that this is what the user wants.
539 ;; Anyway, if a mail field contains all the elements
540 ;; foo@baz.com, "Joe Smith <foo@baz.com>", "Jonathan Smith <foo@baz.com>"
541 ;; we do not know which address to keep and which ones to throw.
542 (dolist (mail (bbdb-record-mail record
))
543 (if (assoc-string mail mails t
) ; duplicate mail address
544 (push mail redundant
)
546 (let ((mail-re (delq nil
(mapcar 'bbdb-mail-redundant-re mails
)))
547 (case-fold-search t
))
548 (if (not (cdr mail-re
)) ; at most one mail-re address to consider
549 (setq okay
(nreverse mails
))
550 (setq mail-re
(concat "\\`\\(?:" (mapconcat 'identity mail-re
"\\|")
553 (if (string-match mail-re mail
) ; redundant mail address
554 (push mail redundant
)
556 (let ((form (format "redundant mail%s %s"
557 (if (< 1 (length redundant
)) "s" "")
558 (bbdb-concat 'mail
(nreverse redundant
)))))
561 (y-or-n-p (format "Delete %s: " form
))))
562 (unless query
(message "Deleting %s" form
))
563 (bbdb-record-set-field record
'mail okay
)
565 (bbdb-change-record record
)))))))
566 (define-obsolete-function-alias 'bbdb-delete-duplicate-mails
567 'bbdb-delete-redundant-mails
"3.0")
569 (defun bbdb-search-duplicates (&optional fields
)
570 "Search all records that have duplicate entries for FIELDS.
571 The list FIELDS may contain the symbols `name', `mail', and `aka'.
572 If FIELDS is nil use all these fields. With prefix, query for FIELDS.
573 The search results are displayed in the BBDB buffer."
574 (interactive (list (if current-prefix-arg
575 (list (intern (completing-read "Field: "
576 '("name" "mail" "aka")
578 (setq fields
(or fields
'(name mail aka
)))
580 (dolist (record (bbdb-records))
582 (when (and (memq 'name fields
)
583 (bbdb-record-name record
)
584 (setq hash
(bbdb-gethash (bbdb-record-name record
)
585 '(fl-name lf-name aka
)))
587 (setq ret
(append hash ret
))
588 (message "BBDB record `%s' has duplicate name."
589 (bbdb-record-name record
))
592 (if (memq 'mail fields
)
593 (dolist (mail (bbdb-record-mail-canon record
))
594 (setq hash
(bbdb-gethash mail
'(mail)))
595 (when (> (length hash
) 1)
596 (setq ret
(append hash ret
))
597 (message "BBDB record `%s' has duplicate mail `%s'."
598 (bbdb-record-name record
) mail
)
601 (if (memq 'aka fields
)
602 (dolist (aka (bbdb-record-aka record
))
603 (setq hash
(bbdb-gethash aka
'(fl-name lf-name aka
)))
604 (when (> (length hash
) 1)
605 (setq ret
(append hash ret
))
606 (message "BBDB record `%s' has duplicate aka `%s'"
607 (bbdb-record-name record
) aka
)
610 (bbdb-display-records (sort (delete-dups ret
)
611 'bbdb-record-lessp
))))
613 (defun bbdb-fix-records (records)
615 Interactively, use BBDB prefix \
616 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
617 (interactive (list (bbdb-do-records)))
619 (dolist (record (bbdb-record-list records
))
620 ;; For the fields which take a list of strings (affix, organization,
621 ;; aka, and mail) `bbdb=record-set-field' calls `bbdb-list-strings'
622 ;; which removes all elements from such a list which are not non-empty
623 ;; strings. This should fix most problems with these fields.
624 (bbdb-record-set-field record
'affix
(bbdb-record-affix record
))
625 (bbdb-record-set-field record
'organization
(bbdb-record-organization record
))
626 (bbdb-record-set-field record
'aka
(bbdb-record-aka record
))
627 (bbdb-record-set-field record
'mail
(bbdb-record-mail record
))
628 (bbdb-change-record record
))
631 (defun bbdb-touch-records (records)
632 "Touch RECORDS by calling `bbdb-change-hook' unconditionally.
633 Interactively, use BBDB prefix \
634 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
635 (interactive (list (bbdb-do-records)))
637 (let ((bbdb-update-unchanged-records t
))
638 (dolist (record (bbdb-record-list records
))
639 (bbdb-change-record record
))))
641 ;;; Time-based functions
643 (defmacro bbdb-compare-records
(cmpval label compare
)
644 "Builds a lambda comparison function that takes one argument, RECORD.
645 RECORD is returned if (COMPARE VALUE CMPVAL) is t, where VALUE
646 is the value of field LABEL of RECORD."
648 (let ((val (bbdb-record-field record
,label
)))
649 (if (and val
(,compare val
,cmpval
))
652 (defsubst bbdb-string
> (a b
)
653 (not (or (string= a b
)
657 (defun bbdb-timestamp-older (date &optional layout
)
658 "Display records with timestamp older than DATE.
659 DATE must be in yyyy-mm-dd format."
660 (interactive (list (read-string "Timestamp older than: (yyyy-mm-dd) ")
661 (bbdb-layout-prefix)))
662 (bbdb-search-prog (bbdb-compare-records date
'timestamp string
<) layout
))
665 (defun bbdb-timestamp-newer (date &optional layout
)
666 "Display records with timestamp newer than DATE.
667 DATE must be in yyyy-mm-dd format."
668 (interactive (list (read-string "Timestamp newer than: (yyyy-mm-dd) ")
669 (bbdb-layout-prefix)))
670 (bbdb-search-prog (bbdb-compare-records date
'timestamp bbdb-string
>) layout
))
673 (defun bbdb-creation-older (date &optional layout
)
674 "Display records with creation-date older than DATE.
675 DATE must be in yyyy-mm-dd format."
676 (interactive (list (read-string "Creation older than: (yyyy-mm-dd) ")
677 (bbdb-layout-prefix)))
678 (bbdb-search-prog (bbdb-compare-records date
'creation-date string
<) layout
))
681 (defun bbdb-creation-newer (date &optional layout
)
682 "Display records with creation-date newer than DATE.
683 DATE must be in yyyy-mm-dd format."
684 (interactive (list (read-string "Creation newer than: (yyyy-mm-dd) ")
685 (bbdb-layout-prefix)))
686 (bbdb-search-prog (bbdb-compare-records date
'creation-date bbdb-string
>) layout
))
689 (defun bbdb-creation-no-change (&optional layout
)
690 "Display records that have the same timestamp and creation-date."
691 (interactive (list (bbdb-layout-prefix)))
693 ;; RECORD is bound in `bbdb-compare-records'.
694 (bbdb-compare-records (bbdb-record-timestamp record
)
695 'creation-date string
=)
698 ;;; Parsing phone numbers
699 ;; XXX this needs expansion to handle international prefixes properly
700 ;; i.e. +353-number without discarding the +353 part. Problem being
701 ;; that this will necessitate yet another change in the database
702 ;; format for people who are using north american numbers.
704 (defsubst bbdb-subint
(string num
)
705 "Used for parsing phone numbers."
706 (string-to-number (match-string num string
)))
708 (defun bbdb-parse-phone (string &optional style
)
709 "Parse a phone number from STRING and return a list of integers the form
710 \(area-code exchange number extension).
711 This is both lenient and strict in what it will parse - whitespace may
712 appear (or not) between any of the groups of digits, parentheses around the
713 area code are optional, as is a dash between the exchange and number, and
714 a '1' preceeding the area code; but there must be three digits in the area
715 code and exchange, and four in the number (if they are present).
716 All of these are unambigously parsable:
718 ( 415 ) 555 - 1212 x123 -> (415 555 1212 123)
719 (415)555-1212 123 -> (415 555 1212 123)
720 (1-415) 555-1212 123 -> (415 555 1212 123)
721 1 (415)-555-1212 123 -> (415 555 1212 123)
722 555-1212 123 -> (0 555 1212 123)
723 555 1212 -> (0 555 1212 0)
724 415 555 1212 -> (415 555 1212 0)
725 1 415 555 1212 -> (415 555 1212 0)
726 5551212 -> (0 555 1212 0)
727 4155551212 -> (415 555 1212 0)
728 4155551212123 -> (415 555 1212 123)
729 5551212x123 -> (0 555 1212 123)
732 Note that \"4151212123\" is ambiguous; it could be interpreted either as
733 \"(415) 121-2123\" or as \"415-1212 x123\".
735 Return a list containing four numbers or one string."
737 ;; RW: Missing parts of NANP numbers are replaced by zeros.
738 ;; Is this always correct? What about an extension zero?
739 ;; Should we use nil instead of zeros?
740 (unless style
(setq style bbdb-phone-style
))
741 (let ((area-regexp (concat "(?[ \t]*\\+?1?[ \t]*[-\(]?[ \t]*[-\(]?[ \t]*"
742 "\\([2-9][0-9][0-9]\\)[ \t]*)?[-./ \t]*"))
743 (main-regexp (concat "\\([1-9][0-9][0-9]\\)[ \t]*[-.]?[ \t]*"
744 "\\([0-9][0-9][0-9][0-9]\\)[ \t]*"))
745 (ext-regexp "x?[ \t]*\\([0-9]+\\)[ \t]*"))
746 (cond ((not (eq style
'nanp
))
747 (list (bbdb-string-trim string
)))
748 ((string-match ;; (415) 555-1212 x123
749 (concat "^[ \t]*" area-regexp main-regexp ext-regexp
"$") string
)
750 (list (bbdb-subint string
1) (bbdb-subint string
2)
751 (bbdb-subint string
3) (bbdb-subint string
4)))
753 ((string-match (concat "^[ \t]*" area-regexp main-regexp
"$") string
)
754 (list (bbdb-subint string
1) (bbdb-subint string
2)
755 (bbdb-subint string
3) 0))
757 ((string-match (concat "^[ \t]*" main-regexp ext-regexp
"$") string
)
758 (list 0 (bbdb-subint string
1) (bbdb-subint string
2)
759 (bbdb-subint string
3)))
761 ((string-match (concat "^[ \t]*" main-regexp
"$") string
)
762 (list 0 (bbdb-subint string
1) (bbdb-subint string
2) 0))
764 ((string-match (concat "^[ \t]*" ext-regexp
"$") string
)
765 (list 0 0 0 (bbdb-subint string
1)))
766 ;; We trust the user she knows what she wants
767 (t (list (bbdb-string-trim string
))))))
769 (defun bbdb-message-search (name mail
)
770 "Return list of BBDB records matching NAME and/or MAIL.
771 First try to find a record matching both NAME and MAIL.
772 If this fails try to find a record matching MAIL.
773 If this fails try to find a record matching NAME.
774 NAME may match FIRST_LAST, LAST_FIRST or AKA.
776 This function performs a fast search using `bbdb-hashtable'.
777 NAME and MAIL must be strings or nil.
778 See `bbdb-search' for searching records with regexps."
780 (bbdb-buffer) ; make sure database is loaded and up-to-date
781 (let ((mrecords (if mail
(bbdb-gethash mail
'(mail))))
782 (nrecords (if name
(bbdb-gethash name
'(fl-name lf-name aka
)))))
783 ;; (1) records matching NAME and MAIL
784 (or (and mrecords nrecords
786 (dolist (record nrecords
)
787 (mapc (lambda (mr) (if (and (eq record mr
)
788 (not (memq record records
)))
789 (push record records
)))
792 ;; (2) records matching MAIL
794 ;; (3) records matching NAME
797 (defun bbdb-read-record (&optional first-and-last
)
798 "Read and return a new BBDB record.
799 Does not insert it into the database or update the hashtables,
800 but does ensure that there will not be name collisions."
802 (let ((record (bbdb-empty-record)))
805 (setq name
(bbdb-read-name first-and-last
))
806 (bbdb-check-name (car name
) (cdr name
)))
807 (bbdb-record-set-firstname record
(car name
))
808 (bbdb-record-set-lastname record
(cdr name
)))
811 (bbdb-record-set-organization record
(bbdb-read-organization))
814 (bbdb-record-set-mail
815 record
(bbdb-split 'mail
(bbdb-read-string "E-Mail Addresses: ")))
817 (let (addresses label address
)
818 (while (not (string= ""
821 "Snail Mail Address Label [RET when done]: "
823 bbdb-address-label-list
))))
824 (setq address
(make-vector bbdb-address-length nil
))
825 (bbdb-record-edit-address address label t
)
826 (push address addresses
))
827 (bbdb-record-set-address record
(nreverse addresses
)))
830 (let (phones phone-list label
)
831 (while (not (string= ""
834 "Phone Label [RET when done]: " nil
835 bbdb-phone-label-list
))))
839 (read-string "Phone: "
840 (and (integerp bbdb-default-area-code
)
842 bbdb-default-area-code
))))))
843 (push (apply 'vector label phone-list
) phones
))
844 (bbdb-record-set-phone record
(nreverse phones
)))
846 ;; `bbdb-default-xfield'
847 (let ((xfield (bbdb-read-xfield bbdb-default-xfield
)))
848 (unless (string= "" xfield
)
849 (bbdb-record-set-xfields
850 record
(list (cons bbdb-default-xfield xfield
)))))
854 (defun bbdb-read-name (&optional first-and-last dfirst dlast
)
855 "Read name for a record from minibuffer.
856 FIRST-AND-LAST controls the reading mode:
857 If it is 'first-last read first and last name separately.
858 If it is 'last-first read last and first name separately.
859 If it is 'fullname read full name at once.
860 If it is t read name parts separately, obeying `bbdb-read-name-format' if possible.
861 Otherwise use `bbdb-read-name-format'.
862 DFIRST and DLAST are default values for the first and last name.
863 Return cons with first and last name."
864 (unless (memq first-and-last
'(first-last last-first fullname
))
865 ;; We do not yet know how to read the name
867 (if (and first-and-last
868 (not (memq bbdb-read-name-format
'(first-last last-first
))))
870 bbdb-read-name-format
)))
871 (let ((name (cond ((eq first-and-last
'last-first
)
873 (setq ln
(bbdb-read-string "Last Name: " dlast
)
874 fn
(bbdb-read-string "First Name: " dfirst
))
876 ((eq first-and-last
'first-last
)
877 (cons (bbdb-read-string "First Name: " dfirst
)
878 (bbdb-read-string "Last Name: " dlast
)))
880 (bbdb-divide-name (bbdb-read-string
881 "Name: " (bbdb-concat 'name-first-last
883 (if (string= (car name
) "") (setcar name nil
))
884 (if (string= (cdr name
) "") (setcdr name nil
))
888 (defun bbdb-create (record)
889 "Add a new RECORD to BBDB.
890 When called interactively read all relevant info.
891 Do not call this from a program; call `bbdb-create-internal' instead."
892 (interactive (list (bbdb-read-record current-prefix-arg
)))
893 (bbdb-change-record record
)
894 (bbdb-display-records (list record
)))
896 (defsubst bbdb-split-maybe
(separator string
)
897 "Split STRING into list of substrings bounded by matches for SEPARATORS.
898 If STRING is a list, return STRING. Throw error if STRING is neither a string
900 (cond ((stringp string
)
901 (bbdb-split separator string
))
902 ((listp string
) string
)
903 (t (error "Cannot convert %s to list" string
))))
906 (defun bbdb-create-internal (&rest spec
)
907 "Add a new record to the database and return it.
909 The following keywords are supported in SPEC:
910 :name VAL String or a cons cell (FIRST . LAST), the name of the person.
911 An error is thrown if VAL is already in use
912 and `bbdb-allow-duplicates' is nil.
913 :affix VAL List of strings.
914 :aka VAL List of strings.
915 :organization VAL List of strings.
916 :mail VAL String with comma-separated mail address
917 or a list of strings.
918 An error is thrown if a mail address in MAIL is already
919 in use and `bbdb-allow-duplicates' is nil.
920 :phone VAL List of phone-number objects. A phone-number is a vector
921 [\"label\" areacode prefix suffix extension-or-nil]
922 or [\"label\" \"phone-number\"]
923 :address VAL List of addresses. An address is a vector of the form
924 \[\"label\" (\"line1\" \"line2\" ... ) \"City\"
925 \"State\" \"Postcode\" \"Country\"].
926 :xfields VAL Alist associating symbols with strings.
927 :uuid VAL String, the uuid.
928 :creation-date VAL String, the creation date.
929 :check If present, throw an error if a field value is not
930 syntactically correct."
932 (let ((record (bbdb-empty-record))
933 (record-type (cdr bbdb-record-type
))
934 (check (prog1 (memq :check spec
)
935 (setq spec
(delq :check spec
))))
939 (while (keywordp (setq keyw
(car spec
)))
940 (setq spec
(cdr spec
))
943 (let ((name (pop spec
)))
944 (cond ((stringp name
)
945 (setq name
(bbdb-divide-name name
)))
946 (check (bbdb-check-type name
'(or (const nil
)
947 (cons string string
))
949 (let ((firstname (car name
))
950 (lastname (cdr name
)))
951 (bbdb-check-name firstname lastname
) ; check for duplicates
952 (bbdb-record-set-firstname record firstname
)
953 (bbdb-record-set-lastname record lastname
))))
956 (let ((affix (bbdb-split-maybe 'affix
(pop spec
))))
957 (if check
(bbdb-check-type affix
(bbdb-record-affix record-type
) t
))
958 (bbdb-record-set-affix record affix
)))
961 (let ((organization (bbdb-split-maybe 'organization
(pop spec
))))
962 (if check
(bbdb-check-type
963 organization
(bbdb-record-organization record-type
) t
))
964 (bbdb-record-set-organization record organization
)))
967 (let ((aka (bbdb-split-maybe 'aka
(pop spec
))))
968 (if check
(bbdb-check-type aka
(bbdb-record-aka record-type
) t
))
969 (bbdb-record-set-aka record aka
)))
972 (let ((mail (bbdb-split-maybe 'mail
(pop spec
))))
973 (if check
(bbdb-check-type mail
(bbdb-record-mail record-type
) t
))
974 (unless bbdb-allow-duplicates
976 (if (bbdb-gethash elt
'(mail))
977 (error "%s is already in the database" elt
))))
978 (bbdb-record-set-mail record mail
)))
981 (let ((phone (pop spec
)))
982 (if check
(bbdb-check-type phone
(bbdb-record-phone record-type
) t
))
983 (bbdb-record-set-phone phone record
)))
986 (let ((address (pop spec
)))
987 (if check
(bbdb-check-type address
(bbdb-record-address record-type
) t
))
988 (bbdb-record-set-address record address
)))
991 (let ((xfields (pop spec
)))
992 (if check
(bbdb-check-type xfields
(bbdb-record-xfields record-type
) t
))
993 (bbdb-record-set-xfields record xfields
)))
996 (let ((uuid (pop spec
)))
997 (if check
(bbdb-check-type uuid
(bbdb-record-uuid record-type
) t
))
998 (bbdb-record-set-uuid record uuid
)))
1001 (let ((creation-date (pop spec
)))
1002 (if check
(bbdb-check-type
1003 creation-date
(bbdb-record-creation-date record-type
) t
))
1004 (bbdb-record-set-creation-date record creation-date
)))
1006 (_ (error "Keyword `%s' undefined" keyw
))))
1008 (bbdb-change-record record
)))
1011 (defun bbdb-insert-field (record field value
)
1012 "For RECORD, add a new FIELD with value VALUE.
1013 Interactively, read FIELD and VALUE; RECORD is the current record.
1014 A non-nil prefix arg is passed on to `bbdb-read-field' as FLAG (see there)."
1016 (let* ((_ (bbdb-editable))
1017 (record (or (bbdb-current-record)
1018 (error "Point not on a record")))
1019 (list (append bbdb-xfield-label-list
1020 '(affix organization aka phone address mail
)))
1022 (completion-ignore-case t
)
1023 (present (mapcar 'car
(bbdb-record-xfields record
))))
1024 (if (bbdb-record-affix record
) (push 'affix present
))
1025 (if (bbdb-record-organization record
) (push 'organization present
))
1026 (if (bbdb-record-mail record
) (push 'mail present
))
1027 (if (bbdb-record-aka record
) (push 'aka present
))
1028 (dolist (field present
)
1029 (setq list
(remq field list
)))
1030 (setq list
(mapcar 'symbol-name list
))
1031 (while (string= field
"")
1032 (setq field
(downcase (completing-read "Insert Field: " list
))))
1033 (setq field
(intern field
))
1034 (if (memq field present
)
1035 (error "Field \"%s\" already exists" field
))
1036 (list record field
(bbdb-read-field record field current-prefix-arg
))))
1040 (if (bbdb-record-affix record
)
1041 (error "Affix field exists already"))
1043 (setq value
(bbdb-split 'affix value
)))
1044 (bbdb-record-set-field record
'affix value
))
1046 ((eq field
'organization
)
1047 (if (bbdb-record-organization record
)
1048 (error "Organization field exists already"))
1050 (setq value
(bbdb-split 'organization value
)))
1051 (bbdb-record-set-field record
'organization value
))
1054 (bbdb-record-set-field record
'phone
1055 (nconc (bbdb-record-phone record
)
1058 ((eq field
'address
)
1059 (bbdb-record-set-field record
'address
1060 (nconc (bbdb-record-address record
)
1064 (if (bbdb-record-mail record
)
1065 (error "Mail field exists already"))
1067 (setq value
(bbdb-split 'mail value
)))
1068 (bbdb-record-set-field record
'mail value
))
1071 (if (bbdb-record-aka record
)
1072 (error "Alternate names field exists already"))
1074 (setq value
(bbdb-split 'aka value
)))
1075 (bbdb-record-set-field record
'aka value
))
1077 ((assq field
(bbdb-record-xfields record
))
1078 (error "Xfield \"%s\" already exists" field
))
1080 (bbdb-record-set-xfield record field value
)))
1081 (unless (bbdb-change-record record
)
1082 (message "Record unchanged")))
1084 (defun bbdb-read-field (record field
&optional flag
)
1085 "For RECORD read new FIELD interactively.
1086 - The phone number style is controlled via `bbdb-phone-style'.
1087 A prefix FLAG inverts the style,
1088 - If a mail address lacks a domain, append `bbdb-default-domain'
1089 if this variable non-nil. With prefix FLAG do not alter the mail address.
1090 - The value of an xfield is a string. With prefix FLAG the value may be
1092 (let* ((init-f (intern-soft (concat "bbdb-init-" (symbol-name field
))))
1093 (init (if (and init-f
(functionp init-f
))
1094 (funcall init-f record
))))
1096 (eq field
'affix
) (bbdb-read-string "Affix: " init
))
1098 ((eq field
'organization
) (bbdb-read-organization init
))
1101 (let ((mail (bbdb-read-string "Mail: " init
)))
1102 (if (string-match "^mailto:" mail
)
1103 (setq mail
(substring mail
(match-end 0))))
1104 (if (or (not bbdb-default-domain
)
1105 flag
(string-match "[@%!]" mail
))
1107 (concat mail
"@" bbdb-default-domain
))))
1109 ((eq field
'aka
) (bbdb-read-string "Alternate Names: " init
))
1112 (let ((bbdb-phone-style
1113 (if flag
(if (eq bbdb-phone-style
'nanp
) nil
'nanp
)
1116 (bbdb-read-string "Label: " nil bbdb-phone-label-list
)
1119 (read-string "Phone: "
1120 (and (integerp bbdb-default-area-code
)
1122 bbdb-default-area-code
))))))))
1124 ((eq field
'address
)
1125 (let ((address (make-vector bbdb-address-length nil
)))
1126 (bbdb-record-edit-address address nil t
)
1129 ((or (memq field bbdb-xfield-label-list
)
1132 (format "\"%s\" is an unknown field name. Define it? " field
))
1134 (bbdb-read-xfield field init flag
)))))
1137 (defun bbdb-edit-field (record field
&optional value flag
)
1138 "Edit the contents of FIELD of RECORD.
1139 If point is in the middle of a multi-line field (e.g., address),
1140 then the entire field is edited, not just the current line.
1141 For editing phone numbers or addresses, VALUE must be the phone number
1142 or address that gets edited. An error is thrown when attempting to edit
1143 a phone number or address with VALUE being nil.
1145 - The value of an xfield is a string. With prefix FLAG the value may be
1150 ;; when at the end of the line take care of it
1151 (if (and (eolp) (not (bobp)) (not (bbdb-current-field)))
1153 (let* ((field-l (bbdb-current-field))
1154 (field (car field-l
))
1155 (value (nth 1 field-l
)))
1156 (unless field
(error "Point not in a field"))
1157 (list (bbdb-current-record)
1158 (if (memq field
'(name affix organization aka mail phone address
1159 uuid creation-date timestamp
))
1160 field
; not an xfield
1161 (elt value
0)) ; xfield
1162 value current-prefix-arg
))))
1164 (cond ((memq field
'(firstname lastname xfields
))
1165 ;; FIXME: We could also edit first and last names.
1166 (error "Field `%s' not editable this way." field
))
1169 (bbdb-record-set-field
1173 ;; Here we try to obey the name-format xfield for
1174 ;; editing the name field. Is this useful? Or is this
1175 ;; irritating overkill and we better obey consistently
1176 ;; `bbdb-read-name-format'?
1177 (or (bbdb-record-xfield-intern record
'name-format
)
1179 (bbdb-record-firstname record
)
1180 (bbdb-record-lastname record
)))))
1183 (unless value
(error "No phone specified"))
1184 (bbdb-record-edit-phone (bbdb-record-phone record
) value
))
1185 ((eq field
'address
)
1186 (unless value
(error "No address specified"))
1187 (bbdb-record-edit-address value nil flag
))
1188 ((eq field
'organization
)
1189 (bbdb-record-set-field
1191 (bbdb-read-organization
1192 (bbdb-concat field
(bbdb-record-organization record
)))))
1193 ((setq edit-str
(assq field
'((affix .
"Affix")
1194 (mail .
"Mail") (aka .
"AKA"))))
1195 (bbdb-record-set-field
1197 (bbdb-split field
(bbdb-read-string
1198 (format "%s: " (cdr edit-str
))
1200 (bbdb-record-field record field
))))))
1202 (bbdb-record-set-field
1203 record
'uuid
(bbdb-read-string "uuid (edit at your own risk): " (bbdb-record-uuid record
))))
1204 ((eq field
'creation-date
)
1205 (bbdb-record-set-creation-date
1206 record
(bbdb-read-string "creation-date: " (bbdb-record-creation-date record
))))
1207 ;; The timestamp is set automatically whenever we save a modified record.
1208 ;; So any editing gets overwritten.
1209 ((eq field
'timestamp
)) ; do nothing
1211 (bbdb-record-set-xfield
1213 (bbdb-read-xfield field
(bbdb-record-xfield record field
) flag
))))
1214 (cond ((eq field
'timestamp
)
1215 (message "timestamp not editable"))
1216 ((bbdb-change-record record
))
1217 (t (message "Record unchanged")))))
1219 (defun bbdb-edit-foo (record field
&optional nvalue
)
1220 "For RECORD edit some FIELD (mostly interactively).
1221 FIELD may take the same values as the elements of the variable `bbdb-edit-foo'.
1222 If FIELD is 'phone or 'address, NVALUE should be an integer in order to edit
1223 the NVALUEth phone or address field; otherwise insert a new phone or address
1226 Interactively, if called without a prefix, the value of FIELD is the car
1227 of the variable `bbdb-edit-foo'. When called with a prefix, the value
1228 of FIELD is the cdr of this variable. Then use minibuffer completion
1229 to select the field."
1231 (let* ((_ (bbdb-editable))
1232 (record (bbdb-current-record))
1233 (tmp (if current-prefix-arg
(cdr bbdb-edit-foo
) (car bbdb-edit-foo
)))
1234 (field (if (memq tmp
'(current-fields all-fields
))
1235 ;; Do not require match so that we can define new xfields.
1236 (intern (completing-read
1237 "Edit field: " (mapcar 'list
(if (eq tmp
'all-fields
)
1238 (append '(name affix organization aka mail phone address uuid creation-date
)
1239 bbdb-xfield-label-list
)
1240 (append (if (bbdb-record-affix record
) '(affix))
1241 (if (bbdb-record-organization record
) '(organization))
1242 (if (bbdb-record-aka record
) '(aka))
1243 (if (bbdb-record-mail record
) '(mail))
1244 (if (bbdb-record-phone record
) '(phone))
1245 (if (bbdb-record-address record
) '(address))
1246 (mapcar 'car
(bbdb-record-xfields record
))
1247 '(name uuid creation-date
))))))
1249 ;; Multiple phone and address fields may use the same label.
1250 ;; So we cannot use these labels to uniquely identify
1251 ;; a phone or address field. So instead we number these fields
1252 ;; consecutively. But we do use the labels to annotate the numbers
1253 ;; (available starting from GNU Emacs 24.1).
1254 (nvalue (cond ((eq field
'phone
)
1255 (let* ((phones (bbdb-record-phone record
))
1256 (collection (cons (cons "new" "new phone #")
1258 (cons (format "%d" n
) (bbdb-phone-label (nth n phones
))))
1259 (number-sequence 0 (1- (length phones
))))))
1260 (completion-extra-properties
1261 `(:annotation-function
1262 (lambda (s) (format " (%s)" (cdr (assoc s
',collection
)))))))
1263 (if (< 0 (length phones
))
1264 (completing-read "Phone field: " collection nil t
)
1266 ((eq field
'address
)
1267 (let* ((addresses (bbdb-record-address record
))
1268 (collection (cons (cons "new" "new address")
1270 (cons (format "%d" n
) (bbdb-address-label (nth n addresses
))))
1271 (number-sequence 0 (1- (length addresses
))))))
1272 (completion-extra-properties
1273 `(:annotation-function
1274 (lambda (s) (format " (%s)" (cdr (assoc s
',collection
)))))))
1275 (if (< 0 (length addresses
))
1276 (completing-read "Address field: " collection nil t
)
1278 (list record field
(and (stringp nvalue
)
1279 (if (string= "new" nvalue
)
1281 (string-to-number nvalue
))))))
1283 (if (memq field
'(firstname lastname name-lf aka-all mail-aka mail-canon
))
1284 (error "Field `%s' illegal" field
))
1285 (let ((value (if (numberp nvalue
)
1286 (nth nvalue
(cond ((eq field
'phone
) (bbdb-record-phone record
))
1287 ((eq field
'address
) (bbdb-record-address record
))
1288 (t (error "%s: nvalue %s meaningless" field nvalue
)))))))
1289 (if (and (numberp nvalue
) (not value
))
1290 (error "%s: nvalue %s out of range" field nvalue
))
1291 (if (or (memq field
'(name uuid creation-date
))
1292 (and (eq field
'affix
) (bbdb-record-affix record
))
1293 (and (eq field
'organization
) (bbdb-record-organization record
))
1294 (and (eq field
'mail
) (bbdb-record-mail record
))
1295 (and (eq field
'aka
) (bbdb-record-aka record
))
1296 (assq field
(bbdb-record-xfields record
))
1298 (bbdb-edit-field record field value
)
1299 (bbdb-insert-field record field
1300 (bbdb-read-field record field
)))))
1302 (defun bbdb-read-xfield (field &optional init sexp
)
1303 "Read xfield FIELD with optional INIT.
1304 This calls bbdb-read-xfield-FIELD if it exists."
1305 (let ((read-fun (intern-soft (format "bbdb-read-xfield-%s" field
))))
1306 (cond ((fboundp read-fun
)
1307 (funcall read-fun init
))
1308 ((and (not sexp
) (string-or-null-p init
))
1309 (bbdb-read-string (format "%s: " field
) init
))
1310 (t (read-minibuffer (format "%s (sexp): " field
)
1311 (prin1-to-string init
))))))
1313 (defun bbdb-read-organization (&optional init
)
1314 "Read organization."
1315 (if (string< "24.3" (substring emacs-version
0 4))
1316 (let ((crm-separator
1318 (cadr (assq 'organization bbdb-separator-alist
))
1320 (crm-local-completion-map bbdb-crm-local-completion-map
))
1321 (completing-read-multiple "Organizations: " bbdb-organization-list
1323 (bbdb-split 'organization
(bbdb-read-string "Organizations: " init
))))
1325 (defun bbdb-record-edit-address (address &optional label ignore-country
)
1327 If LABEL is nil, edit the label sub-field of the address as well.
1328 If the country field of ADDRESS is nonempty and IGNORE-COUNTRY is nil,
1329 use the rule from `bbdb-address-format-list' matching this country.
1330 Otherwise, use the default rule according to `bbdb-address-format-list'."
1332 (setq label
(bbdb-read-string "Label: "
1333 (bbdb-address-label address
)
1334 bbdb-address-label-list
)))
1335 (let ((country (or (bbdb-address-country address
) ""))
1337 (unless (or ignore-country
(string= "" country
))
1338 (let ((list bbdb-address-format-list
)
1340 (while (and (not edit
) (setq elt
(pop list
)))
1341 (setq identifier
(car elt
))
1342 (if (or (and (listp identifier
)
1343 (member-ignore-case country identifier
))
1344 (and (functionp identifier
)
1345 (funcall identifier address
)))
1346 (setq edit
(nth 1 elt
))))))
1348 (setq edit
(nth 1 (assq t bbdb-address-format-list
))))
1349 (unless edit
(error "No address editing function defined"))
1350 (if (functionp edit
)
1351 (setq new-addr
(funcall edit address
))
1352 (setq new-addr
(make-vector 5 ""))
1353 (dolist (elt (string-to-list edit
))
1355 (aset new-addr
0 (bbdb-edit-address-street
1356 (bbdb-address-streets address
))))
1358 (aset new-addr
1 (bbdb-read-string
1359 "City: " (bbdb-address-city address
)
1362 (aset new-addr
2 (bbdb-read-string
1363 "State: " (bbdb-address-state address
)
1368 (bbdb-parse-postcode
1370 "Postcode: " (bbdb-address-postcode address
)
1371 bbdb-postcode-list
)))))
1375 "Country: " (or (bbdb-address-country address
)
1376 bbdb-default-country
)
1377 bbdb-country-list
))))))
1378 (bbdb-address-set-label address label
)
1379 (bbdb-address-set-streets address
(elt new-addr
0))
1380 (bbdb-address-set-city address
(elt new-addr
1))
1381 (bbdb-address-set-state address
(elt new-addr
2))
1382 (bbdb-address-set-postcode address
(elt new-addr
3))
1383 (if (string= "" (bbdb-concat "" (elt new-addr
0) (elt new-addr
1)
1384 (elt new-addr
2) (elt new-addr
3)
1386 ;; User did not enter anything. this causes a display bug.
1387 ;; The following is a temporary fix. Ideally, we would simply discard
1388 ;; the entire address, but that requires bigger hacking.
1389 (bbdb-address-set-country address
"Emacs")
1390 (bbdb-address-set-country address
(elt new-addr
4)))))
1392 (defun bbdb-edit-address-street (streets)
1393 "Edit list STREETS."
1394 (let ((n 0) street list
)
1395 (while (not (string= "" (setq street
1397 (format "Street, line %d: " (1+ n
))
1398 (nth n streets
) bbdb-street-list
))))
1403 ;; This function can provide some guidance for writing
1404 ;; your own address editing function
1405 (defun bbdb-edit-address-default (address)
1406 "Function to use for address editing.
1407 The sub-fields and the prompts used are:
1408 Street, line n: (nth n street)
1413 (list (bbdb-edit-address-street (bbdb-address-streets address
))
1414 (bbdb-read-string "City: " (bbdb-address-city address
) bbdb-city-list
)
1415 (bbdb-read-string "State: " (bbdb-address-state address
)
1418 (bbdb-parse-postcode
1419 (bbdb-read-string "Postcode: " (bbdb-address-postcode address
)
1420 bbdb-postcode-list
)))
1421 (bbdb-read-string "Country: " (or (bbdb-address-country address
)
1422 bbdb-default-country
)
1423 bbdb-country-list
)))
1425 (defun bbdb-record-edit-phone (phones phone
)
1426 "For list PHONES edit PHONE number."
1427 ;; Phone numbers are special. They are vectors with either
1428 ;; two or four elements. We do not know whether after editing PHONE
1429 ;; we still have a number requiring the same format as PHONE.
1430 ;; So we take all numbers PHONES of the record so that we can
1431 ;; replace the element PHONE in PHONES.
1432 (setcar (memq phone phones
)
1434 (bbdb-read-string "Label: "
1435 (bbdb-phone-label phone
)
1436 bbdb-phone-label-list
)
1439 (read-string "Phone: " (bbdb-phone-string phone
)))))))
1441 ;; (bbdb-list-transpose '(a b c d) 1 3)
1442 (defun bbdb-list-transpose (list i j
)
1443 "For LIST transpose elements I and J destructively.
1444 I and J start with zero. Return the modified LIST."
1446 list
; ignore that i, j could be invalid
1448 ;; Travel down LIST only once
1449 (if (> i j
) (setq a i i j j a
)); swap
1450 (setq a
(nthcdr i list
)
1451 b
(nthcdr (- j i
) a
)
1453 (unless b
(error "Args %i, %i beyond length of list." i j
))
1458 (defun bbdb-ident-point (&optional point
)
1459 "Return identifier (RECNUM FIELD NUM) for position POINT.
1460 If POINT is nil use current value of point.
1461 RECNUM is the number of the record (starting from zero).
1462 FIELD is the field type.
1463 If FIELD's value is a list, NUM is the position of the subfield within FIELD.
1464 If any of these terms is not defined at POINT, the respective value is nil."
1465 (unless point
(setq point
(point)))
1466 (let ((recnum (get-text-property point
'bbdb-record-number
))
1467 (field (get-text-property point
'bbdb-field
)))
1469 (list recnum nil nil
))
1470 ((eq (car field
) 'name
)
1471 (list recnum
'name nil
))
1472 ((not (nth 1 field
))
1473 (list recnum
(car field
) nil
))
1475 (let* ((record (car (nth recnum bbdb-records
)))
1476 (fields (bbdb-record-field record
(car field
)))
1479 ;; For xfields we only check the label because the rest of VAL
1480 ;; can be anything. (xfields are unique within a record.)
1481 (if (eq 'xfields
(car field
))
1483 fields
(mapcar 'car fields
)))
1484 (while (and (not done
) (setq elt
(pop fields
)))
1487 (setq num
(1+ num
))))
1488 (unless done
(error "Field %s not found" val
))
1489 (list recnum
(car field
) num
))))))
1492 (defun bbdb-transpose-fields (arg)
1493 "Transpose previous and current field of a BBDB record.
1494 With numeric prefix ARG, take previous field and move it past ARG fields.
1495 With region active or ARG 0, transpose field point is in and field mark is in.
1497 Both fields must be in the same record, and must be of the same basic type
1498 \(that is, you can use this command to change the order in which phone numbers
1499 or email addresses are listed, but you cannot use it to make an address appear
1500 before a phone number; the order of field types is fixed).
1502 If the current field is the name field, transpose first and last name,
1503 irrespective of the value of ARG."
1504 ;; This functionality is inspired by `transpose-lines'.
1507 (let* ((ident (bbdb-ident-point))
1508 (record (and (car ident
) (car (nth (car ident
) bbdb-records
))))
1510 (cond ((not (car ident
))
1511 (error "Point not in BBDB record"))
1512 ((not (nth 1 ident
))
1513 (error "Point not in BBDB field"))
1514 ((eq 'name
(nth 1 ident
))
1515 ;; Transpose firstname and lastname
1516 (bbdb-record-set-name record
(bbdb-record-lastname record
)
1517 (bbdb-record-firstname record
)))
1518 ((not (integerp arg
))
1519 (error "Arg `%s' not an integer" arg
))
1520 ((not (nth 2 ident
))
1521 (error "Point not in a transposable field"))
1523 (if (or (use-region-p) (zerop arg
))
1524 (let ((ident2 (bbdb-ident-point
1525 (or (mark) (error "No mark set in this buffer")))))
1526 (unless (and (eq (car ident
) (car ident2
))
1527 (eq (cadr ident
) (cadr ident2
))
1528 (integerp (nth 2 ident2
)))
1529 (error "Mark (or point) not on transposable field"))
1530 (setq num1
(nth 2 ident
)
1531 num2
(nth 2 ident2
)))
1532 (setq num1
(1- (nth 2 ident
))
1534 (if (or (< (min num1 num2
) 0)
1535 (>= (max num1 num2
) (length (bbdb-record-field
1536 record
(nth 1 ident
)))))
1537 (error "Cannot transpose fields of different types")))
1538 (bbdb-record-set-field
1539 record
(nth 1 ident
)
1540 (bbdb-list-transpose (bbdb-record-field record
(nth 1 ident
))
1542 (bbdb-change-record record
)))
1545 (defun bbdb-delete-field-or-record (records field
&optional noprompt
)
1546 "For RECORDS delete FIELD.
1547 If FIELD is the `name' field, delete RECORDS from datanbase.
1548 Interactively, use BBDB prefix \
1549 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records',
1550 and FIELD is the field point is on.
1551 If prefix NOPROMPT is non-nil, do not confirm deletion."
1552 ;; The value of FIELD is whatever `bbdb-current-field' returns.
1553 ;; This way we can identify more accurately what really needs
1556 (list (bbdb-do-records) (bbdb-current-field) current-prefix-arg
))
1558 (unless field
(error "Not a field"))
1559 (setq records
(bbdb-record-list records
))
1560 (let* ((type (car field
))
1561 (type-x (if (eq type
'xfields
)
1565 (bbdb-delete-records records noprompt
)
1566 (if (memq type
'(firstname lastname
))
1567 (error "Cannot delete field `%s'" type
))
1568 (dolist (record records
)
1570 (y-or-n-p (format "delete this `%s' field (of %s)? "
1571 type-x
(bbdb-record-name record
))))
1572 (cond ((memq type
'(phone address
))
1573 (bbdb-record-set-field
1575 ;; We use `delete' which deletes all phone and address
1576 ;; fields equal to the current one. This works for
1577 ;; multiple records.
1578 (delete (nth 1 field
)
1579 (bbdb-record-field record type
))))
1580 ((memq type
'(affix organization mail aka
))
1581 (bbdb-record-set-field record type nil
))
1583 (bbdb-record-set-xfield record type-x nil
))
1584 (t (error "Unknown field %s" type
)))
1585 (bbdb-change-record record
))))))
1588 (defun bbdb-delete-records (records &optional noprompt
)
1590 Interactively, use BBDB prefix \
1591 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1592 If prefix NOPROMPT is non-nil, do not confirm deletion."
1593 (interactive (list (bbdb-do-records) current-prefix-arg
))
1595 (let ((all-records (bbdb-with-db-buffer bbdb-records
)))
1596 (dolist (record (bbdb-record-list records
))
1597 (cond ((not (memq record all-records
))
1598 ;; Possibly we changed RECORD before deleting it.
1599 ;; Otherwise, do nothing if RECORD is unknown to BBDB.
1600 (setq bbdb-changed-records
(delq record bbdb-changed-records
)))
1602 (y-or-n-p (format "Delete the BBDB record of %s? "
1603 (or (bbdb-record-name record
)
1604 (car (bbdb-record-mail record
))))))
1605 (bbdb-delete-record-internal record t
)
1606 (setq bbdb-changed-records
(delq record bbdb-changed-records
)))))))
1609 (defun bbdb-display-all-records (&optional layout
)
1611 If invoked in a *BBDB* buffer point stays on the currently visible record.
1612 Inverse of `bbdb-display-current-record'."
1613 (interactive (list (bbdb-layout-prefix)))
1614 (let ((current (ignore-errors (bbdb-current-record))))
1615 (bbdb-display-records (bbdb-records) layout
)
1616 (when (setq current
(assq current bbdb-records
))
1617 (redisplay) ; Strange display bug??
1618 (goto-char (nth 2 current
)))))
1619 ;; (set-window-point (selected-window) (nth 2 current)))))
1622 (defun bbdb-display-current-record (&optional layout
)
1623 "Narrow to current record. Inverse of `bbdb-display-all-records'."
1624 (interactive (list (bbdb-layout-prefix)))
1625 (bbdb-display-records (list (bbdb-current-record)) layout
))
1627 (defun bbdb-change-records-layout (records layout
)
1628 (dolist (record records
)
1629 (unless (eq layout
(nth 1 record
))
1630 (setcar (cdr record
) layout
)
1631 (bbdb-redisplay-record (car record
)))))
1634 (defun bbdb-toggle-records-layout (records &optional arg
)
1635 "Toggle layout of RECORDS (elided or expanded).
1636 Interactively, use BBDB prefix \
1637 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1638 With prefix ARG 0, RECORDS are displayed elided.
1639 With any other non-nil ARG, RECORDS are displayed expanded."
1640 (interactive (list (bbdb-do-records t
) current-prefix-arg
))
1641 (let* ((record (bbdb-current-record))
1642 (current-layout (nth 1 (assq record bbdb-records
)))
1644 ;; Try to consider only those layouts that have the `toggle'
1646 (or (delq nil
(mapcar (lambda (l)
1647 (if (and (assq 'toggle l
)
1648 (cdr (assq 'toggle l
)))
1655 ((null current-layout
)
1657 ;; layout is not the last element of layout-alist
1658 ;; and we switch to the following element of layout-alist
1659 ((caar (cdr (memq (assq current-layout layout-alist
)
1661 (t ; layout is the last element of layout-alist
1662 ;; and we switch to the first element of layout-alist
1663 (caar layout-alist
)))))
1664 (message "Using %S layout" layout
)
1665 (bbdb-change-records-layout (bbdb-record-list records t
) layout
)))
1668 (defun bbdb-display-records-completely (records)
1669 "Display RECORDS using layout `full-multi-line' (i.e., display all fields).
1670 Interactively, use BBDB prefix \
1671 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
1672 (interactive (list (bbdb-do-records t
)))
1673 (let* ((record (bbdb-current-record))
1674 (current-layout (nth 1 (assq record bbdb-records
)))
1675 (layout (if (not (eq current-layout
'full-multi-line
))
1678 (bbdb-change-records-layout (bbdb-record-list records t
) layout
)))
1681 (defun bbdb-display-records-with-layout (records layout
)
1682 "Display RECORDS using LAYOUT.
1683 Interactively, use BBDB prefix \
1684 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
1686 (list (bbdb-do-records t
)
1687 (intern (completing-read "Layout: "
1689 (list (symbol-name (car i
))))
1690 bbdb-layout-alist
)))))
1691 (bbdb-change-records-layout (bbdb-record-list records t
) layout
))
1694 (defun bbdb-omit-record (n)
1695 "Remove current record from the display without deleting it from BBDB.
1696 With prefix N, omit the next N records. If negative, omit backwards."
1698 (let ((num (get-text-property (if (and (not (bobp)) (eobp))
1699 (1- (point)) (point))
1700 'bbdb-record-number
)))
1702 (setq n
(min n
(- (length bbdb-records
) num
)))
1703 (setq n
(min (- n
) num
))
1704 (bbdb-prev-record n
))
1706 (bbdb-redisplay-record (bbdb-current-record) nil t
))))
1708 ;;; Fixing up bogus records
1711 (defun bbdb-merge-records (record1 record2
)
1712 "Merge RECORD1 into RECORD2, then delete RECORD1 and return RECORD2.
1713 If both records have name fields ask which one to use.
1714 Concatenate other fields, ignoring duplicates.
1715 RECORD1 need not be known to BBDB, its hash and cache are ignored.
1716 Update hash and cache for RECORD2.
1718 Interactively, RECORD1 is the current record; prompt for RECORD2.
1719 With prefix, RECORD2 defaults to the first record with the same name."
1721 (let* ((_ (bbdb-editable))
1722 (record1 (bbdb-current-record))
1723 (name (bbdb-record-name record1
))
1724 (record2 (and current-prefix-arg
1725 ;; take the first record with the same name
1727 (bbdb-search (bbdb-records) :all-names name
))))))
1729 (message "Merge current record with duplicate record `%s'" name
)
1733 (bbdb-completing-read-record
1734 (format "merge record \"%s\" into: "
1735 (or (bbdb-record-name record1
)
1736 (car (bbdb-record-mail record1
))
1741 (cond ((eq record1 record2
) (error "Records are equal"))
1742 ((null record2
) (error "No record to merge with")))
1745 (let* ((new-name (bbdb-record-name record2
))
1746 (old-name (bbdb-record-name record1
))
1747 (old-aka (bbdb-record-aka record1
))
1750 (cond ((or (string= "" old-name
)
1751 (bbdb-string= old-name new-name
))
1752 (cons (bbdb-record-firstname record2
)
1753 (bbdb-record-lastname record2
)))
1754 ((string= "" new-name
)
1755 (cons (bbdb-record-firstname record1
)
1756 (bbdb-record-lastname record1
)))
1759 (format "Use name \"%s\" instead of \"%s\"? "
1762 (setq extra-name new-name
)
1763 (cons (bbdb-record-firstname record1
)
1764 (bbdb-record-lastname record1
)))
1765 (setq extra-name old-name
)
1766 (cons (bbdb-record-firstname record2
)
1767 (bbdb-record-lastname record2
)))
1768 (unless (bbdb-eval-spec
1769 (bbdb-add-job bbdb-add-aka record2 extra-name
)
1770 (format "Keep \"%s\" as an alternate name? "
1772 (setq extra-name nil
)))))))
1774 (bbdb-record-set-name record2
(car name
) (cdr name
))
1776 (if extra-name
(push extra-name old-aka
))
1777 ;; It is better to delete RECORD1 at the end.
1778 ;; So we must temporarily allow duplicates in RECORD2.
1779 (let ((bbdb-allow-duplicates t
))
1780 (bbdb-record-set-field record2
'aka old-aka t
)))
1782 ;; Merge other stuff
1783 (bbdb-record-set-field record2
'affix
1784 (bbdb-record-affix record1
) t
)
1785 (bbdb-record-set-field record2
'organization
1786 (bbdb-record-organization record1
) t
)
1787 (bbdb-record-set-field record2
'phone
1788 (bbdb-record-phone record1
) t
)
1789 (bbdb-record-set-field record2
'address
1790 (bbdb-record-address record1
) t
)
1791 (let ((bbdb-allow-duplicates t
))
1792 (bbdb-record-set-field record2
'mail
1793 (bbdb-record-mail record1
) t
))
1794 (bbdb-record-set-field record2
'xfields
1795 (bbdb-record-xfields record1
) t
)
1797 ;; `bbdb-delete-records' does nothing if RECORD1 is not known to BBDB.
1798 (bbdb-delete-records (list record1
) 'noprompt
)
1799 (bbdb-change-record record2
)
1802 ;; The following sorting functions are also intended for use
1803 ;; in `bbdb-change-hook'. Then they will be called with one arg, the record.
1806 (defun bbdb-sort-addresses (records &optional update
)
1807 "Sort the addresses in RECORDS according to the label.
1808 Interactively, use BBDB prefix \
1809 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1810 If UPDATE is non-nil (as in interactive calls) update the database.
1811 Otherwise, this is the caller's responsiblity (for example, when used
1812 in `bbdb-change-hook')."
1813 (interactive (list (bbdb-do-records) t
))
1815 (dolist (record (bbdb-record-list records
))
1816 (bbdb-record-set-address
1817 record
(sort (bbdb-record-address record
)
1818 (lambda (xx yy
) (string< (aref xx
0) (aref yy
0)))))
1820 (bbdb-change-record record
))))
1823 (defun bbdb-sort-phones (records &optional update
)
1824 "Sort the phones in RECORDS according to the label.
1825 Interactively, use BBDB prefix \
1826 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1827 If UPDATE is non-nil (as in interactive calls) update the database.
1828 Otherwise, this is the caller's responsiblity (for example, when used
1829 in `bbdb-change-hook')."
1830 (interactive (list (bbdb-do-records) t
))
1832 (dolist (record (bbdb-record-list records
))
1833 (bbdb-record-set-phone
1834 record
(sort (bbdb-record-phone record
)
1835 (lambda (xx yy
) (string< (aref xx
0) (aref yy
0)))))
1837 (bbdb-change-record record
))))
1840 (defun bbdb-sort-xfields (records &optional update
)
1841 "Sort the xfields in RECORDS according to `bbdb-xfields-sort-order'.
1842 Interactively, use BBDB prefix \
1843 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1844 If UPDATE is non-nil (as in interactive calls) update the database.
1845 Otherwise, this is the caller's responsiblity (for example, when used
1846 in `bbdb-change-hook')."
1847 (interactive (list (bbdb-do-records) t
))
1849 (dolist (record (bbdb-record-list records
))
1850 (bbdb-record-set-xfields
1851 record
(sort (bbdb-record-xfields record
)
1853 (< (or (cdr (assq (car a
) bbdb-xfields-sort-order
)) 100)
1854 (or (cdr (assq (car b
) bbdb-xfields-sort-order
)) 100)))))
1856 (bbdb-change-record record
))))
1857 (define-obsolete-function-alias 'bbdb-sort-notes
'bbdb-sort-xfields
"3.0")
1859 ;;; Send-Mail interface
1862 (defun bbdb-dwim-mail (record &optional mail
)
1864 "Return a string to use as the mail address of RECORD.
1865 The name in the mail address is formatted obeying `bbdb-mail-name-format'
1866 and `bbdb-mail-name'. However, if both the first name and last name
1867 are constituents of the address as in John.Doe@Some.Host,
1868 and `bbdb-mail-avoid-redundancy' is non-nil, then the address is used as is
1869 and `bbdb-mail-name-format' and `bbdb-mail-name' are ignored.
1870 If `bbdb-mail-avoid-redundancy' is 'mail-only the name is never included.
1871 MAIL may be a mail address to be used for RECORD.
1872 If MAIL is an integer, use the MAILth mail address of RECORD.
1873 If MAIL is nil use the first mail address of RECORD."
1875 (let ((mails (bbdb-record-mail record
)))
1876 (setq mail
(or (and (integerp mail
) (nth mail mails
))
1878 (unless mail
(error "Record has no mail addresses"))
1880 (cond ((let ((address (bbdb-decompose-bbdb-address mail
)))
1881 ;; We need to know whether we should quote the name part of MAIL
1882 ;; because of special characters.
1884 (setq mail
(cadr address
)
1887 ((functionp bbdb-mail-name
)
1888 (setq name
(funcall bbdb-mail-name record
))
1890 (setq fn
(car name
) ln
(cdr name
)
1891 name
(if (eq bbdb-mail-name-format
'first-last
)
1892 (bbdb-concat 'name-first-last fn ln
)
1893 (bbdb-concat 'name-last-first ln fn
)))
1894 (let ((pair (bbdb-divide-name name
)))
1895 (setq fn
(car pair
) ln
(cdr pair
)))))
1896 ((setq name
(bbdb-record-xfield record bbdb-mail-name
))
1897 (let ((pair (bbdb-divide-name name
)))
1898 (setq fn
(car pair
) ln
(cdr pair
))))
1900 (setq name
(if (eq bbdb-mail-name-format
'first-last
)
1901 (bbdb-record-name record
)
1902 (bbdb-record-name-lf record
))
1903 fn
(bbdb-record-firstname record
)
1904 ln
(bbdb-record-lastname record
))))
1905 (if (or (not name
) (equal "" name
)
1906 (eq 'mail-only bbdb-mail-avoid-redundancy
)
1907 (and bbdb-mail-avoid-redundancy
1909 (let ((fnq (regexp-quote fn
))
1910 (lnq (regexp-quote ln
)))
1911 (or (string-match (concat "\\`[^!@%]*\\b" fnq
1912 "\\b[^!%@]+\\b" lnq
"\\b")
1914 (string-match (concat "\\`[^!@%]*\\b" lnq
1915 "\\b[^!%@]+\\b" fnq
"\\b")
1918 (string-match (concat "\\`[^!@%]*\\b"
1919 (regexp-quote (or fn ln
)) "\\b")
1922 ;; If the name contains backslashes or double-quotes, backslash them.
1923 (setq name
(replace-regexp-in-string "[\\\"]" "\\\\\\&" name
))
1924 ;; If the name contains control chars or RFC822 specials, it needs
1925 ;; to be enclosed in quotes. This quotes a few extra characters as
1926 ;; well (!,%, and $) just for common sense.
1927 ;; `define-mail-alias' uses regexp "[^- !#$%&'*+/0-9=?A-Za-z^_`{|}~]".
1928 (format (if (string-match "[][[:cntrl:]\177()<>@,;:.!$%[:nonascii:]]" name
)
1933 (defun bbdb-compose-mail (&rest args
)
1934 "Start composing a mail message to send.
1935 Use `bbdb-mail-user-agent' or (if nil) use `mail-user-agent'.
1936 ARGS are passed to `compose-mail'."
1937 (let ((mail-user-agent (or bbdb-mail-user-agent mail-user-agent
)))
1938 (apply 'compose-mail args
)))
1941 (defun bbdb-mail (records &optional subject n verbose
)
1942 "Compose a mail message to RECORDS (optional: using SUBJECT).
1943 Interactively, use BBDB prefix \
1944 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1945 By default, the first mail addresses of RECORDS are used.
1946 If prefix N is a number, use Nth mail address of RECORDS (starting from 1).
1947 If prefix N is C-u (t noninteractively) use all mail addresses of RECORDS.
1948 If VERBOSE is non-nil (as in interactive calls) be verbose."
1949 (interactive (list (bbdb-do-records) nil
1950 (or (consp current-prefix-arg
)
1953 (setq records
(bbdb-record-list records
))
1955 (if verbose
(message "No records"))
1956 (let ((to (bbdb-mail-address records n nil verbose
)))
1957 (unless (string= "" to
)
1958 (bbdb-compose-mail to subject
)))))
1960 (defun bbdb-mail-address (records &optional n kill-ring-save verbose
)
1961 "Return mail addresses of RECORDS as a string.
1962 Interactively, use BBDB prefix \
1963 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1964 By default, the first mail addresses of RECORDS are used.
1965 If prefix N is a number, use Nth mail address of RECORDS (starting from 1).
1966 If prefix N is C-u (t noninteractively) use all mail addresses of RECORDS.
1967 If KILL-RING-SAVE is non-nil (as in interactive calls), copy mail addresses
1968 to kill ring. If VERBOSE is non-nil (as in interactive calls) be verbose."
1969 (interactive (list (bbdb-do-records)
1970 (or (consp current-prefix-arg
)
1973 (setq records
(bbdb-record-list records
))
1975 (progn (if verbose
(message "No records")) "")
1976 (let ((good "") bad
)
1977 (dolist (record records
)
1978 (let ((mails (bbdb-record-mail record
)))
1982 (setq good
(bbdb-concat ",\n\t"
1984 (mapcar (lambda (mail)
1985 (bbdb-dwim-mail record mail
))
1988 (setq good
(bbdb-concat ",\n\t" good
1989 (bbdb-dwim-mail record
(or (and (numberp n
)
1992 (when (and bad verbose
)
1993 (message "No mail addresses for %s."
1994 (mapconcat 'bbdb-record-name
(nreverse bad
) ", "))
1995 (unless (string= "" good
) (sit-for 2)))
1996 (when (and kill-ring-save
(not (string= good
"")))
1998 (if verbose
(message "%s" good
)))
2001 ;; Is there better way to yank selected mail addresses from the BBDB
2002 ;; buffer into a message buffer? We need some kind of a link between
2003 ;; the BBDB buffer and the message buffer, where the mail addresses
2004 ;; are supposed to go. Then we could browse the BBDB buffer and copy
2005 ;; selected mail addresses from the BBDB buffer into a message buffer.
2007 (defun bbdb-mail-yank ()
2008 "CC the people displayed in the *BBDB* buffer on this mail message.
2009 The primary mail of each of the records currently listed in the
2010 *BBDB* buffer will be appended to the CC: field of the current buffer."
2012 (let ((addresses (with-current-buffer bbdb-buffer-name
2015 (if (bbdb-record-mail (car x
))
2016 (bbdb-dwim-mail (car x
))))
2018 (case-fold-search t
))
2019 (goto-char (point-min))
2020 (if (re-search-forward "^CC:[ \t]*" nil t
)
2021 ;; We have a CC field. Move to the end of it, inserting a comma
2022 ;; if there are already addresses present.
2025 (while (looking-at "\n[ \t]")
2026 (forward-char) (end-of-line))
2029 ;; Otherwise, if there is an empty To: field, move to the end of it.
2030 (unless (and (re-search-forward "^To:[ \t]*" nil t
)
2032 ;; Otherwise, insert an empty CC: field.
2034 (while (looking-at "\n[ \t]")
2035 (forward-char) (end-of-line))
2038 ;; Now insert each of the addresses on its own line.
2040 (insert (car addresses
))
2041 (when (cdr addresses
) (insert ",\n") (indent-relative))
2042 (setq addresses
(cdr addresses
)))))
2043 (define-obsolete-function-alias 'bbdb-yank-addresses
'bbdb-mail-yank
"3.0")
2048 (defun bbdb-completion-predicate (key records
)
2049 "For use as the third argument to `completing-read'.
2050 Obey `bbdb-completion-list'."
2051 (cond ((null bbdb-completion-list
)
2053 ((eq t bbdb-completion-list
)
2056 (catch 'bbdb-hash-ok
2057 (dolist (record records
)
2058 (bbdb-hash-p key record bbdb-completion-list
))
2061 (defun bbdb-completing-read-records (prompt &optional omit-records
)
2062 "Read and return list of records from the bbdb.
2063 Completion is done according to `bbdb-completion-list'. If the user
2064 just hits return, nil is returned. Otherwise, a valid response is forced."
2065 (let* ((completion-ignore-case t
)
2066 (string (completing-read prompt bbdb-hashtable
2067 'bbdb-completion-predicate t
)))
2068 (unless (string= "" string
)
2070 (dolist (record (gethash string bbdb-hashtable
))
2071 (if (not (memq record omit-records
))
2072 (push record records
)))
2073 (delete-dups records
)))))
2075 (defun bbdb-completing-read-record (prompt &optional omit-records
)
2076 "Prompt for and return a single record from the bbdb;
2077 completion is done according to `bbdb-completion-list'. If the user
2078 just hits return, nil is returned. Otherwise, a valid response is forced.
2079 If OMIT-RECORDS is non-nil it should be a list of records to dis-allow
2081 (let ((records (bbdb-completing-read-records prompt omit-records
)))
2082 (cond ((eq (length records
) 1)
2084 ((> (length records
) 1)
2085 (bbdb-display-records records
'one-line
)
2086 (let* ((count (length records
))
2087 (result (completing-read
2088 (format "Which record (1-%s): " count
)
2089 (mapcar 'number-to-string
(number-sequence 1 count
))
2091 (nth (1- (string-to-number result
)) records
))))))
2094 (defun bbdb-completing-read-mails (prompt &optional init
)
2095 "Like `read-string', but allows `bbdb-complete-mail' style completion."
2096 (read-from-minibuffer prompt init
2097 bbdb-completing-read-mails-map
))
2099 (defconst bbdb-quoted-string-syntax-table
2100 (let ((st (make-syntax-table)))
2101 (modify-syntax-entry ?
\\ "\\" st
)
2102 (modify-syntax-entry ?
\" "\"" st
)
2104 "Syntax-table to parse matched quotes. Used by `bbdb-complete-mail'.")
2107 (defun bbdb-complete-mail (&optional beg cycle-completion-buffer
)
2108 "In a mail buffer, complete the user name or mail before point.
2109 Completion happens up to the preceeding colon, comma, or BEG.
2110 Return non-nil if there is a valid completion, else return nil.
2112 Completion behaviour obeys `bbdb-completion-list' (see there).
2113 If what has been typed matches a unique BBDB record, insert an address
2114 formatted by `bbdb-dwim-mail' (see there). Also, display this record
2115 if `bbdb-completion-display-record' is non-nil,
2116 If what has been typed is a valid completion but does not match
2117 a unique record, display a list of completions.
2118 If the completion is done and `bbdb-complete-mail-allow-cycling' is t
2119 then cycle through the mails for the matching record. If BBDB
2120 would format a given address different from what we have in the mail buffer,
2121 the first round of cycling reformats the address accordingly, then we cycle
2122 through the mails for the matching record.
2123 With prefix CYCLE-COMPLETION-BUFFER non-nil, display a list of all mails
2124 available for cycling.
2126 Set the variable `bbdb-complete-mail' non-nil for enabling this feature
2127 as part of the MUA insinuation."
2128 (interactive (list nil current-prefix-arg
))
2130 (bbdb-buffer) ; Make sure the database is initialized.
2132 ;; Completion should begin after the preceding comma (separating
2133 ;; two addresses) or colon (separating the header field name
2134 ;; from the header field body). We want to ignore these characters
2135 ;; if they appear inside a quoted string (RFC 5322, Sec. 3.2.4).
2136 ;; Note also that a quoted string may span multiple lines
2137 ;; (RFC 5322, Sec. 2.2.3).
2138 ;; So to be save, we go back to the beginning of the header field body
2139 ;; (past the colon, when we are certainly not inside a quoted string),
2140 ;; then we parse forward, looking for commas not inside a quoted string
2141 ;; and positioned before END. - This fails with an unbalanced quote.
2142 ;; But an unbalanced quote is bound to fail anyway.
2143 (when (and (not beg
)
2145 (save-restriction ; `mail-header-end'
2153 ;; A header field name must appear at the beginning of a line,
2154 ;; and it must be terminated by a colon.
2155 (re-search-backward "^[^ \t\n:][^:]*:[ \t\n]+")
2156 (setq beg
(match-end 0)
2159 ;; If we are inside a syntactically correct header field,
2160 ;; all continuation lines in between the field name and point
2161 ;; must begin with a white space character.
2162 (if (re-search-forward "\n[^ \t]" end t
)
2163 ;; An invalid header is identified via BEG set to nil.
2165 ;; Parse field body up to END
2166 (with-syntax-table bbdb-quoted-string-syntax-table
2167 (while (setq pnt
(re-search-forward ",[ \t\n]*" end t
))
2168 (setq state
(parse-partial-sexp start pnt nil nil state
)
2170 (unless (nth 3 state
) (setq beg pnt
))))))))
2172 ;; Do we have a meaningful way to set BEG if we are not in a message header?
2174 (message "Not a valid buffer position for mail completion")
2177 (let* ((end (point))
2178 (done (unless beg
'nothing
))
2179 (orig (and beg
(buffer-substring beg end
)))
2180 (completion-ignore-case t
)
2181 (completion (and orig
2182 (try-completion orig bbdb-hashtable
2183 'bbdb-completion-predicate
)))
2184 all-completions dwim-completions one-record
)
2187 ;; We get fooled if a partial COMPLETION matches "," (for example,
2188 ;; a comma in lf-name). Such a partial COMPLETION cannot be protected
2189 ;; by quoting. Then the comma gets interpreted as BEG.
2190 ;; So we never perform partial completion beyond the first comma.
2191 ;; This works even if we have just one record matching ORIG (thus
2192 ;; allowing dwim-completion) because ORIG is a substring of COMPLETION
2193 ;; even after COMPLETION got truncated; and ORIG by itself must be
2194 ;; sufficient to identify this record.
2195 ;; Yet if multiple records match ORIG we can only offer a *Completions*
2197 (if (and (stringp completion
)
2198 (string-match "," completion
))
2199 (setq completion
(substring completion
0 (match-beginning 0))))
2201 (setq all-completions
(all-completions orig bbdb-hashtable
2202 'bbdb-completion-predicate
))
2203 ;; Resolve the records matching ORIG:
2204 ;; Multiple completions may match the same record
2205 (let ((records (delete-dups
2206 (apply 'append
(mapcar (lambda (compl)
2207 (gethash compl bbdb-hashtable
))
2208 all-completions
)))))
2209 ;; Is there only one matching record?
2210 (setq one-record
(and (not (cdr records
))
2213 ;; Clean up *Completions* buffer window, if it exists
2214 (let ((window (get-buffer-window "*Completions*")))
2215 (if (window-live-p window
)
2216 (quit-window nil window
)))
2219 ;; Match for a single record
2221 (let ((completion-list (if (eq t bbdb-completion-list
)
2222 '(fl-name lf-name mail aka organization
)
2223 bbdb-completion-list
))
2224 (mails (bbdb-record-mail one-record
))
2228 (message "Matching record has no mail field")
2230 (setq done
'nothing
))
2232 ;; Determine the mail address of ONE-RECORD to use for ADDRESS.
2233 ;; Do we have a preferential order for the following tests?
2234 ;; (1) If ORIG matches name, AKA, or organization of ONE-RECORD,
2235 ;; then ADDRESS will be the first mail address of ONE-RECORD.
2236 (if (try-completion orig
2238 (if (memq 'fl-name completion-list
)
2239 (list (or (bbdb-record-name one-record
) "")))
2240 (if (memq 'lf-name completion-list
)
2241 (list (or (bbdb-record-name-lf one-record
) "")))
2242 (if (memq 'aka completion-list
)
2243 (bbdb-record-field one-record
'aka-all
))
2244 (if (memq 'organization completion-list
)
2245 (bbdb-record-organization one-record
))))
2246 (setq mail
(car mails
)))
2247 ;; (2) If ORIG matches one or multiple mail addresses of ONE-RECORD,
2248 ;; then we take the first one matching ORIG.
2249 ;; We got here with MAIL nil only if `bbdb-completion-list'
2250 ;; includes 'mail or 'primary.
2252 (while (setq elt
(pop mails
))
2253 (if (try-completion orig
(list elt
))
2256 ;; This error message indicates a bug!
2257 (unless mail
(error "No match for %s" orig
))
2259 (let ((dwim-mail (bbdb-dwim-mail one-record mail
)))
2260 (if (string= dwim-mail orig
)
2261 ;; We get here if `bbdb-mail-avoid-redundancy' is 'mail-only
2262 ;; and `bbdb-completion-list' includes 'mail.
2263 (unless (and bbdb-complete-mail-allow-cycling
2264 (< 1 (length (bbdb-record-mail one-record
))))
2265 (setq done
'unchanged
))
2266 ;; Replace the text with the expansion
2267 (delete-region beg end
)
2269 (bbdb-complete-mail-cleanup dwim-mail beg
)
2270 (setq done
'unique
))))))
2272 ;; Partial completion
2273 ((and (stringp completion
)
2274 (not (bbdb-string= orig completion
)))
2275 (delete-region beg end
)
2277 (setq done
'partial
))
2279 ;; Partial match not allowing further partial completion
2281 (let ((completion-list (if (eq t bbdb-completion-list
)
2282 '(fl-name lf-name mail aka organization
)
2283 bbdb-completion-list
)))
2284 ;; Now collect all the dwim-addresses for each completion.
2285 ;; Add it if the mail is part of the completions
2286 (dolist (key all-completions
)
2287 (dolist (record (gethash key bbdb-hashtable
))
2288 (let ((mails (bbdb-record-mail record
))
2291 (dolist (field completion-list
)
2292 (cond ((eq field
'fl-name
)
2293 (if (bbdb-string= key
(bbdb-record-name record
))
2294 (push (car mails
) accept
)))
2295 ((eq field
'lf-name
)
2296 (if (bbdb-string= key
(bbdb-cache-lf-name
2297 (bbdb-record-cache record
)))
2298 (push (car mails
) accept
)))
2300 (if (member-ignore-case key
(bbdb-record-field
2302 (push (car mails
) accept
)))
2303 ((eq field
'organization
)
2304 (if (member-ignore-case key
(bbdb-record-organization
2306 (push (car mails
) accept
)))
2307 ((eq field
'primary
)
2308 (if (bbdb-string= key
(car mails
))
2309 (push (car mails
) accept
)))
2311 (dolist (mail mails
)
2312 (if (bbdb-string= key mail
)
2313 (push mail accept
))))))
2314 (dolist (mail (delete-dups accept
))
2315 (push (bbdb-dwim-mail record mail
) dwim-completions
))))))
2317 (setq dwim-completions
(sort (delete-dups dwim-completions
)
2319 (cond ((not dwim-completions
)
2320 (message "Matching record has no mail field")
2322 (setq done
'nothing
))
2323 ;; DWIM-COMPLETIONS may contain only one element,
2324 ;; if multiple completions match the same record.
2325 ;; Then we may proceed with DONE set to `unique'.
2326 ((eq 1 (length dwim-completions
))
2327 (delete-region beg end
)
2328 (insert (car dwim-completions
))
2329 (bbdb-complete-mail-cleanup (car dwim-completions
) beg
)
2330 (setq done
'unique
))
2331 (t (setq done
'choose
)))))))
2333 ;; By now, we have considered all possiblities to perform a completion.
2334 ;; If nonetheless we haven't done anything so far, consider cycling.
2336 ;; Completion and cycling are really two very separate things.
2337 ;; Completion is controlled by the user variable `bbdb-completion-list'.
2338 ;; Cycling assumes that ORIG already holds a valid RFC 822 mail address.
2339 ;; Therefore cycling may consider different records than completion.
2340 (when (and (not done
) bbdb-complete-mail-allow-cycling
)
2341 ;; find the record we are working on.
2342 (let* ((address (bbdb-extract-address-components orig
))
2343 (record (car (bbdb-message-search
2344 (car address
) (cadr address
)))))
2346 (setq dwim-completions
2347 (mapcar (lambda (m) (bbdb-dwim-mail record m
))
2348 (bbdb-record-mail record
))))
2349 (cond ((and (= 1 (length dwim-completions
))
2350 (string= orig
(car dwim-completions
)))
2351 (setq done
'unchanged
))
2352 (cycle-completion-buffer ; use completion buffer
2353 (setq done
'cycle-choose
))
2354 ;; Reformatting / Clean up:
2355 ;; If the canonical mail address (nth 1 address)
2356 ;; matches the Nth canonical mail address of RECORD,
2357 ;; but ORIG is not `equal' to (bbdb-dwim-mail record n),
2358 ;; then we replace ORIG by (bbdb-dwim-mail record n).
2359 ;; For example, the address "JOHN SMITH <FOO@BAR.COM>"
2360 ;; gets reformatted as "John Smith <foo@bar.com>".
2361 ;; We attempt this reformatting before the yet more
2362 ;; aggressive proper cycling.
2363 ((let* ((cmails (bbdb-record-mail-canon record
))
2364 (len (length cmails
))
2366 (while (and (not done
)
2367 (setq mail
(pop cmails
)))
2368 (when (and (bbdb-string= mail
(nth 1 address
)) ; ignore case
2369 (not (string= orig
(setq dwim-mail
2370 (nth (- len
1 (length cmails
))
2371 dwim-completions
)))))
2372 (delete-region beg end
)
2374 (bbdb-complete-mail-cleanup dwim-mail beg
)
2375 (setq done
'reformat
)))
2379 ;; ORIG is `equal' to an element of DWIM-COMPLETIONS
2380 ;; Use the next element of DWIM-COMPLETIONS.
2381 (let ((dwim-mail (or (nth 1 (member orig dwim-completions
))
2382 (nth 0 dwim-completions
))))
2383 ;; replace with new mail address
2384 (delete-region beg end
)
2386 (bbdb-complete-mail-cleanup dwim-mail beg
)
2387 (setq done
'cycle
)))))))
2389 (when (member done
'(choose cycle-choose
))
2390 ;; Pop up a completions window using DWIM-COMPLETIONS.
2391 ;; `completion-in-region' does not work here as DWIM-COMPLETIONS
2392 ;; is not a collection for completion in the usual sense, but it
2393 ;; is really a list of replacements.
2394 (let ((status (not (eq (selected-window) (minibuffer-window))))
2395 (completion-base-position (list beg end
))
2396 ;; We first call the default value of
2397 ;; `completion-list-insert-choice-function'
2398 ;; before performing our own stuff.
2399 (completion-list-insert-choice-function
2400 `(lambda (beg end text
)
2401 ,(if (boundp 'completion-list-insert-choice-function
)
2402 `(funcall ',completion-list-insert-choice-function
2404 (bbdb-complete-mail-cleanup text beg
))))
2405 (if status
(message "Making completion list..."))
2406 (with-output-to-temp-buffer "*Completions*"
2407 (display-completion-list dwim-completions
))
2408 (if status
(message "Making completion list...done"))))
2410 ;; If DONE is `nothing' return nil so that possibly some other code
2412 (unless (eq done
'nothing
)
2416 (define-obsolete-function-alias 'bbdb-complete-name
'bbdb-complete-mail
"3.0")
2418 (defun bbdb-complete-mail-cleanup (mail beg
)
2419 "Clean up after inserting MAIL at position BEG.
2420 If we are past `fill-column', wrap at the previous comma."
2421 (if (and (not (auto-fill-function))
2422 (>= (current-column) fill-column
))
2425 (when (search-backward "," (line-beginning-position) t
)
2429 (if (looking-at "[ \t\n]+")
2430 (delete-region (point) (match-end 0))))))
2431 (if (or bbdb-completion-display-record bbdb-complete-mail-hook
)
2432 (let* ((address (bbdb-extract-address-components mail
))
2433 (records (bbdb-message-search (car address
) (nth 1 address
))))
2434 ;; Update the *BBDB* buffer if desired.
2435 (if bbdb-completion-display-record
2436 (let ((bbdb-silent-internal t
))
2437 ;; FIXME: This pops up *BBDB* before removing *Completions*
2438 (bbdb-display-records records nil t
)))
2439 ;; `bbdb-complete-mail-hook' may access MAIL, ADDRESS, and RECORDS.
2440 (run-hooks 'bbdb-complete-mail-hook
))))
2442 ;;; interface to mail-abbrevs.el.
2445 (defun bbdb-mail-aliases (&optional force-rebuilt noisy
)
2446 "Define mail aliases for the records in the database.
2447 Define a mail alias for every record that has a `mail-alias' field
2448 which is the contents of that field.
2449 If there are multiple comma-separated words in the `mail-alias' field,
2450 then all of those words will be defined as aliases for that person.
2452 If multiple records in the database have the same mail alias,
2453 then that alias expands to a comma-separated list of the mail addresses
2454 of all of these people.
2455 Add this command to `mail-setup-hook'.
2457 Mail aliases are (re)built only if `bbdb-mail-aliases-need-rebuilt' is non-nil
2458 because the database was newly loaded or it has been edited.
2459 Rebuilding the aliases is enforced if prefix FORCE-REBUILT is t."
2460 (interactive (list current-prefix-arg t
))
2461 ;; Build `mail-aliases' if not yet done.
2462 ;; Note: `mail-abbrevs-setup' rebuilds the mail-aliases only if
2463 ;; `mail-personal-alias-file' has changed. So it would not do anything
2464 ;; if we want to rebuild the mail-aliases because of changes in BBDB.
2465 (if (or force-rebuilt
(eq t mail-aliases
)) (build-mail-aliases))
2467 ;; We should be cleverer here and instead of rebuilding all aliases
2468 ;; we should just do what's necessary, i.e. remove deleted records
2469 ;; and add new records
2470 ;; Calling `bbdb-records' can change `bbdb-mail-aliases-need-rebuilt'
2471 (let ((records (bbdb-search (bbdb-records) :xfield
(cons bbdb-mail-alias-field
".")))
2473 (if (not (or force-rebuilt bbdb-mail-aliases-need-rebuilt
))
2474 (if noisy
(message "BBDB mail alias: nothing to do"))
2475 (setq bbdb-mail-aliases-need-rebuilt nil
)
2477 ;; collect an alist of (alias rec1 [rec2 ...])
2478 (dolist (record records
)
2479 (if (bbdb-record-mail record
)
2480 (dolist (alias (bbdb-record-xfield-split record bbdb-mail-alias-field
))
2481 (if (setq match
(assoc alias results
))
2482 ;; If an alias appears more than once, we collect all records
2483 ;; that refer to it.
2484 (nconc match
(list record
))
2485 (push (list alias record
) results
)))
2487 (bbdb-warn "record %S has no mail address, but the aliases: %s"
2488 (bbdb-record-name record
)
2489 (bbdb-record-xfield record bbdb-mail-alias-field
))
2492 ;; Iterate over the results and create the aliases
2493 (dolist (result results
)
2494 (let* ((aliasstem (car result
))
2497 ;; for group aliases we just take all the primary mails
2498 ;; and define only one expansion!
2499 (list (mapconcat (lambda (record) (bbdb-dwim-mail record
))
2500 (cdr result
) mail-alias-separator-string
))
2501 ;; this is an alias for a single person so deal with it
2502 ;; according to `bbdb-mail-alias'
2503 (let* ((record (nth 1 result
))
2504 (mails (bbdb-record-mail record
)))
2505 (if (or (eq 'first bbdb-mail-alias
)
2507 ;; Either we want to define only one alias for
2508 ;; the first mail address or there is anyway
2509 ;; only one address. In either case, we take
2510 ;; take only the first address.
2511 (list (bbdb-dwim-mail record
(car mails
)))
2512 ;; We need to deal with more than one mail address...
2513 (let* ((all (mapcar (lambda (m) (bbdb-dwim-mail record m
))
2515 (star (bbdb-concat mail-alias-separator-string all
)))
2516 (if (eq 'star bbdb-mail-alias
)
2517 (list star
(car all
))
2518 ;; if `bbdb-mail-alias' is 'all, we create
2519 ;; two aliases for the primary mail address
2520 (cons star
(cons (car all
) all
))))))))
2521 (count -
1) ; n=-1: <alias>*; n=0: <alias>; n>0: <alias>n
2522 (len (length expansions
))
2525 ;; create the aliases for each expansion
2526 (dolist (expansion expansions
)
2527 (cond ((or (= 1 len
)
2529 (setq alias aliasstem
))
2530 ((= count -
1) ;; all the mails of a record
2531 (setq alias
(concat aliasstem
"*")))
2532 (t ;; <alias>n for each mail of a record
2533 (setq alias
(format "%s%s" aliasstem count
))))
2534 (setq count
(1+ count
))
2536 (bbdb-pushnew (cons alias expansion
) mail-aliases
)
2538 (define-mail-abbrev alias expansion
)
2539 (unless (setq f-alias
(intern-soft (downcase alias
) mail-abbrevs
))
2540 (error "Cannot find the alias"))
2542 ;; `define-mail-abbrev' initializes f-alias to be
2543 ;; `mail-abbrev-expand-hook'. We replace this by
2544 ;; `bbdb-mail-abbrev-expand-hook'
2545 (unless (eq (symbol-function f-alias
) 'mail-abbrev-expand-hook
)
2546 (error "mail-aliases contains unexpected hook %s"
2547 (symbol-function f-alias
)))
2548 ;; `bbdb-mail-abbrev-hook' is called with mail addresses instead of
2549 ;; bbdb records to avoid keeping pointers to records, which would
2550 ;; lose if the database was reverted.
2551 ;; `bbdb-mail-abbrev-hook' uses `bbdb-message-search' to convert
2552 ;; these mail addresses to records, which is plenty fast.
2553 ;; FIXME: The value of arg MAILS for `bbdb-mail-abbrev-hook'
2554 ;; is wrong. Currently it is based on the list of records that have
2555 ;; referenced ALIASTEM and we simply take the first mail address
2556 ;; from each of these records.
2557 ;; Then `bbdb-message-search' will find the correct records
2558 ;; (assuming that each mail address appears only once in the
2559 ;; database). Nonethless, arg MAILS for `bbdb-mail-abbrev-hook'
2560 ;; does not, in general, contain the actual mail addresses
2561 ;; of EXPANSION. So what we would need is to go back from
2562 ;; EXPANSION to the mail addresses it contains (which is tricky
2563 ;; because mail addresses in the database can be shortcuts for
2564 ;; the addresses in EXPANSION).
2565 (fset f-alias
`(lambda ()
2566 (bbdb-mail-abbrev-expand-hook
2568 ',(mapcar (lambda (r) (car (bbdb-record-mail r
)))
2571 (if noisy
(message "BBDB mail alias: rebuilding done")))))
2573 (defun bbdb-mail-abbrev-expand-hook (alias mails
)
2574 (run-hook-with-args 'bbdb-mail-abbrev-expand-hook alias mails
)
2575 (mail-abbrev-expand-hook)
2576 (when bbdb-completion-display-record
2577 (let ((bbdb-silent-internal t
))
2578 (bbdb-display-records
2580 (mapcar (lambda (mail) (bbdb-message-search nil mail
)) mails
))
2583 (defun bbdb-get-mail-aliases ()
2584 "Return a list of mail aliases used in the BBDB."
2585 (let ((records (bbdb-search (bbdb-records) :xfield
(cons bbdb-mail-alias-field
".")))
2587 (dolist (record records
)
2588 (dolist (alias (bbdb-record-xfield-split record bbdb-mail-alias-field
))
2589 (bbdb-pushnew alias result
)))
2593 (defsubst bbdb-mail-alias-list
(alias)
2595 (bbdb-split bbdb-mail-alias-field alias
)
2598 (defun bbdb-add-mail-alias (records &optional alias delete
)
2599 "Add ALIAS to RECORDS.
2600 If prefix DELETE is non-nil, remove ALIAS from RECORDS.
2601 Interactively, use BBDB prefix \
2602 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
2603 Arg ALIAS is ignored if list RECORDS contains more than one record.
2604 Instead read ALIAS interactively for each record in RECORDS.
2605 If the function `bbdb-init-mail-alias' is defined, it is called with
2606 one arg RECORD to define the default value for ALIAS of RECORD."
2607 (interactive (list (bbdb-do-records) nil current-prefix-arg
))
2609 (setq records
(bbdb-record-list records
))
2610 (if (< 1 (length records
)) (setq alias nil
))
2611 (let* ((tmp (intern-soft
2612 (concat "bbdb-init-" (symbol-name bbdb-mail-alias-field
))))
2613 (init-f (if (functionp tmp
) tmp
)))
2614 (dolist (record records
)
2615 (let ((r-a-list (bbdb-record-xfield-split record bbdb-mail-alias-field
))
2619 (setq a-list
(bbdb-mail-alias-list alias
))
2621 (setq a-list
(bbdb-mail-alias-list (funcall init-f record
))
2622 alias
(if a-list
(bbdb-concat bbdb-mail-alias-field a-list
))))
2623 (let ((crm-separator
2625 (cadr (assq bbdb-mail-alias-field bbdb-separator-alist
))
2627 (crm-local-completion-map bbdb-crm-local-completion-map
)
2628 (prompt (format "%s mail alias:%s " (if delete
"Remove" "Add")
2629 (if alias
(format " (default %s)" alias
) "")))
2630 (collection (if delete
2631 (or r-a-list
(error "Record has no alias"))
2632 (bbdb-get-mail-aliases))))
2633 (setq a-list
(if (string< "24.3" (substring emacs-version
0 4))
2634 (completing-read-multiple prompt collection nil
2635 delete nil nil alias
)
2636 (bbdb-split bbdb-mail-alias-field
2637 (completing-read prompt collection nil
2638 delete nil nil alias
))))))
2641 (setq r-a-list
(delete a r-a-list
))
2642 ;; Add alias only if it is not there yet
2643 (bbdb-pushnew a r-a-list
)))
2644 ;; This also handles `bbdb-mail-aliases-need-rebuilt'
2645 (bbdb-record-set-xfield record bbdb-mail-alias-field
2646 (bbdb-concat bbdb-mail-alias-field r-a-list
))
2647 (bbdb-change-record record
)))))
2649 ;;; Dialing numbers from BBDB
2651 (defun bbdb-dial-number (phone-string)
2652 "Dial the number specified by PHONE-STRING.
2653 This uses the tel URI syntax passed to `browse-url' to make the call.
2654 If `bbdb-dial-function' is non-nil then that is called to make the phone call."
2655 (interactive "sDial number: ")
2656 (if bbdb-dial-function
2657 (funcall bbdb-dial-function phone-string
)
2658 (browse-url (concat "tel:" phone-string
))))
2661 (defun bbdb-dial (phone force-area-code
)
2662 "Dial the number at point.
2663 If the point is at the beginning of a record, dial the first phone number.
2664 Use rules from `bbdb-dial-local-prefix-alist' unless prefix FORCE-AREA-CODE
2665 is non-nil. Do not dial the extension."
2666 (interactive (list (bbdb-current-field) current-prefix-arg
))
2667 (if (eq (car-safe phone
) 'name
)
2668 (setq phone
(car (bbdb-record-phone (bbdb-current-record)))))
2669 (if (eq (car-safe phone
) 'phone
)
2670 (setq phone
(car (cdr phone
))))
2671 (or (vectorp phone
) (error "Not on a phone field"))
2673 (let ((number (bbdb-phone-string phone
))
2676 ;; cut off the extension
2677 (if (string-match "x[0-9]+$" number
)
2678 (setq number
(substring number
0 (match-beginning 0))))
2680 (unless force-area-code
2681 (let ((alist bbdb-dial-local-prefix-alist
) prefix
)
2682 (while (setq prefix
(pop alist
))
2683 (if (string-match (concat "^" (eval (car prefix
))) number
)
2684 (setq shortnumber
(concat (cdr prefix
)
2685 (substring number
(match-end 0)))
2689 (setq number shortnumber
)
2691 ;; This is terrifically Americanized...
2692 ;; Leading 0 => local number (?)
2693 (if (and bbdb-dial-local-prefix
2694 (string-match "^0" number
))
2695 (setq number
(concat bbdb-dial-local-prefix number
)))
2697 ;; Leading + => long distance/international number
2698 (if (and bbdb-dial-long-distance-prefix
2699 (string-match "^\+" number
))
2700 (setq number
(concat bbdb-dial-long-distance-prefix
" "
2701 (substring number
1)))))
2704 (message "Dialing %s" number
))
2705 (bbdb-dial-number number
)))
2710 (defun bbdb-browse-url (records &optional which
)
2711 "Brwose URLs stored in the `url' field of RECORDS.
2712 Interactively, use BBDB prefix \
2713 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
2714 Prefix WHICH specifies which URL in field `url' is used (starting from 0).
2715 Default is the first URL."
2716 (interactive (list (bbdb-get-records "Visit (URL): ")
2717 (and current-prefix-arg
2718 (prefix-numeric-value current-prefix-arg
))))
2719 (unless which
(setq which
0))
2720 (dolist (record (bbdb-record-list records
))
2721 (let ((url (bbdb-record-xfield-split record
'url
)))
2723 (setq url
(read-string "fetch: " (nth which url
)))
2724 (unless (string= "" url
)
2725 (browse-url url
))))))
2728 (defun bbdb-grab-url (record url
)
2729 "Grab URL and store it in RECORD."
2730 (interactive (let ((url (browse-url-url-at-point)))
2731 (unless url
(error "No URL at point"))
2732 (list (bbdb-completing-read-record
2733 (format "Add `%s' for: " url
))
2735 (bbdb-record-set-field record
'url url t
)
2736 (bbdb-change-record record
)
2737 (bbdb-display-records (list record
)))
2739 ;;; Copy to kill ring
2742 (defun bbdb-copy-records-as-kill (records)
2743 "Copy RECORDS to kill ring.
2744 Interactively, use BBDB prefix \
2745 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
2746 (interactive (list (bbdb-do-records t
)))
2748 (dolist (record (bbdb-record-list records t
))
2749 (push (buffer-substring (nth 2 record
)
2750 (or (nth 2 (car (cdr (memq record bbdb-records
))))
2753 (kill-new (replace-regexp-in-string
2755 (mapconcat 'identity
(nreverse drec
) "")))))
2758 (defun bbdb-copy-fields-as-kill (records field
&optional num
)
2759 "For RECORDS copy values of FIELD at point to kill ring.
2760 If FIELD is an address or phone with a label, copy only field values
2761 with the same label. With numeric prefix NUM, if the value of FIELD
2762 is a list, copy only the NUMth list element.
2763 Interactively, use BBDB prefix \
2764 \\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
2766 (list (bbdb-do-records t
) (bbdb-current-field)
2767 (and current-prefix-arg
2768 (prefix-numeric-value current-prefix-arg
))))
2769 (unless field
(error "Not a field"))
2770 (let* ((type (if (eq (car field
) 'xfields
)
2773 (label (if (memq type
'(phone address
))
2774 (aref (cadr field
) 0)))
2775 (ident (and (< 1 (length records
))
2776 (not (eq type
'name
))))
2778 (dolist (record (bbdb-record-list records
))
2779 (let ((raw-val (bbdb-record-field (car record
) type
))
2782 (cond ((eq type
'phone
)
2783 (dolist (elt raw-val
)
2784 (if (equal label
(aref elt
0))
2785 (push (bbdb-phone-string elt
) value
)))
2786 (setq value
(bbdb-concat 'phone
(nreverse value
))))
2788 (dolist (elt raw-val
)
2789 (if (equal label
(aref elt
0))
2790 (push (bbdb-format-address
2791 elt
(if (eq (nth 1 record
) 'one-line
) 3 2))
2793 (setq value
(bbdb-concat 'address
(nreverse value
))))
2795 (setq value
(if num
(nth num raw-val
)
2796 (bbdb-concat type raw-val
))))
2797 (t (setq value raw-val
))))
2800 (bbdb-concat 'name-field
2801 (bbdb-record-name (car record
)) value
)
2803 (let ((str (bbdb-concat 'record
(nreverse val-list
))))
2805 (message "%s" str
))))
2807 ;;; Help and documentation
2812 (info (format "(%s)Top" (or bbdb-info-file
"bbdb"))))
2817 (message (substitute-command-keys "\\<bbdb-mode-map>\
2818 new field: \\[bbdb-insert-field]; \
2819 edit field: \\[bbdb-edit-field]; \
2820 delete field: \\[bbdb-delete-field-or-record]; \
2821 mode help: \\[describe-mode]; \
2822 info: \\[bbdb-info]")))
2826 ;;; bbdb-com.el ends here