* elmo-imap4.el (elmo-imap4-folder-list-range): Fix indent (only cosmetic fix).
[elisp/wanderlust.git] / wl / wl-address.el
index 922975a..f657e3b 100644 (file)
@@ -36,8 +36,9 @@
 (require 'wl-util)
 (require 'wl-vars)
 (require 'std11)
+(eval-when-compile (require 'cl))
 
-(defvar wl-address-complete-header-list 
+(defvar wl-address-complete-header-list
   '("To:" "From:" "Cc:" "Bcc:" "Mail-Followup-To:" "Reply-To:"
     "Return-Receipt-To:"))
 (defvar wl-address-complete-header-regexp nil) ; auto-generated.
@@ -46,6 +47,7 @@
 (defvar wl-address-list nil)
 (defvar wl-address-completion-list nil)
 (defvar wl-address-petname-hash nil)
+(defvar wl-address-enable-strict-loading t)
 
 (defvar wl-address-ldap-search-hash nil)
 
@@ -86,7 +88,7 @@ If level 3 is required for uniqness with other candidates,
 (defconst wl-ldap-alias-sep "/")
 
 (defconst wl-ldap-search-attribute-type-list
-  '("sn" "cn" "mail"))
+  '("sn" "cn" "mail" "email"))
 
 (defun wl-ldap-get-value (type entry)
   ""
@@ -107,11 +109,11 @@ If level 3 is required for uniqness with other candidates,
 (defun wl-ldap-make-filter (pat type-list)
   "Make RFC1558 quiery filter for PAT from ATTR-LIST.
 Each are \"OR\" combination, and PAT is beginning-match."
-  (concat "(&(objectclass=" wl-ldap-objectclass ")(|"
+  (concat "(|"
          (mapconcat (lambda (x) (format "(%s=%s*)" x pat)) ; fixed format
                     type-list
                     "")
-         "))"))
+         ")"))
 
 (defun wl-ldap-make-matched-value-list (regexp type-list entry)
   "Correct matching WORD with value of TYPE-LIST in ENTRY.
@@ -124,8 +126,6 @@ Returns matched uniq string list."
                           (cdr (car entry)))
            values (elmo-flatten values)
            entry (cdr entry))
-      (if (string-match "::?$" type)
-         (setq type (substring type 0 (match-beginning 0))))
       (if (member type type-list)
          (while values
            (setq val (car values)
@@ -139,7 +139,7 @@ Returns matched uniq string list."
   "Modify STR for alias.
 Replace space/tab in STR into '_' char.
 Replace '@' in STR into list of mailbox and sub-domains."
-  (while (string-match "[^_a-zA-Z0-9+@%.!\\-/]+" str)
+  (while (string-match "[ \t]+" str)
     (setq str (concat (substring str 0 (match-beginning 0))
                      "_"
                      (substring str (match-end 0)))))
@@ -203,9 +203,9 @@ Matched address lists are append to CL."
   (let ((pat (if (string-match wl-ldap-alias-sep pattern)
                 (substring pattern 0 (match-beginning 0))
               pattern))
-       (ldap-default-host wl-ldap-server)
-       (ldap-default-port (or wl-ldap-port 389))
-       (ldap-default-base wl-ldap-base)
+       (ldap-default-host (or wl-ldap-server ldap-default-host "localhost"))
+       (ldap-default-port (or wl-ldap-port ldap-default-port 389))
+       (ldap-default-base (or wl-ldap-base ldap-default-base))
        (dnhash (elmo-make-hash))
        cache len sym tmpl regexp entries ent values dn dnstr alias
        result cn mails)
@@ -223,17 +223,14 @@ Matched address lists are append to CL."
     ;; get matched entries
     (if cache
        (setq entries (cdr cache))
-      (condition-case nil
-         (progn
-           (message "Searching in LDAP...")
-           (setq entries (ldap-search-entries
-                          (wl-ldap-make-filter
-                           (concat pat "*")
-                           wl-ldap-search-attribute-type-list)
-                          nil wl-ldap-search-attribute-type-list nil t))
-           (message "Searching in LDAP...done")
-           (elmo-set-hash-val pattern entries wl-address-ldap-search-hash))
-       (error (message ""))))                  ; ignore error: No such object
+      (ignore-errors
+       (message "Searching in LDAP...")
+       (setq entries (ldap-search-entries
+                      (wl-ldap-make-filter
+                       pat wl-ldap-search-attribute-type-list)
+                      nil wl-ldap-search-attribute-type-list nil t))
+       (message "Searching in LDAP...done")
+       (elmo-set-hash-val pattern entries wl-address-ldap-search-hash)))
     ;;
     (setq tmpl entries)
     (while tmpl
@@ -244,8 +241,10 @@ Matched address lists are append to CL."
     (while entries
       (setq ent (cdar entries)
            values (wl-ldap-make-matched-value-list
-                   regexp '("mail" "sn" "cn") ent)
-           mails (wl-ldap-get-value-list "mail" ent)
+                   regexp wl-ldap-search-attribute-type-list
+                   ent)
+           mails (or (wl-ldap-get-value-list "mail" ent)
+                     (wl-ldap-get-value-list "email" ent))
            cn (wl-ldap-get-value "cn" ent)
            dn (car (car entries))
            dnstr (elmo-get-hash-val (upcase dn) dnhash))
@@ -267,7 +266,7 @@ Matched address lists are append to CL."
       ;; make mail addrses list
       (while mails
        (if (null (assoc (car mails) cl)); Not already in cl.
-           ;; (string-match regexp (car mails))
+;;;        (string-match regexp (car mails))
            ;; add mail address itself to completion list
            (setq result (cons (cons (car mails)
                                     (concat cn " <" (car mails) ">"))
@@ -276,12 +275,18 @@ Matched address lists are append to CL."
       (setq entries (cdr entries)))
     (append result cl)))
 
-(defun wl-complete-field-to ()
-  (interactive)
-  (let ((cl wl-address-completion-list))
-    (if cl
-       (completing-read "To: " cl)
-      (read-string "To: "))))
+(defun wl-complete-address (string predicate flag)
+  "Completion function for completing-read (comma separated addresses)."
+  (if (string-match "^\\(.*,\\)\\(.*\\)$" string)
+      (let* ((str1 (match-string 1 string))
+            (str2 (match-string 2 string))
+            (str2-comp (wl-complete-address str2 predicate flag)))
+       (if (and (not flag) (stringp str2-comp))
+           (concat str1 str2-comp)
+         str2-comp))
+    (if (not flag)
+       (try-completion string wl-address-list)
+      (all-completions string wl-address-list))))
 
 (defalias 'wl-address-quote-specials 'elmo-address-quote-specials)
 
@@ -291,28 +296,33 @@ Matched address lists are append to CL."
       (setq addr-tuple (car address-list))
       (setq cl
            (cons
-            (cons (nth 0 addr-tuple)
-                  (if (or (string= (nth 2 addr-tuple) "")
-                          (string-match ".*:.*;$" (nth 0 addr-tuple)))
-                      (nth 0 addr-tuple)
-                    (concat
-                     (wl-address-quote-specials
-                      (nth 2 addr-tuple)) " <"(nth 0 addr-tuple)">")))
+            (wl-address-make-completion-entry 0 addr-tuple)
             cl))
       ;; nickname completion.
-      (setq cl
-           (cons
-            (cons (nth 1 addr-tuple)
-                  (if (or (string= (nth 2 addr-tuple) "")
-                          (string-match ".*:.*;$" (nth 0 addr-tuple)))
-                      (nth 0 addr-tuple)
-                    (concat
-                     (wl-address-quote-specials
-                      (nth 2 addr-tuple)) " <"(nth 0 addr-tuple)">")))
-            cl))
+      (if wl-address-enable-strict-loading
+         (unless (or (equal (nth 1 addr-tuple) (nth 0 addr-tuple))
+                     ;; already exists
+                     (assoc (nth 1 addr-tuple) cl))
+           (setq cl
+                 (cons
+                  (wl-address-make-completion-entry 1 addr-tuple)
+                  cl)))
+       (setq cl
+             (cons
+              (wl-address-make-completion-entry 1 addr-tuple)
+              cl)))
       (setq address-list (cdr address-list)))
     cl))
 
+(defun wl-address-make-completion-entry (index addr-tuple)
+  (cons (nth index addr-tuple)
+       (if (or (string= (nth 2 addr-tuple) "")
+               (string-match ".*:.*;$" (nth 0 addr-tuple)))
+           (nth 0 addr-tuple)
+         (concat
+          (wl-address-quote-specials
+           (nth 2 addr-tuple)) " <"(nth 0 addr-tuple)">"))))
+
 (defun wl-complete-field-body-or-tab ()
   (interactive)
   (let ((case-fold-search t)
@@ -355,8 +365,7 @@ Matched address lists are append to CL."
   (if (and (get-buffer-window wl-completion-buf-name)
           (equal wl-complete-candidates all))
       (let ((win (get-buffer-window wl-completion-buf-name)))
-       (save-excursion
-         (set-buffer wl-completion-buf-name)
+       (with-current-buffer wl-completion-buf-name
          (if (pos-visible-in-window-p (point-max) win)
              (set-window-start win 1)
            (scroll-other-window))))
@@ -365,7 +374,7 @@ Matched address lists are append to CL."
     (with-output-to-temp-buffer
        wl-completion-buf-name
       (display-completion-list all))
-    (message "Making completion list... done")))
+    (message "Making completion list...done")))
 
 (defun wl-complete-window-delete ()
   (let (comp-buf comp-win)
@@ -445,7 +454,8 @@ Matched address lists are append to CL."
             (message "Sole completion"))
            ((and epand-char
                  (> len 0)
-                 (char-equal (aref pattern (1- len)) epand-char)
+                 (or (char-equal (aref pattern (1- len)) epand-char)
+                     (char-equal (aref pattern (1- len)) (string-to-char " ")))
                  (assoc (substring pattern 0 (1- len)) cl))
             (wl-complete-insert
              start end
@@ -514,14 +524,14 @@ Refresh `wl-address-list', `wl-address-completion-list', and
           (wl-address-expand-aliases alist (1+ nest-count))))))
 
 (defun wl-address-make-alist-from-alias-file (file)
-  (elmo-set-work-buf
+  (with-temp-buffer
     (let ((case-fold-search t)
          alias expn alist)
       (insert-file-contents file)
       (while (re-search-forward ",$" nil t)
        (end-of-line)
        (forward-char 1)
-       (delete-backward-char 1))
+       (delete-char -1))
       (goto-char (point-min))
       (while (re-search-forward "^\\([^#;\n][^:]+\\):[ \t]*\\(.*\\)$" nil t)
        (setq alias (wl-match-buffer 1)
@@ -532,46 +542,31 @@ Refresh `wl-address-list', `wl-address-completion-list', and
       )))
 
 (defun wl-address-make-address-list (path)
-  (if (and path (file-readable-p path))
-      (elmo-set-work-buf
-       (let (ret
-            (coding-system-for-read wl-cs-autoconv))
-        (insert-file-contents path)
-        (goto-char (point-min))
-        (while (not (eobp))
-          (if (looking-at
-               "^\\([^#\n][^ \t\n]+\\)[ \t]+\\(\".*\"\\)[ \t]+\\(\".*\"\\)[ \t]*.*$")
-              (setq ret
-                    (cons
-                     (list (wl-match-buffer 1)
-                           (read (wl-match-buffer 2))
-                           (read (wl-match-buffer 3)))
-                     ret)))
-          (forward-line))
-        (nreverse ret)))))
-
-(defun wl-address-get-petname-1 (string)
-  (let ((address (downcase (wl-address-header-extract-address string))))
-    (elmo-get-hash-val address wl-address-petname-hash)))
+  (when (and path (file-readable-p path))
+    (with-temp-buffer
+      (let (ret
+           (coding-system-for-read wl-cs-autoconv))
+       (insert-file-contents path)
+       (goto-char (point-min))
+       (while (not (eobp))
+         (if (looking-at "\
+^\\([^#\n][^ \t\n]+\\)[ \t]+\\(\".*\"\\)[ \t]+\\(\".*\"\\)[ \t]*.*$")
+             (setq ret
+                   (cons
+                    (list (wl-match-buffer 1)
+                          (read (wl-match-buffer 2))
+                          (read (wl-match-buffer 3)))
+                    ret)))
+         (forward-line))
+       (nreverse ret)))))
 
-(defsubst wl-address-get-petname (string)
-  (or (wl-address-get-petname-1 string)
-      string))
-
-(defsubst wl-address-user-mail-address-p (address)
-  "Judge whether ADDRESS is user's or not."
-  (member (downcase (wl-address-header-extract-address address))
-         (or (mapcar 'downcase wl-user-mail-address-list)
-             (list (downcase
-                    (wl-address-header-extract-address
-                     wl-from))))))
 
 (defsubst wl-address-header-extract-address (str)
   "Extracts a real e-mail address from STR and return it.
-e.g. \"Mine Sakurai <m-sakura@ccs.mt.nec.co.jp>\"
-  ->  \"m-sakura@ccs.mt.nec.co.jp\".
-e.g. \"m-sakura@ccs.mt.nec.co.jp (Mine Sakurai)\"
-  ->  \"m-sakura@ccs.mt.nec.co.jp\"."
+e.g. \"Mine Sakurai <m-sakura@example.org>\"
+  ->  \"m-sakura@example.org\".
+e.g. \"m-sakura@example.org (Mine Sakurai)\"
+  ->  \"m-sakura@example.org\"."
   (cond ((string-match ".*<\\([^>]*\\)>" str) ; .* to extract last <>
         (wl-match-string 1 str))
        ((string-match "\\([^ \t\n]*@[^ \t\n]*\\)" str)
@@ -580,20 +575,58 @@ e.g. \"m-sakura@ccs.mt.nec.co.jp (Mine Sakurai)\"
 
 (defsubst wl-address-header-extract-realname (str)
   "Extracts a real name from STR and return it.
-e.g. \"Mr. bar <hoge@foo.com>\"
+e.g. \"Mr. bar <hoge@example.com>\"
   ->  \"Mr. bar\"."
   (cond ((string-match "\\(.*[^ \t]\\)[ \t]*<[^>]*>" str)
         (wl-match-string 1 str))
        (t "")))
 
+
+(defun wl-address-get-petname-1 (string)
+  (let ((address (downcase (wl-address-header-extract-address string))))
+    (elmo-get-hash-val address wl-address-petname-hash)))
+
+(defsubst wl-address-get-petname (string)
+  (or (wl-address-get-petname-1 string)
+      string))
+
+(defun wl-address-user-mail-address-p (address)
+  "Judge whether ADDRESS is user's or not."
+  (if wl-user-mail-address-regexp
+      (string-match wl-user-mail-address-regexp
+                   (wl-address-header-extract-address address))
+    (member (downcase (wl-address-header-extract-address address))
+           (or (mapcar 'downcase wl-user-mail-address-list)
+               (list (downcase
+                      (wl-address-header-extract-address
+                       wl-from)))))))
+
+(defun wl-address-delete-user-mail-addresses (address-list)
+  "Delete user mail addresses from list by side effect.
+Deletion is done by using `elmo-list-delete'."
+  (if wl-user-mail-address-regexp
+      (elmo-list-delete (list wl-user-mail-address-regexp) address-list
+                       (lambda (elem list)
+                         (elmo-delete-if
+                          (lambda (item) (string-match elem item))
+                          list)))
+    (let ((myself (or wl-user-mail-address-list
+                     (list (wl-address-header-extract-address wl-from)))))
+      (elmo-list-delete myself address-list
+                       (lambda (elem list)
+                         (elmo-delete-if
+                          (lambda (item) (string= (downcase elem)
+                                                  (downcase item)))
+                          list))))))
+
 (defmacro wl-address-concat-token (string token)
-  (` (cond
-      ((eq 'quoted-string (car (, token)))
-       (concat (, string) "\"" (cdr (, token)) "\""))
-      ((eq 'comment (car (, token)))
-       (concat (, string) "(" (cdr (, token)) ")"))
-      (t
-       (concat (, string) (cdr (, token)))))))
+  `(cond
+    ((eq 'quoted-string (car ,token))
+     (concat ,string "\"" (cdr ,token) "\""))
+    ((eq 'comment (car ,token))
+     (concat ,string "(" (cdr ,token) ")"))
+    (t
+     (concat ,string (cdr ,token)))))
 
 (defun wl-address-string-without-group-list-contents (sequence)
   "Return address string from lexical analyzed list SEQUENCE.
@@ -638,7 +671,7 @@ Group list contents is not included."
     (with-temp-buffer
       (message "Deleting Address...")
       (insert-file-contents wl-address-file)
-      (delete-matching-lines (concat "^[ \t]*" the-email))
+      (delete-matching-lines (concat "^[ \t]*" the-email "[ \t]+\".*\"[ \t]+\".*\"$"))
       (write-region (point-min) (point-max)
                    wl-address-file nil 'no-msg)
       ;; Delete entries.
@@ -662,9 +695,16 @@ If already registerd, change it."
                                                the-realname)))
     (when change-address
       (setq new-addr (read-from-minibuffer "E-Mail: " address))
-      (if (and (not (string= address new-addr))
-              (assoc new-addr wl-address-list))
-         (error "'%s' already exists" new-addr)))
+      (cond
+       ((or (not (stringp new-addr))
+           (string-match "^[ \t]*$" new-addr))
+       (error "empty address"))
+       ((and (not (string= address new-addr))
+            (assoc new-addr wl-address-list))
+       (error "'%s' already exists" new-addr))
+       (t
+       ;; do nothing
+       )))
     ;; writing to ~/.address
     (let ((output-coding-system
           (mime-charset-to-coding-system wl-mime-charset)))
@@ -680,10 +720,7 @@ If already registerd, change it."
                  (insert "\n")))
          ;; override
          (while (re-search-forward (concat "^[ \t]*" address) nil t)
-           (delete-region (save-excursion (beginning-of-line)
-                                          (point))
-                          (save-excursion (end-of-line)
-                                          (+ 1 (point))))))
+           (delete-region (point-at-bol) (1+ (point-at-eol)))))
        (insert (format "%s\t%s\t%s\n"
                        (or new-addr address)
                        (prin1-to-string the-petname)
@@ -693,6 +730,31 @@ If already registerd, change it."
        (wl-address-init)
        (list (or new-addr address) the-petname the-realname)))))
 
+;; Read addresses from minibuffer with completion.
+(defvar wl-address-minibuffer-history nil)
+(defvar wl-address-minibuffer-local-map nil
+  "Keymap to use when reading address from the minibuffer.")
+
+(unless wl-address-minibuffer-local-map
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map minibuffer-local-map)
+    (define-key map "\C-i"
+      (lambda ()
+       (interactive)
+       (wl-complete-field-body wl-address-completion-list
+                               ?@ nil wl-use-ldap)))
+    (setq wl-address-minibuffer-local-map map)))
+
+(defun wl-address-read-from-minibuffer (prompt &optional
+                                              initial-contents
+                                              default-value)
+  (read-from-minibuffer prompt
+                       initial-contents
+                       wl-address-minibuffer-local-map
+                       nil
+                       'wl-address-minibuffer-history
+                       default-value))
+
 (require 'product)
 (product-provide (provide 'wl-address) (require 'wl-version))