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 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 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 2827