[rc/sway] darker colour for the bar
[~bandali/configs] / lisp / bbdb / bbdb-com.el
... / ...
CommitLineData
1;;; bbdb-com.el --- user-level commands of BBDB -*- lexical-binding: t -*-
2
3;; Copyright (C) 2010-2017 Free Software Foundation, Inc.
4
5;; This file is part of the Insidious Big Brother Database (aka BBDB),
6
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.
11
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.
16
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/>.
19
20;;; Commentary:
21;; This file contains most of the user-level interactive commands for BBDB.
22;; See the BBDB info manual for documentation.
23
24;;; Code:
25
26(require 'bbdb)
27(require 'mailabbrev)
28
29(eval-and-compile
30 (autoload 'build-mail-aliases "mailalias")
31 (autoload 'browse-url-url-at-point "browse-url"))
32
33(require 'crm)
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)
38 map)
39 "Keymap used for BBDB crm completions.")
40
41(defun bbdb-get-records (prompt)
42 "If inside the *BBDB* buffer get the current records.
43In other buffers ask the user."
44 (if (string= bbdb-buffer-name (buffer-name))
45 (bbdb-do-records)
46 (bbdb-completing-read-records prompt)))
47
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.
58If RECORDS is a single record turn it into a list.
59If FULL is non-nil, assume that RECORDS include display information."
60 (if records
61 (if full
62 (if (vectorp (car records)) (list records) records)
63 (if (vectorp records) (list records) records))))
64
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.
69
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))))
76
77;;;###autoload
78(defun bbdb-do-all-records (&optional arg)
79 "Command prefix for operating on all records currently displayed.
80With prefix ARG a positive number, operate on all records.
81With prefix ARG a negative number, operate on current record only.
82This only works for certain commands."
83 (interactive "P")
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))
93
94;;;###autoload
95(defun bbdb-do-records (&optional full)
96 "Return list of records to operate on.
97Normally this list includes only the current record.
98It includes all currently displayed records if the command prefix \
99\\<bbdb-mode-map>\\[bbdb-do-all-records] is used.
100If FULL is non-nil, the list of records includes display information."
101 (if bbdb-do-all-records
102 (progn
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))))
108
109;;;###autoload
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))
117 t)
118 (bbdb-append-display
119 (setq bbdb-append-display nil)
120 t))))
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)))
127 job))
128
129;;;###autoload
130(defun bbdb-append-display (&optional arg)
131 "Toggle appending next searched records in the *BBDB* buffer.
132With prefix ARG \\[universal-argument] always append.
133With ARG a positive number append for that many times.
134With ARG a negative number do not append."
135 (interactive "P")
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)
140 (t 'once)))
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")
146 (t nil)))
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))
152
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)
157 (t bbdb-layout)))
158
159(defun bbdb-search-invert-p ()
160 "Return variable `bbdb-search-invert' and set it to nil.
161To 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)
166 result))
167
168;;;###autoload
169(defun bbdb-search-invert (&optional arg)
170 "Toggle inversion of the next search command.
171With prefix ARG a positive number, invert next search.
172With prefix ARG a negative number, do not invert next search."
173 (interactive "P")
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))
182
183(defmacro bbdb-search (records &rest spec)
184 "Search RECORDS for fields matching SPEC.
185The following keywords are supported in SPEC to search fields in RECORDS
186matching the regexps RE:
187
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.
201
202Each of these keywords may appear multiple times.
203Other keywords:
204
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'.
208
209To reverse the search, bind `bbdb-search-invert' to t.
210See also `bbdb-message-search' for fast searches using `bbdb-hashtable'
211but not allowing for regexps.
212
213For backward compatibility, SPEC may also consist of the optional args
214 NAME ORGANIZATION MAIL XFIELD PHONE ADDRESS
215which is equivalent to
216 :all-names NAME :organization ORGANIZATION :mail MAIL
217 :xfield XFIELD :phone PHONE :address ADDRESS
218This 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'")
224 (sit-for 2))
225 (let (newspec val)
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))))
230
231 (let* ((count 0)
232 (sym-list (mapcar (lambda (_)
233 (make-symbol
234 (format "bbdb-re-%d" (setq count (1+ count)))))
235 spec))
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
241
242 ;; Check keys.
243 (while (keywordp (setq keyw (car spec)))
244 (setq spec (cdr spec))
245 (pcase keyw
246 (`:name
247 (let ((sym (pop sym-list)))
248 (push `(,sym ,(pop spec)) re-list)
249 (push `(string-match ,sym (bbdb-record-name record)) clauses)))
250
251 (`:name-lf
252 (let ((sym (pop sym-list)))
253 (push `(,sym ,(pop spec)) re-list)
254 (push `(string-match ,sym (bbdb-record-name-lf record)) clauses)))
255
256 (`:all-names
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))
262 aka done)
263 (while (and (setq aka (pop akas)) (not done))
264 (setq done (string-match ,sym aka)))
265 done))
266 clauses)))
267
268 (`:affix
269 (let ((sym (pop sym-list)))
270 (push `(,sym ,(pop spec)) re-list)
271 (push `(let ((affixs (bbdb-record-field record 'affix-all))
272 affix done)
273 (if affix
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 "")))
278 done)
279 clauses)))
280
281 (`:aka
282 (let ((sym (pop sym-list)))
283 (push `(,sym ,(pop spec)) re-list)
284 (push `(let ((akas (bbdb-record-field record 'aka-all))
285 aka done)
286 (if aka
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 "")))
291 done)
292 clauses)))
293
294 (`:organization
295 (let ((sym (pop sym-list)))
296 (push `(,sym ,(pop spec)) re-list)
297 (push `(let ((organizations (bbdb-record-organization record))
298 org done)
299 (if organizations
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 "")))
304 done)
305 clauses)))
306
307 (`:phone
308 (let ((sym (pop sym-list)))
309 (push `(,sym ,(pop spec)) re-list)
310 (push `(let ((phones (bbdb-record-phone record))
311 ph done)
312 (if phones
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 "")))
318 done)
319 clauses)))
320
321 (`:address
322 (let ((sym (pop sym-list)))
323 (push `(,sym ,(pop spec)) re-list)
324 (push `(let ((addresses (bbdb-record-address record))
325 a done)
326 (if addresses
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 "")))
332 done)
333 clauses)))
334
335 (`:mail
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
340 m done)
341 (if 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 "")))
346 done)
347 clauses)))
348
349 (`:xfield
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
355 (string-match ,sym
356 (or (bbdb-record-xfield-string
357 record bbdb-default-xfield) "")))
358 ((eq (car ,sym) '*)
359 ;; check all xfields
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)
364 tmp))
365 labels (cdr labels)))
366 done))
367 (t ; check one field
368 (string-match (cdr ,sym)
369 (or (bbdb-record-xfield-string
370 record (car ,sym)) ""))))
371 clauses)))
372
373 (`:creation-date
374 (let ((sym (pop sym-list)))
375 (push `(,sym ,(pop spec)) re-list)
376 (push `(string-match ,sym (bbdb-record-creation-date record))
377 clauses)))
378
379 (`:timestamp
380 (let ((sym (pop sym-list)))
381 (push `(,sym ,(pop spec)) re-list)
382 (push `(string-match ,sym (bbdb-record-timestamp record))
383 clauses)))
384
385 (`:bool
386 (set bool (pop spec)))
387
388 ;; Do we need other keywords?
389
390 (_ (error "Keyword `%s' undefines" keyw))))
391
392 `(let ((case-fold-search bbdb-case-fold-search)
393 (,not-invert (not (bbdb-search-invert-p)))
394 ,@re-list ,matches)
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))))
411
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 " ""))))
417
418;;;###autoload
419(defun bbdb (regexp &optional layout)
420 "Display all records in the BBDB matching REGEXP
421in 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)))
427 (if records
428 (bbdb-display-records records layout nil t)
429 (message "No records matching '%s'" regexp))))
430
431;;;###autoload
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))
437
438;;;###autoload
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)
443 layout))
444
445;;;###autoload
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)
450 layout))
451
452;;;###autoload
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))
457
458;;;###autoload
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))
464
465;;;###autoload
466(defun bbdb-search-xfields (field regexp &optional layout)
467 "Display all BBDB records for which xfield FIELD matches REGEXP."
468 (interactive
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 "")
473 "any xfield"
474 field))
475 (bbdb-layout-prefix))))
476 (bbdb-display-records (bbdb-search (bbdb-records) :xfield (cons field regexp))
477 layout))
478(define-obsolete-function-alias 'bbdb-search-notes 'bbdb-search-xfields "3.0")
479
480;;;###autoload
481(defun bbdb-search-changed (&optional layout)
482 ;; FIXME: "changes" in BBDB lingo are often called "modifications"
483 ;; in Emacs lingo
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)))
493
494(defun bbdb-search-prog (fun &optional layout)
495 "Search records using function FUN.
496FUN is called with one argument, the record, and should return
497the record to be displayed or nil otherwise."
498 (bbdb-display-records (delq nil (mapcar fun (bbdb-records))) layout))
499
500\f
501;; clean-up functions
502
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.
506
507(defun bbdb-mail-redundant-re (mail)
508 "Return a regexp matching redundant variants of email address MAIL.
509For example, \"foo@bar.baz.com\" is redundant w.r.t. \"foo@baz.com\".
510Return nil if MAIL is not a valid plain email address.
511In 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))))
515 (if (and name host)
516 (concat (regexp-quote name) "@.*\\." (regexp-quote host)))))
517
518(defun bbdb-delete-redundant-mails (records &optional query update)
519 "Delete redundant or duplicate mails from RECORDS.
520For example, \"foo@bar.baz.com\" is redundant w.r.t. \"foo@baz.com\".
521Duplicates may (but should not) occur if we feed BBDB automatically.
522Interactively, use BBDB prefix \
523\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
524If QUERY is non-nil (as in interactive calls, unless we use a prefix arg)
525query before deleting the redundant mail addresses.
526If UPDATE is non-nil (as in interactive calls) update the database.
527Otherwise, this is the caller's responsiblity.
528
529Noninteractively, this may be used as an element of `bbdb-notice-record-hook'
530or `bbdb-change-hook'. However, see also `bbdb-ignore-redundant-mails',
531which is probably more suited for your needs."
532 (interactive (list (bbdb-do-records) (not current-prefix-arg) t))
533 (bbdb-editable)
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)
545 (push mail mails)))
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 "\\|")
551 "\\)\\'"))
552 (dolist (mail mails)
553 (if (string-match mail-re mail) ; redundant mail address
554 (push mail redundant)
555 (push mail okay)))))
556 (let ((form (format "redundant mail%s %s"
557 (if (< 1 (length redundant)) "s" "")
558 (bbdb-concat 'mail (nreverse redundant)))))
559 (when (and redundant
560 (or (not query)
561 (y-or-n-p (format "Delete %s: " form))))
562 (unless query (message "Deleting %s" form))
563 (bbdb-record-set-field record 'mail okay)
564 (when update
565 (bbdb-change-record record)))))))
566(define-obsolete-function-alias 'bbdb-delete-duplicate-mails
567 'bbdb-delete-redundant-mails "3.0")
568
569(defun bbdb-search-duplicates (&optional fields)
570 "Search all records that have duplicate entries for FIELDS.
571The list FIELDS may contain the symbols `name', `mail', and `aka'.
572If FIELDS is nil use all these fields. With prefix, query for FIELDS.
573The 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")
577 nil t))))))
578 (setq fields (or fields '(name mail aka)))
579 (let (hash ret)
580 (dolist (record (bbdb-records))
581
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)))
586 (> (length hash) 1))
587 (setq ret (append hash ret))
588 (message "BBDB record `%s' has duplicate name."
589 (bbdb-record-name record))
590 (sit-for 0))
591
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)
599 (sit-for 0))))
600
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)
608 (sit-for 0)))))
609
610 (bbdb-display-records (sort (delete-dups ret)
611 'bbdb-record-lessp))))
612
613(defun bbdb-fix-records (records)
614 "Fix broken RECORDS.
615Interactively, use BBDB prefix \
616\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
617 (interactive (list (bbdb-do-records)))
618 (bbdb-editable)
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))
629 (bbdb-sort-records))
630
631(defun bbdb-touch-records (records)
632 "Touch RECORDS by calling `bbdb-change-hook' unconditionally.
633Interactively, use BBDB prefix \
634\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
635 (interactive (list (bbdb-do-records)))
636 (bbdb-editable)
637 (let ((bbdb-update-unchanged-records t))
638 (dolist (record (bbdb-record-list records))
639 (bbdb-change-record record))))
640
641;;; Time-based functions
642
643(defmacro bbdb-compare-records (cmpval label compare)
644 "Builds a lambda comparison function that takes one argument, RECORD.
645RECORD is returned if (COMPARE VALUE CMPVAL) is t, where VALUE
646is the value of field LABEL of RECORD."
647 `(lambda (record)
648 (let ((val (bbdb-record-field record ,label)))
649 (if (and val (,compare val ,cmpval))
650 record))))
651
652(defsubst bbdb-string> (a b)
653 (not (or (string= a b)
654 (string< a b))))
655
656;;;###autoload
657(defun bbdb-timestamp-older (date &optional layout)
658 "Display records with timestamp older than DATE.
659DATE 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))
663
664;;;###autoload
665(defun bbdb-timestamp-newer (date &optional layout)
666 "Display records with timestamp newer than DATE.
667DATE 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))
671
672;;;###autoload
673(defun bbdb-creation-older (date &optional layout)
674 "Display records with creation-date older than DATE.
675DATE 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))
679
680;;;###autoload
681(defun bbdb-creation-newer (date &optional layout)
682 "Display records with creation-date newer than DATE.
683DATE 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))
687
688;;;###autoload
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)))
692 (bbdb-search-prog
693 ;; RECORD is bound in `bbdb-compare-records'.
694 (bbdb-compare-records (bbdb-record-timestamp record)
695 'creation-date string=)
696 layout))
697
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.
703
704(defsubst bbdb-subint (string num)
705 "Used for parsing phone numbers."
706 (string-to-number (match-string num string)))
707
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).
711This is both lenient and strict in what it will parse - whitespace may
712appear (or not) between any of the groups of digits, parentheses around the
713area code are optional, as is a dash between the exchange and number, and
714a '1' preceeding the area code; but there must be three digits in the area
715code and exchange, and four in the number (if they are present).
716All of these are unambigously parsable:
717
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)
730 1234 -> (0 0 0 1234)
731
732Note that \"4151212123\" is ambiguous; it could be interpreted either as
733\"(415) 121-2123\" or as \"415-1212 x123\".
734
735Return a list containing four numbers or one string."
736
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)))
752 ;; (415) 555-1212
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))
756 ;; 555-1212 x123
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)))
760 ;; 555-1212
761 ((string-match (concat "^[ \t]*" main-regexp "$") string)
762 (list 0 (bbdb-subint string 1) (bbdb-subint string 2) 0))
763 ;; x123
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))))))
768
769(defun bbdb-message-search (name mail)
770 "Return list of BBDB records matching NAME and/or MAIL.
771First try to find a record matching both NAME and MAIL.
772If this fails try to find a record matching MAIL.
773If this fails try to find a record matching NAME.
774NAME may match FIRST_LAST, LAST_FIRST or AKA.
775
776This function performs a fast search using `bbdb-hashtable'.
777NAME and MAIL must be strings or nil.
778See `bbdb-search' for searching records with regexps."
779 (when (or name mail)
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
785 (let (records)
786 (dolist (record nrecords)
787 (mapc (lambda (mr) (if (and (eq record mr)
788 (not (memq record records)))
789 (push record records)))
790 mrecords))
791 records))
792 ;; (2) records matching MAIL
793 mrecords
794 ;; (3) records matching NAME
795 nrecords))))
796
797(defun bbdb-read-record (&optional first-and-last)
798 "Read and return a new BBDB record.
799Does not insert it into the database or update the hashtables,
800but does ensure that there will not be name collisions."
801 (bbdb-editable)
802 (let ((record (bbdb-empty-record)))
803 (let (name)
804 (bbdb-error-retry
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)))
809
810 ;; organization
811 (bbdb-record-set-organization record (bbdb-read-organization))
812
813 ;; mail
814 (bbdb-record-set-mail
815 record (bbdb-split 'mail (bbdb-read-string "E-Mail Addresses: ")))
816 ;; address
817 (let (addresses label address)
818 (while (not (string= ""
819 (setq label
820 (bbdb-read-string
821 "Snail Mail Address Label [RET when done]: "
822 nil
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)))
828
829 ;; phones
830 (let (phones phone-list label)
831 (while (not (string= ""
832 (setq label
833 (bbdb-read-string
834 "Phone Label [RET when done]: " nil
835 bbdb-phone-label-list))))
836 (setq phone-list
837 (bbdb-error-retry
838 (bbdb-parse-phone
839 (read-string "Phone: "
840 (and (integerp bbdb-default-area-code)
841 (format "(%03d) "
842 bbdb-default-area-code))))))
843 (push (apply 'vector label phone-list) phones))
844 (bbdb-record-set-phone record (nreverse phones)))
845
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)))))
851
852 record))
853
854(defun bbdb-read-name (&optional first-and-last dfirst dlast)
855 "Read name for a record from minibuffer.
856FIRST-AND-LAST controls the reading mode:
857If it is 'first-last read first and last name separately.
858If it is 'last-first read last and first name separately.
859If it is 'fullname read full name at once.
860If it is t read name parts separately, obeying `bbdb-read-name-format' if possible.
861Otherwise use `bbdb-read-name-format'.
862DFIRST and DLAST are default values for the first and last name.
863Return 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
866 (setq first-and-last
867 (if (and first-and-last
868 (not (memq bbdb-read-name-format '(first-last last-first))))
869 'first-last
870 bbdb-read-name-format)))
871 (let ((name (cond ((eq first-and-last 'last-first)
872 (let (fn ln)
873 (setq ln (bbdb-read-string "Last Name: " dlast)
874 fn (bbdb-read-string "First Name: " dfirst))
875 (cons fn ln)))
876 ((eq first-and-last 'first-last)
877 (cons (bbdb-read-string "First Name: " dfirst)
878 (bbdb-read-string "Last Name: " dlast)))
879 (t
880 (bbdb-divide-name (bbdb-read-string
881 "Name: " (bbdb-concat 'name-first-last
882 dfirst dlast)))))))
883 (if (string= (car name) "") (setcar name nil))
884 (if (string= (cdr name) "") (setcdr name nil))
885 name))
886
887;;;###autoload
888(defun bbdb-create (record)
889 "Add a new RECORD to BBDB.
890When called interactively read all relevant info.
891Do 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)))
895
896(defsubst bbdb-split-maybe (separator string)
897 "Split STRING into list of substrings bounded by matches for SEPARATORS.
898If STRING is a list, return STRING. Throw error if STRING is neither a string
899nor a list."
900 (cond ((stringp string)
901 (bbdb-split separator string))
902 ((listp string) string)
903 (t (error "Cannot convert %s to list" string))))
904
905;;;###autoload
906(defun bbdb-create-internal (&rest spec)
907 "Add a new record to the database and return it.
908
909The 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."
931 (bbdb-editable)
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))))
936 keyw)
937
938 ;; Check keys.
939 (while (keywordp (setq keyw (car spec)))
940 (setq spec (cdr spec))
941 (pcase keyw
942 (`:name
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))
948 t)))
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))))
954
955 (`:affix
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)))
959
960 (`:organization
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)))
965
966 (`:aka
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)))
970
971 (`:mail
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
975 (dolist (elt mail)
976 (if (bbdb-gethash elt '(mail))
977 (error "%s is already in the database" elt))))
978 (bbdb-record-set-mail record mail)))
979
980 (`:phone
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)))
984
985 (`:address
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)))
989
990 (`:xfields
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)))
994
995 (`:uuid
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)))
999
1000 (`:creation-date
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)))
1005
1006 (_ (error "Keyword `%s' undefined" keyw))))
1007
1008 (bbdb-change-record record)))
1009
1010;;;###autoload
1011(defun bbdb-insert-field (record field value)
1012 "For RECORD, add a new FIELD with value VALUE.
1013Interactively, read FIELD and VALUE; RECORD is the current record.
1014A non-nil prefix arg is passed on to `bbdb-read-field' as FLAG (see there)."
1015 (interactive
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)))
1021 (field "")
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))))
1037
1038 (cond (;; affix
1039 (eq field 'affix)
1040 (if (bbdb-record-affix record)
1041 (error "Affix field exists already"))
1042 (if (stringp value)
1043 (setq value (bbdb-split 'affix value)))
1044 (bbdb-record-set-field record 'affix value))
1045 ;; organization
1046 ((eq field 'organization)
1047 (if (bbdb-record-organization record)
1048 (error "Organization field exists already"))
1049 (if (stringp value)
1050 (setq value (bbdb-split 'organization value)))
1051 (bbdb-record-set-field record 'organization value))
1052 ;; phone
1053 ((eq field 'phone)
1054 (bbdb-record-set-field record 'phone
1055 (nconc (bbdb-record-phone record)
1056 (list value))))
1057 ;; address
1058 ((eq field 'address)
1059 (bbdb-record-set-field record 'address
1060 (nconc (bbdb-record-address record)
1061 (list value))))
1062 ;; mail
1063 ((eq field 'mail)
1064 (if (bbdb-record-mail record)
1065 (error "Mail field exists already"))
1066 (if (stringp value)
1067 (setq value (bbdb-split 'mail value)))
1068 (bbdb-record-set-field record 'mail value))
1069 ;; AKA
1070 ((eq field 'aka)
1071 (if (bbdb-record-aka record)
1072 (error "Alternate names field exists already"))
1073 (if (stringp value)
1074 (setq value (bbdb-split 'aka value)))
1075 (bbdb-record-set-field record 'aka value))
1076 ;; xfields
1077 ((assq field (bbdb-record-xfields record))
1078 (error "Xfield \"%s\" already exists" field))
1079 (t
1080 (bbdb-record-set-xfield record field value)))
1081 (unless (bbdb-change-record record)
1082 (message "Record unchanged")))
1083
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
1091 any lisp object."
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))))
1095 (cond (;; affix
1096 (eq field 'affix) (bbdb-read-string "Affix: " init))
1097 ;; organization
1098 ((eq field 'organization) (bbdb-read-organization init))
1099 ;; mail
1100 ((eq field 'mail)
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))
1106 mail
1107 (concat mail "@" bbdb-default-domain))))
1108 ;; AKA
1109 ((eq field 'aka) (bbdb-read-string "Alternate Names: " init))
1110 ;; Phone
1111 ((eq field 'phone)
1112 (let ((bbdb-phone-style
1113 (if flag (if (eq bbdb-phone-style 'nanp) nil 'nanp)
1114 bbdb-phone-style)))
1115 (apply 'vector
1116 (bbdb-read-string "Label: " nil bbdb-phone-label-list)
1117 (bbdb-error-retry
1118 (bbdb-parse-phone
1119 (read-string "Phone: "
1120 (and (integerp bbdb-default-area-code)
1121 (format "(%03d) "
1122 bbdb-default-area-code))))))))
1123 ;; Address
1124 ((eq field 'address)
1125 (let ((address (make-vector bbdb-address-length nil)))
1126 (bbdb-record-edit-address address nil t)
1127 address))
1128 ;; xfield
1129 ((or (memq field bbdb-xfield-label-list)
1130 ;; New xfield
1131 (y-or-n-p
1132 (format "\"%s\" is an unknown field name. Define it? " field))
1133 (error "Aborted"))
1134 (bbdb-read-xfield field init flag)))))
1135
1136;;;###autoload
1137(defun bbdb-edit-field (record field &optional value flag)
1138 "Edit the contents of FIELD of RECORD.
1139If point is in the middle of a multi-line field (e.g., address),
1140then the entire field is edited, not just the current line.
1141For editing phone numbers or addresses, VALUE must be the phone number
1142or address that gets edited. An error is thrown when attempting to edit
1143a phone number or address with VALUE being nil.
1144
1145- The value of an xfield is a string. With prefix FLAG the value may be
1146 any lisp object."
1147 (interactive
1148 (save-excursion
1149 (bbdb-editable)
1150 ;; when at the end of the line take care of it
1151 (if (and (eolp) (not (bobp)) (not (bbdb-current-field)))
1152 (backward-char 1))
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))))
1163 (let (edit-str)
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))
1167 ((eq field 'name)
1168 (bbdb-error-retry
1169 (bbdb-record-set-field
1170 record 'name
1171 (bbdb-read-name
1172 (if flag
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)
1178 flag))
1179 (bbdb-record-firstname record)
1180 (bbdb-record-lastname record)))))
1181
1182 ((eq field 'phone)
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
1190 record 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
1196 record field
1197 (bbdb-split field (bbdb-read-string
1198 (format "%s: " (cdr edit-str))
1199 (bbdb-concat field
1200 (bbdb-record-field record field))))))
1201 ((eq field 'uuid)
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
1210 (t ; xfield
1211 (bbdb-record-set-xfield
1212 record field
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")))))
1218
1219(defun bbdb-edit-foo (record field &optional nvalue)
1220 "For RECORD edit some FIELD (mostly interactively).
1221FIELD may take the same values as the elements of the variable `bbdb-edit-foo'.
1222If FIELD is 'phone or 'address, NVALUE should be an integer in order to edit
1223the NVALUEth phone or address field; otherwise insert a new phone or address
1224field.
1225
1226Interactively, if called without a prefix, the value of FIELD is the car
1227of the variable `bbdb-edit-foo'. When called with a prefix, the value
1228of FIELD is the cdr of this variable. Then use minibuffer completion
1229to select the field."
1230 (interactive
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))))))
1248 tmp))
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 #")
1257 (mapcar (lambda (n)
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)
1265 "new")))
1266 ((eq field 'address)
1267 (let* ((addresses (bbdb-record-address record))
1268 (collection (cons (cons "new" "new address")
1269 (mapcar (lambda (n)
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)
1277 "new"))))))
1278 (list record field (and (stringp nvalue)
1279 (if (string= "new" nvalue)
1280 'new
1281 (string-to-number nvalue))))))
1282
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))
1297 value)
1298 (bbdb-edit-field record field value)
1299 (bbdb-insert-field record field
1300 (bbdb-read-field record field)))))
1301
1302(defun bbdb-read-xfield (field &optional init sexp)
1303 "Read xfield FIELD with optional INIT.
1304This 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))))))
1312
1313(defun bbdb-read-organization (&optional init)
1314 "Read organization."
1315 (if (string< "24.3" (substring emacs-version 0 4))
1316 (let ((crm-separator
1317 (concat "[ \t\n]*"
1318 (cadr (assq 'organization bbdb-separator-alist))
1319 "[ \t\n]*"))
1320 (crm-local-completion-map bbdb-crm-local-completion-map))
1321 (completing-read-multiple "Organizations: " bbdb-organization-list
1322 nil nil init))
1323 (bbdb-split 'organization (bbdb-read-string "Organizations: " init))))
1324
1325(defun bbdb-record-edit-address (address &optional label ignore-country)
1326 "Edit ADDRESS.
1327If LABEL is nil, edit the label sub-field of the address as well.
1328If the country field of ADDRESS is nonempty and IGNORE-COUNTRY is nil,
1329use the rule from `bbdb-address-format-list' matching this country.
1330Otherwise, use the default rule according to `bbdb-address-format-list'."
1331 (unless label
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) ""))
1336 new-addr edit)
1337 (unless (or ignore-country (string= "" country))
1338 (let ((list bbdb-address-format-list)
1339 identifier elt)
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))))))
1347 (unless edit
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))
1354 (cond ((eq elt ?s)
1355 (aset new-addr 0 (bbdb-edit-address-street
1356 (bbdb-address-streets address))))
1357 ((eq elt ?c)
1358 (aset new-addr 1 (bbdb-read-string
1359 "City: " (bbdb-address-city address)
1360 bbdb-city-list)))
1361 ((eq elt ?S)
1362 (aset new-addr 2 (bbdb-read-string
1363 "State: " (bbdb-address-state address)
1364 bbdb-state-list)))
1365 ((eq elt ?p)
1366 (aset new-addr 3
1367 (bbdb-error-retry
1368 (bbdb-parse-postcode
1369 (bbdb-read-string
1370 "Postcode: " (bbdb-address-postcode address)
1371 bbdb-postcode-list)))))
1372 ((eq elt ?C)
1373 (aset new-addr 4
1374 (bbdb-read-string
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)
1385 (elt new-addr 4)))
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)))))
1391
1392(defun bbdb-edit-address-street (streets)
1393 "Edit list STREETS."
1394 (let ((n 0) street list)
1395 (while (not (string= "" (setq street
1396 (bbdb-read-string
1397 (format "Street, line %d: " (1+ n))
1398 (nth n streets) bbdb-street-list))))
1399 (push street list)
1400 (setq n (1+ n)))
1401 (reverse list)))
1402
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.
1407The sub-fields and the prompts used are:
1408Street, line n: (nth n street)
1409City: city
1410State: state
1411Postcode: postcode
1412Country: country"
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)
1416 bbdb-state-list)
1417 (bbdb-error-retry
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)))
1424
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)
1433 (apply 'vector
1434 (bbdb-read-string "Label: "
1435 (bbdb-phone-label phone)
1436 bbdb-phone-label-list)
1437 (bbdb-error-retry
1438 (bbdb-parse-phone
1439 (read-string "Phone: " (bbdb-phone-string phone)))))))
1440
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.
1444I and J start with zero. Return the modified LIST."
1445 (if (eq i j)
1446 list ; ignore that i, j could be invalid
1447 (let (a b c)
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)
1452 c (car b))
1453 (unless b (error "Args %i, %i beyond length of list." i j))
1454 (setcar b (car a))
1455 (setcar a c)
1456 list)))
1457
1458(defun bbdb-ident-point (&optional point)
1459 "Return identifier (RECNUM FIELD NUM) for position POINT.
1460If POINT is nil use current value of point.
1461RECNUM is the number of the record (starting from zero).
1462FIELD is the field type.
1463If FIELD's value is a list, NUM is the position of the subfield within FIELD.
1464If 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)))
1468 (cond ((not 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))
1474 (t
1475 (let* ((record (car (nth recnum bbdb-records)))
1476 (fields (bbdb-record-field record (car field)))
1477 (val (nth 1 field))
1478 (num 0) done elt)
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))
1482 (setq val (car val)
1483 fields (mapcar 'car fields)))
1484 (while (and (not done) (setq elt (pop fields)))
1485 (if (eq val elt)
1486 (setq done t)
1487 (setq num (1+ num))))
1488 (unless done (error "Field %s not found" val))
1489 (list recnum (car field) num))))))
1490
1491;;;###autoload
1492(defun bbdb-transpose-fields (arg)
1493 "Transpose previous and current field of a BBDB record.
1494With numeric prefix ARG, take previous field and move it past ARG fields.
1495With region active or ARG 0, transpose field point is in and field mark is in.
1496
1497Both 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
1499or email addresses are listed, but you cannot use it to make an address appear
1500before a phone number; the order of field types is fixed).
1501
1502If the current field is the name field, transpose first and last name,
1503irrespective of the value of ARG."
1504 ;; This functionality is inspired by `transpose-lines'.
1505 (interactive "p")
1506 (bbdb-editable)
1507 (let* ((ident (bbdb-ident-point))
1508 (record (and (car ident) (car (nth (car ident) bbdb-records))))
1509 num1 num2)
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"))
1522 (t
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))
1533 num2 (+ num1 arg))
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))
1541 num1 num2))))
1542 (bbdb-change-record record)))
1543
1544;;;###autoload
1545(defun bbdb-delete-field-or-record (records field &optional noprompt)
1546 "For RECORDS delete FIELD.
1547If FIELD is the `name' field, delete RECORDS from datanbase.
1548Interactively, use BBDB prefix \
1549\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records',
1550and FIELD is the field point is on.
1551If 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
1554 ;; to be done.
1555 (interactive
1556 (list (bbdb-do-records) (bbdb-current-field) current-prefix-arg))
1557 (bbdb-editable)
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)
1562 (car (nth 1 field))
1563 type)))
1564 (if (eq type 'name)
1565 (bbdb-delete-records records noprompt)
1566 (if (memq type '(firstname lastname))
1567 (error "Cannot delete field `%s'" type))
1568 (dolist (record records)
1569 (when (or noprompt
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
1574 record type
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))
1582 ((eq type 'xfields)
1583 (bbdb-record-set-xfield record type-x nil))
1584 (t (error "Unknown field %s" type)))
1585 (bbdb-change-record record))))))
1586
1587;;;###autoload
1588(defun bbdb-delete-records (records &optional noprompt)
1589 "Delete RECORDS.
1590Interactively, use BBDB prefix \
1591\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1592If prefix NOPROMPT is non-nil, do not confirm deletion."
1593 (interactive (list (bbdb-do-records) current-prefix-arg))
1594 (bbdb-editable)
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)))
1601 ((or noprompt
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)))))))
1607
1608;;;###autoload
1609(defun bbdb-display-all-records (&optional layout)
1610 "Show all records.
1611If invoked in a *BBDB* buffer point stays on the currently visible record.
1612Inverse 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)))))
1620
1621;;;###autoload
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))
1626
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)))))
1632
1633;;;###autoload
1634(defun bbdb-toggle-records-layout (records &optional arg)
1635 "Toggle layout of RECORDS (elided or expanded).
1636Interactively, use BBDB prefix \
1637\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1638With prefix ARG 0, RECORDS are displayed elided.
1639With 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)))
1643 (layout-alist
1644 ;; Try to consider only those layouts that have the `toggle'
1645 ;; option set
1646 (or (delq nil (mapcar (lambda (l)
1647 (if (and (assq 'toggle l)
1648 (cdr (assq 'toggle l)))
1649 l))
1650 bbdb-layout-alist))
1651 bbdb-layout-alist))
1652 (layout
1653 (cond ((eq arg 0)
1654 'one-line)
1655 ((null current-layout)
1656 'multi-line)
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)
1660 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)))
1666
1667;;;###autoload
1668(defun bbdb-display-records-completely (records)
1669 "Display RECORDS using layout `full-multi-line' (i.e., display all fields).
1670Interactively, 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))
1676 'full-multi-line
1677 'multi-line)))
1678 (bbdb-change-records-layout (bbdb-record-list records t) layout)))
1679
1680;;;###autoload
1681(defun bbdb-display-records-with-layout (records layout)
1682 "Display RECORDS using LAYOUT.
1683Interactively, use BBDB prefix \
1684\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
1685 (interactive
1686 (list (bbdb-do-records t)
1687 (intern (completing-read "Layout: "
1688 (mapcar (lambda (i)
1689 (list (symbol-name (car i))))
1690 bbdb-layout-alist)))))
1691 (bbdb-change-records-layout (bbdb-record-list records t) layout))
1692
1693;;;###autoload
1694(defun bbdb-omit-record (n)
1695 "Remove current record from the display without deleting it from BBDB.
1696With prefix N, omit the next N records. If negative, omit backwards."
1697 (interactive "p")
1698 (let ((num (get-text-property (if (and (not (bobp)) (eobp))
1699 (1- (point)) (point))
1700 'bbdb-record-number)))
1701 (if (> n 0)
1702 (setq n (min n (- (length bbdb-records) num)))
1703 (setq n (min (- n) num))
1704 (bbdb-prev-record n))
1705 (dotimes (_i n)
1706 (bbdb-redisplay-record (bbdb-current-record) nil t))))
1707
1708;;; Fixing up bogus records
1709
1710;;;###autoload
1711(defun bbdb-merge-records (record1 record2)
1712 "Merge RECORD1 into RECORD2, then delete RECORD1 and return RECORD2.
1713If both records have name fields ask which one to use.
1714Concatenate other fields, ignoring duplicates.
1715RECORD1 need not be known to BBDB, its hash and cache are ignored.
1716Update hash and cache for RECORD2.
1717
1718Interactively, RECORD1 is the current record; prompt for RECORD2.
1719With prefix, RECORD2 defaults to the first record with the same name."
1720 (interactive
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
1726 (car (delq record1
1727 (bbdb-search (bbdb-records) :all-names name))))))
1728 (when record2
1729 (message "Merge current record with duplicate record `%s'" name)
1730 (sit-for 1))
1731 (list record1
1732 (or record2
1733 (bbdb-completing-read-record
1734 (format "merge record \"%s\" into: "
1735 (or (bbdb-record-name record1)
1736 (car (bbdb-record-mail record1))
1737 "???"))
1738 (list record1))))))
1739
1740 (bbdb-editable)
1741 (cond ((eq record1 record2) (error "Records are equal"))
1742 ((null record2) (error "No record to merge with")))
1743
1744 ;; Merge names
1745 (let* ((new-name (bbdb-record-name record2))
1746 (old-name (bbdb-record-name record1))
1747 (old-aka (bbdb-record-aka record1))
1748 extra-name
1749 (name
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)))
1757 (t (prog1
1758 (if (y-or-n-p
1759 (format "Use name \"%s\" instead of \"%s\"? "
1760 old-name new-name))
1761 (progn
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? "
1771 extra-name))
1772 (setq extra-name nil)))))))
1773
1774 (bbdb-record-set-name record2 (car name) (cdr name))
1775
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)))
1781
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)
1796
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)
1800 record2)
1801
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.
1804
1805;;;###autoload
1806(defun bbdb-sort-addresses (records &optional update)
1807 "Sort the addresses in RECORDS according to the label.
1808Interactively, use BBDB prefix \
1809\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1810If UPDATE is non-nil (as in interactive calls) update the database.
1811Otherwise, this is the caller's responsiblity (for example, when used
1812in `bbdb-change-hook')."
1813 (interactive (list (bbdb-do-records) t))
1814 (bbdb-editable)
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)))))
1819 (if update
1820 (bbdb-change-record record))))
1821
1822;;;###autoload
1823(defun bbdb-sort-phones (records &optional update)
1824 "Sort the phones in RECORDS according to the label.
1825Interactively, use BBDB prefix \
1826\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1827If UPDATE is non-nil (as in interactive calls) update the database.
1828Otherwise, this is the caller's responsiblity (for example, when used
1829in `bbdb-change-hook')."
1830 (interactive (list (bbdb-do-records) t))
1831 (bbdb-editable)
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)))))
1836 (if update
1837 (bbdb-change-record record))))
1838
1839;;;###autoload
1840(defun bbdb-sort-xfields (records &optional update)
1841 "Sort the xfields in RECORDS according to `bbdb-xfields-sort-order'.
1842Interactively, use BBDB prefix \
1843\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1844If UPDATE is non-nil (as in interactive calls) update the database.
1845Otherwise, this is the caller's responsiblity (for example, when used
1846in `bbdb-change-hook')."
1847 (interactive (list (bbdb-do-records) t))
1848 (bbdb-editable)
1849 (dolist (record (bbdb-record-list records))
1850 (bbdb-record-set-xfields
1851 record (sort (bbdb-record-xfields record)
1852 (lambda (a b)
1853 (< (or (cdr (assq (car a) bbdb-xfields-sort-order)) 100)
1854 (or (cdr (assq (car b) bbdb-xfields-sort-order)) 100)))))
1855 (if update
1856 (bbdb-change-record record))))
1857(define-obsolete-function-alias 'bbdb-sort-notes 'bbdb-sort-xfields "3.0")
1858
1859;;; Send-Mail interface
1860
1861;;;###autoload
1862(defun bbdb-dwim-mail (record &optional mail)
1863 ;; Do What I Mean!
1864 "Return a string to use as the mail address of RECORD.
1865The name in the mail address is formatted obeying `bbdb-mail-name-format'
1866and `bbdb-mail-name'. However, if both the first name and last name
1867are constituents of the address as in John.Doe@Some.Host,
1868and `bbdb-mail-avoid-redundancy' is non-nil, then the address is used as is
1869and `bbdb-mail-name-format' and `bbdb-mail-name' are ignored.
1870If `bbdb-mail-avoid-redundancy' is 'mail-only the name is never included.
1871MAIL may be a mail address to be used for RECORD.
1872If MAIL is an integer, use the MAILth mail address of RECORD.
1873If MAIL is nil use the first mail address of RECORD."
1874 (unless mail
1875 (let ((mails (bbdb-record-mail record)))
1876 (setq mail (or (and (integerp mail) (nth mail mails))
1877 (car mails)))))
1878 (unless mail (error "Record has no mail addresses"))
1879 (let (name fn ln)
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.
1883 (if (car address)
1884 (setq mail (cadr address)
1885 name (car address)
1886 ln name))))
1887 ((functionp bbdb-mail-name)
1888 (setq name (funcall bbdb-mail-name record))
1889 (if (consp name)
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))))
1899 (t
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
1908 (cond ((and fn ln)
1909 (let ((fnq (regexp-quote fn))
1910 (lnq (regexp-quote ln)))
1911 (or (string-match (concat "\\`[^!@%]*\\b" fnq
1912 "\\b[^!%@]+\\b" lnq "\\b")
1913 mail)
1914 (string-match (concat "\\`[^!@%]*\\b" lnq
1915 "\\b[^!%@]+\\b" fnq "\\b")
1916 mail))))
1917 ((or fn ln)
1918 (string-match (concat "\\`[^!@%]*\\b"
1919 (regexp-quote (or fn ln)) "\\b")
1920 mail)))))
1921 mail
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)
1929 "\"%s\" <%s>"
1930 "%s <%s>")
1931 name mail))))
1932
1933(defun bbdb-compose-mail (&rest args)
1934 "Start composing a mail message to send.
1935Use `bbdb-mail-user-agent' or (if nil) use `mail-user-agent'.
1936ARGS are passed to `compose-mail'."
1937 (let ((mail-user-agent (or bbdb-mail-user-agent mail-user-agent)))
1938 (apply 'compose-mail args)))
1939
1940;;;###autoload
1941(defun bbdb-mail (records &optional subject n verbose)
1942 "Compose a mail message to RECORDS (optional: using SUBJECT).
1943Interactively, use BBDB prefix \
1944\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1945By default, the first mail addresses of RECORDS are used.
1946If prefix N is a number, use Nth mail address of RECORDS (starting from 1).
1947If prefix N is C-u (t noninteractively) use all mail addresses of RECORDS.
1948If VERBOSE is non-nil (as in interactive calls) be verbose."
1949 (interactive (list (bbdb-do-records) nil
1950 (or (consp current-prefix-arg)
1951 current-prefix-arg)
1952 t))
1953 (setq records (bbdb-record-list records))
1954 (if (not 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)))))
1959
1960(defun bbdb-mail-address (records &optional n kill-ring-save verbose)
1961 "Return mail addresses of RECORDS as a string.
1962Interactively, use BBDB prefix \
1963\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
1964By default, the first mail addresses of RECORDS are used.
1965If prefix N is a number, use Nth mail address of RECORDS (starting from 1).
1966If prefix N is C-u (t noninteractively) use all mail addresses of RECORDS.
1967If KILL-RING-SAVE is non-nil (as in interactive calls), copy mail addresses
1968to 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)
1971 current-prefix-arg)
1972 t t))
1973 (setq records (bbdb-record-list records))
1974 (if (not records)
1975 (progn (if verbose (message "No records")) "")
1976 (let ((good "") bad)
1977 (dolist (record records)
1978 (let ((mails (bbdb-record-mail record)))
1979 (cond ((not mails)
1980 (push record bad))
1981 ((eq n t)
1982 (setq good (bbdb-concat ",\n\t"
1983 good
1984 (mapcar (lambda (mail)
1985 (bbdb-dwim-mail record mail))
1986 mails))))
1987 (t
1988 (setq good (bbdb-concat ",\n\t" good
1989 (bbdb-dwim-mail record (or (and (numberp n)
1990 (nth (1- n) mails))
1991 (car mails)))))))))
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 "")))
1997 (kill-new good)
1998 (if verbose (message "%s" good)))
1999 good)))
2000
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.
2006
2007(defun bbdb-mail-yank ()
2008 "CC the people displayed in the *BBDB* buffer on this mail message.
2009The 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."
2011 (interactive)
2012 (let ((addresses (with-current-buffer bbdb-buffer-name
2013 (delq nil
2014 (mapcar (lambda (x)
2015 (if (bbdb-record-mail (car x))
2016 (bbdb-dwim-mail (car x))))
2017 bbdb-records))))
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.
2023 (unless (eolp)
2024 (end-of-line)
2025 (while (looking-at "\n[ \t]")
2026 (forward-char) (end-of-line))
2027 (insert ",\n")
2028 (indent-relative))
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)
2031 (eolp))
2032 ;; Otherwise, insert an empty CC: field.
2033 (end-of-line)
2034 (while (looking-at "\n[ \t]")
2035 (forward-char) (end-of-line))
2036 (insert "\nCC:")
2037 (indent-relative)))
2038 ;; Now insert each of the addresses on its own line.
2039 (while addresses
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")
2044
2045;;; completion
2046
2047;;;###autoload
2048(defun bbdb-completion-predicate (key records)
2049 "For use as the third argument to `completing-read'.
2050Obey `bbdb-completion-list'."
2051 (cond ((null bbdb-completion-list)
2052 nil)
2053 ((eq t bbdb-completion-list)
2054 t)
2055 (t
2056 (catch 'bbdb-hash-ok
2057 (dolist (record records)
2058 (bbdb-hash-p key record bbdb-completion-list))
2059 nil))))
2060
2061(defun bbdb-completing-read-records (prompt &optional omit-records)
2062 "Read and return list of records from the bbdb.
2063Completion is done according to `bbdb-completion-list'. If the user
2064just 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)
2069 (let (records)
2070 (dolist (record (gethash string bbdb-hashtable))
2071 (if (not (memq record omit-records))
2072 (push record records)))
2073 (delete-dups records)))))
2074
2075(defun bbdb-completing-read-record (prompt &optional omit-records)
2076 "Prompt for and return a single record from the bbdb;
2077completion is done according to `bbdb-completion-list'. If the user
2078just hits return, nil is returned. Otherwise, a valid response is forced.
2079If OMIT-RECORDS is non-nil it should be a list of records to dis-allow
2080completion with."
2081 (let ((records (bbdb-completing-read-records prompt omit-records)))
2082 (cond ((eq (length records) 1)
2083 (car records))
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))
2090 nil t)))
2091 (nth (1- (string-to-number result)) records))))))
2092
2093;;;###autoload
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))
2098
2099(defconst bbdb-quoted-string-syntax-table
2100 (let ((st (make-syntax-table)))
2101 (modify-syntax-entry ?\\ "\\" st)
2102 (modify-syntax-entry ?\" "\"" st)
2103 st)
2104 "Syntax-table to parse matched quotes. Used by `bbdb-complete-mail'.")
2105
2106;;;###autoload
2107(defun bbdb-complete-mail (&optional beg cycle-completion-buffer)
2108 "In a mail buffer, complete the user name or mail before point.
2109Completion happens up to the preceeding colon, comma, or BEG.
2110Return non-nil if there is a valid completion, else return nil.
2111
2112Completion behaviour obeys `bbdb-completion-list' (see there).
2113If what has been typed matches a unique BBDB record, insert an address
2114formatted by `bbdb-dwim-mail' (see there). Also, display this record
2115if `bbdb-completion-display-record' is non-nil,
2116If what has been typed is a valid completion but does not match
2117a unique record, display a list of completions.
2118If the completion is done and `bbdb-complete-mail-allow-cycling' is t
2119then cycle through the mails for the matching record. If BBDB
2120would format a given address different from what we have in the mail buffer,
2121the first round of cycling reformats the address accordingly, then we cycle
2122through the mails for the matching record.
2123With prefix CYCLE-COMPLETION-BUFFER non-nil, display a list of all mails
2124available for cycling.
2125
2126Set the variable `bbdb-complete-mail' non-nil for enabling this feature
2127as part of the MUA insinuation."
2128 (interactive (list nil current-prefix-arg))
2129
2130 (bbdb-buffer) ; Make sure the database is initialized.
2131
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)
2144 (<= (point)
2145 (save-restriction ; `mail-header-end'
2146 (widen)
2147 (save-excursion
2148 (rfc822-goto-eoh)
2149 (point)))))
2150 (let ((end (point))
2151 start pnt state)
2152 (save-excursion
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)
2157 start beg)
2158 (goto-char beg)
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.
2164 (setq beg 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)
2169 start pnt)
2170 (unless (nth 3 state) (setq beg pnt))))))))
2171
2172 ;; Do we have a meaningful way to set BEG if we are not in a message header?
2173 (unless beg
2174 (message "Not a valid buffer position for mail completion")
2175 (sit-for 1))
2176
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)
2185
2186 (unless done
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*
2196 ;; buffer.
2197 (if (and (stringp completion)
2198 (string-match "," completion))
2199 (setq completion (substring completion 0 (match-beginning 0))))
2200
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))
2211 (car records))))
2212
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)))
2217
2218 (cond
2219 ;; Match for a single record
2220 (one-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))
2225 mail elt)
2226 (if (not mails)
2227 (progn
2228 (message "Matching record has no mail field")
2229 (sit-for 1)
2230 (setq done 'nothing))
2231
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
2237 (append
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.
2251 (unless mail
2252 (while (setq elt (pop mails))
2253 (if (try-completion orig (list elt))
2254 (setq mail elt
2255 mails nil))))
2256 ;; This error message indicates a bug!
2257 (unless mail (error "No match for %s" orig))
2258
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)
2268 (insert dwim-mail)
2269 (bbdb-complete-mail-cleanup dwim-mail beg)
2270 (setq done 'unique))))))
2271
2272 ;; Partial completion
2273 ((and (stringp completion)
2274 (not (bbdb-string= orig completion)))
2275 (delete-region beg end)
2276 (insert completion)
2277 (setq done 'partial))
2278
2279 ;; Partial match not allowing further partial completion
2280 (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))
2289 accept)
2290 (when mails
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)))
2299 ((eq field 'aka)
2300 (if (member-ignore-case key (bbdb-record-field
2301 record 'aka-all))
2302 (push (car mails) accept)))
2303 ((eq field 'organization)
2304 (if (member-ignore-case key (bbdb-record-organization
2305 record))
2306 (push (car mails) accept)))
2307 ((eq field 'primary)
2308 (if (bbdb-string= key (car mails))
2309 (push (car mails) accept)))
2310 ((eq field 'mail)
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))))))
2316
2317 (setq dwim-completions (sort (delete-dups dwim-completions)
2318 'string-lessp))
2319 (cond ((not dwim-completions)
2320 (message "Matching record has no mail field")
2321 (sit-for 1)
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)))))))
2332
2333 ;; By now, we have considered all possiblities to perform a completion.
2334 ;; If nonetheless we haven't done anything so far, consider cycling.
2335 ;;
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)))))
2345 (if (and record
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))
2365 mail dwim-mail)
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)
2373 (insert dwim-mail)
2374 (bbdb-complete-mail-cleanup dwim-mail beg)
2375 (setq done 'reformat)))
2376 done))
2377
2378 (t
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)
2385 (insert dwim-mail)
2386 (bbdb-complete-mail-cleanup dwim-mail beg)
2387 (setq done 'cycle)))))))
2388
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
2403 beg end text))
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"))))
2409
2410 ;; If DONE is `nothing' return nil so that possibly some other code
2411 ;; can take over.
2412 (unless (eq done 'nothing)
2413 done)))
2414
2415;;;###autoload
2416(define-obsolete-function-alias 'bbdb-complete-name 'bbdb-complete-mail "3.0")
2417
2418(defun bbdb-complete-mail-cleanup (mail beg)
2419 "Clean up after inserting MAIL at position BEG.
2420If we are past `fill-column', wrap at the previous comma."
2421 (if (and (not (auto-fill-function))
2422 (>= (current-column) fill-column))
2423 (save-excursion
2424 (goto-char beg)
2425 (when (search-backward "," (line-beginning-position) t)
2426 (forward-char 1)
2427 (insert "\n")
2428 (indent-relative)
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))))
2441
2442;;; interface to mail-abbrevs.el.
2443
2444;;;###autoload
2445(defun bbdb-mail-aliases (&optional force-rebuilt noisy)
2446 "Define mail aliases for the records in the database.
2447Define a mail alias for every record that has a `mail-alias' field
2448which is the contents of that field.
2449If there are multiple comma-separated words in the `mail-alias' field,
2450then all of those words will be defined as aliases for that person.
2451
2452If multiple records in the database have the same mail alias,
2453then that alias expands to a comma-separated list of the mail addresses
2454of all of these people.
2455Add this command to `mail-setup-hook'.
2456
2457Mail aliases are (re)built only if `bbdb-mail-aliases-need-rebuilt' is non-nil
2458because the database was newly loaded or it has been edited.
2459Rebuilding 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))
2466
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 ".")))
2472 results match)
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)
2476
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)))
2486 (unless bbdb-silent
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))
2490 (sit-for 1))))
2491
2492 ;; Iterate over the results and create the aliases
2493 (dolist (result results)
2494 (let* ((aliasstem (car result))
2495 (expansions
2496 (if (cddr 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)
2506 (not (cdr mails)))
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))
2514 mails))
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))
2523 alias f-alias)
2524
2525 ;; create the aliases for each expansion
2526 (dolist (expansion expansions)
2527 (cond ((or (= 1 len)
2528 (= count 0))
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))
2535
2536 (bbdb-pushnew (cons alias expansion) mail-aliases)
2537
2538 (define-mail-abbrev alias expansion)
2539 (unless (setq f-alias (intern-soft (downcase alias) mail-abbrevs))
2540 (error "Cannot find the alias"))
2541
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
2567 ,alias
2568 ',(mapcar (lambda (r) (car (bbdb-record-mail r)))
2569 (cdr result))))))))
2570
2571 (if noisy (message "BBDB mail alias: rebuilding done")))))
2572
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
2579 (apply 'append
2580 (mapcar (lambda (mail) (bbdb-message-search nil mail)) mails))
2581 nil t))))
2582
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 ".")))
2586 result)
2587 (dolist (record records)
2588 (dolist (alias (bbdb-record-xfield-split record bbdb-mail-alias-field))
2589 (bbdb-pushnew alias result)))
2590 result))
2591
2592;;;###autoload
2593(defsubst bbdb-mail-alias-list (alias)
2594 (if (stringp alias)
2595 (bbdb-split bbdb-mail-alias-field alias)
2596 alias))
2597
2598(defun bbdb-add-mail-alias (records &optional alias delete)
2599 "Add ALIAS to RECORDS.
2600If prefix DELETE is non-nil, remove ALIAS from RECORDS.
2601Interactively, use BBDB prefix \
2602\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
2603Arg ALIAS is ignored if list RECORDS contains more than one record.
2604Instead read ALIAS interactively for each record in RECORDS.
2605If the function `bbdb-init-mail-alias' is defined, it is called with
2606one arg RECORD to define the default value for ALIAS of RECORD."
2607 (interactive (list (bbdb-do-records) nil current-prefix-arg))
2608 (bbdb-editable)
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))
2616 (alias alias)
2617 a-list)
2618 (if alias
2619 (setq a-list (bbdb-mail-alias-list alias))
2620 (when init-f
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
2624 (concat "[ \t\n]*"
2625 (cadr (assq bbdb-mail-alias-field bbdb-separator-alist))
2626 "[ \t\n]*"))
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))))))
2639 (dolist (a a-list)
2640 (if delete
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)))))
2648\f
2649;;; Dialing numbers from BBDB
2650
2651(defun bbdb-dial-number (phone-string)
2652 "Dial the number specified by PHONE-STRING.
2653This uses the tel URI syntax passed to `browse-url' to make the call.
2654If `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))))
2659
2660;;;###autoload
2661(defun bbdb-dial (phone force-area-code)
2662 "Dial the number at point.
2663If the point is at the beginning of a record, dial the first phone number.
2664Use rules from `bbdb-dial-local-prefix-alist' unless prefix FORCE-AREA-CODE
2665is 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"))
2672
2673 (let ((number (bbdb-phone-string phone))
2674 shortnumber)
2675
2676 ;; cut off the extension
2677 (if (string-match "x[0-9]+$" number)
2678 (setq number (substring number 0 (match-beginning 0))))
2679
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)))
2686 alist nil)))))
2687
2688 (if shortnumber
2689 (setq number shortnumber)
2690
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)))
2696
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)))))
2702
2703 (unless bbdb-silent
2704 (message "Dialing %s" number))
2705 (bbdb-dial-number number)))
2706
2707;;; url interface
2708
2709;;;###autoload
2710(defun bbdb-browse-url (records &optional which)
2711 "Brwose URLs stored in the `url' field of RECORDS.
2712Interactively, use BBDB prefix \
2713\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
2714Prefix WHICH specifies which URL in field `url' is used (starting from 0).
2715Default 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)))
2722 (when url
2723 (setq url (read-string "fetch: " (nth which url)))
2724 (unless (string= "" url)
2725 (browse-url url))))))
2726
2727;;;###autoload
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))
2734 url)))
2735 (bbdb-record-set-field record 'url url t)
2736 (bbdb-change-record record)
2737 (bbdb-display-records (list record)))
2738
2739;;; Copy to kill ring
2740
2741;;;###autoload
2742(defun bbdb-copy-records-as-kill (records)
2743 "Copy RECORDS to kill ring.
2744Interactively, use BBDB prefix \
2745\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
2746 (interactive (list (bbdb-do-records t)))
2747 (let (drec)
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))))
2751 (point-max)))
2752 drec))
2753 (kill-new (replace-regexp-in-string
2754 "[ \t\n]*\\'" "\n"
2755 (mapconcat 'identity (nreverse drec) "")))))
2756
2757;;;###autoload
2758(defun bbdb-copy-fields-as-kill (records field &optional num)
2759 "For RECORDS copy values of FIELD at point to kill ring.
2760If FIELD is an address or phone with a label, copy only field values
2761with the same label. With numeric prefix NUM, if the value of FIELD
2762is a list, copy only the NUMth list element.
2763Interactively, use BBDB prefix \
2764\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
2765 (interactive
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)
2771 (car (nth 1 field))
2772 (car field)))
2773 (label (if (memq type '(phone address))
2774 (aref (cadr field) 0)))
2775 (ident (and (< 1 (length records))
2776 (not (eq type 'name))))
2777 val-list)
2778 (dolist (record (bbdb-record-list records))
2779 (let ((raw-val (bbdb-record-field (car record) type))
2780 value)
2781 (if raw-val
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))))
2787 ((eq type 'address)
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))
2792 value)))
2793 (setq value (bbdb-concat 'address (nreverse value))))
2794 ((consp raw-val)
2795 (setq value (if num (nth num raw-val)
2796 (bbdb-concat type raw-val))))
2797 (t (setq value raw-val))))
2798 (if value
2799 (push (if ident
2800 (bbdb-concat 'name-field
2801 (bbdb-record-name (car record)) value)
2802 value) val-list))))
2803 (let ((str (bbdb-concat 'record (nreverse val-list))))
2804 (kill-new str)
2805 (message "%s" str))))
2806
2807;;; Help and documentation
2808
2809;;;###autoload
2810(defun bbdb-info ()
2811 (interactive)
2812 (info (format "(%s)Top" (or bbdb-info-file "bbdb"))))
2813
2814;;;###autoload
2815(defun bbdb-help ()
2816 (interactive)
2817 (message (substitute-command-keys "\\<bbdb-mode-map>\
2818new field: \\[bbdb-insert-field]; \
2819edit field: \\[bbdb-edit-field]; \
2820delete field: \\[bbdb-delete-field-or-record]; \
2821mode help: \\[describe-mode]; \
2822info: \\[bbdb-info]")))
2823
2824(provide 'bbdb-com)
2825
2826;;; bbdb-com.el ends here