1 ;;; vm-pcrisis.el --- wide-ranging auto-setup for personalities in VM
3 ;; Copyright (C) 1999 Rob Hodges,
4 ;; 2006 Robert Widhopf, Robert P. Goldman
6 ;; Package: Personality Crisis for VM
9 ;; Maintainer: Robert Widhopf-Fenk <hack@robf.de>
10 ;; X-URL: http://www.robf.de/Hacking/elisp
12 ;; This program is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
17 ;; This program is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with this program; if not, you can either send email to this
24 ;; program's maintainer or write to: The Free Software Foundation,
25 ;; Inc.; 59 Temple Place, Suite 330; Boston, MA 02111-1307, USA.
31 ;; Documentation is now in Texinfo and HTML formats. You should have
32 ;; downloaded one or the other along with this package at the URL
42 ;; get the macros we need.
51 (message "Could not load bbdb.el. Related functions may not work correctly!")
54 ;; -------------------------------------------------------------------
56 ;; -------------------------------------------------------------------
57 (defconst vmpc-version "0.9.1"
58 "Version of pcrisis.")
61 "Manage personalities and more in VM."
64 (defcustom vmpc-conditions ()
65 "*List of conditions which will be checked by pcrisis."
68 (defcustom vmpc-actions ()
70 Actions are associated with conditions from `vmpc-conditions' by one of
71 `vmpc-actions-alist', `vmpc-reply-alist', `', `vmpc-forward-alist',
72 `vmpc-resend-alist', `vmpc-newmail-alist' or `vmpc-automorph-alist'.
74 These are also the actions from which you can choose when using the newmail
75 features of Personality Crisis, or the `vmpc-prompt-for-profile' action."
76 :type '(repeat (list (string :tag "Action name")
77 (sexp :tag "Condition")))
80 (defun vmpc-alist-set (symbol value)
81 "Used as :set for vmpc-*-alist variables.
82 Checks if the condition and all the actions exist."
84 (let ((condition (caar value))
85 (actions (cdar value)))
86 (if (and condition (not (assoc condition vmpc-conditions)))
87 (error "Condition '%s' does not exist!" condition))
89 (if (not (assoc (car actions) vmpc-actions))
90 (error "Action '%s' does not exist!" (car actions)))
91 (setq actions (cdr actions))))
92 (setq value (cdr value)))
95 (defun vmpc-defcustom-alist-type ()
96 "Generate :type for vmpc-*-alist variables."
99 (append '(choice :tag "Condition")
100 (mapcar (lambda (c) (list 'const (car c))) vmpc-conditions)
102 (list 'repeat :tag "Actions to run"
103 (append '(choice :tag "Action")
104 (mapcar (lambda (a) (list 'const (car a))) vmpc-actions)
107 (defcustom vmpc-actions-alist ()
108 "*An alist associating conditions with actions from `vmpc-actions'.
109 If you do not want to map actions for each state, e.g. for replying, forwarding,
110 resending, composing or automorphing, then set this one."
111 :type (vmpc-defcustom-alist-type)
112 ; :set 'vmpc-alist-set
115 (defcustom vmpc-reply-alist ()
116 "*An alist associating conditions with actions from `vmpc-actions' when replying."
117 :type (vmpc-defcustom-alist-type)
118 ; :set 'vmpc-alist-set
121 (defcustom vmpc-forward-alist ()
122 "*An alist associating conditions with actions from `vmpc-actions' when forwarding."
123 :type (vmpc-defcustom-alist-type)
124 ; :set 'vmpc-alist-set
127 (defcustom vmpc-automorph-alist ()
128 "*An alist associating conditions with actions from `vmpc-actions' when automorphing."
129 :type (vmpc-defcustom-alist-type)
130 ; :set 'vmpc-alist-set
133 (defcustom vmpc-newmail-alist ()
134 "*An alist associating conditions with actions from `vmpc-actions' when composing."
135 :type (vmpc-defcustom-alist-type)
136 ; :set 'vmpc-alist-set
139 (defcustom vmpc-resend-alist ()
140 "*An alist associating conditions with actions from `vmpc-actions' when resending."
141 :type (vmpc-defcustom-alist-type)
142 ; :set 'vmpc-alist-set
145 (defcustom vmpc-auto-profiles-file "~/.vmpc-auto-profiles"
146 "*File in which to save information used by `vmpc-prompt-for-profile'.
147 When set to the symbol 'BBDB, profiles will be stored there."
148 :type '(choice (file)
152 (defcustom vmpc-auto-profiles-expunge-days 100
153 "*Number of days after which to expunge old address-profile associations.
154 Performance may suffer noticeably if this file becomes enormous, but in other
155 respects it is preferable for this value to be fairly high. The value that is
156 right for you will depend on how often you send email to new addresses using
157 `vmpc-prompt-for-profile' (with the REMEMBER flag set to 'always or 'prompt)."
161 (defvar vmpc-current-state nil
162 "The current state of pcrisis.
163 It is one of 'reply, 'forward, 'resend, 'automorph or 'newmail.
164 It controls which actions/functions can/will be run.")
166 (defvar vmpc-current-buffer nil
167 "The current buffer, i.e. 'none or 'composition.
168 It is 'none before running an adviced VM function and 'composition afterward,
169 i.e. when within the composition buffer.")
171 (defvar vmpc-saved-headers-alist nil
172 "Alist of headers from the original message saved for later use.")
174 (defvar vmpc-actions-to-run nil
175 "The actions to run.")
177 (defvar vmpc-true-conditions nil
178 "The true conditions.")
180 (defvar vmpc-auto-profiles nil
181 "The auto profiles as stored in `vmpc-auto-profiles-file'.")
183 ;; An "exerlay" is an overlay in FSF Emacs and an extent in XEmacs.
184 ;; It's not a real type; it's just the way I'm dealing with the damn
185 ;; things to produce containers for the signature and pre-signature
186 ;; which can be highlighted etc. and work on both platforms.
188 (defvar vmpc-pre-sig-exerlay ()
189 "Don't mess with this.")
191 (make-variable-buffer-local 'vmpc-pre-sig-exerlay)
193 (defvar vmpc-sig-exerlay ()
194 "Don't mess with this.")
196 (make-variable-buffer-local 'vmpc-sig-exerlay)
198 (defvar vmpc-pre-sig-face (progn (make-face 'vmpc-pre-sig-face
199 "Face used for highlighting the pre-signature.")
201 'vmpc-pre-sig-face "forestgreen")
203 "Face used for highlighting the pre-signature.")
205 (defvar vmpc-sig-face (progn (make-face 'vmpc-sig-face
206 "Face used for highlighting the signature.")
207 (set-face-foreground 'vmpc-sig-face
210 "Face used for highlighting the signature.")
212 (defvar vmpc-intangible-pre-sig 'nil
213 "Whether to forbid the cursor from entering the pre-signature.")
215 (defvar vmpc-intangible-sig 'nil
216 "Whether to forbid the cursor from entering the signature.")
218 (defvar vmpc-expect-default-signature 'nil
219 "*Set this to 't if you have a signature-inserting function.
220 It will ensure that pcrisis correctly handles the signature .")
223 ;; -------------------------------------------------------------------
224 ;; Some easter-egg functionality:
225 ;; -------------------------------------------------------------------
227 (defun vmpc-my-identities (&rest identities)
228 "Setup pcrisis with the given IDENTITIES."
229 (setq vmpc-conditions '(("always true" t))
230 vmpc-actions-alist '(("always true" "prompt for a profile"))
231 vmpc-actions '(("prompt for a profile" (vmpc-prompt-for-profile 'always))))
235 (list i (list 'vmpc-substitute-header "From" i)))
239 (defun vmpc-header-field-for-point ()
240 "*Return a string indicating the mail header field point is in.
241 If point is not in a header field, returns nil."
243 (unless (save-excursion
244 (re-search-backward (regexp-quote mail-header-separator)
246 (re-search-backward "^\\([^ \t\n:]+\\):")
249 (defun vmpc-tab-header-or-tab-stop (&optional backward)
250 "*If in a mail header field, moves to next useful header or body.
251 When moving to the message body, calls the `vmpc-automorph' function.
252 If within the message body, runs `tab-to-tab-stop'.
253 If BACKWARD is specified and non-nil, moves to previous useful header
254 field, whether point is in the body or the headers.
255 \"Useful header fields\" are currently, in order, \"To\" and
258 (let ((curfield) (nextfield) (useful-headers '("To" "Subject")))
259 (if (or (setq curfield (vmpc-header-field-for-point))
263 (- (length useful-headers)
264 (length (member curfield useful-headers))))
266 (setq nextfield (nth (1- nextfield) useful-headers))
267 (setq nextfield (nth (1+ nextfield) useful-headers)))
269 (mail-position-on-field nextfield)
276 (defun vmpc-backward-tab-header-or-tab-stop ()
277 "*Wrapper for `vmpc-tab-header-or-tab-stop' with BACKWARD set."
279 (vmpc-tab-header-or-tab-stop t))
282 ;; -------------------------------------------------------------------
283 ;; Stuff for dealing with exerlays:
284 ;; -------------------------------------------------------------------
286 (defun vmpc-set-overlay-insertion-types (overlay start end)
287 "Set insertion types for OVERLAY from START to END.
288 In fact a new copy of OVERLAY with different insertion types at START and END
289 is created and returned.
291 START and END should be nil or t -- the marker insertion types at the start
292 and end. This seems to be the only way you of changing the insertion types
293 for an overlay -- save the overlay properties that we care about, create a new
294 overlay with the new insertion types, set its properties to the saved ones.
295 Overlays suck. Extents rule. XEmacs got this right."
296 (let* ((useful-props (list 'face 'intangible 'evaporate)) (saved-props)
297 (i 0) (len (length useful-props)) (startpos) (endpos) (new-ovl))
299 (setq saved-props (append saved-props (cons
300 (overlay-get overlay (nth i useful-props)) ())))
302 (setq startpos (overlay-start overlay))
303 (setq endpos (overlay-end overlay))
304 (delete-overlay overlay)
305 (if (and startpos endpos)
306 (setq new-ovl (make-overlay startpos endpos (current-buffer)
308 (setq new-ovl (make-overlay 1 1 (current-buffer) start end))
309 (vmpc-forcefully-detach-exerlay new-ovl))
312 (overlay-put new-ovl (nth i useful-props) (nth i saved-props))
317 (defun vmpc-set-extent-insertion-types (extent start end)
318 "Set the insertion types of EXTENT from START to END.
319 START and END should be either nil or t, indicating the desired value
320 of the 'start-open and 'end-closed properties of the extent
322 This is the XEmacs version of `vmpc-set-overlay-insertion-types'."
323 ;; pretty simple huh?
324 (set-extent-property extent 'start-open start)
325 (set-extent-property extent 'end-closed end))
328 (defun vmpc-set-exerlay-insertion-types (exerlay start end)
329 "Set the insertion types for EXERLAY from START to END.
330 In other words, EXERLAY is the name of the overlay or extent with a quote in
331 front. START and END are the equivalent of the marker insertion types for the
332 start and end of the overlay/extent."
334 (vmpc-set-extent-insertion-types (symbol-value exerlay) start end)
335 (set exerlay (vmpc-set-overlay-insertion-types (symbol-value exerlay)
339 (defun vmpc-exerlay-start (exerlay)
340 "Return buffer position of the start of EXERLAY."
342 (extent-start-position exerlay)
343 (overlay-start exerlay)))
346 (defun vmpc-exerlay-end (exerlay)
347 "Return buffer position of the end of EXERLAY."
349 (extent-end-position exerlay)
350 (overlay-end exerlay)))
353 (defun vmpc-move-exerlay (exerlay new-start new-end)
354 "Change EXERLAY to cover region from NEW-START to NEW-END."
356 (set-extent-endpoints exerlay new-start new-end (current-buffer))
357 (move-overlay exerlay new-start new-end (current-buffer))))
360 (defun vmpc-set-exerlay-detachable-property (exerlay newval)
361 "Set the 'detachable or 'evaporate property for EXERLAY to NEWVAL."
363 (set-extent-property exerlay 'detachable newval)
364 (overlay-put exerlay 'evaporate newval)))
367 (defun vmpc-set-exerlay-intangible-property (exerlay newval)
368 "Set the 'intangible or 'atomic property for EXERLAY to NEWVAL."
371 (require 'atomic-extents)
372 (set-extent-property exerlay 'atomic newval))
373 (overlay-put exerlay 'intangible newval)))
376 (defun vmpc-set-exerlay-face (exerlay newface)
377 "Set the face used by EXERLAY to NEWFACE."
379 (set-extent-face exerlay newface)
380 (overlay-put exerlay 'face newface)))
383 (defun vmpc-forcefully-detach-exerlay (exerlay)
384 "Leave EXERLAY in memory but detaches it from the buffer."
386 (detach-extent exerlay)
387 (delete-overlay exerlay)))
390 (defun vmpc-make-exerlay (startpos endpos)
391 "Create a new exerlay spanning from STARTPOS to ENDPOS."
393 (make-extent startpos endpos (current-buffer))
394 (make-overlay startpos endpos (current-buffer))))
397 (defun vmpc-create-sig-and-pre-sig-exerlays ()
398 "Create the extents in which the pre-sig and sig can reside.
399 Or overlays, in the case of GNU Emacs. Thus, exerlays."
400 (setq vmpc-pre-sig-exerlay (vmpc-make-exerlay 1 2))
401 (setq vmpc-sig-exerlay (vmpc-make-exerlay 3 4))
403 (vmpc-set-exerlay-detachable-property vmpc-pre-sig-exerlay t)
404 (vmpc-set-exerlay-detachable-property vmpc-sig-exerlay t)
405 (vmpc-forcefully-detach-exerlay vmpc-pre-sig-exerlay)
406 (vmpc-forcefully-detach-exerlay vmpc-sig-exerlay)
408 (vmpc-set-exerlay-face vmpc-pre-sig-exerlay 'vmpc-pre-sig-face)
409 (vmpc-set-exerlay-face vmpc-sig-exerlay 'vmpc-sig-face)
411 (vmpc-set-exerlay-intangible-property vmpc-pre-sig-exerlay
412 vmpc-intangible-pre-sig)
413 (vmpc-set-exerlay-intangible-property vmpc-sig-exerlay
416 (vmpc-set-exerlay-insertion-types 'vmpc-pre-sig-exerlay t nil)
417 (vmpc-set-exerlay-insertion-types 'vmpc-sig-exerlay t nil)
419 ;; deal with signatures inserted by other things than vm-pcrisis:
420 (if vmpc-expect-default-signature
422 (let ((p-max (point-max))
423 (body-start (save-excursion (mail-text) (point)))
426 (setq sig-start (re-search-backward "\n-- \n" body-start t))
428 (vmpc-move-exerlay vmpc-sig-exerlay sig-start p-max))))))
431 ;; -------------------------------------------------------------------
432 ;; Functions for vmpc-actions:
433 ;; -------------------------------------------------------------------
435 (defmacro vmpc-composition-buffer (&rest form)
436 "Evaluate FORM if in the composition buffer.
437 That is to say, evaluates the form if you are really in a composition
438 buffer. This function should not be called directly, only from within
439 the `vmpc-actions' list."
440 (list 'if '(eq vmpc-current-buffer 'composition)
441 (list 'eval (cons 'progn form))))
443 (put 'vmpc-composition-buffer 'lisp-indent-hook 'defun)
445 (defmacro vmpc-pre-function (&rest form)
446 "Evaluate FORM if in pre-function state.
447 That is to say, evaluates the FORM before VM does its thing, whether
448 that be creating a new mail or a reply. This function should not be
449 called directly, only from within the `vmpc-actions' list."
450 (list 'if '(and (eq vmpc-current-buffer 'none)
451 (not (eq vmpc-current-state 'automorph)))
452 (list 'eval (cons 'progn form))))
454 (put 'vmpc-pre-function 'lisp-indent-hook 'defun)
456 (defun vmpc-delete-header (hdrfield &optional entire)
457 "Delete the contents of a HDRFIELD in the current mail message.
458 If ENTIRE is specified and non-nil, deletes the header field as well."
459 (if (eq vmpc-current-buffer 'composition)
462 (mail-position-on-field hdrfield)
464 (setq end (+ (point) 1))
466 (re-search-backward ": ")
468 (setq start (progn (beginning-of-line) (point)))
469 (setq start (+ (point) 2)))
470 (delete-region start end)))))
473 (defun vmpc-insert-header (hdrfield content)
474 "Insert to HDRFIELD the new CONTENT.
475 Both arguments are strings. The field can either be present or not,
476 but if present, HDRCONT will be appended to the current header
478 (if (eq vmpc-current-buffer 'composition)
480 (mail-position-on-field hdrfield)
483 (defun vmpc-substitute-header (hdrfield content)
484 "Substitute HDRFIELD with new CONTENT.
485 Both arguments are strings. The field can either be present or not.
486 If the header field is present and already contains something, the
487 contents will be replaced, otherwise a new header is created."
488 (if (eq vmpc-current-buffer 'composition)
490 (vmpc-delete-header hdrfield)
491 (vmpc-insert-header hdrfield content))))
493 (defun vmpc-add-header (hdrfield content)
494 "Add HDRFIELD with CONTENT if it is not present already.
495 Both arguments are strings.
496 If a header field with the same CONTENT is present already nothing will be
497 done, otherwise a new field with the same name and the new CONTENT will be
498 added to the message.
500 This is suitable for FCC, which can be specified multiple times."
501 (unless (eq vmpc-current-buffer 'composition)
502 (error "attempting to insert a header into a non-composition buffer."))
503 (let ((prev-contents (vmpc-get-header-contents hdrfield "\n")))
504 (setq prev-contents (vmpc-split prev-contents "\n"))
505 ;; don't add this new header if it's already there
506 (unless (member content prev-contents)
508 (or (mail-position-on-field hdrfield t) ; Put new field after existing one
509 (mail-position-on-field "to"))
510 (unless (eq (aref hdrfield (1- (length hdrfield))) ?:)
511 (setq hdrfield (concat hdrfield ":")))
512 (insert "\n" hdrfield " ")
515 (defun vmpc-get-current-header-contents (hdrfield &optional clump-sep)
516 "Return the contents of HDRFIELD in the current mail message.
517 Returns an empty string if the header doesn't exist. HDRFIELD should
518 be a string. If the string CLUMP-SEP is specified, it means to return
519 the contents of all headers matching the regexp HDRFIELD, separated by
521 ;; This code is based heavily on vm-get-header-contents and vm-match-header.
523 (if (eq vmpc-current-state 'automorph)
525 (let ((contents nil) (header-name-regexp "\\([^ \t\n:]+\\):")
526 (case-fold-search t) (temp-contents) (end-of-headers) (regexp))
527 (if (not (listp hdrfield))
528 (setq hdrfield (list hdrfield)))
529 ;; find the end of the headers:
530 (goto-char (point-min))
531 (or (re-search-forward
532 (concat "^\\(" (regexp-quote mail-header-separator) "\\)$")
534 (error "Cannot find mail-header-separator %S in buffer %S"
535 mail-header-separator (current-buffer)))
536 (setq end-of-headers (match-beginning 0))
537 ;; now rip through finding all the ones we want:
539 (setq regexp (concat "^\\(" (car hdrfield) "\\)"))
540 (goto-char (point-min))
541 (while (and (or (null contents) clump-sep)
542 (re-search-forward regexp end-of-headers t)
544 (goto-char (match-beginning 0))
545 (let (header-cont-start header-cont-end)
546 (if (if (not clump-sep)
547 (and (looking-at (car hdrfield))
548 (looking-at header-name-regexp))
549 (looking-at header-name-regexp))
551 (goto-char (match-end 0))
552 ;; skip leading whitespace
553 (skip-chars-forward " \t")
554 (setq header-cont-start (point))
556 (while (looking-at "[ \t]")
558 ;; drop the trailing newline
559 (setq header-cont-end (1- (point)))))
561 (buffer-substring header-cont-start
565 (concat contents clump-sep temp-contents))
566 (setq contents temp-contents)))
567 (setq hdrfield (cdr hdrfield)))
573 (defun vmpc-get-current-body-text ()
574 "Return the body text of the mail message in the current buffer."
575 (if (eq vmpc-current-state 'automorph)
577 (goto-char (point-min))
578 (let ((start (re-search-forward
579 (concat "^" (regexp-quote mail-header-separator) "$")))
581 (buffer-substring start end)))))
584 (defun vmpc-get-replied-header-contents (hdrfield &optional clump-sep)
585 "Return the contents of HDRFIELD in the message being replied to.
586 If that header does not exist, returns an empty string. If the string
587 CLUMP-SEP is specified, treat HDRFIELD as a regular expression and
588 return the contents of all header fields which match that regexp,
589 separated from each other by CLUMP-SEP."
590 (if (and (eq vmpc-current-buffer 'none)
591 (memq vmpc-current-state '(reply forward resend)))
592 (let ((mp (car (vm-select-marked-or-prefixed-messages 1)))
594 (if (not (listp hdrfield))
595 (setq hdrfield (list hdrfield)))
597 (setq c (vm-get-header-contents mp (car hdrfield) clump-sep))
598 (if c (setq content (cons c content)))
599 (setq hdrfield (cdr hdrfield)))
600 (or (mapconcat 'identity content "\n") ""))))
602 (defun vmpc-get-header-contents (hdrfield &optional clump-sep)
603 "Return the contents of HDRFIELD."
604 (cond ((and (eq vmpc-current-buffer 'none)
605 (memq vmpc-current-state '(reply forward resend)))
606 (vmpc-get-replied-header-contents hdrfield clump-sep))
607 ((eq vmpc-current-state 'automorph)
608 (vmpc-get-current-header-contents hdrfield clump-sep))))
610 (defun vmpc-get-replied-body-text ()
611 "Return the body text of the message being replied to."
612 (if (and (eq vmpc-current-buffer 'none)
613 (memq vmpc-current-state '(reply forward resend)))
615 (let* ((mp (car (vm-select-marked-or-prefixed-messages 1)))
616 (message (vm-real-message-of mp))
618 (set-buffer (vm-buffer-of message))
621 (setq start (vm-text-of message))
622 (setq end (vm-end-of message))
623 (buffer-substring start end))))))
625 (defun vmpc-save-replied-header (hdrfield)
626 "Save the contents of HDRFIELD in `vmpc-saved-headers-alist'.
627 Does nothing if that header doesn't exist."
628 (let ((hdrcont (vmpc-get-replied-header-contents hdrfield)))
629 (if (and (eq vmpc-current-buffer 'none)
630 (memq vmpc-current-state '(reply forward resend))
631 (not (equal hdrcont "")))
632 (add-to-list 'vmpc-saved-headers-alist (cons hdrfield hdrcont)))))
634 (defun vmpc-get-saved-header (hdrfield)
635 "Return the contents of HDRFIELD from `vmpc-saved-headers-alist'.
636 The alist in question is created by `vmpc-save-replied-header'."
637 (if (and (eq vmpc-current-buffer 'composition)
638 (memq vmpc-current-state '(reply forward resend)))
639 (cdr (assoc hdrfield vmpc-saved-headers-alist))))
641 (defun vmpc-substitute-replied-header (dest src)
642 "Substitute header DEST with content from SRC.
643 For example, if the address you want to send your reply to is the same
644 as the contents of the \"From\" header in the message you are replying
645 to, use (vmpc-substitute-replied-header \"To\" \"From\"."
646 (if (memq vmpc-current-state '(reply forward resend))
648 (if (eq vmpc-current-buffer 'none)
649 (vmpc-save-replied-header src))
650 (if (eq vmpc-current-buffer 'composition)
651 (vmpc-substitute-header dest (vmpc-get-saved-header src))))))
653 (defun vmpc-get-header-extents (hdrfield)
654 "Return buffer positions (START . END) for the contents of HDRFIELD.
655 If HDRFIELD does not exist, return nil."
656 (if (eq vmpc-current-buffer 'composition)
658 (let ((header-name-regexp "^\\([^ \t\n:]+\\):") (start) (end))
660 (if (mail-position-on-field hdrfield t)
664 (if (re-search-backward header-name-regexp (point-min) t)
667 (and start end (<= start end) (cons start end))))))
669 (defun vmpc-substitute-within-header
670 (hdrfield regexp to-string &optional append-if-no-match sep)
671 "Replace in HDRFIELD strings matched by REGEXP with TO-STRING.
672 HDRFIELD need not exist. TO-STRING may contain references to groups
673 within REGEXP, in the same manner as `replace-regexp'. If REGEXP is
674 not found in the header contents, and APPEND-IF-NO-MATCH is t,
675 TO-STRING will be appended to the header contents (with HDRFIELD being
676 created if it does not exist). In this case, if the string SEP is
677 specified, it will be used to separate the previous header contents
678 from TO-STRING, unless HDRFIELD has just been created or was
680 (if (eq vmpc-current-buffer 'composition)
682 (let ((se (vmpc-get-header-extents hdrfield)) (found))
686 (narrow-to-region (car se) (cdr se))
687 (goto-char (point-min))
688 (while (re-search-forward regexp nil t)
690 (replace-match to-string))
691 (if (and (not found) append-if-no-match)
694 (if (and sep (not (equal (car se) (cdr se))))
696 (insert to-string))))
697 ;; HDRFIELD does not exist
698 (if append-if-no-match
700 (mail-position-on-field hdrfield)
701 (insert to-string))))))))
704 (defun vmpc-replace-or-add-in-header (hdrfield regexp hdrcont &optional sep)
705 "Replace in HDRFIELD the match of REGEXP with HDRCONT.
706 All arguments are strings. The field can either be present or not.
707 If the header field is present and already contains something, HDRCONT
708 will be appended and if SEP is none nil it will be used as separator.
710 I use this function to modify recipients in the TO-header.
712 (vmpc-replace-or-add-in-header \"To\" \"[Rr]obert Fenk[^,]*\"
713 \"Robert Fenk\" \", \"))"
714 (if (eq vmpc-current-buffer 'composition)
715 (let ((hdr (vmpc-get-current-header-contents hdrfield))
719 (vmpc-delete-header hdrfield)
720 (if (string-match regexp hdr)
721 (setq hdr (vm-replace-in-string hdr regexp hdrcont))
722 (setq hdr (if sep (concat hdr sep hdrcont)
723 (concat hdr hdrcont))))
724 (vmpc-insert-header hdrfield hdr)
725 (goto-char old-point))
728 (defun vmpc-insert-signature (sig &optional pos)
729 "Insert SIG at the end of `vmpc-sig-exerlay'.
730 SIG is a string. If it is the name of a file, its contents is inserted --
731 otherwise the string itself is inserted. Optional parameter POS means insert
732 the signature at POS if `vmpc-sig-exerlay' is detached."
733 (if (eq vmpc-current-buffer 'composition)
735 (let ((end (or (vmpc-exerlay-end vmpc-sig-exerlay) pos)))
737 (vmpc-set-exerlay-insertion-types 'vmpc-sig-exerlay nil t)
738 (vmpc-set-exerlay-detachable-property vmpc-sig-exerlay nil)
739 (vmpc-set-exerlay-intangible-property vmpc-sig-exerlay nil)
741 (setq end (point-max))
742 (vmpc-move-exerlay vmpc-sig-exerlay end end))
743 (if (and pos (not (vmpc-exerlay-end vmpc-sig-exerlay)))
744 (vmpc-move-exerlay vmpc-sig-exerlay pos pos))
747 (if (and (file-exists-p sig)
748 (file-readable-p sig)
749 (not (equal sig "")))
750 (insert-file-contents sig)
752 (vmpc-set-exerlay-intangible-property vmpc-sig-exerlay
754 (vmpc-set-exerlay-detachable-property vmpc-sig-exerlay t)
755 (vmpc-set-exerlay-insertion-types 'vmpc-sig-exerlay t nil)))))
758 (defun vmpc-delete-signature ()
759 "Deletes the contents of `vmpc-sig-exerlay'."
760 (when (and (eq vmpc-current-buffer 'composition)
761 ;; make sure it's not detached first:
762 (vmpc-exerlay-start vmpc-sig-exerlay))
763 (delete-region (vmpc-exerlay-start vmpc-sig-exerlay)
764 (vmpc-exerlay-end vmpc-sig-exerlay))
765 (vmpc-forcefully-detach-exerlay vmpc-sig-exerlay)))
768 (defun vmpc-signature (sig)
769 "Remove a current signature if present, and replace it with SIG.
770 If the string SIG is the name of a readable file, its contents are
771 inserted as the signature; otherwise SIG is inserted literally. If
772 SIG is the empty string (\"\"), the current signature is deleted if
773 present, and that's all."
774 (if (eq vmpc-current-buffer 'composition)
775 (let ((pos (vmpc-exerlay-start vmpc-sig-exerlay)))
777 (vmpc-delete-signature)
778 (if (not (equal sig ""))
779 (vmpc-insert-signature sig pos))))))
782 (defun vmpc-insert-pre-signature (pre-sig &optional pos)
783 "Insert PRE-SIG at the end of `vmpc-pre-sig-exerlay'.
784 PRE-SIG is a string. If it's the name of a file, the file's contents
785 are inserted; otherwise the string itself is inserted. Optional
786 parameter POS means insert the pre-signature at position POS if
787 `vmpc-pre-sig-exerlay' is detached."
788 (if (eq vmpc-current-buffer 'composition)
790 (let ((end (or (vmpc-exerlay-end vmpc-pre-sig-exerlay) pos))
791 (sigstart (vmpc-exerlay-start vmpc-sig-exerlay)))
793 (vmpc-set-exerlay-insertion-types 'vmpc-pre-sig-exerlay nil t)
794 (vmpc-set-exerlay-detachable-property vmpc-pre-sig-exerlay nil)
795 (vmpc-set-exerlay-intangible-property vmpc-pre-sig-exerlay nil)
799 (setq end (point-max)))
800 (vmpc-move-exerlay vmpc-pre-sig-exerlay end end))
801 (if (and pos (not (vmpc-exerlay-end vmpc-pre-sig-exerlay)))
802 (vmpc-move-exerlay vmpc-pre-sig-exerlay pos pos))
805 (if (and (file-exists-p pre-sig)
806 (file-readable-p pre-sig)
807 (not (equal pre-sig "")))
808 (insert-file-contents pre-sig)
810 (vmpc-set-exerlay-intangible-property vmpc-pre-sig-exerlay
811 vmpc-intangible-pre-sig)
812 (vmpc-set-exerlay-detachable-property vmpc-pre-sig-exerlay t)
813 (vmpc-set-exerlay-insertion-types 'vmpc-pre-sig-exerlay t nil))))
816 (defun vmpc-delete-pre-signature ()
817 "Deletes the contents of `vmpc-pre-sig-exerlay'."
818 ;; make sure it's not detached first:
819 (if (eq vmpc-current-buffer 'composition)
820 (if (vmpc-exerlay-start vmpc-pre-sig-exerlay)
822 (delete-region (vmpc-exerlay-start vmpc-pre-sig-exerlay)
823 (vmpc-exerlay-end vmpc-pre-sig-exerlay))
824 (vmpc-forcefully-detach-exerlay vmpc-pre-sig-exerlay)))))
827 (defun vmpc-pre-signature (pre-sig)
828 "Insert PRE-SIG at the end of `vmpc-pre-sig-exerlay' removing last pre-sig."
829 (if (eq vmpc-current-buffer 'composition)
830 (let ((pos (vmpc-exerlay-start vmpc-pre-sig-exerlay)))
832 (vmpc-delete-pre-signature)
833 (if (not (equal pre-sig ""))
834 (vmpc-insert-pre-signature pre-sig pos))))))
837 (defun vmpc-gregorian-days ()
838 "Return the number of days elapsed since December 31, 1 B.C."
839 ;; this code stolen from gnus-util.el :)
840 (let ((tim (decode-time (current-time))))
841 (timezone-absolute-from-gregorian
842 (nth 4 tim) (nth 3 tim) (nth 5 tim))))
845 (defun vmpc-load-auto-profiles ()
846 "Initialise `vmpc-auto-profiles' from `vmpc-auto-profiles-file'."
848 (setq vmpc-auto-profiles nil)
849 (if (eq vmpc-auto-profiles-file 'BBDB)
850 (let ((records (bbdb-with-db-buffer bbdb-records))
853 (setq rec (car records)
854 profile (bbdb-get-field rec 'vmpc-profile))
855 (when (and profile (> (length profile) 0))
856 (setq nets (bbdb-record-net rec))
858 (setq vmpc-auto-profiles (cons (cons (car nets) (read profile))
861 (setq records (cdr records)))
862 (setq vmpc-auto-profiles (reverse vmpc-auto-profiles)))
863 (when (and (file-exists-p vmpc-auto-profiles-file) ;
864 (file-readable-p vmpc-auto-profiles-file))
866 (set-buffer (get-buffer-create "*pcrisis-temp*"))
867 (buffer-disable-undo (current-buffer))
869 (insert-file-contents vmpc-auto-profiles-file)
870 (goto-char (point-min))
871 (setq vmpc-auto-profiles (read (current-buffer)))
872 (kill-buffer (current-buffer))))))
875 (defun vmpc-save-auto-profiles ()
876 "Save `vmpc-auto-profiles' to `vmpc-auto-profiles-file'."
877 (when (not (eq vmpc-auto-profiles-file 'BBDB))
878 (if (not (file-writable-p vmpc-auto-profiles-file))
879 ;; if file is not writable, signal an error:
880 (error "Error: P-Crisis could not write to file %s"
881 vmpc-auto-profiles-file))
883 (set-buffer (get-buffer-create "*pcrisis-temp*"))
884 (buffer-disable-undo (current-buffer))
886 (goto-char (point-min))
887 ; (prin1 vmpc-auto-profiles (current-buffer))
888 (pp vmpc-auto-profiles (current-buffer))
889 (write-region (point-min) (point-max)
890 vmpc-auto-profiles-file nil 'quietly)
891 (kill-buffer (current-buffer)))))
893 (defun vmpc-fix-auto-profiles-file ()
894 "Change `vmpc-auto-profiles-file' to the format used by v0.82+."
896 (vmpc-load-auto-profiles)
897 (let ((len (length vmpc-auto-profiles)) (i 0) (day))
899 (setq day (cddr (nth i vmpc-auto-profiles)))
901 (setcdr (cdr (nth i vmpc-auto-profiles)) (car day)))
903 (vmpc-save-auto-profiles)
904 (setq vmpc-auto-profiles ()))
907 (defun vmpc-migrate-profiles-to-BBDB ()
908 "Migrate the profiles stored in `vmpc-auto-profiles-file' to the BBDB.
910 This will automatically create records if they do not exist and add the new
911 field `vmpc-profile' to the records which is a sexp not meant to be edited."
913 (if (eq vmpc-auto-profiles-file 'BBDB)
914 (error "`vmpc-auto-profiles-file' has been migrated already."))
915 (unless vmpc-auto-profiles
916 (vmpc-load-auto-profiles))
917 ;; create a BBDB backup
919 (copy-file (expand-file-name bbdb-file)
920 (concat (expand-file-name bbdb-file) "-vmpc-profile-migration-backup"))
921 ;; now migrate the profiles
922 (let ((profiles vmpc-auto-profiles)
923 (records (bbdb-with-db-buffer bbdb-records))
926 (setq p (car profiles)
928 rec (car (bbdb-search records nil nil addr)))
930 (setq rec (bbdb-create-internal "?" nil addr nil nil nil)))
931 (bbdb-record-putprop rec 'vmpc-profile (format "%S" (cdr p)))
932 (setq profiles (cdr profiles))))
933 ;; move old profiles file out of the way
934 (rename-file vmpc-auto-profiles-file
935 (concat vmpc-auto-profiles-file "-migrated-to-BBDB"))
936 ;; switch to BBDB mode
937 (customize-save-variable 'vmpc-auto-profiles-file 'BBDB)
938 (message "`vmpc-auto-profiles-file' has been set to 'BBDB"))
940 (defun vmpc-get-profile-for-address (addr)
941 "Return profile for ADDR."
942 (unless vmpc-auto-profiles
943 (vmpc-load-auto-profiles))
944 ;; TODO: BBDB "normalizes" email addresses, i.e. before we had a one-to-one
945 ;; mapping of address=>actions, now multiple actions may point to the same
946 ;; list of actions. So either we should update vmpc-auto-profiles upon
947 ;; storing a new profile or directly search BBDB for it, which might be
949 (let ((prof (cadr (assoc addr vmpc-auto-profiles))))
951 ;; we found a profile for this address and we are still
952 ;; using it -- so "touch" the record to ensure it stays
953 ;; newer than vmpc-auto-profiles-expunge-days
954 (setcdr (cdr (assoc addr vmpc-auto-profiles)) (vmpc-gregorian-days))
955 (vmpc-save-auto-profiles))
959 (defun vmpc-save-profile-for-address (addr actions)
960 "Save the association ADDR => ACTIONS."
961 (let ((today (vmpc-gregorian-days))
962 (old-association (assoc addr vmpc-auto-profiles))
965 ;; we store the actions list and the durrent date
966 (setq profile (append (list addr actions) today))
968 ;; remove old profile
969 (when old-association
970 ;; now possibly delete it from the BBDB
971 (setq vmpc-auto-profiles (delete old-association vmpc-auto-profiles))
972 (when (and (eq vmpc-auto-profiles-file 'BBDB) (not actions))
973 (let ((records (bbdb-with-db-buffer bbdb-records)) rec)
974 (setq rec (bbdb-search records nil nil addr))
976 (bbdb-record-putprop (car rec) 'vmpc-profile nil)))))
980 (setq vmpc-auto-profiles (cons profile vmpc-auto-profiles))
981 ;; now possibly add it to the BBDB
982 (when (eq vmpc-auto-profiles-file 'BBDB)
983 (let ((records (bbdb-with-db-buffer bbdb-records)) rec)
984 (setq rec (car (bbdb-search records nil nil addr)))
986 (setq rec (bbdb-create-internal "?" nil addr nil nil nil)))
987 (bbdb-record-putprop rec 'vmpc-profile (format "%S" (cdr profile))))))
989 ;; expunge old stuff from the list:
990 (when vmpc-auto-profiles-expunge-days
991 (setq vmpc-auto-profiles
993 (if (> (- today (cddr p)) vmpc-auto-profiles-expunge-days)
997 (setq vmpc-auto-profiles (delete nil vmpc-auto-profiles)))
1000 (vmpc-save-auto-profiles)))
1003 (defun vmpc-string-extract-address (str)
1004 "Find the first email address in the string STR and return it.
1005 If no email address in found in STR, returns nil."
1006 (if (string-match "[^ \t,<]+@[^ \t,>]+" str)
1007 (match-string 0 str)))
1009 (defun vmpc-split (string separators)
1010 "Return a list by splitting STRING at SEPARATORS and trimming all whitespace."
1012 (not-separators (concat "^" separators)))
1014 (set-buffer (get-buffer-create " *split*"))
1017 (goto-char (point-min))
1019 (skip-chars-forward separators)
1020 (skip-chars-forward " \t\n\r")
1022 (let ((begin (point))
1024 (skip-chars-forward not-separators)
1026 (skip-chars-backward " \t\n\r")
1027 (setq result (cons (buffer-substring begin (point)) result))
1032 (defun vmpc-read-actions (prompt)
1033 "Read a list of actions to run and store it in `vmpc-actions-to-run'."
1034 (interactive (list "VMPC actions %s:"))
1035 (let ((actions ()) a)
1036 (while (not (string-equal
1037 (setq a (completing-read
1038 (format prompt (or actions ""))
1039 ;; omit those starting with (vmpc-prompt-for-profile ...
1042 (if (not (or (eq (caadr a) 'vmpc-prompt-for-profile)
1043 (member (car a) actions)))
1048 (setq actions (cons a actions)))
1049 (setq actions (reverse actions))
1050 (when (interactive-p)
1051 (setq vmpc-actions-to-run actions)
1052 (message "VMPC actions to run: %S" actions))
1055 (defcustom vmpc-prompt-for-profile-headers
1056 '((composition ("To" "CC" "BCC"))
1057 (default ("From" "Sender" "Reply-To" "From" "Resent-From")))
1058 "*List of headers to check for email addresses.
1060 `vmpc-prompt-for-profile' will scan the given headers in the given order."
1061 :type '(repeat (list (choice (const default)
1067 (repeat (string :tag "Header"))))
1070 (defun vmpc-prompt-for-profile (&optional remember prompt)
1071 "Find a profile or prompt for it and add its actions to the list of actions.
1073 A profile is an association between a recipient address and a set of the
1074 actions named in `vmpc-actions'. When entering the list of actions, one has
1075 to press ENTER after each action and finish adding action by pressing ENTER
1078 The association is stored in `vmpc-auto-profiles-file' and in the future the
1079 stored actions will automatically run for messages to that address.
1081 REMEMBER can be set to 'always or 'prompt. When set to 'prompt you will
1082 be asked if you want to store the association. When set to 'always a new
1083 profile will be stored without asking.
1085 If you want to change the profile late call this function interactively in a
1086 composition buffer. Set PROFILE to 'never and you will never ever be prompted
1087 for anything, i.e. only existing profiles will be applied."
1088 (interactive (progn (setq vmpc-current-state 'automorph)
1089 (list 'prompt 'again)))
1091 (if (or (and (eq vmpc-current-buffer 'none)
1092 (not (eq vmpc-current-state 'automorph)))
1093 (eq vmpc-current-state 'automorph))
1094 (let ((headers (or (assoc vmpc-current-buffer vmpc-prompt-for-profile-headers)
1095 (assoc vmpc-current-state vmpc-prompt-for-profile-headers)
1096 (assoc 'default vmpc-prompt-for-profile-headers)))
1097 addrs a actions dest)
1098 (setq headers (car (cdr headers)))
1099 ;; search also other headers fro known addresses
1100 (while (and headers (not actions))
1101 (setq addrs (vmpc-get-header-contents (car headers)))
1102 (if addrs (setq addrs (vmpc-split addrs ",")))
1104 (setq a (vmpc-string-extract-address (car addrs)))
1105 (if (vm-ignored-reply-to a)
1107 (setq actions (append (vmpc-get-profile-for-address a) actions))
1108 (if actions (setq remember 'already))
1109 (if (not dest) (setq dest a))
1110 (setq addrs (cdr addrs)))
1111 (setq headers (cdr headers)))
1114 ;; figure out which actions to run
1115 (when (if prompt (not (eq prompt 'never)) (not actions))
1116 (setq actions (vmpc-read-actions
1117 (format "Actions for \"%s\" %%s (end with RET): " dest))))
1119 ;; fixed old style format where there was only a single action
1120 (unless (listp actions)
1121 (setq remember 'again)
1122 (setq actions (list actions)))
1124 ;; save the association of this profile with these actions if applicable
1125 (if (or (and (eq remember 'prompt)
1126 (not (eq prompt 'never))
1128 (y-or-n-p (format "Always run %s for \"%s\"? "
1130 (if (vmpc-get-profile-for-address dest)
1131 (yes-or-no-p (format "Delete profile for \"%s\"? "
1133 (eq remember 'always))
1134 (vmpc-save-profile-for-address dest actions))
1136 ;; TODO: understand when vmpc-prompt-for-profile has to run actions
1137 ;; if we are in automorph (actually being called from within an action)
1138 (if (eq vmpc-current-state 'automorph)
1139 (let ((vmpc-actions-to-run actions))
1141 ;; otherwise add the actions to the end of the list as a side effect
1142 (setq vmpc-actions-to-run (append vmpc-actions-to-run actions)))
1144 ;; return the actions, which makes the condition true if a profile exists
1147 ;; -------------------------------------------------------------------
1148 ;; Functions for vmpc-conditions:
1149 ;; -------------------------------------------------------------------
1151 (defun vmpc-none-true-yet (&optional &rest exceptions)
1152 "True if none of the previous evaluated conditions was true.
1153 This is a condition that can appear in `vmpc-conditions'. If EXCEPTIONS are
1154 specified, it means none were true except those. For example, if you wanted
1155 to check whether no conditions had yet matched with the exception of the two
1156 conditions named \"default\" and \"blah\", you would make the call like this:
1157 (vmpc-none-true-yet \"default\" \"blah\")
1158 Then it will return true regardless of whether \"default\" and \"blah\" had
1160 (let ((lenex (length exceptions)) (lentc (length vmpc-true-conditions)))
1165 (let ((i 0) (j 0) (k 0))
1169 (if (equal (nth i exceptions) (nth k vmpc-true-conditions))
1177 (defun vmpc-other-cond (condition)
1178 "Return true if the specified CONDITION in `vmpc-conditions' matched.
1179 CONDITION can only be the name of a condition specified earlier in
1180 `vmpc-conditions' -- that is to say, any conditions which follow the one
1181 containing `vmpc-other-cond' will show up as not having matched, because they
1182 haven't yet been checked when this one is checked."
1183 (member condition vmpc-true-conditions))
1185 (defun vmpc-folder-match (regexp)
1186 "Return true if the current folder name matches REGEXP."
1187 (string-match regexp (buffer-name)))
1189 (defun vmpc-header-match (hdrfield regexp &optional clump-sep num)
1190 "Return true if the contents of specified header HDRFIELD match REGEXP.
1191 For automorph, this means the header in your message, when replying it means
1192 the header in the message being replied to.
1194 CLUMP-SEP is specified, treat HDRFIELD as a regular expression and
1195 return the contents of all header fields which match that regexp,
1196 separated from each other by CLUMP-SEP.
1198 If NUM is specified return the match string NUM."
1199 (cond ((memq vmpc-current-state '(reply forward resend))
1200 (let ((hdr (vmpc-get-replied-header-contents hdrfield clump-sep)))
1201 (and hdr (string-match regexp hdr)
1202 (if num (match-string num hdr) t))))
1203 ((eq vmpc-current-state 'automorph)
1204 (let ((hdr (vmpc-get-current-header-contents hdrfield clump-sep)))
1205 (and (string-match regexp hdr)
1206 (if num (match-string num hdr) t))))))
1208 (defun vmpc-body-match (regexp)
1209 "Return non-nil if the contents of the message body match REGEXP.
1210 For automorph, this means the body of your message; when replying it means the
1211 body of the message being replied to."
1212 (cond ((and (memq vmpc-current-state '(reply forward resend))
1213 (eq vmpc-current-buffer 'none))
1214 (string-match regexp (vmpc-get-replied-body-text)))
1215 ((eq vmpc-current-state 'automorph)
1216 (string-match regexp (vmpc-get-current-body-text)))))
1219 (defun vmpc-xor (&rest args)
1220 "Return true if one and only one argument in ARGS is true."
1221 (= 1 (length (delete nil args))))
1223 ;; -------------------------------------------------------------------
1224 ;; Support functions for the advices:
1225 ;; -------------------------------------------------------------------
1227 (defun vmpc-true-conditions ()
1228 "Return a list of all true conditions.
1229 Run this function in order to test/check your conditions."
1231 (let (vmpc-true-conditions
1233 vmpc-current-buffer)
1234 (if (eq major-mode 'vm-mail-mode)
1235 (setq vmpc-current-state 'automorph
1236 vmpc-current-buffer 'composition)
1237 (setq vmpc-current-state (intern (completing-read
1238 "VMPC state (default is 'reply): "
1239 '(("reply") ("forward") ("resend")
1240 ("newmail") ("automorph"))
1241 nil t nil nil "reply"))
1242 vmpc-current-buffer 'none))
1243 (vm-follow-summary-cursor)
1244 (vm-select-folder-buffer)
1245 (vm-check-for-killed-summary)
1246 (vm-error-if-folder-empty)
1247 (vmpc-build-true-conditions-list)
1248 (message "VMPC true conditions: %S" vmpc-true-conditions)
1249 vmpc-true-conditions))
1251 (defun vmpc-build-true-conditions-list ()
1252 "Built list of true conditions and store it in variable `vmpc-true-conditions'."
1253 (setq vmpc-true-conditions nil)
1255 (if (save-excursion (eval (cons 'progn (cdr c))))
1256 (setq vmpc-true-conditions (cons (car c) vmpc-true-conditions))))
1258 (setq vmpc-true-conditions (reverse vmpc-true-conditions)))
1260 (defun vmpc-build-actions-to-run-list ()
1261 "Built a list of the actions to run.
1262 These are the true conditions mapped to actions. Duplicates will be
1263 eliminated. You may run it in a composition buffer in order to see what
1264 actions will be run."
1266 (if (and (interactive-p) (not (member major-mode '(vm-mail-mode mail-mode))))
1267 (error "Run `vmpc-build-actions-to-run-list' in a composition buffer!"))
1268 (let ((alist (or (symbol-value (intern (format "vmpc-%s-alist"
1269 vmpc-current-state)))
1270 vmpc-actions-alist))
1271 (old-vmpc-actions-to-run vmpc-actions-to-run)
1273 (setq vmpc-actions-to-run nil)
1275 (setq actions (cdr (assoc c alist)))
1276 ;; TODO: warn about unbound conditions?
1278 (if (not (member (car actions) vmpc-actions-to-run))
1279 (setq vmpc-actions-to-run (cons (car actions) vmpc-actions-to-run)))
1280 (setq actions (cdr actions))))
1281 vmpc-true-conditions)
1282 (setq vmpc-actions-to-run (reverse vmpc-actions-to-run))
1283 (setq vmpc-actions-to-run (append vmpc-actions-to-run old-vmpc-actions-to-run)))
1285 (message "VMPC actions to run: %S" vmpc-actions-to-run))
1286 vmpc-actions-to-run)
1288 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1290 (defun vmpc-run-action (&optional action-regexp)
1291 "Run all actions with names matching the ACTION-REGEXP.
1292 If called interactivly it promts for the regexp. You may also use
1295 (let ((action-names (mapcar '(lambda (a)
1296 (list (regexp-quote (car a)) 1))
1298 (if (not action-regexp)
1299 (setq action-regexp (completing-read "VMPC action-regexp: "
1301 (mapcar '(lambda (action)
1302 (if (string-match action-regexp (car action))
1303 (mapcar '(lambda (action-command)
1304 (eval action-command))
1309 (defun vmpc-run-actions (&optional actions verbose)
1310 "Run the argument actions, or the actions stored in `vmpc-actions-to-run'.
1311 If verbose is supplied, it should be a STRING, indicating the name of a
1312 buffer to which to write diagnostic output."
1315 (if (and (not vmpc-actions-to-run) (not actions) (interactive-p))
1316 (setq vmpc-actions-to-run (vmpc-read-actions "VMPC actions %%s")))
1318 (let ((actions (or actions vmpc-actions-to-run)) form)
1320 (setq form (or (assoc (car actions) vmpc-actions)
1321 (error "Action %S does not exist!" (car actions)))
1322 actions (cdr actions))
1323 (let ((form (cons 'progn (cdr form)))
1324 (results (eval (cons 'progn (cdr form)))))
1327 (set-buffer verbose)
1328 (insert (format "Action form is:\n%S\nResults are:\n%S\n"
1329 form results))))))))
1331 ;; ------------------------------------------------------------------------
1332 ;; The main functions and advices -- these are the entry points to pcrisis:
1333 ;; ------------------------------------------------------------------------
1334 (defun vmpc-init-vars (&optional state buffer)
1335 "Initialize pcrisis variables and optionally set STATE and BUFFER."
1336 (setq vmpc-saved-headers-alist nil
1337 vmpc-actions-to-run nil
1338 vmpc-true-conditions nil
1339 vmpc-current-state state
1340 vmpc-current-buffer (or buffer 'none)))
1342 (defun vmpc-make-vars-local ()
1343 "Make the pcrisis vars buffer local.
1345 When the vars are first set they cannot be made buffer local as we are not in
1346 the composition buffer then.
1348 Unfortunately making them buffer local while they are bound by a `let' does
1349 not work, see the info for `make-local-variable'. So we are using the global
1350 ones and make them buffer local when in the composition buffer. At least for
1351 `saved-headers-alist' this should fix the bug that another composition
1352 overwrites the stored headers for subsequent morphs.
1354 The current solution is not reentrant save, but there also should be no
1355 recursion nor concurrent calls."
1356 ;; make the variables buffer local
1357 (let ((tc vmpc-true-conditions)
1358 (sha vmpc-saved-headers-alist)
1359 (atr vmpc-actions-to-run)
1360 (cs vmpc-current-state))
1361 (make-local-variable 'vmpc-true-conditions)
1362 (make-local-variable 'vmpc-saved-headers-alist)
1363 (make-local-variable 'vmpc-actions-to-run)
1364 (make-local-variable 'vmpc-current-state)
1365 (make-local-variable 'vmpc-current-buffer)
1366 ;; now set them again to make sure the contain the right value
1367 (setq vmpc-true-conditions tc)
1368 (setq vmpc-saved-headers-alist sha)
1369 (setq vmpc-actions-to-run atr)
1370 (setq vmpc-current-state cs))
1371 ;; mark, that we are in the composition buffer now
1372 (setq vmpc-current-buffer 'composition)
1373 ;; BUGME why is the global value resurrected after making the variable
1374 ;; buffer local? Is this related to defadvice? I have no idea what is
1375 ;; going on here! Thus we clear it afterwards now!
1377 (set-buffer (get-buffer-create " *vmpc-cleanup*"))
1379 (setq vmpc-current-buffer nil)))
1381 (defadvice vm-do-reply (around vmpc-reply activate)
1382 "*Reply to a message with pcrisis voodoo."
1383 (vmpc-init-vars 'reply)
1384 (vmpc-build-true-conditions-list)
1385 (vmpc-build-actions-to-run-list)
1388 (vmpc-create-sig-and-pre-sig-exerlays)
1389 (vmpc-make-vars-local)
1392 (defadvice vm-mail (around vmpc-newmail activate)
1393 "*Start a new message with pcrisis voodoo."
1394 (vmpc-init-vars 'newmail)
1395 (vmpc-build-true-conditions-list)
1396 (vmpc-build-actions-to-run-list)
1399 (vmpc-create-sig-and-pre-sig-exerlays)
1400 (vmpc-make-vars-local)
1403 (defadvice vm-compose-mail (around vmpc-compose-newmail activate)
1404 "*Start a new message with pcrisis voodoo."
1405 (vmpc-init-vars 'newmail)
1406 (vmpc-build-true-conditions-list)
1407 (vmpc-build-actions-to-run-list)
1410 (vmpc-create-sig-and-pre-sig-exerlays)
1411 (vmpc-make-vars-local)
1414 (defadvice vm-forward-message (around vmpc-forward activate)
1415 "*Forward a message with pcrisis voodoo."
1416 ;; this stuff is already done when replying, but not here:
1417 (vm-follow-summary-cursor)
1418 (vm-select-folder-buffer)
1419 (vm-check-for-killed-summary)
1420 (vm-error-if-folder-empty)
1421 ;; the rest is almost exactly the same as replying:
1422 (vmpc-init-vars 'forward)
1423 (vmpc-build-true-conditions-list)
1424 (vmpc-build-actions-to-run-list)
1427 (vmpc-create-sig-and-pre-sig-exerlays)
1428 (vmpc-make-vars-local)
1431 (defadvice vm-resend-message (around vmpc-resend activate)
1432 "*Resent a message with pcrisis voodoo."
1433 ;; this stuff is already done when replying, but not here:
1434 (vm-follow-summary-cursor)
1435 (vm-select-folder-buffer)
1436 (vm-check-for-killed-summary)
1437 (vm-error-if-folder-empty)
1438 ;; the rest is almost exactly the same as replying:
1439 (vmpc-init-vars 'resend)
1440 (vmpc-build-true-conditions-list)
1441 (vmpc-build-actions-to-run-list)
1444 (vmpc-create-sig-and-pre-sig-exerlays)
1445 (vmpc-make-vars-local)
1448 (defvar vmpc-no-automorph nil
1449 "When true automorphing will be disabled.")
1451 (make-variable-buffer-local 'vmpc-no-automorph)
1454 (defun vmpc-toggle-no-automorph ()
1455 "Disable automorph for the current buffer.
1456 When automorph is not doing the right thing and you want to disable it for the
1457 current composition, then call this function."
1459 (setq vmpc-no-automorph (not vmpc-no-automorph))
1460 (message (if vmpc-no-automorph
1461 "Automorphing has been enabled"
1462 "Automorphing has been disabled")))
1465 (defun vmpc-automorph ()
1466 "*Change contents of the current mail message based on its own headers.
1467 Unless `vmpc-current-state' is 'no-automorph, headers and signatures can be
1468 changed; pre-signatures added; functions called.
1470 Call `vmpc-no-automorph' to disable it for the current buffer."
1472 (unless vmpc-no-automorph
1473 (vmpc-make-vars-local)
1474 (vmpc-init-vars 'automorph 'composition)
1475 (vmpc-build-true-conditions-list)
1476 (vmpc-build-actions-to-run-list)
1477 (vmpc-run-actions)))
1479 (provide 'vm-pcrisis)
1481 ;;; vm-pcrisis.el ends here