summaryrefslogtreecommitdiffstats
path: root/lisp/bbdb/bbdb-com.el
diff options
context:
space:
mode:
authorAmin Bandali <bandali@gnu.org>2018-12-23 00:20:54 -0500
committerAmin Bandali <bandali@gnu.org>2018-12-23 00:20:54 -0500
commit59937519c5cf62fa7a6b83ff4a5f8f236449ae68 (patch)
tree0f2292d5d1d849d6e199f258383e6e35eb67593c /lisp/bbdb/bbdb-com.el
parent66075b3deb7af9ce375f4655571eaea5d7bdae35 (diff)
downloadconfigs-59937519c5cf62fa7a6b83ff4a5f8f236449ae68.tar.gz
configs-59937519c5cf62fa7a6b83ff4a5f8f236449ae68.tar.xz
configs-59937519c5cf62fa7a6b83ff4a5f8f236449ae68.zip
[emacs] remove bbdb — using ebdb now
Diffstat (limited to '')
-rw-r--r--lisp/bbdb/bbdb-com.el2826
1 files changed, 0 insertions, 2826 deletions
diff --git a/lisp/bbdb/bbdb-com.el b/lisp/bbdb/bbdb-com.el
deleted file mode 100644
index 500a0e0..0000000
--- a/lisp/bbdb/bbdb-com.el
+++ /dev/null
@@ -1,2826 +0,0 @@
-;;; bbdb-com.el --- user-level commands of BBDB -*- lexical-binding: t -*-
-
-;; Copyright (C) 2010-2017 Free Software Foundation, Inc.
-
-;; This file is part of the Insidious Big Brother Database (aka BBDB),
-
-;; BBDB is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-
-;; BBDB is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-;; GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with BBDB. If not, see <http://www.gnu.org/licenses/>.
-
-;;; Commentary:
-;; This file contains most of the user-level interactive commands for BBDB.
-;; See the BBDB info manual for documentation.
-
-;;; Code:
-
-(require 'bbdb)
-(require 'mailabbrev)
-
-(eval-and-compile
- (autoload 'build-mail-aliases "mailalias")
- (autoload 'browse-url-url-at-point "browse-url"))
-
-(require 'crm)
-(defvar bbdb-crm-local-completion-map
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map crm-local-completion-map)
- (define-key map " " 'self-insert-command)
- map)
- "Keymap used for BBDB crm completions.")
-
-(defun bbdb-get-records (prompt)
- "If inside the *BBDB* buffer get the current records.
-In other buffers ask the user."
- (if (string= bbdb-buffer-name (buffer-name))
- (bbdb-do-records)
- (bbdb-completing-read-records prompt)))
-
-;; Note about the arg RECORDS of various BBDB commands:
-;; - Usually, RECORDS is a list of records. (Interactively,
-;; this list of records is set up by `bbdb-do-records'.)
-;; - If these commands are used, e.g., in `bbdb-create-hook' or
-;; `bbdb-change-hook', they will be called with one arg, a single record.
-;; So depending on context the value of RECORDS will be a single record
-;; or a list of records, and we want to handle both cases.
-;; So we pass RECORDS to `bbdb-record-list' to handle both cases.
-(defun bbdb-record-list (records &optional full)
- "Ensure that RECORDS is a list of records.
-If RECORDS is a single record turn it into a list.
-If FULL is non-nil, assume that RECORDS include display information."
- (if records
- (if full
- (if (vectorp (car records)) (list records) records)
- (if (vectorp records) (list records) records))))
-
-;; Note about BBDB prefix commands:
-;; `bbdb-do-all-records', `bbdb-append-display' and `bbdb-search-invert'
-;; are fake prefix commands. They need not precede the main commands.
-;; Also, `bbdb-append-display' can act on multiple commands.
-
-(defun bbdb-prefix-message ()
- "Display a message about selected BBDB prefix commands."
- (let ((msg (bbdb-concat " " (elt bbdb-modeline-info 1)
- (elt bbdb-modeline-info 3)
- (elt bbdb-modeline-info 5))))
- (unless (string= "" msg) (message "%s" msg))))
-
-;;;###autoload
-(defun bbdb-do-all-records (&optional arg)
- "Command prefix for operating on all records currently displayed.
-With prefix ARG a positive number, operate on all records.
-With prefix ARG a negative number, operate on current record only.
-This only works for certain commands."
- (interactive "P")
- (setq bbdb-do-all-records
- (or (and (numberp arg) (< 0 arg))
- (and (not (numberp arg)) (not bbdb-do-all-records))))
- (aset bbdb-modeline-info 4 (if bbdb-do-all-records "all"))
- (aset bbdb-modeline-info 5
- (if bbdb-do-all-records
- (substitute-command-keys
- "\\<bbdb-mode-map>\\[bbdb-do-all-records]")))
- (bbdb-prefix-message))
-
-;;;###autoload
-(defun bbdb-do-records (&optional full)
- "Return list of records to operate on.
-Normally this list includes only the current record.
-It includes all currently displayed records if the command prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records] is used.
-If FULL is non-nil, the list of records includes display information."
- (if bbdb-do-all-records
- (progn
- (setq bbdb-do-all-records nil)
- (aset bbdb-modeline-info 4 nil)
- (aset bbdb-modeline-info 5 nil)
- (if full bbdb-records (mapcar 'car bbdb-records)))
- (list (bbdb-current-record full))))
-
-;;;###autoload
-(defun bbdb-append-display-p ()
- "Return variable `bbdb-append-display' and reset."
- (let ((job (cond ((eq t bbdb-append-display))
- ((numberp bbdb-append-display)
- (setq bbdb-append-display (1- bbdb-append-display))
- (if (zerop bbdb-append-display)
- (setq bbdb-append-display nil))
- t)
- (bbdb-append-display
- (setq bbdb-append-display nil)
- t))))
- (cond ((numberp bbdb-append-display)
- (aset bbdb-modeline-info 0
- (format "(add %dx)" bbdb-append-display)))
- ((not bbdb-append-display)
- (aset bbdb-modeline-info 0 nil)
- (aset bbdb-modeline-info 1 nil)))
- job))
-
-;;;###autoload
-(defun bbdb-append-display (&optional arg)
- "Toggle appending next searched records in the *BBDB* buffer.
-With prefix ARG \\[universal-argument] always append.
-With ARG a positive number append for that many times.
-With ARG a negative number do not append."
- (interactive "P")
- (setq bbdb-append-display
- (cond ((and arg (listp arg)) t)
- ((and (numberp arg) (< 1 arg)) arg)
- ((or (and (numberp arg) (< arg 0)) bbdb-append-display) nil)
- (t 'once)))
- (aset bbdb-modeline-info 0
- (cond ((numberp bbdb-append-display)
- (format "(add %dx)" bbdb-append-display))
- ((eq t bbdb-append-display) "Add")
- (bbdb-append-display "add")
- (t nil)))
- (aset bbdb-modeline-info 1
- (if bbdb-append-display
- (substitute-command-keys
- "\\<bbdb-mode-map>\\[bbdb-append-display]")))
- (bbdb-prefix-message))
-
-(defsubst bbdb-layout-prefix ()
- "Set the LAYOUT arg interactively using the prefix arg."
- (cond ((eq current-prefix-arg 0) 'one-line)
- (current-prefix-arg 'multi-line)
- (t bbdb-layout)))
-
-(defun bbdb-search-invert-p ()
- "Return variable `bbdb-search-invert' and set it to nil.
-To set it again, use command `bbdb-search-invert'."
- (let ((result bbdb-search-invert))
- (setq bbdb-search-invert nil)
- (aset bbdb-modeline-info 2 nil)
- (aset bbdb-modeline-info 3 nil)
- result))
-
-;;;###autoload
-(defun bbdb-search-invert (&optional arg)
- "Toggle inversion of the next search command.
-With prefix ARG a positive number, invert next search.
-With prefix ARG a negative number, do not invert next search."
- (interactive "P")
- (setq bbdb-search-invert
- (or (and (numberp arg) (< 0 arg))
- (and (not (numberp arg)) (not bbdb-search-invert))))
- (aset bbdb-modeline-info 2 (if bbdb-search-invert "inv"))
- (aset bbdb-modeline-info 3 (if bbdb-search-invert
- (substitute-command-keys
- "\\<bbdb-mode-map>\\[bbdb-search-invert]")))
- (bbdb-prefix-message))
-
-(defmacro bbdb-search (records &rest spec)
- "Search RECORDS for fields matching SPEC.
-The following keywords are supported in SPEC to search fields in RECORDS
-matching the regexps RE:
-
-:name RE Match RE against first-last name.
-:name-fl RE Match RE against last-first name.
-:all-names RE Match RE against first-last, last-first, and aka.
-:affix RE Match RE against affixes.
-:aka RE Match RE against akas.
-:organization RE Match RE against organizations.
-:mail RE Match RE against mail addresses.
-:xfield RE Match RE against `bbdb-default-xfield'.
- RE may also be a cons (LABEL . REGEXP).
- Then REGEXP is matched against xfield LABEL.
- If LABEL is '* then RE is matched against all xfields.
-:creation-date RE Match RE against creation-date.
-:timestamp RE Match RE against timestamp.
-
-Each of these keywords may appear multiple times.
-Other keywords:
-
-:bool BOOL Combine the search for multiple fields using BOOL.
- BOOL may be either `or' (match either field)
- or `and' (match all fields) with default `or'.
-
-To reverse the search, bind `bbdb-search-invert' to t.
-See also `bbdb-message-search' for fast searches using `bbdb-hashtable'
-but not allowing for regexps.
-
-For backward compatibility, SPEC may also consist of the optional args
- NAME ORGANIZATION MAIL XFIELD PHONE ADDRESS
-which is equivalent to
- :all-names NAME :organization ORGANIZATION :mail MAIL
- :xfield XFIELD :phone PHONE :address ADDRESS
-This usage is discouraged."
- (when (not (keywordp (car spec)))
- ;; Old format for backward compatibility
- (unless (get 'bbdb-search 'bbdb-outdated)
- (put 'bbdb-search 'bbdb-outdated t)
- (message "Outdated usage of `bbdb-search'")
- (sit-for 2))
- (let (newspec val)
- (dolist (key '(:all-names :organization :mail :xfield :phone :address))
- (if (setq val (pop spec))
- (push (list key val) newspec)))
- (setq spec (apply 'append newspec))))
-
- (let* ((count 0)
- (sym-list (mapcar (lambda (_)
- (make-symbol
- (format "bbdb-re-%d" (setq count (1+ count)))))
- spec))
- (bool (make-symbol "bool"))
- (not-invert (make-symbol "not-invert"))
- (matches (make-symbol "matches"))
- keyw re-list clauses)
- (set bool ''or) ; default
-
- ;; Check keys.
- (while (keywordp (setq keyw (car spec)))
- (setq spec (cdr spec))
- (pcase keyw
- (`:name
- (let ((sym (pop sym-list)))
- (push `(,sym ,(pop spec)) re-list)
- (push `(string-match ,sym (bbdb-record-name record)) clauses)))
-
- (`:name-lf
- (let ((sym (pop sym-list)))
- (push `(,sym ,(pop spec)) re-list)
- (push `(string-match ,sym (bbdb-record-name-lf record)) clauses)))
-
- (`:all-names
- (let ((sym (pop sym-list)))
- (push `(,sym ,(pop spec)) re-list)
- (push `(or (string-match ,sym (bbdb-record-name record))
- (string-match ,sym (bbdb-record-name-lf record))
- (let ((akas (bbdb-record-field record 'aka-all))
- aka done)
- (while (and (setq aka (pop akas)) (not done))
- (setq done (string-match ,sym aka)))
- done))
- clauses)))
-
- (`:affix
- (let ((sym (pop sym-list)))
- (push `(,sym ,(pop spec)) re-list)
- (push `(let ((affixs (bbdb-record-field record 'affix-all))
- affix done)
- (if affix
- (while (and (setq affix (pop affixs)) (not done))
- (setq done (string-match ,sym affix)))
- ;; so that "^$" matches records without affix
- (setq done (string-match ,sym "")))
- done)
- clauses)))
-
- (`:aka
- (let ((sym (pop sym-list)))
- (push `(,sym ,(pop spec)) re-list)
- (push `(let ((akas (bbdb-record-field record 'aka-all))
- aka done)
- (if aka
- (while (and (setq aka (pop akas)) (not done))
- (setq done (string-match ,sym aka)))
- ;; so that "^$" matches records without aka
- (setq done (string-match ,sym "")))
- done)
- clauses)))
-
- (`:organization
- (let ((sym (pop sym-list)))
- (push `(,sym ,(pop spec)) re-list)
- (push `(let ((organizations (bbdb-record-organization record))
- org done)
- (if organizations
- (while (and (setq org (pop organizations)) (not done))
- (setq done (string-match ,sym org)))
- ;; so that "^$" matches records without organizations
- (setq done (string-match ,sym "")))
- done)
- clauses)))
-
- (`:phone
- (let ((sym (pop sym-list)))
- (push `(,sym ,(pop spec)) re-list)
- (push `(let ((phones (bbdb-record-phone record))
- ph done)
- (if phones
- (while (and (setq ph (pop phones)) (not done))
- (setq done (string-match ,sym
- (bbdb-phone-string ph))))
- ;; so that "^$" matches records without phones
- (setq done (string-match ,sym "")))
- done)
- clauses)))
-
- (`:address
- (let ((sym (pop sym-list)))
- (push `(,sym ,(pop spec)) re-list)
- (push `(let ((addresses (bbdb-record-address record))
- a done)
- (if addresses
- (while (and (setq a (pop addresses)) (not done))
- (setq done (string-match ,sym
- (bbdb-format-address a 2))))
- ;; so that "^$" matches records without addresses
- (setq done (string-match ,sym "")))
- done)
- clauses)))
-
- (`:mail
- (let ((sym (pop sym-list)))
- (push `(,sym ,(pop spec)) re-list)
- (push `(let ((mails (bbdb-record-mail record))
- (bbdb-case-fold-search t) ; there is no case for mails
- m done)
- (if mails
- (while (and (setq m (pop mails)) (not done))
- (setq done (string-match ,sym m)))
- ;; so that "^$" matches records without mail
- (setq done (string-match ,sym "")))
- done)
- clauses)))
-
- (`:xfield
- (let ((sym (pop sym-list)))
- (push `(,sym ,(pop spec)) re-list)
- (push `(cond ((stringp ,sym)
- ;; check xfield `bbdb-default-xfield'
- ;; "^$" matches records without notes field
- (string-match ,sym
- (or (bbdb-record-xfield-string
- record bbdb-default-xfield) "")))
- ((eq (car ,sym) '*)
- ;; check all xfields
- (let ((labels bbdb-xfield-label-list) done tmp)
- (while (and (not done) labels)
- (setq tmp (bbdb-record-xfield-string record (car labels))
- done (and tmp (string-match (cdr ,sym)
- tmp))
- labels (cdr labels)))
- done))
- (t ; check one field
- (string-match (cdr ,sym)
- (or (bbdb-record-xfield-string
- record (car ,sym)) ""))))
- clauses)))
-
- (`:creation-date
- (let ((sym (pop sym-list)))
- (push `(,sym ,(pop spec)) re-list)
- (push `(string-match ,sym (bbdb-record-creation-date record))
- clauses)))
-
- (`:timestamp
- (let ((sym (pop sym-list)))
- (push `(,sym ,(pop spec)) re-list)
- (push `(string-match ,sym (bbdb-record-timestamp record))
- clauses)))
-
- (`:bool
- (set bool (pop spec)))
-
- ;; Do we need other keywords?
-
- (_ (error "Keyword `%s' undefines" keyw))))
-
- `(let ((case-fold-search bbdb-case-fold-search)
- (,not-invert (not (bbdb-search-invert-p)))
- ,@re-list ,matches)
- ;; Are there any use cases for `bbdb-search' where BOOL is only
- ;; known at run time? A smart byte compiler will hopefully
- ;; simplify the code below if we know BOOL already at compile time.
- ;; Alternatively, BOOL could also be a user function that
- ;; defines more complicated boolian expressions. Yet then we loose
- ;; the efficiency of `and' and `or' that evaluate its arguments
- ;; as needed. We would need instead boolian macros that the compiler
- ;; can analyze at compile time.
- (if (eq 'and ,(symbol-value bool))
- (dolist (record ,records)
- (unless (eq ,not-invert (not (and ,@clauses)))
- (push record ,matches)))
- (dolist (record ,records)
- (unless (eq ,not-invert (not (or ,@clauses)))
- (push record ,matches))))
- (nreverse ,matches))))
-
-(defun bbdb-search-read (&optional field)
- "Read regexp to search FIELD values of records."
- (read-string (format "Search records%s %smatching regexp: "
- (if field (concat " with " field) "")
- (if bbdb-search-invert "not " ""))))
-
-;;;###autoload
-(defun bbdb (regexp &optional layout)
- "Display all records in the BBDB matching REGEXP
-in either the name(s), organization, address, phone, mail, or xfields."
- (interactive (list (bbdb-search-read) (bbdb-layout-prefix)))
- (let ((records (bbdb-search (bbdb-records) :all-names regexp
- :organization regexp :mail regexp
- :xfield (cons '* regexp)
- :phone regexp :address regexp :bool 'or)))
- (if records
- (bbdb-display-records records layout nil t)
- (message "No records matching '%s'" regexp))))
-
-;;;###autoload
-(defun bbdb-search-name (regexp &optional layout)
- "Display all records in the BBDB matching REGEXP in the name
-\(or ``alternate'' names\)."
- (interactive (list (bbdb-search-read "names") (bbdb-layout-prefix)))
- (bbdb-display-records (bbdb-search (bbdb-records) :all-names regexp) layout))
-
-;;;###autoload
-(defun bbdb-search-organization (regexp &optional layout)
- "Display all records in the BBDB matching REGEXP in the organization field."
- (interactive (list (bbdb-search-read "organization") (bbdb-layout-prefix)))
- (bbdb-display-records (bbdb-search (bbdb-records) :organization regexp)
- layout))
-
-;;;###autoload
-(defun bbdb-search-address (regexp &optional layout)
- "Display all records in the BBDB matching REGEXP in the address fields."
- (interactive (list (bbdb-search-read "address") (bbdb-layout-prefix)))
- (bbdb-display-records (bbdb-search (bbdb-records) :address regexp)
- layout))
-
-;;;###autoload
-(defun bbdb-search-mail (regexp &optional layout)
- "Display all records in the BBDB matching REGEXP in the mail address."
- (interactive (list (bbdb-search-read "mail address") (bbdb-layout-prefix)))
- (bbdb-display-records (bbdb-search (bbdb-records) :mail regexp) layout))
-
-;;;###autoload
-(defun bbdb-search-phone (regexp &optional layout)
- "Display all records in the BBDB matching REGEXP in the phones field."
- (interactive (list (bbdb-search-read "phone") (bbdb-layout-prefix)))
- (bbdb-display-records
- (bbdb-search (bbdb-records) :phone regexp) layout))
-
-;;;###autoload
-(defun bbdb-search-xfields (field regexp &optional layout)
- "Display all BBDB records for which xfield FIELD matches REGEXP."
- (interactive
- (let ((field (completing-read "Xfield to search (RET for all): "
- (mapcar 'list bbdb-xfield-label-list) nil t)))
- (list (if (string= field "") '* (intern field))
- (bbdb-search-read (if (string= field "")
- "any xfield"
- field))
- (bbdb-layout-prefix))))
- (bbdb-display-records (bbdb-search (bbdb-records) :xfield (cons field regexp))
- layout))
-(define-obsolete-function-alias 'bbdb-search-notes 'bbdb-search-xfields "3.0")
-
-;;;###autoload
-(defun bbdb-search-changed (&optional layout)
- ;; FIXME: "changes" in BBDB lingo are often called "modifications"
- ;; in Emacs lingo
- "Display records which have been changed since BBDB was last saved."
- (interactive (list (bbdb-layout-prefix)))
- (if (bbdb-search-invert-p)
- (let (unchanged-records)
- (dolist (record (bbdb-records))
- (unless (memq record bbdb-changed-records)
- (push record unchanged-records)))
- (bbdb-display-records unchanged-records layout))
- (bbdb-display-records bbdb-changed-records layout)))
-
-(defun bbdb-search-prog (fun &optional layout)
- "Search records using function FUN.
-FUN is called with one argument, the record, and should return
-the record to be displayed or nil otherwise."
- (bbdb-display-records (delq nil (mapcar fun (bbdb-records))) layout))
-
-
-;; clean-up functions
-
-;; Sometimes one gets mail from foo@bar.baz.com, and then later gets mail
-;; from foo@baz.com. At this point, one would like to delete the bar.baz.com
-;; address, since the baz.com address is obviously superior.
-
-(defun bbdb-mail-redundant-re (mail)
- "Return a regexp matching redundant variants of email address MAIL.
-For example, \"foo@bar.baz.com\" is redundant w.r.t. \"foo@baz.com\".
-Return nil if MAIL is not a valid plain email address.
-In particular, ignore addresses \"Joe Smith <foo@baz.com>\"."
- (let* ((match (string-match "\\`\\([^ ]+\\)@\\(.+\\)\\'" mail))
- (name (and match (match-string 1 mail)))
- (host (and match (match-string 2 mail))))
- (if (and name host)
- (concat (regexp-quote name) "@.*\\." (regexp-quote host)))))
-
-(defun bbdb-delete-redundant-mails (records &optional query update)
- "Delete redundant or duplicate mails from RECORDS.
-For example, \"foo@bar.baz.com\" is redundant w.r.t. \"foo@baz.com\".
-Duplicates may (but should not) occur if we feed BBDB automatically.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
-If QUERY is non-nil (as in interactive calls, unless we use a prefix arg)
-query before deleting the redundant mail addresses.
-If UPDATE is non-nil (as in interactive calls) update the database.
-Otherwise, this is the caller's responsiblity.
-
-Noninteractively, this may be used as an element of `bbdb-notice-record-hook'
-or `bbdb-change-hook'. However, see also `bbdb-ignore-redundant-mails',
-which is probably more suited for your needs."
- (interactive (list (bbdb-do-records) (not current-prefix-arg) t))
- (bbdb-editable)
- (dolist (record (bbdb-record-list records))
- (let (mails redundant okay)
- ;; We do not look at the canonicalized mail addresses of RECORD.
- ;; An address "Joe Smith <foo@baz.com>" can only be entered manually
- ;; into BBDB, and we assume that this is what the user wants.
- ;; Anyway, if a mail field contains all the elements
- ;; foo@baz.com, "Joe Smith <foo@baz.com>", "Jonathan Smith <foo@baz.com>"
- ;; we do not know which address to keep and which ones to throw.
- (dolist (mail (bbdb-record-mail record))
- (if (assoc-string mail mails t) ; duplicate mail address
- (push mail redundant)
- (push mail mails)))
- (let ((mail-re (delq nil (mapcar 'bbdb-mail-redundant-re mails)))
- (case-fold-search t))
- (if (not (cdr mail-re)) ; at most one mail-re address to consider
- (setq okay (nreverse mails))
- (setq mail-re (concat "\\`\\(?:" (mapconcat 'identity mail-re "\\|")
- "\\)\\'"))
- (dolist (mail mails)
- (if (string-match mail-re mail) ; redundant mail address
- (push mail redundant)
- (push mail okay)))))
- (let ((form (format "redundant mail%s %s"
- (if (< 1 (length redundant)) "s" "")
- (bbdb-concat 'mail (nreverse redundant)))))
- (when (and redundant
- (or (not query)
- (y-or-n-p (format "Delete %s: " form))))
- (unless query (message "Deleting %s" form))
- (bbdb-record-set-field record 'mail okay)
- (when update
- (bbdb-change-record record)))))))
-(define-obsolete-function-alias 'bbdb-delete-duplicate-mails
- 'bbdb-delete-redundant-mails "3.0")
-
-(defun bbdb-search-duplicates (&optional fields)
- "Search all records that have duplicate entries for FIELDS.
-The list FIELDS may contain the symbols `name', `mail', and `aka'.
-If FIELDS is nil use all these fields. With prefix, query for FIELDS.
-The search results are displayed in the BBDB buffer."
- (interactive (list (if current-prefix-arg
- (list (intern (completing-read "Field: "
- '("name" "mail" "aka")
- nil t))))))
- (setq fields (or fields '(name mail aka)))
- (let (hash ret)
- (dolist (record (bbdb-records))
-
- (when (and (memq 'name fields)
- (bbdb-record-name record)
- (setq hash (bbdb-gethash (bbdb-record-name record)
- '(fl-name lf-name aka)))
- (> (length hash) 1))
- (setq ret (append hash ret))
- (message "BBDB record `%s' has duplicate name."
- (bbdb-record-name record))
- (sit-for 0))
-
- (if (memq 'mail fields)
- (dolist (mail (bbdb-record-mail-canon record))
- (setq hash (bbdb-gethash mail '(mail)))
- (when (> (length hash) 1)
- (setq ret (append hash ret))
- (message "BBDB record `%s' has duplicate mail `%s'."
- (bbdb-record-name record) mail)
- (sit-for 0))))
-
- (if (memq 'aka fields)
- (dolist (aka (bbdb-record-aka record))
- (setq hash (bbdb-gethash aka '(fl-name lf-name aka)))
- (when (> (length hash) 1)
- (setq ret (append hash ret))
- (message "BBDB record `%s' has duplicate aka `%s'"
- (bbdb-record-name record) aka)
- (sit-for 0)))))
-
- (bbdb-display-records (sort (delete-dups ret)
- 'bbdb-record-lessp))))
-
-(defun bbdb-fix-records (records)
- "Fix broken RECORDS.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
- (interactive (list (bbdb-do-records)))
- (bbdb-editable)
- (dolist (record (bbdb-record-list records))
- ;; For the fields which take a list of strings (affix, organization,
- ;; aka, and mail) `bbdb=record-set-field' calls `bbdb-list-strings'
- ;; which removes all elements from such a list which are not non-empty
- ;; strings. This should fix most problems with these fields.
- (bbdb-record-set-field record 'affix (bbdb-record-affix record))
- (bbdb-record-set-field record 'organization (bbdb-record-organization record))
- (bbdb-record-set-field record 'aka (bbdb-record-aka record))
- (bbdb-record-set-field record 'mail (bbdb-record-mail record))
- (bbdb-change-record record))
- (bbdb-sort-records))
-
-(defun bbdb-touch-records (records)
- "Touch RECORDS by calling `bbdb-change-hook' unconditionally.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
- (interactive (list (bbdb-do-records)))
- (bbdb-editable)
- (let ((bbdb-update-unchanged-records t))
- (dolist (record (bbdb-record-list records))
- (bbdb-change-record record))))
-
-;;; Time-based functions
-
-(defmacro bbdb-compare-records (cmpval label compare)
- "Builds a lambda comparison function that takes one argument, RECORD.
-RECORD is returned if (COMPARE VALUE CMPVAL) is t, where VALUE
-is the value of field LABEL of RECORD."
- `(lambda (record)
- (let ((val (bbdb-record-field record ,label)))
- (if (and val (,compare val ,cmpval))
- record))))
-
-(defsubst bbdb-string> (a b)
- (not (or (string= a b)
- (string< a b))))
-
-;;;###autoload
-(defun bbdb-timestamp-older (date &optional layout)
- "Display records with timestamp older than DATE.
-DATE must be in yyyy-mm-dd format."
- (interactive (list (read-string "Timestamp older than: (yyyy-mm-dd) ")
- (bbdb-layout-prefix)))
- (bbdb-search-prog (bbdb-compare-records date 'timestamp string<) layout))
-
-;;;###autoload
-(defun bbdb-timestamp-newer (date &optional layout)
- "Display records with timestamp newer than DATE.
-DATE must be in yyyy-mm-dd format."
- (interactive (list (read-string "Timestamp newer than: (yyyy-mm-dd) ")
- (bbdb-layout-prefix)))
- (bbdb-search-prog (bbdb-compare-records date 'timestamp bbdb-string>) layout))
-
-;;;###autoload
-(defun bbdb-creation-older (date &optional layout)
- "Display records with creation-date older than DATE.
-DATE must be in yyyy-mm-dd format."
- (interactive (list (read-string "Creation older than: (yyyy-mm-dd) ")
- (bbdb-layout-prefix)))
- (bbdb-search-prog (bbdb-compare-records date 'creation-date string<) layout))
-
-;;;###autoload
-(defun bbdb-creation-newer (date &optional layout)
- "Display records with creation-date newer than DATE.
-DATE must be in yyyy-mm-dd format."
- (interactive (list (read-string "Creation newer than: (yyyy-mm-dd) ")
- (bbdb-layout-prefix)))
- (bbdb-search-prog (bbdb-compare-records date 'creation-date bbdb-string>) layout))
-
-;;;###autoload
-(defun bbdb-creation-no-change (&optional layout)
- "Display records that have the same timestamp and creation-date."
- (interactive (list (bbdb-layout-prefix)))
- (bbdb-search-prog
- ;; RECORD is bound in `bbdb-compare-records'.
- (bbdb-compare-records (bbdb-record-timestamp record)
- 'creation-date string=)
- layout))
-
-;;; Parsing phone numbers
-;; XXX this needs expansion to handle international prefixes properly
-;; i.e. +353-number without discarding the +353 part. Problem being
-;; that this will necessitate yet another change in the database
-;; format for people who are using north american numbers.
-
-(defsubst bbdb-subint (string num)
- "Used for parsing phone numbers."
- (string-to-number (match-string num string)))
-
-(defun bbdb-parse-phone (string &optional style)
- "Parse a phone number from STRING and return a list of integers the form
-\(area-code exchange number extension).
-This is both lenient and strict in what it will parse - whitespace may
-appear (or not) between any of the groups of digits, parentheses around the
-area code are optional, as is a dash between the exchange and number, and
-a '1' preceeding the area code; but there must be three digits in the area
-code and exchange, and four in the number (if they are present).
-All of these are unambigously parsable:
-
- ( 415 ) 555 - 1212 x123 -> (415 555 1212 123)
- (415)555-1212 123 -> (415 555 1212 123)
- (1-415) 555-1212 123 -> (415 555 1212 123)
- 1 (415)-555-1212 123 -> (415 555 1212 123)
- 555-1212 123 -> (0 555 1212 123)
- 555 1212 -> (0 555 1212 0)
- 415 555 1212 -> (415 555 1212 0)
- 1 415 555 1212 -> (415 555 1212 0)
- 5551212 -> (0 555 1212 0)
- 4155551212 -> (415 555 1212 0)
- 4155551212123 -> (415 555 1212 123)
- 5551212x123 -> (0 555 1212 123)
- 1234 -> (0 0 0 1234)
-
-Note that \"4151212123\" is ambiguous; it could be interpreted either as
-\"(415) 121-2123\" or as \"415-1212 x123\".
-
-Return a list containing four numbers or one string."
-
- ;; RW: Missing parts of NANP numbers are replaced by zeros.
- ;; Is this always correct? What about an extension zero?
- ;; Should we use nil instead of zeros?
- (unless style (setq style bbdb-phone-style))
- (let ((area-regexp (concat "(?[ \t]*\\+?1?[ \t]*[-\(]?[ \t]*[-\(]?[ \t]*"
- "\\([2-9][0-9][0-9]\\)[ \t]*)?[-./ \t]*"))
- (main-regexp (concat "\\([1-9][0-9][0-9]\\)[ \t]*[-.]?[ \t]*"
- "\\([0-9][0-9][0-9][0-9]\\)[ \t]*"))
- (ext-regexp "x?[ \t]*\\([0-9]+\\)[ \t]*"))
- (cond ((not (eq style 'nanp))
- (list (bbdb-string-trim string)))
- ((string-match ;; (415) 555-1212 x123
- (concat "^[ \t]*" area-regexp main-regexp ext-regexp "$") string)
- (list (bbdb-subint string 1) (bbdb-subint string 2)
- (bbdb-subint string 3) (bbdb-subint string 4)))
- ;; (415) 555-1212
- ((string-match (concat "^[ \t]*" area-regexp main-regexp "$") string)
- (list (bbdb-subint string 1) (bbdb-subint string 2)
- (bbdb-subint string 3) 0))
- ;; 555-1212 x123
- ((string-match (concat "^[ \t]*" main-regexp ext-regexp "$") string)
- (list 0 (bbdb-subint string 1) (bbdb-subint string 2)
- (bbdb-subint string 3)))
- ;; 555-1212
- ((string-match (concat "^[ \t]*" main-regexp "$") string)
- (list 0 (bbdb-subint string 1) (bbdb-subint string 2) 0))
- ;; x123
- ((string-match (concat "^[ \t]*" ext-regexp "$") string)
- (list 0 0 0 (bbdb-subint string 1)))
- ;; We trust the user she knows what she wants
- (t (list (bbdb-string-trim string))))))
-
-(defun bbdb-message-search (name mail)
- "Return list of BBDB records matching NAME and/or MAIL.
-First try to find a record matching both NAME and MAIL.
-If this fails try to find a record matching MAIL.
-If this fails try to find a record matching NAME.
-NAME may match FIRST_LAST, LAST_FIRST or AKA.
-
-This function performs a fast search using `bbdb-hashtable'.
-NAME and MAIL must be strings or nil.
-See `bbdb-search' for searching records with regexps."
- (when (or name mail)
- (bbdb-buffer) ; make sure database is loaded and up-to-date
- (let ((mrecords (if mail (bbdb-gethash mail '(mail))))
- (nrecords (if name (bbdb-gethash name '(fl-name lf-name aka)))))
- ;; (1) records matching NAME and MAIL
- (or (and mrecords nrecords
- (let (records)
- (dolist (record nrecords)
- (mapc (lambda (mr) (if (and (eq record mr)
- (not (memq record records)))
- (push record records)))
- mrecords))
- records))
- ;; (2) records matching MAIL
- mrecords
- ;; (3) records matching NAME
- nrecords))))
-
-(defun bbdb-read-record (&optional first-and-last)
- "Read and return a new BBDB record.
-Does not insert it into the database or update the hashtables,
-but does ensure that there will not be name collisions."
- (bbdb-editable)
- (let ((record (bbdb-empty-record)))
- (let (name)
- (bbdb-error-retry
- (setq name (bbdb-read-name first-and-last))
- (bbdb-check-name (car name) (cdr name)))
- (bbdb-record-set-firstname record (car name))
- (bbdb-record-set-lastname record (cdr name)))
-
- ;; organization
- (bbdb-record-set-organization record (bbdb-read-organization))
-
- ;; mail
- (bbdb-record-set-mail
- record (bbdb-split 'mail (bbdb-read-string "E-Mail Addresses: ")))
- ;; address
- (let (addresses label address)
- (while (not (string= ""
- (setq label
- (bbdb-read-string
- "Snail Mail Address Label [RET when done]: "
- nil
- bbdb-address-label-list))))
- (setq address (make-vector bbdb-address-length nil))
- (bbdb-record-edit-address address label t)
- (push address addresses))
- (bbdb-record-set-address record (nreverse addresses)))
-
- ;; phones
- (let (phones phone-list label)
- (while (not (string= ""
- (setq label
- (bbdb-read-string
- "Phone Label [RET when done]: " nil
- bbdb-phone-label-list))))
- (setq phone-list
- (bbdb-error-retry
- (bbdb-parse-phone
- (read-string "Phone: "
- (and (integerp bbdb-default-area-code)
- (format "(%03d) "
- bbdb-default-area-code))))))
- (push (apply 'vector label phone-list) phones))
- (bbdb-record-set-phone record (nreverse phones)))
-
- ;; `bbdb-default-xfield'
- (let ((xfield (bbdb-read-xfield bbdb-default-xfield)))
- (unless (string= "" xfield)
- (bbdb-record-set-xfields
- record (list (cons bbdb-default-xfield xfield)))))
-
- record))
-
-(defun bbdb-read-name (&optional first-and-last dfirst dlast)
- "Read name for a record from minibuffer.
-FIRST-AND-LAST controls the reading mode:
-If it is 'first-last read first and last name separately.
-If it is 'last-first read last and first name separately.
-If it is 'fullname read full name at once.
-If it is t read name parts separately, obeying `bbdb-read-name-format' if possible.
-Otherwise use `bbdb-read-name-format'.
-DFIRST and DLAST are default values for the first and last name.
-Return cons with first and last name."
- (unless (memq first-and-last '(first-last last-first fullname))
- ;; We do not yet know how to read the name
- (setq first-and-last
- (if (and first-and-last
- (not (memq bbdb-read-name-format '(first-last last-first))))
- 'first-last
- bbdb-read-name-format)))
- (let ((name (cond ((eq first-and-last 'last-first)
- (let (fn ln)
- (setq ln (bbdb-read-string "Last Name: " dlast)
- fn (bbdb-read-string "First Name: " dfirst))
- (cons fn ln)))
- ((eq first-and-last 'first-last)
- (cons (bbdb-read-string "First Name: " dfirst)
- (bbdb-read-string "Last Name: " dlast)))
- (t
- (bbdb-divide-name (bbdb-read-string
- "Name: " (bbdb-concat 'name-first-last
- dfirst dlast)))))))
- (if (string= (car name) "") (setcar name nil))
- (if (string= (cdr name) "") (setcdr name nil))
- name))
-
-;;;###autoload
-(defun bbdb-create (record)
- "Add a new RECORD to BBDB.
-When called interactively read all relevant info.
-Do not call this from a program; call `bbdb-create-internal' instead."
- (interactive (list (bbdb-read-record current-prefix-arg)))
- (bbdb-change-record record)
- (bbdb-display-records (list record)))
-
-(defsubst bbdb-split-maybe (separator string)
- "Split STRING into list of substrings bounded by matches for SEPARATORS.
-If STRING is a list, return STRING. Throw error if STRING is neither a string
-nor a list."
- (cond ((stringp string)
- (bbdb-split separator string))
- ((listp string) string)
- (t (error "Cannot convert %s to list" string))))
-
-;;;###autoload
-(defun bbdb-create-internal (&rest spec)
- "Add a new record to the database and return it.
-
-The following keywords are supported in SPEC:
-:name VAL String or a cons cell (FIRST . LAST), the name of the person.
- An error is thrown if VAL is already in use
- and `bbdb-allow-duplicates' is nil.
-:affix VAL List of strings.
-:aka VAL List of strings.
-:organization VAL List of strings.
-:mail VAL String with comma-separated mail address
- or a list of strings.
- An error is thrown if a mail address in MAIL is already
- in use and `bbdb-allow-duplicates' is nil.
-:phone VAL List of phone-number objects. A phone-number is a vector
- [\"label\" areacode prefix suffix extension-or-nil]
- or [\"label\" \"phone-number\"]
-:address VAL List of addresses. An address is a vector of the form
- \[\"label\" (\"line1\" \"line2\" ... ) \"City\"
- \"State\" \"Postcode\" \"Country\"].
-:xfields VAL Alist associating symbols with strings.
-:uuid VAL String, the uuid.
-:creation-date VAL String, the creation date.
-:check If present, throw an error if a field value is not
- syntactically correct."
- (bbdb-editable)
- (let ((record (bbdb-empty-record))
- (record-type (cdr bbdb-record-type))
- (check (prog1 (memq :check spec)
- (setq spec (delq :check spec))))
- keyw)
-
- ;; Check keys.
- (while (keywordp (setq keyw (car spec)))
- (setq spec (cdr spec))
- (pcase keyw
- (`:name
- (let ((name (pop spec)))
- (cond ((stringp name)
- (setq name (bbdb-divide-name name)))
- (check (bbdb-check-type name '(or (const nil)
- (cons string string))
- t)))
- (let ((firstname (car name))
- (lastname (cdr name)))
- (bbdb-check-name firstname lastname) ; check for duplicates
- (bbdb-record-set-firstname record firstname)
- (bbdb-record-set-lastname record lastname))))
-
- (`:affix
- (let ((affix (bbdb-split-maybe 'affix (pop spec))))
- (if check (bbdb-check-type affix (bbdb-record-affix record-type) t))
- (bbdb-record-set-affix record affix)))
-
- (`:organization
- (let ((organization (bbdb-split-maybe 'organization (pop spec))))
- (if check (bbdb-check-type
- organization (bbdb-record-organization record-type) t))
- (bbdb-record-set-organization record organization)))
-
- (`:aka
- (let ((aka (bbdb-split-maybe 'aka (pop spec))))
- (if check (bbdb-check-type aka (bbdb-record-aka record-type) t))
- (bbdb-record-set-aka record aka)))
-
- (`:mail
- (let ((mail (bbdb-split-maybe 'mail (pop spec))))
- (if check (bbdb-check-type mail (bbdb-record-mail record-type) t))
- (unless bbdb-allow-duplicates
- (dolist (elt mail)
- (if (bbdb-gethash elt '(mail))
- (error "%s is already in the database" elt))))
- (bbdb-record-set-mail record mail)))
-
- (`:phone
- (let ((phone (pop spec)))
- (if check (bbdb-check-type phone (bbdb-record-phone record-type) t))
- (bbdb-record-set-phone phone record)))
-
- (`:address
- (let ((address (pop spec)))
- (if check (bbdb-check-type address (bbdb-record-address record-type) t))
- (bbdb-record-set-address record address)))
-
- (`:xfields
- (let ((xfields (pop spec)))
- (if check (bbdb-check-type xfields (bbdb-record-xfields record-type) t))
- (bbdb-record-set-xfields record xfields)))
-
- (`:uuid
- (let ((uuid (pop spec)))
- (if check (bbdb-check-type uuid (bbdb-record-uuid record-type) t))
- (bbdb-record-set-uuid record uuid)))
-
- (`:creation-date
- (let ((creation-date (pop spec)))
- (if check (bbdb-check-type
- creation-date (bbdb-record-creation-date record-type) t))
- (bbdb-record-set-creation-date record creation-date)))
-
- (_ (error "Keyword `%s' undefined" keyw))))
-
- (bbdb-change-record record)))
-
-;;;###autoload
-(defun bbdb-insert-field (record field value)
- "For RECORD, add a new FIELD with value VALUE.
-Interactively, read FIELD and VALUE; RECORD is the current record.
-A non-nil prefix arg is passed on to `bbdb-read-field' as FLAG (see there)."
- (interactive
- (let* ((_ (bbdb-editable))
- (record (or (bbdb-current-record)
- (error "Point not on a record")))
- (list (append bbdb-xfield-label-list
- '(affix organization aka phone address mail)))
- (field "")
- (completion-ignore-case t)
- (present (mapcar 'car (bbdb-record-xfields record))))
- (if (bbdb-record-affix record) (push 'affix present))
- (if (bbdb-record-organization record) (push 'organization present))
- (if (bbdb-record-mail record) (push 'mail present))
- (if (bbdb-record-aka record) (push 'aka present))
- (dolist (field present)
- (setq list (remq field list)))
- (setq list (mapcar 'symbol-name list))
- (while (string= field "")
- (setq field (downcase (completing-read "Insert Field: " list))))
- (setq field (intern field))
- (if (memq field present)
- (error "Field \"%s\" already exists" field))
- (list record field (bbdb-read-field record field current-prefix-arg))))
-
- (cond (;; affix
- (eq field 'affix)
- (if (bbdb-record-affix record)
- (error "Affix field exists already"))
- (if (stringp value)
- (setq value (bbdb-split 'affix value)))
- (bbdb-record-set-field record 'affix value))
- ;; organization
- ((eq field 'organization)
- (if (bbdb-record-organization record)
- (error "Organization field exists already"))
- (if (stringp value)
- (setq value (bbdb-split 'organization value)))
- (bbdb-record-set-field record 'organization value))
- ;; phone
- ((eq field 'phone)
- (bbdb-record-set-field record 'phone
- (nconc (bbdb-record-phone record)
- (list value))))
- ;; address
- ((eq field 'address)
- (bbdb-record-set-field record 'address
- (nconc (bbdb-record-address record)
- (list value))))
- ;; mail
- ((eq field 'mail)
- (if (bbdb-record-mail record)
- (error "Mail field exists already"))
- (if (stringp value)
- (setq value (bbdb-split 'mail value)))
- (bbdb-record-set-field record 'mail value))
- ;; AKA
- ((eq field 'aka)
- (if (bbdb-record-aka record)
- (error "Alternate names field exists already"))
- (if (stringp value)
- (setq value (bbdb-split 'aka value)))
- (bbdb-record-set-field record 'aka value))
- ;; xfields
- ((assq field (bbdb-record-xfields record))
- (error "Xfield \"%s\" already exists" field))
- (t
- (bbdb-record-set-xfield record field value)))
- (unless (bbdb-change-record record)
- (message "Record unchanged")))
-
-(defun bbdb-read-field (record field &optional flag)
- "For RECORD read new FIELD interactively.
-- The phone number style is controlled via `bbdb-phone-style'.
- A prefix FLAG inverts the style,
-- If a mail address lacks a domain, append `bbdb-default-domain'
- if this variable non-nil. With prefix FLAG do not alter the mail address.
-- The value of an xfield is a string. With prefix FLAG the value may be
- any lisp object."
- (let* ((init-f (intern-soft (concat "bbdb-init-" (symbol-name field))))
- (init (if (and init-f (functionp init-f))
- (funcall init-f record))))
- (cond (;; affix
- (eq field 'affix) (bbdb-read-string "Affix: " init))
- ;; organization
- ((eq field 'organization) (bbdb-read-organization init))
- ;; mail
- ((eq field 'mail)
- (let ((mail (bbdb-read-string "Mail: " init)))
- (if (string-match "^mailto:" mail)
- (setq mail (substring mail (match-end 0))))
- (if (or (not bbdb-default-domain)
- flag (string-match "[@%!]" mail))
- mail
- (concat mail "@" bbdb-default-domain))))
- ;; AKA
- ((eq field 'aka) (bbdb-read-string "Alternate Names: " init))
- ;; Phone
- ((eq field 'phone)
- (let ((bbdb-phone-style
- (if flag (if (eq bbdb-phone-style 'nanp) nil 'nanp)
- bbdb-phone-style)))
- (apply 'vector
- (bbdb-read-string "Label: " nil bbdb-phone-label-list)
- (bbdb-error-retry
- (bbdb-parse-phone
- (read-string "Phone: "
- (and (integerp bbdb-default-area-code)
- (format "(%03d) "
- bbdb-default-area-code))))))))
- ;; Address
- ((eq field 'address)
- (let ((address (make-vector bbdb-address-length nil)))
- (bbdb-record-edit-address address nil t)
- address))
- ;; xfield
- ((or (memq field bbdb-xfield-label-list)
- ;; New xfield
- (y-or-n-p
- (format "\"%s\" is an unknown field name. Define it? " field))
- (error "Aborted"))
- (bbdb-read-xfield field init flag)))))
-
-;;;###autoload
-(defun bbdb-edit-field (record field &optional value flag)
- "Edit the contents of FIELD of RECORD.
-If point is in the middle of a multi-line field (e.g., address),
-then the entire field is edited, not just the current line.
-For editing phone numbers or addresses, VALUE must be the phone number
-or address that gets edited. An error is thrown when attempting to edit
-a phone number or address with VALUE being nil.
-
-- The value of an xfield is a string. With prefix FLAG the value may be
- any lisp object."
- (interactive
- (save-excursion
- (bbdb-editable)
- ;; when at the end of the line take care of it
- (if (and (eolp) (not (bobp)) (not (bbdb-current-field)))
- (backward-char 1))
- (let* ((field-l (bbdb-current-field))
- (field (car field-l))
- (value (nth 1 field-l)))
- (unless field (error "Point not in a field"))
- (list (bbdb-current-record)
- (if (memq field '(name affix organization aka mail phone address
- uuid creation-date timestamp))
- field ; not an xfield
- (elt value 0)) ; xfield
- value current-prefix-arg))))
- (let (edit-str)
- (cond ((memq field '(firstname lastname xfields))
- ;; FIXME: We could also edit first and last names.
- (error "Field `%s' not editable this way." field))
- ((eq field 'name)
- (bbdb-error-retry
- (bbdb-record-set-field
- record 'name
- (bbdb-read-name
- (if flag
- ;; Here we try to obey the name-format xfield for
- ;; editing the name field. Is this useful? Or is this
- ;; irritating overkill and we better obey consistently
- ;; `bbdb-read-name-format'?
- (or (bbdb-record-xfield-intern record 'name-format)
- flag))
- (bbdb-record-firstname record)
- (bbdb-record-lastname record)))))
-
- ((eq field 'phone)
- (unless value (error "No phone specified"))
- (bbdb-record-edit-phone (bbdb-record-phone record) value))
- ((eq field 'address)
- (unless value (error "No address specified"))
- (bbdb-record-edit-address value nil flag))
- ((eq field 'organization)
- (bbdb-record-set-field
- record field
- (bbdb-read-organization
- (bbdb-concat field (bbdb-record-organization record)))))
- ((setq edit-str (assq field '((affix . "Affix")
- (mail . "Mail") (aka . "AKA"))))
- (bbdb-record-set-field
- record field
- (bbdb-split field (bbdb-read-string
- (format "%s: " (cdr edit-str))
- (bbdb-concat field
- (bbdb-record-field record field))))))
- ((eq field 'uuid)
- (bbdb-record-set-field
- record 'uuid (bbdb-read-string "uuid (edit at your own risk): " (bbdb-record-uuid record))))
- ((eq field 'creation-date)
- (bbdb-record-set-creation-date
- record (bbdb-read-string "creation-date: " (bbdb-record-creation-date record))))
- ;; The timestamp is set automatically whenever we save a modified record.
- ;; So any editing gets overwritten.
- ((eq field 'timestamp)) ; do nothing
- (t ; xfield
- (bbdb-record-set-xfield
- record field
- (bbdb-read-xfield field (bbdb-record-xfield record field) flag))))
- (cond ((eq field 'timestamp)
- (message "timestamp not editable"))
- ((bbdb-change-record record))
- (t (message "Record unchanged")))))
-
-(defun bbdb-edit-foo (record field &optional nvalue)
- "For RECORD edit some FIELD (mostly interactively).
-FIELD may take the same values as the elements of the variable `bbdb-edit-foo'.
-If FIELD is 'phone or 'address, NVALUE should be an integer in order to edit
-the NVALUEth phone or address field; otherwise insert a new phone or address
-field.
-
-Interactively, if called without a prefix, the value of FIELD is the car
-of the variable `bbdb-edit-foo'. When called with a prefix, the value
-of FIELD is the cdr of this variable. Then use minibuffer completion
-to select the field."
- (interactive
- (let* ((_ (bbdb-editable))
- (record (bbdb-current-record))
- (tmp (if current-prefix-arg (cdr bbdb-edit-foo) (car bbdb-edit-foo)))
- (field (if (memq tmp '(current-fields all-fields))
- ;; Do not require match so that we can define new xfields.
- (intern (completing-read
- "Edit field: " (mapcar 'list (if (eq tmp 'all-fields)
- (append '(name affix organization aka mail phone address uuid creation-date)
- bbdb-xfield-label-list)
- (append (if (bbdb-record-affix record) '(affix))
- (if (bbdb-record-organization record) '(organization))
- (if (bbdb-record-aka record) '(aka))
- (if (bbdb-record-mail record) '(mail))
- (if (bbdb-record-phone record) '(phone))
- (if (bbdb-record-address record) '(address))
- (mapcar 'car (bbdb-record-xfields record))
- '(name uuid creation-date))))))
- tmp))
- ;; Multiple phone and address fields may use the same label.
- ;; So we cannot use these labels to uniquely identify
- ;; a phone or address field. So instead we number these fields
- ;; consecutively. But we do use the labels to annotate the numbers
- ;; (available starting from GNU Emacs 24.1).
- (nvalue (cond ((eq field 'phone)
- (let* ((phones (bbdb-record-phone record))
- (collection (cons (cons "new" "new phone #")
- (mapcar (lambda (n)
- (cons (format "%d" n) (bbdb-phone-label (nth n phones))))
- (number-sequence 0 (1- (length phones))))))
- (completion-extra-properties
- `(:annotation-function
- (lambda (s) (format " (%s)" (cdr (assoc s ',collection)))))))
- (if (< 0 (length phones))
- (completing-read "Phone field: " collection nil t)
- "new")))
- ((eq field 'address)
- (let* ((addresses (bbdb-record-address record))
- (collection (cons (cons "new" "new address")
- (mapcar (lambda (n)
- (cons (format "%d" n) (bbdb-address-label (nth n addresses))))
- (number-sequence 0 (1- (length addresses))))))
- (completion-extra-properties
- `(:annotation-function
- (lambda (s) (format " (%s)" (cdr (assoc s ',collection)))))))
- (if (< 0 (length addresses))
- (completing-read "Address field: " collection nil t)
- "new"))))))
- (list record field (and (stringp nvalue)
- (if (string= "new" nvalue)
- 'new
- (string-to-number nvalue))))))
-
- (if (memq field '(firstname lastname name-lf aka-all mail-aka mail-canon))
- (error "Field `%s' illegal" field))
- (let ((value (if (numberp nvalue)
- (nth nvalue (cond ((eq field 'phone) (bbdb-record-phone record))
- ((eq field 'address) (bbdb-record-address record))
- (t (error "%s: nvalue %s meaningless" field nvalue)))))))
- (if (and (numberp nvalue) (not value))
- (error "%s: nvalue %s out of range" field nvalue))
- (if (or (memq field '(name uuid creation-date))
- (and (eq field 'affix) (bbdb-record-affix record))
- (and (eq field 'organization) (bbdb-record-organization record))
- (and (eq field 'mail) (bbdb-record-mail record))
- (and (eq field 'aka) (bbdb-record-aka record))
- (assq field (bbdb-record-xfields record))
- value)
- (bbdb-edit-field record field value)
- (bbdb-insert-field record field
- (bbdb-read-field record field)))))
-
-(defun bbdb-read-xfield (field &optional init sexp)
- "Read xfield FIELD with optional INIT.
-This calls bbdb-read-xfield-FIELD if it exists."
- (let ((read-fun (intern-soft (format "bbdb-read-xfield-%s" field))))
- (cond ((fboundp read-fun)
- (funcall read-fun init))
- ((and (not sexp) (string-or-null-p init))
- (bbdb-read-string (format "%s: " field) init))
- (t (read-minibuffer (format "%s (sexp): " field)
- (prin1-to-string init))))))
-
-(defun bbdb-read-organization (&optional init)
- "Read organization."
- (if (string< "24.3" (substring emacs-version 0 4))
- (let ((crm-separator
- (concat "[ \t\n]*"
- (cadr (assq 'organization bbdb-separator-alist))
- "[ \t\n]*"))
- (crm-local-completion-map bbdb-crm-local-completion-map))
- (completing-read-multiple "Organizations: " bbdb-organization-list
- nil nil init))
- (bbdb-split 'organization (bbdb-read-string "Organizations: " init))))
-
-(defun bbdb-record-edit-address (address &optional label ignore-country)
- "Edit ADDRESS.
-If LABEL is nil, edit the label sub-field of the address as well.
-If the country field of ADDRESS is nonempty and IGNORE-COUNTRY is nil,
-use the rule from `bbdb-address-format-list' matching this country.
-Otherwise, use the default rule according to `bbdb-address-format-list'."
- (unless label
- (setq label (bbdb-read-string "Label: "
- (bbdb-address-label address)
- bbdb-address-label-list)))
- (let ((country (or (bbdb-address-country address) ""))
- new-addr edit)
- (unless (or ignore-country (string= "" country))
- (let ((list bbdb-address-format-list)
- identifier elt)
- (while (and (not edit) (setq elt (pop list)))
- (setq identifier (car elt))
- (if (or (and (listp identifier)
- (member-ignore-case country identifier))
- (and (functionp identifier)
- (funcall identifier address)))
- (setq edit (nth 1 elt))))))
- (unless edit
- (setq edit (nth 1 (assq t bbdb-address-format-list))))
- (unless edit (error "No address editing function defined"))
- (if (functionp edit)
- (setq new-addr (funcall edit address))
- (setq new-addr (make-vector 5 ""))
- (dolist (elt (string-to-list edit))
- (cond ((eq elt ?s)
- (aset new-addr 0 (bbdb-edit-address-street
- (bbdb-address-streets address))))
- ((eq elt ?c)
- (aset new-addr 1 (bbdb-read-string
- "City: " (bbdb-address-city address)
- bbdb-city-list)))
- ((eq elt ?S)
- (aset new-addr 2 (bbdb-read-string
- "State: " (bbdb-address-state address)
- bbdb-state-list)))
- ((eq elt ?p)
- (aset new-addr 3
- (bbdb-error-retry
- (bbdb-parse-postcode
- (bbdb-read-string
- "Postcode: " (bbdb-address-postcode address)
- bbdb-postcode-list)))))
- ((eq elt ?C)
- (aset new-addr 4
- (bbdb-read-string
- "Country: " (or (bbdb-address-country address)
- bbdb-default-country)
- bbdb-country-list))))))
- (bbdb-address-set-label address label)
- (bbdb-address-set-streets address (elt new-addr 0))
- (bbdb-address-set-city address (elt new-addr 1))
- (bbdb-address-set-state address (elt new-addr 2))
- (bbdb-address-set-postcode address (elt new-addr 3))
- (if (string= "" (bbdb-concat "" (elt new-addr 0) (elt new-addr 1)
- (elt new-addr 2) (elt new-addr 3)
- (elt new-addr 4)))
- ;; User did not enter anything. this causes a display bug.
- ;; The following is a temporary fix. Ideally, we would simply discard
- ;; the entire address, but that requires bigger hacking.
- (bbdb-address-set-country address "Emacs")
- (bbdb-address-set-country address (elt new-addr 4)))))
-
-(defun bbdb-edit-address-street (streets)
- "Edit list STREETS."
- (let ((n 0) street list)
- (while (not (string= "" (setq street
- (bbdb-read-string
- (format "Street, line %d: " (1+ n))
- (nth n streets) bbdb-street-list))))
- (push street list)
- (setq n (1+ n)))
- (reverse list)))
-
-;; This function can provide some guidance for writing
-;; your own address editing function
-(defun bbdb-edit-address-default (address)
- "Function to use for address editing.
-The sub-fields and the prompts used are:
-Street, line n: (nth n street)
-City: city
-State: state
-Postcode: postcode
-Country: country"
- (list (bbdb-edit-address-street (bbdb-address-streets address))
- (bbdb-read-string "City: " (bbdb-address-city address) bbdb-city-list)
- (bbdb-read-string "State: " (bbdb-address-state address)
- bbdb-state-list)
- (bbdb-error-retry
- (bbdb-parse-postcode
- (bbdb-read-string "Postcode: " (bbdb-address-postcode address)
- bbdb-postcode-list)))
- (bbdb-read-string "Country: " (or (bbdb-address-country address)
- bbdb-default-country)
- bbdb-country-list)))
-
-(defun bbdb-record-edit-phone (phones phone)
- "For list PHONES edit PHONE number."
- ;; Phone numbers are special. They are vectors with either
- ;; two or four elements. We do not know whether after editing PHONE
- ;; we still have a number requiring the same format as PHONE.
- ;; So we take all numbers PHONES of the record so that we can
- ;; replace the element PHONE in PHONES.
- (setcar (memq phone phones)
- (apply 'vector
- (bbdb-read-string "Label: "
- (bbdb-phone-label phone)
- bbdb-phone-label-list)
- (bbdb-error-retry
- (bbdb-parse-phone
- (read-string "Phone: " (bbdb-phone-string phone)))))))
-
-;; (bbdb-list-transpose '(a b c d) 1 3)
-(defun bbdb-list-transpose (list i j)
- "For LIST transpose elements I and J destructively.
-I and J start with zero. Return the modified LIST."
- (if (eq i j)
- list ; ignore that i, j could be invalid
- (let (a b c)
- ;; Travel down LIST only once
- (if (> i j) (setq a i i j j a)); swap
- (setq a (nthcdr i list)
- b (nthcdr (- j i) a)
- c (car b))
- (unless b (error "Args %i, %i beyond length of list." i j))
- (setcar b (car a))
- (setcar a c)
- list)))
-
-(defun bbdb-ident-point (&optional point)
- "Return identifier (RECNUM FIELD NUM) for position POINT.
-If POINT is nil use current value of point.
-RECNUM is the number of the record (starting from zero).
-FIELD is the field type.
-If FIELD's value is a list, NUM is the position of the subfield within FIELD.
-If any of these terms is not defined at POINT, the respective value is nil."
- (unless point (setq point (point)))
- (let ((recnum (get-text-property point 'bbdb-record-number))
- (field (get-text-property point 'bbdb-field)))
- (cond ((not field)
- (list recnum nil nil))
- ((eq (car field) 'name)
- (list recnum 'name nil))
- ((not (nth 1 field))
- (list recnum (car field) nil))
- (t
- (let* ((record (car (nth recnum bbdb-records)))
- (fields (bbdb-record-field record (car field)))
- (val (nth 1 field))
- (num 0) done elt)
- ;; For xfields we only check the label because the rest of VAL
- ;; can be anything. (xfields are unique within a record.)
- (if (eq 'xfields (car field))
- (setq val (car val)
- fields (mapcar 'car fields)))
- (while (and (not done) (setq elt (pop fields)))
- (if (eq val elt)
- (setq done t)
- (setq num (1+ num))))
- (unless done (error "Field %s not found" val))
- (list recnum (car field) num))))))
-
-;;;###autoload
-(defun bbdb-transpose-fields (arg)
- "Transpose previous and current field of a BBDB record.
-With numeric prefix ARG, take previous field and move it past ARG fields.
-With region active or ARG 0, transpose field point is in and field mark is in.
-
-Both fields must be in the same record, and must be of the same basic type
-\(that is, you can use this command to change the order in which phone numbers
-or email addresses are listed, but you cannot use it to make an address appear
-before a phone number; the order of field types is fixed).
-
-If the current field is the name field, transpose first and last name,
-irrespective of the value of ARG."
- ;; This functionality is inspired by `transpose-lines'.
- (interactive "p")
- (bbdb-editable)
- (let* ((ident (bbdb-ident-point))
- (record (and (car ident) (car (nth (car ident) bbdb-records))))
- num1 num2)
- (cond ((not (car ident))
- (error "Point not in BBDB record"))
- ((not (nth 1 ident))
- (error "Point not in BBDB field"))
- ((eq 'name (nth 1 ident))
- ;; Transpose firstname and lastname
- (bbdb-record-set-name record (bbdb-record-lastname record)
- (bbdb-record-firstname record)))
- ((not (integerp arg))
- (error "Arg `%s' not an integer" arg))
- ((not (nth 2 ident))
- (error "Point not in a transposable field"))
- (t
- (if (or (use-region-p) (zerop arg))
- (let ((ident2 (bbdb-ident-point
- (or (mark) (error "No mark set in this buffer")))))
- (unless (and (eq (car ident) (car ident2))
- (eq (cadr ident) (cadr ident2))
- (integerp (nth 2 ident2)))
- (error "Mark (or point) not on transposable field"))
- (setq num1 (nth 2 ident)
- num2 (nth 2 ident2)))
- (setq num1 (1- (nth 2 ident))
- num2 (+ num1 arg))
- (if (or (< (min num1 num2) 0)
- (>= (max num1 num2) (length (bbdb-record-field
- record (nth 1 ident)))))
- (error "Cannot transpose fields of different types")))
- (bbdb-record-set-field
- record (nth 1 ident)
- (bbdb-list-transpose (bbdb-record-field record (nth 1 ident))
- num1 num2))))
- (bbdb-change-record record)))
-
-;;;###autoload
-(defun bbdb-delete-field-or-record (records field &optional noprompt)
- "For RECORDS delete FIELD.
-If FIELD is the `name' field, delete RECORDS from datanbase.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records',
-and FIELD is the field point is on.
-If prefix NOPROMPT is non-nil, do not confirm deletion."
- ;; The value of FIELD is whatever `bbdb-current-field' returns.
- ;; This way we can identify more accurately what really needs
- ;; to be done.
- (interactive
- (list (bbdb-do-records) (bbdb-current-field) current-prefix-arg))
- (bbdb-editable)
- (unless field (error "Not a field"))
- (setq records (bbdb-record-list records))
- (let* ((type (car field))
- (type-x (if (eq type 'xfields)
- (car (nth 1 field))
- type)))
- (if (eq type 'name)
- (bbdb-delete-records records noprompt)
- (if (memq type '(firstname lastname))
- (error "Cannot delete field `%s'" type))
- (dolist (record records)
- (when (or noprompt
- (y-or-n-p (format "delete this `%s' field (of %s)? "
- type-x (bbdb-record-name record))))
- (cond ((memq type '(phone address))
- (bbdb-record-set-field
- record type
- ;; We use `delete' which deletes all phone and address
- ;; fields equal to the current one. This works for
- ;; multiple records.
- (delete (nth 1 field)
- (bbdb-record-field record type))))
- ((memq type '(affix organization mail aka))
- (bbdb-record-set-field record type nil))
- ((eq type 'xfields)
- (bbdb-record-set-xfield record type-x nil))
- (t (error "Unknown field %s" type)))
- (bbdb-change-record record))))))
-
-;;;###autoload
-(defun bbdb-delete-records (records &optional noprompt)
- "Delete RECORDS.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
-If prefix NOPROMPT is non-nil, do not confirm deletion."
- (interactive (list (bbdb-do-records) current-prefix-arg))
- (bbdb-editable)
- (let ((all-records (bbdb-with-db-buffer bbdb-records)))
- (dolist (record (bbdb-record-list records))
- (cond ((not (memq record all-records))
- ;; Possibly we changed RECORD before deleting it.
- ;; Otherwise, do nothing if RECORD is unknown to BBDB.
- (setq bbdb-changed-records (delq record bbdb-changed-records)))
- ((or noprompt
- (y-or-n-p (format "Delete the BBDB record of %s? "
- (or (bbdb-record-name record)
- (car (bbdb-record-mail record))))))
- (bbdb-delete-record-internal record t)
- (setq bbdb-changed-records (delq record bbdb-changed-records)))))))
-
-;;;###autoload
-(defun bbdb-display-all-records (&optional layout)
- "Show all records.
-If invoked in a *BBDB* buffer point stays on the currently visible record.
-Inverse of `bbdb-display-current-record'."
- (interactive (list (bbdb-layout-prefix)))
- (let ((current (ignore-errors (bbdb-current-record))))
- (bbdb-display-records (bbdb-records) layout)
- (when (setq current (assq current bbdb-records))
- (redisplay) ; Strange display bug??
- (goto-char (nth 2 current)))))
- ;; (set-window-point (selected-window) (nth 2 current)))))
-
-;;;###autoload
-(defun bbdb-display-current-record (&optional layout)
- "Narrow to current record. Inverse of `bbdb-display-all-records'."
- (interactive (list (bbdb-layout-prefix)))
- (bbdb-display-records (list (bbdb-current-record)) layout))
-
-(defun bbdb-change-records-layout (records layout)
- (dolist (record records)
- (unless (eq layout (nth 1 record))
- (setcar (cdr record) layout)
- (bbdb-redisplay-record (car record)))))
-
-;;;###autoload
-(defun bbdb-toggle-records-layout (records &optional arg)
- "Toggle layout of RECORDS (elided or expanded).
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
-With prefix ARG 0, RECORDS are displayed elided.
-With any other non-nil ARG, RECORDS are displayed expanded."
- (interactive (list (bbdb-do-records t) current-prefix-arg))
- (let* ((record (bbdb-current-record))
- (current-layout (nth 1 (assq record bbdb-records)))
- (layout-alist
- ;; Try to consider only those layouts that have the `toggle'
- ;; option set
- (or (delq nil (mapcar (lambda (l)
- (if (and (assq 'toggle l)
- (cdr (assq 'toggle l)))
- l))
- bbdb-layout-alist))
- bbdb-layout-alist))
- (layout
- (cond ((eq arg 0)
- 'one-line)
- ((null current-layout)
- 'multi-line)
- ;; layout is not the last element of layout-alist
- ;; and we switch to the following element of layout-alist
- ((caar (cdr (memq (assq current-layout layout-alist)
- layout-alist))))
- (t ; layout is the last element of layout-alist
- ;; and we switch to the first element of layout-alist
- (caar layout-alist)))))
- (message "Using %S layout" layout)
- (bbdb-change-records-layout (bbdb-record-list records t) layout)))
-
-;;;###autoload
-(defun bbdb-display-records-completely (records)
- "Display RECORDS using layout `full-multi-line' (i.e., display all fields).
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
- (interactive (list (bbdb-do-records t)))
- (let* ((record (bbdb-current-record))
- (current-layout (nth 1 (assq record bbdb-records)))
- (layout (if (not (eq current-layout 'full-multi-line))
- 'full-multi-line
- 'multi-line)))
- (bbdb-change-records-layout (bbdb-record-list records t) layout)))
-
-;;;###autoload
-(defun bbdb-display-records-with-layout (records layout)
- "Display RECORDS using LAYOUT.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
- (interactive
- (list (bbdb-do-records t)
- (intern (completing-read "Layout: "
- (mapcar (lambda (i)
- (list (symbol-name (car i))))
- bbdb-layout-alist)))))
- (bbdb-change-records-layout (bbdb-record-list records t) layout))
-
-;;;###autoload
-(defun bbdb-omit-record (n)
- "Remove current record from the display without deleting it from BBDB.
-With prefix N, omit the next N records. If negative, omit backwards."
- (interactive "p")
- (let ((num (get-text-property (if (and (not (bobp)) (eobp))
- (1- (point)) (point))
- 'bbdb-record-number)))
- (if (> n 0)
- (setq n (min n (- (length bbdb-records) num)))
- (setq n (min (- n) num))
- (bbdb-prev-record n))
- (dotimes (_i n)
- (bbdb-redisplay-record (bbdb-current-record) nil t))))
-
-;;; Fixing up bogus records
-
-;;;###autoload
-(defun bbdb-merge-records (record1 record2)
- "Merge RECORD1 into RECORD2, then delete RECORD1 and return RECORD2.
-If both records have name fields ask which one to use.
-Concatenate other fields, ignoring duplicates.
-RECORD1 need not be known to BBDB, its hash and cache are ignored.
-Update hash and cache for RECORD2.
-
-Interactively, RECORD1 is the current record; prompt for RECORD2.
-With prefix, RECORD2 defaults to the first record with the same name."
- (interactive
- (let* ((_ (bbdb-editable))
- (record1 (bbdb-current-record))
- (name (bbdb-record-name record1))
- (record2 (and current-prefix-arg
- ;; take the first record with the same name
- (car (delq record1
- (bbdb-search (bbdb-records) :all-names name))))))
- (when record2
- (message "Merge current record with duplicate record `%s'" name)
- (sit-for 1))
- (list record1
- (or record2
- (bbdb-completing-read-record
- (format "merge record \"%s\" into: "
- (or (bbdb-record-name record1)
- (car (bbdb-record-mail record1))
- "???"))
- (list record1))))))
-
- (bbdb-editable)
- (cond ((eq record1 record2) (error "Records are equal"))
- ((null record2) (error "No record to merge with")))
-
- ;; Merge names
- (let* ((new-name (bbdb-record-name record2))
- (old-name (bbdb-record-name record1))
- (old-aka (bbdb-record-aka record1))
- extra-name
- (name
- (cond ((or (string= "" old-name)
- (bbdb-string= old-name new-name))
- (cons (bbdb-record-firstname record2)
- (bbdb-record-lastname record2)))
- ((string= "" new-name)
- (cons (bbdb-record-firstname record1)
- (bbdb-record-lastname record1)))
- (t (prog1
- (if (y-or-n-p
- (format "Use name \"%s\" instead of \"%s\"? "
- old-name new-name))
- (progn
- (setq extra-name new-name)
- (cons (bbdb-record-firstname record1)
- (bbdb-record-lastname record1)))
- (setq extra-name old-name)
- (cons (bbdb-record-firstname record2)
- (bbdb-record-lastname record2)))
- (unless (bbdb-eval-spec
- (bbdb-add-job bbdb-add-aka record2 extra-name)
- (format "Keep \"%s\" as an alternate name? "
- extra-name))
- (setq extra-name nil)))))))
-
- (bbdb-record-set-name record2 (car name) (cdr name))
-
- (if extra-name (push extra-name old-aka))
- ;; It is better to delete RECORD1 at the end.
- ;; So we must temporarily allow duplicates in RECORD2.
- (let ((bbdb-allow-duplicates t))
- (bbdb-record-set-field record2 'aka old-aka t)))
-
- ;; Merge other stuff
- (bbdb-record-set-field record2 'affix
- (bbdb-record-affix record1) t)
- (bbdb-record-set-field record2 'organization
- (bbdb-record-organization record1) t)
- (bbdb-record-set-field record2 'phone
- (bbdb-record-phone record1) t)
- (bbdb-record-set-field record2 'address
- (bbdb-record-address record1) t)
- (let ((bbdb-allow-duplicates t))
- (bbdb-record-set-field record2 'mail
- (bbdb-record-mail record1) t))
- (bbdb-record-set-field record2 'xfields
- (bbdb-record-xfields record1) t)
-
- ;; `bbdb-delete-records' does nothing if RECORD1 is not known to BBDB.
- (bbdb-delete-records (list record1) 'noprompt)
- (bbdb-change-record record2)
- record2)
-
-;; The following sorting functions are also intended for use
-;; in `bbdb-change-hook'. Then they will be called with one arg, the record.
-
-;;;###autoload
-(defun bbdb-sort-addresses (records &optional update)
- "Sort the addresses in RECORDS according to the label.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
-If UPDATE is non-nil (as in interactive calls) update the database.
-Otherwise, this is the caller's responsiblity (for example, when used
-in `bbdb-change-hook')."
- (interactive (list (bbdb-do-records) t))
- (bbdb-editable)
- (dolist (record (bbdb-record-list records))
- (bbdb-record-set-address
- record (sort (bbdb-record-address record)
- (lambda (xx yy) (string< (aref xx 0) (aref yy 0)))))
- (if update
- (bbdb-change-record record))))
-
-;;;###autoload
-(defun bbdb-sort-phones (records &optional update)
- "Sort the phones in RECORDS according to the label.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
-If UPDATE is non-nil (as in interactive calls) update the database.
-Otherwise, this is the caller's responsiblity (for example, when used
-in `bbdb-change-hook')."
- (interactive (list (bbdb-do-records) t))
- (bbdb-editable)
- (dolist (record (bbdb-record-list records))
- (bbdb-record-set-phone
- record (sort (bbdb-record-phone record)
- (lambda (xx yy) (string< (aref xx 0) (aref yy 0)))))
- (if update
- (bbdb-change-record record))))
-
-;;;###autoload
-(defun bbdb-sort-xfields (records &optional update)
- "Sort the xfields in RECORDS according to `bbdb-xfields-sort-order'.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
-If UPDATE is non-nil (as in interactive calls) update the database.
-Otherwise, this is the caller's responsiblity (for example, when used
-in `bbdb-change-hook')."
- (interactive (list (bbdb-do-records) t))
- (bbdb-editable)
- (dolist (record (bbdb-record-list records))
- (bbdb-record-set-xfields
- record (sort (bbdb-record-xfields record)
- (lambda (a b)
- (< (or (cdr (assq (car a) bbdb-xfields-sort-order)) 100)
- (or (cdr (assq (car b) bbdb-xfields-sort-order)) 100)))))
- (if update
- (bbdb-change-record record))))
-(define-obsolete-function-alias 'bbdb-sort-notes 'bbdb-sort-xfields "3.0")
-
-;;; Send-Mail interface
-
-;;;###autoload
-(defun bbdb-dwim-mail (record &optional mail)
- ;; Do What I Mean!
- "Return a string to use as the mail address of RECORD.
-The name in the mail address is formatted obeying `bbdb-mail-name-format'
-and `bbdb-mail-name'. However, if both the first name and last name
-are constituents of the address as in John.Doe@Some.Host,
-and `bbdb-mail-avoid-redundancy' is non-nil, then the address is used as is
-and `bbdb-mail-name-format' and `bbdb-mail-name' are ignored.
-If `bbdb-mail-avoid-redundancy' is 'mail-only the name is never included.
-MAIL may be a mail address to be used for RECORD.
-If MAIL is an integer, use the MAILth mail address of RECORD.
-If MAIL is nil use the first mail address of RECORD."
- (unless mail
- (let ((mails (bbdb-record-mail record)))
- (setq mail (or (and (integerp mail) (nth mail mails))
- (car mails)))))
- (unless mail (error "Record has no mail addresses"))
- (let (name fn ln)
- (cond ((let ((address (bbdb-decompose-bbdb-address mail)))
- ;; We need to know whether we should quote the name part of MAIL
- ;; because of special characters.
- (if (car address)
- (setq mail (cadr address)
- name (car address)
- ln name))))
- ((functionp bbdb-mail-name)
- (setq name (funcall bbdb-mail-name record))
- (if (consp name)
- (setq fn (car name) ln (cdr name)
- name (if (eq bbdb-mail-name-format 'first-last)
- (bbdb-concat 'name-first-last fn ln)
- (bbdb-concat 'name-last-first ln fn)))
- (let ((pair (bbdb-divide-name name)))
- (setq fn (car pair) ln (cdr pair)))))
- ((setq name (bbdb-record-xfield record bbdb-mail-name))
- (let ((pair (bbdb-divide-name name)))
- (setq fn (car pair) ln (cdr pair))))
- (t
- (setq name (if (eq bbdb-mail-name-format 'first-last)
- (bbdb-record-name record)
- (bbdb-record-name-lf record))
- fn (bbdb-record-firstname record)
- ln (bbdb-record-lastname record))))
- (if (or (not name) (equal "" name)
- (eq 'mail-only bbdb-mail-avoid-redundancy)
- (and bbdb-mail-avoid-redundancy
- (cond ((and fn ln)
- (let ((fnq (regexp-quote fn))
- (lnq (regexp-quote ln)))
- (or (string-match (concat "\\`[^!@%]*\\b" fnq
- "\\b[^!%@]+\\b" lnq "\\b")
- mail)
- (string-match (concat "\\`[^!@%]*\\b" lnq
- "\\b[^!%@]+\\b" fnq "\\b")
- mail))))
- ((or fn ln)
- (string-match (concat "\\`[^!@%]*\\b"
- (regexp-quote (or fn ln)) "\\b")
- mail)))))
- mail
- ;; If the name contains backslashes or double-quotes, backslash them.
- (setq name (replace-regexp-in-string "[\\\"]" "\\\\\\&" name))
- ;; If the name contains control chars or RFC822 specials, it needs
- ;; to be enclosed in quotes. This quotes a few extra characters as
- ;; well (!,%, and $) just for common sense.
- ;; `define-mail-alias' uses regexp "[^- !#$%&'*+/0-9=?A-Za-z^_`{|}~]".
- (format (if (string-match "[][[:cntrl:]\177()<>@,;:.!$%[:nonascii:]]" name)
- "\"%s\" <%s>"
- "%s <%s>")
- name mail))))
-
-(defun bbdb-compose-mail (&rest args)
- "Start composing a mail message to send.
-Use `bbdb-mail-user-agent' or (if nil) use `mail-user-agent'.
-ARGS are passed to `compose-mail'."
- (let ((mail-user-agent (or bbdb-mail-user-agent mail-user-agent)))
- (apply 'compose-mail args)))
-
-;;;###autoload
-(defun bbdb-mail (records &optional subject n verbose)
- "Compose a mail message to RECORDS (optional: using SUBJECT).
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
-By default, the first mail addresses of RECORDS are used.
-If prefix N is a number, use Nth mail address of RECORDS (starting from 1).
-If prefix N is C-u (t noninteractively) use all mail addresses of RECORDS.
-If VERBOSE is non-nil (as in interactive calls) be verbose."
- (interactive (list (bbdb-do-records) nil
- (or (consp current-prefix-arg)
- current-prefix-arg)
- t))
- (setq records (bbdb-record-list records))
- (if (not records)
- (if verbose (message "No records"))
- (let ((to (bbdb-mail-address records n nil verbose)))
- (unless (string= "" to)
- (bbdb-compose-mail to subject)))))
-
-(defun bbdb-mail-address (records &optional n kill-ring-save verbose)
- "Return mail addresses of RECORDS as a string.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
-By default, the first mail addresses of RECORDS are used.
-If prefix N is a number, use Nth mail address of RECORDS (starting from 1).
-If prefix N is C-u (t noninteractively) use all mail addresses of RECORDS.
-If KILL-RING-SAVE is non-nil (as in interactive calls), copy mail addresses
-to kill ring. If VERBOSE is non-nil (as in interactive calls) be verbose."
- (interactive (list (bbdb-do-records)
- (or (consp current-prefix-arg)
- current-prefix-arg)
- t t))
- (setq records (bbdb-record-list records))
- (if (not records)
- (progn (if verbose (message "No records")) "")
- (let ((good "") bad)
- (dolist (record records)
- (let ((mails (bbdb-record-mail record)))
- (cond ((not mails)
- (push record bad))
- ((eq n t)
- (setq good (bbdb-concat ",\n\t"
- good
- (mapcar (lambda (mail)
- (bbdb-dwim-mail record mail))
- mails))))
- (t
- (setq good (bbdb-concat ",\n\t" good
- (bbdb-dwim-mail record (or (and (numberp n)
- (nth (1- n) mails))
- (car mails)))))))))
- (when (and bad verbose)
- (message "No mail addresses for %s."
- (mapconcat 'bbdb-record-name (nreverse bad) ", "))
- (unless (string= "" good) (sit-for 2)))
- (when (and kill-ring-save (not (string= good "")))
- (kill-new good)
- (if verbose (message "%s" good)))
- good)))
-
-;; Is there better way to yank selected mail addresses from the BBDB
-;; buffer into a message buffer? We need some kind of a link between
-;; the BBDB buffer and the message buffer, where the mail addresses
-;; are supposed to go. Then we could browse the BBDB buffer and copy
-;; selected mail addresses from the BBDB buffer into a message buffer.
-
-(defun bbdb-mail-yank ()
- "CC the people displayed in the *BBDB* buffer on this mail message.
-The primary mail of each of the records currently listed in the
-*BBDB* buffer will be appended to the CC: field of the current buffer."
- (interactive)
- (let ((addresses (with-current-buffer bbdb-buffer-name
- (delq nil
- (mapcar (lambda (x)
- (if (bbdb-record-mail (car x))
- (bbdb-dwim-mail (car x))))
- bbdb-records))))
- (case-fold-search t))
- (goto-char (point-min))
- (if (re-search-forward "^CC:[ \t]*" nil t)
- ;; We have a CC field. Move to the end of it, inserting a comma
- ;; if there are already addresses present.
- (unless (eolp)
- (end-of-line)
- (while (looking-at "\n[ \t]")
- (forward-char) (end-of-line))
- (insert ",\n")
- (indent-relative))
- ;; Otherwise, if there is an empty To: field, move to the end of it.
- (unless (and (re-search-forward "^To:[ \t]*" nil t)
- (eolp))
- ;; Otherwise, insert an empty CC: field.
- (end-of-line)
- (while (looking-at "\n[ \t]")
- (forward-char) (end-of-line))
- (insert "\nCC:")
- (indent-relative)))
- ;; Now insert each of the addresses on its own line.
- (while addresses
- (insert (car addresses))
- (when (cdr addresses) (insert ",\n") (indent-relative))
- (setq addresses (cdr addresses)))))
-(define-obsolete-function-alias 'bbdb-yank-addresses 'bbdb-mail-yank "3.0")
-
-;;; completion
-
-;;;###autoload
-(defun bbdb-completion-predicate (key records)
- "For use as the third argument to `completing-read'.
-Obey `bbdb-completion-list'."
- (cond ((null bbdb-completion-list)
- nil)
- ((eq t bbdb-completion-list)
- t)
- (t
- (catch 'bbdb-hash-ok
- (dolist (record records)
- (bbdb-hash-p key record bbdb-completion-list))
- nil))))
-
-(defun bbdb-completing-read-records (prompt &optional omit-records)
- "Read and return list of records from the bbdb.
-Completion is done according to `bbdb-completion-list'. If the user
-just hits return, nil is returned. Otherwise, a valid response is forced."
- (let* ((completion-ignore-case t)
- (string (completing-read prompt bbdb-hashtable
- 'bbdb-completion-predicate t)))
- (unless (string= "" string)
- (let (records)
- (dolist (record (gethash string bbdb-hashtable))
- (if (not (memq record omit-records))
- (push record records)))
- (delete-dups records)))))
-
-(defun bbdb-completing-read-record (prompt &optional omit-records)
- "Prompt for and return a single record from the bbdb;
-completion is done according to `bbdb-completion-list'. If the user
-just hits return, nil is returned. Otherwise, a valid response is forced.
-If OMIT-RECORDS is non-nil it should be a list of records to dis-allow
-completion with."
- (let ((records (bbdb-completing-read-records prompt omit-records)))
- (cond ((eq (length records) 1)
- (car records))
- ((> (length records) 1)
- (bbdb-display-records records 'one-line)
- (let* ((count (length records))
- (result (completing-read
- (format "Which record (1-%s): " count)
- (mapcar 'number-to-string (number-sequence 1 count))
- nil t)))
- (nth (1- (string-to-number result)) records))))))
-
-;;;###autoload
-(defun bbdb-completing-read-mails (prompt &optional init)
- "Like `read-string', but allows `bbdb-complete-mail' style completion."
- (read-from-minibuffer prompt init
- bbdb-completing-read-mails-map))
-
-(defconst bbdb-quoted-string-syntax-table
- (let ((st (make-syntax-table)))
- (modify-syntax-entry ?\\ "\\" st)
- (modify-syntax-entry ?\" "\"" st)
- st)
- "Syntax-table to parse matched quotes. Used by `bbdb-complete-mail'.")
-
-;;;###autoload
-(defun bbdb-complete-mail (&optional beg cycle-completion-buffer)
- "In a mail buffer, complete the user name or mail before point.
-Completion happens up to the preceeding colon, comma, or BEG.
-Return non-nil if there is a valid completion, else return nil.
-
-Completion behaviour obeys `bbdb-completion-list' (see there).
-If what has been typed matches a unique BBDB record, insert an address
-formatted by `bbdb-dwim-mail' (see there). Also, display this record
-if `bbdb-completion-display-record' is non-nil,
-If what has been typed is a valid completion but does not match
-a unique record, display a list of completions.
-If the completion is done and `bbdb-complete-mail-allow-cycling' is t
-then cycle through the mails for the matching record. If BBDB
-would format a given address different from what we have in the mail buffer,
-the first round of cycling reformats the address accordingly, then we cycle
-through the mails for the matching record.
-With prefix CYCLE-COMPLETION-BUFFER non-nil, display a list of all mails
-available for cycling.
-
-Set the variable `bbdb-complete-mail' non-nil for enabling this feature
-as part of the MUA insinuation."
- (interactive (list nil current-prefix-arg))
-
- (bbdb-buffer) ; Make sure the database is initialized.
-
- ;; Completion should begin after the preceding comma (separating
- ;; two addresses) or colon (separating the header field name
- ;; from the header field body). We want to ignore these characters
- ;; if they appear inside a quoted string (RFC 5322, Sec. 3.2.4).
- ;; Note also that a quoted string may span multiple lines
- ;; (RFC 5322, Sec. 2.2.3).
- ;; So to be save, we go back to the beginning of the header field body
- ;; (past the colon, when we are certainly not inside a quoted string),
- ;; then we parse forward, looking for commas not inside a quoted string
- ;; and positioned before END. - This fails with an unbalanced quote.
- ;; But an unbalanced quote is bound to fail anyway.
- (when (and (not beg)
- (<= (point)
- (save-restriction ; `mail-header-end'
- (widen)
- (save-excursion
- (rfc822-goto-eoh)
- (point)))))
- (let ((end (point))
- start pnt state)
- (save-excursion
- ;; A header field name must appear at the beginning of a line,
- ;; and it must be terminated by a colon.
- (re-search-backward "^[^ \t\n:][^:]*:[ \t\n]+")
- (setq beg (match-end 0)
- start beg)
- (goto-char beg)
- ;; If we are inside a syntactically correct header field,
- ;; all continuation lines in between the field name and point
- ;; must begin with a white space character.
- (if (re-search-forward "\n[^ \t]" end t)
- ;; An invalid header is identified via BEG set to nil.
- (setq beg nil)
- ;; Parse field body up to END
- (with-syntax-table bbdb-quoted-string-syntax-table
- (while (setq pnt (re-search-forward ",[ \t\n]*" end t))
- (setq state (parse-partial-sexp start pnt nil nil state)
- start pnt)
- (unless (nth 3 state) (setq beg pnt))))))))
-
- ;; Do we have a meaningful way to set BEG if we are not in a message header?
- (unless beg
- (message "Not a valid buffer position for mail completion")
- (sit-for 1))
-
- (let* ((end (point))
- (done (unless beg 'nothing))
- (orig (and beg (buffer-substring beg end)))
- (completion-ignore-case t)
- (completion (and orig
- (try-completion orig bbdb-hashtable
- 'bbdb-completion-predicate)))
- all-completions dwim-completions one-record)
-
- (unless done
- ;; We get fooled if a partial COMPLETION matches "," (for example,
- ;; a comma in lf-name). Such a partial COMPLETION cannot be protected
- ;; by quoting. Then the comma gets interpreted as BEG.
- ;; So we never perform partial completion beyond the first comma.
- ;; This works even if we have just one record matching ORIG (thus
- ;; allowing dwim-completion) because ORIG is a substring of COMPLETION
- ;; even after COMPLETION got truncated; and ORIG by itself must be
- ;; sufficient to identify this record.
- ;; Yet if multiple records match ORIG we can only offer a *Completions*
- ;; buffer.
- (if (and (stringp completion)
- (string-match "," completion))
- (setq completion (substring completion 0 (match-beginning 0))))
-
- (setq all-completions (all-completions orig bbdb-hashtable
- 'bbdb-completion-predicate))
- ;; Resolve the records matching ORIG:
- ;; Multiple completions may match the same record
- (let ((records (delete-dups
- (apply 'append (mapcar (lambda (compl)
- (gethash compl bbdb-hashtable))
- all-completions)))))
- ;; Is there only one matching record?
- (setq one-record (and (not (cdr records))
- (car records))))
-
- ;; Clean up *Completions* buffer window, if it exists
- (let ((window (get-buffer-window "*Completions*")))
- (if (window-live-p window)
- (quit-window nil window)))
-
- (cond
- ;; Match for a single record
- (one-record
- (let ((completion-list (if (eq t bbdb-completion-list)
- '(fl-name lf-name mail aka organization)
- bbdb-completion-list))
- (mails (bbdb-record-mail one-record))
- mail elt)
- (if (not mails)
- (progn
- (message "Matching record has no mail field")
- (sit-for 1)
- (setq done 'nothing))
-
- ;; Determine the mail address of ONE-RECORD to use for ADDRESS.
- ;; Do we have a preferential order for the following tests?
- ;; (1) If ORIG matches name, AKA, or organization of ONE-RECORD,
- ;; then ADDRESS will be the first mail address of ONE-RECORD.
- (if (try-completion orig
- (append
- (if (memq 'fl-name completion-list)
- (list (or (bbdb-record-name one-record) "")))
- (if (memq 'lf-name completion-list)
- (list (or (bbdb-record-name-lf one-record) "")))
- (if (memq 'aka completion-list)
- (bbdb-record-field one-record 'aka-all))
- (if (memq 'organization completion-list)
- (bbdb-record-organization one-record))))
- (setq mail (car mails)))
- ;; (2) If ORIG matches one or multiple mail addresses of ONE-RECORD,
- ;; then we take the first one matching ORIG.
- ;; We got here with MAIL nil only if `bbdb-completion-list'
- ;; includes 'mail or 'primary.
- (unless mail
- (while (setq elt (pop mails))
- (if (try-completion orig (list elt))
- (setq mail elt
- mails nil))))
- ;; This error message indicates a bug!
- (unless mail (error "No match for %s" orig))
-
- (let ((dwim-mail (bbdb-dwim-mail one-record mail)))
- (if (string= dwim-mail orig)
- ;; We get here if `bbdb-mail-avoid-redundancy' is 'mail-only
- ;; and `bbdb-completion-list' includes 'mail.
- (unless (and bbdb-complete-mail-allow-cycling
- (< 1 (length (bbdb-record-mail one-record))))
- (setq done 'unchanged))
- ;; Replace the text with the expansion
- (delete-region beg end)
- (insert dwim-mail)
- (bbdb-complete-mail-cleanup dwim-mail beg)
- (setq done 'unique))))))
-
- ;; Partial completion
- ((and (stringp completion)
- (not (bbdb-string= orig completion)))
- (delete-region beg end)
- (insert completion)
- (setq done 'partial))
-
- ;; Partial match not allowing further partial completion
- (completion
- (let ((completion-list (if (eq t bbdb-completion-list)
- '(fl-name lf-name mail aka organization)
- bbdb-completion-list)))
- ;; Now collect all the dwim-addresses for each completion.
- ;; Add it if the mail is part of the completions
- (dolist (key all-completions)
- (dolist (record (gethash key bbdb-hashtable))
- (let ((mails (bbdb-record-mail record))
- accept)
- (when mails
- (dolist (field completion-list)
- (cond ((eq field 'fl-name)
- (if (bbdb-string= key (bbdb-record-name record))
- (push (car mails) accept)))
- ((eq field 'lf-name)
- (if (bbdb-string= key (bbdb-cache-lf-name
- (bbdb-record-cache record)))
- (push (car mails) accept)))
- ((eq field 'aka)
- (if (member-ignore-case key (bbdb-record-field
- record 'aka-all))
- (push (car mails) accept)))
- ((eq field 'organization)
- (if (member-ignore-case key (bbdb-record-organization
- record))
- (push (car mails) accept)))
- ((eq field 'primary)
- (if (bbdb-string= key (car mails))
- (push (car mails) accept)))
- ((eq field 'mail)
- (dolist (mail mails)
- (if (bbdb-string= key mail)
- (push mail accept))))))
- (dolist (mail (delete-dups accept))
- (push (bbdb-dwim-mail record mail) dwim-completions))))))
-
- (setq dwim-completions (sort (delete-dups dwim-completions)
- 'string-lessp))
- (cond ((not dwim-completions)
- (message "Matching record has no mail field")
- (sit-for 1)
- (setq done 'nothing))
- ;; DWIM-COMPLETIONS may contain only one element,
- ;; if multiple completions match the same record.
- ;; Then we may proceed with DONE set to `unique'.
- ((eq 1 (length dwim-completions))
- (delete-region beg end)
- (insert (car dwim-completions))
- (bbdb-complete-mail-cleanup (car dwim-completions) beg)
- (setq done 'unique))
- (t (setq done 'choose)))))))
-
- ;; By now, we have considered all possiblities to perform a completion.
- ;; If nonetheless we haven't done anything so far, consider cycling.
- ;;
- ;; Completion and cycling are really two very separate things.
- ;; Completion is controlled by the user variable `bbdb-completion-list'.
- ;; Cycling assumes that ORIG already holds a valid RFC 822 mail address.
- ;; Therefore cycling may consider different records than completion.
- (when (and (not done) bbdb-complete-mail-allow-cycling)
- ;; find the record we are working on.
- (let* ((address (bbdb-extract-address-components orig))
- (record (car (bbdb-message-search
- (car address) (cadr address)))))
- (if (and record
- (setq dwim-completions
- (mapcar (lambda (m) (bbdb-dwim-mail record m))
- (bbdb-record-mail record))))
- (cond ((and (= 1 (length dwim-completions))
- (string= orig (car dwim-completions)))
- (setq done 'unchanged))
- (cycle-completion-buffer ; use completion buffer
- (setq done 'cycle-choose))
- ;; Reformatting / Clean up:
- ;; If the canonical mail address (nth 1 address)
- ;; matches the Nth canonical mail address of RECORD,
- ;; but ORIG is not `equal' to (bbdb-dwim-mail record n),
- ;; then we replace ORIG by (bbdb-dwim-mail record n).
- ;; For example, the address "JOHN SMITH <FOO@BAR.COM>"
- ;; gets reformatted as "John Smith <foo@bar.com>".
- ;; We attempt this reformatting before the yet more
- ;; aggressive proper cycling.
- ((let* ((cmails (bbdb-record-mail-canon record))
- (len (length cmails))
- mail dwim-mail)
- (while (and (not done)
- (setq mail (pop cmails)))
- (when (and (bbdb-string= mail (nth 1 address)) ; ignore case
- (not (string= orig (setq dwim-mail
- (nth (- len 1 (length cmails))
- dwim-completions)))))
- (delete-region beg end)
- (insert dwim-mail)
- (bbdb-complete-mail-cleanup dwim-mail beg)
- (setq done 'reformat)))
- done))
-
- (t
- ;; ORIG is `equal' to an element of DWIM-COMPLETIONS
- ;; Use the next element of DWIM-COMPLETIONS.
- (let ((dwim-mail (or (nth 1 (member orig dwim-completions))
- (nth 0 dwim-completions))))
- ;; replace with new mail address
- (delete-region beg end)
- (insert dwim-mail)
- (bbdb-complete-mail-cleanup dwim-mail beg)
- (setq done 'cycle)))))))
-
- (when (member done '(choose cycle-choose))
- ;; Pop up a completions window using DWIM-COMPLETIONS.
- ;; `completion-in-region' does not work here as DWIM-COMPLETIONS
- ;; is not a collection for completion in the usual sense, but it
- ;; is really a list of replacements.
- (let ((status (not (eq (selected-window) (minibuffer-window))))
- (completion-base-position (list beg end))
- ;; We first call the default value of
- ;; `completion-list-insert-choice-function'
- ;; before performing our own stuff.
- (completion-list-insert-choice-function
- `(lambda (beg end text)
- ,(if (boundp 'completion-list-insert-choice-function)
- `(funcall ',completion-list-insert-choice-function
- beg end text))
- (bbdb-complete-mail-cleanup text beg))))
- (if status (message "Making completion list..."))
- (with-output-to-temp-buffer "*Completions*"
- (display-completion-list dwim-completions))
- (if status (message "Making completion list...done"))))
-
- ;; If DONE is `nothing' return nil so that possibly some other code
- ;; can take over.
- (unless (eq done 'nothing)
- done)))
-
-;;;###autoload
-(define-obsolete-function-alias 'bbdb-complete-name 'bbdb-complete-mail "3.0")
-
-(defun bbdb-complete-mail-cleanup (mail beg)
- "Clean up after inserting MAIL at position BEG.
-If we are past `fill-column', wrap at the previous comma."
- (if (and (not (auto-fill-function))
- (>= (current-column) fill-column))
- (save-excursion
- (goto-char beg)
- (when (search-backward "," (line-beginning-position) t)
- (forward-char 1)
- (insert "\n")
- (indent-relative)
- (if (looking-at "[ \t\n]+")
- (delete-region (point) (match-end 0))))))
- (if (or bbdb-completion-display-record bbdb-complete-mail-hook)
- (let* ((address (bbdb-extract-address-components mail))
- (records (bbdb-message-search (car address) (nth 1 address))))
- ;; Update the *BBDB* buffer if desired.
- (if bbdb-completion-display-record
- (let ((bbdb-silent-internal t))
- ;; FIXME: This pops up *BBDB* before removing *Completions*
- (bbdb-display-records records nil t)))
- ;; `bbdb-complete-mail-hook' may access MAIL, ADDRESS, and RECORDS.
- (run-hooks 'bbdb-complete-mail-hook))))
-
-;;; interface to mail-abbrevs.el.
-
-;;;###autoload
-(defun bbdb-mail-aliases (&optional force-rebuilt noisy)
- "Define mail aliases for the records in the database.
-Define a mail alias for every record that has a `mail-alias' field
-which is the contents of that field.
-If there are multiple comma-separated words in the `mail-alias' field,
-then all of those words will be defined as aliases for that person.
-
-If multiple records in the database have the same mail alias,
-then that alias expands to a comma-separated list of the mail addresses
-of all of these people.
-Add this command to `mail-setup-hook'.
-
-Mail aliases are (re)built only if `bbdb-mail-aliases-need-rebuilt' is non-nil
-because the database was newly loaded or it has been edited.
-Rebuilding the aliases is enforced if prefix FORCE-REBUILT is t."
- (interactive (list current-prefix-arg t))
- ;; Build `mail-aliases' if not yet done.
- ;; Note: `mail-abbrevs-setup' rebuilds the mail-aliases only if
- ;; `mail-personal-alias-file' has changed. So it would not do anything
- ;; if we want to rebuild the mail-aliases because of changes in BBDB.
- (if (or force-rebuilt (eq t mail-aliases)) (build-mail-aliases))
-
- ;; We should be cleverer here and instead of rebuilding all aliases
- ;; we should just do what's necessary, i.e. remove deleted records
- ;; and add new records
- ;; Calling `bbdb-records' can change `bbdb-mail-aliases-need-rebuilt'
- (let ((records (bbdb-search (bbdb-records) :xfield (cons bbdb-mail-alias-field ".")))
- results match)
- (if (not (or force-rebuilt bbdb-mail-aliases-need-rebuilt))
- (if noisy (message "BBDB mail alias: nothing to do"))
- (setq bbdb-mail-aliases-need-rebuilt nil)
-
- ;; collect an alist of (alias rec1 [rec2 ...])
- (dolist (record records)
- (if (bbdb-record-mail record)
- (dolist (alias (bbdb-record-xfield-split record bbdb-mail-alias-field))
- (if (setq match (assoc alias results))
- ;; If an alias appears more than once, we collect all records
- ;; that refer to it.
- (nconc match (list record))
- (push (list alias record) results)))
- (unless bbdb-silent
- (bbdb-warn "record %S has no mail address, but the aliases: %s"
- (bbdb-record-name record)
- (bbdb-record-xfield record bbdb-mail-alias-field))
- (sit-for 1))))
-
- ;; Iterate over the results and create the aliases
- (dolist (result results)
- (let* ((aliasstem (car result))
- (expansions
- (if (cddr result)
- ;; for group aliases we just take all the primary mails
- ;; and define only one expansion!
- (list (mapconcat (lambda (record) (bbdb-dwim-mail record))
- (cdr result) mail-alias-separator-string))
- ;; this is an alias for a single person so deal with it
- ;; according to `bbdb-mail-alias'
- (let* ((record (nth 1 result))
- (mails (bbdb-record-mail record)))
- (if (or (eq 'first bbdb-mail-alias)
- (not (cdr mails)))
- ;; Either we want to define only one alias for
- ;; the first mail address or there is anyway
- ;; only one address. In either case, we take
- ;; take only the first address.
- (list (bbdb-dwim-mail record (car mails)))
- ;; We need to deal with more than one mail address...
- (let* ((all (mapcar (lambda (m) (bbdb-dwim-mail record m))
- mails))
- (star (bbdb-concat mail-alias-separator-string all)))
- (if (eq 'star bbdb-mail-alias)
- (list star (car all))
- ;; if `bbdb-mail-alias' is 'all, we create
- ;; two aliases for the primary mail address
- (cons star (cons (car all) all))))))))
- (count -1) ; n=-1: <alias>*; n=0: <alias>; n>0: <alias>n
- (len (length expansions))
- alias f-alias)
-
- ;; create the aliases for each expansion
- (dolist (expansion expansions)
- (cond ((or (= 1 len)
- (= count 0))
- (setq alias aliasstem))
- ((= count -1) ;; all the mails of a record
- (setq alias (concat aliasstem "*")))
- (t ;; <alias>n for each mail of a record
- (setq alias (format "%s%s" aliasstem count))))
- (setq count (1+ count))
-
- (bbdb-pushnew (cons alias expansion) mail-aliases)
-
- (define-mail-abbrev alias expansion)
- (unless (setq f-alias (intern-soft (downcase alias) mail-abbrevs))
- (error "Cannot find the alias"))
-
- ;; `define-mail-abbrev' initializes f-alias to be
- ;; `mail-abbrev-expand-hook'. We replace this by
- ;; `bbdb-mail-abbrev-expand-hook'
- (unless (eq (symbol-function f-alias) 'mail-abbrev-expand-hook)
- (error "mail-aliases contains unexpected hook %s"
- (symbol-function f-alias)))
- ;; `bbdb-mail-abbrev-hook' is called with mail addresses instead of
- ;; bbdb records to avoid keeping pointers to records, which would
- ;; lose if the database was reverted.
- ;; `bbdb-mail-abbrev-hook' uses `bbdb-message-search' to convert
- ;; these mail addresses to records, which is plenty fast.
- ;; FIXME: The value of arg MAILS for `bbdb-mail-abbrev-hook'
- ;; is wrong. Currently it is based on the list of records that have
- ;; referenced ALIASTEM and we simply take the first mail address
- ;; from each of these records.
- ;; Then `bbdb-message-search' will find the correct records
- ;; (assuming that each mail address appears only once in the
- ;; database). Nonethless, arg MAILS for `bbdb-mail-abbrev-hook'
- ;; does not, in general, contain the actual mail addresses
- ;; of EXPANSION. So what we would need is to go back from
- ;; EXPANSION to the mail addresses it contains (which is tricky
- ;; because mail addresses in the database can be shortcuts for
- ;; the addresses in EXPANSION).
- (fset f-alias `(lambda ()
- (bbdb-mail-abbrev-expand-hook
- ,alias
- ',(mapcar (lambda (r) (car (bbdb-record-mail r)))
- (cdr result))))))))
-
- (if noisy (message "BBDB mail alias: rebuilding done")))))
-
-(defun bbdb-mail-abbrev-expand-hook (alias mails)
- (run-hook-with-args 'bbdb-mail-abbrev-expand-hook alias mails)
- (mail-abbrev-expand-hook)
- (when bbdb-completion-display-record
- (let ((bbdb-silent-internal t))
- (bbdb-display-records
- (apply 'append
- (mapcar (lambda (mail) (bbdb-message-search nil mail)) mails))
- nil t))))
-
-(defun bbdb-get-mail-aliases ()
- "Return a list of mail aliases used in the BBDB."
- (let ((records (bbdb-search (bbdb-records) :xfield (cons bbdb-mail-alias-field ".")))
- result)
- (dolist (record records)
- (dolist (alias (bbdb-record-xfield-split record bbdb-mail-alias-field))
- (bbdb-pushnew alias result)))
- result))
-
-;;;###autoload
-(defsubst bbdb-mail-alias-list (alias)
- (if (stringp alias)
- (bbdb-split bbdb-mail-alias-field alias)
- alias))
-
-(defun bbdb-add-mail-alias (records &optional alias delete)
- "Add ALIAS to RECORDS.
-If prefix DELETE is non-nil, remove ALIAS from RECORDS.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
-Arg ALIAS is ignored if list RECORDS contains more than one record.
-Instead read ALIAS interactively for each record in RECORDS.
-If the function `bbdb-init-mail-alias' is defined, it is called with
-one arg RECORD to define the default value for ALIAS of RECORD."
- (interactive (list (bbdb-do-records) nil current-prefix-arg))
- (bbdb-editable)
- (setq records (bbdb-record-list records))
- (if (< 1 (length records)) (setq alias nil))
- (let* ((tmp (intern-soft
- (concat "bbdb-init-" (symbol-name bbdb-mail-alias-field))))
- (init-f (if (functionp tmp) tmp)))
- (dolist (record records)
- (let ((r-a-list (bbdb-record-xfield-split record bbdb-mail-alias-field))
- (alias alias)
- a-list)
- (if alias
- (setq a-list (bbdb-mail-alias-list alias))
- (when init-f
- (setq a-list (bbdb-mail-alias-list (funcall init-f record))
- alias (if a-list (bbdb-concat bbdb-mail-alias-field a-list))))
- (let ((crm-separator
- (concat "[ \t\n]*"
- (cadr (assq bbdb-mail-alias-field bbdb-separator-alist))
- "[ \t\n]*"))
- (crm-local-completion-map bbdb-crm-local-completion-map)
- (prompt (format "%s mail alias:%s " (if delete "Remove" "Add")
- (if alias (format " (default %s)" alias) "")))
- (collection (if delete
- (or r-a-list (error "Record has no alias"))
- (bbdb-get-mail-aliases))))
- (setq a-list (if (string< "24.3" (substring emacs-version 0 4))
- (completing-read-multiple prompt collection nil
- delete nil nil alias)
- (bbdb-split bbdb-mail-alias-field
- (completing-read prompt collection nil
- delete nil nil alias))))))
- (dolist (a a-list)
- (if delete
- (setq r-a-list (delete a r-a-list))
- ;; Add alias only if it is not there yet
- (bbdb-pushnew a r-a-list)))
- ;; This also handles `bbdb-mail-aliases-need-rebuilt'
- (bbdb-record-set-xfield record bbdb-mail-alias-field
- (bbdb-concat bbdb-mail-alias-field r-a-list))
- (bbdb-change-record record)))))
-
-;;; Dialing numbers from BBDB
-
-(defun bbdb-dial-number (phone-string)
- "Dial the number specified by PHONE-STRING.
-This uses the tel URI syntax passed to `browse-url' to make the call.
-If `bbdb-dial-function' is non-nil then that is called to make the phone call."
- (interactive "sDial number: ")
- (if bbdb-dial-function
- (funcall bbdb-dial-function phone-string)
- (browse-url (concat "tel:" phone-string))))
-
-;;;###autoload
-(defun bbdb-dial (phone force-area-code)
- "Dial the number at point.
-If the point is at the beginning of a record, dial the first phone number.
-Use rules from `bbdb-dial-local-prefix-alist' unless prefix FORCE-AREA-CODE
-is non-nil. Do not dial the extension."
- (interactive (list (bbdb-current-field) current-prefix-arg))
- (if (eq (car-safe phone) 'name)
- (setq phone (car (bbdb-record-phone (bbdb-current-record)))))
- (if (eq (car-safe phone) 'phone)
- (setq phone (car (cdr phone))))
- (or (vectorp phone) (error "Not on a phone field"))
-
- (let ((number (bbdb-phone-string phone))
- shortnumber)
-
- ;; cut off the extension
- (if (string-match "x[0-9]+$" number)
- (setq number (substring number 0 (match-beginning 0))))
-
- (unless force-area-code
- (let ((alist bbdb-dial-local-prefix-alist) prefix)
- (while (setq prefix (pop alist))
- (if (string-match (concat "^" (eval (car prefix))) number)
- (setq shortnumber (concat (cdr prefix)
- (substring number (match-end 0)))
- alist nil)))))
-
- (if shortnumber
- (setq number shortnumber)
-
- ;; This is terrifically Americanized...
- ;; Leading 0 => local number (?)
- (if (and bbdb-dial-local-prefix
- (string-match "^0" number))
- (setq number (concat bbdb-dial-local-prefix number)))
-
- ;; Leading + => long distance/international number
- (if (and bbdb-dial-long-distance-prefix
- (string-match "^\+" number))
- (setq number (concat bbdb-dial-long-distance-prefix " "
- (substring number 1)))))
-
- (unless bbdb-silent
- (message "Dialing %s" number))
- (bbdb-dial-number number)))
-
-;;; url interface
-
-;;;###autoload
-(defun bbdb-browse-url (records &optional which)
- "Brwose URLs stored in the `url' field of RECORDS.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
-Prefix WHICH specifies which URL in field `url' is used (starting from 0).
-Default is the first URL."
- (interactive (list (bbdb-get-records "Visit (URL): ")
- (and current-prefix-arg
- (prefix-numeric-value current-prefix-arg))))
- (unless which (setq which 0))
- (dolist (record (bbdb-record-list records))
- (let ((url (bbdb-record-xfield-split record 'url)))
- (when url
- (setq url (read-string "fetch: " (nth which url)))
- (unless (string= "" url)
- (browse-url url))))))
-
-;;;###autoload
-(defun bbdb-grab-url (record url)
- "Grab URL and store it in RECORD."
- (interactive (let ((url (browse-url-url-at-point)))
- (unless url (error "No URL at point"))
- (list (bbdb-completing-read-record
- (format "Add `%s' for: " url))
- url)))
- (bbdb-record-set-field record 'url url t)
- (bbdb-change-record record)
- (bbdb-display-records (list record)))
-
-;;; Copy to kill ring
-
-;;;###autoload
-(defun bbdb-copy-records-as-kill (records)
- "Copy RECORDS to kill ring.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
- (interactive (list (bbdb-do-records t)))
- (let (drec)
- (dolist (record (bbdb-record-list records t))
- (push (buffer-substring (nth 2 record)
- (or (nth 2 (car (cdr (memq record bbdb-records))))
- (point-max)))
- drec))
- (kill-new (replace-regexp-in-string
- "[ \t\n]*\\'" "\n"
- (mapconcat 'identity (nreverse drec) "")))))
-
-;;;###autoload
-(defun bbdb-copy-fields-as-kill (records field &optional num)
- "For RECORDS copy values of FIELD at point to kill ring.
-If FIELD is an address or phone with a label, copy only field values
-with the same label. With numeric prefix NUM, if the value of FIELD
-is a list, copy only the NUMth list element.
-Interactively, use BBDB prefix \
-\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'."
- (interactive
- (list (bbdb-do-records t) (bbdb-current-field)
- (and current-prefix-arg
- (prefix-numeric-value current-prefix-arg))))
- (unless field (error "Not a field"))
- (let* ((type (if (eq (car field) 'xfields)
- (car (nth 1 field))
- (car field)))
- (label (if (memq type '(phone address))
- (aref (cadr field) 0)))
- (ident (and (< 1 (length records))
- (not (eq type 'name))))
- val-list)
- (dolist (record (bbdb-record-list records))
- (let ((raw-val (bbdb-record-field (car record) type))
- value)
- (if raw-val
- (cond ((eq type 'phone)
- (dolist (elt raw-val)
- (if (equal label (aref elt 0))
- (push (bbdb-phone-string elt) value)))
- (setq value (bbdb-concat 'phone (nreverse value))))
- ((eq type 'address)
- (dolist (elt raw-val)
- (if (equal label (aref elt 0))
- (push (bbdb-format-address
- elt (if (eq (nth 1 record) 'one-line) 3 2))
- value)))
- (setq value (bbdb-concat 'address (nreverse value))))
- ((consp raw-val)
- (setq value (if num (nth num raw-val)
- (bbdb-concat type raw-val))))
- (t (setq value raw-val))))
- (if value
- (push (if ident
- (bbdb-concat 'name-field
- (bbdb-record-name (car record)) value)
- value) val-list))))
- (let ((str (bbdb-concat 'record (nreverse val-list))))
- (kill-new str)
- (message "%s" str))))
-
-;;; Help and documentation
-
-;;;###autoload
-(defun bbdb-info ()
- (interactive)
- (info (format "(%s)Top" (or bbdb-info-file "bbdb"))))
-
-;;;###autoload
-(defun bbdb-help ()
- (interactive)
- (message (substitute-command-keys "\\<bbdb-mode-map>\
-new field: \\[bbdb-insert-field]; \
-edit field: \\[bbdb-edit-field]; \
-delete field: \\[bbdb-delete-field-or-record]; \
-mode help: \\[describe-mode]; \
-info: \\[bbdb-info]")))
-
-(provide 'bbdb-com)
-
-;;; bbdb-com.el ends here