(lsdb-modeline-update): Exclude the case 'net is nil.
[elisp/lsdb.git] / lsdb.el
1 ;;; lsdb.el --- the Lovely Sister Database
2
3 ;; Copyright (C) 2002 Daiki Ueno
4
5 ;; Author: Daiki Ueno <ueno@unixuser.org>
6 ;; Keywords: adress book
7
8 ;; This file is part of the Lovely Sister Database.
9
10 ;; This program is free software; you can redistribute it and/or
11 ;; modify it under the terms of the GNU General Public License as
12 ;; published by the Free Software Foundation; either version 2, or (at
13 ;; your option) any later version.
14
15 ;; This program is distributed in the hope that it will be useful, but
16 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 ;; General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with this program; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;;; For Semi-gnus:
28 ;;; (autoload 'lsdb-gnus-insinuate "lsdb")
29 ;;; (autoload 'lsdb-gnus-insinuate-message "lsdb")
30 ;;; (add-hook 'gnus-startup-hook 'lsdb-gnus-insinuate)
31 ;;; (add-hook 'message-setup-hook
32 ;;;           (lambda ()
33 ;;;             (define-key message-mode-map "\M-\t" 'lsdb-complete-name)))
34 ;;; (add-hook 'gnus-summary-mode-hook
35 ;;;           (lambda ()
36 ;;;             (define-key gnus-summary-mode-map ":" 'lsdb-toggle-buffer)))
37
38 ;;; For Wanderlust, put the following lines into your ~/.wl:
39 ;;; (require 'lsdb)
40 ;;; (lsdb-wl-insinuate)
41 ;;; (add-hook 'wl-draft-mode-hook
42 ;;;           (lambda ()
43 ;;;             (define-key wl-draft-mode-map "\M-\t" 'lsdb-complete-name)))
44 ;;; (add-hook 'wl-summary-mode-hook
45 ;;;           (lambda ()
46 ;;;             (define-key wl-summary-mode-map ":" 'lsdb-wl-toggle-buffer)))
47
48 ;;; For Mew, put the following lines into your ~/.mew:
49 ;;; (autoload 'lsdb-mew-insinuate "lsdb")
50 ;;; (add-hook 'mew-init-hook 'lsdb-mew-insinuate)
51 ;;; (add-hook 'mew-draft-mode-hook
52 ;;;           (lambda ()
53 ;;;             (define-key mew-draft-header-map "\M-I" 'lsdb-complete-name)))
54 ;;; (add-hook 'mew-summary-mode-hook
55 ;;;           (lambda ()
56 ;;;             (define-key mew-summary-mode-map "l" 'lsdb-toggle-buffer)))
57
58 ;;; Code:
59
60 (require 'poem)
61 (require 'pces)
62 (require 'mime)
63 (require 'static)
64
65 ;;;_* USER CUSTOMIZATION VARIABLES:
66 (defgroup lsdb nil
67   "The Lovely Sister Database."
68   :group 'news
69   :group 'mail)
70   
71 (defcustom lsdb-file (expand-file-name "~/.lsdb")
72   "The name of the Lovely Sister Database file."
73   :group 'lsdb
74   :type 'file)
75
76 (defcustom lsdb-file-coding-system (find-coding-system 'ctext)
77   "Coding system for `lsdb-file'."
78   :group 'lsdb
79   :type 'symbol)
80
81 (defcustom lsdb-sender-headers
82   "From\\|Resent-From"
83   "List of headers to search for senders."
84   :group 'lsdb
85   :type 'list)
86
87 (defcustom lsdb-recipients-headers
88   "Resent-To\\|Resent-Cc\\|Reply-To\\|To\\|Cc\\|Bcc"
89   "List of headers to search for recipients."
90   :group 'lsdb
91   :type 'list)
92
93 (defcustom lsdb-interesting-header-alist
94   '(("Organization" nil organization)
95     ("\\(X-\\)?User-Agent\\|X-Mailer\\|X-Newsreader" nil user-agent)
96     ("\\(X-\\)?ML-Name" nil mailing-list)
97     ("List-Id" "\\(.*\\)[ \t]+<[^>]+>\\'" mailing-list "\\1")
98     ("X-Sequence" "\\(.*\\)[ \t]+[0-9]+\\'" mailing-list "\\1")
99     ("Delivered-To" "mailing list[ \t]+\\([^@]+\\)@.*" mailing-list "\\1")
100     ("\\(X-URL\\|X-URI\\)" nil www)
101     ("X-Attribution\\|X-cite-me" nil attribution)
102     ("X-Face" nil x-face))
103   "Alist of headers we are interested in.
104 The format of elements of this list should be
105      (FIELD-NAME REGEXP ENTRY STRING)
106 where the last three elements are optional."
107   :group 'lsdb
108   :type 'list)
109
110 (defcustom lsdb-entry-type-alist
111   '((net 5 ?,)
112     (creation-date 2 ?. t)
113     (last-modified 3 ?. t)
114     (mailing-list 4 ?,)
115     (attribution 4 ?.)
116     (organization 4)
117     (www 4)
118     (aka 4 ?,)
119     (score -1)
120     (x-face -1))
121   "Alist of entry types for presentation.
122 The format of elements of this list should be
123      (ENTRY SCORE [CLASS READ-ONLY])
124 where the last two elements are optional.
125 Possible values for CLASS are `?.' and '?,'.  If CLASS is `?.', the
126 entry takes a unique value which is overridden by newly assigned one
127 by `lsdb-mode-edit-entry' or such a command.  If CLASS is `?,', the
128 entry can have multiple values separated by commas.
129 If the fourth element READ-ONLY is non-nil, it is assumed that the
130 entry cannot be modified."
131   :group 'lsdb
132   :type 'list)
133
134 (defcustom lsdb-decode-field-body-function #'lsdb-decode-field-body
135   "Field body decoder."
136   :group 'lsdb
137   :type 'function)
138
139 (defcustom lsdb-canonicalize-full-name-function
140   #'lsdb-canonicalize-spaces-and-dots
141   "Way to canonicalize full name."
142   :group 'lsdb
143   :type 'function)
144
145 (defcustom lsdb-lookup-full-name-functions
146   '(lsdb-lookup-full-name-from-address-cache)
147   "List of functions to pick up the existing full-name of the sender.
148 The sender is passed to each function as the argument."
149   :group 'lsdb
150   :type 'hook)
151
152 (defcustom lsdb-update-record-functions
153   '(lsdb-update-address-cache)
154   "List of functions called after a record is updated.
155 The updated record is passed to each function as the argument."
156   :group 'lsdb
157   :type 'hook)
158
159 (defcustom lsdb-secondary-hash-tables
160   '(lsdb-address-cache)
161   "List of the hash tables for reverse lookup"
162   :group 'lsdb
163   :type 'list)
164
165 (defcustom lsdb-window-max-height 7
166   "Maximum number of lines used to display LSDB record."
167   :group 'lsdb
168   :type 'integer)
169
170 (defcustom lsdb-x-face-image-type nil
171   "A image type of displayed x-face.
172 If non-nil, supersedes the return value of `lsdb-x-face-available-image-type'."
173   :group 'lsdb
174   :type 'symbol)
175
176 (defcustom lsdb-x-face-command-alist
177   '((pbm "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | pnmscale 0.5")
178     (xpm "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | pnmscale 0.5 | ppmtoxpm"))
179   "An alist from an image type to a command to be executed to display an X-Face header.
180 The command will be executed in a sub-shell asynchronously.
181 The compressed face will be piped to this command."
182   :group 'lsdb
183   :type 'list)
184
185 (defcustom lsdb-insert-x-face-function
186   (if (static-if (featurep 'xemacs)
187           (featurep 'xpm)
188         (and (>= emacs-major-version 21)
189              (fboundp 'image-type-available-p)
190              (or (image-type-available-p 'pbm)
191                  (image-type-available-p 'xpm))))
192       #'lsdb-insert-x-face-asynchronously)
193   "Function to display X-Face."
194   :group 'lsdb
195   :type 'function)
196
197 (defcustom lsdb-print-record-hook '(lsdb-expose-x-face)
198   "A hook called after a record is displayed."
199   :group 'lsdb
200   :type 'hook)
201
202 (defcustom lsdb-display-records-sort-predicate nil
203   "A predicate to sort records."
204   :group 'lsdb
205   :type 'function)
206
207 (defcustom lsdb-pop-up-windows t
208   "Non-nil means LSDB should make new windows to display records."
209   :group 'lsdb
210   :type 'boolean)
211
212 (defgroup lsdb-edit-form nil
213   "A mode for editing forms."
214   :group 'lsdb)
215
216 (defcustom lsdb-edit-form-mode-hook nil
217   "Hook run in `lsdb-edit-form-mode' buffers."
218   :group 'lsdb-edit-form
219   :type 'hook)
220
221 (defcustom lsdb-shell-file-name "/bin/sh"
222   "File name to load inferior shells from.
223 Bourne shell or its equivalent \(not tcsh) is needed for \"2>\"."
224   :group 'lsdb
225   :type 'string)
226
227 (defcustom lsdb-shell-command-switch "-c"
228   "Switch used to have the shell execute its command line argument."
229   :group 'lsdb
230   :type 'string)
231
232 ;;;_. Faces
233 (defface lsdb-header-face
234   '((t (:underline t)))
235   "Face for the file header line in `lsdb-mode'."
236   :group 'lsdb)
237 (defvar lsdb-header-face 'lsdb-header-face)
238
239 (defface lsdb-field-name-face
240   '((((class color) (background dark))
241      (:foreground "PaleTurquoise" :bold t))
242     (t (:bold t)))
243   "Face for the message header line in `lsdb-mode'."
244   :group 'lsdb)
245 (defvar lsdb-field-name-face 'lsdb-field-name-face)
246
247 (defface lsdb-field-body-face
248   '((((class color) (background dark))
249      (:foreground "turquoise" :italic t))
250     (t (:italic t)))
251   "Face for the message header line in `lsdb-mode'."
252   :group 'lsdb)
253 (defvar lsdb-field-body-face 'lsdb-field-body-face)
254
255 (defconst lsdb-font-lock-keywords
256   '(("^\\sw[^\r\n]*"
257      (0 lsdb-header-face))
258     ("^\t\t.*$"
259      (0 lsdb-field-body-face))
260     ("^\t\\([^\t:]+:\\)[ \t]*\\(.*\\)$"
261      (1 lsdb-field-name-face)
262      (2 lsdb-field-body-face))))
263
264 (put 'lsdb-mode 'font-lock-defaults '(lsdb-font-lock-keywords t))
265
266 ;;;_* CODE - no user customizations below
267 ;;;_. Internal Variables
268 (defvar lsdb-hash-table nil
269   "Internal hash table to hold LSDB records.")
270
271 (defvar lsdb-address-cache nil
272   "The reverse lookup table for `lsdb-hash-table'.
273 It represents address to full-name mapping.")
274
275 (defvar lsdb-buffer-name "*LSDB*"
276   "Buffer name to display LSDB record.")
277
278 (defvar lsdb-hash-tables-are-dirty nil
279   "Flag to indicate whether the internal hash tables need to be saved.")
280
281 (defvar lsdb-known-entry-names
282   (make-vector 29 0)
283   "An obarray used to complete an entry name.")
284
285 (defvar lsdb-temp-buffer-show-function
286   #'lsdb-temp-buffer-show-function
287   "Non-nil means call as function to display a help buffer.
288 The function is called with one argument, the buffer to be displayed.
289 Overrides `temp-buffer-show-function'.")
290
291 ;;;_. Hash Table Emulation
292 (if (and (fboundp 'make-hash-table)
293          (subrp (symbol-function 'make-hash-table)))
294     (progn
295       (defalias 'lsdb-puthash 'puthash)
296       (defalias 'lsdb-gethash 'gethash)
297       (defalias 'lsdb-remhash 'remhash)
298       (defalias 'lsdb-maphash 'maphash)
299       (defalias 'lsdb-hash-table-size 'hash-table-size)
300       (defalias 'lsdb-hash-table-count 'hash-table-count)
301       (defalias 'lsdb-make-hash-table 'make-hash-table))
302   (defun lsdb-puthash (key value hash-table)
303     "Hash KEY to VALUE in HASH-TABLE."
304     ;; Obarray is regarded as an open hash table, as a matter of
305     ;; fact, rehashing doesn't make sense.
306     (let (new-obarray)
307       (when (> (car hash-table)
308                (* (length (nth 1 hash-table)) 0.7))
309         (setq new-obarray (make-vector (* (length (nth 1 hash-table)) 2) 0))
310         (mapatoms
311          (lambda (symbol)
312            (set (intern (symbol-name symbol) new-obarray)
313                 (symbol-value symbol)))
314          (nth 1 hash-table))
315         (setcdr hash-table (list new-obarray)))
316       (set (intern key (nth 1 hash-table)) value)
317       (setcar hash-table (1+ (car hash-table)))))
318   (defun lsdb-gethash (key hash-table &optional default)
319     "Find hash value for KEY in HASH-TABLE.
320 If there is no corresponding value, return DEFAULT (which defaults to nil)."
321     (let ((symbol (intern-soft key (nth 1 hash-table))))
322       (if symbol
323           (symbol-value symbol)
324         default)))
325   (defun lsdb-remhash (key hash-table)
326     "Remove the entry for KEY from HASH-TABLE.
327 Do nothing if there is no entry for KEY in HASH-TABLE."
328     (unintern key (nth 1 hash-table))
329     (setcar hash-table (1- (car hash-table))))
330   (defun lsdb-maphash (function hash-table)
331     "Map FUNCTION over entries in HASH-TABLE, calling it with two args,
332 each key and value in HASH-TABLE.
333
334 FUNCTION may not modify HASH-TABLE, with the one exception that FUNCTION
335 may remhash or puthash the entry currently being processed by FUNCTION."
336     (mapatoms
337      (lambda (symbol)
338        (funcall function (symbol-name symbol) (symbol-value symbol)))
339      (nth 1 hash-table)))
340   (defun lsdb-hash-table-size (hash-table)
341     "Return the size of HASH-TABLE.
342 This is the current number of slots in HASH-TABLE, whether occupied or not."
343     (length (nth 1 hash-table)))
344   (defalias 'lsdb-hash-table-count 'car)
345   (defun lsdb-make-hash-table (&rest args)
346     "Return a new empty hash table object."
347     (list 0 (make-vector (or (plist-get args :size) 29) 0))))
348
349 ;;;_. Hash Table Reader/Writer
350 (defconst lsdb-secondary-hash-table-start-format
351   ";;; %S\n")
352
353 (defsubst lsdb-secondary-hash-table-start (hash-table)
354   (format lsdb-secondary-hash-table-start-format hash-table))
355
356 (eval-and-compile
357   (condition-case nil
358       (and
359        ;; In XEmacs, hash tables can also be created by the lisp reader
360        ;; using structure syntax.
361        (read-from-string "#s(hash-table)")
362        (defalias 'lsdb-read 'read))
363     (invalid-read-syntax
364      (defun lsdb-read (&optional marker)
365        "Read one Lisp expression as text from MARKER, return as Lisp object."
366        (save-excursion
367          (goto-char marker)
368          (if (looking-at "^#s(")
369              (with-temp-buffer
370                (buffer-disable-undo)
371                (insert-buffer-substring (marker-buffer marker) marker)
372                (goto-char (point-min))
373                (delete-char 2)
374                (let ((object (read (current-buffer)))
375                      hash-table data)
376                  (if (eq 'hash-table (car object))
377                      (progn
378                        (setq hash-table
379                              (lsdb-make-hash-table
380                               :size (plist-get (cdr object) 'size)
381                               :test 'equal)
382                              data (plist-get (cdr object) 'data))
383                        (while data
384                          (lsdb-puthash (pop data) (pop data) hash-table))
385                        hash-table)
386                    object)))))))))
387
388 (defun lsdb-load-hash-tables ()
389   "Read the contents of `lsdb-file' into the internal hash tables."
390   (let ((buffer (find-file-noselect lsdb-file))
391         tables)
392     (unwind-protect
393         (save-excursion
394           (set-buffer buffer)
395           (goto-char (point-min))
396           (re-search-forward "^#s(")
397           (goto-char (match-beginning 0))
398           (setq lsdb-hash-table (lsdb-read (point-marker)))
399           ;; Load the secondary hash tables following.
400           (setq tables lsdb-secondary-hash-tables)
401           (while tables
402             (if (re-search-forward
403                  (concat "^" (lsdb-secondary-hash-table-start
404                               (car tables)))
405                  nil t)
406                 (set (car tables) (lsdb-read (point-marker))))
407             (setq tables (cdr tables))))
408       (kill-buffer buffer))))
409
410 (defun lsdb-insert-hash-table (hash-table)
411   (insert "#s(hash-table size "
412           ;; Reduce the actual size of the close hash table, because
413           ;; XEmacs doesn't have a distinction between index-size and
414           ;; hash-table-size.
415           (number-to-string (lsdb-hash-table-count hash-table))
416           " test equal data (")
417   (lsdb-maphash
418    (lambda (key value)
419      (insert (prin1-to-string key) " " (prin1-to-string value) " "))
420    hash-table)
421   (insert "))"))
422
423 (defun lsdb-save-hash-tables ()
424   "Write the records within the internal hash tables into `lsdb-file'."
425   (let ((coding-system-for-write lsdb-file-coding-system)
426         tables)
427     (with-temp-file lsdb-file
428       (if (and (or (featurep 'mule)
429                    (featurep 'file-coding))
430                lsdb-file-coding-system)
431           (let ((coding-system-name
432                  (if (symbolp lsdb-file-coding-system)
433                      (symbol-name lsdb-file-coding-system)
434                    ;; XEmacs
435                    (static-if (featurep 'xemacs)
436                        (symbol-name (coding-system-name
437                                      lsdb-file-coding-system))))))
438             (if coding-system-name
439                 (insert ";;; -*- coding: " coding-system-name " -*-\n"))))
440       (lsdb-insert-hash-table lsdb-hash-table)
441       ;; Save the secondary hash tables following.
442       (setq tables lsdb-secondary-hash-tables)
443       (while tables
444         (insert "\n" (lsdb-secondary-hash-table-start
445                       (car tables)))
446         (lsdb-insert-hash-table (symbol-value (car tables)))
447         (setq tables (cdr tables))))))
448
449 ;;;_. Mail Header Extraction
450 (defun lsdb-fetch-field-bodies (regexp)
451   (save-excursion
452     (goto-char (point-min))
453     (let ((case-fold-search t)
454           field-bodies)
455       (while (re-search-forward (concat "^\\(" regexp "\\):[ \t]*")
456                                 nil t)
457         (push (funcall lsdb-decode-field-body-function
458                              (buffer-substring (point) (std11-field-end))
459                              (match-string 1))
460                     field-bodies))
461       (nreverse field-bodies))))
462
463 (defun lsdb-canonicalize-spaces-and-dots (string)
464   (while (string-match "  +\\|[\f\t\n\r\v]+\\|\\." string)
465     (setq string (replace-match " " nil t string)))
466   string)
467
468 (defun lsdb-extract-address-components (string)
469   (let ((components (std11-extract-address-components string)))
470     (if (and (nth 1 components)
471              ;; When parsing a group address,
472              ;; std11-extract-address-components is likely to return
473              ;; the ("GROUP" "") form.
474              (not (equal (nth 1 components) "")))
475         (if (car components)
476             (list (funcall lsdb-canonicalize-full-name-function
477                            (car components))
478                   (nth 1 components))
479           (list (nth 1 components) (nth 1 components))))))
480
481 ;; stolen (and renamed) from nnheader.el
482 (defun lsdb-decode-field-body (field-body field-name
483                                           &optional mode max-column)
484   (let ((multibyte enable-multibyte-characters))
485     (unwind-protect
486         (progn
487           (set-buffer-multibyte t)
488           (mime-decode-field-body field-body
489                                   (if (stringp field-name)
490                                       (intern (capitalize field-name))
491                                     field-name)
492                                   mode max-column))
493       (set-buffer-multibyte multibyte))))
494
495 ;;;_. Record Management
496 (defun lsdb-rebuild-secondary-hash-tables (&optional force)
497   (let ((tables lsdb-secondary-hash-tables))
498     (while tables
499       (when (or force (not (symbol-value (car tables))))
500         (set (car tables) (lsdb-make-hash-table :test 'equal))
501         (lsdb-maphash
502          (lambda (key value)
503            (run-hook-with-args
504             'lsdb-update-record-functions
505             (cons key value)))
506          lsdb-hash-table)
507         (setq lsdb-hash-tables-are-dirty t))
508       (setq tables (cdr tables)))))
509
510 (defun lsdb-maybe-load-hash-tables ()
511   (unless lsdb-hash-table
512     (if (file-exists-p lsdb-file)
513         (lsdb-load-hash-tables)
514       (setq lsdb-hash-table (lsdb-make-hash-table :test 'equal)))
515     (lsdb-rebuild-secondary-hash-tables)))
516
517 ;;;_ : Fallback Lookup Functions
518 ;;;_  , #1 Address Cache
519 (defun lsdb-lookup-full-name-from-address-cache (sender)
520   (lsdb-gethash (nth 1 sender) lsdb-address-cache))
521
522 (defun lsdb-update-address-cache (record)
523   (let ((net (cdr (assq 'net record))))
524     (while net
525       (lsdb-puthash (pop net) (car record) lsdb-address-cache))))
526
527 ;;;_  , #2 Iterate on the All Records (very slow)
528 (defun lsdb-lookup-full-name-by-fuzzy-matching (sender)
529   (let ((names
530          (if (string-match
531               "\\`\\(.+\\)[ \t]+\\(/[ \t]+\\|(\\([^)]+\\))\\)"
532               (car sender))
533              (if (match-beginning 3)
534                  (list (match-string 1 (car sender))
535                        (match-string 3 (car sender)))
536                (list (match-string 1 (car sender))
537                      (substring (car sender) (match-end 0))))
538            (list (car sender))))
539         (case-fold-search t))
540     (catch 'found
541       (lsdb-maphash
542        (lambda (key value)
543          (while names
544            (if (or (string-match
545                     (concat "\\<" (regexp-quote (car names)) "\\>")
546                     key)
547                    (string-match
548                     (concat
549                      "\\<"
550                      (regexp-quote
551                       (mapconcat #'identity
552                                  (nreverse (split-string (car names)))
553                                  " "))
554                      "\\>")
555                     key)
556                    ;; Don't assume that we are using address cache.
557                    (member (nth 1 sender) (cdr (assq 'net value))))
558                (throw 'found key))
559            (setq names (cdr names))))
560        lsdb-hash-table))))
561
562 ;;;_ : Update Records
563 (defun lsdb-update-record (sender &optional interesting)
564   (let ((old (lsdb-gethash (car sender) lsdb-hash-table))
565         (new (cons (cons 'net (list (nth 1 sender)))
566                    interesting))
567         merged
568         record
569         full-name)
570     ;; Look for the existing record from the reverse hash table.
571     ;; If it is found, regsiter the current full-name as AKA.
572     (unless old
573       (setq full-name
574             (run-hook-with-args-until-success
575              'lsdb-lookup-full-name-functions
576              sender))
577       (when full-name
578         (setq old (lsdb-gethash full-name lsdb-hash-table)
579               new (cons (list 'aka (car sender)) new))
580         (setcar sender full-name)))
581     (unless old
582       (setq new (cons (cons 'creation-date (format-time-string "%Y-%m-%d"))
583                       new)))
584     (setq merged (lsdb-merge-record-entries old new)
585           record (cons (car sender) merged))
586     (unless (equal merged old)
587       (let ((entry (assq 'last-modified (cdr record)))
588             (last-modified (format-time-string "%Y-%m-%d")))
589         (if entry
590             (setcdr entry last-modified)
591           (setcdr record (cons (cons 'last-modified last-modified)
592                                (cdr record)))))
593       (lsdb-puthash (car record) (cdr record)
594                     lsdb-hash-table)
595       (run-hook-with-args 'lsdb-update-record-functions record)
596       (setq lsdb-hash-tables-are-dirty t))
597     record))
598
599 (defun lsdb-update-records ()
600   (lsdb-maybe-load-hash-tables)
601   (let (senders recipients interesting alist records bodies entry)
602     (save-restriction
603       (std11-narrow-to-header)
604       (setq senders
605             (delq nil (mapcar #'lsdb-extract-address-components
606                               (lsdb-fetch-field-bodies
607                                lsdb-sender-headers)))
608             recipients
609             (delq nil (mapcar #'lsdb-extract-address-components
610                               (lsdb-fetch-field-bodies
611                                lsdb-recipients-headers))))
612       (setq alist lsdb-interesting-header-alist)
613       (while alist
614         (setq bodies
615               (delq nil (mapcar
616                          (lambda (field-body)
617                            (if (nth 1 (car alist))
618                                (and (string-match (nth 1 (car alist))
619                                                   field-body)
620                                     (replace-match (nth 3 (car alist))
621                                                    nil nil field-body))
622                              field-body))
623                          (lsdb-fetch-field-bodies (car (car alist))))))
624         (when bodies
625           (setq entry (or (nth 2 (car alist))
626                           'notes))
627           (push (cons entry
628                       (if (eq ?. (nth 2 (assq entry lsdb-entry-type-alist)))
629                           (car bodies)
630                         bodies))
631                 interesting))
632         (setq alist (cdr alist))))
633     (if senders
634         (setq records (list (lsdb-update-record (pop senders) interesting))))
635     (setq alist (nconc senders recipients))
636     (while alist
637       (setq records (cons (lsdb-update-record (pop alist)) records)))
638     (nreverse records)))
639
640 (defun lsdb-merge-record-entries (old new)
641   (setq old (copy-sequence old))
642   (while new
643     (let ((entry (assq (car (car new)) old))
644           list pointer)
645       (if (null entry)
646           (setq old (nconc old (list (car new))))
647         (if (listp (cdr entry))
648             (progn
649               (setq list (cdr (car new)) pointer list)
650               (while pointer
651                 (if (member (car pointer) (cdr entry))
652                     (setq list (delq (car pointer) list)))
653                 (setq pointer (cdr pointer)))
654               (setcdr entry (nconc (cdr entry) list)))
655           (setcdr entry (cdr (car new))))))
656     (setq new (cdr new)))
657   old)
658
659 ;;;_. Display Management
660 (defun lsdb-fit-window-to-buffer (&optional window)
661   (save-selected-window
662     (if window
663         (select-window window))
664     (unless (pos-visible-in-window-p (point-max))
665       (enlarge-window (- lsdb-window-max-height (window-height))))
666     (shrink-window-if-larger-than-buffer)
667     (let ((height (window-height)))
668       (if (> height lsdb-window-max-height)
669           (shrink-window (- height lsdb-window-max-height)))
670       (set-window-start window (point-min)))))
671
672 (defun lsdb-temp-buffer-show-function (buffer)
673   (when lsdb-pop-up-windows
674     (save-selected-window
675       (let ((window (or (get-buffer-window lsdb-buffer-name)
676                         (progn
677                           (select-window (get-largest-window))
678                           (split-window-vertically)))))
679         (set-window-buffer window buffer)
680         (lsdb-fit-window-to-buffer window)))))
681
682 (defun lsdb-display-record (record)
683   "Display only one RECORD, then shrink the window as possible."
684   (let ((temp-buffer-show-function lsdb-temp-buffer-show-function))
685     (lsdb-display-records (list record))))
686
687 (defun lsdb-display-records (records)
688   (with-output-to-temp-buffer lsdb-buffer-name
689     (set-buffer standard-output)
690     (setq records
691           (sort (copy-sequence records)
692                 (or lsdb-display-records-sort-predicate
693                     (lambda (record1 record2)
694                       (string-lessp (car record1) (car record2))))))
695     (while records
696       (save-restriction
697         (narrow-to-region (point) (point))
698         (lsdb-print-record (car records)))
699       (goto-char (point-max))
700       (setq records (cdr records)))
701     (lsdb-mode)))
702
703 (defsubst lsdb-entry-score (entry)
704   (or (nth 1 (assq (car entry) lsdb-entry-type-alist)) 0))
705
706 (defun lsdb-insert-entry (entry)
707   (let ((entry-name (capitalize (symbol-name (car entry)))))
708     (intern entry-name lsdb-known-entry-names)
709     (if (>= (lsdb-entry-score entry) 0)
710         (insert "\t" entry-name ": "
711                 (if (listp (cdr entry))
712                     (mapconcat
713                      #'identity (cdr entry)
714                      (if (eq ?, (nth 2 (assq (car entry)
715                                              lsdb-entry-type-alist)))
716                          ", "
717                        "\n\t\t"))
718                   (cdr entry))
719                 "\n"))))
720
721 (defun lsdb-print-record (record)
722   (insert (car record) "\n")
723   (let ((entries
724          (sort (copy-sequence (cdr record))
725                (lambda (entry1 entry2)
726                  (> (lsdb-entry-score entry1) (lsdb-entry-score entry2))))))
727     (while entries
728       (lsdb-insert-entry (car entries))
729       (setq entries (cdr entries))))
730   (add-text-properties (point-min) (point-max)
731                        (list 'lsdb-record record))
732   (run-hooks 'lsdb-print-record-hook))
733
734 ;;;_. Completion
735 (defvar lsdb-last-completion nil)
736 (defvar lsdb-last-candidates nil)
737 (defvar lsdb-last-candidates-pointer nil)
738 (defvar lsdb-complete-marker nil)
739
740 ;;;_ : Matching Highlight
741 (defvar lsdb-last-highlight-overlay nil)
742
743 (defun lsdb-complete-name-highlight (start end)
744   (make-local-hook 'pre-command-hook)
745   (add-hook 'pre-command-hook 'lsdb-complete-name-highlight-update nil t)
746   (save-excursion
747     (goto-char start)
748     (search-forward lsdb-last-completion end)
749     (setq lsdb-last-highlight-overlay
750           (make-overlay (match-beginning 0) (match-end 0)))
751     (overlay-put lsdb-last-highlight-overlay 'face
752                  (or (find-face 'isearch-secondary)
753                      (find-face 'isearch-lazy-highlight-face)
754                      'underline))))
755
756 (defun lsdb-complete-name-highlight-update ()
757   (unless (eq this-command 'lsdb-complete-name)
758     (if lsdb-last-highlight-overlay
759         (delete-overlay lsdb-last-highlight-overlay))
760     (set-marker lsdb-complete-marker nil)
761     (remove-hook 'pre-command-hook
762                  'lsdb-complete-name-highlight-update t)))
763
764 ;;;_ : Name Completion
765 (defun lsdb-complete-name ()
766   "Complete the user full-name or net-address before point"
767   (interactive)
768   (lsdb-maybe-load-hash-tables)
769   (unless (markerp lsdb-complete-marker)
770     (setq lsdb-complete-marker (make-marker)))
771   (let* ((start
772           (or (and (eq (marker-buffer lsdb-complete-marker) (current-buffer))
773                    (marker-position lsdb-complete-marker))
774               (save-excursion
775                 (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*")
776                 (set-marker lsdb-complete-marker (match-end 0)))))
777          pattern
778          (case-fold-search t)
779          (completion-ignore-case t))
780     (unless (eq last-command this-command)
781       (setq lsdb-last-candidates nil
782             lsdb-last-candidates-pointer nil
783             lsdb-last-completion (buffer-substring start (point))
784             pattern (concat "\\<" (regexp-quote lsdb-last-completion)))
785       (lsdb-maphash
786        (lambda (key value)
787          (let ((net (cdr (assq 'net value))))
788            (if (string-match pattern key)
789                (setq lsdb-last-candidates
790                      (nconc lsdb-last-candidates
791                             (mapcar (lambda (address)
792                                       (if (equal key address)
793                                           key
794                                         (concat key " <" address ">")))
795                                     net)))
796              (while net
797                (if (string-match pattern (car net))
798                    (push (car net) lsdb-last-candidates))
799                (setq net (cdr net))))))
800        lsdb-hash-table)
801       ;; Sort candidates by the position where the pattern occurred.
802       (setq lsdb-last-candidates
803             (sort lsdb-last-candidates
804                   (lambda (cand1 cand2)
805                     (< (if (string-match pattern cand1)
806                            (match-beginning 0))
807                        (if (string-match pattern cand2)
808                            (match-beginning 0)))))))
809     (unless lsdb-last-candidates-pointer
810       (setq lsdb-last-candidates-pointer lsdb-last-candidates))
811     (when lsdb-last-candidates-pointer
812       (delete-region start (point))
813       (insert (pop lsdb-last-candidates-pointer))
814       (lsdb-complete-name-highlight start (point)))))
815
816 ;;;_. Major Mode (`lsdb-mode') Implementation
817 ;;;_ : Modeline Buffer Identification
818 (defconst lsdb-pointer-xpm
819   "/* XPM */
820 static char * lsdb_pointer_xpm[] = {
821 \"14 14 5 1\",
822 \"      c None\",
823 \"+     c #FF9696\",
824 \"@     c #FF0000\",
825 \"#     c #FF7575\",
826 \"$     c #FF5959\",
827 \"              \",
828 \"  +++   @@@   \",
829 \" +++## @@@@@  \",
830 \" ++### @@@@@  \",
831 \" +#####@@@@@  \",
832 \" +###$$@@@@@  \",
833 \" +###$$@@@@@  \",
834 \"  ##$$$@@@@   \",
835 \"   #$$$@@@    \",
836 \"    $$@@@     \",
837 \"     $@@      \",
838 \"      @       \",
839 \"              \",
840 \"              \"};")
841
842 (static-if (featurep 'xemacs)
843     (progn
844       (defvar lsdb-xemacs-modeline-left-extent
845         (copy-extent modeline-buffer-id-left-extent))
846
847       (defvar lsdb-xemacs-modeline-right-extent
848         (copy-extent modeline-buffer-id-right-extent))
849
850       (defun lsdb-modeline-buffer-identification (line)
851         "Decorate 1st element of `mode-line-buffer-identification' LINE.
852 Modify whole identification by side effect."
853         (let ((id (car line)) chopped)
854           (if (and (stringp id) (string-match "^LSDB:" id))
855               (progn
856                 (setq chopped (substring id 0 (match-end 0))
857                       id (substring id (match-end 0)))
858                 (nconc
859                  (list
860                   (let ((glyph
861                          (make-glyph
862                           (nconc
863                            (if (featurep 'xpm)
864                                (list (vector 'xpm :data lsdb-pointer-xpm)))
865                            (list (vector 'string :data chopped))))))
866                     (set-glyph-face glyph 'modeline-buffer-id)
867                     (cons lsdb-xemacs-modeline-left-extent glyph))
868                   (cons lsdb-xemacs-modeline-right-extent id))
869                  (cdr line)))
870             line))))
871   (condition-case nil
872       (progn
873         (require 'image)
874         (defun lsdb-modeline-buffer-identification (line)
875           "Decorate 1st element of `mode-line-buffer-identification' LINE.
876 Modify whole identification by side effect."
877           (let ((id (copy-sequence (car line)))
878                 (image
879                  (if (image-type-available-p 'xpm)
880                      (create-image lsdb-pointer-xpm 'xpm t :ascent 'center))))
881             (when (and image
882                        (stringp id) (string-match "^LSDB:" id))
883               (add-text-properties 0 (length id)
884                                    (list 'display image
885                                          'rear-nonsticky (list 'display))
886                                    id)
887               (setcar line id))
888             line)))
889     (error
890      (defalias 'lsdb-modeline-buffer-identification 'identity))))
891
892 (defvar lsdb-mode-map
893   (let ((keymap (make-sparse-keymap)))
894     (define-key keymap "a" 'lsdb-mode-add-entry)
895     (define-key keymap "d" 'lsdb-mode-delete-entry)
896     (define-key keymap "e" 'lsdb-mode-edit-entry)
897     (define-key keymap "l" 'lsdb-mode-load)
898     (define-key keymap "s" 'lsdb-mode-save)
899     (define-key keymap "q" 'lsdb-mode-quit-window)
900     (define-key keymap "g" 'lsdb-mode-lookup)
901     (define-key keymap "p" 'lsdb-mode-previous-record)
902     (define-key keymap "n" 'lsdb-mode-next-record)
903     (define-key keymap " " 'scroll-up)
904     (define-key keymap [delete] 'scroll-down)
905     (define-key keymap "\177" 'scroll-down)
906     (define-key keymap [backspace] 'scroll-down)
907     keymap)
908   "LSDB's keymap.")
909
910 (defvar lsdb-modeline-string "")
911
912 (define-derived-mode lsdb-mode fundamental-mode "LSDB"
913   "Major mode for browsing LSDB records."
914   (setq buffer-read-only t)
915   (static-if (featurep 'xemacs)
916       ;; In XEmacs, setting `font-lock-defaults' only affects on
917       ;; `find-file-hooks'.
918       (font-lock-set-defaults)
919     (set (make-local-variable 'font-lock-defaults)
920          '(lsdb-font-lock-keywords t)))
921   (make-local-hook 'post-command-hook)
922   (add-hook 'post-command-hook 'lsdb-modeline-update nil t)
923   (make-local-variable 'lsdb-modeline-string)
924   (setq mode-line-buffer-identification
925         (lsdb-modeline-buffer-identification
926          '("LSDB: " lsdb-modeline-string)))
927   (lsdb-modeline-update)
928   (force-mode-line-update))
929
930 (defun lsdb-modeline-update ()
931   (let ((record
932          (get-text-property (if (eobp) (point-min) (point)) 'lsdb-record))
933         net)
934     (if record
935         (progn
936           (setq net (car (cdr (assq 'net (cdr record)))))
937           (if (and net (equal net (car record)))
938               (setq lsdb-modeline-string net)
939             (setq lsdb-modeline-string (concat (car record) " <" net ">"))))
940       (setq lsdb-modeline-string ""))))
941
942 (defun lsdb-narrow-to-record ()
943   "Narrow to the current record."
944   (let ((end (next-single-property-change (point) 'lsdb-record nil
945                                           (point-max))))
946     (narrow-to-region
947      (previous-single-property-change end 'lsdb-record nil (point-min))
948      end)
949     (goto-char (point-min))))
950
951 (defun lsdb-current-record ()
952   "Return the current record name."
953   (get-text-property (point) 'lsdb-record))
954
955 (defun lsdb-current-entry ()
956   "Return the current entry name in canonical form."
957   (save-excursion
958     (beginning-of-line)
959     (if (looking-at "^\t\\([^\t][^:]+\\):")
960         (intern (downcase (match-string 1))))))
961
962 (defun lsdb-read-entry (record &optional prompt)
963   "Prompt to select an entry in the given RECORD."
964   (let* ((completion-ignore-case t)
965          (entry-name
966           (completing-read
967            (or prompt
968                "Which entry: ")
969            (mapcar (lambda (entry)
970                      (list (capitalize (symbol-name (car entry)))))
971                    (cdr record))
972            nil t)))
973     (unless (equal entry-name "")
974       (intern (downcase entry-name)))))
975
976 (defun lsdb-mode-add-entry (entry-name)
977   "Add an entry on the current line."
978   (interactive
979    (let ((completion-ignore-case t))
980      (list (completing-read "Entry name: " lsdb-known-entry-names))))
981   (beginning-of-line)
982   (unless (symbolp entry-name)
983     (setq entry-name (intern (downcase entry-name))))
984   (when (assq entry-name (cdr (lsdb-current-record)))
985     (error "The entry already exists"))
986   (let ((marker (point-marker)))
987     (lsdb-edit-form
988      nil "Editing the entry."
989      `(lambda (form)
990         (when form
991           (save-excursion
992             (set-buffer lsdb-buffer-name)
993             (goto-char ,marker)
994             (let ((record (lsdb-current-record))
995                   (inhibit-read-only t)
996                   buffer-read-only)
997               (setcdr record (cons (cons ',entry-name form) (cdr record)))
998               (lsdb-puthash (car record) (cdr record)
999                             lsdb-hash-table)
1000               (run-hook-with-args 'lsdb-update-record-functions record)
1001               (setq lsdb-hash-tables-are-dirty t)
1002               (beginning-of-line 2)
1003               (add-text-properties
1004                (point)
1005                (progn
1006                  (lsdb-insert-entry (cons ',entry-name form))
1007                  (point))
1008                (list 'lsdb-record record)))))))))
1009
1010 (defun lsdb-mode-delete-entry (&optional entry-name dont-update)
1011   "Delete the entry on the current line."
1012   (interactive)
1013   (let ((record (lsdb-current-record))
1014         entry)
1015     (unless record
1016       (error "There is nothing to follow here"))
1017     (unless entry-name
1018       (setq entry-name (or (lsdb-current-entry)
1019                            (lsdb-read-entry
1020                             record "Which entry to delete: "))))
1021     (setq entry (assq entry-name (cdr record)))
1022     (when (and entry
1023                (not dont-update))
1024       (setcdr record (delq entry (cdr record)))
1025       (lsdb-puthash (car record) (cdr record)
1026                     lsdb-hash-table)
1027       (run-hook-with-args 'lsdb-update-record-functions record)
1028       (setq lsdb-hash-tables-are-dirty t))
1029     (save-restriction
1030       (lsdb-narrow-to-record)
1031       (let ((case-fold-search t)
1032             (inhibit-read-only t)
1033             buffer-read-only)
1034         (goto-char (point-min))
1035         (if (re-search-forward
1036              (concat "^\t" (capitalize (symbol-name
1037                                         (or entry-name
1038                                             (lsdb-current-entry))))
1039                      ":")
1040              nil t)
1041             (delete-region (match-beginning 0)
1042                            (if (re-search-forward
1043                                 "^\t[^\t][^:]+:" nil t)
1044                                (match-beginning 0)
1045                              (point-max))))))))
1046
1047 (defun lsdb-mode-edit-entry ()
1048   "Edit the entry on the current line."
1049   (interactive)
1050   (let ((record (lsdb-current-record))
1051         entry-name entry marker)
1052     (unless record
1053       (error "There is nothing to follow here"))
1054     (setq entry-name (or (lsdb-current-entry)
1055                          (lsdb-read-entry record "Which entry to edit: "))
1056           entry (assq entry-name (cdr record))
1057           marker (point-marker))
1058     (lsdb-edit-form
1059      (cdr entry) "Editing the entry."
1060      `(lambda (form)
1061         (unless (equal form ',(cdr entry))
1062           (save-excursion
1063             (set-buffer lsdb-buffer-name)
1064             (goto-char ,marker)
1065             (let ((record (lsdb-current-record))
1066                   entry
1067                   (inhibit-read-only t)
1068                   buffer-read-only)
1069               (unless record
1070                 (error "The entry currently in editing is discarded"))
1071               (setq entry (assq ',entry-name (cdr record)))
1072               (setcdr entry form)
1073               (run-hook-with-args 'lsdb-update-record-functions record)
1074               (setq lsdb-hash-tables-are-dirty t)
1075               (lsdb-mode-delete-entry ',entry-name t)
1076               (beginning-of-line)
1077               (add-text-properties
1078                (point)
1079                (progn
1080                  (lsdb-insert-entry (cons ',entry-name form))
1081                  (point))
1082                (list 'lsdb-record record)))))))))
1083
1084 (defun lsdb-mode-save (&optional dont-ask)
1085   "Save LSDB hash table into `lsdb-file'."
1086   (interactive)
1087   (if (not lsdb-hash-tables-are-dirty)
1088       (message "(No changes need to be saved)")
1089     (when (or (interactive-p)
1090               dont-ask
1091               (y-or-n-p "Save the LSDB now? "))
1092       (lsdb-save-hash-tables)
1093       (setq lsdb-hash-tables-are-dirty nil)
1094       (message "The LSDB was saved successfully."))))
1095
1096 (defun lsdb-mode-load ()
1097   "Load LSDB hash table from `lsdb-file'."
1098   (interactive)
1099   (let (lsdb-secondary-hash-tables)
1100     (lsdb-load-hash-tables))
1101   (message "Rebuilding secondary hash tables...")
1102   (lsdb-rebuild-secondary-hash-tables t)
1103   (message "Rebuilding secondary hash tables...done"))
1104
1105 (defun lsdb-mode-quit-window (&optional kill window)
1106   "Quit the current buffer.
1107 It partially emulates the GNU Emacs' of `quit-window'."
1108   (interactive "P")
1109   (unless window
1110     (setq window (selected-window)))
1111   (let ((buffer (window-buffer window)))
1112     (unless (save-selected-window
1113               (select-window window)
1114               (one-window-p))
1115       (delete-window window))
1116     (if kill
1117         (kill-buffer buffer)
1118       (bury-buffer (unless (eq buffer (current-buffer)) buffer)))))
1119
1120 (defun lsdb-hide-buffer ()
1121   "Hide the LSDB window."
1122   (let ((window (get-buffer-window lsdb-buffer-name)))
1123     (if window
1124         (lsdb-mode-quit-window nil window))))
1125
1126 (defun lsdb-show-buffer ()
1127   "Show the LSDB window."
1128   (if (get-buffer lsdb-buffer-name)
1129       (if lsdb-temp-buffer-show-function
1130           (let ((lsdb-pop-up-windows t))
1131             (funcall lsdb-temp-buffer-show-function lsdb-buffer-name))
1132         (pop-to-buffer lsdb-buffer-name))))
1133
1134 (defun lsdb-toggle-buffer (&optional arg)
1135   "Toggle hiding of the LSDB window.
1136 If given a negative prefix, always show; if given a positive prefix,
1137 always hide."
1138   (interactive
1139    (list (if current-prefix-arg
1140              (prefix-numeric-value current-prefix-arg)
1141            0)))
1142   (unless arg                           ;called noninteractively?
1143     (setq arg 0))
1144   (cond
1145    ((or (< arg 0)
1146         (and (zerop arg)
1147              (not (get-buffer-window lsdb-buffer-name))))
1148     (lsdb-show-buffer))
1149    ((or (> arg 0)
1150         (and (zerop arg)
1151              (get-buffer-window lsdb-buffer-name)))
1152     (lsdb-hide-buffer))))
1153
1154 (defun lsdb-lookup-records (regexp &optional entry-name)
1155   "Return the all records in the LSDB matching the REGEXP.
1156 If the optional 2nd argument ENTRY-NAME is given, matching only
1157 performed against the entry field."
1158   (let (records)
1159     (lsdb-maphash
1160      (if entry-name
1161          (progn
1162            (lambda (key value)
1163              (let ((entry (cdr (assq entry-name value)))
1164                    found)
1165                (unless (listp entry)
1166                  (setq entry (list entry)))
1167                (while (and (not found) entry)
1168                  (if (string-match regexp (pop entry))
1169                      (setq found t)))
1170                (if found
1171                    (push (cons key value) records)))))
1172        (lambda (key value)
1173          (if (string-match regexp key)
1174              (push (cons key value) records))))
1175      lsdb-hash-table)
1176     records))
1177
1178 (defvar lsdb-mode-lookup-history nil)
1179
1180 (defun lsdb-mode-lookup (regexp &optional entry-name)
1181   "Display the all records in the LSDB matching the REGEXP.
1182 If the optional 2nd argument ENTRY-NAME is given, matching only
1183 performed against the entry field."
1184   (interactive
1185    (let* ((completion-ignore-case t)
1186           (entry-name
1187            (if current-prefix-arg
1188                (completing-read "Entry name: "
1189                                 lsdb-known-entry-names))))
1190      (list
1191       (read-from-minibuffer
1192        (if entry-name
1193            (format "Search records `%s' regexp: " entry-name)
1194          "Search records regexp: ")
1195        nil nil nil 'lsdb-mode-lookup-history)
1196       (if (and entry-name (not (equal entry-name "")))
1197           (intern (downcase entry-name))))))
1198   (lsdb-maybe-load-hash-tables)
1199   (let ((records (lsdb-lookup-records regexp entry-name)))
1200     (if records
1201         (lsdb-display-records records))))
1202
1203 ;;;###autoload
1204 (defalias 'lsdb 'lsdb-mode-lookup)
1205
1206 (defun lsdb-mode-next-record (&optional arg)
1207   "Go to the next record."
1208   (interactive "p")
1209   (unless arg                           ;called noninteractively?
1210     (setq arg 1))
1211   (if (< arg 0)
1212       (lsdb-mode-previous-record (- arg))
1213     (while (> arg 0)
1214       (goto-char (next-single-property-change
1215                   (point) 'lsdb-record nil (point-max)))
1216       (setq arg (1- arg)))))
1217
1218 (defun lsdb-mode-previous-record (&optional arg)
1219   "Go to the previous record."
1220   (interactive "p")
1221   (unless arg                           ;called noninteractively?
1222     (setq arg 1))
1223   (if (< arg 0)
1224       (lsdb-mode-next-record (- arg))
1225     (while (> arg 0)
1226       (goto-char (previous-single-property-change
1227                   (point) 'lsdb-record nil (point-min)))
1228       (setq arg (1- arg)))))
1229
1230 ;;;_ : Edit Forms -- stolen (and renamed) from gnus-eform.el
1231 (defvar lsdb-edit-form-buffer "*LSDB edit form*")
1232 (defvar lsdb-edit-form-done-function nil)
1233 (defvar lsdb-previous-window-configuration nil)
1234
1235 (defvar lsdb-edit-form-mode-map
1236   (let ((keymap (make-sparse-keymap)))
1237     (set-keymap-parent keymap emacs-lisp-mode-map)
1238     (define-key keymap "\C-c\C-c" 'lsdb-edit-form-done)
1239     (define-key keymap "\C-c\C-k" 'lsdb-edit-form-exit)
1240     keymap)
1241   "Edit form's keymap.")
1242
1243 (defun lsdb-edit-form-mode ()
1244   "Major mode for editing forms.
1245 It is a slightly enhanced emacs-lisp-mode.
1246
1247 \\{lsdb-edit-form-mode-map}"
1248   (interactive)
1249   (kill-all-local-variables)
1250   (setq major-mode 'lsdb-edit-form-mode
1251         mode-name "LSDB Edit Form")
1252   (use-local-map lsdb-edit-form-mode-map)
1253   (make-local-variable 'lsdb-edit-form-done-function)
1254   (make-local-variable 'lsdb-previous-window-configuration)
1255   (run-hooks 'lsdb-edit-form-mode-hook))
1256
1257 (defun lsdb-edit-form (form documentation exit-func)
1258   "Edit FORM in a new buffer.
1259 Call EXIT-FUNC on exit.  Display DOCUMENTATION in the beginning
1260 of the buffer."
1261   (let ((window-configuration
1262          (current-window-configuration)))
1263     (switch-to-buffer (get-buffer-create lsdb-edit-form-buffer))
1264     (lsdb-edit-form-mode)
1265     (setq lsdb-previous-window-configuration window-configuration
1266           lsdb-edit-form-done-function exit-func)
1267     (erase-buffer)
1268     (insert documentation)
1269     (unless (bolp)
1270       (insert "\n"))
1271     (goto-char (point-min))
1272     (while (not (eobp))
1273       (insert ";;; ")
1274       (forward-line 1))
1275     (insert ";; Type `C-c C-c' after you've finished editing.\n")
1276     (insert "\n")
1277     (let ((p (point)))
1278       (pp form (current-buffer))
1279       (insert "\n")
1280       (goto-char p))))
1281
1282 (defun lsdb-edit-form-done ()
1283   "Update changes and kill the current buffer."
1284   (interactive)
1285   (goto-char (point-min))
1286   (let ((form (condition-case nil
1287                   (read (current-buffer))
1288                 (end-of-file nil)))
1289         (func lsdb-edit-form-done-function))
1290     (lsdb-edit-form-exit)
1291     (funcall func form)))
1292
1293 (defun lsdb-edit-form-exit ()
1294   "Kill the current buffer."
1295   (interactive)
1296   (let ((window-configuration lsdb-previous-window-configuration))
1297     (kill-buffer (current-buffer))
1298     (set-window-configuration window-configuration)))
1299
1300 ;;;_. Interface to Semi-gnus
1301 ;;;###autoload
1302 (defun lsdb-gnus-insinuate ()
1303   "Call this function to hook LSDB into Semi-gnus."
1304   (add-hook 'gnus-article-prepare-hook 'lsdb-gnus-update-record)
1305   (add-hook 'gnus-save-newsrc-hook 'lsdb-mode-save))
1306
1307 (defvar gnus-current-headers)
1308 (defun lsdb-gnus-update-record ()
1309   (let ((entity gnus-current-headers)
1310         records)
1311     (with-temp-buffer
1312       (set-buffer-multibyte nil)
1313       (buffer-disable-undo)
1314       (mime-insert-entity entity)
1315       (setq records (lsdb-update-records))
1316       (when records
1317         (lsdb-display-record (car records))))))
1318
1319 ;;;_. Interface to Wanderlust
1320 ;;;###autoload
1321 (defun lsdb-wl-insinuate ()
1322   "Call this function to hook LSDB into Wanderlust."
1323   (add-hook 'wl-message-redisplay-hook 'lsdb-wl-update-record)
1324   (add-hook 'wl-summary-exit-hook 'lsdb-hide-buffer)
1325   (add-hook 'wl-summary-toggle-disp-off-hook 'lsdb-hide-buffer)
1326   (add-hook 'wl-summary-toggle-disp-folder-on-hook 'lsdb-hide-buffer)
1327   (add-hook 'wl-summary-toggle-disp-folder-off-hook 'lsdb-hide-buffer)
1328   (add-hook 'wl-summary-toggle-disp-folder-message-resumed-hook
1329             'lsdb-wl-show-buffer)
1330   (add-hook 'wl-exit-hook 'lsdb-mode-save)
1331   (add-hook 'wl-save-hook 'lsdb-mode-save))
1332
1333 (eval-when-compile
1334   (autoload 'wl-message-get-original-buffer "wl-message"))
1335 (defun lsdb-wl-update-record ()
1336   (save-excursion
1337     (set-buffer (wl-message-get-original-buffer))
1338     (let ((records (lsdb-update-records)))
1339       (when records
1340         (let ((lsdb-temp-buffer-show-function
1341                #'lsdb-wl-temp-buffer-show-function))
1342           (lsdb-display-record (car records)))))))
1343
1344 (defun lsdb-wl-toggle-buffer (&optional arg)
1345   "Toggle hiding of the LSDB window for Wanderlust.
1346 If given a negative prefix, always show; if given a positive prefix,
1347 always hide."
1348   (interactive
1349    (list (if current-prefix-arg
1350              (prefix-numeric-value current-prefix-arg)
1351            0)))
1352   (let ((lsdb-temp-buffer-show-function
1353          #'lsdb-wl-temp-buffer-show-function))
1354     (lsdb-toggle-buffer arg)))
1355
1356 (defun lsdb-wl-show-buffer ()
1357   (when lsdb-pop-up-windows
1358     (let ((lsdb-temp-buffer-show-function
1359            #'lsdb-wl-temp-buffer-show-function))
1360       (lsdb-show-buffer))))
1361
1362 (defvar wl-current-summary-buffer)
1363 (defvar wl-message-buffer)
1364 (defun lsdb-wl-temp-buffer-show-function (buffer)
1365   (when lsdb-pop-up-windows
1366     (save-selected-window
1367       (let ((window (or (get-buffer-window lsdb-buffer-name)
1368                         (progn
1369                           (select-window 
1370                            (or (save-excursion
1371                                  (if (buffer-live-p wl-current-summary-buffer)
1372                                      (set-buffer wl-current-summary-buffer))
1373                                  (get-buffer-window wl-message-buffer))
1374                                (get-largest-window)))
1375                           (split-window-vertically)))))
1376         (set-window-buffer window buffer)
1377         (lsdb-fit-window-to-buffer window)))))
1378
1379 ;;;_. Interface to Mew written by Hideyuki SHIRAI <shirai@meadowy.org>
1380 (eval-when-compile
1381   (autoload 'mew-sinfo-get-disp-msg "mew")
1382   (autoload 'mew-current-get-fld "mew")
1383   (autoload 'mew-current-get-msg "mew")
1384   (autoload 'mew-frame-id "mew")
1385   (autoload 'mew-cache-hit "mew")
1386   (autoload 'mew-xinfo-get-decode-err "mew")
1387   (autoload 'mew-xinfo-get-action "mew"))
1388
1389 ;;;###autoload
1390 (defun lsdb-mew-insinuate ()
1391   "Call this function to hook LSDB into Mew."
1392   (add-hook 'mew-message-hook 'lsdb-mew-update-record)
1393   (add-hook 'mew-summary-toggle-disp-msg-hook
1394             (lambda ()
1395               (unless (mew-sinfo-get-disp-msg)
1396                 (lsdb-hide-buffer))))
1397   (add-hook 'mew-suspend-hook 'lsdb-hide-buffer)
1398   (add-hook 'mew-quit-hook 'lsdb-mode-save)
1399   (add-hook 'kill-emacs-hook 'lsdb-mode-save)
1400   (cond
1401    ;; Mew 3
1402    ((fboundp 'mew-summary-visit-folder)
1403     (defadvice mew-summary-visit-folder (before lsdb-hide-buffer activate)
1404       (lsdb-hide-buffer)))
1405    ;; Mew 2
1406    ((fboundp 'mew-summary-switch-to-folder)
1407     (defadvice mew-summary-switch-to-folder (before lsdb-hide-buffer activate)
1408       (lsdb-hide-buffer)))))
1409
1410 (defun lsdb-mew-update-record ()
1411   (let* ((fld (mew-current-get-fld (mew-frame-id)))
1412          (msg (mew-current-get-msg (mew-frame-id)))
1413          (cache (mew-cache-hit fld msg))
1414          records)
1415     (when cache
1416       (save-excursion
1417         (set-buffer cache)
1418         (unless (or (mew-xinfo-get-decode-err) (mew-xinfo-get-action))
1419           (make-local-variable 'lsdb-decode-field-body-function)
1420           (setq lsdb-decode-field-body-function
1421                 (lambda (body name)
1422                   (set-text-properties 0 (length body) nil body)
1423                   body))
1424           (when (setq records (lsdb-update-records))
1425             (lsdb-display-record (car records))))))))
1426
1427 ;;;_. Interface to MU-CITE
1428 (eval-when-compile
1429   (autoload 'mu-cite-get-value "mu-cite"))
1430
1431 (defun lsdb-mu-attribution (address)
1432   "Extract attribute information from LSDB."
1433   (let ((records
1434          (lsdb-lookup-records (concat "\\<" address "\\>") 'net)))
1435     (if records
1436         (cdr (assq 'attribution (cdr (car records)))))))
1437
1438 (defun lsdb-mu-set-attribution (attribution address)
1439   "Add attribute information to LSDB."
1440   (let ((records
1441          (lsdb-lookup-records (concat "\\<" address "\\>") 'net))
1442         entry)
1443     (when records
1444       (setq entry (assq 'attribution (cdr (car records))))
1445       (if entry
1446           (setcdr entry attribution)
1447         (setcdr (car records) (cons (cons 'attribution attribution)
1448                                     (cdr (car records))))
1449         (lsdb-puthash (car (car records)) (cdr (car records))
1450                       lsdb-hash-table)
1451         (run-hook-with-args 'lsdb-update-record-functions (car records))
1452         (setq lsdb-hash-tables-are-dirty t)))))
1453
1454 (defun lsdb-mu-get-prefix-method ()
1455   "A mu-cite method to return a prefix from LSDB or \">\".
1456 If an `attribution' value is found in LSDB, the value is returned.
1457 Otherwise \">\" is returned."
1458   (or (lsdb-mu-attribution (mu-cite-get-value 'address))
1459       ">"))
1460
1461 (defvar minibuffer-allow-text-properties)
1462
1463 (defvar lsdb-mu-history nil)
1464
1465 (defun lsdb-mu-get-prefix-register-method ()
1466   "A mu-cite method to return a prefix from LSDB or register it.
1467 If an `attribution' value is found in LSDB, the value is returned.
1468 Otherwise the function requests a prefix from a user.  The prefix will
1469 be registered to LSDB if the user wants it."
1470   (let ((address (mu-cite-get-value 'address)))
1471     (or (lsdb-mu-attribution address)
1472         (let* (minibuffer-allow-text-properties
1473                (result (read-string "Citation name? "
1474                                     (or (mu-cite-get-value 'x-attribution)
1475                                         (mu-cite-get-value 'full-name))
1476                                     'lsdb-mu-history)))
1477           (if (and (not (string-equal result ""))
1478                    (y-or-n-p (format "Register \"%s\"? " result)))
1479               (lsdb-mu-set-attribution result address))
1480           result))))
1481
1482 (defun lsdb-mu-get-prefix-register-verbose-method ()
1483   "A mu-cite method to return a prefix using LSDB.
1484
1485 In this method, a user must specify a prefix unconditionally.  If an
1486 `attribution' value is found in LSDB, the value is used as a initial
1487 value to input the prefix.  The prefix will be registered to LSDB if
1488 the user wants it."
1489   (let* ((address (mu-cite-get-value 'address))
1490          (attribution (lsdb-mu-attribution address))
1491          minibuffer-allow-text-properties
1492          (result (read-string "Citation name? "
1493                               (or attribution
1494                                   (mu-cite-get-value 'x-attribution)
1495                                   (mu-cite-get-value 'full-name))
1496                               'lsdb-mu-history)))
1497     (if (and (not (string-equal result ""))
1498              (not (string-equal result attribution))
1499              (y-or-n-p (format "Register \"%s\"? " result)))
1500         (lsdb-mu-set-attribution result address))
1501     result))
1502
1503 (defvar mu-cite-methods-alist)
1504 ;;;###autoload
1505 (defun lsdb-mu-insinuate ()
1506   (add-hook 'mu-cite-instantiation-hook
1507             (lambda ()
1508               (setq mu-cite-methods-alist
1509                     (nconc
1510                      mu-cite-methods-alist
1511                      (list
1512                       (cons 'lsdb-prefix
1513                             #'lsdb-mu-get-prefix-method)
1514                       (cons 'lsdb-prefix-register
1515                             #'lsdb-mu-get-prefix-register-method)
1516                       (cons 'lsdb-prefix-register-verbose
1517                             #'lsdb-mu-get-prefix-register-verbose-method)))))))
1518
1519 ;;;_. X-Face Rendering
1520 (defvar lsdb-x-face-cache
1521   (lsdb-make-hash-table :test 'equal))
1522
1523 (defun lsdb-x-face-available-image-type ()
1524   (static-if (featurep 'xemacs)
1525       (if (featurep 'xpm)
1526           'xpm)
1527     (and (>= emacs-major-version 21)
1528          (fboundp 'image-type-available-p)
1529          (if (image-type-available-p 'pbm)
1530              'pbm
1531            (if (image-type-available-p 'xpm)
1532                'xpm)))))
1533
1534 (defun lsdb-expose-x-face ()
1535   (let* ((record (get-text-property (point-min) 'lsdb-record))
1536          (x-face (cdr (assq 'x-face (cdr record))))
1537          (delimiter "\r "))
1538     (when (and lsdb-insert-x-face-function
1539                x-face)
1540       (goto-char (point-min))
1541       (end-of-line)
1542       (put-text-property 0 1 'invisible t delimiter) ;hide "\r"
1543       (put-text-property
1544        (point)
1545        (progn
1546          (insert delimiter)
1547          (while x-face
1548            (funcall lsdb-insert-x-face-function (pop x-face)))
1549          (point))
1550        'lsdb-record record))))
1551
1552 (defun lsdb-insert-x-face-image (data type marker)
1553   (static-if (featurep 'xemacs)
1554       (save-excursion
1555         (set-buffer (marker-buffer marker))
1556         (goto-char marker)
1557         (let* ((inhibit-read-only t)
1558                buffer-read-only
1559                (glyph (make-glyph (vector type :data data))))
1560           (set-extent-begin-glyph
1561            (make-extent (point) (point))
1562            glyph)))
1563     (save-excursion
1564       (set-buffer (marker-buffer marker))
1565       (goto-char marker)
1566       (let* ((inhibit-read-only t)
1567              buffer-read-only
1568              (image (create-image data type t :ascent 'center))
1569              (record (get-text-property (point) 'lsdb-record)))
1570         (put-text-property (point) (progn
1571                                      (insert-image image)
1572                                      (point))
1573                            'lsdb-record record)))))
1574
1575 (defun lsdb-insert-x-face-asynchronously (x-face)
1576   (let* ((type (or lsdb-x-face-image-type
1577                    (lsdb-x-face-available-image-type)))
1578          (shell-file-name lsdb-shell-file-name)
1579          (shell-command-switch lsdb-shell-command-switch)
1580          (process-connection-type nil)
1581          (cached (cdr (assq type (lsdb-gethash x-face lsdb-x-face-cache))))
1582          (marker (point-marker))
1583          process)
1584     (if cached
1585         (lsdb-insert-x-face-image cached type marker)
1586       (setq process
1587             (start-process-shell-command
1588              "lsdb-x-face-command" (generate-new-buffer " *lsdb work*")
1589              (concat "{ "
1590                      (nth 1 (assq type lsdb-x-face-command-alist))
1591                      "; } 2> /dev/null")))
1592       (process-send-string process (concat x-face "\n"))
1593       (process-send-eof process)
1594       (set-process-sentinel
1595        process
1596        `(lambda (process string)
1597           (unwind-protect
1598               (when (and (buffer-live-p (marker-buffer ,marker))
1599                          (equal string "finished\n"))
1600                 (let ((data
1601                        (with-current-buffer (process-buffer process)
1602                          (set-buffer-multibyte nil)
1603                          (buffer-string))))
1604                   (lsdb-insert-x-face-image data ',type ,marker)
1605                   (lsdb-puthash ,x-face (list (cons ',type data))
1606                                 lsdb-x-face-cache)))
1607             (kill-buffer (process-buffer process))))))))
1608
1609 (require 'product)
1610 (provide 'lsdb)
1611
1612 (product-provide 'lsdb
1613   (product-define "LSDB" nil '(0 7)))
1614
1615 ;;;_* Local emacs vars.
1616 ;;; The following `outline-layout' local variable setting:
1617 ;;;  - closes all topics from the first topic to just before the third-to-last,
1618 ;;;  - shows the children of the third to last (config vars)
1619 ;;;  - and the second to last (code section),
1620 ;;;  - and closes the last topic (this local-variables section).
1621 ;;;Local variables:
1622 ;;;outline-layout: (0 : -1 -1 0)
1623 ;;;End:
1624
1625 ;;; lsdb.el ends here