Commit | Line | Data |
---|---|---|
1a5de666 AB |
1 | ;;; bbdb.el --- core of BBDB -*- lexical-binding: t -*- |
2 | ||
3 | ;; Copyright (C) 2010-2017 Free Software Foundation, Inc. | |
4 | ||
5 | ;; Version: 3.2 | |
6 | ;; Package-Requires: ((emacs "24")) | |
7 | ||
8 | ;; This file is part of the Insidious Big Brother Database (aka BBDB), | |
9 | ||
10 | ;; BBDB is free software: you can redistribute it and/or modify | |
11 | ;; it under the terms of the GNU General Public License as published by | |
12 | ;; the Free Software Foundation, either version 3 of the License, or | |
13 | ;; (at your option) any later version. | |
14 | ||
15 | ;; BBDB is distributed in the hope that it will be useful, | |
16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | ;; GNU General Public License for more details. | |
19 | ||
20 | ;; You should have received a copy of the GNU General Public License | |
21 | ;; along with BBDB. If not, see <http://www.gnu.org/licenses/>. | |
22 | ||
23 | ;;; Commentary: | |
24 | ||
25 | ;; This file is the core of the Insidious Big Brother Database (aka BBDB), | |
26 | ;; See the BBDB info manual for documentation. | |
27 | ;; | |
28 | ;; ----------------------------------------------------------------------- | |
29 | ;; | There is a mailing list for discussion of BBDB: | | |
30 | ;; | bbdb-user@nongnu.org | | |
31 | ;; | To join, go to https://lists.nongnu.org/mailman/listinfo/bbdb-user | | |
32 | ;; | | | |
33 | ;; | When joining this list or reporting bugs, please mention which | | |
34 | ;; | version of BBDB you have. | | |
35 | ;; ----------------------------------------------------------------------- | |
36 | ||
37 | ;;; Code: | |
38 | ||
39 | (require 'timezone) | |
40 | (require 'bbdb-site) | |
41 | ||
42 | ;; When running BBDB, we have (require 'bbdb-autoloads) | |
43 | (declare-function widget-group-match "wid-edit") | |
44 | (declare-function bbdb-migrate "bbdb-migrate") | |
45 | (declare-function bbdb-do-records "bbdb-com") | |
46 | (declare-function bbdb-append-display-p "bbdb-com") | |
47 | (declare-function bbdb-toggle-records-layout "bbdb-com") | |
48 | (declare-function bbdb-dwim-mail "bbdb-com") | |
49 | (declare-function bbdb-layout-prefix "bbdb-com") | |
50 | (declare-function bbdb-completing-read-records "bbdb-com") | |
51 | (declare-function bbdb-merge-records "bbdb-com") | |
52 | (declare-function mail-position-on-field "sendmail") | |
53 | (declare-function vm-select-folder-buffer "vm-folder") | |
54 | ||
55 | ;; cannot use autoload for variables... | |
56 | (defvar message-mode-map) ;; message.el | |
57 | (defvar mail-mode-map) ;; sendmail.el | |
58 | (defvar gnus-article-buffer) ;; gnus-art.el | |
59 | ||
60 | ;; Custom groups | |
61 | ||
62 | (defgroup bbdb nil | |
63 | "The Insidious Big Brother Database." | |
64 | :group 'news | |
65 | :group 'mail) | |
66 | ||
67 | (defgroup bbdb-record-display nil | |
68 | "Variables that affect the display of BBDB records" | |
69 | :group 'bbdb) | |
70 | ||
71 | (defgroup bbdb-record-edit nil | |
72 | "Variables that affect the editing of BBDB records" | |
73 | :group 'bbdb) | |
74 | ||
75 | (defgroup bbdb-sendmail nil | |
76 | "Variables that affect sending mail." | |
77 | :group 'bbdb) | |
78 | ||
79 | (defgroup bbdb-mua nil | |
80 | "Variables that specify the BBDB-MUA interface" | |
81 | :group 'bbdb) | |
82 | ||
83 | (defgroup bbdb-mua-gnus nil | |
84 | "Gnus-specific BBDB customizations" | |
85 | :group 'bbdb-mua) | |
86 | (put 'bbdb-mua-gnus 'custom-loads '(bbdb-gnus)) | |
87 | ||
88 | (defgroup bbdb-mua-gnus-scoring nil | |
89 | "Gnus-specific scoring BBDB customizations" | |
90 | :group 'bbdb-mua-gnus) | |
91 | (put 'bbdb-mua-gnus-scoring 'custom-loads '(bbdb-gnus)) | |
92 | ||
93 | (defgroup bbdb-mua-gnus-splitting nil | |
94 | "Gnus-specific splitting BBDB customizations" | |
95 | :group 'bbdb-mua-gnus) | |
96 | (put 'bbdb-mua-gnus-splitting 'custom-loads '(bbdb-gnus)) | |
97 | ||
98 | (defgroup bbdb-mua-vm nil | |
99 | "VM-specific BBDB customizations" | |
100 | :group 'bbdb-mua) | |
101 | (put 'bbdb-mua-vm 'custom-loads '(bbdb-vm)) | |
102 | ||
103 | (defgroup bbdb-mua-message nil | |
104 | "Message-specific BBDB customizations" | |
105 | :group 'bbdb-mua) | |
106 | (put 'bbdb-mua-message 'custom-loads '(bbdb-message)) | |
107 | ||
108 | (defgroup bbdb-utilities nil | |
109 | "Customizations for BBDB Utilities" | |
110 | :group 'bbdb) | |
111 | ||
112 | (defgroup bbdb-utilities-dialing nil | |
113 | "BBDB Customizations for phone number dialing" | |
114 | :group 'bbdb) | |
115 | ||
116 | (defgroup bbdb-utilities-tex nil | |
117 | "Customizations for TeXing BBDB." | |
118 | :group 'bbdb) | |
119 | (put 'bbdb-utilities-tex 'custom-loads '(bbdb-tex)) | |
120 | ||
121 | (defgroup bbdb-utilities-anniv nil | |
122 | "Customizations for BBDB Anniversaries" | |
123 | :group 'bbdb-utilities) | |
124 | (put 'bbdb-utilities-anniv 'custom-loads '(bbdb-anniv)) | |
125 | ||
126 | (defgroup bbdb-utilities-ispell nil | |
127 | "Customizations for BBDB ispell interface" | |
128 | :group 'bbdb-utilities) | |
129 | (put 'bbdb-utilities-ispell 'custom-loads '(bbdb-ispell)) | |
130 | ||
131 | (defgroup bbdb-utilities-snarf nil | |
132 | "Customizations for BBDB snarf" | |
133 | :group 'bbdb-utilities) | |
134 | (put 'bbdb-utilities-snarf 'custom-loads '(bbdb-snarf)) | |
135 | ||
136 | (defgroup bbdb-utilities-pgp nil | |
137 | "Customizations for BBDB pgp" | |
138 | :group 'bbdb-utilities) | |
139 | (put 'bbdb-utilities-pgp 'custom-loads '(bbdb-pgp)) | |
140 | ||
141 | (defgroup bbdb-utilities-sc nil | |
142 | "Customizations for using Supercite with the BBDB." | |
143 | :group 'bbdb-utilities | |
144 | :prefix "bbdb-sc") | |
145 | (put 'bbdb-utilities-sc 'custom-loads '(bbdb-sc)) | |
146 | ||
147 | ;;; Customizable variables | |
148 | (defcustom bbdb-file (locate-user-emacs-file "bbdb" ".bbdb") | |
149 | "The name of the Insidious Big Brother Database file." | |
150 | :group 'bbdb | |
151 | :type 'file) | |
152 | ||
153 | ;; This should be removed, and the following put in place: | |
154 | ;; a hierarchical structure of bbdb files, some perhaps read-only, | |
155 | ;; perhaps caching in the local bbdb. This way one could have, e.g. an | |
156 | ;; organization address book, with each person having access to it, and | |
157 | ;; then a local address book with personal stuff in it. | |
158 | (defcustom bbdb-file-remote nil | |
159 | "The remote file to save the BBDB database to. | |
160 | When this is non-nil, it should be a file name. | |
161 | When BBDB reads `bbdb-file', it also checks this file, | |
162 | and if it is newer than `bbdb-file', it loads this file. | |
163 | When BBDB writes `bbdb-file', it also writes this file. | |
164 | ||
165 | This feature allows one to keep the database in one place while using | |
166 | different computers, thus reducing the need for merging different files." | |
167 | :group 'bbdb | |
168 | :type '(choice (const :tag "none" nil) | |
169 | (file :tag "remote file name"))) | |
170 | ||
171 | (defcustom bbdb-file-remote-save-always t | |
172 | "If t `bbdb-file-remote' is saved automatically when `bbdb-file' is saved. | |
173 | When nil, ask." | |
174 | :group 'bbdb | |
175 | :type 'boolean) | |
176 | ||
177 | (defcustom bbdb-read-only nil | |
178 | "If t then BBDB will not modify `bbdb-file'. | |
179 | If you have more than one Emacs running at the same time, you might want | |
180 | to set this to t in all but one of them." | |
181 | :group 'bbdb | |
182 | :type '(choice (const :tag "Database is read-only" t) | |
183 | (const :tag "Database is writable" nil))) | |
184 | ||
185 | (defcustom bbdb-auto-revert nil | |
186 | "If t revert unchanged database without querying. | |
187 | If t and `bbdb-file' has changed on disk, while the database | |
188 | has not been modified inside Emacs, revert the database automatically. | |
189 | If nil or the database has been changed inside Emacs, always query | |
190 | before reverting." | |
191 | :group 'bbdb | |
192 | :type '(choice (const :tag "Revert unchanged database without querying" t) | |
193 | (const :tag "Ask before reverting database" nil))) | |
194 | ||
195 | (defcustom bbdb-check-auto-save-file nil | |
196 | "If t BBDB will check its auto-save file. | |
197 | If this file is newer than `bbdb-file', BBDB will offer to revert." | |
198 | :group 'bbdb | |
199 | :type '(choice (const :tag "Check auto-save file" t) | |
200 | (const :tag "Do not check auto-save file" nil))) | |
201 | ||
202 | (defcustom bbdb-before-save-hook nil | |
203 | "Hook run before saving `bbdb-file'." | |
204 | :group 'bbdb | |
205 | :type 'hook) | |
206 | ||
207 | (defcustom bbdb-after-save-hook nil | |
208 | "Hook run after saving `bbdb-file'." | |
209 | :group 'bbdb | |
210 | :type 'hook) | |
211 | ||
212 | (defcustom bbdb-create-hook nil | |
213 | "*Hook run each time a new BBDB record is created. | |
214 | Run with one argument, the new record. This is called before the record is | |
215 | added to the database, followed by a call of `bbdb-change-hook'. | |
216 | ||
217 | If a record has been created by analyzing a mail message, hook functions | |
218 | can use the variable `bbdb-update-records-address' to determine the header | |
219 | and class of the mail address according to `bbdb-message-headers'." | |
220 | :group 'bbdb | |
221 | :type 'hook) | |
222 | ||
223 | (defcustom bbdb-change-hook nil | |
224 | "*Hook run each time a BBDB record is changed. | |
225 | Run with one argument, the record. This is called before the database | |
226 | is modified. If a new bbdb record is created, `bbdb-create-hook' is called | |
227 | first, followed by a call of this hook." | |
228 | :group 'bbdb | |
229 | :type 'hook) | |
230 | ||
231 | (defcustom bbdb-merge-records-function nil | |
232 | "If non-nil, a function for merging two records. | |
233 | This function is called when loading a record into BBDB that has the same uuid | |
234 | as an exisiting record. If nil use `bbdb-merge-records'. | |
235 | This function should take two arguments RECORD1 and RECORD2, with RECORD2 | |
236 | being the already existing record. It should merge RECORD1 into RECORD2, | |
237 | and return RECORD2." | |
238 | :group 'bbdb | |
239 | :type 'function) | |
240 | ||
241 | (defcustom bbdb-time-stamp-format "%Y-%m-%d %T %z" | |
242 | "The BBDB time stamp format. See `format-time-string'. | |
243 | This function is called with arg UNIVERSAL being non-nil." | |
244 | :group 'bbdb | |
245 | :type 'string) | |
246 | ||
247 | (defcustom bbdb-after-change-hook nil | |
248 | "Hook run each time a BBDB record is changed. | |
249 | Run with one argument, the record. This is called after the database | |
250 | is modified. So if you want to modify a record when it is created or changed, | |
251 | use instead `bbdb-create-hook' and / or `bbdb-change-hook'." | |
252 | :group 'bbdb | |
253 | :type 'hook) | |
254 | ||
255 | (defcustom bbdb-after-read-db-hook nil | |
256 | "Hook run (with no arguments) after `bbdb-file' is read. | |
257 | Note that this can be called more than once if the BBDB is reverted." | |
258 | :group 'bbdb | |
259 | :type 'hook) | |
260 | ||
261 | (defcustom bbdb-initialize-hook nil | |
262 | "Normal hook run after the BBDB initialization function `bbdb-initialize'." | |
263 | :group 'bbdb | |
264 | :type 'hook) | |
265 | ||
266 | (defcustom bbdb-mode-hook nil | |
267 | "Normal hook run when the *BBDB* buffer is created." | |
268 | :group 'bbdb | |
269 | :type 'hook) | |
270 | ||
271 | (defcustom bbdb-silent nil | |
272 | "If t, BBDB suppresses all its informational messages and queries. | |
273 | Be very very certain you want to set this to t, because it will suppress | |
274 | queries to alter record names, assign names to addresses, etc. | |
275 | Lisp Hackers: See also `bbdb-silent-internal'." | |
276 | :group 'bbdb | |
277 | :type '(choice (const :tag "Run silently" t) | |
278 | (const :tag "Disable silent running" nil))) | |
279 | ||
280 | (defcustom bbdb-info-file nil | |
281 | "Location of the bbdb info file, if it's not in the standard place." | |
282 | :group 'bbdb | |
283 | :type '(choice (const :tag "Standard location" nil) | |
284 | (file :tag "Nonstandard location"))) | |
285 | ||
286 | \f | |
287 | ;;; Record display | |
288 | ||
289 | (defcustom bbdb-pop-up-window-size 0.5 | |
290 | "Vertical size of BBDB window (vertical split). | |
291 | If it is an integer number, it is the number of lines used by BBDB. | |
292 | If it is a fraction between 0.0 and 1.0 (inclusive), it is the fraction | |
293 | of the tallest existing window that BBDB will take over. | |
294 | If it is t use `display-buffer'/`pop-to-buffer' to create the BBDB window. | |
295 | See also `bbdb-mua-pop-up-window-size'." | |
296 | :group 'bbdb-record-display | |
297 | :type '(choice (number :tag "BBDB window size") | |
298 | (const :tag "Use `pop-to-buffer'" t))) | |
299 | ||
300 | (defcustom bbdb-dedicated-window nil | |
301 | "Make *BBDB* window a dedicated window. | |
302 | Allowed values include nil (not dedicated) 'bbdb (weakly dedicated) | |
303 | and t (strongly dedicated)." | |
304 | :group 'bbdb-record-display | |
305 | :type '(choice (const :tag "BBDB window not dedicated" nil) | |
306 | (const :tag "BBDB window weakly dedicated" bbdb) | |
307 | (const :tag "BBDB window strongly dedicated" t))) | |
308 | ||
309 | (defcustom bbdb-layout-alist | |
310 | '((one-line (order . (phone mail-alias mail notes)) | |
311 | (name-end . 24) | |
312 | (toggle . t)) | |
313 | (multi-line (omit . (uuid creation-date timestamp | |
314 | name-format name-face)) | |
315 | (toggle . t) | |
316 | (indentation . 21)) | |
317 | (pop-up-multi-line (omit . (uuid creation-date timestamp | |
318 | name-format name-face)) | |
319 | (indentation . 21)) | |
320 | (full-multi-line (indentation . 21))) | |
321 | "Alist describing each display layout. | |
322 | The format of an element is (LAYOUT-NAME OPTION-ALIST). | |
323 | ||
324 | By default there are four different layout types used by BBDB, which are | |
325 | `one-line', `multi-line', `pop-up-multi-line' (used for pop-ups) and | |
326 | `full-multi-line' (showing all fields of a record). | |
327 | ||
328 | OPTION-ALIST specifies the options for the layout. Valid options are: | |
329 | ||
330 | ------- Availability -------- | |
331 | Format one-line multi-line default if unset | |
332 | ------------------------------------------------------------------------------ | |
333 | (toggle . BOOL) + + nil | |
334 | (order . FIELD-LIST) + + '(phone ...) | |
335 | (omit . FIELD-LIST) + + nil | |
336 | (name-end . INTEGER) + - 40 | |
337 | (indentation . INTEGER) - + 21 | |
338 | (primary . BOOL) - + nil | |
339 | (display-p . FUNCTION) + + nil | |
340 | ||
341 | - toggle: controls if this layout is included when toggeling the layout | |
342 | - order: defines a user specific order for the fields, where t is a place | |
343 | holder for all remaining fields | |
344 | - omit: is a list of xfields which should not be displayed | |
345 | or t to exclude all xfields except those listed in the order option | |
346 | - name-end: sets the column where the name should end in one-line layout. | |
347 | - indentation: sets the level of indentation for multi-line display. | |
348 | - primary: controls whether only the primary mail is shown or all are shown. | |
349 | - display-p: a function controlling whether the record is to be displayed. | |
350 | ||
351 | When you add a new layout FOO, you can write a corresponding layout | |
352 | function `bbdb-display-record-layout-FOO'. If you do not write your own | |
353 | layout function, the multi-line layout will be used." | |
354 | :group 'bbdb-record-display | |
355 | :type | |
356 | `(repeat | |
357 | (cons :tag "Layout Definition" | |
358 | (choice :tag "Layout type" | |
359 | (const one-line) | |
360 | (const multi-line) | |
361 | (const pop-up-multi-line) | |
362 | (const full-multi-line) | |
363 | (symbol)) | |
364 | (set :tag "Properties" | |
365 | (cons :tag "Order" | |
366 | (const :tag "List of fields to order by" order) | |
367 | (repeat (choice (const phone) | |
368 | (const address) | |
369 | (const mail) | |
370 | (const AKA) | |
371 | (const notes) | |
372 | (symbol :tag "other") | |
373 | (const :tag "Remaining fields" t)))) | |
374 | (choice :tag "Omit" | |
375 | :value (omit . nil) | |
376 | (cons :tag "List of fields to omit" | |
377 | (const :tag "Fields not to display" omit) | |
378 | (repeat (choice (const phone) | |
379 | (const address) | |
380 | (const mail) | |
381 | (const AKA) | |
382 | (const notes) | |
383 | (symbol :tag "other")))) | |
384 | (const :tag "Exclude all fields except those listed in the order note" t)) | |
385 | (cons :tag "Indentation" | |
386 | :value (indentation . 14) | |
387 | (const :tag "Level of indentation for multi-line layout" | |
388 | indentation) | |
389 | (number :tag "Column")) | |
390 | (cons :tag "End of name field" | |
391 | :value (name-end . 24) | |
392 | (const :tag "The column where the name should end in one-line layout" | |
393 | name-end) | |
394 | (number :tag "Column")) | |
395 | (cons :tag "Toggle" | |
396 | (const :tag "The layout is included when toggling layout" toggle) | |
397 | boolean) | |
398 | (cons :tag "Primary Mail Only" | |
399 | (const :tag "Only the primary mail address is included" primary) | |
400 | boolean) | |
401 | (cons :tag "Display-p" | |
402 | (const :tag "Show only records passing this test" display-p) | |
403 | (choice (const :tag "No test" nil) | |
404 | (function :tag "Predicate"))))))) | |
405 | ||
406 | (defcustom bbdb-layout 'multi-line | |
407 | "Default display layout." | |
408 | :group 'bbdb-record-display | |
409 | :type '(choice (const one-line) | |
410 | (const multi-line) | |
411 | (const full-multi-line) | |
412 | (symbol))) | |
413 | ||
414 | (defcustom bbdb-pop-up-layout 'pop-up-multi-line | |
415 | "Default layout for pop-up BBDB buffers (mail, news, etc.)." | |
416 | :group 'bbdb-record-display | |
417 | :type '(choice (const one-line) | |
418 | (const multi-line) | |
419 | (const full-multi-line) | |
420 | (symbol))) | |
421 | ||
422 | (defcustom bbdb-wrap-column nil | |
423 | "Wrap column for multi-line display. If nil do not wrap lines." | |
424 | :group 'bbdb-record-display | |
425 | :type '(choice (const :tag "No line wrapping" nil) | |
426 | (number :tag "Wrap column"))) | |
427 | ||
428 | (defcustom bbdb-case-fold-search (default-value 'case-fold-search) | |
429 | "Value of `case-fold-search' used by BBDB and friends. | |
430 | This variable lets the case-sensitivity of the BBDB commands | |
431 | be different from standard commands like command `isearch-forward'." | |
432 | :group 'bbdb-record-display | |
433 | :type 'boolean) | |
434 | ||
435 | (defcustom bbdb-name-format 'first-last | |
436 | "Format for displaying names. | |
437 | If first-last names are displayed as \"Firstname Lastname\". | |
438 | If last-first names are displayed as \"Lastname, Firstname\". | |
439 | This can be overriden per record via the xfield name-format, | |
440 | which should take the same values. | |
441 | See also `bbdb-read-name-format'." | |
442 | :group 'bbdb-record-display | |
443 | :type '(choice (const :tag "Firstname Lastname" first-last) | |
444 | (const :tag "Lastname, Firstname" last-first))) | |
445 | ||
446 | ;; See http://en.wikipedia.org/wiki/Postal_address | |
447 | ;; http://www.upu.int/en/activities/addressing/postal-addressing-systems-in-member-countries.html | |
448 | (defcustom bbdb-address-format-list | |
449 | '((("Argentina") "spcSC" "@%s\n@%p, @%c@, %S@\n%C@" "@%c@") | |
450 | (("Australia") "scSpC" "@%s\n@%c@ %S@ %p@\n%C@" "@%c@") | |
451 | (("Austria" "Germany" "Spain" "Switzerland") | |
452 | "spcSC" "@%s\n@%p @%c@ (%S)@\n%C@" "@%c@") | |
453 | (("Canada") "scSCp" "@%s\n@%c@, %S@\n%C@ %p@" "@%c@") | |
454 | (("China") "scpSC" "@%s\n@%c@\n%p@ %S@\n%C@" "@%c@") ; English format | |
455 | ; (("China") "CpScs" "@%C @%p\n@%S @%c@ %s@" "@%c@") ; Chinese format | |
456 | (("India") "scpSC" "@%s\n@%c@ %p@ (%S)@\n%C@" "@%c@") | |
457 | (("USA") "scSpC" "@%s\n@%c@, %S@ %p@\n%C@" "@%c@") | |
458 | (t bbdb-edit-address-default bbdb-format-address-default "@%c@")) | |
459 | "List of address editing and formatting rules for BBDB. | |
460 | Each rule is a list (IDENTIFIER EDIT FORMAT FORMAT). | |
461 | The first rule for which IDENTIFIER matches an address is used for editing | |
462 | and formatting the address. | |
463 | ||
464 | IDENTIFIER may be a list of countries. | |
465 | IDENTIFIER may also be a function that is called with one arg, the address | |
466 | to be used. The rule applies if the function returns non-nil. | |
467 | See `bbdb-address-continental-p' for an example. | |
468 | If IDENTIFIER is t, this rule always applies. Usually, this should be | |
469 | the last rule that becomes a fall-back (default). | |
470 | ||
471 | EDIT may be a function that is called with one argument, the address. | |
472 | See `bbdb-edit-address-default' for an example. | |
473 | ||
474 | EDIT may also be an editting format string. It is a string containing | |
475 | the five letters s, c, p, S, and C that specify the order for editing | |
476 | the five elements of an address: | |
477 | ||
478 | s streets | |
479 | c city | |
480 | p postcode | |
481 | S state | |
482 | C country | |
483 | ||
484 | The first FORMAT of each rule is used for multi-line layout, the second FORMAT | |
485 | is used for one-line layout. | |
486 | ||
487 | FORMAT may be a function that is called with one argument, the address. | |
488 | See `bbdb-format-address-default' for an example. | |
489 | ||
490 | FORMAT may also be a format string. It consists of formatting elements | |
491 | separated by a delimiter defined via the first (and last) character of FORMAT. | |
492 | Each formatting element may contain one of the following format specifiers: | |
493 | ||
494 | %s streets (used repeatedly for each street part) | |
495 | %c city | |
496 | %p postcode | |
497 | %S state | |
498 | %C country | |
499 | ||
500 | A formatting element will be applied only if the corresponding part | |
501 | of the address is a non-empty string. | |
502 | ||
503 | See also `bbdb-tex-address-format-list'." | |
504 | :group 'bbdb-record-display | |
505 | :type '(repeat (list (choice (const :tag "Default" t) | |
506 | (function :tag "Function") | |
507 | (repeat (string))) | |
508 | (choice (string) | |
509 | (function :tag "Function")) | |
510 | (choice (string) | |
511 | (function :tag "Function")) | |
512 | (choice (string) | |
513 | (function :tag "Function"))))) | |
514 | ||
515 | (defcustom bbdb-continental-postcode-regexp | |
516 | "^\\s *[A-Z][A-Z]?\\s *-\\s *[0-9][0-9][0-9]" | |
517 | "Regexp matching continental postcodes. | |
518 | Used by address format identifier `bbdb-address-continental-p'. | |
519 | The regexp should match postcodes of the form CH-8052, NL-2300RA, | |
520 | and SE-132 54." | |
521 | :group 'bbdb-record-display | |
522 | :type 'regexp) | |
523 | ||
524 | (defcustom bbdb-default-separator '("[,;]" ", ") | |
525 | "The default field separator. It is a list (SPLIT-RE JOIN). | |
526 | This is used for fields which do not have an entry in `bbdb-separator-alist'. | |
527 | Whitespace surrounding SPLIT-RE is ignored." | |
528 | :group 'bbdb-record-display | |
529 | :type '(list regexp string)) | |
530 | ||
531 | (defcustom bbdb-separator-alist | |
532 | '((record "\n\n" "\n\n") ; used by `bbdb-copy-fields-as-kill' | |
533 | (name-first-last "[ ,;]" " ") (name-last-first "[ ,;]" ", ") | |
534 | (name-field ":\n" ":\n") ; used by `bbdb-copy-fields-as-kill' | |
535 | (phone "[,;]" ", ") (address ";\n" ";\n") ; ditto | |
536 | (organization "[,;]" ", ") (affix "[,;]" ", ") (aka "[,;]" ", ") | |
537 | (mail "[,;]" ", ") (mail-alias "[,;]" ", ") (vm-folder "[,;]" ", ") | |
538 | (birthday "\n" "\n") (wedding "\n" "\n") (anniversary "\n" "\n") | |
539 | (notes "\n" "\n") (tex-name "#" " # ")) | |
540 | "Alist of field separators. | |
541 | Each element is of the form (FIELD SPLIT-RE JOIN). | |
542 | Whitespace surrounding SPLIT-RE is ignored. | |
543 | For fields lacking an entry here `bbdb-default-separator' is used instead." | |
544 | :group 'bbdb-record-display | |
545 | :type '(repeat (list symbol regexp string))) | |
546 | ||
547 | (defcustom bbdb-user-menu-commands nil | |
548 | "User defined menu entries which should be appended to the BBDB menu. | |
549 | This should be a list of menu entries. | |
550 | When set to a function, it is called with two arguments RECORD and FIELD | |
551 | and it should either return nil or a list of menu entries. | |
552 | Used by `bbdb-mouse-menu'." | |
553 | :group 'bbdb-record-display | |
554 | :type 'sexp) | |
555 | ||
556 | (defcustom bbdb-display-hook nil | |
557 | "Hook run after the *BBDB* is filled in." | |
558 | :group 'bbdb-record-display | |
559 | :type 'hook) | |
560 | ||
561 | (defcustom bbdb-multiple-buffers nil | |
562 | "When non-nil we create a new buffer of every buffer causing pop-ups. | |
563 | You can also set this to a function returning a buffer name. | |
564 | Here a value may be the predefined function `bbdb-multiple-buffers-default'." | |
565 | :group 'bbdb-record-display | |
566 | :type '(choice (const :tag "Disabled" nil) | |
567 | (function :tag "Enabled" bbdb-multiple-buffers-default) | |
568 | (function :tag "User defined function"))) | |
569 | ||
570 | (defcustom bbdb-image nil | |
571 | "If non-nil display records with an image. | |
572 | If a symbol this should be an xfield holding the name of the image file | |
573 | associated with the record. If it is `name' or `fl-name', the first and last | |
574 | name of the record are used as file name. If it is `lf-name', the last and | |
575 | first name of the record are used as file name. | |
576 | If a function it is called with one arg, the record, and it should return | |
577 | the name of the image file. | |
578 | The file is searched in the directories in `bbdb-image-path'. | |
579 | File name suffixes are appended according to `bbdb-image-suffixes'. | |
580 | See `locate-file'." | |
581 | :group 'bbdb-record-display | |
582 | :type '(choice (const :tag "Disabled" nil) | |
583 | (function :tag "User defined function") | |
584 | (symbol :tag "Record field"))) | |
585 | ||
586 | (defcustom bbdb-image-path nil | |
587 | "List of directories to search for `bbdb-image'." | |
588 | :group 'bbdb-record-display | |
589 | :type '(repeat (directory))) | |
590 | ||
591 | (defcustom bbdb-image-suffixes '(".png" ".jpg" ".gif" ".xpm") | |
592 | "List of file name suffixes searched for `bbdb-image'." | |
593 | :group 'bbdb-record-display | |
594 | :type '(repeat (string :tag "File suffix"))) | |
595 | ||
596 | (defcustom bbdb-read-name-format 'fullname | |
597 | "Default format for reading names via `bbdb-read-name'. | |
598 | If it is 'first-last read first and last name separately. | |
599 | If it is 'last-first read last and first name separately. | |
600 | With any other value read full name at once. | |
601 | See also `bbdb-name-format'." | |
602 | :group 'bbdb-record-display | |
603 | :type '(choice (const :tag "Firstname Lastname" first-last) | |
604 | (const :tag "Lastname, Firstname" last-first) | |
605 | (const :tag "Full name" fullname))) | |
606 | ||
607 | \f | |
608 | ;;; Record editing | |
609 | (defcustom bbdb-lastname-prefixes | |
610 | '("von" "de" "di") | |
611 | "List of lastname prefixes recognized in name fields. | |
612 | Used to enhance dividing name strings into firstname and lastname parts. | |
613 | Case is ignored." | |
614 | :group 'bbdb-record-edit | |
615 | :type '(repeat string)) | |
616 | ||
617 | (defcustom bbdb-lastname-re | |
618 | (concat "[- \t]*\\(\\(?:\\<" | |
619 | (regexp-opt bbdb-lastname-prefixes) | |
620 | ;; multiple last names concatenated by `-' | |
621 | "\\>[- \t]+\\)?\\(?:\\w+[ \t]*-[ \t]*\\)*\\w+\\)\\'") | |
622 | "Regexp matching the last name of a full name. | |
623 | Its first parenthetical subexpression becomes the last name." | |
624 | :group 'bbdb-record-edit | |
625 | :type 'regexp) | |
626 | ||
627 | (defcustom bbdb-lastname-suffixes | |
628 | '("Jr" "Sr" "II" "III") | |
629 | "List of lastname suffixes recognized in name fields. | |
630 | Used to dividing name strings into firstname and lastname parts. | |
631 | All suffixes are complemented by optional `.'. Case is ignored." | |
632 | :group 'bbdb-record-edit | |
633 | :type '(repeat string)) | |
634 | ||
635 | (defcustom bbdb-lastname-suffix-re | |
636 | (concat "[-,. \t/\\]+\\(" | |
637 | (regexp-opt bbdb-lastname-suffixes) | |
638 | ;; suffices are complemented by optional `.'. | |
639 | "\\.?\\)\\W*\\'") | |
640 | "Regexp matching the suffix of a last name. | |
641 | Its first parenthetical subexpression becomes the suffix." | |
642 | :group 'bbdb-record-edit | |
643 | :type 'regexp) | |
644 | ||
645 | (defcustom bbdb-default-domain nil | |
646 | "Default domain to append when reading a new mail address. | |
647 | If a mail address does not contain `[@%!]', append @`bbdb-default-domain' to it. | |
648 | ||
649 | The address is not altered if `bbdb-default-domain' is nil | |
650 | or if a prefix argument is given to the command `bbdb-insert-field'." | |
651 | :group 'bbdb-record-edit | |
652 | :type '(choice (const :tag "none" nil) | |
653 | (string :tag "Default Domain"))) | |
654 | ||
655 | (defcustom bbdb-phone-style 'nanp | |
656 | "Phone numbering plan assumed by BBDB. | |
657 | The value 'nanp refers to the North American Numbering Plan. | |
658 | The value nil refers to a free-style numbering plan. | |
659 | ||
660 | You can have both styles of phone number in your database by providing a | |
661 | prefix argument to the command `bbdb-insert-field'." | |
662 | :group 'bbdb-record-edit | |
663 | :type '(choice (const :tag "NANP" nanp) | |
664 | (const :tag "none" nil))) | |
665 | ||
666 | (defcustom bbdb-default-area-code nil | |
667 | "Default area code to use when reading a new phone number. | |
668 | This variable also affects dialing." | |
669 | :group 'bbdb-record-edit | |
670 | :type '(choice (const :tag "none" nil) | |
671 | (integer :tag "Default Area Code")) | |
672 | :set (lambda( symb val ) | |
673 | (if (or (and (stringp val) | |
674 | (string-match "^[0-9]+$" val)) | |
675 | (integerp val) | |
676 | (null val)) | |
677 | (set symb val) | |
678 | (error "%s must contain digits only." symb)))) | |
679 | ||
680 | (defcustom bbdb-allow-duplicates nil | |
681 | "When non-nil BBDB allows records with duplicate names and email addresses. | |
682 | In rare cases, this may lead to confusion with BBDB's MUA interface." | |
683 | :group 'bbdb-record-edit | |
684 | :type 'boolean) | |
685 | ||
686 | (defcustom bbdb-default-label-list '("home" "work" "other") | |
687 | "Default list of labels for Address and Phone fields." | |
688 | :group 'bbdb-record-edit | |
689 | :type '(repeat string)) | |
690 | ||
691 | (defcustom bbdb-address-label-list bbdb-default-label-list | |
692 | "List of labels for Address field." | |
693 | :group 'bbdb-record-edit | |
694 | :type '(repeat string)) | |
695 | ||
696 | (defcustom bbdb-phone-label-list '("home" "work" "cell" "other") | |
697 | "List of labels for Phone field." | |
698 | :group 'bbdb-record-edit | |
699 | :type '(repeat string)) | |
700 | ||
701 | (defcustom bbdb-default-country "Emacs";; what do you mean, it's not a country? | |
702 | "Default country to use if none is specified." | |
703 | :group 'bbdb-record-edit | |
704 | :type '(choice (const :tag "None" nil) | |
705 | (string :tag "Default Country"))) | |
706 | ||
707 | (defcustom bbdb-check-postcode t | |
708 | "If non-nil, require legal postcodes when entering an address. | |
709 | The format of legal postcodes is determined by the variable | |
710 | `bbdb-legal-postcodes'." | |
711 | :group 'bbdb-record-edit | |
712 | :type 'boolean) | |
713 | ||
714 | (defcustom bbdb-legal-postcodes | |
715 | '(;; empty string | |
716 | "^$" | |
717 | ;; Matches 1 to 6 digits. | |
718 | "^[ \t\n]*[0-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[ \t\n]*$" | |
719 | ;; Matches 5 digits and 3 or 4 digits. | |
720 | "^[ \t\n]*\\([0-9][0-9][0-9][0-9][0-9]\\)[ \t\n]*-?[ \t\n]*\\([0-9][0-9][0-9][0-9]?\\)[ \t\n]*$" | |
721 | ;; Match postcodes for Canada, UK, etc. (result is ("LL47" "U4B")). | |
722 | "^[ \t\n]*\\([A-Za-z0-9]+\\)[ \t\n]+\\([A-Za-z0-9]+\\)[ \t\n]*$" | |
723 | ;; Match postcodes for continental Europe. Examples "CH-8057" | |
724 | ;; or "F - 83320" (result is ("CH" "8057") or ("F" "83320")). | |
725 | ;; Support for "NL-2300RA" added at request from Carsten Dominik | |
726 | ;; <dominik@astro.uva.nl> | |
727 | "^[ \t\n]*\\([A-Z]+\\)[ \t\n]*-?[ \t\n]*\\([0-9]+ ?[A-Z]*\\)[ \t\n]*$" | |
728 | ;; Match postcodes from Sweden where the five digits are grouped 3+2 | |
729 | ;; at the request from Mats Lofdahl <MLofdahl@solar.stanford.edu>. | |
730 | ;; (result is ("SE" (133 36))) | |
731 | "^[ \t\n]*\\([A-Z]+\\)[ \t\n]*-?[ \t\n]*\\([0-9]+\\)[ \t\n]+\\([0-9]+\\)[ \t\n]*$") | |
732 | "List of regexps that match legal postcodes. | |
733 | Whether this is used at all depends on the variable `bbdb-check-postcode'." | |
734 | :group 'bbdb-record-edit | |
735 | :type '(repeat regexp)) | |
736 | ||
737 | (defcustom bbdb-default-xfield 'notes | |
738 | "Default xfield when editing BBDB records." | |
739 | :group 'bbdb-record-edit | |
740 | :type '(symbol :tag "Xfield")) | |
741 | ||
742 | (defcustom bbdb-edit-foo (cons bbdb-default-xfield 'current-fields) | |
743 | "Fields to edit with command `bbdb-edit-foo'. | |
744 | This is a cons pair (WITHOUT-PREFIX . WITH-PREFIX). | |
745 | The car is used if the command is called without a prefix. | |
746 | The cdr is used if the command is called with a prefix. | |
747 | ||
748 | WITHOUT-PREFIX and WITH-PREFIX may take the values: | |
749 | name The full name | |
750 | affix The list of affixes | |
751 | organization The list of organizations | |
752 | aka the list of AKAs | |
753 | mail the list of email addresses | |
754 | phone the list of phone numbers | |
755 | address the list of addresses | |
756 | current-fields Read the field to edit using a completion table | |
757 | that includes all fields of the current record. | |
758 | all-fields Read the field to edit using a completion table | |
759 | that includes all fields currently known to BBDB. | |
760 | ||
761 | Any other symbol is interpreted as the label of an xfield." | |
762 | :group 'bbdb-record-edit | |
763 | :type '(cons (symbol :tag "Field without prefix") | |
764 | (symbol :tag "Field with prefix"))) | |
765 | ||
766 | \f | |
767 | ;;; MUA interface | |
768 | ||
769 | (defcustom bbdb-annotate-field bbdb-default-xfield | |
770 | "Field to annotate via `bbdb-annotate-record' and friends. | |
771 | This may take the values: | |
772 | affix The list of affixes | |
773 | organization The list of organizations | |
774 | aka the list of AKAs | |
775 | mail the list of email addresses | |
776 | all-fields Read the field to edit using a completion table | |
777 | that includes all fields currently known to BBDB. | |
778 | ||
779 | Any other symbol is interpreted as the label of an xfield." | |
780 | :group 'bbdb-mua | |
781 | :type '(symbol :tag "Field to annotate")) | |
782 | ||
783 | (defcustom bbdb-mua-edit-field bbdb-default-xfield | |
784 | "Field to edit with command `bbdb-mua-edit-field' and friends. | |
785 | This may take the values: | |
786 | name The full name | |
787 | affix The list of affixes | |
788 | organization The list of organizations | |
789 | aka the list of AKAs | |
790 | mail the list of email addresses | |
791 | all-fields Read the field to edit using a completion table | |
792 | that includes all fields currently known to BBDB. | |
793 | ||
794 | Any other symbol is interpreted as the label of an xfield." | |
795 | :group 'bbdb-mua | |
796 | :type '(symbol :tag "Field to edit")) | |
797 | ||
798 | (defcustom bbdb-mua-update-interactive-p '(search . query) | |
799 | "How BBDB's interactive MUA commands update BBDB records. | |
800 | This is a cons pair (WITHOUT-PREFIX . WITH-PREFIX). | |
801 | The car is used if the command is called without a prefix. | |
802 | The cdr is used if the command is called with a prefix (and if the prefix | |
803 | is not used for another purpose). | |
804 | ||
805 | WITHOUT-PREFIX and WITH-PREFIX may take the values | |
806 | \(here ADDRESS is an email address found in a message): | |
807 | nil Do nothing. | |
808 | search Search for existing records matching ADDRESS. | |
809 | update Search for existing records matching ADDRESS; | |
810 | update name and mail field if necessary. | |
811 | query Search for existing records matching ADDRESS; | |
812 | query for creation of a new record if the record does not exist. | |
813 | create or t Search for existing records matching ADDRESS; | |
814 | create a new record if it does not yet exist. | |
815 | a function This functions will be called with no arguments. | |
816 | It should return one of the above values. | |
817 | read Read the value interactively." | |
818 | :group 'bbdb-mua | |
819 | :type '(cons (choice (const :tag "do nothing" nil) | |
820 | (const :tag "search for existing records" search) | |
821 | (const :tag "update existing records" update) | |
822 | (const :tag "query annotation of all messages" query) | |
823 | (const :tag "annotate all messages" create) | |
824 | (function :tag "User-defined function") | |
825 | (const :tag "read arg interactively" read)) | |
826 | (choice (const :tag "do nothing" nil) | |
827 | (const :tag "search for existing records" search) | |
828 | (const :tag "update existing records" update) | |
829 | (const :tag "query annotation of all messages" query) | |
830 | (const :tag "annotate all messages" create) | |
831 | (function :tag "User-defined function") | |
832 | (const :tag "read arg interactively" read)))) | |
833 | ||
834 | (defcustom bbdb-mua-auto-update-p 'bbdb-select-message | |
835 | "How `bbdb-mua-auto-update' updates BBDB records automatically. | |
836 | ||
837 | Allowed values are (here ADDRESS is an email address found in a message): | |
838 | nil Do nothing. | |
839 | search Search for existing records matching ADDRESS. | |
840 | update Search for existing records matching ADDRESS; | |
841 | update name and mail field if necessary. | |
842 | query Search for existing records matching ADDRESS; | |
843 | query for creation of a new record if the record does not exist. | |
844 | create or t Search for existing records matching ADDRESS; | |
845 | create a new record if it does not yet exist. | |
846 | a function This functions will be called with no arguments. | |
847 | It should return one of the above values. | |
848 | For an example, see `bbdb-select-message' with | |
849 | `bbdb-mua-update-records-p', `bbdb-accept-message-alist' | |
850 | and `bbdb-ignore-message-alist'. | |
851 | ||
852 | To initiate auto-updating of BBDB records, call `bbdb-mua-auto-update-init' | |
853 | for the respective MUAs in your init file." | |
854 | :group 'bbdb-mua | |
855 | :type '(choice (const :tag "do nothing" nil) | |
856 | (const :tag "search for existing records" search) | |
857 | (const :tag "update existing records" update) | |
858 | (const :tag "query annotation of all messages" query) | |
859 | (const :tag "annotate all messages" create) | |
860 | (function :tag "User-defined function"))) | |
861 | ||
862 | (defcustom bbdb-update-records-p 'search | |
863 | "Return value for `bbdb-select-message' and friends. | |
864 | These functions can select messages for further processing by BBDB, | |
865 | The amount of subsequent processing is determined by `bbdb-update-records-p'. | |
866 | ||
867 | Allowed values are (here ADDRESS is an email address selected | |
868 | by `bbdb-select-message'): | |
869 | nil Do nothing. | |
870 | search Search for existing records matching ADDRESS. | |
871 | update Search for existing records matching ADDRESS; | |
872 | update name and mail field if necessary. | |
873 | query Search for existing records matching ADDRESS; | |
874 | query for creation of a new record if the record does not exist. | |
875 | create or t Search for existing records matching ADDRESS; | |
876 | create a new record if it does not yet exist. | |
877 | a function This functions will be called with no arguments. | |
878 | It should return one of the above values." | |
879 | ;; Also: Used for communication between `bbdb-update-records' | |
880 | ;; and `bbdb-query-create'. | |
881 | :group 'bbdb-mua | |
882 | :type '(choice (const :tag "do nothing" nil) | |
883 | (const :tag "search for existing records" search) | |
884 | (const :tag "update existing records" update) | |
885 | (const :tag "query annotation of all messages" query) | |
886 | (const :tag "annotate all messages" create) | |
887 | (function :tag "User-defined function"))) | |
888 | ||
889 | (defcustom bbdb-message-headers | |
890 | '((sender "From" "Resent-From" "Reply-To" "Sender") | |
891 | (recipients "Resent-To" "Resent-CC" "To" "CC" "BCC")) | |
892 | "Alist of headers to search for sender and recipients mail addresses. | |
893 | Each element is of the form | |
894 | ||
895 | (CLASS HEADER ...) | |
896 | ||
897 | The symbol CLASS defines a class of headers. | |
898 | The strings HEADER belong to CLASS." | |
899 | :group 'bbdb-mua | |
900 | :type 'list) | |
901 | ||
902 | (defcustom bbdb-message-all-addresses nil | |
903 | "If t `bbdb-update-records' returns all mail addresses of a message. | |
904 | Otherwise this function returns only the first mail address of each message." | |
905 | :group 'bbdb-mua | |
906 | :type 'boolean) | |
907 | ||
908 | (defcustom bbdb-message-try-all-headers nil | |
909 | "If t try all message headers to extract an email address from a message. | |
910 | Several BBDB commands extract either the sender or the recipients' email | |
911 | addresses from a message according to `bbdb-message-headers'. If BBDB does not | |
912 | find any email address in this subset of message headers (for example, because | |
913 | an email address is excluded because of `bbdb-user-mail-address-re') | |
914 | but `bbdb-message-try-all-headers' is t, then these commands will also consider | |
915 | the email addresses in the remaining headers." | |
916 | :group 'bbdb-mua | |
917 | :type 'boolean) | |
918 | ||
919 | (defcustom bbdb-accept-message-alist t | |
920 | "Alist describing which messages to automatically create BBDB records for. | |
921 | The format of this alist is | |
922 | ((HEADER-NAME . REGEXP) ...) | |
923 | For example, if | |
924 | ((\"From\" . \"@.*\\.maximegalon\\.edu\") | |
925 | (\"Subject\" . \"time travel\")) | |
926 | BBDB records are only created for messages sent by people at Maximegalon U., | |
927 | or people posting about time travel. | |
928 | If t accept all messages. If nil do not accept any messages. | |
929 | ||
930 | See also `bbdb-ignore-message-alist', which has the opposite effect." | |
931 | :group 'bbdb-mua | |
932 | :type '(repeat (cons | |
933 | (string :tag "Header name") | |
934 | (regexp :tag "Regexp to match on header value")))) | |
935 | ||
936 | (defcustom bbdb-ignore-message-alist nil | |
937 | "Alist describing which messages not to automatically create BBDB records for. | |
938 | The format of this alist is | |
939 | ((HEADER-NAME . REGEXP) ... ) | |
940 | For example, if | |
941 | ((\"From\" . \"mailer-daemon\") | |
942 | ((\"To\" \"CC\") . \"mailing-list-1\\\\|mailing-list-2\")) | |
943 | no BBDB records are created for messages from any mailer daemon, | |
944 | or messages sent to or CCed to either of two mailing lists. | |
945 | If t ignore all messages. If nil do not ignore any messages. | |
946 | ||
947 | See also `bbdb-accept-message-alist', which has the opposite effect." | |
948 | :group 'bbdb-mua | |
949 | :type '(repeat (cons | |
950 | (string :tag "Header name") | |
951 | (regexp :tag "Regexp to match on header value")))) | |
952 | ||
953 | (defcustom bbdb-user-mail-address-re | |
954 | (and (stringp user-mail-address) | |
955 | (string-match "\\`\\([^@]*\\)\\(@\\|\\'\\)" user-mail-address) | |
956 | (concat "\\<" (regexp-quote (match-string 1 user-mail-address)) "\\>")) | |
957 | "A regular expression matching your mail addresses. | |
958 | Several BBDB commands extract either the sender or the recipients' email | |
959 | addresses from a message according to `bbdb-message-headers'. Yet an email | |
960 | address will be ignored if it matches `bbdb-user-mail-address-re'. This way | |
961 | the commands will not operate on your own record. | |
962 | See also `bbdb-message-try-all-headers'." | |
963 | :group 'bbdb-mua | |
964 | :type '(regexp :tag "Regexp matching your mail addresses")) | |
965 | ||
966 | (defcustom bbdb-add-name 'query | |
967 | "How to handle new names for existing BBDB records. | |
968 | This handles messages where the real name differs from the name | |
969 | in a BBDB record with the same mail address, as in \"John Smith <jqs@frob.com>\" | |
970 | versus \"John Q. Smith <jqs@frob.com>\". | |
971 | Allowed values are: | |
972 | t Automatically change the name to the new value. | |
973 | query Query whether to use the new name. | |
974 | nil Ignore the new name. | |
975 | a number Number of seconds BBDB displays the name mismatch. | |
976 | (without further action). | |
977 | a function This is called with two args, the record and the new name. | |
978 | It should return one of the above values. | |
979 | a regexp If the new name matches this regexp ignore it. | |
980 | Otherwise query to add it. | |
981 | See also `bbdb-add-aka'." | |
982 | :group 'bbdb-mua | |
983 | :type '(choice (const :tag "Automatically use the new name" t) | |
984 | (const :tag "Query for name changes" query) | |
985 | (const :tag "Ignore the new name" nil) | |
986 | (integer :tag "Number of seconds to display name mismatch") | |
987 | (function :tag "Function for analyzing name handling") | |
988 | (regexp :tag "If the new name matches this regexp ignore it."))) | |
989 | ||
990 | (defcustom bbdb-add-aka 'query | |
991 | "How to handle alternate names for existing BBDB records. | |
992 | Allowed values are: | |
993 | t Automatically store alternate names as AKA. | |
994 | query Query whether to store alternate names as an AKA. | |
995 | nil Ignore alternate names. | |
996 | a function This is called with two args, the record and the new name. | |
997 | It should return one of the above values. | |
998 | a regexp If the alternate name matches this regexp ignore it. | |
999 | Otherwise query to add it. | |
1000 | See also `bbdb-add-name'." | |
1001 | :group 'bbdb-mua | |
1002 | :type '(choice (const :tag "Automatically store alternate names as AKA" t) | |
1003 | (const :tag "Query for alternate names" query) | |
1004 | (const :tag "Ignore alternate names" nil) | |
1005 | (function :tag "Function for alternate name handling") | |
1006 | (regexp :tag "If the alternate name matches this regexp ignore it."))) | |
1007 | ||
1008 | (defcustom bbdb-add-mails 'query | |
1009 | "How to handle new mail addresses for existing BBDB records. | |
1010 | This handles messages where the mail address differs from the mail addresses | |
1011 | in a BBDB record with the same name as in \"John Q. Smith <jqs@foo.com>\" | |
1012 | versus \"John Q. Smith <jqs@bar.com>\". | |
1013 | Allowed values are: | |
1014 | t Automatically add new mail addresses to the list of mail addresses. | |
1015 | query Query whether to add it. | |
1016 | nil Ignore new mail addresses. | |
1017 | a number Number of seconds BBDB displays the new address | |
1018 | (without further action). | |
1019 | a function This is called with two args, the record and the new mail address. | |
1020 | It should return one of the above values. | |
1021 | a regexp If the new mail address matches this regexp ignore the new address. | |
1022 | Otherwise query to add it. | |
1023 | See also `bbdb-new-mails-primary' and `bbdb-ignore-redundant-mails'." | |
1024 | :group 'bbdb-mua | |
1025 | :type '(choice (const :tag "Automatically add new mail addresses" t) | |
1026 | (const :tag "Query before adding new mail addresses" query) | |
1027 | (const :tag "Never add new mail addresses" nil) | |
1028 | (number :tag "Number of seconds to display new addresses") | |
1029 | (function :tag "Function for analyzing name handling") | |
1030 | (regexp :tag "If the new address matches this regexp ignore it."))) | |
1031 | ||
1032 | (defcustom bbdb-new-mails-primary 'query | |
1033 | "Where to put new mail addresses for existing BBDB records. | |
1034 | A new mail address may either become the new primary mail address, | |
1035 | when it is put at the beginning of the list of mail addresses. | |
1036 | Or the new mail address is added at the end of the list of mail addresses. | |
1037 | Allowed values are: | |
1038 | t Make a new address automatically the primary address. | |
1039 | query Query whether to make it the primary address. | |
1040 | nil Add the new address to the end of the list. | |
1041 | a function This is called with two args, the record and the new mail address. | |
1042 | It should return one of the above values. | |
1043 | a regexp If the new mail address matches this regexp put it at the end. | |
1044 | Otherwise query to make it the primary address. | |
1045 | See also `bbdb-add-mails'." | |
1046 | :group 'bbdb-mua | |
1047 | :type '(choice (const :tag "New address automatically made primary" t) | |
1048 | (const :tag "Query before making a new address primary" query) | |
1049 | (const :tag "Do not make new address primary" nil) | |
1050 | (function :tag "Function for analyzing primary handling") | |
1051 | (regexp :tag "If the new mail address matches this regexp put it at the end."))) | |
1052 | ||
1053 | (defcustom bbdb-canonicalize-mail-function nil | |
1054 | "If non-nil, it should be a function of one arg: a mail address string. | |
1055 | When BBDB \"notices\" a message, the corresponding mail addresses are passed | |
1056 | to this function first. It acts as a kind of \"filter\" to transform | |
1057 | the mail addresses before they are compared against or added to the database. | |
1058 | See `bbdb-canonicalize-mail-1' for a more complete example. | |
1059 | If this function returns nil, BBDB assumes that there is no mail address. | |
1060 | ||
1061 | See also `bbdb-ignore-redundant-mails'." | |
1062 | :group 'bbdb-mua | |
1063 | :type 'function) | |
1064 | ||
1065 | (defcustom bbdb-ignore-redundant-mails 'query | |
1066 | "How to handle redundant mail addresses for existing BBDB records. | |
1067 | For example, \"foo@bar.baz.com\" is redundant w.r.t. \"foo@baz.com\". | |
1068 | This affects two things, whether a new redundant mail address is added | |
1069 | to BBDB and whether an old mail address, which has become redundant | |
1070 | because of a newly added mail address, is removed from BBDB. | |
1071 | ||
1072 | Allowed values are: | |
1073 | t Automatically ignore redundant mail addresses. | |
1074 | query Query whether to ignore them. | |
1075 | nil Do not ignore redundant mail addresses. | |
1076 | a number Number of seconds BBDB displays redundant mail addresses | |
1077 | (without further action). | |
1078 | a function This is called with two args, the record and the new mail address. | |
1079 | It should return one of the above values. | |
1080 | a regexp If the new mail address matches this regexp never ignore | |
1081 | this mail address. Otherwise query to ignore it. | |
1082 | See also `bbdb-add-mails' and `bbdb-canonicalize-mail-function'." | |
1083 | :group 'bbdb-mua | |
1084 | :type '(choice (const :tag "Automatically ignore redundant mail addresses" t) | |
1085 | (const :tag "Query whether to ignore them" query) | |
1086 | (const :tag "Do not ignore redundant mail addresses" nil) | |
1087 | (number :tag "Number of seconds to display redundant addresses") | |
1088 | (function :tag "Function for handling redundant mail addresses") | |
1089 | (regexp :tag "If the new address matches this regexp never ignore it."))) | |
1090 | (define-obsolete-variable-alias 'bbdb-canonicalize-redundant-mails | |
1091 | 'bbdb-ignore-redundant-mails "3.0") | |
1092 | ||
1093 | (defcustom bbdb-message-clean-name-function 'bbdb-message-clean-name-default | |
1094 | "Function to clean up the name in the header of a message. | |
1095 | It takes one argument, the name as extracted by | |
1096 | `mail-extract-address-components'." | |
1097 | :group 'bbdb-mua | |
1098 | :type 'function) | |
1099 | ||
1100 | (defcustom bbdb-message-mail-as-name t | |
1101 | "If non-nil use mail address of message as fallback for name of new records." | |
1102 | :group 'bbdb-mua | |
1103 | :type 'boolean) | |
1104 | ||
1105 | (defcustom bbdb-notice-mail-hook nil | |
1106 | "Hook run each time a mail address of a record is \"noticed\" in a message. | |
1107 | This means that the mail address in a message belongs to an existing BBDB record | |
1108 | or to a record BBDB has created for the mail address. | |
1109 | ||
1110 | Run with one argument, the record. It is up to the hook function | |
1111 | to determine which MUA is used and to act appropriately. | |
1112 | Hook functions can use the variable `bbdb-update-records-address' | |
1113 | to determine the header and class of the mail address according | |
1114 | to `bbdb-message-headers'. See `bbdb-auto-notes' for how to annotate records | |
1115 | using `bbdb-update-records-address' and the headers of a mail message. | |
1116 | ||
1117 | If a message contains multiple mail addresses belonging to one BBDB record, | |
1118 | this hook is run for each mail address. Use `bbdb-notice-record-hook' | |
1119 | if you want to notice each record only once per message." | |
1120 | :group 'bbdb-mua | |
1121 | :type 'hook) | |
1122 | ||
1123 | (defcustom bbdb-notice-record-hook nil | |
1124 | "Hook run each time a BBDB record is \"noticed\" in a message. | |
1125 | This means that one of the mail addresses in a message belongs to an existing | |
1126 | record or it is a record BBDB has created for the mail address. If a message | |
1127 | contains multiple mail addresses belonging to one BBDB record, this hook | |
1128 | is nonetheless run only once. Use `bbdb-notice-mail-hook' if you want to run | |
1129 | a hook function for each mail address in a message. | |
1130 | ||
1131 | Hook is run with one argument, the record." | |
1132 | :group 'bbdb-mua | |
1133 | :type 'hook) | |
1134 | ||
1135 | (define-widget 'bbdb-alist-with-header 'group | |
1136 | "My group" | |
1137 | :match 'bbdb-alist-with-header-match | |
1138 | :value-to-internal (lambda (_widget value) | |
1139 | (if value (list (car value) (cdr value)))) | |
1140 | :value-to-external (lambda (_widget value) | |
1141 | (if value (append (list (car value)) (cadr value))))) | |
1142 | ||
1143 | (defun bbdb-alist-with-header-match (widget value) | |
1144 | (widget-group-match widget | |
1145 | (widget-apply widget :value-to-internal value))) | |
1146 | ||
1147 | (defvar bbdb-auto-notes-rules-expanded nil | |
1148 | "Expanded `bbdb-auto-notes-rules'.") ; Internal variable | |
1149 | ||
1150 | (defcustom bbdb-auto-notes-rules nil | |
1151 | "List of rules for adding notes to records of mail addresses of messages. | |
1152 | This automatically annotates the BBDB record of the sender or recipient | |
1153 | of a message based on the value of a header such as the Subject header. | |
1154 | This requires that `bbdb-notice-mail-hook' contains `bbdb-auto-notes' | |
1155 | and that the record already exists or `bbdb-update-records-p' returns such that | |
1156 | the record will be created. Messages matching `bbdb-auto-notes-ignore-messages' | |
1157 | are ignored. | |
1158 | ||
1159 | The elements of this list are | |
1160 | ||
1161 | (MUA FROM-TO HEADER ANNOTATE ...) | |
1162 | (FROM-TO HEADER ANNOTATE ...) | |
1163 | (HEADER ANNOTATE ...) | |
1164 | ||
1165 | MUA is the active MUA or a list of MUAs (see `bbdb-mua'). | |
1166 | If MUA is missing or t, use this rule for all MUAs. | |
1167 | ||
1168 | FROM-TO is a list of headers and/or header classes as in `bbdb-message-headers'. | |
1169 | The record corresponding to a mail address of a message is considered for | |
1170 | annotation if this mail address was found in a header matching FROM-TO. | |
1171 | If FROM-TO is missing or t, records for each mail address are considered | |
1172 | irrespective of where the mail address was found in a message. | |
1173 | ||
1174 | HEADER is a message header that is considered for generating the annotation. | |
1175 | ||
1176 | ANNOTATE may take the following values: | |
1177 | ||
1178 | (REGEXP . STRING) [this is equivalent to (REGEXP notes STRING)] | |
1179 | (REGEXP FIELD STRING) | |
1180 | (REGEXP FIELD STRING REPLACE) | |
1181 | ||
1182 | REGEXP must match the value of HEADER for generating an annotation. | |
1183 | However, if the value of HEADER also matches an element of | |
1184 | `bbdb-auto-notes-ignore-headers' no annotation is generated. | |
1185 | ||
1186 | The annotation will be added to FIELD of the respective record. | |
1187 | FIELD defaults to `bbdb-default-xfield'. | |
1188 | ||
1189 | STRING defines a replacement for the match of REGEXP in the value of HEADER. | |
1190 | It may contain \\& or \\N specials used by `replace-match'. | |
1191 | The resulting string becomes the annotation. | |
1192 | If STRING is an integer N, the Nth matching subexpression is used. | |
1193 | If STRING is a function, it will be called with one arg, the value of HEADER. | |
1194 | The return value (which must be a string) is then used. | |
1195 | ||
1196 | If REPLACE is t, the resulting string replaces the old contents of FIELD. | |
1197 | If it is nil, the string is appended to the contents of FIELD (unless the | |
1198 | annotation is already part of the content of field). | |
1199 | ||
1200 | For example, | |
1201 | ||
1202 | ((\"To\" (\"-vm@\" . \"VM mailing list\")) | |
1203 | (\"Subject\" (\"sprocket\" . \"mail about sprockets\") | |
1204 | (\"you bonehead\" . \"called me a bonehead\"))) | |
1205 | ||
1206 | will cause the text \"VM mailing list\" to be added to the notes field | |
1207 | of the records corresponding to anyone you get mail from via one of the VM | |
1208 | mailing lists. | |
1209 | ||
1210 | If multiple clauses match the message, all of the corresponding strings | |
1211 | will be added. | |
1212 | ||
1213 | See also variables `bbdb-auto-notes-ignore-messages' and | |
1214 | `bbdb-auto-notes-ignore-headers'. | |
1215 | ||
1216 | For speed-up, the function `bbdb-auto-notes' actually use expanded rules | |
1217 | stored in the internal variable `bbdb-auto-notes-rules-expanded'. | |
1218 | If you change the value of `bbdb-auto-notes-rules' outside of customize, | |
1219 | set `bbdb-auto-notes-rules-expanded' to nil, so that the expanded rules | |
1220 | will be re-evaluated." | |
1221 | :group 'bbdb-mua | |
1222 | :set (lambda (symbol value) | |
1223 | (set-default symbol value) | |
1224 | (setq bbdb-auto-notes-rules-expanded nil)) | |
1225 | :type '(repeat | |
1226 | (bbdb-alist-with-header | |
1227 | (repeat (choice | |
1228 | (const sender) | |
1229 | (const recipients))) | |
1230 | (string :tag "Header name") | |
1231 | (repeat (choice | |
1232 | (cons :tag "Value Pair" | |
1233 | (regexp :tag "Regexp to match on header value") | |
1234 | (string :tag "String for notes if regexp matches")) | |
1235 | (list :tag "Replacement list" | |
1236 | (regexp :tag "Regexp to match on header value") | |
1237 | (choice :tag "Record field" | |
1238 | (const notes :tag "xfields") | |
1239 | (const organization :tag "Organization") | |
1240 | (symbol :tag "Other")) | |
1241 | (choice :tag "Regexp match" | |
1242 | (string :tag "Replacement string") | |
1243 | (integer :tag "Subexpression match") | |
1244 | (function :tag "Callback Function")) | |
1245 | (choice :tag "Replace previous contents" | |
1246 | (const :tag "No" nil) | |
1247 | (const :tag "Yes" t)))))))) | |
1248 | ||
1249 | (defcustom bbdb-auto-notes-ignore-messages nil | |
1250 | "List of rules for ignoring entire messages in `bbdb-auto-notes'. | |
1251 | The elements may have the following values: | |
1252 | a function This function is called with one arg, the record | |
1253 | that would be annotated. | |
1254 | Ignore this message if the function returns non-nil. | |
1255 | This function may use `bbdb-update-records-address'. | |
1256 | MUA Ignore messages from MUA (see `bbdb-mua'). | |
1257 | (HEADER . REGEXP) Ignore messages where HEADER matches REGEXP. | |
1258 | For example, (\"From\" . bbdb-user-mail-address-re) | |
1259 | disables any recording of notes for mail addresses | |
1260 | found in messages coming from yourself, see | |
1261 | `bbdb-user-mail-address-re'. | |
1262 | (MUA HEADER REGEXP) Ignore messages from MUA where HEADER | |
1263 | matches REGEXP. | |
1264 | See also `bbdb-auto-notes-ignore-headers'." | |
1265 | :group 'bbdb-mua | |
1266 | :type '(repeat (cons | |
1267 | (string :tag "Header name") | |
1268 | (regexp :tag "Regexp to match on header value")))) | |
1269 | ||
1270 | (defcustom bbdb-auto-notes-ignore-headers nil | |
1271 | "Alist of headers and regexps to ignore in `bbdb-auto-notes'. | |
1272 | Each element is of the form | |
1273 | ||
1274 | (HEADER . REGEXP) | |
1275 | ||
1276 | For example, | |
1277 | ||
1278 | (\"Organization\" . \"^Gatewayed from\\\\\|^Source only\") | |
1279 | ||
1280 | will exclude the phony `Organization:' headers in GNU mailing-lists | |
1281 | gatewayed to gnu.* newsgroups. | |
1282 | See also `bbdb-auto-notes-ignore-messages'." | |
1283 | :group 'bbdb-mua | |
1284 | :type '(repeat (cons | |
1285 | (string :tag "Header name") | |
1286 | (regexp :tag "Regexp to match on header value")))) | |
1287 | ||
1288 | (defcustom bbdb-mua-pop-up t | |
1289 | "If non-nil, display an auto-updated BBDB window while using a MUA. | |
1290 | If 'horiz, stack the window horizontally if there is room. | |
1291 | If this is nil, BBDB is updated silently. | |
1292 | ||
1293 | See also `bbdb-mua-pop-up-window-size' and `bbdb-horiz-pop-up-window-size'." | |
1294 | :group 'bbdb-mua | |
1295 | :type '(choice (const :tag "MUA BBDB window stacked vertically" t) | |
1296 | (const :tag "MUA BBDB window stacked horizontally" horiz) | |
1297 | (const :tag "No MUA BBDB window" nil))) | |
1298 | (define-obsolete-variable-alias 'bbdb-message-pop-up 'bbdb-mua-pop-up "3.0") | |
1299 | ||
1300 | (defcustom bbdb-mua-pop-up-window-size bbdb-pop-up-window-size | |
1301 | "Vertical size of MUA pop-up BBDB window (vertical split). | |
1302 | If it is an integer number, it is the number of lines used by BBDB. | |
1303 | If it is a fraction between 0.0 and 1.0 (inclusive), it is the fraction | |
1304 | of the tallest existing window that BBDB will take over. | |
1305 | If it is t use `pop-to-buffer' to create the BBDB window. | |
1306 | See also `bbdb-pop-up-window-size'." | |
1307 | :group 'bbdb-mua | |
1308 | :type '(choice (number :tag "BBDB window size") | |
1309 | (const :tag "Use `pop-to-buffer'" t))) | |
1310 | ||
1311 | (defcustom bbdb-horiz-pop-up-window-size '(112 . 0.3) | |
1312 | "Horizontal size of a MUA pop-up BBDB window (horizontal split). | |
1313 | It is a cons pair (TOTAL . BBDB-SIZE). | |
1314 | The window that will be considered for horizontal splitting must have | |
1315 | at least TOTAL columns. BBDB-SIZE is the horizontal size of the BBDB window. | |
1316 | If it is an integer number, it is the number of columns used by BBDB. | |
1317 | If it is a fraction between 0 and 1, it is the fraction of the | |
1318 | window width that BBDB will take over." | |
1319 | :group 'bbdb-mua | |
1320 | :type '(cons (number :tag "Total number of columns") | |
1321 | (number :tag "Horizontal size of BBDB window"))) | |
1322 | ||
1323 | \f | |
1324 | ;;; xfields processing | |
1325 | (defcustom bbdb-xfields-sort-order | |
1326 | '((notes . 0) (url . 1) (ftp . 2) (gopher . 3) (telnet . 4) (mail-alias . 5) | |
1327 | (mail-folder . 6) (lpr . 7)) | |
1328 | "The order for sorting the xfields. | |
1329 | If an xfield is not in the alist, it is assigned weight 100, so all xfields | |
1330 | with weights less then 100 will be in the beginning, and all xfields with | |
1331 | weights more than 100 will be in the end." | |
1332 | :group 'bbdb-mua | |
1333 | :type '(repeat (cons | |
1334 | (symbol :tag "xfield") | |
1335 | (number :tag "Weight")))) | |
1336 | (define-obsolete-variable-alias 'bbdb-notes-sort-order 'bbdb-xfields-sort-order "3.0") | |
1337 | ||
1338 | (defcustom bbdb-merge-xfield-function-alist nil | |
1339 | "Alist defining merging functions for particular xfields. | |
1340 | Each element is of the form (LABEL . MERGE-FUN). | |
1341 | For merging xfield LABEL, this will use MERGE-FUN." | |
1342 | :group 'bbdb-mua | |
1343 | :type '(repeat (cons | |
1344 | (symbol :tag "xfield") | |
1345 | (function :tag "merge function")))) | |
1346 | (define-obsolete-variable-alias 'bbdb-merge-notes-function-alist | |
1347 | 'bbdb-merge-xfield-function-alist "3.0") | |
1348 | ||
1349 | (defcustom bbdb-mua-summary-unification-list | |
1350 | '(name mail message-name message-mail message-address) | |
1351 | "List of FIELDs considered by `bbdb-mua-summary-unify'. | |
1352 | For the RECORD matching the address of a message, `bbdb-mua-summary-unify' | |
1353 | returns the first non-empty field value matching an element FIELD from this list. | |
1354 | Each element FIELD may be a valid argument of `bbdb-record-field' for RECORD. | |
1355 | In addition, this list may also include the following elements: | |
1356 | message-name The name in the address of the message | |
1357 | message-mail The mail in the address of the message | |
1358 | message-address The complete address of the message | |
1359 | These provide a fallback if a message does not have a matching RECORD | |
1360 | or if some FIELD of RECORD is empty." | |
1361 | :group 'bbdb-mua | |
1362 | :type '(repeat (symbol :tag "Field"))) | |
1363 | ||
1364 | (defcustom bbdb-mua-summary-mark-field 'mark-char | |
1365 | "BBDB xfield whose value is used to mark message addresses known to BBDB. | |
1366 | This may also be a function, called with one arg, the record, which should | |
1367 | return the mark. See `bbdb-mua-summary-mark' and `bbdb-mua-summary-unify'. | |
1368 | See also `bbdb-mua-summary-mark'." | |
1369 | :group 'bbdb-mua-gnus | |
1370 | :type 'symbol) | |
1371 | ||
1372 | (defcustom bbdb-mua-summary-mark "+" | |
1373 | "Default mark for message addresses known to BBDB. | |
1374 | If nil do not mark message addresses known to BBDB. | |
1375 | See `bbdb-mua-summary-mark' and `bbdb-mua-summary-unify'. | |
1376 | See also `bbdb-mua-summary-mark-field'." | |
1377 | :group 'bbdb-mua | |
1378 | :type '(choice (string :tag "Mark used") | |
1379 | (const :tag "Do not mark known posters" nil))) | |
1380 | ||
1381 | (defcustom bbdb-mua-summary-unify-format-letter "B" | |
1382 | "Letter required for `bbdb-mua-summary-unify' in the MUA Summary format string. | |
1383 | For Gnus, combine it with the %u specifier in `gnus-summary-line-format' | |
1384 | \(see there), for example use \"%U%R%z%I%(%[%4L: %-23,23uB%]%) %s\\n\". | |
1385 | For VM, combine it with the %U specifier in `vm-summary-format' (see there), | |
1386 | for example, use \"%n %*%a %-17.17UB %-3.3m %2d %4l/%-5c %I\\\"%s\\\"\\n\". | |
1387 | This customization of `gnus-summary-line-format' / `vm-summary-format' | |
1388 | is required to use `bbdb-mua-summary-unify'. | |
1389 | Currently no other MUAs support this BBDB feature." | |
1390 | :group 'bbdb-mua | |
1391 | :type 'string) | |
1392 | ||
1393 | (defcustom bbdb-mua-summary-mark-format-letter "b" | |
1394 | "Letter required for `bbdb-mua-summary-mark' in the MUA Summary format string. | |
1395 | For Gnus, combine it with the %u specifier in `gnus-summary-line-format' | |
1396 | \(see there), for example, use \"%U%R%z%I%(%[%4L: %ub%-23,23f%]%) %s\\n\". | |
1397 | For VM, combine it with the %U specifier in `vm-summary-format' (see there), | |
1398 | for example, use \"%n %*%a %Ub%-17.17F %-3.3m %2d %4l/%-5c %I\\\"%s\\\"\\n\". | |
1399 | This customization of `gnus-summary-line-format' / `vm-summary-format' | |
1400 | is required to use `bbdb-mua-summary-mark'. | |
1401 | Currently no other MUAs support this BBDB feature." | |
1402 | :group 'bbdb-mua | |
1403 | :type 'string) | |
1404 | ||
1405 | \f | |
1406 | ;;; Sending mail | |
1407 | (defcustom bbdb-mail-user-agent mail-user-agent | |
1408 | "Mail user agent used by BBDB. | |
1409 | Allowed values are those allowed for `mail-user-agent'." | |
1410 | :group 'bbdb-sendmail | |
1411 | :type '(radio (function-item :tag "Message package" | |
1412 | :format "%t\n" | |
1413 | message-user-agent) | |
1414 | (function-item :tag "Mail package" | |
1415 | :format "%t\n" | |
1416 | sendmail-user-agent) | |
1417 | (function-item :tag "Emacs interface to MH" | |
1418 | :format "%t\n" | |
1419 | mh-e-user-agent) | |
1420 | (function-item :tag "Message with full Gnus features" | |
1421 | :format "%t\n" | |
1422 | gnus-user-agent) | |
1423 | (function-item :tag "VM" | |
1424 | :format "%t\n" | |
1425 | vm-user-agent) | |
1426 | (function :tag "Other") | |
1427 | (const :tag "Default" nil))) | |
1428 | ||
1429 | (defcustom bbdb-mail-name-format 'first-last | |
1430 | "Format for names when sending mail. | |
1431 | If first-last format names as \"Firstname Lastname\". | |
1432 | If last-first format names as \"Lastname, Firstname\". | |
1433 | If `bbdb-mail-name' returns the full name as a single string, this takes | |
1434 | precedence over `bbdb-mail-name-format'. Likewise, if the mail address itself | |
1435 | includes a name, this is not reformatted." | |
1436 | :group 'bbdb-sendmail | |
1437 | :type '(choice (const :tag "Firstname Lastname" first-last) | |
1438 | (const :tag "Lastname, Firstname" last-first))) | |
1439 | ||
1440 | (defcustom bbdb-mail-name 'mail-name | |
1441 | "Xfield holding the full name for a record when sending mail. | |
1442 | This may also be a function taking one argument, a record. | |
1443 | If it returns the full mail name as a single string, this is used \"as is\". | |
1444 | If it returns a cons pair (FIRST . LAST) with the first and last name | |
1445 | for this record, these are formatted obeying `bbdb-mail-name-format'." | |
1446 | :group 'bbdb-sendmail | |
1447 | :type '(choice (symbol :tag "xfield") | |
1448 | (function :tag "mail name function"))) | |
1449 | ||
1450 | (defcustom bbdb-mail-alias-field 'mail-alias | |
1451 | "Xfield holding the mail alias for a record. | |
1452 | Used by `bbdb-mail-aliases'. See also `bbdb-mail-alias'." | |
1453 | :group 'bbdb-sendmail | |
1454 | :type 'symbol) | |
1455 | ||
1456 | (defcustom bbdb-mail-alias 'first | |
1457 | "Defines which mail aliases are generated for a BBDB record. | |
1458 | first: Generate one alias \"<alias>\" that expands to the first mail address | |
1459 | of a record. | |
1460 | star: Generate a second alias \"<alias>*\" that expands to all mail addresses | |
1461 | of a record. | |
1462 | all: Generate the aliases \"<alias>\" and \"<alias>*\" (as for 'star) | |
1463 | and aliases \"<alias>n\" for each mail address, where n is the position | |
1464 | of the mail address of a record." | |
1465 | :group 'bbdb-sendmail | |
1466 | :type '(choice (symbol :tag "Only first" first) | |
1467 | (symbol :tag "<alias>* for all mails" star) | |
1468 | (symbol :tag "All aliases" all))) | |
1469 | ||
1470 | (defcustom bbdb-mail-avoid-redundancy nil | |
1471 | "Mail address to use for BBDB records when sending mail. | |
1472 | If non-nil do not use full name in mail address when same as mail. | |
1473 | If value is mail-only never use full name." | |
1474 | :group 'bbdb-sendmail | |
1475 | :type '(choice (const :tag "Allow redundancy" nil) | |
1476 | (const :tag "Never use full name" mail-only) | |
1477 | (const :tag "Avoid redundancy" t))) | |
1478 | ||
1479 | (defcustom bbdb-complete-mail t | |
1480 | "If t MUA insinuation provides key binding for command `bbdb-complete-mail'." | |
1481 | :group 'bbdb-sendmail | |
1482 | :type 'boolean) | |
1483 | ||
1484 | (defcustom bbdb-completion-list t | |
1485 | "Controls the behaviour of `bbdb-complete-mail'. | |
1486 | If a list of symbols, it specifies which fields to complete. Symbols include | |
1487 | fl-name (= first and last name) | |
1488 | lf-name (= last and first name) | |
1489 | organization | |
1490 | aka | |
1491 | mail (= all email addresses of each record) | |
1492 | primary (= first email address of each record) | |
1493 | If t, completion is done for all of the above. | |
1494 | If nil, no completion is offered." | |
1495 | ;; These symbols match the fields for which BBDB provides entries in | |
1496 | ;; `bbdb-hashtable'. | |
1497 | :group 'bbdb-sendmail | |
1498 | :type '(choice (const :tag "No Completion" nil) | |
1499 | (const :tag "Complete across all fields" t) | |
1500 | (repeat :tag "Field" | |
1501 | (choice (const fl-name) | |
1502 | (const lf-name) | |
1503 | (const aka) | |
1504 | (const organization) | |
1505 | (const primary) | |
1506 | (const mail))))) | |
1507 | ||
1508 | (defcustom bbdb-complete-mail-allow-cycling nil | |
1509 | "If non-nil cycle mail addresses when calling `bbdb-complete-mail'." | |
1510 | :group 'bbdb-sendmail | |
1511 | :type 'boolean) | |
1512 | ||
1513 | (defcustom bbdb-complete-mail-hook nil | |
1514 | "List of functions called after a sucessful completion." | |
1515 | :group 'bbdb-sendmail | |
1516 | :type 'hook) | |
1517 | ||
1518 | (defcustom bbdb-mail-abbrev-expand-hook nil | |
1519 | ;; Replacement for function `mail-abbrev-expand-hook'. | |
1520 | "Function (not hook) run each time an alias is expanded. | |
1521 | The function is called with two args the alias and the list | |
1522 | of corresponding mail addresses." | |
1523 | :group 'bbdb-sendmail | |
1524 | :type 'function) | |
1525 | ||
1526 | (defcustom bbdb-completion-display-record t | |
1527 | "If non-nil `bbdb-complete-mail' displays the BBDB record after completion." | |
1528 | :group 'bbdb-sendmail | |
1529 | :type '(choice (const :tag "Update the BBDB buffer" t) | |
1530 | (const :tag "Do not update the BBDB buffer" nil))) | |
1531 | ||
1532 | \f | |
1533 | ;;;Dialing | |
1534 | (defcustom bbdb-dial-local-prefix-alist | |
1535 | '(((if (integerp bbdb-default-area-code) | |
1536 | (format "(%03d)" bbdb-default-area-code) | |
1537 | (or bbdb-default-area-code "")) | |
1538 | . "")) | |
1539 | "Mapping to remove local prefixes from numbers. | |
1540 | If this is non-nil, it should be an alist of | |
1541 | \(PREFIX . REPLACEMENT) elements. The first part of a phone number | |
1542 | matching the regexp returned by evaluating PREFIX will be replaced by | |
1543 | the corresponding REPLACEMENT when dialing." | |
1544 | :group 'bbdb-utilities-dialing | |
1545 | :type 'sexp) | |
1546 | ||
1547 | (defcustom bbdb-dial-local-prefix nil | |
1548 | "Local prefix digits. | |
1549 | If this is non-nil, it should be a string of digits which your phone | |
1550 | system requires before making local calls (for example, if your phone system | |
1551 | requires you to dial 9 before making outside calls.) In BBDB's | |
1552 | opinion, you're dialing a local number if it starts with a 0 after | |
1553 | processing `bbdb-dial-local-prefix-alist'." | |
1554 | :group 'bbdb-utilities-dialing | |
1555 | :type '(choice (const :tag "No digits required" nil) | |
1556 | (string :tag "Dial this first" "9"))) | |
1557 | ||
1558 | (defcustom bbdb-dial-long-distance-prefix nil | |
1559 | "Long distance prefix digits. | |
1560 | If this is non-nil, it should be a string of digits which your phone | |
1561 | system requires before making a long distance call (one not in your local | |
1562 | area code). For example, in some areas you must dial 1 before an area | |
1563 | code. Note that this is used to replace the + sign in phone numbers | |
1564 | when dialling (international dialing prefix.)" | |
1565 | :group 'bbdb-utilities-dialing | |
1566 | :type '(choice (const :tag "No digits required" nil) | |
1567 | (string :tag "Dial this first" "1"))) | |
1568 | ||
1569 | (defcustom bbdb-dial-function nil | |
1570 | "If non-nil this should be a function used for dialing phone numbers. | |
1571 | This function is used by `bbdb-dial-number'. It requires one | |
1572 | argument which is a string for the number that is dialed. | |
1573 | If nil then `bbdb-dial-number' uses the tel URI syntax passed to `browse-url' | |
1574 | to make the call." | |
1575 | :group 'bbdb-utilities-dialing | |
1576 | :type 'function) | |
1577 | ||
1578 | \f | |
1579 | ;; Faces for font-lock | |
1580 | (defgroup bbdb-faces nil | |
1581 | "Faces used by BBDB." | |
1582 | :group 'bbdb | |
1583 | :group 'faces) | |
1584 | ||
1585 | (defface bbdb-name | |
1586 | '((t (:inherit font-lock-function-name-face))) | |
1587 | "Face used for BBDB names." | |
1588 | :group 'bbdb-faces) | |
1589 | ||
1590 | ;; KEY needs to match the value of the xfield name-face, which is a string. | |
1591 | ;; To avoid confusion, we make KEY a string, too, though symbols might be | |
1592 | ;; faster. | |
1593 | (defcustom bbdb-name-face-alist nil | |
1594 | "Alist used for font-locking the name of a record. | |
1595 | Each element should be a cons cell (KEY . FACE) with string KEY and face FACE. | |
1596 | To use FACE for font-locking the name of a record, | |
1597 | the xfield name-face of this record should have the value KEY. | |
1598 | The value of name-face may also be a face which is then used directly. | |
1599 | If none of these schemes succeeds, the face `bbdb-name' is used." | |
1600 | :group 'bbdb-faces | |
1601 | :type '(repeat (cons (symbol :tag "Key") (face :tag "Face")))) | |
1602 | ||
1603 | (defface bbdb-organization | |
1604 | '((t (:inherit font-lock-comment-face))) | |
1605 | "Face used for BBDB names." | |
1606 | :group 'bbdb-faces) | |
1607 | ||
1608 | (defface bbdb-field-name | |
1609 | '((t (:inherit font-lock-variable-name-face))) | |
1610 | "Face used for BBDB names." | |
1611 | :group 'bbdb-faces) | |
1612 | ||
1613 | ;;; Internal variables | |
1614 | (eval-and-compile | |
1615 | (defvar bbdb-debug t | |
1616 | "Enable debugging if non-nil during compile time. | |
1617 | You really should not disable debugging. But it will speed things up.")) | |
1618 | ||
1619 | (defconst bbdb-file-format 9 | |
1620 | "BBDB file format.") | |
1621 | ||
1622 | (defconst bbdb-record-type | |
1623 | '(vector (or string (const nil)) ; first name | |
1624 | (or string (const nil)) ; last name | |
1625 | (repeat string) ; affix | |
1626 | (repeat string) ; aka | |
1627 | (repeat string) ; organization | |
1628 | (repeat (or (vector string string) | |
1629 | (vector string integer integer integer integer))) ; phone | |
1630 | (repeat (vector string (repeat string) string string | |
1631 | string string)) ; address | |
1632 | (repeat string) ; mail | |
1633 | (repeat (cons symbol sexp)) ; xfields | |
1634 | (cons symbol string) ; uuid | |
1635 | (cons symbol string) ; creation-date | |
1636 | (cons symbol string) ; timestamp | |
1637 | sexp) ; cache | |
1638 | "Pseudo-code for the structure of a record. Used by `bbdb-check-type'.") | |
1639 | ||
1640 | (defconst bbdb-file-coding-system 'utf-8 | |
1641 | "Coding system used for reading and writing `bbdb-file'.") | |
1642 | ||
1643 | (defvar bbdb-mail-aliases-need-rebuilt nil | |
1644 | "Non-nil if mail aliases need to be rebuilt.") | |
1645 | ||
1646 | (defvar bbdb-buffer nil "Buffer visiting `bbdb-file'.") | |
1647 | ||
1648 | (defvar bbdb-buffer-name "*BBDB*" "Name of the BBDB buffer.") | |
1649 | ||
1650 | (defvar bbdb-silent-internal nil | |
1651 | "Bind this to t to quiet things down - do not set it. | |
1652 | See also `bbdb-silent'.") | |
1653 | ||
1654 | (defvar bbdb-init-forms | |
1655 | '((gnus ; gnus 3.15 or newer | |
1656 | (add-hook 'gnus-startup-hook 'bbdb-insinuate-gnus)) | |
1657 | (mh-e ; MH-E | |
1658 | (add-hook 'mh-folder-mode-hook 'bbdb-insinuate-mh)) | |
1659 | (rmail ; RMAIL | |
1660 | (add-hook 'rmail-mode-hook 'bbdb-insinuate-rmail)) | |
1661 | (vm ; newer versions of vm do not have `vm-load-hook' | |
1662 | (eval-after-load "vm" '(bbdb-insinuate-vm))) | |
1663 | (mail ; the standard mail user agent | |
1664 | (add-hook 'mail-setup-hook 'bbdb-insinuate-mail)) | |
1665 | (sendmail | |
1666 | (progn (message "BBDB: sendmail insinuation deprecated. Use mail.") | |
1667 | (add-hook 'mail-setup-hook 'bbdb-insinuate-mail))) | |
1668 | (message ; the gnus mail user agent | |
1669 | (add-hook 'message-setup-hook 'bbdb-insinuate-message)) | |
1670 | (mu4e ; the mu4e user agent | |
1671 | (add-hook 'mu4e-main-mode-hook 'bbdb-insinuate-mu4e)) | |
1672 | ||
1673 | (sc ; supercite | |
1674 | (add-hook 'sc-load-hook 'bbdb-insinuate-sc)) | |
1675 | (anniv ; anniversaries | |
1676 | (add-hook 'diary-list-entries-hook 'bbdb-anniv-diary-entries)) | |
1677 | (pgp ; pgp-mail | |
1678 | (progn | |
1679 | (add-hook 'message-send-hook 'bbdb-pgp) | |
1680 | (add-hook 'mail-send-hook 'bbdb-pgp))) | |
1681 | (wl | |
1682 | (add-hook 'wl-init-hook 'bbdb-insinuate-wl))) | |
1683 | "Alist mapping features to insinuation forms.") | |
1684 | ||
1685 | (defvar bbdb-search-invert nil | |
1686 | "Bind this variable to t in order to invert the result of `bbdb-search'.") | |
1687 | ||
1688 | (defvar bbdb-do-all-records nil | |
1689 | "Controls the behavior of the command `bbdb-do-all-records'.") | |
1690 | ||
1691 | (defvar bbdb-append-display nil | |
1692 | "Controls the behavior of the command `bbdb-append-display'.") | |
1693 | ||
1694 | (defvar bbdb-offer-to-create nil | |
1695 | "For communication between `bbdb-update-records' and `bbdb-query-create'.") | |
1696 | ||
1697 | (defvar bbdb-update-records-address nil | |
1698 | "For communication between `bbdb-update-records' and `bbdb-query-create'. | |
1699 | It is a list with elements (NAME MAIL HEADER HEADER-CLASS MUA).") | |
1700 | ||
1701 | ;;; Buffer-local variables for the database. | |
1702 | (defvar bbdb-records nil | |
1703 | "BBDB records list. | |
1704 | In buffer `bbdb-file' this list includes all records. | |
1705 | In the *BBDB* buffers it includes the records that are actually displayed | |
1706 | and its elements are (RECORD DISPLAY-FORMAT MARKER-POS).") | |
1707 | (make-variable-buffer-local 'bbdb-records) | |
1708 | ||
1709 | (defvar bbdb-changed-records nil | |
1710 | "List of records that has been changed since BBDB was last saved. | |
1711 | Use `bbdb-search-changed' to display these records.") | |
1712 | ||
1713 | (defvar bbdb-end-marker nil | |
1714 | "Marker holding the buffer position of the end of the last record.") | |
1715 | ||
1716 | (defvar bbdb-hashtable (make-hash-table :test 'equal) | |
1717 | "Hash table for BBDB records. | |
1718 | Hashes the fields first-last-name, last-first-name, organization, aka, and mail.") | |
1719 | ||
1720 | (defvar bbdb-uuid-table (make-hash-table :test 'equal) | |
1721 | "Hash table for uuid's of BBDB records.") | |
1722 | ||
1723 | (defvar bbdb-xfield-label-list nil | |
1724 | "List of labels for xfields.") | |
1725 | ||
1726 | (defvar bbdb-organization-list nil | |
1727 | "List of organizations known to BBDB.") | |
1728 | ||
1729 | (defvar bbdb-street-list nil | |
1730 | "List of streets known to BBDB.") | |
1731 | ||
1732 | (defvar bbdb-city-list nil | |
1733 | "List of cities known to BBDB.") | |
1734 | ||
1735 | (defvar bbdb-state-list nil | |
1736 | "List of states known to BBDB.") | |
1737 | ||
1738 | (defvar bbdb-postcode-list nil | |
1739 | "List of post codes known to BBDB.") | |
1740 | ||
1741 | (defvar bbdb-country-list nil | |
1742 | "List of countries known to BBDB.") | |
1743 | ||
1744 | (defvar bbdb-modeline-info (make-vector 6 nil) | |
1745 | "Precalculated mode line info for BBDB commands. | |
1746 | This is a vector [APPEND-M APPEND INVERT-M INVERT ALL-M ALL]. | |
1747 | APPEND-M is the mode line info if `bbdb-append-display' is non-nil. | |
1748 | INVERT-M is the mode line info if `bbdb-search-invert' is non-nil. | |
1749 | ALL-M is the mode line info if `bbdb-do-all-records' is non-nil. | |
1750 | APPEND, INVERT, and ALL appear in the message area.") | |
1751 | ||
1752 | (defvar bbdb-update-unchanged-records nil | |
1753 | "If non-nil update unchanged records in the database. | |
1754 | Normally calls of `bbdb-change-hook' and updating of a record are suppressed, | |
1755 | if an editing command did not really change the record. Bind this to t | |
1756 | if you want to call `bbdb-change-hook' and update the record unconditionally.") | |
1757 | ||
1758 | ;;; Keymap | |
1759 | (defvar bbdb-mode-map | |
1760 | (let ((km (make-sparse-keymap))) | |
1761 | (define-key km "*" 'bbdb-do-all-records) | |
1762 | (define-key km "+" 'bbdb-append-display) | |
1763 | (define-key km "!" 'bbdb-search-invert) | |
1764 | (define-key km "a" 'bbdb-add-mail-alias) | |
1765 | (define-key km "A" 'bbdb-mail-aliases) | |
1766 | (define-key km "c" 'bbdb-create) | |
1767 | (define-key km "e" 'bbdb-edit-field) | |
1768 | (define-key km ";" 'bbdb-edit-foo) | |
1769 | (define-key km "n" 'bbdb-next-record) | |
1770 | (define-key km "p" 'bbdb-prev-record) | |
1771 | (define-key km "N" 'bbdb-next-field) | |
1772 | (define-key km "\t" 'bbdb-next-field) ; TAB | |
1773 | (define-key km "P" 'bbdb-prev-field) | |
1774 | (define-key km "\d" 'bbdb-prev-field) ; DEL | |
1775 | (define-key km "d" 'bbdb-delete-field-or-record) | |
1776 | (define-key km "\C-k" 'bbdb-delete-field-or-record) | |
1777 | (define-key km "i" 'bbdb-insert-field) | |
1778 | (define-key km "s" 'bbdb-save) | |
1779 | (define-key km "\C-x\C-s" 'bbdb-save) | |
1780 | (define-key km "t" 'bbdb-toggle-records-layout) | |
1781 | (define-key km "T" 'bbdb-display-records-completely) | |
1782 | (define-key km "o" 'bbdb-omit-record) | |
1783 | (define-key km "m" 'bbdb-mail) | |
1784 | (define-key km "M" 'bbdb-mail-address) | |
1785 | (define-key km "\M-d" 'bbdb-dial) | |
1786 | (define-key km "h" 'bbdb-info) | |
1787 | (define-key km "?" 'bbdb-help) | |
1788 | ;; (define-key km "q" 'quit-window) ; part of `special-mode' bindings | |
1789 | (define-key km "\C-x\C-t" 'bbdb-transpose-fields) | |
1790 | (define-key km "Cr" 'bbdb-copy-records-as-kill) | |
1791 | (define-key km "Cf" 'bbdb-copy-fields-as-kill) | |
1792 | (define-key km "u" 'bbdb-browse-url) | |
1793 | (define-key km "\C-c\C-t" 'bbdb-tex) | |
1794 | (define-key km "=" 'delete-other-windows) | |
1795 | ||
1796 | ;; Search keys | |
1797 | (define-key km "b" 'bbdb) | |
1798 | (define-key km "/1" 'bbdb-display-records) | |
1799 | (define-key km "/n" 'bbdb-search-name) | |
1800 | (define-key km "/o" 'bbdb-search-organization) | |
1801 | (define-key km "/p" 'bbdb-search-phone) | |
1802 | (define-key km "/a" 'bbdb-search-address) | |
1803 | (define-key km "/m" 'bbdb-search-mail) | |
1804 | (define-key km "/N" 'bbdb-search-xfields) | |
1805 | (define-key km "/x" 'bbdb-search-xfields) | |
1806 | (define-key km "/c" 'bbdb-search-changed) | |
1807 | (define-key km "/d" 'bbdb-search-duplicates) | |
1808 | (define-key km "\C-xnw" 'bbdb-display-all-records) | |
1809 | (define-key km "\C-xnd" 'bbdb-display-current-record) | |
1810 | ||
1811 | (define-key km [delete] 'scroll-down) ; 24.1: part of `special-mode' | |
1812 | (define-key km " " 'scroll-up) ; 24.1: part of `special-mode' | |
1813 | ||
1814 | (define-key km [mouse-3] 'bbdb-mouse-menu) | |
1815 | (define-key km [mouse-2] (lambda (event) | |
1816 | ;; Toggle record layout | |
1817 | (interactive "e") | |
1818 | (save-excursion | |
1819 | (posn-set-point (event-end event)) | |
1820 | (bbdb-toggle-records-layout | |
1821 | (bbdb-do-records t) current-prefix-arg)))) | |
1822 | km) | |
1823 | "Keymap for Insidious Big Brother Database. | |
1824 | This is a child of `special-mode-map'.") | |
1825 | ||
1826 | (easy-menu-define | |
1827 | bbdb-menu bbdb-mode-map "BBDB Menu" | |
1828 | '("BBDB" | |
1829 | ("Display" | |
1830 | ["Previous field" bbdb-prev-field t] | |
1831 | ["Next field" bbdb-next-field t] | |
1832 | ["Previous record" bbdb-prev-record t] | |
1833 | ["Next record" bbdb-next-record t] | |
1834 | "--" | |
1835 | ["Show all records" bbdb-display-all-records t] | |
1836 | ["Show current record" bbdb-display-current-record t] | |
1837 | ["Omit record" bbdb-omit-record t] | |
1838 | "--" | |
1839 | ["Toggle layout" bbdb-toggle-records-layout t] | |
1840 | ["Show all fields" bbdb-display-records-completely t]) | |
1841 | ("Searching" | |
1842 | ["General search" bbdb t] | |
1843 | ["Search one record" bbdb-display-records t] | |
1844 | ["Search name" bbdb-search-name t] | |
1845 | ["Search organization" bbdb-search-organization t] | |
1846 | ["Search phone" bbdb-search-phone t] | |
1847 | ["Search address" bbdb-search-address t] | |
1848 | ["Search mail" bbdb-search-mail t] | |
1849 | ["Search xfields" bbdb-search-xfields t] | |
1850 | ["Search changed records" bbdb-search-changed t] | |
1851 | ["Search duplicates" bbdb-search-duplicates t] | |
1852 | "--" | |
1853 | ["Old time stamps" bbdb-timestamp-older t] | |
1854 | ["New time stamps" bbdb-timestamp-newer t] | |
1855 | ["Old creation date" bbdb-creation-older t] | |
1856 | ["New creation date" bbdb-creation-newer t] | |
1857 | ["Creation date = time stamp" bbdb-creation-no-change t] | |
1858 | "--" | |
1859 | ["Append search" bbdb-append-display t] | |
1860 | ["Invert search" bbdb-search-invert t]) | |
1861 | ("Mail" | |
1862 | ["Send mail" bbdb-mail t] | |
1863 | ["Save mail address" bbdb-mail-address t] | |
1864 | "--" | |
1865 | ["Add mail alias" bbdb-add-mail-alias t] | |
1866 | ["(Re-)Build mail aliases" bbdb-mail-aliases t]) | |
1867 | ("Use database" | |
1868 | ["Prefix: do all records" bbdb-do-all-records t] | |
1869 | "--" | |
1870 | ["Send mail" bbdb-mail t] | |
1871 | ["Dial phone number" bbdb-dial t] | |
1872 | ["Browse URL" bbdb-browse-url t] | |
1873 | ["Copy records as kill" bbdb-copy-records-as-kill t] | |
1874 | ["Copy fields as kill" bbdb-copy-fields-as-kill t] | |
1875 | "--" | |
1876 | ["TeX records" bbdb-tex t]) | |
1877 | ("Manipulate database" | |
1878 | ["Prefix: do all records" bbdb-do-all-records t] | |
1879 | "--" | |
1880 | ["Create new record" bbdb-create t] | |
1881 | ["Edit current field" bbdb-edit-field t] | |
1882 | ["Insert new field" bbdb-insert-field t] | |
1883 | ["Edit some field" bbdb-edit-foo t] | |
1884 | ["Transpose fields" bbdb-transpose-fields t] | |
1885 | ["Delete record or field" bbdb-delete-field-or-record t] | |
1886 | "--" | |
1887 | ["Sort addresses" bbdb-sort-addresses t] | |
1888 | ["Sort phones" bbdb-sort-phones t] | |
1889 | ["Sort xfields" bbdb-sort-xfields t] | |
1890 | ["Merge records" bbdb-merge-records t] | |
1891 | ["Sort database" bbdb-sort-records t] | |
1892 | ["Delete duplicate mails" bbdb-delete-redundant-mails t] | |
1893 | "--" | |
1894 | ["Save BBDB" bbdb-save t] | |
1895 | ["Revert BBDB" revert-buffer t]) | |
1896 | ("Help" | |
1897 | ["Brief help" bbdb-help t] | |
1898 | ["BBDB Manual" bbdb-info t]) | |
1899 | "--" | |
1900 | ["Quit" quit-window t])) | |
1901 | ||
1902 | (defvar bbdb-completing-read-mails-map | |
1903 | (let ((map (copy-keymap minibuffer-local-completion-map))) | |
1904 | (define-key map " " 'self-insert-command) | |
1905 | (define-key map "\t" 'bbdb-complete-mail) | |
1906 | (define-key map "\M-\t" 'bbdb-complete-mail) | |
1907 | map) | |
1908 | "Keymap used by `bbdb-completing-read-mails'.") | |
1909 | ||
1910 | \f | |
1911 | ||
1912 | ;;; Helper functions | |
1913 | ||
1914 | (defun bbdb-warn (&rest args) | |
1915 | "Display a message at the bottom of the screen. | |
1916 | ARGS are passed to `message'." | |
1917 | (ding t) | |
1918 | (apply 'message args)) | |
1919 | ||
1920 | (defun bbdb-string-trim (string &optional null) | |
1921 | "Remove leading and trailing whitespace and all properties from STRING. | |
1922 | If STRING is nil return an empty string unless NULL is non-nil." | |
1923 | (if (null string) | |
1924 | (unless null "") | |
1925 | (setq string (substring-no-properties string)) | |
1926 | (if (string-match "\\`[ \t\n]+" string) | |
1927 | (setq string (substring-no-properties string (match-end 0)))) | |
1928 | (if (string-match "[ \t\n]+\\'" string) | |
1929 | (setq string (substring-no-properties string 0 (match-beginning 0)))) | |
1930 | (unless (and null (string= "" string)) | |
1931 | string))) | |
1932 | ||
1933 | (defsubst bbdb-string= (str1 str2) | |
1934 | "Return t if strings STR1 and STR2 are equal, ignoring case." | |
1935 | (and (stringp str1) (stringp str2) | |
1936 | (eq t (compare-strings str1 0 nil str2 0 nil t)))) | |
1937 | ||
1938 | (defun bbdb-split (separator string) | |
1939 | "Split STRING into list of substrings bounded by matches for SEPARATORS. | |
1940 | SEPARATOR may be a regexp. SEPARATOR may also be a symbol | |
1941 | \(a field name). Then look up the value in `bbdb-separator-alist' | |
1942 | or use `bbdb-default-separator'. | |
1943 | Whitespace around SEPARATOR is ignored unless SEPARATOR matches | |
1944 | the string \" \\t\\n\". | |
1945 | Almost the inverse function of `bbdb-concat'." | |
1946 | (if (symbolp separator) | |
1947 | (setq separator (car (or (cdr (assq separator bbdb-separator-alist)) | |
1948 | bbdb-default-separator)))) | |
1949 | (if (<= 24.4 (string-to-number emacs-version)) | |
1950 | ;; `split-string' applied to an empty STRING gives nil. | |
1951 | (split-string string separator t | |
1952 | (unless (string-match separator " \t\n") "[ \t\n]*")) | |
1953 | (unless (string-match separator " \t\n") | |
1954 | (setq separator (concat "[ \t\n]*" separator "[ \t\n]*"))) | |
1955 | (split-string (bbdb-string-trim string) separator t))) | |
1956 | ||
1957 | (defun bbdb-concat (separator &rest strings) | |
1958 | "Concatenate STRINGS to a string sticking in SEPARATOR. | |
1959 | STRINGS may be strings or lists of strings. Empty strings are ignored. | |
1960 | SEPARATOR may be a string. | |
1961 | SEPARATOR may also be a symbol (a field name). Then look up the value | |
1962 | of SEPARATOR in `bbdb-separator-alist' or use `bbdb-default-separator'. | |
1963 | The inverse function of `bbdb-split'." | |
1964 | (if (symbolp separator) | |
1965 | (setq separator (nth 1 (or (cdr (assq separator bbdb-separator-alist)) | |
1966 | bbdb-default-separator)))) | |
1967 | (mapconcat 'identity | |
1968 | (delete "" (apply 'append (mapcar (lambda (x) (if (stringp x) | |
1969 | (list x) x)) | |
1970 | strings))) separator)) | |
1971 | ||
1972 | (defun bbdb-list-strings (list) | |
1973 | "Remove all elements from LIST which are not non-empty strings." | |
1974 | (let (new-list) | |
1975 | (dolist (elt list) | |
1976 | (if (and (stringp elt) (not (string= "" elt))) | |
1977 | (push elt new-list))) | |
1978 | (nreverse new-list))) | |
1979 | ||
1980 | ;; A call of `indent-region' swallows any indentation | |
1981 | ;; that might be part of the field itself. So we indent manually. | |
1982 | (defsubst bbdb-indent-string (string column) | |
1983 | "Indent nonempty lines in STRING to COLUMN (except first line). | |
1984 | This happens in addition to any pre-defined indentation of STRING." | |
1985 | (replace-regexp-in-string "\n\\([^\n]\\)" | |
1986 | (concat "\n" (make-string column ?\s) "\\1") | |
1987 | string)) | |
1988 | ||
1989 | (defun bbdb-read-string (prompt &optional init collection require-match) | |
1990 | "Read a string, trimming whitespace and text properties. | |
1991 | PROMPT is a string to prompt with. | |
1992 | INIT appears as initial input which is useful for editing existing records. | |
1993 | COLLECTION and REQUIRE-MATCH have the same meaning as in `completing-read'." | |
1994 | (bbdb-string-trim | |
1995 | (if collection | |
1996 | ;; Hack: In `minibuffer-local-completion-map' remove | |
1997 | ;; the binding of SPC to `minibuffer-complete-word' | |
1998 | ;; and of ? to `minibuffer-completion-help'. | |
1999 | (minibuffer-with-setup-hook | |
2000 | (lambda () | |
2001 | (use-local-map | |
2002 | (let ((map (make-sparse-keymap))) | |
2003 | (set-keymap-parent map (current-local-map)) | |
2004 | (define-key map " " nil) | |
2005 | (define-key map "?" nil) | |
2006 | map))) | |
2007 | (completing-read prompt collection nil require-match init)) | |
2008 | (read-string prompt init)))) | |
2009 | ||
2010 | ;; The following macros implement variants of `pushnew' (till emacs 24.2) | |
2011 | ;; or `cl-pushnew' (since emacs 24.3). To be compatible with older and newer | |
2012 | ;; versions of emacs we use our own macros. We call these macros often. | |
2013 | ;; So we keep them simple. Nothing fancy is needed here. | |
2014 | (defmacro bbdb-pushnew (element listname) | |
2015 | "Add ELEMENT to the value of LISTNAME if it isn't there yet. | |
2016 | The test for presence of ELEMENT is done with `equal'. | |
2017 | The return value is the new value of LISTNAME." | |
2018 | `(let ((elt ,element)) | |
2019 | (if (member elt ,listname) | |
2020 | ,listname | |
2021 | (setq ,listname (cons elt ,listname))))) | |
2022 | ||
2023 | (defmacro bbdb-pushnewq (element listname) | |
2024 | "Add ELEMENT to the value of LISTNAME if it isn't there yet. | |
2025 | The test for presence of ELEMENT is done with `eq'. | |
2026 | The return value is the new value of LISTNAME." | |
2027 | `(let ((elt ,element)) | |
2028 | (if (memq elt ,listname) | |
2029 | ,listname | |
2030 | (setq ,listname (cons elt ,listname))))) | |
2031 | ||
2032 | (defmacro bbdb-pushnewt (element listname) | |
2033 | "Add ELEMENT to the value of LISTNAME if it isn't there yet and non-nil. | |
2034 | The test for presence of ELEMENT is done with `equal'. | |
2035 | The return value is the new value of LISTNAME." | |
2036 | `(let ((elt ,element)) | |
2037 | (if (or (not elt) | |
2038 | (member elt ,listname)) | |
2039 | ,listname | |
2040 | (setq ,listname (cons elt ,listname))))) | |
2041 | ||
2042 | (defun bbdb-current-record (&optional full) | |
2043 | "Return the record point is at. | |
2044 | If FULL is non-nil record includes the display information." | |
2045 | (unless (eq major-mode 'bbdb-mode) | |
2046 | (error "This only works while in BBDB buffers.")) | |
2047 | (let ((num (get-text-property (if (and (not (bobp)) (eobp)) | |
2048 | (1- (point)) (point)) | |
2049 | 'bbdb-record-number)) | |
2050 | record) | |
2051 | (unless num (error "Not a BBDB record")) | |
2052 | (setq record (nth num bbdb-records)) | |
2053 | (if full record (car record)))) | |
2054 | ||
2055 | (defun bbdb-current-field () | |
2056 | "Return current field point is on." | |
2057 | (unless (bbdb-current-record) (error "Not a BBDB record")) | |
2058 | (get-text-property (point) 'bbdb-field)) | |
2059 | ||
2060 | (defmacro bbdb-debug (&rest body) | |
2061 | "Excecute BODY just like `progn' with debugging capability. | |
2062 | Debugging is enabled if variable `bbdb-debug' is non-nil during compile. | |
2063 | You really should not disable debugging. But it will speed things up." | |
2064 | (declare (indent 0)) | |
2065 | (if bbdb-debug ; compile-time switch | |
2066 | `(let ((debug-on-error t)) | |
2067 | ,@body))) | |
2068 | ||
2069 | ;; inspired by `gnus-bind-print-variables' | |
2070 | (defmacro bbdb-with-print-loadably (&rest body) | |
2071 | "Bind print-* variables for BBDB and evaluate BODY. | |
2072 | This macro is used with `prin1', `prin1-to-string', etc. in order to ensure | |
2073 | printed Lisp objects are loadable by BBDB." | |
2074 | (declare (indent 0)) | |
2075 | `(let ((print-escape-newlines t) ;; BBDB needs this! | |
2076 | print-escape-nonascii print-escape-multibyte | |
2077 | print-quoted print-length print-level) | |
2078 | ;; print-circle print-gensym | |
2079 | ;; print-continuous-numbering | |
2080 | ;; print-number-table | |
2081 | ;; float-output-format | |
2082 | ,@body)) | |
2083 | ||
2084 | (defun bbdb-timestamp (_record) | |
2085 | "" | |
2086 | (unless (get 'bbdb-timestamp 'bbdb-obsolete) | |
2087 | (put 'bbdb-timestamp 'bbdb-obsolete t) | |
2088 | (message "Function `bbdb-timestamp' is obsolete. Remove it from any hooks.") | |
2089 | (sit-for 2))) | |
2090 | (make-obsolete 'bbdb-timestamp nil "2017-08-09") | |
2091 | ||
2092 | (defun bbdb-creation-date (_record) | |
2093 | "" | |
2094 | (unless (get 'bbdb-creation-date 'bbdb-obsolete) | |
2095 | (put 'bbdb-creation-date 'bbdb-obsolete t) | |
2096 | (message "Function `bbdb-creation-date' is obsolete. Remove it from any hooks.") | |
2097 | (sit-for 2))) | |
2098 | (make-obsolete 'bbdb-creation-date nil "2017-08-09") | |
2099 | ||
2100 | ;; Copied from org-id.el | |
2101 | (defun bbdb-uuid () | |
2102 | "Return string with random (version 4) UUID." | |
2103 | (let ((rnd (md5 (format "%s%s%s%s%s%s%s" | |
2104 | (random) | |
2105 | (current-time) | |
2106 | (user-uid) | |
2107 | (emacs-pid) | |
2108 | (user-full-name) | |
2109 | user-mail-address | |
2110 | (recent-keys))))) | |
2111 | (format "%s-%s-4%s-%s%s-%s" | |
2112 | (substring rnd 0 8) | |
2113 | (substring rnd 8 12) | |
2114 | (substring rnd 13 16) | |
2115 | (format "%x" | |
2116 | (logior | |
2117 | #b10000000 | |
2118 | (logand | |
2119 | #b10111111 | |
2120 | (string-to-number | |
2121 | (substring rnd 16 18) 16)))) | |
2122 | (substring rnd 18 20) | |
2123 | (substring rnd 20 32)))) | |
2124 | ||
2125 | (defun bbdb-multiple-buffers-default () | |
2126 | "Default function for guessing a name for new *BBDB* buffers. | |
2127 | May be used as value of variable `bbdb-multiple-buffers'." | |
2128 | (save-current-buffer | |
2129 | (cond ((memq major-mode '(vm-mode vm-summary-mode vm-presentation-mode | |
2130 | vm-virtual-mode)) | |
2131 | (vm-select-folder-buffer) | |
2132 | (buffer-name)) | |
2133 | ((memq major-mode '(gnus-summary-mode gnus-group-mode)) | |
2134 | (set-buffer gnus-article-buffer) | |
2135 | (buffer-name)) | |
2136 | ((memq major-mode '(mail-mode vm-mail-mode message-mode)) | |
2137 | "message composition")))) | |
2138 | ||
2139 | (defsubst bbdb-add-job (spec record string) | |
2140 | "Internal function: Evaluate SPEC for RECORD and STRING. | |
2141 | If SPEC is a function call it with args RECORD and STRING. Return value. | |
2142 | If SPEC is a regexp, return 'query unless SPEC matches STRING. | |
2143 | Otherwise return SPEC. | |
2144 | Used with variable `bbdb-add-name' and friends." | |
2145 | (cond ((functionp spec) | |
2146 | (funcall spec record string)) | |
2147 | ((stringp spec) | |
2148 | (unless (string-match spec string) 'query)) ; be least aggressive | |
2149 | (spec))) | |
2150 | ||
2151 | (defsubst bbdb-eval-spec (spec prompt) | |
2152 | "Internal function: Evaluate SPEC using PROMPT. | |
2153 | Return t if either SPEC equals t, or SPEC equals 'query and `bbdb-silent' | |
2154 | is non-nil or `y-or-no-p' returns t using PROMPT. | |
2155 | Used with return values of `bbdb-add-job'." | |
2156 | (or (eq spec t) | |
2157 | (and (eq spec 'query) | |
2158 | (or bbdb-silent (y-or-n-p prompt))))) | |
2159 | ||
2160 | (defun bbdb-clean-address-components (components) | |
2161 | "Clean mail address COMPONENTS. | |
2162 | COMPONENTS is a list (FULL-NAME CANONICAL-ADDRESS) as returned | |
2163 | by `mail-extract-address-components'. | |
2164 | Pass FULL-NAME through `bbdb-message-clean-name-function' | |
2165 | and CANONICAL-ADDRESS through `bbdb-canonicalize-mail-function'." | |
2166 | (list (if (car components) | |
2167 | (if bbdb-message-clean-name-function | |
2168 | (funcall bbdb-message-clean-name-function (car components)) | |
2169 | (car components))) | |
2170 | (if (cadr components) | |
2171 | (if bbdb-canonicalize-mail-function | |
2172 | (funcall bbdb-canonicalize-mail-function (cadr components)) | |
2173 | ;; Minimalistic clean-up | |
2174 | (bbdb-string-trim (cadr components)))))) | |
2175 | ||
2176 | (defun bbdb-extract-address-components (address &optional all) | |
2177 | "Given an RFC-822 address ADDRESS, extract full name and canonical address. | |
2178 | This function behaves like `mail-extract-address-components', but it passes | |
2179 | its return value through `bbdb-clean-address-components'. | |
2180 | See also `bbdb-decompose-bbdb-address'." | |
2181 | (if all | |
2182 | (mapcar 'bbdb-clean-address-components | |
2183 | (mail-extract-address-components address t)) | |
2184 | (bbdb-clean-address-components (mail-extract-address-components address)))) | |
2185 | ||
2186 | ;; Inspired by `gnus-extract-address-components' from gnus-utils. | |
2187 | (defun bbdb-decompose-bbdb-address (mail) | |
2188 | "Given an RFC-822 address MAIL, extract full name and canonical address. | |
2189 | In general, this function behaves like the more sophisticated function | |
2190 | `mail-extract-address-components'. Yet for an address `<Joe_Smith@foo.com>' | |
2191 | lacking a real name the latter function returns the name \"Joe Smith\". | |
2192 | This is useful when analyzing the headers of email messages we receive | |
2193 | from the outside world. Yet when analyzing the mail addresses stored | |
2194 | in BBDB, this pollutes the mail-aka space. So we define here | |
2195 | an intentionally much simpler function for decomposing the names | |
2196 | and canonical addresses in the mail field of BBDB records." | |
2197 | (let (name address) | |
2198 | ;; First find the address - the thing with the @ in it. | |
2199 | (cond (;; Check `<foo@bar>' first in order to handle the quite common | |
2200 | ;; form `"abc@xyz" <foo@bar>' (i.e. `@' as part of a comment) | |
2201 | ;; correctly. | |
2202 | (string-match "<\\([^@ \t<>]+[!@][^@ \t<>]+\\)>" mail) | |
2203 | (setq address (match-string 1 mail))) | |
2204 | ((string-match "\\b[^@ \t<>]+[!@][^@ \t<>]+\\b" mail) | |
2205 | (setq address (match-string 0 mail)))) | |
2206 | ;; Then check whether the `name <address>' format is used. | |
2207 | (and address | |
2208 | ;; Linear white space is not required. | |
2209 | (string-match (concat "[ \t]*<" (regexp-quote address) ">") mail) | |
2210 | (setq name (substring mail 0 (match-beginning 0))) | |
2211 | ;; Strip any quotes mail the name. | |
2212 | (string-match "^\".*\"$" name) | |
2213 | (setq name (substring name 1 (1- (match-end 0))))) | |
2214 | ;; If not, then check whether the `address (name)' format is used. | |
2215 | (or name | |
2216 | (and (string-match "(\\([^)]+\\))" mail) | |
2217 | (setq name (match-string 1 mail)))) | |
2218 | (list (if (equal name "") nil name) (or address mail)))) | |
2219 | ||
2220 | ;;; Massage of mail addresses | |
2221 | ||
2222 | (defcustom bbdb-canonical-hosts | |
2223 | ;; Example | |
2224 | (regexp-opt '("cs.cmu.edu" "ri.cmu.edu")) | |
2225 | "Regexp matching the canonical part of the domain part of a mail address. | |
2226 | If the domain part of a mail address matches this regexp, the domain | |
2227 | is replaced by the substring that actually matched this address. | |
2228 | ||
2229 | Used by `bbdb-canonicalize-mail-1'. See also `bbdb-ignore-redundant-mails'." | |
2230 | :group 'bbdb-mua | |
2231 | :type '(regexp :tag "Regexp matching sites")) | |
2232 | ||
2233 | (defun bbdb-canonicalize-mail-1 (address) | |
2234 | "Example of `bbdb-canonicalize-mail-function'. | |
2235 | However, this function is too specific to be useful for the general user. | |
2236 | Take it as a source of inspiration for what can be done." | |
2237 | (setq address (bbdb-string-trim address)) | |
2238 | (cond | |
2239 | ;; Rewrite mail-drop hosts. | |
2240 | ;; RW: The following is now also handled by `bbdb-ignore-redundant-mails' | |
2241 | ((string-match | |
2242 | (concat "\\`\\([^@%!]+@\\).*\\.\\(" bbdb-canonical-hosts "\\)\\'") | |
2243 | address) | |
2244 | (concat (match-string 1 address) (match-string 2 address))) | |
2245 | ;; | |
2246 | ;; Here at Lucid, our workstation names sometimes get into our mail | |
2247 | ;; addresses in the form "jwz%thalidomide@lucid.com" (instead of simply | |
2248 | ;; "jwz@lucid.com"). This removes the workstation name. | |
2249 | ((string-match "\\`\\([^@%!]+\\)%[^@%!.]+@\\(lucid\\.com\\)\\'" address) | |
2250 | (concat (match-string 1 address) "@" (match-string 2 address))) | |
2251 | ;; | |
2252 | ;; Another way that our local mailer is misconfigured: sometimes addresses | |
2253 | ;; which should look like "user@some.outside.host" end up looking like | |
2254 | ;; "user%some.outside.host" or even "user%some.outside.host@lucid.com" | |
2255 | ;; instead. This rule rewrites it into the original form. | |
2256 | ((string-match "\\`\\([^@%]+\\)%\\([^@%!]+\\)\\(@lucid\\.com\\)?\\'" address) | |
2257 | (concat (match-string 1 address) "@" (match-string 2 address))) | |
2258 | ;; | |
2259 | ;; Sometimes I see addresses like "foobar.com!user@foobar.com". | |
2260 | ;; That's totally redundant, so this rewrites it as "user@foobar.com". | |
2261 | ((string-match "\\`\\([^@%!]+\\)!\\([^@%!]+[@%]\\1\\)\\'" address) | |
2262 | (match-string 2 address)) | |
2263 | ;; | |
2264 | ;; Sometimes I see addresses like "foobar.com!user". Turn it around. | |
2265 | ((string-match "\\`\\([^@%!.]+\\.[^@%!]+\\)!\\([^@%]+\\)\\'" address) | |
2266 | (concat (match-string 2 address) "@" (match-string 1 address))) | |
2267 | ;; | |
2268 | ;; The mailer at hplb.hpl.hp.com tends to puke all over addresses which | |
2269 | ;; pass through mailing lists which are maintained there: it turns normal | |
2270 | ;; addresses like "user@foo.com" into "user%foo.com@hplb.hpl.hp.com". | |
2271 | ;; This reverses it. (I actually could have combined this rule with | |
2272 | ;; the similar lucid.com rule above, but then the regexp would have been | |
2273 | ;; more than 80 characters long...) | |
2274 | ((string-match "\\`\\([^@!]+\\)%\\([^@%!]+\\)@hplb\\.hpl\\.hp\\.com\\'" | |
2275 | address) | |
2276 | (concat (match-string 1 address) "@" (match-string 2 address))) | |
2277 | ;; | |
2278 | ;; Another local mail-configuration botch: sometimes mail shows up | |
2279 | ;; with addresses like "user@workstation", where "workstation" is a | |
2280 | ;; local machine name. That should really be "user" or "user@netscape.com". | |
2281 | ;; (I'm told this one is due to a bug in SunOS 4.1.1 sendmail.) | |
2282 | ((string-match "\\`\\([^@%!]+\\)[@%][^@%!.]+\\'" address) | |
2283 | (match-string 1 address)) | |
2284 | ;; | |
2285 | ;; Sometimes I see addresses like "foo%somewhere%uunet.uu.net@somewhere.else". | |
2286 | ;; This is silly, because I know that I can send mail to uunet directly. | |
2287 | ((string-match ".%uunet\\.uu\\.net@[^@%!]+\\'" address) | |
2288 | (concat (substring address 0 (+ (match-beginning 0) 1)) "@UUNET.UU.NET")) | |
2289 | ;; | |
2290 | ;; Otherwise, leave it as it is. | |
2291 | (t address))) | |
2292 | ||
2293 | (defun bbdb-message-clean-name-default (name) | |
2294 | "Default function for `bbdb-message-clean-name-function'. | |
2295 | This strips garbage from the user full NAME string." | |
2296 | ;; Remove leading non-alpha chars | |
2297 | (if (string-match "\\`[^[:alpha:]]+" name) | |
2298 | (setq name (substring name (match-end 0)))) | |
2299 | ||
2300 | (if (string-match "^\\([^@]+\\)@" name) | |
2301 | ;; The name is really a mail address and we use the part preceeding "@". | |
2302 | ;; Everything following "@" is ignored. | |
2303 | (setq name (match-string 1 name))) | |
2304 | ||
2305 | ;; Replace "firstname.surname" by "firstname surname". | |
2306 | ;; Do not replace ". " with " " because that could be an initial. | |
2307 | (setq name (replace-regexp-in-string "\\.\\([^ ]\\)" " \\1" name)) | |
2308 | ||
2309 | ;; Replace tabs, spaces, and underscores with a single space. | |
2310 | (setq name (replace-regexp-in-string "[ \t\n_]+" " " name)) | |
2311 | ||
2312 | ;; Remove trailing comments separated by "(" or " [-#]" | |
2313 | ;; This does not work all the time because some of our friends in | |
2314 | ;; northern europe have brackets in their names... | |
2315 | (if (string-match "[^ \t]\\([ \t]*\\((\\| [-#]\\)\\)" name) | |
2316 | (setq name (substring name 0 (match-beginning 1)))) | |
2317 | ||
2318 | ;; Remove phone extensions (like "x1234" and "ext. 1234") | |
2319 | (let ((case-fold-search t)) | |
2320 | (setq name (replace-regexp-in-string | |
2321 | "\\W+\\(x\\|ext\\.?\\)\\W*[-0-9]+" "" name))) | |
2322 | ||
2323 | ;; Remove trailing non-alpha chars | |
2324 | (if (string-match "[^[:alpha:]]+\\'" name) | |
2325 | (setq name (substring name 0 (match-beginning 0)))) | |
2326 | ||
2327 | ;; Remove text properties | |
2328 | (substring-no-properties name)) | |
2329 | ||
2330 | ;; BBDB data structure | |
2331 | (defmacro bbdb-defstruct (name &rest elts) | |
2332 | "Define two functions to operate on vector NAME for each symbol ELT in ELTS. | |
2333 | The function bbdb-NAME-ELT returns the element ELT in vector NAME. | |
2334 | The function bbdb-NAME-set-ELT sets ELT. | |
2335 | Also define a constant bbdb-NAME-length that holds the number of ELTS | |
2336 | in vector NAME." | |
2337 | (declare (indent 1)) | |
2338 | (let* ((count 0) | |
2339 | (sname (symbol-name name)) | |
2340 | (uname (upcase sname)) | |
2341 | (cname (concat "bbdb-" sname "-")) | |
2342 | body) | |
2343 | (dolist (elt elts) | |
2344 | (let* ((selt (symbol-name elt)) | |
2345 | (setname (intern (concat cname "set-" selt)))) | |
2346 | (push (list 'defsubst (intern (concat cname selt)) `(,name) | |
2347 | (format "For BBDB %s read element %i `%s'." | |
2348 | uname count selt) | |
2349 | ;; Use `elt' instead of `aref' so that these functions | |
2350 | ;; also work for the `bbdb-record-type' pseudo-code. | |
2351 | `(elt ,name ,count)) body) | |
2352 | (push (list 'defsubst setname `(,name value) | |
2353 | (format "For BBDB %s set element %i `%s' to VALUE. \ | |
2354 | Return VALUE. | |
2355 | Do not call this function directly. Call instead `bbdb-record-set-field' | |
2356 | which ensures the integrity of the database. Also, this makes your code | |
2357 | more robust with respect to possible future changes of BBDB's innermost | |
2358 | internals." | |
2359 | uname count selt) | |
2360 | `(aset ,name ,count value)) body)) | |
2361 | (setq count (1+ count))) | |
2362 | (push (list 'defconst (intern (concat cname "length")) count | |
2363 | (concat "Length of BBDB `" sname "'.")) body) | |
2364 | (cons 'progn body))) | |
2365 | ||
2366 | ;; Define RECORD: | |
2367 | (bbdb-defstruct record | |
2368 | firstname lastname affix aka organization phone address mail xfields | |
2369 | uuid creation-date timestamp cache) | |
2370 | ||
2371 | ;; Define PHONE: | |
2372 | (bbdb-defstruct phone | |
2373 | label area exchange suffix extension) | |
2374 | ||
2375 | ;; Define ADDRESS: | |
2376 | (bbdb-defstruct address | |
2377 | label streets city state postcode country) | |
2378 | ||
2379 | ;; Define record CACHE: | |
2380 | ;; - fl-name (first and last name of the person referred to by the record), | |
2381 | ;; - lf-name (last and first name of the person referred to by the record), | |
2382 | ;; - mail-aka (list of names associated with mail addresses) | |
2383 | ;; - mail-canon (list of canonical mail addresses) | |
2384 | ;; - sortkey (the concatenation of the elements used for sorting the record), | |
2385 | ;; - marker (position of beginning of record in `bbdb-file') | |
2386 | (bbdb-defstruct cache | |
2387 | fl-name lf-name mail-aka mail-canon sortkey marker) | |
2388 | ||
2389 | (defsubst bbdb-record-mail-aka (record) | |
2390 | "Record cache function: Return mail-aka for RECORD." | |
2391 | (bbdb-cache-mail-aka (bbdb-record-cache record))) | |
2392 | ||
2393 | (defsubst bbdb-record-mail-canon (record) | |
2394 | "Record cache function: Return mail-canon for RECORD." | |
2395 | (bbdb-cache-mail-canon (bbdb-record-cache record))) | |
2396 | ||
2397 | (defun bbdb-empty-record () | |
2398 | "Return a new empty record structure with a cache. | |
2399 | It is the caller's responsibility to make the new record known to BBDB." | |
2400 | (let ((record (make-vector bbdb-record-length nil))) | |
2401 | (bbdb-record-set-cache record (make-vector bbdb-cache-length nil)) | |
2402 | record)) | |
2403 | ||
2404 | ;; `bbdb-hashtable' associates with each KEY a list of matching records. | |
2405 | ;; KEY includes fl-name, lf-name, organizations, AKAs and email addresses. | |
2406 | ;; When loading the database the hash table is initialized by calling | |
2407 | ;; `bbdb-hash-record' for each record. This function is also called | |
2408 | ;; when new records are added to the database. | |
2409 | ;; `bbdb-delete-record-internal' with arg REMHASH non-nil removes a record | |
2410 | ;; from the hash table (besides deleting the record from the database). | |
2411 | ;; When an existing record is modified, the code that modifies the record | |
2412 | ;; needs to update the hash table, too. This includes removing the outdated | |
2413 | ;; associations between KEYs and record as well as adding the new associations. | |
2414 | ;; This is one reason to modify records by calling `bbdb-record-set-field' | |
2415 | ;; which properly updates the hash table. | |
2416 | ;; The hash table can be accessed via `bbdb-gethash' | |
2417 | ;; and via functions like `completing-read'. | |
2418 | ||
2419 | (defun bbdb-puthash (key record) | |
2420 | "Associate RECORD with KEY in `bbdb-hashtable'. | |
2421 | KEY must be a string or nil. Empty strings and nil are ignored." | |
2422 | (if (and key (not (string= "" key))) ; do not hash empty strings | |
2423 | (let* ((key (downcase key)) | |
2424 | (records (gethash key bbdb-hashtable))) | |
2425 | (puthash key (if records (bbdb-pushnewq record records) | |
2426 | (list record)) | |
2427 | bbdb-hashtable)))) | |
2428 | ||
2429 | (defun bbdb-gethash (key &optional predicate) | |
2430 | "Return list of records associated with KEY in `bbdb-hashtable'. | |
2431 | KEY must be a string or nil. Empty strings and nil are ignored. | |
2432 | PREDICATE may take the same values as `bbdb-completion-list'." | |
2433 | (when (and key (not (string= "" key))) | |
2434 | (let* ((key (downcase key)) | |
2435 | (all-records (gethash key bbdb-hashtable)) | |
2436 | records) | |
2437 | (if (or (not predicate) (eq t predicate)) | |
2438 | all-records | |
2439 | (dolist (record all-records) | |
2440 | (if (catch 'bbdb-hash-ok | |
2441 | (bbdb-hash-p key record predicate)) | |
2442 | (push record records))) | |
2443 | records)))) | |
2444 | ||
2445 | (defun bbdb-hash-p (key record predicate) | |
2446 | "Throw `bbdb-hash-ok' non-nil if KEY matches RECORD acording to PREDICATE. | |
2447 | PREDICATE may take the same values as the elements of `bbdb-completion-list'." | |
2448 | (if (and (memq 'fl-name predicate) | |
2449 | (bbdb-string= key (or (bbdb-record-name record) ""))) | |
2450 | (throw 'bbdb-hash-ok 'fl-name)) | |
2451 | (if (and (memq 'lf-name predicate) | |
2452 | (bbdb-string= key (or (bbdb-record-name-lf record) ""))) | |
2453 | (throw 'bbdb-hash-ok 'lf-name)) | |
2454 | (if (memq 'organization predicate) | |
2455 | (mapc (lambda (organization) (if (bbdb-string= key organization) | |
2456 | (throw 'bbdb-hash-ok 'organization))) | |
2457 | (bbdb-record-organization record))) | |
2458 | (if (memq 'aka predicate) | |
2459 | (mapc (lambda (aka) (if (bbdb-string= key aka) | |
2460 | (throw 'bbdb-hash-ok 'aka))) | |
2461 | (bbdb-record-field record 'aka-all))) | |
2462 | (if (and (memq 'primary predicate) | |
2463 | (bbdb-string= key (car (bbdb-record-mail-canon record)))) | |
2464 | (throw 'bbdb-hash-ok 'primary)) | |
2465 | (if (memq 'mail predicate) | |
2466 | (mapc (lambda (mail) (if (bbdb-string= key mail) | |
2467 | (throw 'bbdb-hash-ok 'mail))) | |
2468 | (bbdb-record-mail-canon record))) | |
2469 | nil) | |
2470 | ||
2471 | (defun bbdb-remhash (key record) | |
2472 | "Remove RECORD from list of records associated with KEY. | |
2473 | KEY must be a string or nil. Empty strings and nil are ignored." | |
2474 | (if (and key (not (string= "" key))) | |
2475 | (let* ((key (downcase key)) | |
2476 | (records (gethash key bbdb-hashtable))) | |
2477 | (when records | |
2478 | (setq records (delq record records)) | |
2479 | (if records | |
2480 | (puthash key records bbdb-hashtable) | |
2481 | (remhash key bbdb-hashtable)))))) | |
2482 | ||
2483 | (defun bbdb-hash-record (record) | |
2484 | "Insert RECORD in `bbdb-hashtable'. | |
2485 | This performs all initializations required for a new record. | |
2486 | Do not call this for existing records that require updating." | |
2487 | (bbdb-puthash (bbdb-record-name record) record) | |
2488 | (bbdb-puthash (bbdb-record-name-lf record) record) | |
2489 | (dolist (organization (bbdb-record-organization record)) | |
2490 | (bbdb-puthash organization record)) | |
2491 | (dolist (aka (bbdb-record-aka record)) | |
2492 | (bbdb-puthash aka record)) | |
2493 | (bbdb-puthash-mail record) | |
2494 | (puthash (bbdb-record-uuid record) record bbdb-uuid-table)) | |
2495 | ||
2496 | (defun bbdb-puthash-mail (record) | |
2497 | "For RECORD put mail into `bbdb-hashtable'." | |
2498 | (let (mail-aka mail-canon address) | |
2499 | (dolist (mail (bbdb-record-mail record)) | |
2500 | (setq address (bbdb-decompose-bbdb-address mail)) | |
2501 | (when (car address) | |
2502 | (push (car address) mail-aka) | |
2503 | (bbdb-puthash (car address) record)) | |
2504 | (push (nth 1 address) mail-canon) | |
2505 | (bbdb-puthash (nth 1 address) record)) | |
2506 | (bbdb-cache-set-mail-aka (bbdb-record-cache record) | |
2507 | (nreverse mail-aka)) | |
2508 | (bbdb-cache-set-mail-canon (bbdb-record-cache record) | |
2509 | (nreverse mail-canon)))) | |
2510 | ||
2511 | (defun bbdb-hash-update (record old new) | |
2512 | "Update hash for RECORD. Remove OLD, insert NEW. | |
2513 | Both OLD and NEW are lists of values." | |
2514 | (dolist (elt old) | |
2515 | (bbdb-remhash elt record)) | |
2516 | (dolist (elt new) | |
2517 | (bbdb-puthash elt record))) | |
2518 | ||
2519 | (defun bbdb-check-name (first last &optional record) | |
2520 | "Check whether the name FIRST LAST is a valid name. | |
2521 | This throws an error if the name is already used by another record | |
2522 | and `bbdb-allow-duplicates' is nil. If RECORD is non-nil, FIRST and LAST | |
2523 | may correspond to RECORD without raising an error." | |
2524 | ;; Are there more useful checks for names beyond checking for duplicates? | |
2525 | (unless bbdb-allow-duplicates | |
2526 | (let* ((name (bbdb-concat 'name-first-last first last)) | |
2527 | (records (bbdb-gethash name '(fl-name lf-name aka)))) | |
2528 | (if (or (and (not record) records) | |
2529 | (remq record records)) | |
2530 | (error "%s is already in BBDB" name))))) | |
2531 | ||
2532 | (defun bbdb-record-name (record) | |
2533 | "Record cache function: Return the full name FIRST_LAST of RECORD. | |
2534 | Return empty string if both the first and last name are nil. | |
2535 | If the name is not available in the name cache, the name cache value | |
2536 | is generated and stored." | |
2537 | (or (bbdb-cache-fl-name (bbdb-record-cache record)) | |
2538 | ;; Build the name cache for a record. | |
2539 | (bbdb-record-set-name record t t))) | |
2540 | ||
2541 | (defun bbdb-record-name-lf (record) | |
2542 | "Record cache function: Return the full name LAST_FIRST of RECORD. | |
2543 | If the name is not available in the name cache, the name cache value | |
2544 | is generated and stored." | |
2545 | (or (bbdb-cache-lf-name (bbdb-record-cache record)) | |
2546 | ;; Build the name cache for a record. | |
2547 | (progn (bbdb-record-set-name record t t) | |
2548 | (bbdb-cache-lf-name (bbdb-record-cache record))))) | |
2549 | ||
2550 | (defun bbdb-record-set-name (record first last) | |
2551 | "Record cache function: For RECORD set full name based on FIRST and LAST. | |
2552 | If FIRST or LAST are t use respective existing entries of RECORD. | |
2553 | Set full name in cache and hash. Return first-last name." | |
2554 | (let* ((cache (bbdb-record-cache record)) | |
2555 | (fl-name (bbdb-cache-fl-name cache)) | |
2556 | (lf-name (bbdb-cache-lf-name cache))) | |
2557 | (if fl-name (bbdb-remhash fl-name record)) | |
2558 | (if lf-name (bbdb-remhash lf-name record))) | |
2559 | (if (eq t first) | |
2560 | (setq first (bbdb-record-firstname record)) | |
2561 | (bbdb-record-set-firstname record first)) | |
2562 | (if (eq t last) | |
2563 | (setq last (bbdb-record-lastname record)) | |
2564 | (bbdb-record-set-lastname record last)) | |
2565 | (let ((fl-name (bbdb-concat 'name-first-last first last)) | |
2566 | (lf-name (bbdb-concat 'name-last-first last first)) | |
2567 | (cache (bbdb-record-cache record))) | |
2568 | ;; Set cache of RECORD | |
2569 | (bbdb-cache-set-fl-name cache fl-name) | |
2570 | (bbdb-cache-set-lf-name cache lf-name) | |
2571 | ;; Set hash. For convenience, the hash contains the full name | |
2572 | ;; as first-last and last-fist. | |
2573 | (bbdb-puthash fl-name record) | |
2574 | (bbdb-puthash lf-name record) | |
2575 | fl-name)) | |
2576 | ||
2577 | (defun bbdb-record-sortkey (record) | |
2578 | "Record cache function: Return the sortkey for RECORD. | |
2579 | Set and store it if necessary." | |
2580 | (or (bbdb-cache-sortkey (bbdb-record-cache record)) | |
2581 | (bbdb-record-set-sortkey record))) | |
2582 | ||
2583 | (defun bbdb-record-set-sortkey (record) | |
2584 | "Record cache function: Set and return RECORD's sortkey." | |
2585 | (bbdb-cache-set-sortkey | |
2586 | (bbdb-record-cache record) | |
2587 | (downcase | |
2588 | (bbdb-concat "" (bbdb-record-lastname record) | |
2589 | (bbdb-record-firstname record) | |
2590 | (bbdb-record-organization record))))) | |
2591 | ||
2592 | (defsubst bbdb-record-marker (record) | |
2593 | "Record cache function: Return the marker for RECORD." | |
2594 | (bbdb-cache-marker (bbdb-record-cache record))) | |
2595 | ||
2596 | (defsubst bbdb-record-set-marker (record marker) | |
2597 | "Record cache function: Set and return RECORD's MARKER." | |
2598 | (bbdb-cache-set-marker (bbdb-record-cache record) marker)) | |
2599 | ||
2600 | (defsubst bbdb-record-xfield (record label) | |
2601 | "For RECORD return value of xfield LABEL. | |
2602 | Return nil if xfield LABEL is undefined." | |
2603 | (cdr (assq label (bbdb-record-xfields record)))) | |
2604 | ||
2605 | ;; The values of xfields are normally strings. The following function | |
2606 | ;; comes handy if we want to treat these values as symbols. | |
2607 | (defun bbdb-record-xfield-intern (record label) | |
2608 | "For RECORD return interned value of xfield LABEL. | |
2609 | Return nil if xfield LABEL does not exist." | |
2610 | (let ((value (bbdb-record-xfield record label))) | |
2611 | ;; If VALUE is not a string, return whatever it is. | |
2612 | (if (stringp value) (intern value) value))) | |
2613 | ||
2614 | (defun bbdb-record-xfield-string (record label) | |
2615 | "For RECORD return value of xfield LABEL as string. | |
2616 | Return nil if xfield LABEL does not exist." | |
2617 | (let ((value (bbdb-record-xfield record label))) | |
2618 | (if (string-or-null-p value) | |
2619 | value | |
2620 | (let ((print-escape-newlines t)) | |
2621 | (prin1-to-string value))))) | |
2622 | ||
2623 | (defsubst bbdb-record-xfield-split (record label) | |
2624 | "For RECORD return value of xfield LABEL split as a list. | |
2625 | Splitting is based on `bbdb-separator-alist'." | |
2626 | (let ((val (bbdb-record-xfield record label))) | |
2627 | (cond ((stringp val) (bbdb-split label val)) | |
2628 | (val (error "Cannot split `%s'" val))))) | |
2629 | ||
2630 | (defun bbdb-record-set-xfield (record label value) | |
2631 | "For RECORD set xfield LABEL to VALUE. | |
2632 | If VALUE is nil or an empty string, remove xfield LABEL from RECORD. | |
2633 | Return VALUE." | |
2634 | ;; In principle we can also have xfield labels `name' or `mail', etc. | |
2635 | ;; Yet the actual code would get rather confused. So we throw an error. | |
2636 | (if (memq label '(name firstname lastname affix organization | |
2637 | mail aka phone address xfields)) | |
2638 | (error "xfield label `%s' illegal" label)) | |
2639 | (if (eq label 'mail-alias) | |
2640 | (setq bbdb-mail-aliases-need-rebuilt 'edit)) | |
2641 | (if (stringp value) (setq value (bbdb-string-trim value t))) | |
2642 | (let ((old-xfield (assq label (bbdb-record-xfields record)))) | |
2643 | ;; Do nothing if both OLD-XFIELD and VALUE are nil. | |
2644 | (cond ((and old-xfield value) ; update | |
2645 | (setcdr old-xfield value)) | |
2646 | (value ; new xfield | |
2647 | (bbdb-pushnewq label bbdb-xfield-label-list) | |
2648 | (bbdb-record-set-xfields record | |
2649 | (append (bbdb-record-xfields record) | |
2650 | (list (cons label value))))) | |
2651 | (old-xfield ; remove | |
2652 | (bbdb-record-set-xfields record | |
2653 | (delq old-xfield | |
2654 | (bbdb-record-xfields record)))))) | |
2655 | value) | |
2656 | ||
2657 | (defun bbdb-check-type (object type &optional abort extended) | |
2658 | "Return non-nil if OBJECT is of type TYPE. | |
2659 | TYPE is a pseudo-code as in `bbdb-record-type'. | |
2660 | If ABORT is non-nil, abort with error message if type checking fails. | |
2661 | If EXTENDED is non-nil, consider extended atomic types which may include | |
2662 | symbols, numbers, markers, and strings." | |
2663 | (let (tmp) | |
2664 | ;; Add more predicates? Compare info node `(elisp.info)Type Predicates'. | |
2665 | (or (cond ((eq type 'symbol) (symbolp object)) | |
2666 | ((eq type 'integer) (integerp object)) | |
2667 | ((eq type 'marker) (markerp object)) | |
2668 | ((eq type 'number) (numberp object)) | |
2669 | ((eq type 'string) (stringp object)) | |
2670 | ((eq type 'sexp) t) ; matches always | |
2671 | ((eq type 'face) (facep object)) | |
2672 | ;; not quite a type | |
2673 | ((eq type 'bound) (and (symbolp object) (boundp object))) | |
2674 | ((eq type 'function) (functionp object)) | |
2675 | ((eq type 'vector) (vectorp object)) | |
2676 | ((and extended | |
2677 | (cond ((symbolp type) (setq tmp (eq type object)) t) | |
2678 | ((or (numberp type) (markerp type)) | |
2679 | (setq tmp (= type object)) t) | |
2680 | ((stringp type) | |
2681 | (setq tmp (and (stringp object) | |
2682 | (string= type object))) t))) | |
2683 | tmp) | |
2684 | ((not (consp type)) | |
2685 | (error "Atomic type `%s' undefined" type)) | |
2686 | ((eq 'const (setq tmp (car type))) | |
2687 | (equal (nth 1 type) object)) | |
2688 | ((eq tmp 'cons) | |
2689 | (and (consp object) | |
2690 | (bbdb-check-type (car object) (nth 1 type) abort extended) | |
2691 | (bbdb-check-type (cdr object) (nth 2 type) abort extended))) | |
2692 | ((eq tmp 'list) | |
2693 | (and (listp object) | |
2694 | (eq (length (cdr type)) (length object)) | |
2695 | (let ((type (cdr type)) (object object) (ok t)) | |
2696 | (while type | |
2697 | (unless (bbdb-check-type (pop object) (pop type) | |
2698 | abort extended) | |
2699 | (setq ok nil type nil))) | |
2700 | ok))) | |
2701 | ((eq tmp 'repeat) | |
2702 | (and (listp object) | |
2703 | (let ((tp (nth 1 type)) (object object) (ok t)) | |
2704 | (while object | |
2705 | (unless (bbdb-check-type (pop object) tp abort extended) | |
2706 | (setq ok nil object nil))) | |
2707 | ok))) | |
2708 | ((eq tmp 'vector) | |
2709 | (and (vectorp object) | |
2710 | (let* ((i 0) (type (cdr type)) | |
2711 | (ok (eq (length object) (length type)))) | |
2712 | (when ok | |
2713 | (while type | |
2714 | (if (bbdb-check-type (aref object i) (pop type) | |
2715 | abort extended) | |
2716 | (setq i (1+ i)) | |
2717 | (setq ok nil type nil))) | |
2718 | ok)))) | |
2719 | ((eq tmp 'or) ; like customize `choice' type | |
2720 | (let ((type (cdr type)) ok) | |
2721 | (while type | |
2722 | (if (bbdb-check-type object (pop type) nil extended) | |
2723 | (setq ok t type nil))) | |
2724 | ok)) | |
2725 | ;; User-defined predicate | |
2726 | ((eq tmp 'user-p) (funcall (nth 1 type) object)) | |
2727 | (t (error "Compound type `%s' undefined" tmp))) | |
2728 | (and abort | |
2729 | (error "Type mismatch: expect %s, got `%s'" type object))))) | |
2730 | ||
2731 | ;; (bbdb-check-type 'bar 'symbol) | |
2732 | ;; (bbdb-check-type 'bar 'bar) | |
2733 | ;; (bbdb-check-type "foo" 'symbol t) | |
2734 | ;; (bbdb-check-type "foo" '(or symbol string)) | |
2735 | ;; (bbdb-check-type nil '(const nil)) | |
2736 | ;; (bbdb-check-type '(bar . "foo") '(cons symbol string)) | |
2737 | ;; (bbdb-check-type '(bar "foo") '(list symbol string)) | |
2738 | ;; (bbdb-check-type '("bar" "foo") '(repeat string)) | |
2739 | ;; (bbdb-check-type (vector 'bar "foo") '(vector symbol string)) | |
2740 | ;; (bbdb-check-type (vector 'bar "foo") 'vector) | |
2741 | ;; (bbdb-check-type '(bar (bar . "foo")) '(list symbol (cons symbol string))) | |
2742 | ;; (bbdb-check-type '("aa" . "bb") '(or (const nil) (cons string string)) t) | |
2743 | ;; (bbdb-check-type nil '(or nil (cons string string)) t t) | |
2744 | ;; (bbdb-check-type "foo" '(user-p (lambda (a) (stringp a)))) | |
2745 | ;; (bbdb-check-type 'set 'function) | |
2746 | ||
2747 | (defun bbdb-record-field (record field) | |
2748 | "For RECORD return the value of FIELD. | |
2749 | ||
2750 | FIELD may take the following values | |
2751 | firstname Return the first name of RECORD | |
2752 | lastname Return the last name of RECORD | |
2753 | name Return the full name of RECORD (first name first) | |
2754 | name-lf Return the full name of RECORD (last name first) | |
2755 | affix Return the list of affixes | |
2756 | organization Return the list of organizations | |
2757 | aka Return the list of AKAs | |
2758 | aka-all Return the list of AKAs plus mail-akas. | |
2759 | mail Return the list of email addresses | |
2760 | mail-aka Return the list of name parts in mail addresses | |
2761 | mail-canon Return the list of canonical mail addresses. | |
2762 | phone Return the list of phone numbers | |
2763 | address Return the list of addresses | |
2764 | uuid Return the uuid of RECORD | |
2765 | creation-date Return the creation-date | |
2766 | timestamp Return the timestamp | |
2767 | xfields Return the list of all xfields | |
2768 | ||
2769 | Any other symbol is interpreted as the label for an xfield. | |
2770 | Then return the value of this xfield. | |
2771 | ||
2772 | See also `bbdb-record-set-field'." | |
2773 | (cond ((eq field 'firstname) (bbdb-record-firstname record)) | |
2774 | ((eq field 'lastname) (bbdb-record-lastname record)) | |
2775 | ((eq field 'name) (bbdb-record-name record)) | |
2776 | ((eq field 'name-lf) (bbdb-record-name-lf record)) | |
2777 | ((eq field 'affix) (bbdb-record-affix record)) | |
2778 | ((eq field 'organization) (bbdb-record-organization record)) | |
2779 | ((eq field 'mail) (bbdb-record-mail record)) | |
2780 | ((eq field 'mail-canon) (bbdb-record-mail-canon record)) ; derived (cached) field | |
2781 | ((eq field 'mail-aka) (bbdb-record-mail-aka record)) ; derived (cached) field | |
2782 | ((eq field 'aka) (bbdb-record-aka record)) | |
2783 | ((eq field 'aka-all) (append (bbdb-record-aka record) ; derived field | |
2784 | (bbdb-record-mail-aka record))) | |
2785 | ((eq field 'phone) (bbdb-record-phone record)) | |
2786 | ((eq field 'address) (bbdb-record-address record)) | |
2787 | ((eq field 'uuid) (bbdb-record-uuid record)) | |
2788 | ((eq field 'creation-date) (bbdb-record-creation-date record)) | |
2789 | ((eq field 'timestamp) (bbdb-record-timestamp record)) | |
2790 | ;; Return all xfields | |
2791 | ((eq field 'xfields) (bbdb-record-xfields record)) | |
2792 | ;; Return xfield FIELD (e.g., `notes') or nil if FIELD is not defined. | |
2793 | ((symbolp field) (bbdb-record-xfield record field)) | |
2794 | (t (error "Unknown field type `%s'" field)))) | |
2795 | (define-obsolete-function-alias 'bbdb-record-get-field 'bbdb-record-field "3.0") | |
2796 | ||
2797 | (defun bbdb-record-set-field (record field value &optional merge check) | |
2798 | "For RECORD set FIELD to VALUE. Return VALUE. | |
2799 | If MERGE is non-nil, merge VALUE with the current value of FIELD. | |
2800 | If CHECK is non-nil, check syntactically whether FIELD may take VALUE. | |
2801 | This function also updates the hash table. However, it does not update | |
2802 | RECORD in the database. Use `bbdb-change-record' for that. | |
2803 | ||
2804 | FIELD may take the following values | |
2805 | firstname VALUE is the first name of RECORD | |
2806 | lastname VALUE is the last name of RECORD | |
2807 | name VALUE is the full name of RECORD either as one string | |
2808 | or as a cons pair (FIRST . LAST) | |
2809 | affix VALUE is the list of affixes | |
2810 | organization VALUE is the list of organizations | |
2811 | aka VALUE is the list of AKAs | |
2812 | mail VALUE is the list of email addresses | |
2813 | phone VALUE is the list of phone numbers | |
2814 | address VALUE is the list of addresses | |
2815 | uuid VALUE is the uuid of RECORD | |
2816 | creation-date VALUE is the creation-date | |
2817 | timestamp VALUE is the timestamp | |
2818 | xfields VALUE is the list of all xfields | |
2819 | ||
2820 | Any other symbol is interpreted as the label for an xfield. | |
2821 | Then VALUE is the value of this xfield. | |
2822 | ||
2823 | See also `bbdb-record-field'." | |
2824 | (bbdb-editable) | |
2825 | (if (memq field '(name-lf mail-aka mail-canon aka-all)) | |
2826 | (error "`%s' is not allowed as the name of a field" field)) | |
2827 | (let ((record-type (cdr bbdb-record-type))) | |
2828 | (cond ((eq field 'firstname) ; First name | |
2829 | (if merge (error "Does not merge names")) | |
2830 | (if check (bbdb-check-type value (bbdb-record-firstname record-type) t)) | |
2831 | (bbdb-check-name value (bbdb-record-lastname record) record) | |
2832 | (bbdb-record-set-name record value t)) | |
2833 | ||
2834 | ;; Last name | |
2835 | ((eq field 'lastname) | |
2836 | (if merge (error "Does not merge names")) | |
2837 | (if check (bbdb-check-type value (bbdb-record-lastname record-type) t)) | |
2838 | (bbdb-check-name (bbdb-record-firstname record) value record) | |
2839 | (bbdb-record-set-name record t value)) | |
2840 | ||
2841 | ;; Name | |
2842 | ((eq field 'name) | |
2843 | (if merge (error "Does not merge names")) | |
2844 | (if (stringp value) | |
2845 | (setq value (bbdb-divide-name value)) | |
2846 | (if check (bbdb-check-type value '(cons string string) t))) | |
2847 | (let ((fn (car value)) (ln (cdr value))) | |
2848 | (bbdb-check-name fn ln record) | |
2849 | (bbdb-record-set-name record fn ln))) | |
2850 | ||
2851 | ;; Affix | |
2852 | ((eq field 'affix) | |
2853 | (if merge (setq value (bbdb-merge-lists (bbdb-record-affix record) | |
2854 | value 'bbdb-string=))) | |
2855 | (if check (bbdb-check-type value (bbdb-record-affix record-type) t)) | |
2856 | (setq value (bbdb-list-strings value)) | |
2857 | (bbdb-record-set-affix record value)) | |
2858 | ||
2859 | ;; Organization | |
2860 | ((eq field 'organization) | |
2861 | (if merge (setq value (bbdb-merge-lists (bbdb-record-organization record) | |
2862 | value 'bbdb-string=))) | |
2863 | (if check (bbdb-check-type value (bbdb-record-organization record-type) t)) | |
2864 | (setq value (bbdb-list-strings value)) | |
2865 | (bbdb-hash-update record (bbdb-record-organization record) value) | |
2866 | (dolist (organization value) | |
2867 | (bbdb-pushnew organization bbdb-organization-list)) | |
2868 | (bbdb-record-set-organization record value)) | |
2869 | ||
2870 | ;; AKA | |
2871 | ((eq field 'aka) | |
2872 | (if merge (setq value (bbdb-merge-lists (bbdb-record-aka record) | |
2873 | value 'bbdb-string=))) | |
2874 | (if check (bbdb-check-type value (bbdb-record-aka record-type) t)) | |
2875 | (setq value (bbdb-list-strings value)) | |
2876 | (unless bbdb-allow-duplicates | |
2877 | (dolist (aka value) | |
2878 | (let ((old (remq record (bbdb-gethash aka '(fl-name lf-name aka))))) | |
2879 | (if old (error "Alternate name address \"%s\" is used by \"%s\"" | |
2880 | aka (mapconcat 'bbdb-record-name old ", ")))))) | |
2881 | (bbdb-hash-update record (bbdb-record-aka record) value) | |
2882 | (bbdb-record-set-aka record value)) | |
2883 | ||
2884 | ||
2885 | ((eq field 'mail) | |
2886 | (if merge (setq value (bbdb-merge-lists (bbdb-record-mail record) | |
2887 | value 'bbdb-string=))) | |
2888 | (if check (bbdb-check-type value (bbdb-record-mail record-type) t)) | |
2889 | (setq value (bbdb-list-strings value)) | |
2890 | (unless bbdb-allow-duplicates | |
2891 | (dolist (mail value) | |
2892 | (let ((old (remq record (bbdb-gethash mail '(mail))))) | |
2893 | (if old (error "Mail address \"%s\" is used by \"%s\"" | |
2894 | mail (mapconcat 'bbdb-record-name old ", ")))))) | |
2895 | (dolist (aka (bbdb-record-mail-aka record)) | |
2896 | (bbdb-remhash aka record)) | |
2897 | (dolist (mail (bbdb-record-mail-canon record)) | |
2898 | (bbdb-remhash mail record)) | |
2899 | (bbdb-record-set-mail record value) | |
2900 | (bbdb-puthash-mail record)) | |
2901 | ||
2902 | ;; Phone | |
2903 | ((eq field 'phone) | |
2904 | (if merge (setq value (bbdb-merge-lists (bbdb-record-phone record) | |
2905 | value 'equal))) | |
2906 | (if check (bbdb-check-type value (bbdb-record-phone record-type) t)) | |
2907 | (dolist (phone value) | |
2908 | (bbdb-pushnew (bbdb-phone-label phone) bbdb-phone-label-list)) | |
2909 | (bbdb-record-set-phone record value)) | |
2910 | ||
2911 | ;; Address | |
2912 | ((eq field 'address) | |
2913 | (if merge (setq value (bbdb-merge-lists (bbdb-record-address record) | |
2914 | value 'equal))) | |
2915 | (if check (bbdb-check-type value (bbdb-record-address record-type) t)) | |
2916 | (dolist (address value) | |
2917 | (bbdb-pushnew (bbdb-address-label address) bbdb-address-label-list) | |
2918 | (mapc (lambda (street) (bbdb-pushnewt street bbdb-street-list)) | |
2919 | (bbdb-address-streets address)) | |
2920 | (bbdb-pushnewt (bbdb-address-city address) bbdb-city-list) | |
2921 | (bbdb-pushnewt (bbdb-address-state address) bbdb-state-list) | |
2922 | (bbdb-pushnewt (bbdb-address-postcode address) bbdb-postcode-list) | |
2923 | (bbdb-pushnewt (bbdb-address-country address) bbdb-country-list)) | |
2924 | (bbdb-record-set-address record value)) | |
2925 | ||
2926 | ;; uuid | |
2927 | ((eq field 'uuid) | |
2928 | ;; MERGE not meaningful | |
2929 | (if check (bbdb-check-type value (bbdb-record-uuid record-type) t)) | |
2930 | (let ((old-uuid (bbdb-record-uuid record))) | |
2931 | (unless (string= old-uuid value) | |
2932 | (remhash old-uuid bbdb-uuid-table) | |
2933 | (bbdb-record-set-uuid record value) | |
2934 | (puthash value record bbdb-uuid-table)))) | |
2935 | ||
2936 | ;; creation-date | |
2937 | ((eq field 'creation-date) | |
2938 | ;; MERGE not meaningful | |
2939 | (if check (bbdb-check-type value (bbdb-record-creation-date record-type) t)) | |
2940 | (bbdb-record-set-creation-date record value)) | |
2941 | ||
2942 | ;; timestamp | |
2943 | ((eq field 'timestamp) | |
2944 | ;; MERGE not meaningful | |
2945 | (if check (bbdb-check-type value (bbdb-record-timestamp record-type) t)) | |
2946 | (bbdb-record-set-timestamp record value)) | |
2947 | ||
2948 | ;; all xfields | |
2949 | ((eq field 'xfields) | |
2950 | (if merge | |
2951 | (let ((xfields (bbdb-record-xfields record)) | |
2952 | xfield) | |
2953 | (dolist (nv value) | |
2954 | (if (setq xfield (assq (car nv) xfields)) | |
2955 | (setcdr xfield (bbdb-merge-xfield | |
2956 | (car nv) (cdr xfield) (cdr nv))) | |
2957 | (setq xfields (append xfields (list nv))))) | |
2958 | (setq value xfields))) | |
2959 | (if check (bbdb-check-type value (bbdb-record-xfields record-type) t)) | |
2960 | (let (new-xfields) | |
2961 | (dolist (xfield value) | |
2962 | ;; Ignore junk | |
2963 | (when (and (cdr xfield) (not (equal "" (cdr xfield)))) | |
2964 | (push xfield new-xfields) | |
2965 | (bbdb-pushnewq (car xfield) bbdb-xfield-label-list))) | |
2966 | (bbdb-record-set-xfields record (nreverse new-xfields)))) | |
2967 | ||
2968 | ;; Single xfield | |
2969 | ((symbolp field) | |
2970 | (if merge | |
2971 | (setq value (bbdb-merge-xfield field (bbdb-record-xfield record field) | |
2972 | value))) | |
2973 | ;; The following test always succeeds | |
2974 | ;; (if check (bbdb-check-type value 'sexp t)) | |
2975 | ;; This removes xfield FIELD if its value is nil. | |
2976 | (bbdb-record-set-xfield record field value)) | |
2977 | ||
2978 | (t (error "Unknown field type `%s'" field))))) | |
2979 | ||
2980 | ;; Currently unused (but possible entry for `bbdb-merge-xfield-function-alist') | |
2981 | (defun bbdb-merge-concat (string1 string2 &optional separator) | |
2982 | "Return the concatenation of STRING1 and STRING2. | |
2983 | SEPARATOR defaults to \"\\n\"." | |
2984 | (concat string1 (or separator "\n") string2)) | |
2985 | ||
2986 | ;; Currently unused (but possible entry for `bbdb-merge-xfield-function-alist') | |
2987 | (defun bbdb-merge-concat-remove-duplicates (string1 string2) | |
2988 | "Concatenate STRING1 and STRING2, but remove duplicate lines." | |
2989 | (let ((lines (split-string string1 "\n"))) | |
2990 | (dolist (line (split-string string2 "\n")) | |
2991 | (bbdb-pushnew line lines)) | |
2992 | (bbdb-concat "\n" lines))) | |
2993 | ||
2994 | (defun bbdb-merge-string-least (string1 string2) | |
2995 | "Return the string out of STRING1 and STRING2 that is `string-lessp'." | |
2996 | (if (string-lessp string1 string2) | |
2997 | string1 | |
2998 | string2)) | |
2999 | ||
3000 | (defun bbdb-merge-string-most (string1 string2) | |
3001 | "Return the string out of STRING1 and STRING2 that is not `string-lessp'." | |
3002 | (if (string-lessp string1 string2) | |
3003 | string2 | |
3004 | string1)) | |
3005 | ||
3006 | (defun bbdb-merge-lists (l1 l2 cmp) | |
3007 | "Merge two lists L1 and L2 based on comparison CMP. | |
3008 | An element from L2 is added to L1 if CMP returns nil for all elements of L1. | |
3009 | If L1 or L2 are not lists, they are replaced by (list L1) and (list L2)." | |
3010 | (let (merge) | |
3011 | (unless (listp l1) (setq l1 (list l1))) | |
3012 | (dolist (e2 (if (listp l2) l2 (list l2))) | |
3013 | (let ((ll1 l1) e1 fail) | |
3014 | (while (setq e1 (pop ll1)) | |
3015 | (if (funcall cmp e1 e2) | |
3016 | (setq ll1 nil | |
3017 | fail t))) | |
3018 | (unless fail (push e2 merge)))) | |
3019 | (append l1 (nreverse merge)))) | |
3020 | ||
3021 | (defun bbdb-merge-xfield (label value1 value2) | |
3022 | "For LABEL merge VALUE1 with VALUE2. | |
3023 | If LABEL has an entry in `bbdb-merge-xfield-function-alist', use it. | |
3024 | If VALUE1 or VALUE2 is a substring of the other, return the longer one. | |
3025 | Otherwise use `bbdb-concat'. Return nil if we have nothing to merge." | |
3026 | (if (stringp value1) (setq value1 (bbdb-string-trim value1 t))) | |
3027 | (if (stringp value2) (setq value2 (bbdb-string-trim value2 t))) | |
3028 | (cond ((and value1 value2) | |
3029 | (let ((fun (cdr (assq label bbdb-merge-xfield-function-alist)))) | |
3030 | (cond (fun (funcall fun value1 value2)) | |
3031 | ((not (and (stringp value1) (stringp value2))) | |
3032 | (cons value1 value2)) ; concatenate lists | |
3033 | ((string-match (regexp-quote value1) value2) value2) | |
3034 | ((string-match (regexp-quote value2) value1) value1) | |
3035 | (t (bbdb-concat label value1 value2))))) | |
3036 | (value1) | |
3037 | (value2))) | |
3038 | ||
3039 | ;;; Parsing other things | |
3040 | ||
3041 | (defun bbdb-divide-name (string) | |
3042 | "Divide STRING into a first name and a last name. | |
3043 | Case is ignored. Return name as (FIRST . LAST). | |
3044 | LAST is always a string (possibly empty). FIRST may be nil." | |
3045 | (let ((case-fold-search t) | |
3046 | first suffix) | |
3047 | ;; Separate a suffix. | |
3048 | (if (string-match bbdb-lastname-suffix-re string) | |
3049 | (setq suffix (concat " " (match-string 1 string)) | |
3050 | string (substring string 0 (match-beginning 0)))) | |
3051 | (cond ((string-match "\\`\\(.+\\),[ \t\n]*\\(.+\\)\\'" string) | |
3052 | ;; If STRING contains a comma, this probably means that STRING | |
3053 | ;; is of the form "Last, First". | |
3054 | (setq first (match-string 2 string) | |
3055 | string (match-string 1 string))) | |
3056 | ((string-match bbdb-lastname-re string) | |
3057 | (setq first (and (not (zerop (match-beginning 0))) | |
3058 | (substring string 0 (match-beginning 0))) | |
3059 | string (match-string 1 string)))) | |
3060 | (cons (and first (bbdb-string-trim first)) | |
3061 | (bbdb-string-trim (concat string suffix))))) | |
3062 | ||
3063 | (defun bbdb-parse-postcode (string) | |
3064 | "Check whether STRING is a legal postcode. | |
3065 | Do this only if `bbdb-check-postcode' is non-nil." | |
3066 | (if bbdb-check-postcode | |
3067 | (let ((postcodes bbdb-legal-postcodes) re done) | |
3068 | (while (setq re (pop postcodes)) | |
3069 | (if (string-match re string) | |
3070 | (setq done t postcodes nil))) | |
3071 | (if done string | |
3072 | (error "not a valid postcode."))) | |
3073 | string)) | |
3074 | ||
3075 | (defun bbdb-phone-string (phone) | |
3076 | "Massage string PHONE into a standard format." | |
3077 | ;; Phone numbers should come in two forms: | |
3078 | (if (= 2 (length phone)) | |
3079 | ;; (1) ["where" "the number"] | |
3080 | (if (stringp (aref phone 1)) | |
3081 | (aref phone 1) | |
3082 | (error "Not a valid phone number: %s" (aref phone 1))) | |
3083 | ;; (2) ["where" 415 555 1212 99] | |
3084 | (unless (and (integerp (aref phone 2)) | |
3085 | (integerp (aref phone 3))) | |
3086 | (error "Not an NANP number: %s %s" (aref phone 2) (aref phone 3))) | |
3087 | (concat (if (/= 0 (bbdb-phone-area phone)) | |
3088 | (format "(%03d) " (bbdb-phone-area phone)) | |
3089 | "") | |
3090 | (if (/= 0 (bbdb-phone-exchange phone)) | |
3091 | (format "%03d-%04d" | |
3092 | (bbdb-phone-exchange phone) (bbdb-phone-suffix phone)) | |
3093 | "") | |
3094 | (if (and (bbdb-phone-extension phone) | |
3095 | (/= 0 (bbdb-phone-extension phone))) | |
3096 | (format " x%d" (bbdb-phone-extension phone)) | |
3097 | "")))) | |
3098 | ||
3099 | (defsubst bbdb-record-lessp (record1 record2) | |
3100 | (string< (bbdb-record-sortkey record1) | |
3101 | (bbdb-record-sortkey record2))) | |
3102 | ||
3103 | (defmacro bbdb-error-retry (&rest body) | |
3104 | "Repeatedly execute BODY ignoring errors till no error occurs." | |
3105 | `(catch '--bbdb-error-retry-- | |
3106 | (while t | |
3107 | (condition-case --c-- | |
3108 | (throw '--bbdb-error-retry-- (progn ,@body)) | |
3109 | (error (ding) | |
3110 | (message "Error: %s" (nth 1 --c--)) | |
3111 | (sit-for 2)))))) | |
3112 | ||
3113 | \f | |
3114 | ;;; Reading and Writing the BBDB | |
3115 | ||
3116 | (defun bbdb-buffer () | |
3117 | "Return buffer that visits the BBDB file `bbdb-file'. | |
3118 | Ensure that this buffer is in sync with `bbdb-file'. | |
3119 | Revert the buffer if necessary. | |
3120 | If `bbdb-file-remote' is non-nil and it is newer than `bbdb-file', | |
3121 | copy it to `bbdb-file'." | |
3122 | (unless (buffer-live-p bbdb-buffer) | |
3123 | (if (and bbdb-file-remote | |
3124 | (file-newer-than-file-p bbdb-file-remote bbdb-file)) | |
3125 | (copy-file bbdb-file-remote bbdb-file t t)) | |
3126 | ||
3127 | (with-current-buffer (setq bbdb-buffer (find-file-noselect bbdb-file)) | |
3128 | ||
3129 | ;; Check whether auto-save file is newer than `bbdb-file' | |
3130 | ;; Do this only when reading `bbdb-file'. | |
3131 | (let ((auto-save-file (make-auto-save-file-name))) | |
3132 | (when (and bbdb-check-auto-save-file | |
3133 | (file-newer-than-file-p auto-save-file buffer-file-name)) | |
3134 | (recover-file buffer-file-name) ; this queries | |
3135 | (bury-buffer) ; `recover-file' selects `bbdb-buffer' | |
3136 | (auto-save-mode 1) ; turn auto-save back on | |
3137 | ;; Delete auto-save file even if the user rejected to recover it, | |
3138 | ;; so we do not keep asking. | |
3139 | (condition-case nil | |
3140 | (delete-file auto-save-file) | |
3141 | (file-error nil)))))) | |
3142 | ||
3143 | ;; Make sure `bbdb-buffer' is not out of sync with disk. | |
3144 | (with-current-buffer bbdb-buffer | |
3145 | (cond ((verify-visited-file-modtime)) | |
3146 | ((bbdb-revert-buffer)) | |
3147 | ;; This is the case where `bbdb-file' has changed; the buffer | |
3148 | ;; has changed as well; and the user has answered "no" to the | |
3149 | ;; "flush your changes and revert" question. The only other | |
3150 | ;; alternative is to save the file right now. If they answer | |
3151 | ;; no to the following question, they will be asked the | |
3152 | ;; preceeding question again and again some large (but finite) | |
3153 | ;; number of times. `bbdb-buffer' is called a lot, you see... | |
3154 | ((buffer-modified-p) | |
3155 | ;; this queries | |
3156 | (bbdb-save t t)) | |
3157 | (t ; Buffer and file are inconsistent, but we let them stay that way | |
3158 | (message "Continuing with inconsistent BBDB buffers"))) | |
3159 | ||
3160 | ;; `bbdb-revert-buffer' kills all local variables. | |
3161 | (unless (assq 'bbdb-records (buffer-local-variables)) | |
3162 | ;; We are reading / reverting `bbdb-buffer'. | |
3163 | (set (make-local-variable 'revert-buffer-function) | |
3164 | 'bbdb-revert-buffer) | |
3165 | ||
3166 | (setq buffer-file-coding-system bbdb-file-coding-system | |
3167 | buffer-read-only bbdb-read-only | |
3168 | bbdb-mail-aliases-need-rebuilt 'parse | |
3169 | bbdb-changed-records nil) | |
3170 | ||
3171 | ;; `bbdb-before-save-hook' and `bbdb-after-save-hook' are user variables. | |
3172 | ;; To avoid confusion, we hide the hook functions `bbdb-before-save' | |
3173 | ;; and `bbdb-after-save' from the user as these are essential for BBDB. | |
3174 | (dolist (hook (cons 'bbdb-before-save bbdb-before-save-hook)) | |
3175 | (add-hook 'before-save-hook hook nil t)) | |
3176 | (dolist (hook (cons 'bbdb-after-save bbdb-after-save-hook)) | |
3177 | (add-hook 'after-save-hook hook nil t)) | |
3178 | ||
3179 | (clrhash bbdb-hashtable) | |
3180 | (clrhash bbdb-uuid-table) | |
3181 | ||
3182 | (if (/= (point-min) (point-max)) | |
3183 | (bbdb-parse-records) ; normal case: nonempty db | |
3184 | ;; Empty db: the following does not require `insert-before-markers' | |
3185 | ;; because there are no db-markers in this buffer. | |
3186 | (insert (format (concat ";; -*- mode: Emacs-Lisp; coding: %s; -*-" | |
3187 | "\n;;; file-format: %d\n") | |
3188 | bbdb-file-coding-system bbdb-file-format)) | |
3189 | ;; We pretend that `bbdb-buffer' is still unmodified, | |
3190 | ;; so that we will (auto-)save it only if we also add records to it. | |
3191 | (set-buffer-modified-p nil) | |
3192 | (setq bbdb-end-marker (point-marker) | |
3193 | ;; Setting `bbdb-records' makes it buffer-local, | |
3194 | ;; so that we can use it as a test whether we have | |
3195 | ;; initialized BBDB. | |
3196 | bbdb-records nil)) | |
3197 | ||
3198 | (run-hooks 'bbdb-after-read-db-hook))) | |
3199 | ||
3200 | ;; return `bbdb-buffer' | |
3201 | bbdb-buffer) | |
3202 | ||
3203 | (defmacro bbdb-with-db-buffer (&rest body) | |
3204 | "Execute the forms in BODY with `bbdb-buffer' temporarily current. | |
3205 | If `bbdb-debug' was non-nil at compile-time, and `bbdb-buffer' is visible | |
3206 | in a window, temporarilly switch to that window. So when we come out, | |
3207 | that window has been scrolled to the record we have just modified." | |
3208 | (declare (indent 0)) | |
3209 | (if bbdb-debug | |
3210 | `(let* ((buffer (bbdb-buffer)) | |
3211 | (window (get-buffer-window buffer))) | |
3212 | (if window | |
3213 | (with-selected-window window | |
3214 | ,@body) | |
3215 | (with-current-buffer buffer | |
3216 | ,@body))) | |
3217 | `(with-current-buffer (bbdb-buffer) | |
3218 | ,@body))) | |
3219 | ||
3220 | (defun bbdb-editable () | |
3221 | "Ensure that BBDB is editable, otherwise throw an error. | |
3222 | If BBDB is out of sync try to revert. | |
3223 | BBDB is not editable if it is read-only." | |
3224 | (if bbdb-read-only (error "BBDB is read-only")) | |
3225 | (let ((buffer (bbdb-buffer))) ; this reverts if necessary / possible | |
3226 | ;; Is the following possible? Superfluous tests do not hurt. | |
3227 | ;; It is relevant only for editing commands in a BBDB buffer, | |
3228 | ;; but not for MUA-related editing functions. | |
3229 | (if (and (eq major-mode 'bbdb-mode) | |
3230 | bbdb-records | |
3231 | (not (memq (caar bbdb-records) | |
3232 | (with-current-buffer buffer bbdb-records)))) | |
3233 | (error "BBDB is out of sync"))) | |
3234 | t) | |
3235 | ||
3236 | ;;;###autoload | |
3237 | (defsubst bbdb-records () | |
3238 | "Return a list of all BBDB records; read in and parse the db if necessary. | |
3239 | This function also notices if the corresponding file on disk has been modified." | |
3240 | (with-current-buffer (bbdb-buffer) | |
3241 | bbdb-records)) | |
3242 | ||
3243 | (defun bbdb-revert-buffer (&optional ignore-auto noconfirm) | |
3244 | "The `revert-buffer-function' for `bbdb-buffer' visiting `bbdb-file'. | |
3245 | IGNORE-AUTO and NOCONFIRM have same meaning as in `revert-buffer'. | |
3246 | See also variable `bbdb-auto-revert'. | |
3247 | Return t if the reversion was successful (or not needed). | |
3248 | Return nil otherwise." | |
3249 | (interactive (list (not current-prefix-arg))) ; as in `revert-buffer' | |
3250 | (unless (buffer-live-p bbdb-buffer) | |
3251 | (error "No live BBDB buffer to revert")) | |
3252 | (with-current-buffer bbdb-buffer | |
3253 | (cond ((not buffer-file-number) | |
3254 | ;; We have not yet created `bbdb-file' | |
3255 | (when (or noconfirm | |
3256 | (yes-or-no-p "Flush your changes? ")) | |
3257 | (erase-buffer) | |
3258 | (kill-all-local-variables) ; clear database | |
3259 | (bbdb-buffer) ; re-initialize | |
3260 | (set-buffer-modified-p nil) | |
3261 | (bbdb-undisplay-records t))) | |
3262 | ;; If nothing has changed do nothing, return t. | |
3263 | ((and (verify-visited-file-modtime) | |
3264 | (not (buffer-modified-p)))) | |
3265 | ((or (and (not (verify-visited-file-modtime bbdb-buffer)) | |
3266 | ;; File changed on disk | |
3267 | (or noconfirm | |
3268 | (and bbdb-auto-revert | |
3269 | (not (buffer-modified-p))) | |
3270 | (yes-or-no-p | |
3271 | (if (buffer-modified-p) | |
3272 | "BBDB changed on disk; flush your changes and revert? " | |
3273 | "BBDB changed on disk; revert? ")))) | |
3274 | (and (verify-visited-file-modtime bbdb-buffer) | |
3275 | ;; File not changed on disk, but buffer modified | |
3276 | (buffer-modified-p) | |
3277 | (or noconfirm | |
3278 | (yes-or-no-p "Flush your changes and revert BBDB? ")))) | |
3279 | (unless (file-exists-p bbdb-file) | |
3280 | (error "BBDB: file %s no longer exists" bbdb-file)) | |
3281 | (kill-all-local-variables) ; clear database | |
3282 | ;; `revert-buffer-function' has the permanent-local property | |
3283 | ;; So to avoid looping, we need to bind it to nil explicitly. | |
3284 | (let (revert-buffer-function) | |
3285 | (revert-buffer ignore-auto t)) | |
3286 | (bbdb-buffer) ; re-initialize | |
3287 | (bbdb-undisplay-records t) | |
3288 | t)))) ; return nil if the user rejected to revert | |
3289 | ||
3290 | (defun bbdb-goto-first-record () | |
3291 | "Go to where first record begins, Move to end of file if no records." | |
3292 | (goto-char (point-min)) | |
3293 | (if (search-forward "\n[" nil 'move) | |
3294 | (forward-char -1))) | |
3295 | ||
3296 | (defun bbdb-parse-records () | |
3297 | "Parse BBDB records and initialize various internal variables. | |
3298 | If `bbdb-file' uses an outdated format, migrate to `bbdb-file-format'." | |
3299 | (save-excursion | |
3300 | (save-restriction | |
3301 | (widen) | |
3302 | (bbdb-goto-first-record) | |
3303 | (let* ((file (abbreviate-file-name buffer-file-name)) | |
3304 | (file-format (save-excursion | |
3305 | (if (re-search-backward | |
3306 | "^;+[ \t]*file-\\(format\\|version\\):[ \t]*\\([0-9]+\\)[ \t]*$" nil t) | |
3307 | (string-to-number (match-string 2)) | |
3308 | ;; No file-format line. | |
3309 | (error "BBDB corrupted: no file-format line")))) | |
3310 | (migrate (< file-format bbdb-file-format)) | |
3311 | records) | |
3312 | (if (> file-format bbdb-file-format) | |
3313 | (error "%s understands file format %s but not %s." | |
3314 | (bbdb-version) bbdb-file-format file-format)) | |
3315 | ||
3316 | (if (and migrate | |
3317 | (not (yes-or-no-p | |
3318 | (format (concat "Migrate `%s' to BBDB file format %s " | |
3319 | "(back-up recommended)? ") | |
3320 | file bbdb-file-format)))) | |
3321 | (progn | |
3322 | (message "Abort loading %s" file) | |
3323 | (sleep-for 2) | |
3324 | (setq bbdb-records nil | |
3325 | ;; Avoid unexpected surprises | |
3326 | buffer-read-only t) | |
3327 | 'abort) | |
3328 | ||
3329 | (or (eobp) (looking-at "\\[") | |
3330 | (error "BBDB corrupted: no following bracket")) | |
3331 | ||
3332 | (unless bbdb-silent (message "Parsing BBDB file `%s'..." file)) | |
3333 | ||
3334 | ;; narrow the buffer to skip over the rubbish before the first record. | |
3335 | (narrow-to-region (point) (point-max)) | |
3336 | (let ((modp (buffer-modified-p)) | |
3337 | ;; Make sure those parens get cleaned up. | |
3338 | ;; This code had better stay simple! | |
3339 | (inhibit-quit t) | |
3340 | (buffer-undo-list t) | |
3341 | buffer-read-only) | |
3342 | (goto-char (point-min)) (insert "(\n") | |
3343 | (goto-char (point-max)) (insert "\n)") | |
3344 | (goto-char (point-min)) | |
3345 | (unwind-protect | |
3346 | (setq records (read (current-buffer))) | |
3347 | (goto-char (point-min)) (delete-char 2) | |
3348 | (goto-char (point-max)) (delete-char -2) | |
3349 | (set-buffer-modified-p modp))) | |
3350 | (widen) | |
3351 | ||
3352 | ;; Migrate if `bbdb-file' is outdated. | |
3353 | (if migrate (setq records (bbdb-migrate records file-format))) | |
3354 | ||
3355 | ;; We could first set `bbdb-phone-label-list' and | |
3356 | ;; `bbdb-address-label-list' to their customized values. Bother? | |
3357 | (setq bbdb-records records | |
3358 | bbdb-xfield-label-list nil | |
3359 | bbdb-organization-list nil | |
3360 | bbdb-street-list nil | |
3361 | bbdb-city-list nil | |
3362 | bbdb-state-list nil | |
3363 | bbdb-postcode-list nil | |
3364 | bbdb-country-list nil) | |
3365 | ||
3366 | (bbdb-goto-first-record) | |
3367 | (dolist (record records) | |
3368 | ;; We assume that the markers for each record need to go at each | |
3369 | ;; newline. If this is not the case, things can go *very* wrong. | |
3370 | (bbdb-debug | |
3371 | (unless (looking-at "\\[") | |
3372 | (error "BBDB corrupted: junk between records at %s" (point)))) | |
3373 | ||
3374 | (bbdb-cache-set-marker | |
3375 | (bbdb-record-set-cache record (make-vector bbdb-cache-length nil)) | |
3376 | (point-marker)) | |
3377 | (forward-line 1) | |
3378 | ||
3379 | ;; Every record must have a unique uuid in `bbdb-uuid-table'. | |
3380 | (if (gethash (bbdb-record-uuid record) bbdb-uuid-table) | |
3381 | ;; Is there a more useful action than throwing an error? | |
3382 | ;; We are just loading BBDB, so we are not yet ready | |
3383 | ;; for sophisticated solutions. | |
3384 | (error "Duplicate UUID %s" (bbdb-record-uuid record))) | |
3385 | ||
3386 | ;; Set the completion lists | |
3387 | (dolist (phone (bbdb-record-phone record)) | |
3388 | (bbdb-pushnew (bbdb-phone-label phone) bbdb-phone-label-list)) | |
3389 | (dolist (address (bbdb-record-address record)) | |
3390 | (bbdb-pushnew (bbdb-address-label address) bbdb-address-label-list) | |
3391 | (mapc (lambda (street) (bbdb-pushnewt street bbdb-street-list)) | |
3392 | (bbdb-address-streets address)) | |
3393 | (bbdb-pushnewt (bbdb-address-city address) bbdb-city-list) | |
3394 | (bbdb-pushnewt (bbdb-address-state address) bbdb-state-list) | |
3395 | (bbdb-pushnewt (bbdb-address-postcode address) bbdb-postcode-list) | |
3396 | (bbdb-pushnewt (bbdb-address-country address) bbdb-country-list)) | |
3397 | (dolist (xfield (bbdb-record-xfields record)) | |
3398 | (bbdb-pushnewq (car xfield) bbdb-xfield-label-list)) | |
3399 | (dolist (organization (bbdb-record-organization record)) | |
3400 | (bbdb-pushnew organization bbdb-organization-list)) | |
3401 | ||
3402 | (let ((name (bbdb-concat 'name-first-last | |
3403 | (bbdb-record-firstname record) | |
3404 | (bbdb-record-lastname record)))) | |
3405 | (when (and (not bbdb-allow-duplicates) | |
3406 | (bbdb-gethash name '(fl-name aka))) | |
3407 | ;; This does not check for duplicate mail fields. | |
3408 | ;; Yet under normal circumstances, this should really | |
3409 | ;; not be necessary each time BBDB is loaded as BBDB checks | |
3410 | ;; whether creating a new record or modifying an existing one | |
3411 | ;; results in duplicates. | |
3412 | ;; Alternatively, you can use `bbdb-search-duplicates'. | |
3413 | (message "Duplicate BBDB record encountered: %s" name) | |
3414 | (sit-for 1))) | |
3415 | ||
3416 | ;; If `bbdb-allow-duplicates' is non-nil, we allow that two records | |
3417 | ;; (with different uuids) refer to the same person (same name etc.). | |
3418 | ;; Such duplicate records are always hashed. | |
3419 | ;; Otherwise, an unhashed record would not be available for things | |
3420 | ;; like completion (and we would not know which record to keeep | |
3421 | ;; and which one to hide). We trust the user she knows what | |
3422 | ;; she wants if she keeps duplicate records in the database though | |
3423 | ;; `bbdb-allow-duplicates' is nil. | |
3424 | (bbdb-hash-record record)) | |
3425 | ||
3426 | ;; Note that `bbdb-xfield-label-list' serves two purposes: | |
3427 | ;; - check whether an xfield is new to BBDB | |
3428 | ;; - list of known xfields for minibuffer completion | |
3429 | ;; Only in the latter case, we might want to exclude | |
3430 | ;; those xfields that are handled automatically. | |
3431 | ;; So the following is not a satisfactory solution. | |
3432 | ||
3433 | ;; (dolist (label (bbdb-layout-get-option 'multi-line 'omit)) | |
3434 | ;; (setq bbdb-xfield-label-list (delq label bbdb-xfield-label-list))) | |
3435 | ||
3436 | ;; `bbdb-end-marker' allows to put comments at the end of `bbdb-file' | |
3437 | ;; that are ignored. | |
3438 | (setq bbdb-end-marker (point-marker)) | |
3439 | ||
3440 | (when migrate | |
3441 | (dolist (record bbdb-records) | |
3442 | (bbdb-overwrite-record-internal record)) | |
3443 | ;; update file format | |
3444 | (goto-char (point-min)) | |
3445 | (if (re-search-forward (format "^;;; file-\\(version\\|format\\): %d$" | |
3446 | file-format) | |
3447 | nil t) | |
3448 | (replace-match (format ";;; file-format: %d" bbdb-file-format)))) | |
3449 | ||
3450 | (unless bbdb-silent (message "Parsing BBDB file `%s'...done" file)) | |
3451 | bbdb-records))))) | |
3452 | ||
3453 | (defun bbdb-before-save () | |
3454 | "Run before saving `bbdb-file' as buffer-local part of `before-save-hook'." | |
3455 | (when (and bbdb-file-remote | |
3456 | (or bbdb-file-remote-save-always | |
3457 | (y-or-n-p (format "Save the remote BBDB file %s too? " | |
3458 | bbdb-file-remote)))) | |
3459 | ;; Write the current buffer `bbdb-file' into `bbdb-file-remote'. | |
3460 | (let ((coding-system-for-write bbdb-file-coding-system)) | |
3461 | (write-region (point-min) (point-max) bbdb-file-remote)))) | |
3462 | ||
3463 | (defun bbdb-after-save () | |
3464 | "Run after saving `bbdb-file' as buffer-local part of `after-save-hook'." | |
3465 | (setq bbdb-changed-records nil) | |
3466 | (dolist (buffer (buffer-list)) | |
3467 | (with-current-buffer buffer | |
3468 | (if (eq major-mode 'bbdb-mode) | |
3469 | (set-buffer-modified-p nil))))) | |
3470 | ||
3471 | (defun bbdb-change-record (record &rest ignored) | |
3472 | "Update the database after a change of RECORD. | |
3473 | Return RECORD if RECORD got changed compared with the database, | |
3474 | return nil otherwise. | |
3475 | Hash RECORD if it is new. If RECORD is not new, it is the the caller's | |
3476 | responsibility to update the hashtables for RECORD. (Up-to-date hashtables are | |
3477 | ensured if the fields are modified by calling `bbdb-record-set-field'.) | |
3478 | Redisplay RECORD if it is not new. | |
3479 | ||
3480 | Args IGNORED are ignored and their use is discouraged. | |
3481 | They are present only for backward compatibility." | |
3482 | (when (and ignored (get 'bbdb-change-record 'bbdb-outdated)) | |
3483 | (put 'bbdb-change-record 'bbdb-outdated t) | |
3484 | (message "Outdated usage of `bbdb-change-record'") | |
3485 | (sit-for 2)) | |
3486 | ||
3487 | (if bbdb-read-only | |
3488 | (error "The Insidious Big Brother Database is read-only.")) | |
3489 | ;; The call of `bbdb-records' checks file synchronization. | |
3490 | ;; If RECORD refers to an existing record that has been changed, | |
3491 | ;; yet in the meanwhile we reverted the BBDB file, then RECORD | |
3492 | ;; no longer refers to a record in `bbdb-records'. RECORD will then | |
3493 | ;; be treated as new, when we try to merge it with the known record. | |
3494 | (let ((tail (memq record (bbdb-records)))) | |
3495 | (if tail ; RECORD is not new | |
3496 | ;; If the string we currently have for RECORD in `bbdb-buffer' | |
3497 | ;; is `equal' to the string we would write to `bbdb-buffer', | |
3498 | ;; we really did not change RECORD at all. So we don't update RECORD | |
3499 | ;; unless `bbdb-update-unchanged-records' tells us to do so anyway. | |
3500 | ;; Also, we only call `bbdb-change-hook' and `bbdb-after-change-hook' | |
3501 | ;; if RECORD got changed. | |
3502 | (when (or bbdb-update-unchanged-records | |
3503 | (not (string= (bbdb-with-db-buffer | |
3504 | (buffer-substring-no-properties | |
3505 | (bbdb-record-marker record) | |
3506 | (1- (if (cdr tail) | |
3507 | (bbdb-record-marker (cadr tail)) | |
3508 | bbdb-end-marker)))) | |
3509 | (let ((cache (bbdb-record-cache record)) | |
3510 | (inhibit-quit t)) | |
3511 | (bbdb-record-set-cache record nil) | |
3512 | (prog1 (bbdb-with-print-loadably | |
3513 | (prin1-to-string record)) | |
3514 | (bbdb-record-set-cache record cache)))))) | |
3515 | (bbdb-record-set-timestamp | |
3516 | record (format-time-string bbdb-time-stamp-format nil t)) | |
3517 | (run-hook-with-args 'bbdb-change-hook record) | |
3518 | (let ((sort (not (equal (bbdb-cache-sortkey (bbdb-record-cache record)) | |
3519 | (bbdb-record-set-sortkey record))))) | |
3520 | (if (not sort) ;; If we do not need to sort, overwrite RECORD. | |
3521 | (bbdb-overwrite-record-internal record) | |
3522 | ;; Since we need to sort, delete then insert RECORD. | |
3523 | ;; Do not mess with the hash tables here. | |
3524 | ;; We assume they got updated by the caller. | |
3525 | (bbdb-delete-record-internal record) | |
3526 | (bbdb-insert-record-internal record)) | |
3527 | (bbdb-pushnewq record bbdb-changed-records) | |
3528 | (run-hook-with-args 'bbdb-after-change-hook record) | |
3529 | (bbdb-redisplay-record-globally record sort)) | |
3530 | record) | |
3531 | ||
3532 | ;; Record is new and not yet in BBDB. | |
3533 | (unless (bbdb-record-cache record) | |
3534 | (bbdb-record-set-cache record (make-vector bbdb-cache-length nil))) | |
3535 | (unless (bbdb-record-uuid record) | |
3536 | (bbdb-record-set-uuid record (bbdb-uuid))) | |
3537 | (unless (bbdb-record-creation-date record) | |
3538 | (bbdb-record-set-creation-date | |
3539 | record (format-time-string bbdb-time-stamp-format nil t)) | |
3540 | (run-hook-with-args 'bbdb-create-hook record)) | |
3541 | ||
3542 | (let ((old-record (gethash (bbdb-record-uuid record) bbdb-uuid-table))) | |
3543 | (if old-record | |
3544 | ;; RECORD is really OLD-RECORD. Merge and return OLD-RECORD. | |
3545 | (if bbdb-merge-records-function | |
3546 | (funcall bbdb-merge-records-function record old-record) | |
3547 | (bbdb-merge-records record old-record)) | |
3548 | ||
3549 | ;; RECORD is really new. | |
3550 | (bbdb-record-set-timestamp | |
3551 | record (format-time-string bbdb-time-stamp-format nil t)) | |
3552 | (run-hook-with-args 'bbdb-change-hook record) | |
3553 | (bbdb-insert-record-internal record) | |
3554 | (bbdb-hash-record record) | |
3555 | (bbdb-pushnewq record bbdb-changed-records) | |
3556 | (run-hook-with-args 'bbdb-after-change-hook record) | |
3557 | record))))) | |
3558 | ||
3559 | (defun bbdb-delete-record-internal (record &optional completely) | |
3560 | "Delete RECORD in the database file. | |
3561 | With COMPLETELY non-nil, also undisplay RECORD and remove it | |
3562 | from the hash table." | |
3563 | (unless (bbdb-record-marker record) (error "BBDB: marker absent")) | |
3564 | (if completely (bbdb-redisplay-record-globally record nil t)) | |
3565 | (bbdb-with-db-buffer | |
3566 | (barf-if-buffer-read-only) | |
3567 | (let ((tail (memq record bbdb-records)) | |
3568 | (inhibit-quit t)) | |
3569 | (unless tail (error "BBDB record absent: %s" record)) | |
3570 | (delete-region (bbdb-record-marker record) | |
3571 | (if (cdr tail) | |
3572 | (bbdb-record-marker (car (cdr tail))) | |
3573 | bbdb-end-marker)) | |
3574 | (setq bbdb-records (delq record bbdb-records)) | |
3575 | (when completely | |
3576 | (bbdb-remhash (bbdb-record-name record) record) | |
3577 | (bbdb-remhash (bbdb-record-name-lf record) record) | |
3578 | (dolist (organization (bbdb-record-organization record)) | |
3579 | (bbdb-remhash organization record)) | |
3580 | (dolist (mail (bbdb-record-mail-canon record)) | |
3581 | (bbdb-remhash mail record)) | |
3582 | (dolist (aka (bbdb-record-field record 'aka-all)) | |
3583 | (bbdb-remhash aka record)))))) | |
3584 | ||
3585 | (defun bbdb-insert-record-internal (record) | |
3586 | "Insert RECORD into the database file. Return RECORD. | |
3587 | Do not call this function directly, call instead `bbdb-change-record' | |
3588 | that calls the hooks, too." | |
3589 | (unless (bbdb-record-marker record) | |
3590 | (bbdb-record-set-marker record (make-marker))) | |
3591 | (bbdb-with-db-buffer | |
3592 | (barf-if-buffer-read-only) | |
3593 | ;; splice record into `bbdb-records' | |
3594 | (bbdb-debug (if (memq record bbdb-records) | |
3595 | (error "BBDB record not unique: - %s" record))) | |
3596 | (if (or (not bbdb-records) ; first record in new database | |
3597 | (bbdb-record-lessp record (car bbdb-records))) | |
3598 | (push record bbdb-records) | |
3599 | (let ((records bbdb-records)) | |
3600 | (while (and (cdr records) | |
3601 | (bbdb-record-lessp (nth 1 records) record)) | |
3602 | (setq records (cdr records))) | |
3603 | (setcdr records (cons record (cdr records))))) | |
3604 | ||
3605 | (let ((next (car (cdr (memq record bbdb-records))))) | |
3606 | (goto-char (if next | |
3607 | (bbdb-record-marker next) | |
3608 | bbdb-end-marker))) | |
3609 | ;; Before writing the record, remove the cache (we do not want that | |
3610 | ;; written to the file.) After writing, put the cache back and update | |
3611 | ;; the cache's marker. | |
3612 | (let ((cache (bbdb-record-cache record)) | |
3613 | (point (point)) | |
3614 | (inhibit-quit t)) | |
3615 | (bbdb-debug | |
3616 | (if (= point (point-min)) | |
3617 | (error "Inserting at point-min (%s)" point)) | |
3618 | (if (and (/= point bbdb-end-marker) | |
3619 | (not (looking-at "^\\["))) | |
3620 | (error "Not inserting before a record (%s)" point))) | |
3621 | (bbdb-record-set-cache record nil) | |
3622 | (insert-before-markers | |
3623 | (bbdb-with-print-loadably (prin1-to-string record)) "\n") | |
3624 | (set-marker (bbdb-cache-marker cache) point) | |
3625 | (bbdb-record-set-cache record cache)) | |
3626 | record)) | |
3627 | ||
3628 | (defun bbdb-overwrite-record-internal (record) | |
3629 | "Overwrite RECORD in the database file. Return RECORD. | |
3630 | Do not call this function directly, call instead `bbdb-change-record' | |
3631 | that calls the hooks, too." | |
3632 | (bbdb-with-db-buffer | |
3633 | (barf-if-buffer-read-only) | |
3634 | (let* ((tail (memq record bbdb-records)) | |
3635 | (_ (unless tail (error "BBDB record absent: %s" record))) | |
3636 | (cache (bbdb-record-cache record)) | |
3637 | (inhibit-quit t)) | |
3638 | (bbdb-debug | |
3639 | (if (<= (bbdb-cache-marker cache) (point-min)) | |
3640 | (error "Cache marker is %s" (bbdb-cache-marker cache)))) | |
3641 | (goto-char (bbdb-cache-marker cache)) | |
3642 | (bbdb-debug | |
3643 | (if (and (/= (point) bbdb-end-marker) | |
3644 | (not (looking-at "\\["))) | |
3645 | (error "Not inserting before a record (%s)" (point)))) | |
3646 | ||
3647 | (bbdb-record-set-cache record nil) | |
3648 | (insert (bbdb-with-print-loadably (prin1-to-string record)) "\n") | |
3649 | (delete-region (point) | |
3650 | (if (cdr tail) | |
3651 | (bbdb-record-marker (car (cdr tail))) | |
3652 | bbdb-end-marker)) | |
3653 | (bbdb-record-set-cache record cache) | |
3654 | ||
3655 | (bbdb-debug | |
3656 | (if (<= (if (cdr tail) | |
3657 | (bbdb-record-marker (car (cdr tail))) | |
3658 | bbdb-end-marker) | |
3659 | (bbdb-record-marker record)) | |
3660 | (error "Overwrite failed"))) | |
3661 | ||
3662 | record))) | |
3663 | ||
3664 | ;; Record formatting: | |
3665 | ;; This does not insert anything into the *BBDB* buffer, | |
3666 | ;; which is handled in a second step by the display functions. | |
3667 | ||
3668 | (defun bbdb-layout-get-option (layout option) | |
3669 | "For LAYOUT return value of OPTION according to `bbdb-layout-alist'." | |
3670 | (let ((layout-spec (if (listp layout) | |
3671 | layout | |
3672 | (assq layout bbdb-layout-alist))) | |
3673 | option-value) | |
3674 | (and layout-spec | |
3675 | (setq option-value (assq option layout-spec)) | |
3676 | (cdr option-value)))) | |
3677 | ||
3678 | (defun bbdb-address-continental-p (address) | |
3679 | "Return non-nil if ADDRESS is a continental address. | |
3680 | This is done by comparing the postcode to `bbdb-continental-postcode-regexp'. | |
3681 | ||
3682 | This is a possible identifying function for | |
3683 | `bbdb-address-format-list' and `bbdb-tex-address-format-list'." | |
3684 | (string-match bbdb-continental-postcode-regexp | |
3685 | (bbdb-address-postcode address))) | |
3686 | ||
3687 | ;; This function can provide some guidance for writing | |
3688 | ;; your own address formatting function | |
3689 | (defun bbdb-format-address-default (address) | |
3690 | "Return formatted ADDRESS as a string. | |
3691 | This is the default format; it is used in the US, for example. | |
3692 | The result looks like this: | |
3693 | label: street | |
3694 | street | |
3695 | ... | |
3696 | city, state postcode | |
3697 | country. | |
3698 | ||
3699 | This function is a possible formatting function for | |
3700 | `bbdb-address-format-list'." | |
3701 | (let ((country (bbdb-address-country address)) | |
3702 | (streets (bbdb-address-streets address))) | |
3703 | (concat (if streets | |
3704 | (concat (mapconcat 'identity streets "\n") "\n")) | |
3705 | (bbdb-concat ", " (bbdb-address-city address) | |
3706 | (bbdb-concat " " (bbdb-address-state address) | |
3707 | (bbdb-address-postcode address))) | |
3708 | (unless (or (not country) (string= "" country)) | |
3709 | (concat "\n" country))))) | |
3710 | ||
3711 | (defun bbdb-format-address (address layout) | |
3712 | "Format ADDRESS using LAYOUT. Return result as a string. | |
3713 | The formatting rules are defined in `bbdb-address-format-list'." | |
3714 | (let ((list bbdb-address-format-list) | |
3715 | (country (bbdb-address-country address)) | |
3716 | elt string) | |
3717 | (while (and (not string) (setq elt (pop list))) | |
3718 | (let ((identifier (car elt)) | |
3719 | (format (nth layout elt)) | |
3720 | ;; recognize case for format identifiers | |
3721 | case-fold-search str) | |
3722 | (when (or (eq t identifier) ; default | |
3723 | (and (functionp identifier) | |
3724 | (funcall identifier address)) | |
3725 | (and country | |
3726 | (listp identifier) | |
3727 | ;; ignore case for countries | |
3728 | (member-ignore-case country identifier))) | |
3729 | (cond ((functionp format) | |
3730 | (setq string (funcall format address))) | |
3731 | ((stringp format) | |
3732 | (setq string "") | |
3733 | (dolist (form (split-string (substring format 1 -1) | |
3734 | (substring format 0 1) t)) | |
3735 | (cond ((string-match "%s" form) ; street | |
3736 | (mapc (lambda (s) (setq string (concat string (format form s)))) | |
3737 | (bbdb-address-streets address))) | |
3738 | ((string-match "%c" form) ; city | |
3739 | (unless (or (not (setq str (bbdb-address-city address))) (string= "" str)) | |
3740 | (setq string (concat string (format (replace-regexp-in-string "%c" "%s" form) str))))) | |
3741 | ((string-match "%p" form) ; postcode | |
3742 | (unless (or (not (setq str (bbdb-address-postcode address))) (string= "" str)) | |
3743 | (setq string (concat string (format (replace-regexp-in-string "%p" "%s" form) str))))) | |
3744 | ((string-match "%S" form) ; state | |
3745 | (unless (or (not (setq str (bbdb-address-state address))) (string= "" str)) | |
3746 | (setq string (concat string (format (replace-regexp-in-string "%S" "%s" form t) str))))) | |
3747 | ((string-match "%C" form) ; country | |
3748 | (unless (or (not country) (string= "" country)) | |
3749 | (setq string (concat string (format (replace-regexp-in-string "%C" "%s" form t) country))))) | |
3750 | (t (error "Malformed address format element %s" form))))) | |
3751 | (t (error "Malformed address format %s" format)))))) | |
3752 | (unless string | |
3753 | (error "No match of `bbdb-address-format-list'")) | |
3754 | string)) | |
3755 | ||
3756 | ;;; Record display: | |
3757 | ;; This inserts formatted (pieces of) records into the BBDB buffer. | |
3758 | ||
3759 | (defsubst bbdb-field-property (start field) | |
3760 | "Set text property bbdb-field of text between START and point to FIELD." | |
3761 | (put-text-property start (point) 'bbdb-field field)) | |
3762 | ||
3763 | (defsubst bbdb-display-text (text field &optional face) | |
3764 | "Insert TEXT at point. Set its text property bbdb-field to FIELD. | |
3765 | If FACE is non-nil, also add face FACE." | |
3766 | (let ((start (point))) | |
3767 | (insert text) | |
3768 | (bbdb-field-property start field) | |
3769 | (if face (put-text-property start (point) 'face face)))) | |
3770 | ||
3771 | (defun bbdb-display-list (list field &optional terminator face indent) | |
3772 | "Insert elements of LIST at point. | |
3773 | For inserted text, set text property bbdb-field to FIELD. | |
3774 | If TERMINATOR is non-nil use it to terminate the inserted text. | |
3775 | If FACE is non-nil use it as FACE for inserted text. | |
3776 | If INDENT and `bbdb-wrap-column' are integers, insert line breaks in between | |
3777 | elements of LIST if otherwise inserted text exceeds `bbdb-wrap-column'." | |
3778 | ;; `truncate-lines' is fine for one-line layout. But it is annyoing | |
3779 | ;; for records that are displayed with multi-line layout. | |
3780 | ;; Non-nil `word-wrap' would be much nicer. How can we switch between | |
3781 | ;; non-nil `truncate-lines' and non-nil `word-wrap' on a per-record basis? | |
3782 | ;; The following code is an alternative solution using `bbdb-wrap-column'. | |
3783 | (let* ((separator (nth 1 (or (cdr (assq field bbdb-separator-alist)) | |
3784 | bbdb-default-separator))) | |
3785 | (indent-flag (and (integerp bbdb-wrap-column) | |
3786 | (integerp indent))) | |
3787 | (prefix (if indent-flag | |
3788 | (concat separator "\n" (make-string indent ?\s)))) | |
3789 | elt) | |
3790 | (while (setq elt (pop list)) | |
3791 | (bbdb-display-text elt (list field elt) face) | |
3792 | (cond ((and list indent-flag | |
3793 | (> (+ (current-column) (length (car list))) | |
3794 | bbdb-wrap-column)) | |
3795 | (bbdb-display-text prefix (list field) face)) | |
3796 | (list | |
3797 | (bbdb-display-text separator (list field) face)) | |
3798 | (terminator | |
3799 | (bbdb-display-text terminator (list field) face)))))) | |
3800 | ||
3801 | (defun bbdb-display-name-organization (record) | |
3802 | "Insert name, affix, and organization of RECORD. | |
3803 | If RECORD has an xfield name-face, its value is used for font-locking name. | |
3804 | The value of name-face may be a face that is used directly. | |
3805 | The value may also be a key in `bbdb-name-face-alist'. Then the | |
3806 | corresponding cdr is used. If none of these schemes succeeds the face | |
3807 | `bbdb-face' is used." | |
3808 | ;; Should this be further customizable? We could build the following | |
3809 | ;; from a customizable list containing function calls and strings. | |
3810 | ;; Name | |
3811 | (let ((name (if (eq 'last-first | |
3812 | (or (bbdb-record-xfield-intern record 'name-format) | |
3813 | bbdb-name-format)) | |
3814 | (bbdb-record-name-lf record) | |
3815 | ;; default: Firstname Lastname | |
3816 | (bbdb-record-name record))) | |
3817 | (name-face (bbdb-record-xfield record 'name-face))) | |
3818 | (if (string= "" name) (setq name "???")) | |
3819 | (bbdb-display-text name (list 'name name) | |
3820 | (if name-face | |
3821 | (cond ((facep name-face) name-face) | |
3822 | ((cdr (assoc name-face bbdb-name-face-alist))) | |
3823 | (t 'bbdb-name)) | |
3824 | 'bbdb-name))) | |
3825 | ;; Affix | |
3826 | (let ((affix (bbdb-record-affix record))) | |
3827 | (when affix | |
3828 | (insert ", ") | |
3829 | (bbdb-display-list affix 'affix))) | |
3830 | ;; Organization | |
3831 | (let ((organization (bbdb-record-organization record))) | |
3832 | (when organization | |
3833 | (insert " - ") | |
3834 | (bbdb-display-list organization 'organization nil | |
3835 | 'bbdb-organization))) | |
3836 | ;; Image | |
3837 | (if (and bbdb-image (display-images-p)) | |
3838 | (let ((image (cond ((functionp bbdb-image) | |
3839 | (funcall bbdb-image record)) | |
3840 | ((memq bbdb-image '(name fl-name)) | |
3841 | (bbdb-record-name record)) | |
3842 | ((eq bbdb-image 'lf-name) | |
3843 | (bbdb-record-name-lf record)) | |
3844 | (t | |
3845 | (bbdb-record-xfield record bbdb-image))))) | |
3846 | (when (and image | |
3847 | (setq image (locate-file image bbdb-image-path | |
3848 | bbdb-image-suffixes)) | |
3849 | (setq image (create-image image))) | |
3850 | (insert " ") | |
3851 | (insert-image image))))) | |
3852 | ||
3853 | (defun bbdb-display-record-one-line (record layout field-list) | |
3854 | "Format RECORD for the one-line FORMAT using LAYOUT. | |
3855 | See `bbdb-layout-alist' for more info on layouts. | |
3856 | FIELD-LIST is the list of actually displayed FIELDS." | |
3857 | ;; Name, affix, and organizations | |
3858 | (bbdb-display-name-organization record) | |
3859 | (let ((name-end (or (bbdb-layout-get-option layout 'name-end) | |
3860 | 40)) | |
3861 | (start (line-beginning-position))) | |
3862 | (when (> (- (point) start -1) name-end) | |
3863 | (put-text-property (+ start name-end -4) (point) 'invisible t) | |
3864 | (insert "...")) | |
3865 | (indent-to name-end)) | |
3866 | ;; rest of the fields | |
3867 | (let (formatfun start) | |
3868 | (dolist (field field-list) | |
3869 | (cond (;; customized formatting | |
3870 | (setq formatfun (intern-soft (format "bbdb-display-%s-one-line" field))) | |
3871 | (funcall formatfun record)) | |
3872 | ;; phone | |
3873 | ((eq field 'phone) | |
3874 | (let ((phones (bbdb-record-phone record)) phone) | |
3875 | (if phones | |
3876 | (while (setq phone (pop phones)) | |
3877 | (bbdb-display-text (format "%s " (aref phone 0)) | |
3878 | `(phone ,phone field-label) | |
3879 | 'bbdb-field-name) | |
3880 | (bbdb-display-text (format "%s%s" (aref phone 1) | |
3881 | (if phones " " "; ")) | |
3882 | `(phone ,phone)))))) | |
3883 | ;; address | |
3884 | ((eq field 'address) | |
3885 | (dolist (address (bbdb-record-address record)) | |
3886 | (setq start (point)) | |
3887 | (insert (bbdb-format-address address 3)) | |
3888 | (bbdb-field-property start `(address ,address)) | |
3889 | (insert "; "))) | |
3890 | ||
3891 | ((eq field 'mail) | |
3892 | (let ((mail (bbdb-record-mail record))) | |
3893 | (if mail | |
3894 | (bbdb-display-list (if (bbdb-layout-get-option layout 'primary) | |
3895 | (list (car mail)) mail) | |
3896 | 'mail "; ")))) | |
3897 | ;; AKA | |
3898 | ((eq field 'aka) | |
3899 | (let ((aka (bbdb-record-aka record))) | |
3900 | (if aka | |
3901 | (bbdb-display-list aka 'aka "; ")))) | |
3902 | ;; uuid | |
3903 | ((eq field 'uuid) | |
3904 | (let ((uuid (bbdb-record-uuid record))) | |
3905 | (bbdb-display-text (format "%s; " uuid) `(uuid ,uuid)))) | |
3906 | ;; creation-date | |
3907 | ((eq field 'creation-date) | |
3908 | (let ((creation-date (bbdb-record-creation-date record))) | |
3909 | (bbdb-display-text (format "%s; " creation-date) `(creation-date ,creation-date)))) | |
3910 | ;; timestamp | |
3911 | ((eq field 'timestamp) | |
3912 | (let ((timestamp (bbdb-record-timestamp record))) | |
3913 | (bbdb-display-text (format "%s; " timestamp) `(timestamp ,timestamp)))) | |
3914 | ;; xfields | |
3915 | (t | |
3916 | (let* ((xfield (assq field (bbdb-record-xfields record))) | |
3917 | (value (cdr xfield))) | |
3918 | (if value | |
3919 | (bbdb-display-text | |
3920 | (concat (if (stringp value) | |
3921 | (replace-regexp-in-string | |
3922 | "\n" "; " value) | |
3923 | ;; value of xfield is a sexp | |
3924 | (let ((print-escape-newlines t)) | |
3925 | (prin1-to-string value))) | |
3926 | "; ") | |
3927 | `(xfields ,xfield))))))) | |
3928 | ;; delete the trailing "; " | |
3929 | (if (looking-back "; " nil) | |
3930 | (backward-delete-char 2)) | |
3931 | (insert "\n"))) | |
3932 | ||
3933 | (defun bbdb-display-record-multi-line (record layout field-list) | |
3934 | "Format RECORD for the multi-line FORMAT using LAYOUT. | |
3935 | See `bbdb-layout-alist' for more info on layouts. | |
3936 | FIELD-LIST is the list of actually displayed FIELDS." | |
3937 | (bbdb-display-name-organization record) | |
3938 | (insert "\n") | |
3939 | (let* ((indent (or (bbdb-layout-get-option layout 'indentation) 21)) | |
3940 | ;; The format string FMT adds three extra characters. | |
3941 | ;; So we subtract those from the value of INDENT. | |
3942 | (fmt (format " %%%ds: " (- indent 3))) | |
3943 | start formatfun) | |
3944 | (dolist (field field-list) | |
3945 | (setq start (point)) | |
3946 | (cond (;; customized formatting | |
3947 | (setq formatfun (intern-soft (format "bbdb-display-%s-multi-line" field))) | |
3948 | (funcall formatfun record indent)) | |
3949 | ;; phone | |
3950 | ((eq field 'phone) | |
3951 | (dolist (phone (bbdb-record-phone record)) | |
3952 | (bbdb-display-text (format fmt (concat "phone (" | |
3953 | (bbdb-phone-label phone) | |
3954 | ")")) | |
3955 | `(phone ,phone field-label) | |
3956 | 'bbdb-field-name) | |
3957 | (bbdb-display-text (concat (bbdb-phone-string phone) "\n") | |
3958 | `(phone ,phone)))) | |
3959 | ;; address | |
3960 | ((eq field 'address) | |
3961 | (dolist (address (bbdb-record-address record)) | |
3962 | (bbdb-display-text (format fmt (concat "address (" | |
3963 | (bbdb-address-label address) | |
3964 | ")")) | |
3965 | `(address ,address field-label) | |
3966 | 'bbdb-field-name) | |
3967 | (setq start (point)) | |
3968 | (insert (bbdb-indent-string (bbdb-format-address address 2) indent) | |
3969 | "\n") | |
3970 | (bbdb-field-property start `(address ,address)))) | |
3971 | ||
3972 | ((eq field 'mail) | |
3973 | (let ((mail (bbdb-record-mail record))) | |
3974 | (when mail | |
3975 | (bbdb-display-text (format fmt "mail") '(mail nil field-label) | |
3976 | 'bbdb-field-name) | |
3977 | (bbdb-display-list (if (bbdb-layout-get-option layout 'primary) | |
3978 | (list (car mail)) mail) | |
3979 | 'mail "\n" nil indent)))) | |
3980 | ;; AKA | |
3981 | ((eq field 'aka) | |
3982 | (let ((aka (bbdb-record-aka record))) | |
3983 | (when aka | |
3984 | (bbdb-display-text (format fmt "AKA") '(aka nil field-label) | |
3985 | 'bbdb-field-name) | |
3986 | (bbdb-display-list aka 'aka "\n")))) | |
3987 | ;; uuid | |
3988 | ((eq field 'uuid) | |
3989 | (let ((uuid (bbdb-record-uuid record))) | |
3990 | (bbdb-display-text (format fmt "uuid") `(uuid ,uuid field-label) | |
3991 | 'bbdb-field-name) | |
3992 | (bbdb-display-text (format "%s\n" uuid) `(uuid ,uuid)))) | |
3993 | ;; creation-date | |
3994 | ((eq field 'creation-date) | |
3995 | (let ((creation-date (bbdb-record-creation-date record))) | |
3996 | (bbdb-display-text (format fmt "creation-date") `(creation-date ,creation-date field-label) | |
3997 | 'bbdb-field-name) | |
3998 | (bbdb-display-text (format "%s\n" creation-date) `(creation-date ,creation-date)))) | |
3999 | ;; timestamp | |
4000 | ((eq field 'timestamp) | |
4001 | (let ((timestamp (bbdb-record-timestamp record))) | |
4002 | (bbdb-display-text (format fmt "timestamp") `(timestamp ,timestamp field-label) | |
4003 | 'bbdb-field-name) | |
4004 | (bbdb-display-text (format "%s\n" timestamp) `(timestamp ,timestamp)))) | |
4005 | ;; xfields | |
4006 | (t | |
4007 | (let* ((xfield (assq field (bbdb-record-xfields record))) | |
4008 | (value (cdr xfield))) | |
4009 | (when value | |
4010 | (bbdb-display-text (format fmt field) | |
4011 | `(xfields ,xfield field-label) | |
4012 | 'bbdb-field-name) | |
4013 | (setq start (point)) | |
4014 | (insert (bbdb-indent-string | |
4015 | (if (stringp value) | |
4016 | value | |
4017 | ;; value of xfield is a sexp | |
4018 | (let ((string (pp-to-string value))) | |
4019 | (if (string-match "[ \t\n]+\\'" string) | |
4020 | (substring-no-properties | |
4021 | string 0 (match-beginning 0)) | |
4022 | string))) | |
4023 | indent) "\n") | |
4024 | (bbdb-field-property start `(xfields ,xfield))))))) | |
4025 | (insert "\n"))) | |
4026 | ||
4027 | (defalias 'bbdb-display-record-full-multi-line | |
4028 | 'bbdb-display-record-multi-line) | |
4029 | ||
4030 | (defalias 'bbdb-display-record-pop-up-multi-line | |
4031 | 'bbdb-display-record-multi-line) | |
4032 | ||
4033 | (defun bbdb-display-record (record layout number) | |
4034 | "Insert a formatted RECORD into the current buffer at point. | |
4035 | LAYOUT can be a symbol describing a layout in `bbdb-layout-alist'. | |
4036 | If it is nil, use `bbdb-layout'. | |
4037 | NUMBER is the number of RECORD among the displayed records. | |
4038 | Move point to the end of the inserted record." | |
4039 | (unless layout (setq layout bbdb-layout)) | |
4040 | (unless (assq layout bbdb-layout-alist) | |
4041 | (error "Unknown layout `%s'" layout)) | |
4042 | (let ((display-p (bbdb-layout-get-option layout 'display-p)) | |
4043 | (omit-list (bbdb-layout-get-option layout 'omit)) ; omitted fields | |
4044 | (order-list (bbdb-layout-get-option layout 'order)); requested field order | |
4045 | (all-fields (append '(phone address mail aka) ; default field order | |
4046 | (mapcar 'car (bbdb-record-xfields record)) | |
4047 | '(uuid creation-date timestamp))) | |
4048 | (beg (point)) | |
4049 | format-function field-list) | |
4050 | (when (or (not display-p) | |
4051 | (and display-p | |
4052 | (funcall display-p))) | |
4053 | (if (functionp omit-list) | |
4054 | (setq omit-list (funcall omit-list record layout))) | |
4055 | (if (functionp order-list) | |
4056 | (setq order-list (funcall order-list record layout))) | |
4057 | ;; first omit unwanted fields | |
4058 | (when (and omit-list (or (not order-list) (memq t order-list))) | |
4059 | (if (listp omit-list) | |
4060 | ;; show all fields except those listed here | |
4061 | (dolist (omit omit-list) | |
4062 | (setq all-fields (delq omit all-fields))) | |
4063 | (setq all-fields nil))) ; show nothing | |
4064 | ;; then order them | |
4065 | (cond ((not order-list) | |
4066 | (setq field-list all-fields)) | |
4067 | ((not (memq t order-list)) | |
4068 | (setq field-list order-list)) | |
4069 | (t | |
4070 | (setq order-list (reverse order-list) | |
4071 | all-fields (delq nil (mapcar (lambda (f) | |
4072 | (unless (memq f order-list) | |
4073 | f)) | |
4074 | all-fields))) | |
4075 | (dolist (order order-list) | |
4076 | (if (eq t order) | |
4077 | (setq field-list (append all-fields field-list)) | |
4078 | (push order field-list))))) | |
4079 | ;; call the actual format function | |
4080 | (setq format-function | |
4081 | (intern-soft (format "bbdb-display-record-%s" layout))) | |
4082 | (if (functionp format-function) | |
4083 | (funcall format-function record layout field-list) | |
4084 | (bbdb-display-record-multi-line record layout field-list)) | |
4085 | (put-text-property beg (point) 'bbdb-record-number number)))) | |
4086 | ||
4087 | (defun bbdb-display-records (records &optional layout append | |
4088 | select horiz-p) | |
4089 | "Display RECORDS using LAYOUT. | |
4090 | If APPEND is non-nil append RECORDS to the already displayed records. | |
4091 | Otherwise RECORDS overwrite the displayed records. | |
4092 | SELECT and HORIZ-P have the same meaning as in `bbdb-pop-up-window'." | |
4093 | (interactive (list (bbdb-completing-read-records "Display records: ") | |
4094 | (bbdb-layout-prefix))) | |
4095 | (if (bbdb-append-display-p) (setq append t)) | |
4096 | ;; `bbdb-redisplay-record' calls `bbdb-display-records' | |
4097 | ;; with display information already amended to RECORDS. | |
4098 | (unless (or (null records) | |
4099 | (consp (car records))) | |
4100 | ;; add layout and a marker to the local list of records | |
4101 | (setq layout (or layout bbdb-layout) | |
4102 | records (mapcar (lambda (record) | |
4103 | (list record layout (make-marker))) | |
4104 | records))) | |
4105 | ||
4106 | (let ((first-new (caar records)) ; first new record | |
4107 | new-name) | |
4108 | ||
4109 | ;; If `bbdb-multiple-buffers' is non-nil we create a new BBDB buffer | |
4110 | ;; when not already within one. The new buffer name starts with a space, | |
4111 | ;; i.e. it does not clutter the buffer list. | |
4112 | (when (and bbdb-multiple-buffers | |
4113 | (not (assq 'bbdb-buffer-name (buffer-local-variables)))) | |
4114 | (setq new-name (concat " *BBDB " (if (functionp bbdb-multiple-buffers) | |
4115 | (funcall bbdb-multiple-buffers) | |
4116 | (buffer-name)) | |
4117 | "*")) | |
4118 | ;; `bbdb-buffer-name' becomes buffer-local in the current buffer | |
4119 | ;; as well as in the buffer `bbdb-buffer-name' | |
4120 | (set (make-local-variable 'bbdb-buffer-name) new-name)) | |
4121 | ||
4122 | (with-current-buffer (get-buffer-create bbdb-buffer-name) ; *BBDB* | |
4123 | ;; If we are appending RECORDS to the ones already displayed, | |
4124 | ;; then first remove any duplicates, and then sort them. | |
4125 | (if append | |
4126 | (let ((old-rec (mapcar 'car bbdb-records))) | |
4127 | (dolist (record records) | |
4128 | (unless (memq (car record) old-rec) | |
4129 | (push record bbdb-records))) | |
4130 | (setq records | |
4131 | (sort bbdb-records | |
4132 | (lambda (x y) (bbdb-record-lessp (car x) (car y))))))) | |
4133 | ||
4134 | (bbdb-mode) | |
4135 | ;; Normally `bbdb-records' is the only BBDB-specific buffer-local variable | |
4136 | ;; in the *BBDB* buffer. It is intentionally not permanent-local. | |
4137 | ;; A value of nil indicates that we need to (re)process the records. | |
4138 | (setq bbdb-records records) | |
4139 | (if new-name | |
4140 | (set (make-local-variable 'bbdb-buffer-name) new-name)) | |
4141 | ||
4142 | (unless (or bbdb-silent-internal bbdb-silent) | |
4143 | (message "Formatting BBDB...")) | |
4144 | (let ((record-number 0) | |
4145 | buffer-read-only all-records) | |
4146 | (erase-buffer) | |
4147 | (bbdb-debug (setq all-records (bbdb-records))) | |
4148 | (dolist (record records) | |
4149 | (bbdb-debug (unless (memq (car record) all-records) | |
4150 | (error "Record %s does not exist" (car record)))) | |
4151 | (set-marker (nth 2 record) (point)) | |
4152 | (bbdb-display-record (nth 0 record) (nth 1 record) record-number) | |
4153 | (setq record-number (1+ record-number))) | |
4154 | ||
4155 | (run-hooks 'bbdb-display-hook)) | |
4156 | ||
4157 | (unless (or bbdb-silent-internal bbdb-silent) | |
4158 | (message "Formatting BBDB...done.")) | |
4159 | (set-buffer-modified-p nil) | |
4160 | ||
4161 | (bbdb-pop-up-window select horiz-p) | |
4162 | (if (not first-new) | |
4163 | (goto-char (point-min)) | |
4164 | ;; Put point on first new record in *BBDB* buffer. | |
4165 | (goto-char (nth 2 (assq first-new bbdb-records))) | |
4166 | (set-window-start (get-buffer-window (current-buffer)) (point)))))) | |
4167 | ||
4168 | (defun bbdb-undisplay-records (&optional all-buffers) | |
4169 | "Undisplay records in *BBDB* buffer, leaving this buffer empty. | |
4170 | If ALL-BUFFERS is non-nil undisplay records in all BBDB buffers." | |
4171 | (dolist (buffer (cond (all-buffers (buffer-list)) | |
4172 | ((let ((buffer (get-buffer bbdb-buffer-name))) | |
4173 | (and (buffer-live-p buffer) (list buffer)))))) | |
4174 | (with-current-buffer buffer | |
4175 | (when (eq major-mode 'bbdb-mode) | |
4176 | (let (buffer-read-only) | |
4177 | (erase-buffer)) | |
4178 | (setq bbdb-records nil) | |
4179 | (set-buffer-modified-p nil))))) | |
4180 | ||
4181 | (defun bbdb-redisplay-record (record &optional sort delete-p) | |
4182 | "Redisplay RECORD in current BBDB buffer. | |
4183 | If SORT is t, usually because RECORD has a new sortkey, re-sort | |
4184 | the displayed records. | |
4185 | If DELETE-P is non-nil RECORD is removed from the BBDB buffer." | |
4186 | ;; For deletion in the *BBDB* buffer we use the full information | |
4187 | ;; about the record in the database. Therefore, we need to delete | |
4188 | ;; the record in the *BBDB* buffer before deleting the record in | |
4189 | ;; the database. | |
4190 | ;; FIXME: If point is initially inside RECORD, `bbdb-redisplay-record' | |
4191 | ;; puts point at the beginning of the redisplayed RECORD. | |
4192 | ;; Ideally, `bbdb-redisplay-record' should put point such that it | |
4193 | ;; matches the previous value `bbdb-ident-point'. | |
4194 | (let ((full-record (assq record bbdb-records))) | |
4195 | (unless full-record | |
4196 | (error "Record `%s' not displayed" (bbdb-record-name record))) | |
4197 | (if (and sort (not delete-p)) | |
4198 | ;; FIXME: For records requiring re-sorting it may be more efficient | |
4199 | ;; to insert these records in their proper location instead of | |
4200 | ;; re-displaying all records. | |
4201 | (bbdb-display-records (list record) nil t) | |
4202 | (let ((marker (nth 2 full-record)) | |
4203 | (end-marker (nth 2 (car (cdr (memq full-record bbdb-records))))) | |
4204 | buffer-read-only record-number) | |
4205 | ;; If point is inside record, put it at the beginning of the record. | |
4206 | (if (and (<= marker (point)) | |
4207 | (< (point) (or end-marker (point-max)))) | |
4208 | (goto-char marker)) | |
4209 | (save-excursion | |
4210 | (goto-char marker) | |
4211 | (setq record-number (get-text-property (point) 'bbdb-record-number)) | |
4212 | (unless delete-p | |
4213 | ;; First insert the reformatted record, then delete the old one, | |
4214 | ;; so that the marker of this record cannot collapse with the | |
4215 | ;; marker of the subsequent record | |
4216 | (bbdb-display-record (car full-record) (nth 1 full-record) | |
4217 | record-number)) | |
4218 | (delete-region (point) (or end-marker (point-max))) | |
4219 | ;; If we deleted a record we need to update the subsequent | |
4220 | ;; record numbers. | |
4221 | (when delete-p | |
4222 | (let* ((markers (append (mapcar (lambda (x) (nth 2 x)) | |
4223 | (cdr (memq full-record bbdb-records))) | |
4224 | (list (point-max)))) | |
4225 | (start (pop markers))) | |
4226 | (dolist (end markers) | |
4227 | (put-text-property start end | |
4228 | 'bbdb-record-number record-number) | |
4229 | (setq start end | |
4230 | record-number (1+ record-number)))) | |
4231 | (setq bbdb-records (delq full-record bbdb-records))) | |
4232 | (run-hooks 'bbdb-display-hook)))))) | |
4233 | ||
4234 | (defun bbdb-redisplay-record-globally (record &optional sort delete-p) | |
4235 | "Redisplay RECORD in all BBDB buffers. | |
4236 | If SORT is t, usually because RECORD has a new sortkey, re-sort | |
4237 | the displayed records. | |
4238 | If DELETE-P is non-nil RECORD is removed from the BBDB buffers." | |
4239 | (dolist (buffer (buffer-list)) | |
4240 | (with-current-buffer buffer | |
4241 | (if (and (eq major-mode 'bbdb-mode) | |
4242 | (memq record (mapcar 'car bbdb-records))) | |
4243 | (let ((window (get-buffer-window bbdb-buffer-name))) | |
4244 | (if window | |
4245 | (with-selected-window window | |
4246 | (bbdb-redisplay-record record sort delete-p)) | |
4247 | (bbdb-redisplay-record record sort delete-p))))))) | |
4248 | (define-obsolete-function-alias 'bbdb-maybe-update-display | |
4249 | 'bbdb-redisplay-record-globally "3.0") | |
4250 | \f | |
4251 | ||
4252 | ;;; window configuration hackery | |
4253 | (defun bbdb-pop-up-window (&optional select horiz-p) | |
4254 | "Display *BBDB* buffer by popping up a new window. | |
4255 | Finds the largest window on the screen, splits it, displaying the | |
4256 | *BBDB* buffer in the bottom `bbdb-pop-up-window-size' lines (unless | |
4257 | the *BBDB* buffer is already visible, in which case do nothing.) | |
4258 | Select this window if SELECT is non-nil. | |
4259 | ||
4260 | If `bbdb-mua-pop-up' is 'horiz, and the first window matching | |
4261 | the predicate HORIZ-P is wider than the car of `bbdb-horiz-pop-up-window-size' | |
4262 | then the window will be split horizontally rather than vertically." | |
4263 | (let ((buffer (get-buffer bbdb-buffer-name))) | |
4264 | (unless buffer | |
4265 | (error "No %s buffer to display" bbdb-buffer-name)) | |
4266 | (cond ((let ((window (get-buffer-window buffer t))) | |
4267 | ;; We already have a BBDB window so that at most we select it | |
4268 | (and window | |
4269 | (or (not select) (select-window window))))) | |
4270 | ||
4271 | ;; try horizontal split | |
4272 | ((and (eq bbdb-mua-pop-up 'horiz) | |
4273 | horiz-p | |
4274 | (>= (frame-width) (car bbdb-horiz-pop-up-window-size)) | |
4275 | (let ((window-list (window-list)) | |
4276 | (b-width (cdr bbdb-horiz-pop-up-window-size)) | |
4277 | (search t) s-window) | |
4278 | (while (and (setq s-window (pop window-list)) | |
4279 | (setq search (not (funcall horiz-p s-window))))) | |
4280 | (unless (or search (<= (window-width s-window) | |
4281 | (car bbdb-horiz-pop-up-window-size))) | |
4282 | (condition-case nil ; `split-window' might fail | |
4283 | (let ((window (split-window | |
4284 | s-window | |
4285 | (if (integerp b-width) | |
4286 | (- (window-width s-window) b-width) | |
4287 | (round (* (- 1 b-width) (window-width s-window)))) | |
4288 | t))) ; horizontal split | |
4289 | (set-window-buffer window buffer) | |
4290 | (cond (bbdb-dedicated-window | |
4291 | (set-window-dedicated-p window bbdb-dedicated-window)) | |
4292 | ((fboundp 'display-buffer-record-window) ; GNU Emacs >= 24.1 | |
4293 | (set-window-prev-buffers window nil) | |
4294 | (display-buffer-record-window 'window window buffer))) | |
4295 | (if select (select-window window)) | |
4296 | t) | |
4297 | (error nil)))))) | |
4298 | ||
4299 | ((eq t bbdb-pop-up-window-size) | |
4300 | (bbdb-pop-up-window-simple buffer select)) | |
4301 | ||
4302 | (t ;; vertical split | |
4303 | (let* ((window (selected-window)) | |
4304 | (window-height (window-height window))) | |
4305 | ;; find the tallest window... | |
4306 | (mapc (lambda (w) | |
4307 | (let ((w-height (window-height w))) | |
4308 | (if (> w-height window-height) | |
4309 | (setq window w window-height w-height)))) | |
4310 | (window-list)) | |
4311 | (condition-case nil | |
4312 | (progn | |
4313 | (unless (eql bbdb-pop-up-window-size 1.0) | |
4314 | (setq window (split-window ; might fail | |
4315 | window | |
4316 | (if (integerp bbdb-pop-up-window-size) | |
4317 | (- window-height 1 ; for mode line | |
4318 | (max window-min-height bbdb-pop-up-window-size)) | |
4319 | (round (* (- 1 bbdb-pop-up-window-size) | |
4320 | window-height)))))) | |
4321 | (set-window-buffer window buffer) ; might fail | |
4322 | (cond (bbdb-dedicated-window | |
4323 | (set-window-dedicated-p window bbdb-dedicated-window)) | |
4324 | ((and (fboundp 'display-buffer-record-window) ; GNU Emacs >= 24.1 | |
4325 | (not (eql bbdb-pop-up-window-size 1.0))) | |
4326 | (set-window-prev-buffers window nil) | |
4327 | (display-buffer-record-window 'window window buffer))) | |
4328 | (if select (select-window window))) | |
4329 | (error (bbdb-pop-up-window-simple buffer select)))))))) | |
4330 | ||
4331 | (defun bbdb-pop-up-window-simple (buffer select) | |
4332 | "Display BUFFER in some window, selecting it if SELECT is non-nil. | |
4333 | If `bbdb-dedicated-window' is non-nil, mark the window as dedicated." | |
4334 | (let ((window (if select | |
4335 | (progn (pop-to-buffer buffer) | |
4336 | (get-buffer-window)) | |
4337 | (display-buffer buffer)))) | |
4338 | (if bbdb-dedicated-window | |
4339 | (set-window-dedicated-p window bbdb-dedicated-window)))) | |
4340 | ||
4341 | \f | |
4342 | ;;; BBDB mode | |
4343 | ||
4344 | ;;;###autoload | |
4345 | (define-derived-mode bbdb-mode special-mode "BBDB" | |
4346 | "Major mode for viewing and editing the Insidious Big Brother Database. | |
4347 | Letters no longer insert themselves. Numbers are prefix arguments. | |
4348 | You can move around using the usual cursor motion commands. | |
4349 | \\<bbdb-mode-map> | |
4350 | \\[bbdb-add-mail-alias]\t Add new mail alias to visible records or \ | |
4351 | remove it. | |
4352 | \\[bbdb-edit-field]\t Edit the field on the current line. | |
4353 | \\[bbdb-delete-field-or-record]\t Delete the field on the \ | |
4354 | current line. If the current line is the\n\t first line of a record, then \ | |
4355 | delete the entire record. | |
4356 | \\[bbdb-insert-field]\t Insert a new field into the current record. \ | |
4357 | Note that this\n\t will let you add new fields of your own as well. | |
4358 | \\[bbdb-transpose-fields]\t Swap the field on the current line with the \ | |
4359 | previous field. | |
4360 | \\[bbdb-dial]\t Dial the current phone field. | |
4361 | \\[bbdb-next-record], \\[bbdb-prev-record]\t Move to the next or the previous \ | |
4362 | displayed record, respectively. | |
4363 | \\[bbdb-create]\t Create a new record. | |
4364 | \\[bbdb-toggle-records-layout]\t Toggle whether the current record is displayed in a \ | |
4365 | one-line\n\t listing, or a full multi-line listing. | |
4366 | \\[bbdb-do-all-records]\\[bbdb-toggle-records-layout]\t Do that \ | |
4367 | for all displayed records. | |
4368 | \\[bbdb-merge-records]\t Merge the contents of the current record with \ | |
4369 | some other, and then\n\t delete the current record. | |
4370 | \\[bbdb-omit-record]\t Remove the current record from the display without \ | |
4371 | deleting it from\n\t the database. This is often a useful thing to do \ | |
4372 | before using one\n\t of the `*' commands. | |
4373 | \\[bbdb]\t Search for records in the database (on all fields). | |
4374 | \\[bbdb-search-mail]\t Search for records by mail address. | |
4375 | \\[bbdb-search-organization]\t Search for records by organization. | |
4376 | \\[bbdb-search-xfields]\t Search for records by xfields. | |
4377 | \\[bbdb-search-name]\t Search for records by name. | |
4378 | \\[bbdb-search-changed]\t Display records that have changed since the database \ | |
4379 | was saved. | |
4380 | \\[bbdb-mail]\t Compose mail to the person represented by the \ | |
4381 | current record. | |
4382 | \\[bbdb-do-all-records]\\[bbdb-mail]\t Compose mail \ | |
4383 | to everyone whose record is displayed. | |
4384 | \\[bbdb-save]\t Save the BBDB file to disk. | |
4385 | \\[bbdb-tex]\t Create a TeX listing of the current record. | |
4386 | \\[bbdb-do-all-records]\\[bbdb-tex]\t Do that for all \ | |
4387 | displayed record. | |
4388 | \\[other-window]\t Move to another window. | |
4389 | \\[bbdb-info]\t Read the Info documentation for BBDB. | |
4390 | \\[bbdb-help]\t Display a one line command summary in the echo area. | |
4391 | \\[bbdb-browse-url]\t Visit Web sites listed in the `url' field(s) of the current \ | |
4392 | record. | |
4393 | ||
4394 | For address completion using the names and mail addresses in the database: | |
4395 | \t in Mail mode, type \\<mail-mode-map>\\[bbdb-complete-mail]. | |
4396 | \t in Message mode, type \\<message-mode-map>\\[bbdb-complete-mail]. | |
4397 | ||
4398 | Important variables: | |
4399 | \t `bbdb-auto-revert' | |
4400 | \t `bbdb-ignore-redundant-mails' | |
4401 | \t `bbdb-case-fold-search' | |
4402 | \t `bbdb-completion-list' | |
4403 | \t `bbdb-default-area-code' | |
4404 | \t `bbdb-default-domain' | |
4405 | \t `bbdb-layout' | |
4406 | \t `bbdb-file' | |
4407 | \t `bbdb-phone-style' | |
4408 | \t `bbdb-check-auto-save-file' | |
4409 | \t `bbdb-pop-up-layout' | |
4410 | \t `bbdb-pop-up-window-size' | |
4411 | \t `bbdb-add-name' | |
4412 | \t `bbdb-add-aka' | |
4413 | \t `bbdb-add-mails' | |
4414 | \t `bbdb-new-mails-primary' | |
4415 | \t `bbdb-read-only' | |
4416 | \t `bbdb-mua-pop-up' | |
4417 | \t `bbdb-user-mail-address-re' | |
4418 | ||
4419 | There are numerous hooks. M-x apropos ^bbdb.*hook RET | |
4420 | ||
4421 | \\{bbdb-mode-map}" | |
4422 | (setq truncate-lines t | |
4423 | default-directory (file-name-directory bbdb-file) | |
4424 | mode-line-buffer-identification | |
4425 | (list 24 (buffer-name) " " | |
4426 | '(:eval (format "%d/%d/%d" | |
4427 | (1+ (or (get-text-property | |
4428 | (point) 'bbdb-record-number) -1)) | |
4429 | (length bbdb-records) | |
4430 | ;; This code gets called a lot. | |
4431 | ;; So we keep it as simple as possible. | |
4432 | (with-current-buffer bbdb-buffer | |
4433 | (length bbdb-records)))) | |
4434 | '(:eval (concat " " | |
4435 | (bbdb-concat " " (elt bbdb-modeline-info 0) | |
4436 | (elt bbdb-modeline-info 2) | |
4437 | (elt bbdb-modeline-info 4))))) | |
4438 | mode-line-modified | |
4439 | ;; For the mode-line we want to be fast. So we skip the checks | |
4440 | ;; performed by `bbdb-with-db-buffer'. | |
4441 | '(:eval (if (buffer-modified-p bbdb-buffer) | |
4442 | (if bbdb-read-only "%*" "**") | |
4443 | (if bbdb-read-only "%%" "--")))) | |
4444 | ;; `bbdb-revert-buffer' acts on `bbdb-buffer'. Yet this command is usually | |
4445 | ;; called from the *BBDB* buffer. | |
4446 | (set (make-local-variable 'revert-buffer-function) | |
4447 | 'bbdb-revert-buffer) | |
4448 | (add-hook 'post-command-hook 'force-mode-line-update nil t)) | |
4449 | ||
4450 | \f | |
4451 | ||
4452 | (defun bbdb-sendmail-menu (record) | |
4453 | "Menu items for email addresses of RECORD." | |
4454 | (let ((mails (bbdb-record-mail record))) | |
4455 | (list | |
4456 | (if (cdr mails) | |
4457 | ;; Submenu for multiple mail addresses | |
4458 | (cons "Send mail to..." | |
4459 | (mapcar (lambda (address) | |
4460 | (vector address `(bbdb-compose-mail | |
4461 | ,(bbdb-dwim-mail record address)) | |
4462 | t)) | |
4463 | mails)) | |
4464 | ;; Single entry for single mail address | |
4465 | (vector (concat "Send mail to " (car mails)) | |
4466 | `(bbdb-compose-mail ,(bbdb-dwim-mail record (car mails))) | |
4467 | t))))) | |
4468 | ||
4469 | (defun bbdb-field-menu (record field) | |
4470 | "Menu items specifically for FIELD of RECORD." | |
4471 | (let ((type (car field))) | |
4472 | (append | |
4473 | (list | |
4474 | (format "Commands for %s Field:" | |
4475 | (cond ((eq type 'xfields) | |
4476 | (format "\"%s\"" (symbol-name (car (nth 1 field))))) | |
4477 | ((eq type 'name) "Name") | |
4478 | ((eq type 'affix) "Affix") | |
4479 | ((eq type 'organization) "Organization") | |
4480 | ((eq type 'aka) "Alternate Names") | |
4481 | ((eq type 'mail) "Mail Addresses") | |
4482 | ((memq type '(address phone)) | |
4483 | (format "\"%s\" %s" (aref (nth 1 field) 0) | |
4484 | (capitalize (symbol-name type))))))) | |
4485 | (cond ((eq type 'phone) | |
4486 | (list (vector (concat "Dial " (bbdb-phone-string (nth 1 field))) | |
4487 | `(bbdb-dial ',field nil) t))) | |
4488 | ((eq type 'xfields) | |
4489 | (let* ((field (cadr field)) | |
4490 | (type (car field))) | |
4491 | (cond ((eq type 'url ) | |
4492 | (list (vector (format "Browse \"%s\"" (cdr field)) | |
4493 | `(bbdb-browse-url ,record) t))))))) | |
4494 | '(["Edit Field" bbdb-edit-field t]) | |
4495 | (unless (eq type 'name) | |
4496 | '(["Delete Field" bbdb-delete-field-or-record t]))))) | |
4497 | ||
4498 | (defun bbdb-insert-field-menu (record) | |
4499 | "Submenu for inserting a new field for RECORD." | |
4500 | (cons "Insert New Field..." | |
4501 | (mapcar | |
4502 | (lambda (field) | |
4503 | (if (stringp field) field | |
4504 | (vector (symbol-name field) | |
4505 | `(bbdb-insert-field | |
4506 | ,record ',field (bbdb-read-field ,record ',field | |
4507 | ,current-prefix-arg)) | |
4508 | (not (or (and (eq field 'affix) (bbdb-record-affix record)) | |
4509 | (and (eq field 'organization) | |
4510 | (bbdb-record-organization record)) | |
4511 | (and (eq field 'mail) (bbdb-record-mail record)) | |
4512 | (and (eq field 'aka) (bbdb-record-aka record)) | |
4513 | (assq field (bbdb-record-xfields record))))))) | |
4514 | (append '(affix organization aka phone address mail) | |
4515 | '("--") bbdb-xfield-label-list)))) | |
4516 | ||
4517 | (defun bbdb-mouse-menu (event) | |
4518 | "BBDB mouse menu for EVENT," | |
4519 | (interactive "e") | |
4520 | (mouse-set-point event) | |
4521 | (let* ((record (bbdb-current-record)) | |
4522 | (field (bbdb-current-field)) | |
4523 | (menu (if (and record field (functionp bbdb-user-menu-commands)) | |
4524 | (funcall bbdb-user-menu-commands record field) | |
4525 | bbdb-user-menu-commands))) | |
4526 | (if record | |
4527 | (popup-menu | |
4528 | (append | |
4529 | (list | |
4530 | (format "Commands for record \"%s\":" (bbdb-record-name record)) | |
4531 | ["Delete Record" bbdb-delete-records t] | |
4532 | ["Toggle Record Display Layout" bbdb-toggle-records-layout t] | |
4533 | (if (and (not (eq 'full-multi-line | |
4534 | (nth 1 (assq record bbdb-records)))) | |
4535 | (bbdb-layout-get-option 'multi-line 'omit)) | |
4536 | ["Fully Display Record" bbdb-display-records-completely t]) | |
4537 | ["Omit Record" bbdb-omit-record t] | |
4538 | ["Merge Record" bbdb-merge-records t]) | |
4539 | (if (bbdb-record-mail record) | |
4540 | (bbdb-sendmail-menu record)) | |
4541 | (list "--" (bbdb-insert-field-menu record)) | |
4542 | (if field | |
4543 | (cons "--" (bbdb-field-menu record field))) | |
4544 | (if menu | |
4545 | (append '("--" "User Defined Commands") menu))))))) | |
4546 | ||
4547 | \f | |
4548 | ||
4549 | (defun bbdb-scan-property (property predicate n) | |
4550 | "Scan for change of PROPERTY matching PREDICATE for N times. | |
4551 | Return position of beginning of matching interval." | |
4552 | (let ((fun (if (< 0 n) 'next-single-property-change | |
4553 | 'previous-single-property-change)) | |
4554 | (limit (if (< 0 n) (point-max) (point-min))) | |
4555 | (nn (abs n)) | |
4556 | (i 0) | |
4557 | (opoint (point)) | |
4558 | npoint) | |
4559 | ;; For backward search, move point to beginning of interval with PROPERTY. | |
4560 | (if (and (<= n 0) | |
4561 | (< (point-min) opoint) | |
4562 | (let ((prop (get-text-property opoint property))) | |
4563 | (and (eq prop (get-text-property (1- opoint) property)) | |
4564 | (funcall predicate prop)))) | |
4565 | (setq opoint (previous-single-property-change opoint property nil limit))) | |
4566 | (if (zerop n) | |
4567 | opoint ; Return beginning of interval point is in | |
4568 | (while (and (< i nn) | |
4569 | (let (done) | |
4570 | (while (and (not done) | |
4571 | (setq npoint (funcall fun opoint property nil limit))) | |
4572 | (cond ((and (/= opoint npoint) | |
4573 | (funcall predicate (get-text-property | |
4574 | npoint property))) | |
4575 | (setq opoint npoint done t)) | |
4576 | ((= opoint npoint) | |
4577 | ;; Search reached beg or end of buffer: abort. | |
4578 | (setq done t i nn npoint nil)) | |
4579 | (t (setq opoint npoint)))) | |
4580 | done)) | |
4581 | (setq i (1+ i))) | |
4582 | npoint))) | |
4583 | ||
4584 | (defun bbdb-next-record (n) | |
4585 | "Move point to the beginning of the next BBDB record. | |
4586 | With prefix N move forward N records." | |
4587 | (interactive "p") | |
4588 | (let ((npoint (bbdb-scan-property 'bbdb-record-number 'integerp n))) | |
4589 | (if npoint (goto-char npoint) | |
4590 | (error "No %s record" (if (< 0 n) "next" "previous"))))) | |
4591 | ||
4592 | (defun bbdb-prev-record (n) | |
4593 | "Move point to the beginning of the previous BBDB record. | |
4594 | With prefix N move backwards N records." | |
4595 | (interactive "p") | |
4596 | (bbdb-next-record (- n))) | |
4597 | ||
4598 | (defun bbdb-next-field (n) | |
4599 | "Move point to next (sub)field. | |
4600 | With prefix N move forward N (sub)fields." | |
4601 | (interactive "p") | |
4602 | (let ((npoint (bbdb-scan-property | |
4603 | 'bbdb-field | |
4604 | (lambda (p) (and (nth 1 p) | |
4605 | (not (eq (nth 2 p) 'field-label)))) | |
4606 | n))) | |
4607 | (if npoint (goto-char npoint) | |
4608 | (error "No %s field" (if (< 0 n) "next" "previous"))))) | |
4609 | ||
4610 | (defun bbdb-prev-field (n) | |
4611 | "Move point to previous (sub)field. | |
4612 | With prefix N move backwards N (sub)fields." | |
4613 | (interactive "p") | |
4614 | (bbdb-next-field (- n))) | |
4615 | ||
4616 | (defun bbdb-save (&optional prompt noisy) | |
4617 | "Save the BBDB if it is modified. | |
4618 | If PROMPT is non-nil prompt before saving. | |
4619 | If NOISY is non-nil as in interactive calls issue status messages." | |
4620 | (interactive (list nil t)) | |
4621 | (bbdb-with-db-buffer | |
4622 | (if (buffer-modified-p) | |
4623 | (if (or (not prompt) | |
4624 | (y-or-n-p | |
4625 | (if bbdb-read-only | |
4626 | "Save the BBDB, even though it is supposedly read-only? " | |
4627 | "Save the BBDB now? "))) | |
4628 | (save-buffer)) | |
4629 | (if noisy (message "(No BBDB changes need to be saved)"))))) | |
4630 | ||
4631 | ;;;###autoload | |
4632 | (defun bbdb-version (&optional arg) | |
4633 | "Return string describing the version of BBDB. | |
4634 | With prefix ARG, insert string at point." | |
4635 | (interactive (list (or (and current-prefix-arg 1) t))) | |
4636 | (let* ((version | |
4637 | (if (string-match "\\`[ \t\n]*[1-9]" bbdb-version) | |
4638 | bbdb-version | |
4639 | (let ((source (find-function-noselect 'bbdb-version))) | |
4640 | (if source | |
4641 | (with-current-buffer (car source) | |
4642 | (prog1 (save-excursion | |
4643 | (goto-char (point-min)) | |
4644 | (when (re-search-forward | |
4645 | "^;;+ *Version: \\(.*\\)" nil t) | |
4646 | (match-string-no-properties 1))) | |
4647 | (unless (get-buffer-window nil t) | |
4648 | (kill-buffer (current-buffer))))))))) | |
4649 | (version-string (format "BBDB version %s" (or version "<unknown>")))) | |
4650 | (cond ((numberp arg) (insert (message version-string))) | |
4651 | ((eq t arg) (message version-string)) | |
4652 | (t version-string)))) | |
4653 | ||
4654 | \f | |
4655 | ||
4656 | (defun bbdb-sort-records () | |
4657 | "Sort BBDB database. | |
4658 | This is not needed when using BBDB itself. It might be necessary, | |
4659 | however, after having used other programs to add records to the BBDB." | |
4660 | (interactive) | |
4661 | (let* ((records (copy-sequence (bbdb-records)))) | |
4662 | (bbdb-with-db-buffer | |
4663 | (setq bbdb-records (sort bbdb-records 'bbdb-record-lessp)) | |
4664 | (if (equal records bbdb-records) | |
4665 | (message "BBDB already sorted properly") | |
4666 | (message "BBDB was mis-sorted; fixing...") | |
4667 | (bbdb-goto-first-record) | |
4668 | (delete-region (point) bbdb-end-marker) | |
4669 | (let ((buf (current-buffer)) | |
4670 | (inhibit-quit t) ; really, don't mess with this | |
4671 | cache) | |
4672 | (dolist (record bbdb-records) | |
4673 | ;; Before printing the record, remove cache (we do not want that | |
4674 | ;; written to the file.) Ater writing, put the cache back | |
4675 | ;; and update the cache's marker. | |
4676 | (setq cache (bbdb-record-cache record)) | |
4677 | (set-marker (bbdb-cache-marker cache) (point)) | |
4678 | (bbdb-record-set-cache record nil) | |
4679 | (bbdb-with-print-loadably (prin1 record buf)) | |
4680 | (bbdb-record-set-cache record cache) | |
4681 | (insert ?\n))) | |
4682 | (dolist (buffer (buffer-list)) | |
4683 | (with-current-buffer buffer | |
4684 | (if (eq major-mode 'bbdb-mode) | |
4685 | ; Redisplay all records | |
4686 | (bbdb-display-records nil nil t)))) | |
4687 | (message "BBDB was mis-sorted; fixing...done"))))) | |
4688 | ||
4689 | \f | |
4690 | ||
4691 | ;;;###autoload | |
4692 | (defun bbdb-initialize (&rest muas) | |
4693 | "Initialize BBDB for MUAS and miscellaneous packages. | |
4694 | List MUAS may include the following symbols to initialize the respective | |
4695 | mail/news readers, composers, and miscellaneous packages: | |
4696 | gnus Gnus mail/news reader. | |
4697 | mh-e MH-E mail reader. | |
4698 | mu4e Mu4e mail reader. | |
4699 | rmail Rmail mail reader. | |
4700 | vm VM mail reader. | |
4701 | mail Mail (M-x mail). | |
4702 | message Message mode. | |
4703 | wl Wanderlust mail reader. | |
4704 | ||
4705 | anniv Anniversaries in Emacs diary. | |
4706 | ||
4707 | sc Supercite. However, this is not the full story. | |
4708 | See bbdb-sc.el for how to fully hook BBDB into Supercite. | |
4709 | ||
4710 | pgp PGP support: this adds `bbdb-pgp' to `message-send-hook' | |
4711 | and `mail-send-hook' so that `bbdb-pgp' runs automatically | |
4712 | when a message is sent. | |
4713 | Yet see info node `(message)Signing and encryption' | |
4714 | why you might not want to rely for encryption on a hook | |
4715 | function which runs just before the message is sent, | |
4716 | that is, you might want to call the command `bbdb-pgp' manually, | |
4717 | then call `mml-preview'. | |
4718 | ||
4719 | See also `bbdb-mua-auto-update-init'. The latter is a separate function | |
4720 | as this allows one to initialize the auto update feature for some MUAs only, | |
4721 | for example only for outgoing messages." | |
4722 | (dolist (mua muas) | |
4723 | (let ((init (assq mua bbdb-init-forms))) | |
4724 | (if init | |
4725 | ;; Should we make sure that each insinuation happens only once? | |
4726 | (eval (cadr init)) | |
4727 | (bbdb-warn "Do not know how to insinuate `%s'" mua)))) | |
4728 | (run-hooks 'bbdb-initialize-hook)) | |
4729 | ||
4730 | \f | |
4731 | (provide 'bbdb) | |
4732 | ||
4733 | ;;; bbdb.el ends here |