]> git.donarmstrong.com Git - lib.git/blob - emacs_el/tiny-tools/tiny/tinymail.el
add tiny-tools
[lib.git] / emacs_el / tiny-tools / tiny / tinymail.el
1 ;;; tinymail.el --- Mail add-ons. Report incoming mail, passwd, BBDB complete.
2
3 ;; This file is not part of Emacs
4
5 ;;{{{ Id
6
7 ;; Copyright (C)    1996-2007 Jari Aalto
8 ;; Keywords:        tools
9 ;; Author:          Jari Aalto
10 ;; Maintainer:      Jari Aalto
11 ;;
12 ;; To get information on this program run M-x tinymail-version
13 ;; Look at the code with folding.el
14
15 ;; COPYRIGHT NOTICE
16 ;;
17 ;; This program is free software; you can redistribute it and/or modify it
18 ;; under the terms of the GNU General Public License as published by the Free
19 ;; Software Foundation; either version 2 of the License, or (at your option)
20 ;; any later version.
21 ;;
22 ;; This program is distributed in the hope that it will be useful, but
23 ;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
24 ;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
25 ;; for more details.
26 ;;
27 ;; You should have received a copy of the GNU General Public License
28 ;; along with program; see the file COPYING. If not, write to the
29 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
30 ;; Boston, MA 02110-1301, USA.
31 ;;
32 ;; Visit <http://www.gnu.org/copyleft/gpl.html> for more information
33
34 ;;}}}
35 ;;{{{ Install
36
37 ;; ....................................................... &t-install ...
38 ;;  Put this file on your Emacs-Lisp load path, add following into your
39 ;;   ~/.emacs startup file:
40 ;;
41 ;;      (require 'tinymail-install)
42 ;;      (require 'tinymail-install-extras)  ;; optional
43 ;;
44 ;;  Other setting you may wish to add:
45 ;;
46 ;;      ;;  Activate nice citation
47 ;;      (add-hook 'tinymail-:load-hook 'tinymail-install-citation)
48 ;;
49 ;;      ;;  If you want nice TAB to indent for your messages,
50 ;;      ;;  add this. You TAB advances 4 spaces in the body of message.
51 ;;      (autoload 'turn-on-tinytab-mode  "tinytab" "" t)
52 ;;      (add-hook 'tinymail-:mode-hook   'turn-on-tinytab-mode)
53 ;;
54 ;;      ;;  If you use NIS, use "ypcat passwd"
55 ;;      (setq tinymail-:password-cat-cmd "cat /etc/passwd")
56 ;;
57 ;;      ;;  Protect plain text email addresses in the body
58 ;;      (add-hook 'mail-send-hook    'tinymail-buffer-email-address-scramble)
59 ;;      (add-hook 'message-send-hook 'tinymail-buffer-email-address-scramble)
60 ;;
61 ;;   If you have any questions, use 'submit' function. In case of error
62 ;;   or misbehavior, turn on the debug and send the debug result and
63 ;;   describe what you did and what went wrong.
64 ;;
65 ;;      .. do what you did in mail buffer ..
66 ;;      M-x tinymail-debug-toggle            Make sure debug is on
67 ;;      C-u M-x tinymail-process-1           Run this if you got error signall
68 ;;      M-x tinymail-submit-bug-report       And compose bug report
69 ;;
70 ;;  To read the documentation, run
71 ;;
72 ;;      M-x load-library RET tinymail RET
73 ;;      M-x tinymail-version                 [Add C-u, shows version only]
74
75 ;;}}}
76 ;;{{{ Documentation
77
78 ;; ..................................................... &t-commentary ...
79
80 ;;; Commentary:
81
82 ;;  Overview of features
83 ;;
84 ;;      o   Generate sendmail PLUS address: login@domain (Mr Foo+info)
85 ;;          Works like real sendmail PLUS addressing:
86 ;;          login+info@domain (Mr Foo)
87 ;;      o   Generate anti-ube addresses to prevent UBE/Spam from arriving
88 ;;          to your mailbox.
89 ;;      o   Changes Fcc dynamically according to header content.
90 ;;      o   Very easy TAB completion: two modes, alias and definition string.
91 ;;          Also completes password file entries if your .mailrc doesn't
92 ;;          contain a match.
93 ;;      o   Easy interface for completing any field with TAB. E.g. complete
94 ;;          `Followup-To:', `Gcc', `Newsgroups' and any user defined fields,
95 ;;          like `Class' or `Priority'.
96 ;;      o   Fcc/Gcc folder can have compression extension .gz or .Z
97 ;;      o   When you reply, tour address is removed from CC to prevent
98 ;;          duplicates.
99 ;;      o   `mail-mode], Gnus `message-mode' and VM compatible.
100 ;;      o   MIME support: turns on Multi part sending if buffer size is
101 ;;          bigger than 50K.
102 ;;
103 ;;     BBDB supported
104 ;;
105 ;;      Search BBDB for partial matches when you complete *To* and *Cc*
106 ;;      fields in header. E.g. if you remember person's address, "site" or
107 ;;      something, hit just TAB and all found BBDB's `net' field completions
108 ;;      are offered.
109 ;;
110 ;;      Notice that you have to _manually_ add full
111 ;;      user name, phone number, whatever to the Net Field on order to
112 ;;      complete to those items. The default `:' command adds only
113 ;;      this:
114 ;;
115 ;;          Foo Bar
116 ;;               net: abc@example.com
117 ;;
118 ;;      An in order to make that useful for completion purposes, you need to
119 ;;      modify the `net' field with `C-o'
120 ;;
121 ;;          Foo Bar
122 ;;               net: Foo Bar - Head of Skyscraper inc. <abc@example.com>
123 ;;
124 ;;      Now you can complete to any word found in the `net' line.
125 ;;      If you want case sensitive completions, set this:
126 ;;
127 ;;          (setq tinymail-:complete-bbdb-case-fold-search nil)
128 ;;
129 ;;  Installation note
130 ;;
131 ;;      This package installs itself to `mail-setup-hook' and you should
132 ;;      know why if you try to get the package running for some other
133 ;;      mail agent than Emacs mail, RMAIL, Gnus and VM where this package
134 ;;      has been tested.
135 ;;
136 ;;      The `mail-setup-hook' is called *after* the basic headers, like
137 ;;      `To' and `Subject', which are already in the buffer. Function
138 ;;      `tinymail-mail' needs to read the contents of `To' in order to
139 ;;      determine how it starts. It puts 1 or 2 spaces at the beginning of
140 ;;      `To' field at the initial start, so that the packages `Cc' control
141 ;;      is started correctly. When you use simple mail, `C-x' `m', the auto
142 ;;      Cc feature addds 1 space (on) and when you hit reply, the auto Cc
143 ;;      feature adds 2 spaces (off).
144 ;;      So, if your mail agent doesn't call `mail-setup-hook', find similar
145 ;;      hook that runs after the headers are in the buffer and install
146 ;;      to that hook `tinymail-mail'.
147 ;;
148 ;;  Completion: Guess Completion feature
149 ;;
150 ;;      There are two basic completion modes: 'alias and 'string, which is
151 ;;      selected via `tinymail-:complete-mode'. They refer to your
152 ;;      ~/.mailrc definitions: When you hit the completions key (TAB in
153 ;;      headers) the current word is picked at point and searched from
154 ;;      either of these two definition lists.
155 ;;
156 ;;          alias test "Mister Foo, Skyscraper Doing co. <foo@company.com>"
157 ;;                |             |
158 ;;                alias mode    string match mode
159 ;;
160 ;;      The caces 1-4 below present words that you can type into the `To' field
161 ;;      before you hit the completion key, TAB.
162 ;;
163 ;;          1  To: company
164 ;;          2  To: Foo
165 ;;          3  To: sky
166 ;;          4  To: mister
167 ;;
168 ;;      It doesn't matter what you type initially; it can be anything you
169 ;;      remember from the person's definition string. TheTAB calls function
170 ;;      `tinymail-complete-guess' and any of those lines, 1-4, will be
171 ;;      replaced with
172 ;;
173 ;;          To: Mister Foo, Skyscraper Doing co. <foo@doing.com>
174 ;;
175 ;;      If there are more than one match, a completion list is displayed.
176 ;;
177 ;;     Completion and BBDB integration
178 ;;
179 ;;      The completion is integrated to BBDB, but you have to have
180 ;;      BBDB present with appropriate (require 'bbdb). The fields
181 ;;      in NET and NAME are searched by default, but you can make the
182 ;;      completion feature to try ANY-FIELD if you change the value
183 ;;      of `tinymail-:complete-bbdb-fuzzy-method'. See variable documentation
184 ;;      for complete description.
185 ;;
186 ;;     Accepting the found match from .mailrc
187 ;;
188 ;;      TinyMail supports running several completion functions so that
189 ;;      the right match is inserted into the buffer. In order to
190 ;;      discard the found match from .mailrc file, you can set a trigger
191 ;;      to `tinymail-:confirm-mailrc-regexp'. Suppose, you want to confirm
192 ;;      completion whenever you are sending mail to your colleagues that
193 ;;      work in "disney.com". You'd set:
194 ;;
195 ;;          (setq tinymail-:confirm-mailrc-regexp "disney.com")
196 ;;
197 ;;      And if the match was picked from mailrc, you have a chance to
198 ;;      reject the string and move on with other completion functions.
199 ;;
200 ;;          To: world
201 ;;
202 ;;      When you hit tab here, a string "info@disneyword.com" was found from
203 ;;      the mailrc, but may not be what you want to insert. Because
204 ;;      you had set `tinymail-:confirm-mailrc-regexp', you get confirmation:
205 ;;
206 ;;          TinyMail: Use? info@disneyword.com
207 ;;
208 ;;      Where you can answer "n". The completion is canceled and all
209 ;;      other completion function have a chance to find more
210 ;;      suitable choice. (See Shared Shared Tab key explanation later)
211 ;;
212 ;;  Completion: Password table
213 ;;
214 ;;      In addition to .mailrc completion, there is support for completing
215 ;;      entries found from *passwd* file. If the guess complete above fails
216 ;;      the password file is examined if the mode is turned on. See
217 ;;      variable
218 ;;
219 ;;          tinymail-:password-mode
220 ;;
221 ;;      Which is set to t by default. When you complete password entries
222 ;;      for the first time, building all necessary variables will take some
223 ;;      time. After the password file completions have been parsed, the
224 ;;      content is written to cache file
225 ;;
226 ;;          tinymail-:password-file
227 ;;
228 ;;      Next time you need password completions, if this file exists,
229 ;;      it will be read instead of heavy /etc/passwd file parsing. If you
230 ;;      want to force reading the /etc/passwd again, just delete
231 ;;      `tinymail-:password-file' and it will be recreated next time
232 ;;      password completion is used.
233 ;;
234 ;;  Completion: Custom completion of any header
235 ;;
236 ;;      You can complete any field by setting variable
237 ;;      `tinymail-:table-header-complete' For example to complete "Class" header,
238 ;;      you would set the variable like this. See variable documentation
239 ;;      for more information.
240 ;;
241 ;;          (require 'assoc)
242 ;;          (aput
243 ;;           'tinymail-:table-header-complete
244 ;;           "Class"                ;; Add new header for use with TAB
245 ;;           (list
246 ;;            '("confidential"       ;; completion list
247 ;;              "private"
248 ;;              "for internal use only"
249 ;;              "personal private"
250 ;;              "personal another")))
251 ;;          ;; end example
252 ;;
253 ;;  CC field tracking
254 ;;
255 ;;      You can delete any elements from `Cc' field if you set variable
256 ;;      `tinymail-:cc-kill-regexp'. This feature can be used to delete
257 ;;      e.g. your email addresses from the list of `Cc' recipients to
258 ;;      to avoid getting duplicate copies of the mail when you reply.
259 ;;
260 ;;      At any time you can add two spaces in front of `Cc' field to
261 ;;      disable this "kill" feature. This is desirable if you WANT to
262 ;;      add a CC to your other email addresses. An example:
263 ;;
264 ;;        (setq tinymail-:cc-kill-regexp \"me@here.at\")
265 ;;
266 ;;        To: some@example.com
267 ;;        CC: me@here.at      << will be removed
268 ;;        CC:  me@here.at     << NOT removed, because field has two spaces.
269 ;;
270 ;;  RMAIL Fcc field tracking
271 ;;
272 ;;      By default the `Fcc' is not added in your mail message, thus this
273 ;;      package's automatic Fcc tracking isn't activated. Add following entry
274 ;;      into your $HOME/.emacs to record your outgoing mail messages
275 ;;
276 ;;          (setq mail-archive-file-name "~/.RMAIL.out")
277 ;;
278 ;;      When Emacs sees that you have set this, it adds the Fcc field to
279 ;;      your mail message. Alternatively you can press keys
280 ;;
281 ;;          C-c C-f C-f    ;; mail-fcc
282 ;;
283 ;;      in *mail* buffer and it asks you to insert the Fcc field.  Only
284 ;;      now, when the `Fcc' is in the message, the automatic Fcc handling
285 ;;      starts snooping around your headers and changing it if it finds a
286 ;;      match from variable
287 ;;
288 ;;          tinymail-:table-fcc
289 ;;
290 ;;      If you want to disable Fcc changing (and edit it by hand),
291 ;;      put two spaces at front of the Fcc. like this:
292 ;;
293 ;;          FCC:  ~/.RMAIL.secondary
294 ;;              ^^
295 ;;
296 ;;     Fcc and saving outgoing copy in compressed format
297 ;;
298 ;;      If you have an count that has quota limits, you want to save space
299 ;;      as much as possible. You can save your outgoing mail copy in
300 ;;      compressed format if you prepend the filename with ".gz" or
301 ;;      ".Z". TinyMail will automatically load jka-compr if it sees any of
302 ;;      those extensions. The fcc folder definition looks like this.
303 ;;
304 ;;          (defconst tinymail-:table-fcc
305 ;;             (list
306 ;;              (list "elisp-archive"    " ~/.mail.elisp-post.gz")
307 ;;              (list "bug-gnu"          " ~/.mail.bug.gz")
308 ;;              (list "."                " ~/.mail.out.gz"))) ;; general
309 ;;
310 ;;      You _have_ to add (require 'jka-compr) is you want to use compresses
311 ;;      RMAIL file.
312 ;;
313 ;;          ;;  first one is defined in paths.el
314 ;;          (setq rmail-file-name        "~/RMAIL.gz")
315 ;;          (setq mail-archive-file-name "~/.RMAIL.out.gz")
316 ;;
317 ;;  Gnus Gcc archiving
318 ;;
319 ;;      `Gcc' feature is similar to Fcc, but the Gcc is special to Gnus.
320 ;;      All instruction you read above for Fcc are same for
321 ;;      Gcc tracking feature. The table you have to configure is
322 ;;
323 ;;          tinymail-:table-gcc
324 ;;
325 ;;      Before you start defining Gnus folders, you must create them from
326 ;;      Gnus *Group* buffer with command `G' `m'. E.g. you may have
327 ;;      created following Gnus folders for newsgroup posting
328 ;;
329 ;;          nnfolder+archive:post-pgp
330 ;;          nnfolder+archive:post-emacs
331 ;;          nnfolder+archive:post-gen
332 ;;
333 ;;      The code below sets the `Gcc' folder only once when you start
334 ;;      composing message, probably a followup and there is a `Newsgroups'
335 ;;      header in the buffer. But if you hit `R' or `r' to reply directly to
336 ;;      person (or use message-mode for mailing), there is `To' header in
337 ;;      the buffer. Only now this package changes the Gcc field according to `To'
338 ;;      field contents. The code below is for Newsgroup posting.
339 ;;
340 ;;          (setq gnus-message-archive-group 'my-gnus-archive)
341 ;;
342 ;;          (defun my-gnus-archive  (group)
343 ;;            "Archive outgoing mail to right group: Create the group by G m"
344 ;;            (interactive)
345 ;;            (let* ()
346 ;;              (or (stringp group)                 ;No accidents...
347 ;;                  (setq group ""))
348 ;;              (cond
349 ;;               ((string-match "pgp\\|anon\\|privacy" group)
350 ;;                "nnfolder+archive:post-pgp")
351 ;;               ((string-match "emacs\\|gnu" group)
352 ;;                "nnfolder+archive:post-emacs")
353 ;;               (t
354 ;;                "nnfolder+archive:post-gen"))))
355 ;;
356 ;;  Feature: Sending message to mailing list
357 ;;
358 ;;      In Gnus you may have defined mailing lists like this
359 ;;
360 ;;          list.linux-announce
361 ;;          list.ding
362 ;;          list.java
363 ;;
364 ;;      And your personal work and mail groups with
365 ;;
366 ;;          mail.private
367 ;;          mail.misc
368 ;;
369 ;;          work.documents
370 ;;          work.fault
371 ;;          work.customer
372 ;;
373 ;;      Daemon messages to junk.daemon, Spam to junk.spam and so on.
374 ;;      Now suppose you are reading group `list.xxx' and you hit `f'
375 ;;      to send followup to an article. Your composed message looks like this:
376 ;;
377 ;;          To: answer-to-person <foo@bar.com>
378 ;;          Cc: <someone@list.com>, <list-foo@bar.com>
379 ;;
380 ;;      The Message goes to two people in the list and gets CC'd to
381 ;;      list. Not what you want. You want simple:
382 ;;
383 ;;          To: <list-foo@bar.com>
384 ;;
385 ;;      And this is what this package does for you. All you need to do it to
386 ;;      make sure the current group has Group parameter `to-list'.
387 ;;      defined. You add one with `G' `p] From *Group* and typing
388 ;;
389 ;;          ((to-list . "The List FOO <list-foo@bar.com>"))
390 ;;
391 ;;      This feature is controlled by `tinymail-:feature-hook' which contains
392 ;;      function `tinymail-mail-send-to-list'. If you remove the function from
393 ;;      the hook, this feature is disabled
394 ;;
395 ;;  Feature: Reporting incoming mail in local mail spool
396 ;;
397 ;;      Function to control mail reporting:
398 ;;
399 ;;          turn-on-tinymail-report-mail
400 ;;          turn-off-tinymail-report-mail
401 ;;
402 ;;      When you load this package the Report Mail feature is activated.
403 ;;      If you're running windowed Emacs, the X-drag bar (top of the frame)
404 ;;      is used to display the last incoming mail and count of pending
405 ;;      unread mail. Here the last message was from Mr. foo and the
406 ;;      pending mail count in spool is six.
407 ;;
408 ;;          "foo@bar.com 6" ;; See variable `tinymail-:report-format-string'
409 ;;
410 ;;      In non-windowed Emacs this same information is displayed in echo
411 ;;      are instead. If you would like to have it always displayed in
412 ;;      echo area, even in X environment, then set variable
413 ;;      `tinymail-:report-window-system' to nil before loading this package.
414 ;;
415 ;;      If you would like see more information about the arrived mail, you
416 ;;      can adjust `tinymail-:report-spool-buffer-control' e.g. to keep
417 ;;      permanent record of incoming mail. Value 'keep says that the report
418 ;;      mail buffer is kept when mail is queried, so you can glance it from
419 ;;      time to to for full information about arrived messages.
420 ;;
421 ;;      If the only feature you want is the mail reporting functionality,
422 ;;      you can activate it and disable all other settings with:
423 ;;
424 ;;          ;; Don't activate tinymail-mode
425 ;;          (setq tinymail-:enter-mail-hook-list nil)
426 ;;          (require 'tinymail)
427 ;;
428 ;;     Setting up report mail notify program
429 ;;
430 ;;      The `tinymail-:report-mail-notify-program' fetches the Berkeley Mailbox
431 ;;      formatted information from mailboxes. The default program used
432 ;;      is `from(1)', but in case you don't have it, a equivalent command
433 ;;      "grep '^From ' $MAIL" is used. See also `frm(1)'
434 ;;      and `nfrm(1)' `newmail(1)' and `mailfrom(1)' if you can
435 ;;      find those in your system.
436 ;;
437 ;;      If you use Gnus and separate spool files, like you would do with
438 ;;      Procmail, then you need to gather mail arrival information from all
439 ;;      the spool files. Let's suppose you don't want to get notified on
440 ;;      mailing list messages, but only messages saved to your private and
441 ;;      work spool files:
442 ;;
443 ;;          ~/Mail/spool        or `nnmail-procmail-directory'
444 ;;          mail.misc
445 ;;          mail.private
446 ;;          mail.programming
447 ;;          mail.emacs
448 ;;          mail.java
449 ;;          ...
450 ;;
451 ;;          work.meetings
452 ;;          work.docs
453 ;;          work.customer
454 ;;          ...
455 ;;
456 ;;      In that case you have to install custom mail notify program. A
457 ;;      simple multiple mailbox grep will work here. Note, we also grep
458 ;;      default $MAIL:
459 ;;
460 ;;          (setq tinymail-:report-mail-notify-program
461 ;;            (format
462 ;;              "grep '^From ' %s %s %s "
463 ;;              (or (getenv "MAIL") (error "No $MAIL defined"))
464 ;;              (concat (expand-file-name "~/Mail/spool/") "mail.*")
465 ;;              (concat (expand-file-name "~/Mail/spool/") "work.*")))
466 ;;
467 ;;      Take a look at variable `tinymail-:report-spool-buffer-control'
468 ;;      which has default value 'keep where the
469 ;;      `tinymail-:report-mail-notify-program' results are gathered. You
470 ;;      may find it useful to keep the `tinymail-:report-spool-buffer'
471 ;;      *tinymail-mail-spool* visible in some frame to act like `biff(1)'.
472 ;;      From there you can find more detailed information of incoming
473 ;;      message queue, than the simple message count in echo-area or x-drag
474 ;;      bar.
475 ;;
476 ;;      _Note_: XEmacs has package `reportmail.el'. In case that package
477 ;;      is loaded, the report mail feature here is not installed.
478 ;;
479 ;;  Feature: Saving unused mail buffers on Emacs exit
480 ;;
481 ;;      This file installs one function to `kill-emacs-hook' that loops
482 ;;      through all mail buffers and appends the buffer content to
483 ;;
484 ;;          tinymail-:dead-mail-file
485 ;;
486 ;;      If you had some unfinished messages that you didn't yet send, you
487 ;;      can restore the copy from this file when you restart emacs again.
488 ;;      In Gnus `message-mode', you can use following to trash sent mail:
489 ;;
490 ;;          (setq message-kill-buffer-on-exit t)
491 ;;
492 ;;      If you don't want to use this feature, add following code to your
493 ;;      $HOME/.emacs
494 ;;
495 ;;          (add-hook 'tinymail-:load-hook 'my-tinymail-:load-hook)
496 ;;          (defun my-tinymail-:load-hook  ()
497 ;;            (remove-hook 'kill-emacs-hook 'tinymail-save-dead-mail))
498 ;;
499 ;;      _Note_: VM and Gnus can keep the sent mail buffer around. This
500 ;;      package won't install `tinymail-save-dead-mail-maybe' the dead mail
501 ;;      collector under
502 ;;      Gnus and VM.
503 ;;
504 ;;  Feature: anti-ube email addresses
505 ;;
506 ;;     Philosophy
507 ;;
508 ;;      Changing the email address so that is is not pointing to your
509 ;;      natural address is usually referred as "address munging". There are
510 ;;      two schools that take firm position to express their views in this
511 ;;      matter. Those who say that it is "plain wrong to munge address" and
512 ;;      those who say "RFC does not require you to use REAL, returnable,
513 ;;      address". It can be argued that the email address is property of
514 ;;      an individual who can take measures to protect himself from getting
515 ;;      into the email harvester's "2 billion email address on a CD for
516 ;;      $100"
517 ;;
518 ;;      Here is an opinion whether it is right to munge the
519 ;;      address according to RFC by Marty Fouts
520 ;;      1997-11-05 in newsgroup gnus.emacs.gnus:
521 ;;
522 ;;      o   The real implementation of news software doesn't care if the from
523 ;;          field is munged or not
524 ;;      o   No RFC forces the address of the poster to be a *reachable* addr.
525 ;;          It only requires such addresses to be syntactically correct.
526 ;;      o   RFC 1036 _specifically_ states that it is not an Internet
527 ;;          standard.
528 ;;      o   News is a *public* forum. Mail is a *private*
529 ;;          communication medium. Posting in a _public_ forum does not
530 ;;          require that you give you access to _private_ address, just as
531 ;;          speaking at a public meeting does not require that I give you give
532 ;;          unlisted phone number.
533 ;;
534 ;;     Why to munge From address
535 ;;
536 ;;      o   Email address is one's own property. The reasons to munge are
537 ;;          one's own. In perfect world you wouldn't need lock to your
538 ;;          doors, but you do have them in houses. The world has changed
539 ;;          in respect to email too.
540 ;;      o   Filter solution is no-road. It's an arms race; some UBE always
541 ;;          sneaks through and it will never stop the actual UBE.
542 ;;      o   Not all people have access to filtering tools (some amy
543 ;;          require certain Operating System e.g. Unix Procmail).
544 ;;      o   POP users download their post and each UBE byte costs in transfer
545 ;;          time.
546 ;;      o   Nothing works as well as *not* giving the real address in the
547 ;;          first place.
548 ;;
549 ;;      This package can activate the address munging very easily for
550 ;;      selected newsgroups and make those email harverters gathering job
551 ;;      more difficult. Humans that want to contact the person can still
552 ;;      decode the address.
553 ;;
554 ;;      To activate address munging for newsgroups matching regexp, set
555 ;;      variable `tinymail-:from-anti-ube-regexp'. Your `user-mail-address'
556 ;;      is be hashed and different address is generated for each post.
557 ;;
558 ;;          me@here.com   --> me.ads-hang@here.com, me.hate-ube@here.com ...
559 ;;
560 ;;  Feature: Sendmail Plus Addressing (introduction)
561 ;;
562 ;;        [excerpted from http://pm-doc.sourceforge.net/ for background]
563 ;;        Recall from [rfc1036] that the preferred Usenet email address
564 ;;        formats are following
565 ;;
566 ;;              From: login@example.com
567 ;;              From: login@example.com (First Surname)
568 ;;              From: First Surname <login@example.com>
569 ;;
570 ;;        A new sendmail supports plus addressing, where the address is
571 ;;        treated like <login@example.com> and the extra "plus-info" is
572 ;;        available eg to procmail or other LDAs. See Eli'd faq for more
573 ;;        information at http://www.faqs.org/faqs/mail/addressing/ A typical
574 ;;        sendmail enabled plus address looks like:
575 ;;
576 ;;            login+plus-info@domain
577 ;;
578 ;;        We can simulate plus addressing with pure RFC compliant address.
579 ;;        We exploit RFC comment syntax, where comment is any text inside
580 ;;        parentheses. According to Eli's paper, comments should be
581 ;;        preserved during transit. They may not appear in the exact place
582 ;;        where originally put, but that shouldn't be a problem. So, we
583 ;;        send out message with following `From' or `Reply-To' line:
584 ;;
585 ;;            first.surname@domain (First Surname+mail.default)
586 ;;
587 ;;        Now, when someone replies to you, the MUA usually copies that
588 ;;        address as is and you can read in the receiving end the PLUS
589 ;;        information and drop the mail to appropriate folder: `mail.default'.
590 ;;
591 ;;        [About subscribing to mailing lists with RFC comment-plus addess]
592 ;;
593 ;;        It's very unfortunate that when you subscribe to lists, the comment
594 ;;        is not preserved when you're added to the list database. Only the
595 ;;        address part is preserved. I even put the comment inside angles to
596 ;;        fool program to pick up everything between angles.
597 ;;
598 ;;            first.surname(+list.linux)@example.com
599 ;;
600 ;;        But I had no luck. They have too good RFC parsers, which throw away
601 ;;        and clean comments like this. E.g. procmail based mailing lists, the
602 ;;        famous `Smartlist', use `formail' to derive the return address and
603 ;;        `formail' does not preserve comments. The above gets truncated to
604 ;;
605 ;;            first.surname@example.com
606 ;;
607 ;;        You can put anything inside RFC comment and do whatever you want
608 ;;        with these plus addresses. _NOTE_: there are no guarantees that
609 ;;        the RFC comment is preserved every time. Well, the standard RFC822
610 ;;        says is must be passed untouched, but I'd say it is 90% of the
611 ;;        cases where mail is delivered from one server to another, it is
612 ;;        kept.
613 ;;
614 ;;        Example: if you discuss in usenet groups, you could use address
615 ;;
616 ;;            first.surname@example.com (First Surname+usenet.default)
617 ;;            first.surname@example.com (First Surname+usenet.games)
618 ;;            first.surname@example.com (First Surname+usenet.emacs)
619 ;;            first.surname@example.com (First Surname+usenet.linux)
620 ;;
621 ;;  Feature: Sendmail Plus Addressing in this package
622 ;;
623 ;;      The idea of setting PLUS information is that you "tag" you messages
624 ;;      and when messages are returned to you, you can file the messages to
625 ;;      proper folders. Unix users can set up a procmail receipe to trap
626 ;;      the plus information. Alternatively Emacs Gnus can be configured
627 ;;      to use fancy splitting methods for IMAP, POP and regular
628 ;;      mailbox.
629 ;;
630 ;;      The sender field generation is disabled in `message-mode-hook' by
631 ;;      function `tinymail-message-disable-sender', so that *From* field
632 ;;      gets a trusted status. If you still want to generate the *Sender*
633 ;;      field, then add this after package has been loaded.
634 ;;
635 ;;          (remove-hook 'tinymail-message-disable-sender 'message-mode-hook)
636 ;;
637 ;;     Non-Newsgroup posting
638 ;;
639 ;;      Use your custom function to decide what address to use and what
640 ;;      plus information to use by setting function to
641 ;;      `tinymail-:from-info-function'. Non-Newsroup posting means, that
642 ;;      you're not inside a Gnus Newsgroup from where you initiate
643 ;;      a "post". A typical invocation to non-Newsroup posting is `C-x' `m'.
644 ;;
645 ;;     Newsgroup posting
646 ;;
647 ;;      You might want to set `tinymail-:from-info-function' return
648 ;;      different email address for Usenet newsgroup posts. Set up an free
649 ;;      email account somewhere and use only that for Usenet discussions.
650 ;;      That way you can reserve your normal address to your private email
651 ;;      communication.
652 ;;
653 ;;      The settings you need to enable the address generation is simple.
654 ;;      Table `tinymail-:from-table-prefix' sets the left part of
655 ;;      the plus address component and `tinymail-:from-table-postfix' can
656 ;;      set the right part after period.
657 ;;
658 ;;          tinymail-:from-table-prefix + tinymail-:from-table-postfix
659 ;;
660 ;;      This makes the the *+left.right* information which is added after
661 ;;      your `user-full-name' part. If `tinymail-:from-table-prefix'
662 ;;      returns nothing, the `tinymail-:from-table-postfix' is used as is.
663 ;;      Here is example setup. Pay attention to the "work.misc" which is
664 ;;      the return value for all addresses matching "my-work-site".
665 ;;
666 ;;          (setq tinymail-:from-table-prefix
667 ;;            '(("emacs\\|perl" . "mail")
668 ;;              ("."            . "usenet"))
669 ;;
670 ;;          (setq tinymail-:from-table-postfix
671 ;;            '(("games"                                 . "games")
672 ;;              ("emacs\\|[a-z]+\\.el\\>\\|(def\\|(setq" . "emacs")
673 ;;              ("perl\\|\\.pl\\>"                       . "perl")
674 ;;              ("my-work-site\\>"                       . "work.misc"))
675 ;;
676 ;;     Gnus support
677 ;;
678 ;;      If you use Gnus news reader, then you get some bonus. For Gnus
679 ;;      users the default plus information is generated based on the group
680 ;;      you're posting from. In general the plus address generated is
681 ;;      directly the group's name. That's quite convenient. To make this
682 ;;      this effective for mailing lists too, do this:
683 ;;
684 ;;      o   Rename all your mailing lists to start with *list.NAME*
685 ;;          like list.ding, list.linux, list.procmail, list.dance ...
686 ;;      o   Edit each mailing lists group parameter with `G' `p'
687 ;;          from *Group* buffer and add mailing list destination address:
688 ;;
689 ;;              ((to-list . "Mailing List Name <address@example.com>"))
690 ;;
691 ;;      Now when the `to-list' property is set, The Gnus group is labeled
692 ;;      as "mailing list". If the `to-list' property is not set, the group
693 ;;      is not considered as mailing list.
694 ;;
695 ;;  Feature: Toggle plugged state
696 ;;
697 ;;      With dial up connections, it is customary to swap between on-line
698 ;;      and off-line mode. If you use Gnus as your mail reader, TinyMail
699 ;;      can show the plugged status in the `mode-line'. If you see "tm!"
700 ;;      you're plugged (on-line). The key to change the Gnus plugged status
701 ;;      is bound to `C-c' `t' `j' in TinyMail controlled mail buffer.
702 ;;
703 ;;  Configuration: Highlighting color settings
704 ;;
705 ;;      The default highlighting is only provided to your convenience. If
706 ;;      you use `font-lock' the internal highlighting is *automatically*
707 ;;      suppressed.
708 ;;
709 ;;  Configuration: Default citation header
710 ;;
711 ;;      This feature is mainly designed for Gnus `message-mode'. Use it
712 ;;      like this:
713 ;;
714 ;;          (setq mail-yank-prefix  "| ")   ;; less noisy, than "> "
715 ;;          (setq mail-user-agent   'message-user-agent)
716 ;;
717 ;;      There is function `tinymail-citation-generate' which generates
718 ;;      citation that uses international ISO 8601 date format, user name
719 ;;      and the Gnus mailing group from where the reply started:
720 ;;
721 ;;          * Tue YYYY-MM-DD John Doe <johnd@example.com> mail.emacs
722 ;;          | ...said something
723 ;;
724 ;;      To activate this citation reference function with your Mail User
725 ;;      Agent (Gnus, RMAIL ..), call:
726 ;;
727 ;;          (add-hook 'tinymail-:load-hook 'tinymail-install-citation)
728 ;;
729 ;;      For supercite, install this function to the handlers and select
730 ;;      it with index 0:
731 ;;
732 ;;          (require 'sc)
733 ;;          (push (list tinymail-citation-generate) sc-rewrite-header-list)
734 ;;          (setq sc-preferred-header-style 0)
735 ;;
736 ;;  Code Note: shared TAB key
737 ;;
738 ;;      When TinyMail is active in the mail buffer, it takes ower the tab
739 ;;      key. The default function `tinymail-complete-guess-in-headers' is
740 ;;      electric, meaning that it behaves like ordinary tab if the point is
741 ;;      not in completing headers. E.g. If the point is in `Cc' or in `To',
742 ;;      then the completion feature is activated. If you have plans to use
743 ;;      the tab key to do some other special things in other headers,
744 ;;      you're free to to do so. All you have to do is to add your own
745 ;;      custom function into
746 ;;
747 ;;          `tinymail-:complete-key-hook'
748 ;;
749 ;;      The custom function must return `t' if it did something. See also
750 ;;      `tinymail-:table-header-complete' where it is possible to define
751 ;;      custom headers and the completions easily.
752
753 ;;}}}
754
755 ;;; Change Log:
756
757 ;;; Code:
758
759 ;;{{{ setup: require
760
761 ;;; ......................................................... &require ...
762
763 (require 'tinylibm)
764 (require 'tinylibmail)
765
766 ;;  We must have these minor modes loaded beforehand. That's
767 ;;  because we can't override that TAB key unless we "became" minor
768 ;;  mode after these packages.
769
770 (require 'tinytab    nil 'noerr)
771 (require 'tinyindent nil 'noerr)
772
773 (autoload 'bbdb-hashtable    "bbdb" "" nil 'macro)
774 (autoload 'bbdb-gethash      "bbdb")
775 (autoload 'bbdb-record-net   "bbdb")
776 (autoload 'bbdb-record-name  "bbdb")
777 (autoload 'bbdb-record-notes "bbdb")
778
779 (autoload 'mail-position-on-field              "sendmail")
780 (autoload 'mml-secure-message-sign-pgpmime     "mml")
781 (autoload 'mml-secure-message-encrypt-pgpmime  "mml")
782
783 (eval-and-compile
784
785   (ti::package-use-dynamic-compilation)
786   (ti::package-require-mail-abbrevs)
787
788   ;;  forward declarations for byte compiler
789   (defvar message-citation-line-function)
790   (defvar message-reply-headers)
791   (defvar bbdb-file)
792   (defvar tinytab-:tab-insert-hook)
793   (defvar tinytab-mode)
794
795   (unless (locate-library "bbdb")
796     (message "\
797   ** tinymail.el: No bbdb.el along `load-path'. http://bbdb.sourceforge.net/
798                   You can still use the package if you do not byte compile it.
799                   Package will adapt to missing BBDB features."))
800
801   (autoload 'message-tab                "message"  "" t)
802   (autoload 'message-narrow-to-headers  "message")
803
804   (let ((loc (locate-library "nnheader")))
805     (unless loc
806       (message "\
807   ** tinymail.el: You have too old Gnus, visit http://www.gnus.org/
808                   Old Gnus version found at %s" loc)))
809
810   (autoload 'mail-header-from   "nnheader" "" nil 'macro)
811   (autoload 'mail-header-date   "nnheader" "" nil 'macro))
812
813 (eval-when-compile
814   ;; (require 'advice)
815   (ti::package-use-dynamic-compilation))
816
817 (ti::package-defgroup-tiny TinyMail tinymail-: mail
818   "Some mail additions: dynamic Fcc, Cc
819         Overview of features
820
821         o  Some handy additions to mail sending interface.
822         o  Adds automatically Cc field when you type the To: address.
823         o  Changes Fcc dynamically according to header content.
824         o  Very easy TAB completion: two modes, alias and definition string.
825            or password file entry completion.
826         o  if Fcc folder has .gz or .Z name it automatically triggers
827            loading jka-compr.")
828
829 ;; Without fully qualified domain name,  smtpmail.el
830 ;; can't send messages. Make sure the email is in format user@domain.com
831
832 (when (or (not (stringp user-mail-address))
833           (not (string-match ".+@.*\\..+"
834                              (or user-mail-address
835                                  ""))))
836   (message
837    (concat "Tinymail: [ERROR] Please set `user-mail-address' "
838            "to \"user@somewhere.net\". Was %s")
839    (prin1-to-string user-mail-address)))
840
841 ;;}}}
842 ;;{{{ setup: hooks
843
844 ;;; ......................................................... &v-hooks ...
845 ;;; hooks and functions
846
847 (defcustom tinymail-:load-hook nil
848   "*Hook run when package has been loaded."
849   :type  'hook
850   :group 'TinyMail)
851
852 ;;  Add more dynamic change functions to this hook
853
854 (defcustom tinymail-:process-hook nil
855   "*Hook run when `tinymail-:awake-time' is up. This hook is always run."
856   :type  'hook
857   :group 'TinyMail)
858
859 (defcustom tinymail-:feature-hook '(tinymail-mail-send-to-list)
860   "*Hook run when idle time is up. Optional features to run.
861 Eg If you're using Gnus for mailing lists. Please define `to-list'
862 Group parameter for each group."
863   :type  'hook
864   :group 'TinyMail)
865
866 (defcustom tinymail-:complete-key-hook
867   '(tinymail-complete-everything
868     ;; tinymail-complete-bbdb-fuzzy ;honor BBDB first
869
870     ;;  tinymail-complete-bbdb <NO GOOD> because displays
871     ;;  BBDB record.
872     tinymail-complete-simple
873     tinymail-complete-guess-in-headers  ; then passwd
874
875     ;;  tinymail-complete-bbdb <NO GOOD> because displays
876     ;;  BBDB record. fuzzy is better
877
878     ;; tinymail-complete-bbdb-fuzzy
879
880     tinymail-complete-headers-nothing-found
881
882     ;; tinymail-complete-guest-packages
883     ;; tinymail-complete-abbrevs
884     ;; tinymail-complete-headers-move-to-next-field
885
886     tinymail-complete-everything)
887   "*Run each function with argument nil until success.
888 This function contains default try funcions
889 to completes email addresses in the Cc and To headers. It is strongly
890 suggested that you don't add new functions to this hook with `add-hook',
891 but that that you set the value manually. The order of the tried functions
892 is important.
893
894 Default value for this hook is as follows. These are preset at startup
895 by calling function `tinymail-install-hooks' at package load time.
896
897   '(tinymail-complete-everything
898     tinymail-complete-simple
899     tinymail-complete-headers-nothing-found
900     tinymail-complete-abbrevs
901     ;; put your own completion functions here. Befor call to guest packages
902     tinymail-complete-guest-packages)
903
904 Function call arguments:
905
906   info  This variable holds the string part at current point
907   '(BEG END STRING)
908
909 Function should return:
910
911   nil               Did nothing; pass control to next function in hook.
912   non-nil           Handled the Tab at point"
913   :type  'hook
914   :group 'TinyMail)
915
916 (defcustom tinymail-:complete-body-hook
917   '(tinymail-complete-bbdb-fuzzy-at-point
918     tinymail-complete-guess)
919   "*Run each function with argument nil until completion success.
920 This is similar variable like `tinymail-:complete-key-hook' but run in the
921 message body. E.g. When user want to add a BBDB net entry to the current point."
922   :type  'hook
923   :group 'TinyMail)
924
925 (defcustom tinymail-:send-mail-hook-list
926   '(mail-send-hook ;; VM runs this too
927     message-send-hook
928     mh-before-send-letter-hook)
929   "*List of mail sending hooks."
930   :type  '(repeat (symbol :tag "Hook"))
931   :group 'TinyMail)
932
933 (defcustom tinymail-:citation-message-id-function 'tinymail-message-id
934   "Return message-id line that is added above the citation header."
935   :type  'function
936   :group 'TinyMail)
937
938 ;;}}}
939 ;;{{{ setup: config public
940
941 ;;; ........................................................ &v-public ...
942 ;;; User configurable
943
944 (defcustom tinymail-:protect-email-addresses t
945   "*If non-nil, then scrable all words that look like an email address.
946 E.g. this@example.com is made something like this <AT> example.com.
947
948 Note: If you are sending other content with mail, like patches, make sure
949 you protect those with base64 encoding to prevent changing the content."
950   :type '(repeat (symbol :tag "Keymap variable"))
951   :group 'TinyMail)
952
953 (defcustom tinymail-:table-keymap-list
954   '(mail-mode-map
955     message-mode-map
956     mh-letter-mode-map)
957   "*List of keymaps where to install default bindings."
958   :type '(repeat (symbol :tag "Keymap variable"))
959   :group 'TinyMail)
960
961 (defcustom tinymail-:enter-mail-hook-list
962   '( ;; gnus-message-setup-hook
963     message-header-setup-hook
964
965     ;; mail-send-hook
966     mail-setup-hook
967
968     mh-letter-mode-hook             ;; MH
969     vm-mail-mode-hook)              ;; VM
970   "*List of hooks where to install `tinymail-mail'."
971   :type '(repeat (symbol :tag "Hook"))
972   :group 'TinyMail)
973
974 (defcustom tinymail-:dead-mail-file
975   (ti::package-config-file-prefix "tinymail-dead-mail")
976   "*Append all mail buffers to this fAile on Emacs exit."
977   :type  'file
978   :group 'TinyMail)
979
980 (defcustom tinymail-:awake-time
981   (if (ti::xemacs-p)
982       10                                ;XEmacs needs lower value
983     15)
984   "*Sleep time of `post-command-hook' before activation."
985   :type  '(integer :tag "Movement Cycles")
986   :group 'TinyMail)
987
988 (defcustom tinymail-:confirm-mailrc-regexp  "."
989   "*If matches, confirm picked completions from .mailrc file.
990 When the completion is found from the .mailrc it is matched against
991 this regexp. If `tinymail-:confirm-mailrc-regexp' matches, then
992 you're asked if you accept the match. If you discard it the other
993 completion functions get a chance to run."
994   :type  'regexp
995   :group 'TinyMail)
996
997 (defcustom tinymail-:cc-kill-regexp (and (stringp user-mail-address)
998                                          (regexp-quote user-mail-address))
999   "*Kill all CC field elements matching regexp.
1000 The usual value is you possibel Email addresses that you
1001 wish to remove from CC fields to avoid duplicate copies when
1002 you already use Means of Fcc, Gcc etc.
1003
1004 At any time you can add two spaces in front of CC field to
1005 disable this \"kill\" feature. This is desirable if you WANT to
1006 add a CC to your other email addresses. An example:
1007
1008   (setq tinymail-:cc-kill-regexp \"me@here.at\")
1009
1010   To: some@example.com
1011   CC: me@here.at             << will be removed
1012   CC:  me@here.at            << NOT removed, because field has two spaces."
1013   :type  'regexp
1014   :group 'TinyMail)
1015
1016 (defcustom tinymail-:password-mode t
1017   "*Should we try to complete passwd entries?.
1018 if normal .mailrc completion fails then  non-nil enables completion.
1019
1020 If you're running slow machine and huge amount of users, and
1021 you can't afford to use `tinymail-:password-file' due to disk quota
1022 reasons, set this variable to nil and no passwords entries are
1023 completed. It's faster to defne .mailrc aliases that you need.
1024
1025 This variable can be toggled with \\[tinymail-complete-password-mode]."
1026   :type  'boolen
1027   :group 'TinyMail)
1028
1029 (defcustom tinymail-:password-file
1030   (ti::package-config-file-prefix "tinypath-passwd.el")
1031   "*Preparsed password file completions.
1032 If this file does not exist it will be created when passwd
1033 completion is needed. You _can_ keep this file in compressed format by
1034 adding extension .gz to filename.
1035
1036 If this file is nil, then no file is read or written to."
1037   :type  'file
1038   :group 'TinyMail)
1039
1040 (defcustom tinymail-:password-cat-cmd
1041   (cond
1042    ((ti::os-check-hpux-p)
1043     "ypcat passwd")
1044    ((ti::os-check-sunos-p)
1045     "cat /etc/passwd")
1046    ((string-match "irix" (emacs-version))
1047     "ypcat passwd")
1048    ((ti::os-check-linux-like-p)
1049     "cat /etc/passwd")
1050    ((ti::win32-p)
1051     nil) ;; No password file here
1052    (t
1053     (error
1054      (substitute-command-keys
1055       (concat
1056        "TinyMail: No tinymail-:password-cat-cmd. Please share your know-how"
1057        " with \\[tinymail-submit-bug-report]")))))
1058   "*Shell command that print the contents of standard UNIX passwd file.
1059 If your systems shell command isn't seen here, contact maintainer
1060 immedately and report right shell command, so that it is set automatically
1061 right."
1062   :type  '(string :tag "Command")
1063   :group 'TinyMail)
1064
1065 (defcustom tinymail-:complete-bbdb-fuzzy-method
1066   '( ;; Can't funcall macros, so wrap them inside lambda's
1067     (lambda (record) (bbdb-record-net        record))
1068     (lambda (record) (bbdb-record-aka        record))
1069     (lambda (record) (bbdb-record-name       record))
1070     (lambda (record) (bbdb-record-raw-notes  record)))
1071   "*Which fields to check against the completion string.
1072 The value must be list of functions to return a string or list of strings
1073 to match when passed and BBDB RECORD.
1074
1075 The value must be callable by `funcall', e.g. macros are not callable."
1076   :type  '(list sexp)
1077   :group 'TinyMail)
1078
1079 (defcustom tinymail-:complete-bbdb-case-fold-search case-fold-search
1080   "*Should completing against BBDB record be case sensitive.")
1081
1082 (defcustom tinymail-:complete-mode 'string
1083   "*Control how completion is done.
1084
1085 'alias
1086
1087     Means that we should complete alias names and
1088     that the alias expansion is shown in echo-area.
1089
1090 'string
1091
1092     Means that _string_, which may include any character including white
1093     spaces, is searched from the full expansion list of aliases. This way
1094     you can remember anything from the person itself and it will be
1095     searched. Found expansion is inserted in place of typed
1096     string.
1097
1098     Your ~/.mailrc can have entries like this:
1099
1100     alias mark   \"Mark Eggert -- Project engineer <meg@twenix.com>\"
1101     alias mike   \"Michael Lowell  -- SkyTrax consulting <ml@sky.com>\"
1102
1103     The right hand strings are searched with picked _string_ and
1104     if there is only one match for the string, the expansion (rh element)
1105     is inserted into buffer."
1106   :type '(choice
1107           (const alias)
1108           (const expansion))
1109   :group 'TinyMail)
1110
1111 (defconst tinymail-:idle-timer-seconds 1
1112   "*Seconds after Emacs is idle to check the mail contant in buffer.")
1113
1114 ;;; ........................................................ &v-tables ...
1115
1116 (defcustom tinymail-:table-fcc nil
1117   "*Replace Fcc content with FCC-FIELD-STRING when headers match REGEXP.
1118 If there is _two_ spaces in the Fcc field, the Fcc header is not touched.
1119 Format is '((REGEXP FCC-FIELD-STRING) ..)"
1120   :type  '(repeat
1121            (list
1122             (string :tag "To Regexp")
1123             (sexp   :tag "Fcc field string")))
1124   :group 'TinyMail)
1125
1126 (defcustom tinymail-:table-gcc nil
1127   "*Replace Gcc content with GCC-FIELD-STRING when headers match REGEXP.
1128 If there is _two_ spaces in the Gcc field, the Gcc header is not touched.
1129 Format is '((REGEXP GCC-FIELD-STRING) ..)"
1130   :type  '(repeat
1131            (list
1132             (string :tag "To Regexp")
1133             (sexp   :tag "Gcc field string")))
1134   :group 'TinyMail)
1135
1136 ;; (all-completions "nnml" gnus-active-hashtb 'gnus-valid-move-group-p)
1137
1138 (defcustom tinymail-:table-header-complete nil
1139   "*List of field names and their copletion values.
1140 If after HEADER-FIELD the value is not a string, the rest value is evaluated.
1141 HEADER-FIELD must not contain colon.
1142
1143 Format:
1144  '((HEADER-FIELD (COMPLETION-STRING COMPLETION-STRING ..)
1145    (HEADER-FIELD EVAL-FORM)
1146    ..)
1147
1148 Notes
1149
1150   The EVAL-FORM must set `tinymail-:complete-key-return-value' to non-nil
1151   if it does not return a list of completions, but otherwise handles
1152   the completions itself. This stops running other completion functions.
1153
1154 Example:
1155
1156 If you want to to complete header `Class' with values Urgent, Note, Memo,
1157 FYI, Announce.
1158
1159 In addition completing the Gnus Gcc and Newsgroup header is easy. Some notes
1160 about the EVAL-FORM used: the form is called in function
1161 `tinymail-complete-simple', so all variables used there are visible in
1162 EVAL-FORM. The `string' is the read word from current point, which you
1163 should use when searching completions.
1164
1165  (setq tinymail-:table-header-complete
1166    '((\"Class\"
1167       (\"Urgent\" \"Note\" \"Memo\" \"FYI\" \"Announce\"))
1168
1169      (\"Gcc\"
1170       (when (and (featurep 'gnus) (stringp string))
1171         (all-completions
1172          string
1173          gnus-active-hashtb 'gnus-valid-move-group-p)))
1174
1175     (\"Newsgroups\"
1176       (when (and (featurep 'gnus) (stringp string))
1177         (all-completions
1178          string
1179          gnus-active-hashtb
1180          (gnus-read-active-file-p))))))"
1181   :type  '(repeat
1182            (list
1183             (string :tag "Field")
1184             (repeat (string :tag "value"))))
1185   :group  'TinyMail)
1186
1187 ;;}}}
1188 ;;{{{ setup: Sendmail like PLUS Address configuration
1189
1190 (defcustom tinymail-:from-field-plus-separator "+"
1191   "The string to separate `user-full-name' from plus information.
1192 Note: some MTAs may not accept '+' character. An alternative
1193 could be '--'.
1194
1195   login+plus-information@example.com
1196
1197   login@example.com (First Surname+plus-information)"
1198   :type  'string
1199   :group 'TinyMail)
1200
1201 (defcustom tinymail-:from-field-enable-flag t
1202   "*Non-nil means that From: field generation is allowd.
1203 The function to generate the from field is `tinymail-from-set-field'."
1204   :type  'boolean
1205   :group 'TinyMail)
1206
1207 (defcustom tinymail-:from-anti-ube-regexp "games\\|ibm"
1208   "*If Regexp match Newsgroups header, generate anti-ube email From address.
1209 Generated address is based un `user-mail-address' with hashed words in
1210 the address. The generated email is made with `ti::mail-email-make-anti-spam-address'.
1211 Returned value is different each time.
1212
1213    me@here.com   --> me.ads-hang@here.com, me.hate-ube@here.com ...
1214
1215 References:
1216
1217   For complete email address control, you want to use
1218   `tinymail-:from-info-function'."
1219   :type  'regexp
1220   :group 'TinyMail)
1221
1222 (defcustom tinymail-:from-info-function  nil
1223   "*Functon to return the suitable `user-mail-address' for message.
1224
1225 Return value:
1226
1227  '(email-address [plus-string] [Filername Surname])
1228
1229     If if function wants to change only the email-address for the message,
1230     the return value is in format:
1231
1232      '(\"foo@bar.com\")
1233
1234     And if the Plus info and Another user-id FirstName and Surname is
1235     wanted, then return value is:
1236
1237      '(\"foo@bar.com\" \"mail.priv\" \"Mr. Foo\")
1238
1239     If the return value is nil, the `user-mail-address' is used.
1240
1241 Notes
1242
1243     Value returned from this function overrides
1244
1245         `tinymail-:from-table-prefix'
1246         `tinymail-:from-table-postfix'
1247         `tinymail-:from-anti-ube-regexp'
1248
1249     If you want to protect yourself from UBE (Unsolicited bulk Email), you
1250     can use function `ti::mail-email-make-anti-spam-address' which uses hash table
1251     to construct human, but not easily machine decodable address.
1252
1253 Example one:
1254
1255     Suppose you have some public domain email address, like hotmail
1256     and you want to use that in your Usenet postings instead of your normal
1257     email address. Here is code to do that:
1258
1259     (setq tinymail-:from-info-function 'my-tinymail-address)
1260
1261     (defun my-tinymail-address ()
1262       (when (mail-fetch-field \"Newsgroups\")
1263         (list \"my-virtual@hotmail.com\")))
1264
1265 Example two:
1266
1267     Suippose you want to make email harverter's work harder and use non-spam
1268     address in the high traffic Usenet groups. Here the ibm and games groups
1269     get \"protected\" address, which human can decode if they wish to contact
1270     you personally. Other usenet groups use your normal virtual aaddress.
1271     All other mail use your default `user-mail-address'.
1272
1273     (setq tinymail-:from-info-function 'my-tinymail-address)
1274
1275     (defun my-tinymail-address ()
1276       let ((group (or (mail-fetch-field \"Newsgroups\") \"\" ))
1277            (addr  \"my-virtual@hotmail.com\"))
1278       (cond
1279         ((string-match \"games\\\\|ibm\" group)
1280          (list (ti::mail-email-make-anti-spam-address addr)))   ;; grumbled address
1281         ((string= \"\" group)
1282          (list addr))))                              ;; use normal virtual address
1283
1284     This differ's from `tinymail-:from-anti-ube-regexp' so that you have full
1285     control what address is used to generate the anti-ube address."
1286   :type  'string
1287   :group 'TinyMail)
1288
1289 (defcustom tinymail-:from-table-prefix nil
1290   "*If `Newsgroup' header, match regexp, return plus address prefix.
1291
1292 Format:
1293
1294   '((REGEXP . STRING)
1295     (REGEXP . STRING)
1296     ..)
1297
1298 Example
1299
1300   '((\"emacs\\\\|perl\" . \"mail\")
1301     (\".\"   \"usenet\"))"
1302   :type  '(repeat (cons regexp string))
1303   :group 'TinyMail)
1304
1305 (defcustom tinymail-:from-table-postfix nil
1306   "*Rules for constructing COMMENT PLUS part of the From address.
1307
1308 Match the Newsgroup header:
1309
1310     If there is `Newsgroup' header, match regexp AND combine
1311     result of `tinymail-:from-table-prefix' with `tinymail-:from-table-postfix'
1312     match
1313
1314 In normal mail
1315
1316     o   Go to the beginning of body, after headers and search body
1317         for regexp and return STRING from `tinymail-:from-table-postfix'.
1318         `tinymail-:from-table-prefix' IS NOT USED.
1319     o   If not found, search Gcc Gnus header and use it
1320     o   Otherwise use no postfix
1321
1322 Format:
1323
1324     The left hand element can also be FUNCTION, which is called. It must
1325     return STRING like in the cdr element.
1326
1327    '((REGEXP . STRING)
1328      (REGEXP . STRING)
1329      (FUNCTION)
1330      ..)
1331
1332 Example:
1333
1334     From: foo@bar.com (Foo Bar+mail.emacs)
1335     To: somebody@else.com
1336     Subject: Re: Emacs keybindings
1337     Gcc: nnml:mail.reply
1338     --text follows this line--
1339
1340         ...See function global-set-key and frieds in your Emacs.
1341
1342 Suppose we have above example mail in the buffer. The From line contains
1343 string +mail.emacs added inside the comment (), because word 'emacs' were
1344 found from the body of text according to the following varible contents:
1345
1346   (setq tinymail-:from-table-postfix
1347     '(
1348       ;;  Restrictive regexp first. These are searched from body
1349       ;;  in normal mail
1350
1351       (\"[a-z]+\\\\.el\\\\>\\\\|(def\\\\|setq\"   . \"mail.emacs\")
1352       (\"\\.pl\\\\>\"                        . \"mail.perl\")
1353
1354       (\"games\"                . \"mail.games\")
1355       (\"emacs\"                . \"mail.emacs\")
1356       (\"perl\"                 . \"mail.perl\")))"
1357   :type '(repeat (cons
1358                   (choice regexp function)
1359                   string))
1360   :group 'TinyMail)
1361
1362 ;;}}}
1363 ;;{{{ setup: Reportmail
1364
1365 (defcustom tinymail-:report-window-system (ti::compat-window-system)
1366   "*If non-nil; then never try to use X dragbar to announce mail.
1367 Display the mail message in echo area instead."
1368   :type  'boolean
1369   :group 'TinyMail)
1370
1371 (defconst tinymail-:report-asychronous-timeout 3
1372   "If non-nil, SECONDS to wait for `tinymail-:report-mail-notify-program' finish.
1373 If you are in a system where mailbox is over NFS and there are lot of
1374 periodic NFS mount or access problems (automount failure, hardware
1375 problem or whatever); then set this variable to number of seconds to timeout
1376 `tinymail-:report-mail-notify-program'.
1377
1378 If you don't set the timeout in NFS problematic environment, then the
1379 call to repor tmail is blocked until answer has been received. This may freeze
1380 your whole Emacs for several minutes.")
1381
1382 (defcustom tinymail-:display-time t
1383   "*If non-nil, display the current time, load, and mail flag."
1384   :type  'boolean
1385   :group 'TinyMail)
1386
1387 (defvar tinymail-:report-spool-buffer "*tinymail-mail-spool*"
1388   "*Buffer where to write mail spool information.
1389 If this value is initially set to nil, no mail reporting is done.
1390 See `tinymail-:report-spool-buffer-control'.")
1391
1392 (defcustom tinymail-:report-spool-buffer-control 'keep
1393   "*How to treat the `tinymail-:report-spool-buffer'.
1394 Accepted values are:
1395
1396   'kill     Query mail spool and kill the buffer
1397   'keep     Query mail spool but do not kill after query
1398   'raise    If there is mail and mail count has changed since the last
1399             query; show the buffer in current working frame."
1400   :type '(choice
1401           (const kill)
1402           (const keep)
1403           (const raise))
1404
1405   :group 'TinyMail)
1406
1407 (eval-and-compile
1408   (defun tinymail-default-report-mail-command ()
1409     "Construct default report mail shell call."
1410     (let ((mail (getenv "MAIL"))
1411           cmd)
1412       (setq
1413        cmd
1414        (or (and (file-exists-p "~/.procmailrc") ;; [1]
1415                 (message "\
1416 TinyMail: [WARNING] autosetup aborted. $HOME/.procmailrc found. Please set
1417 manually `tinymail-:report-mail-notify-program' to cover incoming mail
1418 spool folders.")
1419                 'procmail-error)
1420            (executable-find "from")     ;; [2a]
1421            (executable-find "mailfrom") ;; [2b]
1422            (and mail                    ;; [3]
1423                 (or (file-exists-p mail)
1424                     ;;  In pristine system, user may not have received
1425                     ;;  mail yet, but if the leading directory is there,
1426                     ;;  then it's good enough
1427                     ;;
1428                     ;;  /var/spool/mail/LOGIN => /var/spool/mail/
1429                     ;;
1430                     (file-directory-p
1431                      (file-name-directory mail))
1432                     (message "TinyMail: [ERROR] Environment variable MAIL is invalid: %s "
1433                              mail))
1434                 (executable-find "grep")
1435                 (format "%s \"^From \"  %s"
1436                         (executable-find "grep")
1437                         mail))
1438            ;;  Okay, we give up. This is the fall-through case
1439            (let ((function (if (ti::win32-p)
1440                                'message
1441                              'error)))
1442              (funcall function "\                                   ;; [4]
1443 TinyMail: [WARNING] Can't guess `tinymail-:report-mail-notify-program'. Set manually.")
1444              nil)))
1445       (when cmd
1446         (cond
1447          ((and (ti::win32-p)
1448                (stringp cmd)
1449                (string-match "\\<bin\\>" (or shell-file-name "")))
1450           ;; This system is using Cygwin bash
1451           (ti::file-name-forward-slashes-cygwin cmd))
1452          ((and (stringp cmd)
1453                (ti::emacs-type-unix-like-p)) ;Unix, return as is
1454           cmd)
1455          ((stringp cmd)
1456           (ti::file-name-backward-slashes cmd)))))))
1457
1458 (defcustom tinymail-:report-mail-notify-program
1459   (let ((cmd (tinymail-default-report-mail-command)))
1460     (when (stringp cmd)
1461       cmd))
1462   "*A shell call to return entries in the mail spool(s).
1463 Set to nil if tou have lo local mail folders to scan.
1464
1465 Warning:
1466
1467    If you're mixing Cygwin32 and DOS shell buffers in your Emacs,
1468    you MUST SET THIS variable and not rely on the automatic detection
1469    of Cygwin, which is determined by examining `shell-file-name'.
1470
1471    The call must reflect you `shell-file-name', where paths must be
1472    Unix or Win32 styled accordingly.
1473
1474 Program must return entries in following format, which is the Berkeley mailbox
1475 format or commonly known as Unix MBOX format:
1476
1477    From login@site.xx Mon Feb 26 14:41:50 EET 1996
1478
1479 See if you can use from(1), mailfrom(1) or equivalent: \"man -k from\".
1480 Make sure the binary is on your path, possibly located at /usr/ucb/ or
1481 /usr/bin/. If you use absolute path, this program executes faster.
1482
1483 The `tinymail-:report-mail-notify-program' value can be:
1484
1485 STRING  A shell program is called to return the lines
1486 SYMBOL  an Emacs Lisp function is called to return the lines. Lisp function
1487         must return list of string or nil. There is default function
1488         `tinymail-report-mail-info-spool' which searches all messages in
1489         `tinymail-:report-mail-spool-dir'"
1490   :type '(choice
1491           (string   :tag "Shell program")
1492           (function :tag "Lisp function"))
1493   :group 'TinyMail)
1494
1495 (defcustom tinymail-:report-mail-kill-line-regexp
1496   (concat
1497    "Command.*finished"
1498    "\\|no mail"
1499    "\\|can't open")
1500   "Kill lines matching this regexp from report mail buffer.
1501 When `tinymail-:report-mail-notify-program' has finished printing the addresses,
1502 it may print some garbage into the buffer like: 'command finished'
1503 'No mail'. With this regexp you can kill these unwanted lines, otherwise
1504 the line count would have been equal to the pending mail count.
1505 Below the actual count is (1) and the message should display the
1506 last message, not the 'Command finished'.
1507
1508    From login@site2.xx Mon Feb 26 14:41:50 EET 1996
1509    From login@site1.xx Mon Feb 26 14:41:50 EET 1996
1510    Command finished"
1511   :type  'string
1512   :group 'TinyMail)
1513
1514 (defcustom tinymail-:report-keep-intact-list
1515   '("VM")
1516   "*A list of frame names not to change."
1517   :type  '(repeat string)
1518   :group 'TinyMail)
1519
1520 (defcustom tinymail-:report-no-mail-string
1521   (if tinymail-:report-window-system
1522       " ----"
1523     ;;  This is better for echo area in non-window emacs
1524     "-- No Mail --")
1525   "*String to be printed to dragbar when no mail is pending.
1526 If this string is nil, then nothing is displayed in
1527 the echo area if Emacs is running in non-windowed envinronment."
1528   :type  'string
1529   :group 'TinyMail)
1530
1531 (defcustom tinymail-:report-format-string
1532   '(concat
1533     tinymail-:report-old-frame-string
1534     " "
1535     ;;  Use (display-time) in you ~/.Emacs to define display-time-string
1536     (if (and (boundp 'display-time-string)      ;may not exist ?
1537              (stringp display-time-string))     ;XEmacs has vector
1538         display-time-string
1539       "")
1540     tinymail-:report-mail-info-string)
1541   "*Customize your display string layout here."
1542   :type  'sexp
1543   :group 'TinyMail)
1544
1545 (defcustom tinymail-:report-mail-info-shorten-regexp nil
1546   "*Regexp to match local site address.
1547 When you're in local host and receive mail internally, you
1548 propably want to display user's account name only instead of full
1549 email name. This is REGEXP that is tried upon arrived email address,
1550 if it matches, the email address is truncated to account name."
1551   :type  '(string :tag "Regexp")
1552   :group 'TinyMail)
1553
1554 ;;}}}
1555 ;;{{{ Setup: private
1556
1557 (defvar tinymail-:report-old-frame-string     nil
1558   "Private.")
1559
1560 (defvar tinymail-:report-old-mail-info-string nil
1561   "Private.")
1562
1563 (defvar tinymail-:report-timer-object nil
1564   "Private. When package is activated this hold the timer object ativated.")
1565
1566 (defvar tinymail-:report-mail-info-string nil
1567   "Private. Mail message information string.
1568 This variable has one leading and trailingspace around the message.")
1569
1570 (defvar tinymail-:timer-elt nil
1571   "Timer element is stored here.")
1572
1573 (defvar tinymail-:y-or-n-p  nil
1574   "Andwered key from `tinymail-y-or-n-p'")
1575
1576 (defvar tinymail-:tm-mode-name ""
1577   "TM MIME message split indicator.")
1578
1579 (defvar tinymail-:message-type nil
1580   "Private flag. The initial message type in mail buffer.
1581 When tinymail is first turned on, it checks if the message
1582 is composed with \\[mail] or if you have replied to someone
1583 else's message with 'r' from some mail mode. This initial
1584 message type determines how \\[tinymail-mail] call behaves in the buffer.")
1585
1586 (put 'tinymail-:message-type 'permanen-local t)
1587 (make-variable-buffer-local 'tinymail-:message-type)
1588 (setq-default tinymail-:message-type nil)
1589
1590 (defvar tinymail-:last-to-field nil
1591   "Private. Last to: field value.")
1592 (make-variable-buffer-local 'tinymail-:last-to-field)
1593
1594 (defvar tinymail-:mail-aliases-alist nil
1595   "Private. Cached aliases.
1596 Run function `tinymail-update-mail-abbrevs' if you change your
1597 ~/.mailrc so that this variable gets updated.
1598
1599 Format: ((\"ALIAS\" . \"EXPANDED\") ..)")
1600
1601 (defvar tinymail-:temp-buffer " *tinymail-tmp*"
1602   "Temporary buffer.")
1603
1604 (defvar tinymail-:password-alist nil
1605   "Private. Password file in assoc form: '((LOGNAME . PASSWD-ENTRY)).")
1606
1607 (defvar tinymail-:password-completion-alist nil
1608   "Private. Completion table of login names.")
1609
1610 (defvar tinymail-:user-mail-address nil
1611   "This is made local to mail buffer.
1612 Only ised if `tinymail-from-anti-ube-maybe' is in effect.")
1613
1614 ;;}}}
1615 ;;{{{ setup: private
1616
1617 (defvar tinymail-:complete-key-return-value  nil
1618   "Value set to non-nil in `tinymail-:table-header-complete' EVAL-FORM.")
1619
1620 ;;}}}
1621 ;;{{{ setup: version
1622
1623 ;;;###autoload (autoload 'tinymail-version "tinymail" "Display commentary." t)
1624
1625 (eval-and-compile
1626   (ti::macrof-version-bug-report
1627    "tinymail.el"
1628    "tinymail"
1629    tinymail-:version-id
1630    "$Id: tinymail.el,v 2.88 2007/08/03 20:16:25 jaalto Exp $"
1631    '(tinymail-:version-id
1632      timer-idle-list
1633      timer-list
1634      itimer-list
1635      write-file-hooks
1636      message-mode-hook
1637      message-setup-hook
1638      message-header-setup-hook
1639      mail-mode-hook
1640      mail-setup-hook
1641      mail-send-hook
1642      mh-letter-mode-hook
1643      tinytab-:tab-insert-hook
1644      ;; This list is automatically generated by tinylisp-mode "$ v"
1645      tinymail-:load-hook
1646      tinymail-:process-hook
1647      tinymail-:feature-hook
1648      tinymail-:complete-key-hook
1649      tinymail-:complete-body-hook
1650      tinymail-:send-mail-hook-list
1651      tinymail-:citation-message-id-function
1652      tinymail-:y-or-n-p
1653      tinymail-:tm-mode-name
1654      tinymail-:message-type
1655      tinymail-:last-to-field
1656      tinymail-:mail-aliases-alist
1657      tinymail-:temp-buffer
1658      tinymail-:password-alist
1659      tinymail-:password-completion-alist
1660      tinymail-:table-keymap-list
1661      tinymail-:enter-mail-hook-list
1662      tinymail-:dead-mail-file
1663      tinymail-:confirm-mailrc-regexp
1664      tinymail-:cc-kill-regexp
1665      tinymail-:password-mode
1666      tinymail-:password-file
1667      tinymail-:password-cat-cmd
1668      tinymail-:complete-bbdb-fuzzy-method
1669      tinymail-:complete-bbdb-case-fold-search
1670      tinymail-:complete-mode
1671      tinymail-:table-fcc
1672      tinymail-:table-gcc
1673      tinymail-:table-header-complete
1674      tinymail-:from-field-plus-separator
1675      tinymail-:from-field-enable-flag
1676      tinymail-:from-anti-ube-regexp
1677      tinymail-:from-info-function
1678      tinymail-:from-table-prefix
1679      tinymail-:from-table-postfix
1680      tinymail-:report-window-system
1681      tinymail-:display-time
1682      tinymail-:report-spool-buffer
1683      tinymail-:report-spool-buffer-control
1684      tinymail-:report-mail-notify-program
1685      tinymail-:report-mail-kill-line-regexp
1686      tinymail-:report-keep-intact-list
1687      tinymail-:report-no-mail-string
1688      tinymail-:report-format-string
1689      tinymail-:report-mail-info-shorten-regexp
1690      tinymail-:report-old-frame-string
1691      tinymail-:report-old-mail-info-string
1692      tinymail-:report-timer-object
1693      tinymail-:report-mail-info-string
1694      tinymail-:complete-key-return-value)
1695    '(tinymail-:debug-buffer)))
1696
1697 ;;;### (autoload 'tinymail-debug-toggle "tinymail" "" t)
1698 ;;;### (autoload 'tinymail-debug-show   "tinymail" "" t)
1699
1700 (eval-and-compile (ti::macrof-debug-standard "tinymail" "-:"))
1701
1702 ;;}}}
1703 ;;{{{ code: install
1704
1705 ;;;###autoload (autoload 'tinymail-mode          "tinymail" "" t)
1706 ;;;###autoload (autoload 'turn-on-tinymail-mode  "tinymail" "" t)
1707 ;;;###autoload (autoload 'turn-off-tinymail-mode "tinymail" "" t)
1708 ;;;###autoload (autoload 'tinymail-commentary    "tinymail" "" t)
1709
1710 (eval-and-compile
1711   (ti::macrof-minor-mode-wizard
1712    "tinymail-" " tm" "\C-ct" "tm" 'TinyMail "tinymail-:" ;1-6
1713
1714    "Mail enchancements.
1715 For Documentation, run \\[tinymail-version]
1716
1717 Defined keys:
1718
1719 Prefix key to access the minor mode is defined in `tinymail-:mode-prefix-key'
1720
1721 \\{tinymail-:mode-prefix-map}"
1722
1723    "Tinymail"
1724    (progn
1725      (cond
1726       (tinymail-mode
1727        (if buffer-read-only
1728            (error "TinyMail: Buffer is read-only, cannot turn on mode")
1729          (tinymail-mail)))
1730       (t
1731        (tinymail-mail 'disable))))
1732    "Mail enchancement mode"
1733    (list
1734     tinymail-:mode-easymenu-name
1735     ["TO field tracking on/off" tinymail-on-off-toggle            t]
1736     ["Complete by guessing"     tinymail-complete-guess           t]
1737     ["Complete in body"         tinymail-complete-guess-in-body   t]
1738     ["Complete passwords mode"  tinymail-complete-password-mode   t]
1739     ["Abbrev expand at point"   expand-abbrev                     t]
1740     ["Abbrev rebuild (.mailrc)" tinymail-update-mail-abbrevs      t]
1741     ["Deactivate and set address"
1742      tinymail-deactivate-and-send-to-you                  t]
1743     ["Toggle Gnus plugged state"
1744      tinymail-gnus-agent-toggle-plugged                   t]
1745     "----"
1746     ["Debug toggle"           tinymail-debug-toggle       t]
1747     ["Debug show"             tinymail-debug-show         t]
1748     "----"
1749     ;; ["Keyboard menu"          tinymail-menu-main       t]
1750     ["Package version"        tinymail-version            t]
1751     ["Package commentary"     tinymail-commentary         t]
1752     ["Mode help"              tinymail-mode-help          t]
1753     ["Mode off (exit)"        turn-off-tinymail-mode      t])
1754    (progn
1755      (define-key root-map "\t"  'tinymail-complete-key-interactive)
1756 ;;;    (define-key root-map " "   'tinymail-expand-abbrev)
1757      (define-key map  "dd" 'tinymail-debug-toggle)
1758      (define-key map  "ds" 'tinymail-debug-show)
1759      (define-key map  "j"  'tinymail-gnus-agent-toggle-plugged)
1760      (define-key map  "p"  'tinymail-complete-password-mode)
1761      (define-key map  "u"  'tinymail-update-mail-abbrevs)
1762      (define-key map  "t"  'tinymail-on-off-toggle)
1763      (define-key map  "\t" 'tinymail-complete-guess-in-body)
1764      (define-key map  "x"  'turn-off-tinymail-mode)
1765      (define-key map  "?"  'tinymail-mode-help)
1766      (define-key map  "Hm" 'tinymail-mode-help)
1767      (define-key map  "Hc" 'tinymail-commentary)
1768      (define-key map  "Hv" 'tinymail-version))))
1769
1770 ;;; ----------------------------------------------------------------------
1771 ;;;
1772 (defun tinymail-modeline-update (&rest plugged-status)
1773   "Udate `tinymail-:mode-name' to show ! in plugged state."
1774   (let* ((status (if (not (zerop (length plugged-status)))
1775                      (car plugged-status)
1776                    (ti::mail-plugged-p))))
1777     (if status
1778         (unless (string-match "!" tinymail-:mode-name)
1779           (setq tinymail-:mode-name (concat tinymail-:mode-name "!")))
1780       (when (string-match "^\\([^!]+\\)!" tinymail-:mode-name )
1781         (setq tinymail-:mode-name (match-string 1 tinymail-:mode-name ))))))
1782
1783 ;;; ----------------------------------------------------------------------
1784 ;;;
1785 (defun tinymail-gnus-agent-toggle-plugged (&optional mode)
1786   "Toggle Gnus plugged state if Gnus has been loaded."
1787   (interactive  "P")
1788   (if (not (fboundp 'gnus-agent-toggle-plugged))
1789       (message "Can't change plugged staus. Gnus Agent is not loaded.")
1790     (let ((status (ti::mail-plugged-p)))
1791       (ti::bool-toggle status mode)
1792       (if status
1793           (ti::funcall 'gnus-agent-toggle-plugged t)
1794         (ti::funcall 'gnus-agent-toggle-plugged nil))
1795       (tinymail-modeline-update (ti::mail-plugged-p)))))
1796
1797 ;;; ----------------------------------------------------------------------
1798 ;;; #todo: is this really needed
1799 ;;;
1800 (defun tinymail-expand-abbrev (&optional arg)
1801   "Call `abbrev-expand' if cursor is inside header or `self-insert-command'.
1802 If Prefix argument is given, call `self-insert-command' with ARG.
1803 This function should be bound to SPACE key."
1804   (interactive "P")
1805   (if (or (not (null arg))
1806           (not (and (< (point) (ti::mail-hmax))
1807                     (fboundp 'expand-abbrev)
1808                     (expand-abbrev))))
1809       (self-insert-command (prefix-numeric-value arg))))
1810
1811 ;;; ----------------------------------------------------------------------
1812 ;;;
1813 (defun tinymail-install-hooks (&optional remove verb)
1814   "Install needed hooks, optionally REMOVE. VERB."
1815   (let* ((list '(
1816                  ;; tinymail-complete-everything
1817                  ;; tinymail-complete-bbdb-fuzzy ;honor BBDB first
1818                  ;;  tinymail-complete-bbdb <NO GOOD> because displays
1819                  ;;  BBDB record.
1820                  tinymail-complete-simple
1821                  tinymail-complete-guess-in-headers ; then passwd
1822                  ;;  tinymail-complete-bbdb <NO GOOD> because displays
1823                  ;;  BBDB record. fuzzy is better
1824                  ;; tinymail-complete-bbdb-fuzzy
1825                  tinymail-complete-headers-nothing-found)))
1826     ;; tinymail-complete-guest-packages
1827     ;; tinymail-complete-abbrevs
1828     ;; tinymail-complete-headers-move-to-next-field
1829     (ti::add-hooks 'tinymail-:process-hook
1830                    '(tinymail-modeline-update)
1831                    remove)
1832     (ti::add-hooks 'kill-emacs-hook 'tinymail-save-dead-mail-maybe remove)
1833     ;; Install the default functions only if this hook is initially nil
1834     (cond
1835      (remove
1836       (ti::add-hooks 'tinymail-:complete-key-hook list 'remove))
1837      ((null tinymail-:complete-key-hook)
1838       ;;  The TAB key handler. First remove the hooks, and then add, so that
1839       ;;  they will be in this order. The order is _very_ important.
1840       (ti::add-hooks 'tinymail-:complete-key-hook list 'remove)
1841       ;;  The first function that runs must be at the end of list
1842       (ti::add-hooks 'tinymail-:complete-key-hook (reverse list))))
1843     ;;  List of hooks where to install us
1844     (ti::add-hooks tinymail-:enter-mail-hook-list
1845                    'turn-on-tinymail-mode  remove)
1846     (ti::add-hooks 'write-file-hooks
1847                    'tinymail-write-file-hook remove)
1848     (ti::add-hooks 'tinymail-:mode-define-keys-hook
1849                    'tinymail-mode-define-keys remove)
1850     ;;  If user has allowed message-mode to run tinymail, then also install
1851     ;;  this function, which prevents Sender field genearation. (We generate
1852     ;;  the From field).
1853     (when (memq 'message-header-setup-hook  tinymail-:enter-mail-hook-list)
1854       (ti::add-hooks 'message-mode-hook
1855                      'tinymail-message-disable-sender remove))
1856     (when verb
1857       (if remove
1858           (message "TinyMail: hooks removed.")
1859         (message "TinyMail: hooks installed")))))
1860
1861 ;;; ----------------------------------------------------------------------
1862 ;;;
1863 (defun tinymail-install-table-header-complete-gnus ()
1864   "Add Gnus Followup-To, Gcc, Newsgroups to `tinymail-:table-header-complete'."
1865   ;;  Debian bug report header
1866   ;;  http://www.debian.org/Bugs/Reporting
1867   ;;  See http://www.debian.org/Bugs/Developer#severities
1868   (aput 'tinymail-:table-header-complete
1869         "Severity"
1870         (list
1871          '("critical"    ;; Whole system break, serious data loss
1872            "grave"       ;; unuseable, data loss, security hole
1873            "serious" ;; violation of Debian policy, unsuitable for release.
1874            "important" ;; major effect withour  completely unusable.
1875            "normal"    ;; the default value, applicable to most bugs.
1876            "minor"     ;; doesn't affect the package's usefulness
1877            "wishlist"  ;; feature request
1878            "fixed")))  ;; fixed but should not yet be closed.
1879   ;;  Debian bug report header
1880   (aput 'tinymail-:table-header-complete
1881         "Tags"
1882         (list
1883          '("patch"       ;;
1884            "wontfix"     ;;  change will cause other, worse, problems
1885            "moreinfo" ;;  more information must be provided by the submitter
1886            "unreproducible" ;; can't be reproduced on the maintainer's system
1887            "fixed" ;;  bug is fixed or worked around, needs to be resolved
1888            "security"                ;:  security problem in a package
1889            "potato"                  ;;  potato release
1890            "woody"                   ;;  woody distribution
1891            "s1id"))) ;;  architecture that is currently unreleased
1892   (aput 'tinymail-:table-header-complete
1893         "Followup-To"
1894         (list
1895          '(when (eq major-mode 'message-mode)
1896             (call-interactively 'message-tab)
1897             ;;   We must stop the other completion function from running
1898             (setq tinymail-:complete-key-return-value t)
1899             nil)))
1900   (aput 'tinymail-:table-header-complete
1901         "Gcc"
1902         (list
1903          '(if (not (featurep 'gnus))
1904               (prog1 nil (message "TinyMail: Gcc completion needs Gnus..."))
1905             (when (stringp string))
1906             (all-completions
1907              string
1908              gnus-active-hashtb 'gnus-valid-move-group-p))))
1909   (aput 'tinymail-:table-header-complete
1910         "Newsgroups"
1911         (list
1912          '(if (not (featurep 'gnus))
1913               (prog1 nil
1914                 (message "TinyMail: Newsgroups completion needs Gnus..."))
1915             (when (stringp string))
1916             (all-completions
1917              string
1918              gnus-active-hashtb
1919              (gnus-read-active-file-p))))))
1920
1921 ;;; ----------------------------------------------------------------------
1922 ;;;
1923 (defun tinymail-read-version (sym)
1924   "Read version number from variable SYM if variable exists.
1925 Otherwise return ''."
1926   (or (ti::string-match "[0-9][0-9.]+" 0
1927                         (if (boundp sym) (symbol-value sym) ""))
1928       ""))
1929
1930 ;;; ----------------------------------------------------------------------
1931 ;;; - This is the main controller "install" that calls all other
1932 ;;;   functions.
1933 ;;;
1934 (defun tinymail-install-to-buffers (&optional remove verb)
1935   "Activate or REMOVE tinyamil from mail buffers."
1936   (dolist (buffer (buffer-list)) ;;  Activate in current Emacs
1937     (with-current-buffer buffer
1938       (when (and (memq major-mode '(message-mode mail-mode))
1939                  ;; #todo:  vm send mode?
1940                  (null buffer-read-only))
1941         (message "TinyMail: mode %s in buffer %s"
1942                  (if remove "deactivated" "activated")
1943                  (buffer-name))
1944         (tinymail-mode (if remove -1 1) )))))
1945
1946 ;;; ----------------------------------------------------------------------
1947 ;;; - This is the main controller "install" that calls all other
1948 ;;;   functions.
1949 ;;;
1950 (defun tinymail-install (&optional remove)
1951   "Install or REMOVE package."
1952   (interactive "P")
1953   (let ((idle-p (ti::idle-timer-supported-p)))
1954     (if (null idle-p)
1955         (message "\
1956 TinyMail: This Emacs does not support idle timers. Using regular timers."))
1957     (ti::compat-timer-cancel-function 'tinymail-process)
1958     (tinymail-report-mail-install-maybe remove)
1959     (tinymail-install-hooks remove)
1960     ;;  If the idle timer is available, use it. Otherwise we would have
1961     ;;  no other option but occupy post command hook
1962     (unless remove
1963 ;;; 2007-05-18 disabled. FIXME: needed? Too much CPU?
1964 ;;;      (if idle-p
1965 ;;;          (setq tinymail-:timer-elt
1966 ;;;                (ti::funcall
1967 ;;;                 'run-with-idle-timer
1968 ;;;                 tinymail-:idle-timer-seconds
1969 ;;;                 t
1970 ;;;                 'tinymail-process))
1971 ;;;        ;;  Can't respect tinymail-:idle-timer-seconds,
1972 ;;;        ;;  so use 20 seconds repeat time.
1973 ;;;        (setq tinymail-:timer-elt
1974 ;;;              (run-at-time "20 sec" 20 'tinymail-process)))
1975       (when (ti::nil-p (user-full-name))
1976         (message
1977          (concat
1978           "TinyMail: [ERROR] please set variable `user-full-name'."
1979           "Was [%s]")
1980          (prin1-to-string (user-full-name))))
1981       (tinymail-install-table-header-complete-gnus)
1982       (tinymail-install-to-buffers)
1983       (message
1984        "TinyMail: Installed. Read documentation with M-x tinymail-version"))))
1985
1986 ;;; ----------------------------------------------------------------------
1987 ;;;
1988 (defun tinymail-install-citation ()
1989   "Install First line citation function for Mail user agents."
1990   (setq message-citation-line-function
1991         'tinymail-message-citation-line-function))
1992
1993 ;;}}}
1994 ;;{{{ code: misc
1995
1996 ;;; ----------------------------------------------------------------------
1997 ;;;
1998 (defsubst tinymail-y-or-n-p-abort-p ()
1999   "Check if `tinymail-y-or-n-p' was abort."
2000   (ti::char-in-list-case tinymail-:y-or-n-p '(?q ?Q)))
2001
2002 ;;; ------------------------------------------------------------ &misc ---
2003 ;;;
2004 (defsubst tinymail-field-off-p (header-name &optional header-value)
2005   "Check status of HEADER-NAME field which has optional HEADER-VALUE.
2006 If there is 2 or more leading spaces, then the field is considered 'off'."
2007   (when (and (stringp header-name)
2008              (stringp header-value))
2009     (not (memq (ti::mail-field-space-count header-name header-value)
2010                '(0 1)))))
2011
2012 ;;; ----------------------------------------------------------------------
2013 ;;;
2014 (defmacro tinymail-mail-aliases ()
2015   "Return `tinymail-:mail-aliases-alist' or build it if it is empty."
2016   (` (or tinymail-:mail-aliases-alist
2017          (tinymail-update-mail-abbrevs))))
2018
2019 ;;; ----------------------------------------------------------------------
2020 ;;;
2021 (defun tinymail-make-local-hook-available-p ()
2022   "Check if `make-local-hook' is really available."
2023   (or (ti::xemacs-p)                    ;Always in XEmacs
2024       (and (ti::emacs-p)                ;19.30 and up
2025            (not (string-match "19.2[0-9]" (emacs-version))))))
2026
2027 ;;; ----------------------------------------------------------------------
2028 ;;;
2029 (defun tinymail-y-or-n-p (message)
2030   "Ask confirmation for MESSAGE. Accept TAB as yes.
2031 Setq global variable `tinymail-y-or-n-p' to the result."
2032   (setq tinymail-:y-or-n-p
2033         (ti::read-char-safe-until
2034          (concat message  " (tab/n or q)")
2035          '(?\t
2036            ?\ ?y ?Y ?n
2037            ;;  NO keys
2038            ?\b ?\177 ?\C-h ?\127
2039            ?N ?q ?Q
2040            ?\e ?\n ?\r
2041            ;;  These keys are usually above the TAB key, so you can answer
2042            ;;  NO with your left hand.
2043            ?\247
2044            ?\`
2045            ?\~)))
2046   ;;  YES answers.
2047   (ti::char-in-list-case tinymail-:y-or-n-p '(?y ?Y ?\ ?\t ?\n ?\r)))
2048
2049 ;;; ----------------------------------------------------------------------
2050 ;;;
2051 (defun tinymail-write-file-hook ()
2052   "Rebuild aliases everytime .mailrc is saved."
2053   (tinymail-debug "tinymail-write-file-hook" "MODE" major-mode (buffer-name))
2054   (when (string-match "\\.mailrc" (or (buffer-file-name) ""))
2055     (message "TinyMail: Updating mail aliases and abbrevs...")
2056     (build-mail-aliases)
2057     (when (fboundp 'build-mail-abbrevs) ;update abbrevs too
2058       (ti::funcall 'build-mail-abbrevs))
2059     (tinymail-update-mail-abbrevs 'force)
2060     (message "TinyMail: Updating mail aliases and abbrevs...done")
2061     ;;  Hook return value
2062     nil))
2063
2064 ;;; ----------------------------------------------------------------------
2065 ;;;
2066 (defun tinymail-update-mail-abbrevs (&optional force)
2067   "Build up all aliases to `tinymail-:mail-aliases-alist' cache and return it.
2068 Optional FORCE builds the list in any case.
2069 You need to run this function if you change your ~/.mailrc."
2070   (interactive)
2071   (tinymail-debug "tinymail-update-mail-abbrevs")
2072   (when (and (fboundp 'build-mail-abbrevs) ;update abbrevs too
2073              (or force (interactive-p)))
2074     (ti::funcall 'build-mail-abbrevs))
2075   (setq tinymail-:mail-aliases-alist (ti::mail-abbrev-get-alist)))
2076
2077 ;;; ----------------------------------------------------------------------
2078 ;;;
2079 ;;;###autoload
2080 (defun tinymail-deactivate-and-send-to-you ()
2081   "Deactivate TinyMail and change To field to point to your address.
2082 This function is normally used when you use mailing lists. See
2083 documentation in the tinymail.el or call \\[tinymail-version]."
2084   (interactive)
2085   (tinymail-debug "tinymail-deactivate-and-send-to-you")
2086   (if (ti::nil-p user-mail-address)
2087       (error "TinyMail: Please set variable `user-mail-address'")
2088     (ti::mail-kill-field  "^to:" user-mail-address)
2089     (tinymail-field-to-off)
2090     (if (interactive-p)
2091         (message "Address changed to point to you. TinyMail signs off."))))
2092
2093 ;;; ----------------------------------------------------------------------
2094 ;;;
2095 (defun tinymail-buffer-email-address-scramble-area ()
2096   "Return are of eamil that can be scrambled.
2097 Exclude patches and attachments."
2098   (let ((list
2099          (list
2100           "^RCS[ \t]+file:.*,v\\|^diff[ \t]+-[^- \t\r\n]"
2101           "[<]#part" ;; Gnus attachment
2102           "\\[Attachment:"))
2103         (point-list (list (point-max)))
2104         beg
2105         end)
2106     (save-excursion
2107       (ti::pmin)
2108       (when (search-forward (or mail-header-separator
2109                                 "---NOTHING__TO_FIND")
2110                             nil t)
2111         (setq beg (1+ (line-end-position)))
2112         (dolist (re list)
2113           (when (re-search-forward re nil t)
2114             (push (line-beginning-position) point-list)))
2115         (setq end (apply 'min point-list))))
2116     (when beg
2117       (list beg end))))
2118
2119 ;;; ----------------------------------------------------------------------
2120 ;;;
2121 (defun tinymail-buffer-email-address-scramble-1 ()
2122   "Spam protect email address words.
2123 Scramble Email addresses do that spammers cannot use them.
2124 The end position is before text that looks like a patch or `point-max'"
2125   (multiple-value-bind (beg end)
2126       (tinymail-buffer-email-address-scramble-area)
2127     (when beg
2128       (save-excursion
2129         (goto-char beg)
2130         (let ( ;;  If there is patch in this buffer, limit changes before it.
2131               (regexp
2132                (concat
2133                 ;; Must be separated by space or "<".
2134                 ;; this email@example.com or <email@example.com>
2135                 ;; But not http://user@site.com/
2136                 "\\(^\\|[ \t]\\)"
2137                 "\\([^ /\t\r\r]+\\)@\\([^ /\t\r\r]+\\.[^ /\t\r\r]+\\)"
2138                 "\\(^\\|[ \t]\\)")))
2139           (while (re-search-forward regexp end t)
2140             (replace-match "\\1\\2 AT \\3\\4")))))))
2141
2142 ;;; ----------------------------------------------------------------------
2143 ;;;
2144 (defun tinymail-buffer-email-address-scramble ()
2145   "If `tinymail-:protect-email-addresses' is non-nil, scrable addresses."
2146   (if tinymail-:protect-email-addresses
2147       (tinymail-buffer-email-address-scramble-1))
2148   ;; Hook function. Return nil
2149   nil)
2150
2151 ;;; ----------------------------------------------------------------------
2152 ;;;
2153 (defun tinymail-active-p ()
2154   "Check if TinyMail is active in current buffer."
2155   tinymail-mode)
2156
2157 ;;; ----------------------------------------------------------------------
2158 ;;;
2159 ;;;###autoload
2160 (defun tinymail-mail (&optional disable verb)
2161   "Prepare mail mode.
2162 Add or changes Cc, FF, X-Sender-Info fields on the fly while you're
2163 composing the message.
2164
2165 Input:
2166
2167   DISABLE       Disables package.
2168   VERB          print verbose message.
2169
2170 References:
2171
2172   `tinymail-:feature-hook'."
2173   (let* ((fid "tinymail-mail")
2174          to-list)
2175     (unless fid ;; No-op. XEmacs byte compiler silencer
2176       (setq fid nil))
2177     (tinymail-debug
2178      fid "in:"
2179      "dis-flag"     disable
2180      "MODE"         major-mode)
2181     (when (featurep 'tinytab)
2182       ;;  - make TinyTab.el work with TinyMail so that they share
2183       ;;    common TAB key.
2184       ;;  - Remove and add make sure the function is at the beginning and
2185       ;;    runs first.
2186       (remove-hook 'tinytab-:tab-insert-hook
2187                    'tinymail-complete-key-interactive)
2188       (add-hook    'tinytab-:tab-insert-hook
2189                    'tinymail-complete-key-interactive))
2190     (unless disable
2191       ;;  If you're replying to someone else's message, the TO field
2192       ;;  must have two spaces to turn off TinyMail so that the remaining
2193       ;;  Cc fields are not modified.
2194       ;;
2195       ;;  R and r keys don't add Cc field, so we put there only one space.
2196       ;;
2197       ;;  For simple C-x m TO field will be initially empty.
2198       (tinymail-debug
2199        fid
2200        "MAIN STATUS (BEFORE)"
2201        "point"      (point)
2202        "msg type"   tinymail-:message-type
2203        "to"         (ti::mail-get-field-1 "to")
2204        "cc"         (ti::mail-get-field-1 "cc")
2205        "Subject"    (ti::mail-get-field-1 "subject")
2206        "MODE"       major-mode
2207        "\n["
2208        (buffer-substring (point-min) (point-max))
2209        "]\n")
2210       (run-hooks 'tinymail-:feature-hook)
2211       (cond
2212        (tinymail-:message-type
2213         ;;  User calls us again
2214         (if (ti::nil-p (ti::mail-get-field-1 "subject"))
2215             (tinymail-field-to-off)))
2216        (t
2217         ;;  We're called from some mail setup hook. See what is the initial
2218         ;;  state of the buffer...
2219         (cond
2220          ((setq to-list (ti::mail-to-list-p))
2221           (setq tinymail-:message-type 'to-list)
2222           (tinymail-field-to-on))
2223          ((ti::nil-p (ti::mail-get-field-1 "subject"))
2224           ;;  simple mail: there is no subject field filled
2225           (setq tinymail-:message-type 'simple))
2226          (t                             ;R or r; No cc field
2227           (setq tinymail-:message-type 'reply)
2228           (tinymail-field-to-on))))))
2229     (tinymail-field-to-move-maybe)
2230     (unless to-list ;; No-op. XEmacs byte compiler silencer
2231       (setq to-list nil))
2232     (tinymail-debug
2233      fid
2234      "MAIN AFTER"
2235      "point"        (point)
2236      "disable"      disable
2237      "msg type"     tinymail-:message-type
2238      "to-list"      to-list
2239      "MODE"         major-mode
2240      "to"           (ti::mail-get-field-1 "to")
2241      "Subject"      (ti::mail-get-field-1 "subject")
2242      "\n["
2243      (buffer-substring (point-min) (point-max))
2244      "]\n")))
2245
2246 ;;; ----------------------------------------------------------------------
2247 ;;;
2248 (defun tinymail-mail-send-to-list ()
2249   "Check if message is being sent to mailing list and Fix CC/To.
2250 This function makes the To to point to mailing list and delete
2251 any CC. Set Gnus group parameter to take use of this feature: (G p
2252 in *Group* buffer):
2253
2254    '(...
2255      (to-list . \"discussion-list@list.com\")
2256      ...)"
2257   (when (eq major-mode 'message-mode)
2258     (let* ((fid     "tinymail-mail-send-to-list:")
2259            (tofield (mail-fetch-field  "To"))
2260            (ccfield (mail-fetch-field  "Cc"))
2261            (to      (ti::mail-to-list-p))
2262            (email   (if to
2263                         (car-safe (ti::mail-email-from-string to)))))
2264       (unless fid ;; No-op. XEmacs byte compiler silencer
2265         (setq fid nil))
2266       (tinymail-debug fid "to" to "email" email)
2267       ;;  If TO is not in the headers; then this is private reply with
2268       ;;  "r". A followup will include TO in To or Ccc field.
2269       (when (and (string-match "^[ \t]*$" (or tofield ""))
2270                  (string-match "^[ \t]*$" (or ccfield ""))
2271                  to
2272                  email
2273                  ;; (save-restriction
2274                  ;;  (message-narrow-to-headers)
2275                  ;;  (not (ti::re-search-check email)))
2276                  (not (tinymail-field-off-p "To" to)))
2277         (tinymail-debug fid "TO-LIST SET, killed To/Cc")
2278         (ti::mail-kill-field "^To" to)
2279         (ti::mail-kill-field "^CC")
2280         t))))
2281
2282 ;;; ----------------------------------------------------------------------
2283 ;;;
2284 (defun tinymail-resolve-abbrevs (list)
2285   "Resolves LIST of mail abbrevs in format '(\"abbrav\" \"abbrev\" ..)
2286
2287 Return:
2288
2289   ((\"alias\" . \"expansion\") (A . E) .. )
2290   alias        = the alias definition
2291   expansion    = expanded alias"
2292   (let* ((abbrevs   (tinymail-mail-aliases))
2293          pre-abbrev-expand-hook ;; prevent recursion
2294          exp-list
2295          hit)
2296     (dolist (elt list)
2297       (tinymail-debug "tinymail-resolve-abbrevs" elt)
2298       ;;  Returns (ABBR . ABBR-EXPANDED)
2299       (if (not (setq hit (assoc elt abbrevs)))
2300           (message "TinyMail: Can't find abbrev '%s', is it in ~/.mailrc ?" elt)
2301         (if (not (member hit exp-list))
2302             (push hit exp-list))))
2303     exp-list))
2304
2305 ;;; ----------------------------------------------------------------------
2306 ;;;
2307 (defun tinymail-password-save (&optional load verb)
2308   "Save passwd completions to file `tinymail-:password-file'. Optionally LOAD.
2309 If that variable is nil, then do nothing. VERB."
2310   (let* ((file  tinymail-:password-file)
2311 ;;;      (list  tinymail-:password-completion-alist)
2312          (list2 tinymail-:password-alist))
2313     (ti::verb)
2314     (tinymail-debug "tinymail-password-save" file load verb)
2315     (when (stringp file)
2316       (if (string-match "\\.gz$\\|\\.Z$" file)
2317           (ti::use-file-compression))
2318       (cond
2319        (load
2320         (if (not (file-exists-p file))  ;Create file then
2321             (tinymail-password-define-variables 'force))
2322         (ti::load-file-with-wrapper file)
2323         (if verb (message "TinyMail: passwd completions loaded.")))
2324        (t
2325         (if (null list2)
2326             (message "\
2327 TinyMail: `tinymail-:password-alist' is empty, nothing to save.
2328 Call `tinymail-password-define-variables' with argument FORCE.")
2329           (ti::write-file-variable-state
2330            file "TinyMail.el password completions"
2331            '(tinymail-:password-completion-alist tinymail-:password-alist))
2332           (if verb
2333               (message "TinyMail: passwd completions saved."))))))))
2334
2335 ;;; ----------------------------------------------------------------------
2336 ;;;
2337 (defun tinymail-password-define-variables (&optional force no-save)
2338   "Defines passwd variables.
2339 Read definitions from  `tinymail-:password-file' if FORCE is nil.
2340
2341 Input:
2342
2343   FORCE     flag, if non-nil, read passwd table and reset all variables.
2344   NO-SAVE   flag, if non-nil, do not save passwd completions to file.
2345
2346 Return:
2347
2348   non-nil   if password completion can be used.
2349
2350 References:
2351
2352   `tinymail-:password-mode'"
2353
2354   (tinymail-debug "tinymail-password-define-variables"
2355                   'force force
2356                   'no-save no-save
2357                   'passwd-file tinymail-:password-file)
2358   (when (and tinymail-:password-mode
2359              tinymail-:password-cat-cmd)
2360     (cond
2361      ;; .................................................... cond-save ...
2362      ((or force
2363           (and tinymail-:password-file
2364                (not (file-exists-p tinymail-:password-file)))
2365           (and (null tinymail-:password-file)
2366                (null tinymail-:password-alist)))
2367       (message "TinyMail: Buildig password completions...")
2368       (setq tinymail-:password-alist
2369             (ti::file-passwd-build-alist tinymail-:password-cat-cmd))
2370       (message "TinyMail: Buildig password completions...done"))
2371      ;; .................................................... cond-load ...
2372      ((and (file-exists-p tinymail-:password-file)
2373            (null tinymail-:password-alist))
2374       (tinymail-password-save 'load)))
2375     ;; ......................................................... build ...
2376     (when (or force
2377               (null tinymail-:password-completion-alist))
2378       (setq tinymail-:password-completion-alist
2379             (mapcar (function
2380                      (lambda (x) (cons (car x) 1)))
2381                     tinymail-:password-alist))
2382       (if (null no-save)
2383           (tinymail-password-save)))
2384     tinymail-:password-completion-alist))
2385
2386 ;;}}}
2387 ;;{{{ Completion
2388
2389 ;;; ----------------------------------------------------------------------
2390 ;;;
2391 (defun tinymail-complete-password-mode (&optional mode)
2392   "Toggle `tinymail-:password-mode'  on or off."
2393   (interactive "P")
2394   (ti::bool-toggle tinymail-:password-mode mode)
2395   (when (interactive-p)
2396     (message "TinyMail: Password complete mode is now %s"
2397              (if tinymail-:password-mode "on" "off"))))
2398
2399 ;;; ----------------------------------------------------------------------
2400 ;;;
2401 (defun tinymail-complete-string-read ()
2402   "Return completion string from current point or nil.
2403 The String must be delimited by comma as in mail header are.
2404
2405 Return:
2406  (beg-marker end-marker string)"
2407   (let* ((fid    "tinymail-complete-string-read")
2408          (point  (point))
2409          (heder-p (ti::mail-point-at-header-p))
2410          string
2411          beg-marker
2412          end-marker)
2413     (unless fid ;; No-op. XEmacs byte compiler silencer
2414       (setq fid nil))
2415     (when (and (not (bolp))             ;Nothing to read
2416                (not (char-equal (char-syntax (preceding-char)) ?\ )))
2417       (ti::narrow-safe (line-beginning-position) (line-end-position)
2418         (goto-char point)
2419         ;;  First, go away from whitespace so that match-end gets
2420         ;;  length in next case statement
2421         (skip-chars-forward " \t")
2422         (cond
2423          ((or (if (not heder-p)
2424                   (skip-chars-backward "^ \t")
2425                 (or (re-search-backward ",[ \t]*" nil t)
2426                     (and (re-search-backward "^[^:]+:" nil t)
2427                          (goto-char (match-end 0)))
2428                     ;; continued line
2429                     ;;
2430                     ;;  Cc: this,
2431                     ;;
2432                     ;;
2433                     ;;
2434                     ;;      here_is_point
2435                     ;;
2436                     (re-search-backward "[:, ][ \t]*"  nil t))))
2437           (skip-chars-forward " ,\t")   ;Goto word
2438 ;;;       (ti::d! 1 (buffer-substring (point) (line-end-position)))
2439           nil)
2440          ((re-search-backward "^[ \t]*" nil t)
2441 ;;;       (ti::d! 2 (buffer-substring (point) (line-end-position)))
2442           (goto-char (1+ (point)))))
2443         (setq beg-marker (point-marker))
2444 ;;;      (ti::d! beg (looking-at "[^\n\t ,:]+") (buffer-substring beg (line-end-position)))
2445         ;;  There must be somthing, not just empty lines
2446         (when (looking-at "[^\n\t ,:]+")
2447           (cond
2448            ((re-search-forward " *,"  nil t)
2449             (setq end-marker (make-marker))
2450             (move-marker end-marker (match-beginning 0)))
2451            ((re-search-forward "[ \t]*,\\|[ \t]*$"  nil t)
2452             (setq end-marker (make-marker))
2453             (move-marker end-marker (match-beginning 0)))))
2454
2455         (if (and beg-marker end-marker)
2456             (setq string (buffer-substring-no-properties
2457                           (marker-position beg-marker)
2458                           (marker-position end-marker)))))
2459       (tinymail-debug fid "RET" string beg-marker end-marker)
2460       (if (null string)
2461           (setq beg-marker nil ;; Kill possible markers
2462                 end-marker nil)
2463         (list
2464          beg-marker
2465          end-marker
2466          string)))))
2467
2468 ;;; ----------------------------------------------------------------------
2469 ;;;
2470 (defun tinymail-complete-list-mail-aliases (&optional mode data)
2471   "Return '(match match ...) from mail aliases.
2472
2473 Input MODE:
2474
2475   The default match is made against all the alias expansion ('string mode).
2476   With 'alias, only the alias names are matched."
2477   (let* ((fid    "tinymail-complete-guess-2-choices: ")
2478          (list   (tinymail-mail-aliases))
2479          (mail   (ti::mail-mail-p))
2480          elt
2481          beg
2482          end
2483          str)
2484     (unless fid ;; No-op. XEmacs byte compiler silencer
2485       (setq fid nil))
2486     (tinymail-debug fid "in" mode 'mail-p mail)
2487     (when mail
2488       ;; ... ... ... ... ... ... ... ... ... ... ... ... ... .. get word ...
2489       (cond
2490        ;; ... ... ... ... ... ... ... ... ... ... ... ...  second mode ..
2491        ((eq mode 'alias)
2492         (save-excursion
2493           (when (< (skip-chars-backward "^ \t\n") 0)
2494             (setq beg (point))
2495             (when (> (skip-chars-forward "^ \n\t") 0)
2496               (setq end (point)))))
2497         (if (and beg end)
2498             (setq str (buffer-substring beg end))
2499           (setq str nil)))
2500        (t
2501         (or data
2502             (setq data   (tinymail-complete-string-read)))
2503         (if data
2504             (setq beg (nth 0 data)
2505                   end (nth 1 data)
2506                   str (regexp-quote (nth 2 data))))))
2507       ;; ... ... ... ... ... ... ... ... ... ... ... ... .. find matches ...
2508       ;;#todo: this code must be rewritten, ti::list-find and `function'
2509       ;;is flow combination.
2510
2511       (when (and (not (ti::nil-p str))
2512                  (setq elt
2513                        (ti::list-find
2514                         list str
2515                         (function
2516                          (lambda (arg elt)
2517                            (if (eq mode 'string)
2518                                (string-match arg (cdr elt))
2519                              (string-match (concat "^" arg)
2520                                            (car elt)))))
2521                         'all-matches)))
2522         (tinymail-debug fid "after type" beg end str)
2523         (mapcar 'cdr elt)))))
2524
2525 ;;; ----------------------------------------------------------------------
2526 ;;;
2527 (defun tinymail-complete-guess-1 (&optional mode verb)
2528   "Try to expand using underlying characters.
2529 Look completion from `mail-aliases'. If there is more than 1 match,
2530 ask which one to use.
2531
2532 If MODE is 'string, then text read from buffer must be separated by
2533
2534     LEFT-COLON:          txt [COMMA,WHITESPACE]
2535     LEFT-ALL-WHITESPACE  txt [COMMA,WHITESPACE]
2536     COMMA                txt [COMMA,WHITESPACE]
2537
2538 If MODE is 'alias then text is read direcly under point separated
2539 by spaces. This function does nothing if the first line doesn't contain
2540
2541     KEYWORD:
2542
2543 Indicating a mail like mode. VERB prints verbose messages.
2544
2545 Return:
2546
2547   t         completed
2548   nil"
2549   (let* ((fid    "tinymail-complete-guess-2: ")
2550          (list   (tinymail-mail-aliases))
2551          (mail   (ti::mail-mail-p))
2552          (check-regexp  tinymail-:confirm-mailrc-regexp)
2553          user-selected-p
2554          data
2555          elt
2556          beg end str
2557          done
2558          ret)
2559     (unless fid ;; No-op. XEmacs byte compiler silencer
2560       (setq fid nil))
2561     (tinymail-debug fid "in" mode verb mail)
2562
2563     (when mail
2564       ;; ... ... ... ... ... ... ... ... ... ... ... ... ... .. get word ...
2565       (cond
2566        ((eq mode 'string)
2567         (setq data   (tinymail-complete-string-read))
2568         (if data
2569             (setq beg (nth 0 data)
2570                   end (nth 1 data)
2571                   str (regexp-quote (nth 2 data)))))
2572        ;; ... ... ... ... ... ... ... ... ... ... ... ...  second mode ..
2573        ((eq mode 'alias)
2574         (save-excursion
2575           (when (< (skip-chars-backward "^ \t\n") 0)
2576             (setq beg (make-marker))
2577             (when (> (skip-chars-forward "^ \n\t") 0)
2578               (setq end (make-marker)))))
2579         (if (and beg end)
2580             (setq str (buffer-substring
2581                        (marker-position beg)
2582                        (marker-position end)))
2583           (setq str nil))))
2584       (tinymail-debug fid "after type" beg end str)
2585       ;; ... ... ... ... ... ... ... ... ... ... ... ... .. find matches ...
2586       (when (and (not (ti::nil-p str))
2587                  (setq elt
2588                        (ti::list-find
2589                         list str
2590                         (function
2591                          (lambda (arg elt)
2592                            (if (eq mode 'string)
2593                                (string-match arg (cdr elt))
2594                              (string-match (concat "^" arg)
2595                                            (car elt)))))
2596                         'all-matches)))
2597         (setq str nil)
2598         (tinymail-debug fid "ELT matches" (length elt) (cdr (car elt)) elt)
2599         ;; ............................................... any matches ...
2600         ;;  How many matches?
2601         (cond
2602          ((eq 1 (length elt))
2603           (setq elt (car elt)))    ; '( (alias . string) ) --> (a . s)
2604          (elt
2605           (let (completion-ignore-case)
2606             (setq str
2607                   (completing-read
2608                    (format "%d Choose: " (length elt))
2609                    (ti::list-to-assoc-menu (mapcar 'cdr elt))
2610                    nil ;; predicate
2611                    (not 'match-it))))
2612
2613           (if (ti::nil-p str)
2614               (setq elt nil)            ;User didn't select anything
2615             (setq  user-selected-p t
2616                    elt (rassoc str elt)
2617                    ret t))))
2618         (tinymail-debug fid "ELT" elt)
2619         ;; .............................................. select match ...
2620         ;;  Now we have a MATCH unless user cancelled the choices
2621         (when elt
2622           (if (eq mode 'string)
2623               (setq str (cdr elt))
2624             (setq str (car elt))))
2625         (tinymail-debug fid "SELECTION" mode str elt)
2626         (unless (ti::nil-p str)
2627           ;;  For some strings, ask confirmation.
2628           ;;  Ie. Give a chance to discard this completions and move on...
2629
2630           (tinymail-debug fid
2631                           "CHECK" check-regexp
2632                           (string-match check-regexp str))
2633           ;;  *) If user already did selected this match from several
2634           ;;     choices, then go ahead
2635           ;;  *) If we found only one match, then confirm that match
2636
2637           (when (or user-selected-p
2638                     (not (stringp check-regexp))
2639                     (or (null (string-match check-regexp str))
2640                         (and (string-match check-regexp str)
2641                              (tinymail-y-or-n-p (concat "TinyMail: " str)))))
2642             (goto-char (marker-position beg))
2643             (delete-region (marker-position beg) (marker-position end))
2644             (setq  beg nil  end nil) ;; Kill markers
2645             (insert str)
2646             (setq done t  ret t)))) ;; when-nil-var
2647       (cond
2648        ((and verb (null done) str)
2649         (message (format "TinyMail: no completion match on '%s'" str)))
2650        ((and verb str done (eq mode 'alias))
2651         (message (cdr (car elt)))))
2652       (tinymail-debug fid "RET" ret)
2653       ret)))
2654
2655 ;;; ----------------------------------------------------------------------
2656 ;;;
2657 (defun tinymail-complete-passwd (&optional force verb)
2658   "Complete names in passwd in header area, otw do nothing.
2659
2660 Input:
2661
2662   FORCE     Complete anyway
2663   VERB      enable verbose messages.
2664
2665 Return:
2666
2667   t     completed
2668   nil   nothing done"
2669   (interactive "P")
2670   (let* ((fid      "tinymail-complete-passwd")
2671          (header-p (< (point) (ti::mail-hmax) ))
2672          ret
2673          table
2674          word
2675          str
2676          completions)
2677     (unless fid ;; No-op. XEmacs byte compiler silencer
2678       (setq fid nil))
2679     (ti::verb)
2680     (save-excursion
2681       (forward-char -1)                 ;move over some char
2682       (setq word (ti::buffer-read-word "[-_+a-zA-Z0-9]" 'strict)))
2683     (when (and (or force header-p)
2684                (not (ti::nil-p word))
2685                (tinymail-password-define-variables))
2686       (setq table       tinymail-:password-completion-alist)
2687       (setq completions (all-completions word table))
2688       (tinymail-debug fid "COMPLETIONS" word completions)
2689       (cond
2690        ((eq 1 (length completions))
2691         (setq str (car completions))
2692         (if (null (tinymail-y-or-n-p (format "Accept Passwd match: %s " str)))
2693             (tinymail-y-or-n-p-abort-p)
2694           ;;  We only insert the missing part to the buffer.
2695           ;;  abcDEF
2696           ;;     * if tab was pressed after abc
2697           (insert  (substring (car completions) (length word)))
2698           (setq ret t)))
2699        ((setq completions (tinymail-password-grep word 'verb))
2700         (tinymail-display-list completions)
2701         ;;  Show the matched entries from passwd table, sometimes
2702         ;;  User doens't want to use them but continue calling other
2703         ;;  functions. Ask what's up.
2704         (setq ret
2705               (not
2706                (tinymail-y-or-n-p
2707                 "TinyMail: Continue calling more completion functions?"))))))
2708     (tinymail-debug fid "RET" ret word)
2709     ret))
2710
2711 ;;; ----------------------------------------------------------------------
2712 ;;;
2713 (defun tinymail-complete-guess (&optional verb)
2714   "Complete using .mailrc and passwd.
2715 Optional VERB allows displaying messages.
2716
2717 References:
2718
2719   The completion type is determined by variable `tinymail-:complete-mode',
2720   which can be 'alias or 'string
2721
2722   This function is part of the other completion possibilities run by
2723   `tinymail-complete-key'  and installed in `tinymail-:complete-key-hook'.
2724
2725 Return:
2726
2727  non-nil    Completion handled
2728  nil        Not completed"
2729   (interactive "*")
2730   (let ((mode  tinymail-:complete-mode)
2731         (pmode tinymail-:password-mode)
2732         ret)
2733     (ti::verb)
2734     (cond
2735      ((eq  mode 'alias)
2736       (and (null (setq ret (tinymail-complete-guess-1 'alias  verb)))
2737            pmode
2738            (setq ret (tinymail-complete-passwd nil verb))))
2739      ((eq  mode 'string)
2740       (and (null (setq ret (tinymail-complete-guess-1 'string  verb)))
2741            pmode
2742            (tinymail-field-in-to-cc-p)
2743            (setq ret (tinymail-complete-passwd nil verb))))
2744      (t
2745       (error "TinyMail: Unknown mode %s" mode)))
2746     (tinymail-debug "tinymail-complete-guess" mode "PASS-MODE" pmode "RET" ret)
2747     ret))
2748
2749 ;;; ----------------------------------------------------------------------
2750 ;;;
2751 (defun tinymail-complete-everything (&optional verb)
2752   "Gather list of possible completions and let user choose."
2753   (interactive)
2754   (let ((data (tinymail-complete-string-read)))
2755     (when (and data
2756                ;;  It doesn't make sense to search items that already
2757                ;;  look like email,  this@here.com
2758                (not (string-match "@" (nth 2 data))))
2759       (let* ((fid           "tinymail-complete-everything:")
2760              (check-regexp  tinymail-:confirm-mailrc-regexp)
2761              (mode          tinymail-:complete-mode)
2762              matches
2763              (beg (nth 0 data))
2764              (end (nth 1 data))
2765              choice
2766              done
2767              ret
2768              user-selected-p)
2769         (unless fid ;; No-op. XEmacs byte compiler silencer
2770           (setq fid nil))
2771         ;; .......................................... clean duplicates ...
2772         (dolist (results (list
2773                           (tinymail-complete-list-mail-aliases mode data)
2774                           (tinymail-complete-list-bbdb mode data)
2775                           (tinymail-complete-list-passwd mode data)))
2776           (dolist (address results)
2777             (pushnew address matches :test 'string=)))
2778         ;; ............................................... any matches ...
2779         ;;  How many matches?
2780         (cond
2781          ((eq 1 (length matches))
2782           (setq choice (car matches)))
2783          (matches
2784           (setq choice
2785                 (completing-read
2786                  (format "%d Choose: " (length matches))
2787                  (ti::list-to-assoc-menu matches)
2788                  nil
2789                  (not 'must-match)))
2790           (if (ti::nil-p choice)
2791               (setq choice nil)
2792             (setq  user-selected-p  t
2793                    ret              t))))
2794         (tinymail-debug fid "CHOICE" choice)
2795         ;; .............................................. select match ...
2796         ;;  Now we have a MATCH unless user cancelled the choices
2797         (when choice
2798           (tinymail-debug fid 'mode mode "CHOICE" choice)
2799           ;;  For some strings, ask confirmation.
2800           ;;  Ie. Give a chance to discard this completions and move on...
2801           (tinymail-debug fid
2802                           "CHECK" check-regexp
2803                           (string-match check-regexp choice))
2804           ;;  *) If user already did selected this match from several
2805           ;;     choices, then go ahead
2806           ;;  *) If we found only one match, then confirm that match
2807           (when (or user-selected-p
2808                     (not (stringp check-regexp))
2809                     (or (null (string-match check-regexp choice))
2810                         (and (string-match check-regexp choice)
2811                              (tinymail-y-or-n-p (concat "TinyMail: " choice)))))
2812             (goto-char (marker-position beg))
2813             (delete-region (marker-position beg) (marker-position end))
2814             (setq  beg nil  end nil) ;; Kill markers.
2815             (insert choice)
2816             (setq done t  ret t)))
2817         (if (and verb (null done) choice)
2818             (message (format "TinyMail: no completion match on '%s'" choice)))
2819         (tinymail-debug fid "RET" ret)
2820         ret))))
2821
2822 ;;; ----------------------------------------------------------------------
2823 ;;;
2824 (defun tinymail-complete-guess-in-headers (&optional arg)
2825   "Like `tinymail-complete-guess', but complete only in headers. Ignore ARG."
2826   (interactive)
2827   (ti::mail-point-in-header-macro
2828    (when (ti::mail-field-email-address-p)
2829      (tinymail-debug 'tinymail-complete-guess-in-headers
2830                      'ARG arg 'MODE major-mode 'POINT (point))
2831      (tinymail-complete-everything))))
2832
2833 ;;; ----------------------------------------------------------------------
2834 ;;;
2835 (defun tinymail-complete-guess-in-body (&optional arg)
2836   "Like `tinymail-complete-guess', but complete only in body. Ignore ARG."
2837   (interactive)
2838   (when (>(point) (ti::mail-hmax))
2839     (let* ((fid    "tinymail-complete-guess-in-body")
2840            (hook   tinymail-:complete-body-hook)
2841            (data  (tinymail-complete-string-read))
2842            ret)
2843       (unless fid ;; No-op. XEmacs byte compiler silencer
2844         (setq fid nil))
2845       (tinymail-debug 'tinymail-complete-guess-in-body
2846                       'ARG     arg
2847                       'MODE    major-mode
2848                       'POINT   (point)
2849                       'data    data
2850                       'hook    hook)
2851       (dolist (func hook)
2852         (tinymail-debug fid 'FUNC func)
2853         (when (cond
2854                ((not (fboundp func))
2855                 (tinymail-debug fid 'FUNC func "not exist")
2856                 nil)
2857                (t
2858                 (funcall func data)))
2859           (setq ret t)
2860           (return)))
2861       ret)))
2862
2863 ;;; ----------------------------------------------------------------------
2864 ;;;
2865 (defun tinymail-complete-bbdb  (&rest args)
2866   "Call bbdb-complete-name' if is bbdb loaded and ignore ARGS."
2867   (when (and (fboundp 'bbdb-complete-name)
2868              (tinymail-field-in-to-cc-p))
2869     (let* ((point (point)))
2870       (call-interactively 'bbdb-complete-name)
2871       (if (eq (point) point)
2872           nil                          ;Point not moved, not completed
2873         ;; point moved, completed
2874         t))))
2875
2876 ;;; ----------------------------------------------------------------------
2877 ;;;
2878 (defun tinymail-bbdb-parse-to-string ()
2879   "Parse BBDB to a fast search format."
2880   (let ((str "")
2881         record
2882         tmp)
2883     (mapatoms
2884      (function
2885       (lambda (sym &optional symbol val name notes)
2886         (setq symbol (symbol-name sym))
2887         (setq record (bbdb-gethash symbol))
2888         (if (and (listp record)
2889                  (vectorp (setq tmp (car-safe record))))
2890             (setq record tmp))
2891         (when record
2892           (setq name  (bbdb-record-name record))
2893
2894           (setq str (concat str (format "\C-m%s\C-j%s"
2895                                         name
2896                                         (prin1-to-string record)))))))
2897      (bbdb-hashtable))
2898     str))
2899
2900 ;;; ----------------------------------------------------------------------
2901 ;;;
2902 (defsubst tinymail-bbdb-record-fix (record)
2903   "Fix BBDB RECORD to pure vector.
2904 Upgrading from v3 to v5 BBDB database, the
2905 entries are returned as ([ ... ]) by
2906 bbdb-gethash, but this format is not suitable for
2907 calling (bbdb-record-net record)
2908
2909 The code below removes the extra () and only
2910 leaves RECORD [ .. ]."
2911   (let (tmp)
2912     (if (and (listp record)
2913              (vectorp (setq tmp (car-safe record))))
2914         tmp
2915       record)))
2916
2917 ;;; ----------------------------------------------------------------------
2918 ;;;
2919 (defun  tinymail-bbdb-data-read ()
2920   "Read user information based on current line in `bbdb-file'."
2921   (let* ((fid   "tinymail-bbdb-data-read:")
2922          (point (point))
2923          (case-fold-search
2924           tinymail-:complete-bbdb-case-fold-search)
2925          one
2926          two
2927          key
2928          record)
2929     (unless fid ;; No-op. XEmacs byte compiler silencer
2930       (setq fid nil))
2931     (beginning-of-line)
2932     ;;  ["Jack E." "Den" nil nil nil
2933     ;;    |         |
2934     ;;    one       two
2935     (when (looking-at "^.\"\\([^\n\r\"]+\\)[ \t\"]+\\([^ \t\"]+\\)")
2936       (setq one (match-string 1)
2937             two (match-string 2))
2938       (if (string= one "nil")
2939           (setq one nil))
2940       (if (string= two "nil")
2941           (setq two nil))
2942       (cond
2943        ((and one two)
2944         (setq key (format "%s %s" one two)))
2945        (one
2946         (setq key one))
2947        (two
2948         (setq key two)))
2949       (setq record (bbdb-gethash (downcase key))))
2950     (goto-char point) ;; faster than save-excursion
2951     (if tinymail-:debug
2952         (tinymail-debug fid one two 'key key '=> record))
2953     record))
2954
2955 ;;; ----------------------------------------------------------------------
2956 ;;;
2957 (defun tinymail-bbdb-record-net-completions (record)
2958   "Construct email completions for RECORD."
2959   (let* ((fid "tinymail-bbdb-record-net-completions:")
2960          completion
2961          tmp
2962          name
2963          list)
2964     (unless fid ;; No-op. XEmacs byte compiler silencer
2965       (setq fid nil))
2966     (dolist (net (bbdb-record-net record))
2967       (when (and (stringp net)
2968                  (string-match "@" net))
2969         (setq completion
2970               ;;  If user has given a custom name to a NET,
2971               ;;  Like: Customer Support -- Phone Number <@>
2972               ;;  Then use that. Otherwise
2973               ;;  combine name and plain address
2974               (if (string-match "[<>]" net)
2975                   net
2976                 (setq name  (bbdb-record-name record))
2977                 ;; DO NOT ADD name "John doe" if address already
2978                 ;; has those in john.doe@some.com because it looks
2979                 ;; funny to read "john doe john.doeEMAIL" multiple times
2980                 (if tinymail-:debug
2981                     (tinymail-debug fid
2982                                     "\n\t" 'SPLIT
2983                                     (split-string name) net))
2984                 (if (and (setq tmp (split-string name))
2985                          (> (length tmp) 1)
2986                          (string-match (regexp-quote (nth 0 tmp)) net)
2987                          (string-match (regexp-quote (nth 1 tmp)) net))
2988                     net
2989                   (format "%s <%s>" name net))))
2990         (push completion list)))
2991     (if tinymail-:debug
2992         (tinymail-debug fid "\n\t" 'RET list))
2993     list))
2994
2995 ;;; ----------------------------------------------------------------------
2996 ;;; Switched to another implementation (2). Read the matches
2997 ;;; directly from the BBDB data buffer, because it is faster than reading
2998 ;;; with `mapatoms' => obarray.
2999 ;;;
3000 (defun tinymail-complete-list-bbdb-2 (regexp &optional check)
3001   "Return list of strings that match REGEXP in BBDB hash table.
3002
3003 Input:
3004
3005   REGEXP  Regexp to match for mail fields
3006   CHECK  See `tinymail-:complete-bbdb-fuzzy-method'."
3007   (let ((fid  "tinymail-complete-list-bbdb-2: ")
3008         buffer
3009         list
3010         str
3011         record)
3012     (unless fid ;; No-op. XEmacs byte compiler silencer
3013       (setq fid nil))
3014     ;; The BBDB intrface code is filled with condition statements:
3015     ;;
3016     ;; (if tinymail-:debug
3017     ;;     (tinymail-debug
3018     ;;
3019     ;; This prevents function call to happen, so that
3020     ;; the BBDB interface is as fast as possible.
3021     (when (and (featurep 'bbdb)
3022                (setq buffer (find-buffer-visiting bbdb-file)))
3023       (with-current-buffer buffer
3024         ;;  Don't want to see text properties in this buffer.
3025         (if (and (boundp 'font-lock-mode)
3026                  (symbol-value 'font-lock-mode))
3027             (font-lock-mode -1))
3028         (ti::pmin)
3029         (while (re-search-forward regexp nil t)
3030           (if tinymail-:debug
3031               (tinymail-debug fid
3032                               'found (ti::buffer-read-space-word)
3033                               (ti::read-current-line)
3034                               "\n"))
3035           (when (setq record (tinymail-bbdb-data-read))
3036             (setq record (tinymail-bbdb-record-fix record))
3037             ;; ......................................... field match ...
3038             (if (null check)
3039                 nil ;; (setq ok t)
3040               (dolist (func check)
3041                 (when (and (functionp func)
3042                            (setq str (funcall func record))
3043                            (cond
3044                             ((stringp str)
3045                              (string-match regexp str))
3046                             ((and (listp str)
3047                                   ;; '((field . "str") ..)
3048                                   (ti::consp (car-safe str)))
3049                              (dolist (elt str)
3050                                (setq elt (cdr elt))
3051                                (when (and (stringp elt)
3052                                           (string-match regexp elt))
3053                                  (return t))))
3054                             ((and (listp str)
3055                                   (stringp (car-safe str)))
3056                              (dolist (s str)
3057                                (when (string-match regexp s)
3058                                  (return t))))))
3059                   (if tinymail-:debug
3060                       (tinymail-debug fid 'MATCH regexp func str))
3061                   (return))))
3062             ;; .................................... make completions ...
3063             (dolist (elt (inline
3064                            (tinymail-bbdb-record-net-completions
3065                             record)))
3066               ;;  Previously used `pushnew' to to remove duplicates.
3067               ;;  push is faster. See `tinymail-complete-everything'
3068               ;;
3069               ;;  (pushnew elt list :test 'string=)
3070               (push elt list)))
3071           (forward-line 1))))
3072     (if tinymail-:debug
3073         (tinymail-debug fid 'RET list))
3074     list))
3075
3076 ;;; ----------------------------------------------------------------------
3077 ;;;
3078 (defun tinymail-complete-list-bbdb-1 (regexp &optional fields)
3079   "Return list of strings that match REGEXP and @ in BBDB hash table.
3080
3081 Input:
3082
3083   REGEXP        Regexp to match for mail fields
3084   FIELDS        See `tinymail-:complete-bbdb-fuzzy-method'."
3085   (let ((fid  "tinymail-complete-list-bbdb-1: ")
3086         list
3087         record
3088         completion
3089         tmp)
3090     (unless fid ;; No-op. XEmacs byte compiler silencer
3091       (setq fid nil))
3092     (when (featurep 'bbdb)
3093       (mapatoms
3094        (function
3095         (lambda (sym &optional symbol val name notes)
3096           (setq symbol (symbol-name sym))
3097           ;;  Look at all atoms in BBDB and try to find email addresses
3098           ;;  that have string that would match.
3099           (setq record (bbdb-gethash symbol))
3100           ;;  NOTE: upgrading from v3 to v5 BBDB database, the
3101           ;;  entries are returned as ([ ... ]) by
3102           ;;  bbdb-gethash, but this format is not suitable for
3103           ;;  calling (bbdb-record-net record)
3104           ;;
3105           ;;  The code below removes the extra () and only
3106           ;;  leaves RECORD [ .. ]
3107           (if (and (listp record)
3108                    (vectorp (setq tmp (car-safe record))))
3109               (setq record tmp))
3110           (when record
3111             (setq name  (bbdb-record-name record)
3112                   notes (bbdb-record-notes record)))
3113           ;; .......................................... select record ...
3114           (when fields
3115             ;;  If ANYTHING has been set:
3116             ;;  -- Compare element in BBDB if it is string
3117             ;;  -- Require at least 3 characters to compare
3118             ;;     (it makes no sense to complete one character "a")
3119             ;;  -- Match the element
3120             ;; ["John" "Doe" nil nil nil nil ("jdoe@example.com")
3121             ;;   ((creation-date . "2000-09-09") (timestamp . "2000-09-09")
3122             ;;   (notes . "that.el"))
3123             ;;   ["John Doe" nil #<marker at 114932 in bbdb-data.el> nil]])
3124             (when (and fields record)
3125               ;;  #todo: Is there function to `dolist' over vector list?
3126               (let* ((i   0)
3127                      (len (if (integerp fields)
3128                               fields
3129                             3))
3130                      (max (1- (length record)))
3131                      elt)
3132                 (when (>= (length regexp) len)
3133                   (while (< i max)
3134                     (setq elt (aref record i))
3135                     (incf i)
3136                     (if (not (listp elt))
3137                         (setq elt (list elt)))
3138                     (dolist (item elt)
3139                       ;;   Try CDR: (notes . "value")
3140                       ;;   or  CAR: ("string")
3141                       (if (listp item)
3142                           (setq item (or (cdr-safe item)
3143                                          (car-safe item))))
3144                       (when (and (stringp item)
3145                                  (string-match  regexp item))
3146                         (tinymail-debug fid 'ANYTHING regexp elt)
3147                         (setq max (1+ max))
3148                         (return))))))))
3149           ;; ..................................... make completions ...
3150           (when (and record
3151                      name
3152                      (prog1 t (tinymail-debug fid 'BBDB-SCAN record))
3153
3154                      ;;  If there is case sensitive search in effect, check that,
3155                      ;;  before adding to completion list
3156
3157                      (let ((case-fold-search
3158                             tinymail-:complete-bbdb-case-fold-search))
3159                        (or (string-match regexp symbol)
3160                            (string-match regexp name)
3161                            (string-match regexp (or notes ""))))
3162                      (setq val (bbdb-record-net record)))
3163             (tinymail-debug fid
3164                             'MATCHED-OK
3165                             "REGEXP"  regexp
3166                             "SYMBOL"  symbol
3167                             "ATOM "   val
3168                             "RECORD"  record
3169                             "NET"     val
3170                             "NOTES"   notes)
3171             (dolist (net val)
3172               (when (and (stringp net)
3173                          (string-match "@" net))
3174                 (setq completion
3175                       ;;  If user has given a custom name to a NET,
3176                       ;;  Like: Customer Support -- Phone Number <@>
3177                       ;;  Then use that. Otherwise
3178                       ;;  combine name and plain address
3179                       (if (string-match "[<>]" net)
3180                           net
3181                         ;; DO NOT ADD name "John doe" if address already
3182                         ;; has those in john.doe@some.com because it looks
3183                         ;; funny to read NAME EMAIL multiple times
3184                         (tinymail-debug fid
3185                                         "\n\t" 'SPLIT
3186                                         (split-string name) net)
3187                         (if (and (setq tmp (split-string name))
3188                                  (> (length tmp) 1)
3189                                  (string-match (regexp-quote (nth 0 tmp)) net)
3190                                  (string-match (regexp-quote (nth 1 tmp)) net))
3191                             net
3192                           (format "%s <%s>" name net))))
3193                 (pushnew completion list :test 'string=)))) ;; When-end
3194           (setq record nil)))
3195        (bbdb-hashtable))
3196
3197       (tinymail-debug fid 'RETURN-COMPLETIONS list)
3198       list)))
3199
3200 ;;; ----------------------------------------------------------------------
3201 ;;;
3202 (defun tinymail-complete-list-bbdb (mode data)
3203   "Return list of matches from BBDB.
3204
3205 Input:
3206
3207   MODE is the value of  `tinymail-:complete-mode'.
3208   DATA can contain values returned from `tinymail-complete-string-read'."
3209   (when (or data
3210             (setq data (tinymail-complete-string-read)))
3211     (setq data (regexp-quote (nth 2 data)))
3212     (tinymail-complete-list-bbdb-2
3213      data
3214      tinymail-:complete-bbdb-fuzzy-method)))
3215
3216 ;;; ----------------------------------------------------------------------
3217 ;;;
3218 (defun tinymail-complete-bbdb-fuzzy (&optional info &optional force)
3219   "Scan through BBDB 'net for partial matches and offer completion list.
3220
3221 Input:
3222
3223   INFO   '(beg end STRING)  of the completion word
3224   FORCE  Normally this function completes only in Header To/Cc fields< but if
3225          this is non-nil, complete at point."
3226   (when (and (featurep 'bbdb)
3227              (or force (tinymail-field-in-to-cc-p))
3228              (eq tinymail-:complete-mode 'string))
3229     (tinymail-debug 'tinymail-complete-bbdb-fuzzy info 'FORCE force)
3230     (let* ((fid    "tinymail-complete-bbdb-fuzzy:")
3231            (string (nth 2 info))
3232            (list   (and string
3233                         (tinymail-complete-list-bbdb-2
3234                          (regexp-quote string)))))
3235       (unless fid ;; No-op. XEmacs byte compiler silencer
3236         (setq fid nil))
3237       (tinymail-debug fid 'LIST list 'STRING string)
3238
3239       (when list
3240         (cond
3241          ((eq 1 (length list))
3242           (if (tinymail-y-or-n-p (concat "TinyMail bbdb accept: " (car list)))
3243               (tinymail-complete-insert-completion (car list) info)
3244             (tinymail-y-or-n-p-abort-p)))
3245          (list
3246           (setq string (completing-read
3247                         (format "TinyMail bbdb fuzzy %d (empty to cancel): "
3248                                 (length list))
3249                         (ti::list-to-assoc-menu list)))
3250           (unless (ti::nil-p string)
3251             (tinymail-complete-insert-completion string info)
3252             t)))))))
3253
3254 ;;; ----------------------------------------------------------------------
3255 ;;;
3256 (defun tinymail-complete-bbdb-fuzzy-at-point (info)
3257   "Call tinymail-complete-bbdb-fuzzy with INFO and `FORCE' argument."
3258   (tinymail-complete-bbdb-fuzzy info 'force))
3259
3260 ;;; ----------------------------------------------------------------------
3261 ;;;
3262 (defun tinymail-complete-insert-completion (string info)
3263   "Replace the content with STRING by using the INFO.
3264 INFO contains list '(begin-point end-point text-between-points)."
3265   (interactive)
3266   (delete-region (nth 0 info) (nth 1 info))
3267   (insert string)
3268   (skip-chars-backward " ")
3269   t)
3270
3271 ;;; ----------------------------------------------------------------------
3272 ;;;
3273 (defun tinymail-header-complete-choices (field)
3274   "Return completion choices for HEADER-FIELD."
3275   (let* ((fid  "tinymail-header-complete-choices:")
3276          (ret  (nth 1 (assoc field tinymail-:table-header-complete))))
3277     (unless fid ;; No-op. XEmacs byte compiler silencer
3278       (setq fid nil))
3279     (tinymail-debug fid 'CHOICES-RAW ret)
3280     ;;  If the first element is string, then suppose list of strings
3281     ;;  If not, evaluate `choices' to get list of strings.
3282     (when ret
3283       (if (not (stringp (car ret)))
3284           (setq ret (eval ret))))
3285     (tinymail-debug fid 'CHOICES-FINAL ret)
3286     ret))
3287
3288 ;;; ----------------------------------------------------------------------
3289 ;;;
3290 (defun tinymail-complete-simple (&optional info)
3291   "Complete according to `tinymail-:table-header-complete'.
3292 INFO is '(string beg end) of the completion word"
3293   (interactive)
3294   (ti::mail-point-in-header-macro
3295    (let* ((fid        "tinymail-complete-simple: ")
3296           (field-1    (ti::remove-properties (ti::mail-current-field-name)))
3297           (field      (and field-1
3298                            (capitalize field-1))) ;; gcc -> Gcc
3299           (field-info (or info
3300                           (tinymail-complete-string-read)))
3301           multi-word
3302           complete-list
3303           tmp
3304           choices
3305           string
3306           ret)
3307      (unless fid ;; No-op. XEmacs byte compiler silencer
3308        (setq fid nil))
3309      ;;  The EVAL-FORM may set this if it does not return `choices'
3310      (setq tinymail-:complete-key-return-value nil)
3311      ;;  The STRING is dynamically bound and visible for EVAL CHOICES
3312      (when (stringp (setq string (nth 2 field-info)))
3313        (setq choices (tinymail-header-complete-choices field)))
3314      (tinymail-debug fid
3315                      'INFO    info
3316                      'FIELD   field
3317                      'STRING  string
3318                      'CHOICES choices)
3319      ;; ............................................... check choices ...
3320      (when choices
3321        (cond
3322         ((null string) ;; Empty field, user expects all completions
3323          (setq string (completing-read
3324                        (concat field ": ")
3325                        (ti::list-to-assoc-menu choices)))
3326          (unless (ti::nil-p string)
3327            (insert string)
3328            (setq ret t)))
3329         (t
3330          (setq choices (ti::list-to-assoc-menu choices))
3331          ;;  Forget choices that are multiwords "val val"
3332          (unless (string-match " " string)
3333            (setq complete-list (all-completions string choices))
3334            ;;  This is the common string at the beginning
3335            (setq tmp (try-completion string choices))
3336            (tinymail-debug fid
3337                            'COMPLETE-LIST complete-list
3338                            'TRY tmp
3339                            'str string)
3340            (dolist (completion complete-list)
3341              (when (string-match " " completion)
3342                (setq multi-word t)
3343                (return)))
3344            ;; ....................................... completion-list ...
3345            (cond
3346             ((null complete-list)
3347              (message "TinyMail: no simple completions matching `%s'" string))
3348             ((or (and (eq 1 (length complete-list))      ;; ONE found
3349                       (setq string (car complete-list))) ;; that's it
3350                  (and tmp
3351                       ;;  Don't accept partial match from "Multi Word"
3352                       ;;  completion strings.
3353                       multi-word
3354                       (not (ti::nil-p
3355                             (setq string
3356                                   (completing-read
3357                                    "Complete: "
3358                                    (ti::list-to-assoc-menu complete-list)
3359                                    nil
3360                                    nil
3361                                    ;; initial value
3362                                    tmp))))))
3363              (tinymail-complete-insert-completion string info)
3364              (setq ret t))
3365
3366             ((and tmp (not (string= tmp string)))
3367              ;;  there was common denominator, complete further
3368              (tinymail-complete-insert-completion tmp info)
3369              (message "Tinymail complete:  %s"
3370                       (ti::list-to-string complete-list ", " ))
3371              (setq ret t))
3372             (complete-list
3373              (let (ret)
3374                (setq ret (completing-read
3375                           (concat field ": ")
3376                           (ti::list-to-assoc-menu complete-list)
3377                           nil
3378                           nil
3379                           string))
3380                (unless (ti::nil-p ret)
3381                  (tinymail-complete-insert-completion ret info)))
3382              ;; More than 1, stop and return t
3383              (setq ret t)))))))
3384      (tinymail-debug fid
3385                      "RET"    ret
3386                      "GLOBAL COMPLETE VALUE"
3387                      tinymail-:complete-key-return-value)
3388      ;;  Return status if we did something in this function
3389      (or ret
3390          tinymail-:complete-key-return-value))))
3391
3392 ;;; ----------------------------------------------------------------------
3393 ;;;
3394 (defun tinymail-complete-guest-packages (&optional arg)
3395   "Support minor modes like tinytab and tinyindent which also use TAB key.
3396 Ignore ARG."
3397   (interactive "P")
3398   (let* ((fid     "tinymail-complete-guest-packages:")
3399          (ch      last-command-char))
3400     (unless fid ;; No-op. XEmacs byte compiler silencer
3401       (setq fid nil))
3402     (tinymail-debug fid 'ARG arg 'MODE major-mode 'POINT (point))
3403     ;;  The TinyTab minor mode overrides tab, return nil
3404     ;;  so that it can proceed
3405     (cond
3406      ((and (featurep 'tinytab)
3407            (symbol-value 'tinytab-mode)
3408            (fboundp 'tinytab-tab-key))
3409       (tinymail-debug fid 'tinytab-tab-key tinytab-:tab-insert-hook)
3410       (ti::funcall 'tinytab-tab-key))
3411      ((and (featurep 'tinyindent)
3412            (symbol-value 'tinyindent-mode)
3413            (fboundp 'tinyindent-tab-key))
3414       (tinymail-debug fid 'tinyindent-tab-key)
3415       (ti::funcall 'tinyindent-tab-key))
3416      (t
3417       (when ch
3418         (self-insert-command 1)
3419         t)))))
3420
3421 ;;; ----------------------------------------------------------------------
3422 ;;;
3423 (defun tinymail-complete-list-passwd (&optional mode data force)
3424   "Return list of matches from password file.
3425
3426 Input:
3427
3428   MODE is the value of  `tinymail-:complete-mode'.
3429   DATA can contain values returned from `tinymail-complete-string-read'."
3430   (let* ((fid  "tinymail-complete-list-passwd")
3431          str
3432          table
3433          completions)
3434     (unless fid ;; No-op. XEmacs byte compiler silencer
3435       (setq fid nil))
3436     (when (or data
3437               (setq data (tinymail-complete-string-read)))
3438       (setq str (regexp-quote (nth 2 data))))
3439     (if (or force
3440             (null tinymail-:password-completion-alist))
3441         (tinymail-password-define-variables))
3442     (setq table       tinymail-:password-completion-alist)
3443     (setq completions (all-completions str table))
3444     (tinymail-debug fid str "COMPLETIONS" completions)
3445     completions))
3446
3447 ;;; ----------------------------------------------------------------------
3448 ;;;
3449 (defun tinymail-display-list (list &optional flash)
3450   "Display LIST or alist in `tinymail-:temp-buffer' or FLASH in echo area."
3451   (when list
3452     (if flash
3453         (message (ti::list-to-string (mapcar 'car list)))
3454       (let* ((buffer (ti::temp-buffer tinymail-:temp-buffer 'clear)))
3455         (with-current-buffer buffer
3456           (dolist (elt list)
3457             (insert (format "%-10s %s\n"  (car elt) (or (cdr elt) "")))))
3458         (display-buffer buffer)
3459         (ti::save-excursion-macro ;; Go and make displayed buffer small
3460           (select-window (get-buffer-window buffer))
3461           (shrink-window-if-larger-than-buffer))))))
3462
3463 ;;; ----------------------------------------------------------------------
3464 ;;;
3465 (defun tinymail-password-grep (match &optional verb)
3466   "Grep USER from passwd.
3467
3468 Input:
3469
3470   MATCH     String, to grep
3471   DISPLAY   flag, display results in separate buffer.
3472   VERB      flag, Verbose messages"
3473   (interactive "sUser regexp: ")
3474   (let ((fid      "tinymail-password-grep")
3475         alist)
3476     (unless fid ;; No-op. XEmacs byte compiler silencer
3477       (setq fid nil))
3478     (ti::verb)
3479     (tinymail-debug fid match verb)
3480     (if (null tinymail-:password-alist)
3481         (tinymail-password-define-variables))
3482     ;;   Force loading it if not exist
3483     (if verb (message "Grepping passwd contents..."))
3484     (setq alist (ti::file-passwd-grep-user-alist
3485                  match nil tinymail-:password-alist))
3486     (if verb (message "Grepping...done"))
3487     alist))
3488
3489 ;;; ----------------------------------------------------------------------
3490 ;;;
3491 (defun tinymail-complete-headers-move-to-next-field (&rest ignore)
3492   "Move to next field if cursor is at the end of field in header."
3493   (interactive)
3494   (ti::mail-point-in-header-macro
3495    (let* ((str (buffer-substring (line-beginning-position) (point)))
3496           (max (ti::mail-text-start)))
3497      (when (and (not (ti::nil-p str))
3498                 (eolp))
3499        (when (re-search-forward ":." max t)
3500          (end-of-line))))))
3501
3502 ;;; ----------------------------------------------------------------------
3503 ;;;
3504 (defun tinymail-complete-headers-nothing-found (&rest ignore)
3505   "Display 'No completions found' in header and return t. IGNORE arguments.
3506 Advance by 4 spaces if there is only spaces to the left."
3507   (interactive)
3508   (ti::mail-point-in-header-macro
3509    ;; User started a continuing line. Point is at mark (!)
3510    ;;
3511    ;;   CC: some@example.com
3512    ;;   !
3513    ;;   To: him@there.at
3514    ;;
3515    (cond
3516     ((or (ti::nil-p (buffer-substring (line-beginning-position) (point)))
3517          (char-equal (char-syntax (preceding-char)) ?\ ))
3518      (insert "    "))
3519     ((ti::mail-point-at-header-p)
3520      ;;  this message is displayed only when cursor is next to character
3521      ;; (forward-word 1)
3522      (message "TinyMail: No completions found.")
3523      t)
3524     (nil))))
3525
3526 ;;; ----------------------------------------------------------------------
3527 ;;;
3528 (defun tinymail-complete-abbrevs (&optional info)
3529   "Complete using abbrevs. INFO."
3530   ;; Actually we don't need this because SPACE already expands abbrevs
3531   ;; if abbrev mode is on.
3532   ;; (expand-abbrev)
3533   nil)
3534
3535 ;;; ----------------------------------------------------------------------
3536 ;;;
3537 (defun tinymail-complete-key-remove-itself ()
3538   "Remove calls from `tinytab-:tab-insert-hook'. See
3539 Source code of `tinymail-complete-key' why. "
3540   ;;  In calling function this variable is `let' bound, so the
3541   ;;  change is temporary.
3542   (let (clean-hook)
3543     (when (boundp 'tinytab-:tab-insert-hook)
3544       (dolist (function tinytab-:tab-insert-hook)
3545         (if (not (string-match "tinymail"
3546                                (or (symbol-name function) "")))
3547             (push function clean-hook)))
3548       (setq tinytab-:tab-insert-hook (nreverse clean-hook)))))
3549
3550 ;;; ----------------------------------------------------------------------
3551 ;;;
3552 (defun tinymail-tab-to-tab-stop (&rest args)
3553   "Ignore ARGS and call `tab-to-tab-stop'."
3554   (tab-to-tab-stop))
3555
3556 ;;; ----------------------------------------------------------------------
3557 ;;;
3558 (defun tinymail-complete-key (&optional header-check)
3559   "Run functions in `tinymail-:complete-key-hook'.
3560 Te first function that return non-nil terminates calling the rest of the
3561 functions. Each function is passed the word info at point: '(BEG END STRING)."
3562   (interactive)
3563   (tinymail-debug 'tinymail-complete-key
3564                   'BEGIN
3565                   tinymail-:complete-key-hook)
3566   ;; It makes no use to call this function anywhere elase than in Mail
3567   ;; buffer. (this prevent's double call from tinytab.el too)
3568   (when (ti::mail-mail-p)
3569     (tinymail-debug 'tinymail-complete-key
3570                     'HEADER-CHECK header-check
3571                     (save-excursion
3572                       (concat "\n************ START **********\n"
3573                               (buffer-substring
3574                                (progn (forward-line -2) (point))
3575                                (progn (forward-line 2) (point)))
3576                               "\n************ END **********\n")))
3577     (let* ((fid    "tinymail-complete-key:")
3578            ;; Make copies of these
3579            (tinytab-:tab-insert-hook
3580             (if (boundp 'tinytab-:tab-insert-hook)
3581                 (symbol-value 'tinytab-:tab-insert-hook)))
3582            (tinymail-:complete-key-hook tinymail-:complete-key-hook)
3583            string
3584            ret)
3585       (unless fid ;; No-op. XEmacs byte compiler silencer
3586         (setq fid nil))
3587       ;; Avoid resursive calls by removing all tinymail entries
3588       (tinymail-complete-key-remove-itself)
3589       ;;  It doesn't make sense to run mail completions inside BODY,
3590       ;;  remove unnecessary hooks
3591       (cond
3592        ((ti::mail-point-at-body-p)
3593         (tinymail-debug 'tinymail-complete-key 'POINT-INSIDE-BODY)
3594         ;; Leaves
3595         ;;
3596         ;; tinymail-complete-abbrevs
3597         ;; tinymail-complete-guest-packages
3598         (ti::add-hooks 'tinymail-:complete-key-hook
3599                        '(tinymail-complete-everything
3600                          tinymail-complete-simple
3601                          tinymail-complete-guess-in-headers
3602                          tinymail-complete-headers-nothing-found
3603                          tinymail-complete-headers-move-to-next-field)
3604                        'remove)
3605         (cond
3606          ((and (boundp 'tinytab-mode)
3607                tinytab-mode
3608                (fboundp 'tinytab-indent-by-tab-width))
3609           (add-hook 'tinymail-:complete-key-hook
3610                     'tinytab-indent-by-tab-width))
3611          (t
3612           (add-hook 'tinymail-:complete-key-hook
3613                     'tinymail-tab-to-tab-stop)))
3614         (tinymail-debug fid 'BODY-AREA tinymail-:complete-key-hook))
3615        (t
3616         (tinymail-debug 'tinymail-complete-key 'POINT-INSIDE-HEADER)
3617         (setq string (tinymail-complete-string-read))
3618         ;;  Complete only in CC, Bcc, To .. fields. If not there,
3619         ;;  remove function
3620         (when (and header-check
3621                    (not (ti::mail-field-email-address-p)))
3622           (tinymail-debug 'tinymail-complete-key 'NOT-IN-TO-CC-BCC)
3623           (tinymail-debug fid 'header-check 'REMOVED
3624                           'tinymail-complete-everything)
3625           (remove-hook 'tinymail-:complete-key-hook
3626                        'tinymail-complete-everything))))
3627       ;; .................................................... cond-end ...
3628       (tinymail-debug fid 'LOOPING-LIST tinymail-:complete-key-hook)
3629       (dolist (func tinymail-:complete-key-hook)
3630         (tinymail-debug fid 'FUNC func string)
3631         (when (cond
3632                ((not (fboundp func))
3633                 (tinymail-debug fid 'FUNC func "not exist")
3634                 nil)
3635                (t
3636                 (funcall func string)))
3637           (setq ret t)
3638           (return)))
3639       (tinymail-debug fid fid 'RET ret)
3640       ret)))
3641
3642 ;;; ----------------------------------------------------------------------
3643 ;;;
3644 (defun tinymail-complete-key-interactive ()
3645   "See `tinymail-complete-key'. Comlete only in header."
3646   (interactive)
3647   (tinymail-complete-key 'only-complete-in-headers))
3648
3649 ;;}}}
3650 ;;{{{ advice
3651
3652 ;;; .......................................................... &advice ...
3653
3654 ;;  Old message has autosave name "*message*", but that does not work in
3655 ;;  Win32 platform (C-x m  M-x message-mode and  C-x s and Emacs
3656 ;;  dies on error)
3657
3658 (when (ti::win32-p)
3659   (require 'message)
3660   (if (string-match
3661        "[*]message"
3662        (prin1-to-string
3663         (symbol-function 'message-set-auto-save-file-name)))
3664       (defadvice message-set-auto-save-file-name (around tinymail act)
3665         "\
3666 Replace function. Change the autosave name from *message* to #message# due to Win32"
3667         (when message-auto-save-directory
3668           (if (gnus-alive-p)
3669               (setq message-draft-article
3670                     (nndraft-request-associate-buffer "drafts"))
3671             (setq buffer-file-name
3672                   (expand-file-name "#message#"
3673                                     message-auto-save-directory))
3674             (setq buffer-auto-save-file-name (make-auto-save-file-name)))
3675           (clear-visited-file-modtime)
3676           (setq buffer-file-coding-system message-draft-coding-system)))))
3677
3678 ;;}}}
3679 ;;{{{ Extra
3680
3681 ;;; ----------------------------------------------------------------------
3682 ;;;
3683 (defun tinymail-save-dead-mail-maybe ()
3684   "Call `tinymail-save-dead-mail' only if RMAIL is used as MUA.
3685 All other Agents have some sort of 'todo' message save feature."
3686   ;;  VM after sending, keeps the corresponding mail
3687   ;;  buffer which implies that the dead letter facility
3688   ;;
3689   ;;  Gnus has also Gcc feature; but we can't know if User uses it for mail?
3690   ;;  User may only read News.
3691   (when (featurep 'rmail)
3692     (tinymail-save-dead-mail)))
3693
3694 ;;; ----------------------------------------------------------------------
3695 ;;;
3696 (defun tinymail-save-dead-mail ()
3697   "Save mail buffers to `tinymail-:dead-mail-file' on Emacs exit."
3698   (ti::dolist-buffer-list
3699    (memq major-mode '(mail-mode
3700                       message-mode))
3701    'loop-temp-buffers
3702    nil
3703    (progn
3704      ;;  In message.el is possible to "save a draft" in normal manner:
3705      ;;  C-x C-s. If the mail buffer has already been saved, we ignore
3706      ;;  that buffer.
3707      (when (buffer-modified-p)
3708        (set-buffer-modified-p nil)      ;"no changes in this buffer"
3709        (append-to-file
3710         (point-min)
3711         (point-max)
3712         tinymail-:dead-mail-file)))))
3713
3714 ;;}}}
3715
3716 ;;{{{ Email notification (old Dragbar Time package)
3717
3718 ;;; ...................................................... &reportmail ...
3719
3720 ;;; ----------------------------------------------------------------------
3721 ;;;
3722 (defun tinymail-report-get-email-word (str)
3723   "Return first word, separated by space from STR."
3724   (let* ((word str))                    ;set default
3725     (when (string-match "From \\([^ ]+\\) " str)
3726       (setq word (substring str (match-beginning 1)  (match-end 1))))
3727     word))
3728
3729 ;;; ----------------------------------------------------------------------
3730 ;;;
3731 (defun tinymail-report-break-email (str)
3732   "Break email STR into two words.
3733 Return:
3734    (ACCOUNT SITE)  or nil"
3735   (let* (w1
3736          w2
3737          ret)
3738     (when (string-match "[@!]" str)
3739       (setq w1 (substring str 0 (match-beginning 0))
3740             w2 (substring str (1+ (match-beginning 0))))
3741       ;;   Some sites has "from" command that sends the info in format:
3742       ;;     "From site.com!login Mon Feb 26 15:50:18 1996"
3743       ;;
3744       ;;   And not in traditional format
3745       ;;     "From login@site.com Mon Feb 26 15:50:18 1996"
3746       ;;
3747       ;;   We have to swap the order
3748       (cond
3749        ((string-match "!" str)
3750         ;; then swap order, since word1 = site, w2 = account
3751         (setq ret (list w2 w1)))
3752        (t
3753         (setq ret (list w1 w2)))))
3754     ret))
3755
3756 ;;; ----------------------------------------------------------------------
3757 ;;;
3758 (defun tinymail-report-mail-info-1 (shell-call)
3759   "Run SHELL-CALL to get information about arrived mail.
3760
3761 Input:
3762
3763   SHELL-CALL    If string, run `shell-command'.
3764
3765                 If function, call function with no arguments.
3766
3767                 Otherwise eval it.
3768
3769                 The SHELL-CALL must return Mailbox From information
3770                 to current empty buffer. Oldest entries first, newest last.
3771
3772 Return:
3773
3774    list       (line line ..)  Berkeley MBOX 'From ' lines. Oldest first.
3775    nil        No new mail"
3776   (let* ((default-directory  default-directory)
3777          (buffer             (get-buffer-create tinymail-:report-spool-buffer))
3778          (kill-p             (eq tinymail-:report-spool-buffer-control 'kill))
3779 ;;; #todo: not yet used
3780 ;;;      (timeout            tinymail-:report-asychronous-timeout)
3781          (tmp-dir            "/tmp/")
3782          (kill-re            tinymail-:report-mail-kill-line-regexp)
3783          ret)
3784     (unwind-protect
3785         (with-current-buffer buffer
3786           (erase-buffer)
3787           ;; - launch up the process and restore the directory setting
3788           ;; - The output is like:
3789           ;;
3790           ;;   From aa@zig.com Thu May 11 19:05:36 EET 1995
3791           ;;   From bb@zag.com.edu Thu May 11 18:55:59 EET 1995
3792           (setq default-directory tmp-dir)
3793           (cond
3794            ((stringp shell-call)
3795             (shell-command shell-call buffer))
3796            ((fboundp shell-call)
3797             (funcall shell-call))
3798            (t
3799             (eval shell-call)))
3800           (when (stringp kill-re)
3801             (ti::pmin)
3802             (flush-lines kill-re))
3803           (unless (eq (point-min) (point-max)) ;No output ?
3804             ;;  - Now read persons email delimited by spaces
3805             ;;  - Read the last line to get newest mail arrival
3806             (ti::pmin)
3807             (while (not (eobp))
3808               (push (ti::read-current-line) ret)
3809               (forward-line 1))))
3810       ;; Unwind
3811       (when (and kill-p (buffer-live-p (setq buffer (get-buffer buffer))))
3812         (with-current-buffer buffer
3813           (set-buffer-modified-p nil))  ;No confirmations
3814         (kill-buffer buffer)))
3815     ret))
3816
3817 ;;; ----------------------------------------------------------------------
3818 ;;;
3819 (defun tinymail-report-mail-info ()
3820   "Run `tinymail-:report-mail-notify-program'."
3821   (and tinymail-:report-mail-notify-program
3822        (tinymail-report-mail-info-1 tinymail-:report-mail-notify-program)))
3823
3824 ;;; ----------------------------------------------------------------------
3825 ;;;
3826 (defun tinymail-report-get-mail-info-string ()
3827   "Return mail string: last sender and mail count."
3828   (let* ((list          (tinymail-report-mail-info))
3829          (re            tinymail-:report-mail-info-shorten-regexp)
3830          (ret           tinymail-:report-no-mail-string)
3831          last-line
3832          email
3833          count)
3834     (tinymail-debug 'tinymail-report-get-mail-info-string list)
3835     (cond
3836      ((ti::listp list)
3837       (setq count       (length list)
3838             last-line   (car (nreverse list))
3839             email       (tinymail-report-get-email-word last-line))
3840       ;;  does user want shortened version ?
3841       (when (and (stringp re)           ;no regexp
3842                  (string-match re email)
3843                  (setq list (tinymail-report-break-email email)))
3844         (setq email (nth 0 list)))
3845       (setq ret (concat " " email " " (number-to-string count))))
3846      ((not (null list))
3847       (message "TinyMail: *** tinymail-report-mail-info didn't return list")))
3848     ret))
3849
3850 ;;; ----------------------------------------------------------------------
3851 ;;;
3852 (defun turn-on-tinymail-report-mail (&optional verb)
3853   "Call `tinymail-report-mail-install-maybe'."
3854   (ti::verb)
3855   (tinymail-report-mail-install-maybe verb))
3856
3857 ;;; ----------------------------------------------------------------------
3858 ;;;
3859 (defun turn-off-tinymail-report-mail (&optional verb)
3860   "Call `tinymail-report-mail-install' with prefix argument."
3861   (interactive)
3862   (ti::verb)
3863   (tinymail-report-mail-install 'uninstall verb))
3864
3865 ;;; ----------------------------------------------------------------------
3866 ;;;
3867 (defun tinymail-report-update (&rest args)
3868   "Update mail status information.
3869 Update the frame's status line, or in non-X show the message in echo area.
3870 ARGS are ignored."
3871   (let* ((buffer  (and (stringp tinymail-:report-spool-buffer)
3872                        (get-buffer-create tinymail-:report-spool-buffer)))
3873          (raise   (and buffer
3874                        (eq tinymail-:report-spool-buffer-control 'raise)))
3875          str
3876          mail-info
3877          display-string)
3878     (when buffer
3879       ;;  Save the contents of frame name, e.g. host name only once
3880       (if (and tinymail-:report-window-system
3881                (null tinymail-:report-old-frame-string))
3882           (setq tinymail-:report-old-frame-string
3883                 (ti::compat-set-frame-name nil nil 'get)))
3884       (when (stringp (setq mail-info (tinymail-report-get-mail-info-string)))
3885         (setq tinymail-:report-mail-info-string (format " %s " mail-info))
3886         (setq display-string (eval tinymail-:report-format-string)))
3887       (cond
3888        ;; ..................................................... windowed ...
3889        (tinymail-:report-window-system
3890         (dolist (elt (frame-list)) ;Update frames that are not exluded
3891           (if (not (member
3892                     (ti::compat-set-frame-name nil nil 'get)
3893                     tinymail-:report-keep-intact-list))
3894               (ti::compat-set-frame-name display-string elt))))
3895        ;; ...................................................... non-win ...
3896        ;; Do nothing if this is nil, user doesn't want to see evel "No mail"
3897        ;; message.
3898        ((null mail-info))
3899        ;; ................................................. non-windowed ...
3900        (t
3901         (if (and (stringp tinymail-:report-no-mail-string)
3902                  (not (string= mail-info tinymail-:report-no-mail-string)))
3903             (setq str "Mail: "))
3904         (cond
3905          ((and (not (ti::compat-executing-macro))
3906                ;; printing message while user is in minibuffer
3907                ;; makes it impossible to see what he's doing.
3908                (not (eq (selected-window) (minibuffer-window)))
3909                (sit-for 0.50))
3910           (message "%s%s" (or str "") display-string)
3911           ;;  make sure user sees it
3912           (sleep-for 1)))))
3913       ;; .......................................................... beep ...
3914       ;; Notify about new mail ?
3915       (unless (stringp tinymail-:report-old-mail-info-string)
3916         (setq tinymail-:report-old-mail-info-string
3917               tinymail-:report-no-mail-string))
3918 ;;;    (ti::d!! mail-info tinymail-:report-no-mail-string tinymail-:report-old-mail-info-string "\n")
3919       (when (and (stringp mail-info)
3920                  (stringp tinymail-:report-no-mail-string)
3921                  (not (string= mail-info
3922                                tinymail-:report-no-mail-string))
3923                  (not (string= mail-info
3924                                tinymail-:report-old-mail-info-string)))
3925         (setq tinymail-:report-old-mail-info-string mail-info)
3926         (beep)
3927         (sit-for 0.15)
3928         (beep)
3929         (if raise
3930             (display-buffer buffer)))
3931       (setq tinymail-:report-old-mail-info-string mail-info))))
3932
3933 ;;; ----------------------------------------------------------------------
3934 ;;;
3935 (defun tinymail-report-mail-install (&optional uninstall verb)
3936   "Install or UNINSTALL mail watchdog (report mail).
3937 References:
3938   `tinymail-:report-window-system'"
3939   (interactive "P")
3940   (ti::verb)
3941   (if (featurep 'reportmail)
3942       (message "\
3943 TinyMail: tinymail-report-mail-install: 'reportmail feature found, install ignored.")
3944     (let* (process-connection-type)     ;Nicer process communication
3945       (if tinymail-:display-time
3946           (display-time))) ;; time.el
3947     ;; In XEmacs the frame must be configured by hand
3948     (when (and tinymail-:report-window-system (ti::xemacs-p))
3949       ;;  make sure it's list
3950       (setq frame-title-format (ti::list-make frame-title-format))
3951       (if uninstall
3952           (delete 'tinymail-:report-mail-info-string frame-title-format)
3953         (pushnew 'tinymail-:report-mail-info-string
3954                  frame-title-format
3955                  :test 'equal)))
3956     ;; Delete old timer
3957     (ti::compat-timer-cancel-function 'tinymail-report-update)
3958     (setq tinymail-:report-timer-object nil)
3959     (unless uninstall
3960       (setq tinymail-:report-timer-object
3961             (run-at-time "1 min" (* 60 10) 'tinymail-report-update)))
3962     (when verb
3963       (message "TinyMail: Report mail feature is %s"
3964                (if uninstall
3965                    "OFF"
3966                  "ON")))))
3967
3968 ;;; ----------------------------------------------------------------------
3969 ;;;
3970 (defun tinymail-report-mail-install-maybe (&optional uninstall verb)
3971   "Don't call `tinymail-report-mail-install' if there already exists reporter.
3972 E.g. in XEmacs you can use package reportmail.el."
3973   (interactive "P")
3974   (ti::verb)
3975   (cond
3976    ;;  #todo: Any other report features we should check?
3977    ((featurep 'reportmail)
3978     (message "TinyMail: reportmail.el present, not installing."))
3979    (t
3980     (tinymail-report-mail-install uninstall verb))))
3981
3982 ;;}}}
3983 ;;{{{ From-address generator (sendmail PLUS emulation)
3984
3985 ;;; ----------------------------------------------------------------------
3986 ;;;
3987 (defun tinymail-from-anti-ube-maybe ()
3988   "Return anti-ube address if `newsgroups' match `tinymail-:from-anti-ube-regexp'"
3989   (when (and (stringp tinymail-:from-anti-ube-regexp)
3990              (stringp user-mail-address)
3991              user-mail-address)
3992     (let* ((group (mail-fetch-field "Newsgroups")))
3993       (when (string-match tinymail-:from-anti-ube-regexp
3994                           (or group ""))
3995         ;;  - Because the anti-ube returns different email every
3996         ;;    time it is called, cache the first value.
3997         ;;  - The changing value would otherwise cause indication
3998         ;;    "Headers have changed".
3999         (make-local-variable 'tinymail-:user-mail-address)
4000         (let ((addr (or tinymail-:user-mail-address
4001                         (ti::mail-email-make-anti-spam-address
4002                          user-mail-address))))
4003           (setq tinymail-:user-mail-address addr)
4004           addr)))))
4005
4006 ;;; ----------------------------------------------------------------------
4007 ;;;
4008 (defun tinymail-from-field-value-plus ()
4009   "Return special plus address emulation (RFC Comment)."
4010   (let* ((fid      "tinymail-from-field-value-plus:")
4011          (news     (mail-fetch-field "newsgroups"))
4012          (prefixes  tinymail-:from-table-prefix)
4013          (postfixes tinymail-:from-table-postfix)
4014          ;;  Posting from Gnus, so get the Group name
4015          ;;
4016          ;;   backend:mail.xxx --> mail.xxx
4017          (grp      (ti::mail-news-group))
4018          (group    (and (stringp grp)
4019                         (eq major-mode 'message-mode)
4020                         (or (ti::string-match ":\\(.*\\)" 1 grp)
4021                             grp)))
4022          prefix
4023          postfix
4024          ret
4025          msg-postfix
4026          condition)
4027     (unless fid ;; No-op. XEmacs byte compiler silencer
4028       (setq fid nil))
4029     (cond
4030      ;; ............................................... news followup ...
4031      (news
4032       ;;  Direct some mesages to my "mail" group, others to
4033       ;;  general usenet group.
4034       (setq prefix  (cdr-safe (ti::list-find prefixes news )))
4035       (setq postfix "")) ;; (cdr-safe (ti::list-find prefixes news )))
4036      (group
4037       ;; If posting from inside Group, add Group based PLUS address
4038       ;;
4039       ;;  list.xxx --> '("list" "xxx")
4040       ;;  For Imap folders; INBOX.list.foo => list.foo
4041       (when (string-match "INBOX\\.\\(.+\\)" group)
4042         (setq group (match-string 1 group)))
4043       (setq prefix  (ti::string-match "^\\([^.]+\\)" 1 group)
4044             postfix (ti::string-match "\\.\\(.+\\)"  1 group))))
4045     ;; ................................ according to message content ...
4046     (setq msg-postfix
4047           (save-excursion
4048             (dolist (elt postfixes)
4049               (ti::mail-text-start 'move)
4050               (setq condition (car elt))
4051               (when (if (stringp condition)
4052                         (re-search-forward condition nil t)
4053                       (setq ret (funcall condition)))
4054                 (unless ret
4055                   (tinymail-debug fid 'point (point) 'LOOP-SELECT elt )
4056                   (setq ret (cdr elt)))
4057                 (return)))
4058             ret))
4059     ;; ............................................... guess mail type ...
4060     ;; If not yet set, look at message and decide right postfix
4061     (setq ret
4062           (cond
4063            (msg-postfix                 ;Always obey this
4064             msg-postfix)
4065            ((and (ti::nil-p prefix) postfix)
4066             postfix)
4067            ((and (ti::nil-p postfix) prefix)
4068             prefix)
4069            ((and prefix postfix)
4070             (concat prefix "." postfix))))
4071     (tinymail-debug fid 'ret ret
4072                     'NEWS           news
4073                     'GROUP          group
4074                     'msg-postfix    msg-postfix
4075                     'prefix prefix 'postfix postfix )
4076     ret))
4077
4078 ;;; ----------------------------------------------------------------------
4079 ;;;
4080 (defun tinymail-from-field-value ()
4081   "Make From Address.
4082
4083 References:
4084
4085   `user-full-name'
4086   `user-mail-address'
4087   `tinymail-:from-info-function'
4088   `tinymail-:from-field-plus-separator'"
4089   (interactive)
4090   (let* ((fid      "tinymail-from-field-value:")
4091          (separator tinymail-:from-field-plus-separator)
4092          (info     (and (fboundp tinymail-:from-info-function)
4093                         (funcall tinymail-:from-info-function)))
4094          (address   (or (and (listp info)
4095                              (nth 0 info))
4096                         (tinymail-from-anti-ube-maybe)
4097                         (or (stringp user-mail-address)
4098                             (error
4099                              "TinyMail: Please set `user-mail-address'."))))
4100          (name      (or (user-full-name)
4101                         (error "TinyMail: Please set `user-full-name'")))
4102          (plus      (or (and (listp info)
4103                              (nth 1 info))
4104                         (tinymail-from-field-value-plus)))
4105          localpart
4106          domain
4107          ret)
4108     (unless fid ;; No-op. XEmacs byte compiler silencer
4109       (setq fid nil))
4110     ;;   With procmail you can have plus addresses:
4111     ;;
4112     ;;      login+additional-info@site.com
4113     ;;
4114     ;;   But you can accomplish the same with RFC comment syntax
4115     ;;
4116     ;;     login@site.com (Foo Bar+additional-info)
4117     ;;
4118     ;;   The extra "+" is just added there to mark that this is
4119     ;;   PLUS addess.
4120     (cond
4121      ((and (not (ti::nil-p plus))
4122            (not (ti::nil-p name)))
4123       (setq plus (format " (%s%s%s)" name separator plus )))
4124      ((not (ti::nil-p name))      ;If no plus info, use normal address
4125       (setq plus (format " (%s)" name))))
4126     (when (stringp address)
4127       (setq localpart (or (ti::string-match "^[^@]+" 0 address) "")
4128             domain    (or (ti::string-match "@.*"    0 address) ""))
4129       ;;  RFC 1036/2.1.1 Says that following address formats are preferred in
4130       ;;  USENET posts
4131       ;;
4132       ;;  From: mark@cbosgd.ATT.COM
4133       ;;  From: mark@cbosgd.ATT.COM (Mark Horton)
4134       ;;  From: Mark Horton <mark@cbosgd.ATT.COM>
4135       (setq ret (format "%s%s%s" localpart  domain  (or plus "") )))
4136     (tinymail-debug fid
4137                     'info-function tinymail-:from-info-function
4138                     'info info
4139                     'localpart localpart
4140                     'domain domain
4141                     'name
4142                     'RETURN ret)
4143     ret))
4144
4145 ;;; ----------------------------------------------------------------------
4146 ;;;
4147 (defun tinymail-message-disable-sender ()
4148   "Disable Sender field generation permanently."
4149   ;; Gnus message-mode
4150   ;; Don't generate Sender address, but trust From address
4151   (interactive)
4152   (when (boundp 'message-syntax-checks)
4153     (let* ((syntaxes  (and (boundp 'message-syntax-checks)
4154                            (symbol-value 'message-syntax-checks)))
4155            ;; Gnus Group / Agent J S  comamdn set this to
4156            ;; value 'dont-check-for-anything-just-trust-me
4157            ;; => skip any checks
4158            (list-p    (ti::listp syntaxes))
4159            (pointer   (and list-p
4160                            (assq 'sender syntaxes))))
4161       (when list-p
4162         (if pointer
4163             (setcdr pointer 'disabled)
4164           (add-to-list 'message-syntax-checks '(sender . disabled)))))))
4165
4166 ;;}}}
4167
4168 ;;{{{ code: Cc, X-Sender-Info
4169
4170 ;;; ............................................................. &fld ...
4171
4172 (defun tinymail-field-cc-kill-by-regexp ()
4173   "Kill entry from CC field that match `my-:email-regexp'"
4174   ;;  don't touch CC field if user has put two spaces in front.
4175   (when (and (mail-fetch-field "CC")
4176              (not (tinymail-field-off-p "CC"))
4177              (stringp tinymail-:cc-kill-regexp))
4178     (let* ((cc      (mail-fetch-field "cc"))
4179            (cc-list (and cc (split-string cc ",[ \t\n]*")))
4180            (count   0)
4181            ccl)
4182       (when cc-list
4183         (dolist (elt cc-list)
4184           (unless (string-match tinymail-:cc-kill-regexp elt)
4185             (incf count)
4186             (setq ccl (format "%s\n  %s," (or ccl "")  elt))))
4187
4188         (when (and cc-list  (not (stringp ccl)))
4189           (ti::mail-kill-field "^CC")) ;All CC memebers killed. Wipe field
4190
4191         (when (and (stringp ccl)
4192                    (not (eq count (length cc-list)))) ;items removed
4193           ;; delete leading \n and trailing comma
4194           (setq ccl (substring ccl 1 (1- (length ccl))))
4195           (ti::mail-kill-field "^CC" ccl)
4196           ;;  we did something
4197           t)))))
4198
4199 ;;; ----------------------------------------------------------------------
4200 ;;;
4201 (defun tinymail-field-in-to-cc-p ()
4202   "Check if point is at field To, Bcc, Cc."
4203   (and (< (point) (ti::mail-hmax))
4204        (save-excursion
4205          (and (ti::mail-next-field-start 'move 'back)
4206               (looking-at "CC\\|BCC\\|To")))))
4207
4208 ;;; ----------------------------------------------------------------------
4209 ;;;
4210 (defun tinymail-field-to-move-maybe ()
4211   "Move cursor to the end of TO field if it is empty."
4212   (when (save-excursion (beginning-of-line) (looking-at "To: *$"))
4213     (end-of-line)
4214     (tinymail-debug "TO-move:" (ti::read-current-line) (point))))
4215
4216 ;;}}}
4217 ;;{{{ code: Fcc handling
4218
4219 ;;; ......................................................... &fld-fcc ...
4220
4221 ;;; ----------------------------------------------------------------------
4222 ;;;
4223 (defun tinymail-field-fcc-determine (&optional type hsize)
4224   "Look if default folder must be changed.
4225 Tries to find RE given in `tinymail-:table-fcc' by looking at header area.
4226
4227 Input:
4228
4229   TYPE      nil: Find Fcc folder. 'gcc: Find Gcc folder.
4230   HSIZE     The header size precalculated.
4231
4232 Return:
4233
4234    string   suggested folder
4235    nil"
4236   (let* ((fid   "tinymail-field-fcc-determine: ")
4237          (ptr   (if type
4238                     tinymail-:table-gcc
4239                   tinymail-:table-fcc))
4240          (sym    (if type 'gcc 'fcc))
4241          (get-hsize (get 'tinymail-:table-fcc 'hsize))
4242          (get-sym   (get 'tinymail-:table-fcc sym))
4243          hmax
4244          ret
4245          re
4246          folder)
4247     (unless fid ;; No-op. XEmacs byte compiler silencer
4248       (setq fid nil))
4249     (or hsize
4250         (setq hsize  (ti::mail-header-area-size)))
4251     (tinymail-debug fid "IN"
4252                     "SYM"        sym
4253                     "HSIZE"      hsize
4254                     "GET HSIZE"  get-hsize
4255                     "GET SYM"    get-sym)
4256     (when (not (and (eq hsize get-hsize)
4257                     ;;   Previous folder value
4258                     (setq ret get-sym)))
4259       ;; Header are has changed; calculate new field and update values
4260       (put 'tinymail-:table-fcc 'hsize hsize)
4261       (put 'tinymail-:table-fcc sym nil)
4262       (when (setq hmax (ti::mail-hmax)) ;header end must be found
4263         (save-excursion
4264           (ti::pmin)
4265           (dolist (elt ptr)
4266             (setq re     (nth 0 elt)
4267                   folder (nth 1 elt))
4268             (when (re-search-forward re hmax t)
4269               (setq ret folder)
4270               (return)))))
4271       (if (and (stringp ret)
4272                (string-match "gz$\\|Z$" ret))
4273           (ti::use-file-compression))
4274       (put 'tinymail-:table-fcc sym ret)
4275       (tinymail-debug  fid "SET hmax" hmax "ret" ret  "re" re))
4276     ret))
4277
4278 ;;; ----------------------------------------------------------------------
4279 ;;;
4280 (defun tinymail-field-fcc (&optional type hsize)
4281   "Set right [GF]cc folder if there is match in `tinymail-:table-[gf]cc'.
4282
4283 Input:
4284
4285   TYPE      nil: Find Fcc folder. 'gcc: Find Gcc folder.
4286   HSIZE     The header size precalculated."
4287   (let* ((fid    "tinymail-field-fcc: ")
4288          fld
4289          sym
4290          str
4291          folder
4292          prev)
4293     (unless fid ;; No-op. XEmacs byte compiler silencer
4294       (setq fid nil))
4295     (tinymail-debug fid "in TYPE" type "HSIZE"  hsize)
4296     (or hsize
4297         (setq hsize (ti::mail-header-area-size)))
4298     (cond
4299      (type (setq fld "GCC"  sym 'gcc-old))
4300      (t    (setq fld "FCC"  sym 'fcc-old)))
4301     (setq prev   (get 'tinymail-:table-fcc sym))
4302     (setq folder (tinymail-field-fcc-determine type hsize))
4303     (tinymail-debug fid fld type hsize
4304                     "PREV-FLD"  prev
4305                     "FLD"       folder
4306                     "CHECK"     (if folder
4307                                     (ti::re-search-check (regexp-quote folder)))
4308                     "MODE"      major-mode)
4309
4310     (when (and (stringp folder)
4311                (or (not (stringp prev))
4312                    (not (ti::re-search-check (regexp-quote folder)))
4313                    (not (string-match (regexp-quote prev) folder)))
4314                (setq str (mail-fetch-field fld))
4315                (not (tinymail-field-off-p nil str)))
4316       (put 'tinymail-:table-fcc sym folder)
4317       (ti::save-line-column-macro nil nil
4318         (tinymail-debug fid "SET" folder (current-buffer))
4319         (ti::mail-kill-field (concat "^" fld) folder)))))
4320
4321 ;;; ----------------------------------------------------------------------
4322 ;;;
4323 (defun tinymail-field-to-off (&optional count field)
4324   "Disable TinyMail by space COUNT for FIELD.
4325 2  spaces disables Cc tracking
4326 3  spaces disables both Cc and other tracking."
4327   (let* (str)
4328     (setq str (make-string (or count 2) ?\ ))
4329     (setq field (or field "to"))
4330     (tinymail-debug "tinymail-field-to-off, count, field" count field)
4331     (save-excursion
4332       (ti::pmin)
4333       (when (re-search-forward
4334              (concat "^" field ":\\([ \t]*\\)")
4335              (ti::mail-hmax)
4336              t)
4337         (if (match-beginning 1)
4338             (ti::replace-match 1 str)   ;There is spaces
4339           (insert str))                 ;There is no spaces
4340         t))
4341     (tinymail-field-to-move-maybe)))
4342
4343 ;;; ----------------------------------------------------------------------
4344 ;;;
4345 (defun tinymail-field-to-on ()
4346   "Keep activated by making sure the To: field has only one space."
4347   (tinymail-debug "tinymail-field-to-on")
4348   (tinymail-field-to-off 1))
4349
4350 ;;; ----------------------------------------------------------------------
4351 ;;;
4352 ;;;###autoload
4353 (defun tinymail-on-off-toggle (&optional arg)
4354   "Toggle TinyMail mode on and off by Changing spacing of To field.
4355 This affects automatic Cc and X-Sender-Info tracking.
4356 ARG behaves  like mode argument.
4357
4358 Without arg, this toggless Cc tracking, with prefix argument,
4359 it toggless both Cc and X-Sender-Info tracking."
4360   (interactive "P")
4361   (if arg
4362       (setq arg 3))                     ;3 spaces turn off completely.
4363   (if (null arg)
4364       (cond
4365        ((save-excursion
4366           (ti::pmin)
4367           (re-search-forward "^to:  " nil t))
4368         (tinymail-field-to-on)
4369         (message "TinyMail: mail field tracking mode on."))
4370        (t
4371         (tinymail-field-to-off arg)
4372         (message "TinyMail: mail field tracking mode off.")))
4373     (tinymail-field-to-off arg)
4374     (message "TinyMail: mail field tracking mode off.")))
4375
4376 ;;}}}
4377 ;;{{{ code: citation
4378
4379 ;;; ----------------------------------------------------------------------
4380 ;;;
4381 (defun tinymail-iso8601-date-value ()
4382   "Read Date field and return ISO 8601 date: WEEKDAY YYYY-MM-DD."
4383   (let* ((date "")
4384          yyyy
4385          mm
4386          dd
4387          week-day)
4388     (when (setq date (or (mail-fetch-field "date")
4389                          (and (featurep 'message)
4390                               message-reply-headers
4391                               (mail-header-date message-reply-headers))))
4392       (setq date (ti::date-parse-date date))
4393
4394       (setq yyyy     (nth 0 date)
4395             mm       (nth 1 date)
4396             dd       (nth 2 date)
4397             week-day (nth 4 date))
4398
4399       (setq date
4400             (format "%s%s%s%s"
4401                     (or week-day "")
4402                     (if yyyy             (concat (if week-day " " "") yyyy))
4403                     (if (and yyyy mm)    (concat "-" mm) "")
4404                     (if (and yyyy mm dd) (concat "-" dd) ""))))
4405     date))
4406
4407 ;;; ----------------------------------------------------------------------
4408 ;;;
4409 (defun tinymail-citation-who-said (str)
4410   "Formats sender line reference. Input is From/To field.
4411
4412 Return:
4413
4414   str  formatted line, without 'From:'
4415   nil  if cannot format"
4416   (let ((limit (- 75 13))              ; line-lenght - date  ==> limit
4417         ;;  Get the group name only when posting from GNUS
4418         ;;  gnus-group-real-name
4419         (grp        (replace-regexp-in-string
4420                      ".*:" "" (or (ti::mail-news-group) "")))
4421         list
4422         fn                              ; first name
4423         sn                              ; surname
4424         email
4425         ret)
4426     (setq ret str)
4427     (when ret
4428       ;;  Remove quotes: "Mr. this" <email@example.com>
4429       (setq ret (replace-regexp-in-string "['\"]+" "" ret))
4430       ;;  Remove Middle names: Foo X. Bar
4431       (setq ret (replace-regexp-in-string " [A-Z]\\." "" ret)))
4432     ;;   If the line is exessive long, say;
4433     ;;   "Mr. Foo the most spectacular..." <foo@camel.com>
4434     ;;   Then we make it smaller.
4435     (when (> (length ret) limit)
4436       ;;  Get only the email, and drop all others
4437       (setq list  (ti::mail-parse-name str))
4438       (setq email (or (car-safe (ti::mail-email-from-string str)) ""))
4439       (when list
4440         (setq fn (nth 0 list)   sn (nth 1 list)) ;; first/surname
4441         ;;   this should suffice
4442         (setq ret (concat fn " " sn " <" email ">"))))
4443     ;;  Does the group name fit in too ?
4444     (if (and grp
4445              (< (+ (length ret) (length grp) 1) limit))
4446         (setq ret (concat ret " " grp)))
4447     ret))
4448
4449 ;;; ----------------------------------------------------------------------
4450 ;;;
4451 (defun tinymail-message-id-value ()
4452   "Return Google group url."
4453   (let ((id (mail-fetch-field "References")))
4454     (and id
4455          ;;  There are several Message-Id's in a thread. Pick latest.
4456          (setq id  (car (nreverse (split-string id))))
4457          (ti::string-match "<\\([^ \t\n>]+\\)>" 1 id))))
4458
4459 ;;; ----------------------------------------------------------------------
4460 ;;;
4461 (defun tinymail-url-reference-google-group ()
4462   "Return Google group url."
4463   (let* ((id     (tinymail-message-id-value))
4464          (group  (and id
4465                       (mail-fetch-field "Newsgroups"))))
4466     (when (and (stringp group)
4467                ;;  See http://groups.google.com/
4468                (string-match
4469                 group
4470                 (concat
4471                  "^\\(alt\\|biz\\|comp\\|humanities"
4472                  "\\|misc\\|news\\|rec\\|sci\\|soc\\|talk")))
4473       (concat
4474        ;; Old format was
4475        ;;  http://search.dejanews.com/msgid.xp
4476        ;;  ?MID=%3C3cgd8m0w.fsf@blue.sea.net%3E&format=threaded
4477        ;;
4478        "<http://groups.google.com/groups?oi=djq"
4479        "&as_umsgid=%3C"
4480        id
4481        ">"))))
4482
4483 ;;; ----------------------------------------------------------------------
4484 ;;;
4485 (defun tinymail-url-reference-mailing-list ()
4486   "Return maling list URL refence."
4487   (when (or (ti::mail-to-list-p)
4488             (string-match
4489              "^gmane"
4490              (or (mail-fetch-field "Newsgroups") "")))
4491     (let* ((id (tinymail-message-id-value)))
4492       (concat "Message-Id: " id))))
4493
4494 ;;; ----------------------------------------------------------------------
4495 ;;;
4496 (defun tinymail-message-id ()
4497   "Return message id or empty.
4498 This function works best with Gnus:
4499
4500 - Mailing lists replies contain Message-Id reference.
4501   The mailing list status is indicated by Gnus group property `to-list'.
4502 - Newsgroup replies contain URL reference.
4503 - Private mail does _not_ include any extra references."
4504   (let* ((url  (or (tinymail-url-reference-google-group)
4505                    (tinymail-url-reference-mailing-list))))
4506     (when (stringp url)
4507       (concat "* " url "\n"))))
4508
4509 ;;; ----------------------------------------------------------------------
4510 ;;;
4511 (defun tinymail-message-citation-line-function ()
4512   "Generate citation line.
4513
4514     * Tue YYYY-MM-DD John Doe <johnd@example.com> mail.emacs
4515     * Message-Id: <......>
4516     | Thankyou for helping me...
4517     | ...
4518
4519 It is important to include the Message-Id reference because then it is
4520 possible to retrieve whole News thread e.g. from GOOGLE group. Message-Id is
4521 a handly way to refer to past articles."
4522   (let* ((hdrs (if (boundp 'message-reply-headers)
4523                    (symbol-value 'message-reply-headers)))
4524          (from (and hdrs (tinymail-citation-who-said
4525                           (mail-header-from hdrs))))
4526          (date (tinymail-iso8601-date-value)))
4527     (delete-horizontal-space)
4528     (insert "* " date " " from "\n")
4529     (let ((id (funcall tinymail-:citation-message-id-function)))
4530       (when (stringp id)
4531         (insert id)))))
4532
4533 ;;; ----------------------------------------------------------------------
4534 ;;;
4535 (defun tinymail-citation-generate ()
4536   "Write reference line."
4537   (if (eq major-mode 'message-mode)
4538       (tinymail-message-citation-line-function)
4539     (tinymail-citation-who-said (mail-fetch-field "From"))))
4540
4541 ;;}}}
4542 ;;{{{ GPG + BBDB
4543
4544 (defun tinymail-gpg-recipient ()
4545   "Check BBDB field gnus-pgp for 'sign' and 'encrypt'."
4546   (when (and (eq (major-mode 'message-mode)
4547                  (featurep 'bbdb)))
4548     (when (and (not message-has-gpg)
4549                (message-mail-p))
4550       (let* ((to-field      (mail-fetch-field "to"))
4551              (components    (mail-extract-address-components to_field t))
4552              recipient)
4553         (when (= (length components) 1)
4554           ;; Only a single recipient
4555           (setq recipient (nth 1 (car components)))
4556           (let* ((record (bbdb-search-simple nil recipient))
4557                  gpg)
4558             (when record
4559               (setq gpg (bbdb-get-field record 'gnus-gpg)))
4560             (when (> (length gpg) 0)
4561               (cond
4562                ((string= gpg "sign")
4563                 (mml-secure-message-sign-pgpmime))
4564                ((string= gpg "encrypt")
4565                 (mml-secure-message-encrypt-pgpmime))))))))))
4566
4567 ;;}}}
4568 ;;{{{ code: main
4569
4570 ;;; ----------------------------------------------------------------------
4571 ;;;
4572 (defun tinymail-from-set-field (&optional from-field)
4573   "Check FROM-FIELD and set From: unless it has two spaces in front."
4574   (save-excursion
4575     (let ((from (or from-field
4576                     (ti::mail-get-field-1 "From")))
4577           str)
4578       (tinymail-debug fid 'initial-from from-field)
4579       (cond
4580        ;;  The field was there, if there in NO two spaces, replace
4581        ;;  the content with new dynamic value
4582        ;;
4583        ;;  If used puts two spaces at from, he want to modify
4584        ;;  the field himself
4585        ((and (stringp from)
4586              (string-match "^  " from-field))
4587         (tinymail-debug fid 'from-disabled-space))
4588        ((stringp (setq str (tinymail-from-field-value)))
4589         ;;  Will create if not exists.
4590         (mail-position-on-field "From")
4591         (ti::mail-kill-field "^From" str))))))
4592
4593 ;;; ----------------------------------------------------------------------
4594 ;;;
4595 (defun tinymail-process-1 (&optional force)
4596   "See `tinymail-process'. If FORCE is non-nil, run immediately.
4597 This function should be called interactive only when debugging errors:
4598 C-u M-x tinymail-process-1."
4599   (interactive "P")
4600   (let* ((fid           "tinymail-process: ")
4601          (last-to       tinymail-:last-to-field)
4602          (alias-alist   (tinymail-mail-aliases))
4603          to
4604          from
4605          hsize
4606          ohsize)
4607     (unless fid ;; No-op. XEmacs byte compiler silencer
4608       (setq fid nil))
4609     ;;  - If "To:" field content has two spaces at front, this is signal
4610     ;;    to stay away.
4611     ;;
4612     ;;  - If To address has remained the same, we do nothing.
4613     ;;    If we would aways go into expanding and killing the Cc,Fcc
4614     ;;    fields blindly, user would notice that while he was
4615     ;;    writing his message. Avoid that as much as posisble
4616
4617     (setq to    (ti::remove-properties
4618                  (or (ti::mail-get-field-1 "To")
4619                      (ti::mail-get-field-1 "newsgroups")))
4620           from        (ti::remove-properties (ti::mail-get-field-1 "From"))
4621           hsize (ti::remove-properties (ti::mail-header-area-size))
4622           ohsize        (get 'tinymail-:process-hook 'old-hsize))
4623     ;;  Record the values now so that they aren't calculated any more
4624     (put 'tinymail-:process-hook 'from from)
4625     (put 'tinymail-:process-hook 'to to)
4626     (put 'tinymail-:process-hook 'new-hsize hsize)
4627     (tinymail-debug fid "TO"    to
4628                     "LAST-TO"   last-to
4629                     "POST-hook" tinymail-:process-hook
4630                     "hsize"     hsize ohsize
4631                     "MODE"      major-mode)
4632     (cond
4633      ((or (not (stringp to))
4634           (tinymail-field-off-p nil to))
4635       nil)                              ;flag DISABLED
4636      ((and (not (string= to last-to))   ;not same as previously ?
4637            (not (ti::nil-p to)))
4638       (save-excursion
4639         (ti::mail-abbrev-expand-mail-aliases
4640          (point-min) (ti::mail-hmax) alias-alist))
4641       ;;  what was expanded
4642       (setq to (ti::mail-get-field "To"))))
4643     (if tinymail-:from-field-enable-flag
4644         (tinymail-from-set-field from))
4645     ;; ............................................ header changed ...
4646     (tinymail-field-cc-kill-by-regexp)
4647     (when (not (eq hsize ohsize)) ;;  Handle dynamic save to folders
4648       (tinymail-field-fcc nil hsize)
4649       (tinymail-field-fcc 'gcc hsize)
4650       (tinymail-debug
4651        fid
4652        "Running post hook"      tinymail-:process-hook
4653        "MODE"                   major-mode)
4654       (setq tinymail-:last-to-field to) ;update
4655       (tinymail-debug fid "END" "MODE" major-mode))
4656     ;;  User's things now
4657     (run-hooks 'tinymail-:process-hook)))
4658
4659 ;;; ----------------------------------------------------------------------
4660 ;;;
4661 (defun tinymail-process-run-p ()
4662   "Return t if `tinymail-process' is allowed to run."
4663   (and (get-buffer-window (current-buffer))
4664        ;;  If buffer is not displayed, do nothing.
4665        (not buffer-read-only)
4666        (ti::mail-mail-p)
4667        tinymail-mode))
4668
4669 ;;; ----------------------------------------------------------------------
4670 ;;;
4671 (defun tinymail-process (&optional force)
4672   "Expand mail aliases and inserts additional info.
4673
4674 optional FORCE argument causes running post hook now.
4675
4676 If you take advantage of the `tinymail-:process-hook', please remember
4677 following
4678
4679 - Your hook must run as fast as possible so that it won't disturb
4680   writing the text.
4681 - You can peek contents of the precaculated values instead of reading
4682   then again in the buffer
4683
4684         (get 'tinymail-:process-hook 'to to)      ;; To field content
4685         (get 'tinymail-:process-hook 'new-hsize)  ;; Header size now
4686         (get 'tinymail-:process-hook 'old-hsize)  ;; old header size"
4687   (when (or force
4688             (and (tinymail-process-run-p)
4689                  ;; If this doesn't look like mail, don't bother
4690                  (ti::mail-mail-p)))
4691     (condition-case error
4692         (tinymail-process-1 force)
4693       (error
4694        (message "TinyMail: post-command error: %s
4695 Spot the error by turning on Emacs debug and calling
4696 (tinymail-process-1 'force) or C-u M-x tinymail-process-1"
4697                 (prin1-to-string error))
4698        (ding)
4699        (sit-for 5)                      ;Make sure user notices this ;
4700        (message "TinyMail: (error watch) Please Check *Messages* buffer.")))))
4701
4702 ;;}}}
4703
4704 (provide   'tinymail)
4705 (run-hooks 'tinymail-:load-hook)
4706
4707 ;;; tinymail.el ends here