;; -*- mode: Emacs-Lisp; mode: rainbow; -*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; This program is free software: you can redistribute it and/or modify ;;
;; it under the terms of the version 3 of the GNU General Public License ;;
;; as published by the Free Software Foundation. ;;
;; ;;
;; This program is distributed in the hope that it will be useful, but ;;
;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;;
;; General Public License for more details. ;;
;; ;;
;; You should have received a copy of the GNU General Public License ;;
;; along with this program. If not, see . ;;
;; ;;
;; Written by and Copyright (C) Francois Fleuret ;;
;; Contact for comments & bug reports ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; A fast indexed / search in mbox
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; This is one of my own things, check my web page to get it
(when (ff/load-or-alert "~/sources/gpl/mymail/mymail-vm.el")
(define-key vm-summary-mode-map "\\" 'mymail/vm-visit-folder)
(define-key global-map [S-f7] 'mymail/vm-visit-folder)
(setq mymail/default-search-request "today"
mymail/default-additional-search-requests "!s ^\\[SPAM\\],!s \\] STATUS,")
(add-to-list 'recentf-exclude "/tmp/mymail-vm-.*\.mbox")
)
(setq-default vm-summary-show-threads t)
;; (setq vm-preview-lines nil)
;; (setq vm-auto-displayed-mime-content-types '("text"));; "multipart"));; "image/xpm"
(setq vm-mime-thumbnail-max-geometry nil)
(setq vm-auto-displayed-mime-content-types '(
"text"
;; "image/jpeg"
;; "image/png"
"multipart"
"message/rfc822"
))
(setq
;; browse-url-mozilla-program "iceweasel"
;; mail-complete-style nil
;; mail-from-style nil
;; vm-coding-system-priorities '(utf-8)
;; vm-highlighted-header-regexp "From:\\|Subject:\\|Cc:\\|To:\\|Bcc:\\|Reply-To:"
;; vm-included-text-attribution-format "\nOn %w, %m %d, at %H, you wrote:\n\n"
;; vm-jump-to-new-messages nil
;; vm-mime-7bit-composition-charset "latin-1"
;; vm-mime-8bit-composition-charset "utf-8"
;; vm-preview-read-messages t
;; vm-summary-format " %*%A %-3.3m %2d %5US %I%UA %s\n"
;; vm-summary-uninteresting-senders-arrow "->"
;; vm-summary-uninteresting-senders-arrow "►"
;; vm-summary-uninteresting-senders-arrow "➔"
;; vm-summary-uninteresting-senders-arrow "➤"
bbdb-send-mail-style 'vm
bbdb/mail-auto-create-p nil
mail-signature t
mail-specify-envelope-from t
vm-auto-folder-case-fold-search t
vm-auto-get-new-mail t
vm-circular-folders nil
vm-confirm-new-folders t
vm-delete-after-archiving t
vm-delete-after-saving t
vm-forwarding-digest-type "mime"
vm-forwarding-subject-format "(forwarded from %F) %s"
vm-frame-per-folder nil
vm-frame-per-summary nil
vm-highlighted-header-regexp "From:\\|Subject:\\|Reply-To:"
vm-in-reply-to-format nil
vm-included-text-attribution-format "\nOn %w, %m %d, %y (%h), %F wrote:\n\n"
vm-included-text-prefix " > "
vm-keep-sent-messages nil
vm-mime-8bit-composition-charset "iso-8859-1"
vm-mime-attachment-save-directory "~/misc/attachments"
vm-mime-yank-attachments nil
vm-mutable-frames nil
vm-mutable-windows t
vm-netscape-program browse-url-mozilla-program
vm-preview-lines nil
vm-reply-subject-prefix "Re: "
vm-skip-deleted-messages nil
vm-skip-read-messages nil
vm-startup-message-displayed t
vm-startup-with-summary t
vm-summary-arrow "> "
vm-summary-format " %*%a %-3.3m %2d %5US %I%UA %s\n"
vm-summary-thread-indent-level 1
vm-summary-uninteresting-senders-arrow ">"
vm-use-menus nil
vm-use-toolbar nil
vm-use-toolbar nil
)
;; (add-to-list 'vm-visible-headers "From " t)
(add-to-list 'vm-visible-headers "Reply-To:" t)
;; (add-to-list 'vm-visible-headers "X-Mailer:" t)
;; (add-to-list 'vm-visible-headers "X-from-in-bbdb:" t)
;; (add-to-list 'vm-visible-headers "Return-Path:")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Mime-related stuff
(setq
vm-infer-mime-types t
vm-mime-use-image-strips nil
vm-mime-base64-decoder-program "base64"
vm-mime-base64-decoder-switches '("-d")
vm-mime-base64-encoder-program "base64"
vm-mime-base64-encoder-switches '()
vm-mime-internal-content-types '(
"multipart"
"text"
;; "plain text"
;; "plain text/utf8"
"image/xpm"
)
;; To force it to be converted to plain text
;; vm-mime-internal-content-type-exceptions '("text/html")
vm-mime-external-content-types-alist '(
("application/x-dvi" "xdvi")
("image/postscript" "gv")
("application/pdf" "xpdf")
("application/postscript" "gv")
("image" "pho")
("video" "mplayer")
;; ("text/html" "firefox")
;; ("application/pdf" "epdfview")
)
)
(require 'vm-rfaddons)
;; The two following lines deal with windows-1252 buggy encoding
;;**;; ;; First, don't display iso-8859-1 as-is in default face
;;**;; (delete "iso-8859-1" vm-mime-default-face-charsets)
;;**;; ;; Then substitute windows-1252 for iso-8859-1
;;**;; (add-to-list 'vm-mime-mule-charset-to-coding-alist '("iso-8859-1" windows-1252))
;; (setq vm-mime-default-face-charsets t)
;; (add-to-list 'vm-mime-default-face-charsets "utf-8")
;; (add-to-list 'vm-mime-default-face-charsets "iso-8859-1")
;; (add-to-list 'vm-mime-default-face-charsets "Windows-1251")
;; (add-hook 'vm-mail-send-hook 'ff/vm-remove-non-7bits-in-headers)
(add-hook 'vm-mail-send-hook 'vm-mime-encode-headers)
(add-hook 'vm-mail-send-hook 'vm-mail-check-recipients)
(add-hook 'vm-reply-hook (lambda () (set-buffer-modified-p nil)))
(add-to-list 'vm-mime-attachment-auto-type-alist '(".*" . "application/octet-stream") t)
;; (add-to-list 'vm-mime-type-converter-alist '("text/html" "text/plain" "cat"))
;; (add-to-list 'vm-mime-type-converter-alist '("text/html" "text/plain" "lynx -nolist -force_html -dump -stdin"))
;; (add-to-list 'vm-mime-type-converter-alist
;; '("text/html" "text/plain"
;; "w3m -cols 75 -graph -dump -T text/html"
;; ))
;; (add-to-list 'vm-mime-type-converter-alist
;; '("text/html" "text/plain"
;; "html2text -style pretty -nobs"
;; ))
;; (add-to-list 'vm-mime-type-converter-alist
;; '("text/html" "text/plain"
;; "html2text.sh"
;; ))
(add-to-list 'vm-mime-type-converter-alist
'("image" "image/xpm"
"/usr/bin/convert -geometry 640x480 - xpm:-"))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; (defun ff/vm-remove-properties () (interactive)
;; (save-excursion
;; (goto-char (point-min))
;; (re-search-forward (concat "^" mail-header-separator "$"))
;; (set-text-properties (point) (point-max) nil)
;; )
;; )
;; (add-hook 'vm-mail-send-hook 'ff/vm-remove-properties)
;; (defun ff/vm-mime-save-all-files (&optional delete)
;; "Save all the mail attachments. With delete argument, remove
;; the attachement from mail."
;; (interactive "P")
;; (let ((vm-mime-delete-after-saving delete))
;; (while (and (vm-mime-reader-map-save-file)
;; (condition-case nil (vm-move-to-next-button 1)
;; (error nil)))))
;; )
(defun ff/vm-mime-save-file (&optional delete)
"Save the current attachement. With delete argument, remove the
attachement from mail."
(interactive "P")
(let ((vm-mime-delete-after-saving delete))
(vm-mime-reader-map-save-file))
(condition-case nil (vm-move-to-next-button 1) (error (message "No more attachment"))))
;; (define-key vm-summary-mode-map [(r)] 'vm-reply-include-text)
;; (define-key vm-summary-mode-map [(R)] 'vm-followup-include-text)
(define-key vm-summary-mode-map [(control o)] 'ff/vm-mime-save-file)
(define-key vm-summary-mode-map [(control t)]
(lambda () (interactive)
(vm-toggle-threads-display)
(unless vm-summary-show-threads
(vm-sort-messages "date"))))
(defun ff/vm-select-thread-for-next-command () (interactive)
(vm-mark-thread-subtree)
(vm-next-command-uses-marks))
(define-key vm-summary-mode-map "T" 'ff/vm-select-thread-for-next-command)
(defun ff/vm-attach-file-or-dir
(&optional dir)
"Attaches the file or recursively the content of the directory with
`vm-mime-attach-file'."
(interactive "fFile or directory: ")
(save-excursion
(goto-char (point-max))
(insert "\n")
(if (file-regular-p dir)
(vm-mime-attach-file dir (vm-mime-default-type-from-filename dir))
(if (file-directory-p dir)
(mapcar
(lambda (x)
(when (not (string-match "^\\." (car x)))
(ff/vm-attach-file-or-dir
(concat dir
(unless (string-match "/$" dir) "/")
(car x)))))
(directory-files-and-attributes dir)
)
(error "Can attach only files and directories")
))))
(define-key vm-mail-mode-map [(control c) (control a)] 'ff/vm-attach-file-or-dir)
;; Found no other way to avoid displaying the icones
(load "vm-mime")
(defun vm-mime-set-image-stamp-for-type (e type))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Summary stuff
(defun vm-summary-function-A (message)
(let* ((from (vm-su-from message)))
(if (string-match vm-summary-uninteresting-senders from)
(concat vm-summary-uninteresting-senders-arrow " " (ff/explicit-name (vm-su-to message)))
(ff/explicit-name from))))
(defun vm-summary-function-S (&optional message)
(let ((s (string-to-int (vm-su-byte-count message))))
(if (> s 32768)
(propertize (concat (int-to-string (/ s 1024)) "k")
'face 'italic
)
"")))
(defun ff/vm-delete-and-go-down () (interactive)
;; (vm-goto-message)
(vm-delete-message 1)
(condition-case nil (vm-next-message-no-skip 1) (error nil)))
(defun ff/vm-expunge-folder ()
(unless vm-folder-read-only (vm-expunge-folder)))
(add-hook 'vm-quit-hook 'ff/vm-expunge-folder)
(add-hook 'vm-quit-hook 'bbdb-save-db)
(add-hook 'vm-retrieved-spooled-mail-hook 'display-time-update)
(ff/configure-faces '((ff/summary-highlight-face :background "yellow"
;; :weight 'bold
)))
(setq vm-summary-highlight-face 'ff/summary-highlight-face)
(define-key vm-summary-mode-map [(K)] 'ff/vm-delete-and-go-down)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Activate the required modes and authorize the commenting
(defun ff/prepare-mail-mode ()
(bbdb-define-all-aliases)
(flyspell-mode)
(auto-fill-mode)
(mail-abbrevs-setup)
(yas/minor-mode)
;; (setq fill-paragraph-function 'mail-mode-fill-paragraph)
;; Since I set the comment prefix, I have to tell the filling
;; functions not to use it
;; ******************* removed Aug 23
;; (setq fill-paragraph-handle-comment nil)
;; ;; (when message-yank-prefix
(set (make-local-variable 'comment-start) vm-included-text-prefix)
;; (set (make-local-variable 'comment-start-skip)
;; (concat "^\\(" (regexp-quote vm-included-text-prefix) "\\)"))
;; ;; )
)
(add-hook 'mail-mode-hook 'ff/prepare-mail-mode)
;; (add-hook 'mail-mode-hook 'orgtbl-mode)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; To have a slightly darker background for headers
(ff/configure-faces
;; '((ff/mail-header-face :background "#c8c8ff"))
'((ff/mail-header-face :foreground "blue4"))
)
(defun ff/colorize-headers () (interactive)
(let ((inhibit-read-only t))
(save-excursion
(goto-char (point-min))
(while (vm-match-header)
(goto-char (vm-matched-header-end)))
(add-text-properties
;; (vm-matched-header-contents-start)
;; (vm-matched-header-contents-end)
(point-min)
(point-at-bol)
'(face ff/mail-header-face)
)
)))
(defadvice vm-highlight-headers (after ff/colorize-headers nil activate)
(ff/colorize-headers))
(defun ff/highlight-important-words ()
(let ((inhibit-read-only t))
(save-excursion
(goto-char (point-min))
(while (re-search-forward "Fleuret" nil t)
(message "%d-%d" (match-beginning 0) (match-end 0))
;; (add-text-properties (match-beginning 0) (match-end 0)
;; '(face (:background "red"))
;; )
))
))
;; (add-hook 'vm-select-message-hook 'ff/highlight-important-words)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; I want to have a file associated to every mail I am writing
(defcustom ff/vm-mail-draft-directory "~/"
"Where to save mail drafts with VM")
(defun ff/associate-file-to-vm-mail-buffer ()
"Associate the current buffer to a file whose name is built from the current time."
(unless (buffer-file-name)
(set-visited-file-name (format
"%s/mail-%s"
ff/vm-mail-draft-directory
(format-time-string
"%04Y%02m%02d-%02H%02M%02S"
(current-time))))
(set-buffer-modified-p nil)))
(add-hook 'mail-setup-hook 'ff/associate-file-to-vm-mail-buffer)
(defun ff/mail-header-field (field) (interactive)
"Grab the value of a certain field from the mail header."
(let ((s "no-subject"))
(save-excursion
(goto-char (point-min))
(let ((l (re-search-forward (concat "^" mail-header-separator "$") nil t)))
(when l
(goto-char (point-min))
(when (re-search-forward (concat "^" field ": ") l t nil)
(setq s (buffer-substring-no-properties (point) (point-at-eol))))
)
)
)
s))
(defun ff/dissociate-file-from-vm-mail-buffer ()
"Save the file under a new name and set the associated file to nil."
(let ((bn (buffer-file-name)))
(when bn
(set-visited-file-name
(concat (file-name-directory bn)
"sent-"
(file-name-nondirectory bn)
"-"
(replace-regexp-in-string "[^a-zA-Z0-9]+" "_"
(ff/mail-header-field "Subject"))
))
(save-buffer)
(set-visited-file-name nil))
)
)
(defun ff/find-file-in-vm-mail-mode (filename) (interactive)
;; No easy way to activate vm-mail-mode, so we create such a
;; buffer, erase its content and insert the file
(vm-compose-mail)
(when (file-exists-p filename)
(erase-buffer)
(insert-file filename))
(set-visited-file-name filename)
(set-buffer-modified-p nil)
;; (run-hooks find-file-hooks)
(when (functionp 'alarm-vc-check) (alarm-vc-check))
;; Move the cursor at a convenient location
(when (re-search-forward (concat "^" mail-header-separator "$") nil t)
(if (re-search-forward "^-- $" nil t)
(previous-line 1)
(next-line 1))
(end-of-line))
)
;; All this mess to activate the vm-mail-mode when loading a file
;; looking like a mail draft. Did I miss something ?
(defadvice find-file (around ff/find-file-or-mail
(filename &optional wildcards)
activate)
(interactive "FFind file: \np")
(if (string-match "^\\(mail\\|sent\\)-[^/]+$"
(file-name-nondirectory filename))
(if (find-buffer-visiting filename)
(switch-to-buffer (find-buffer-visiting filename))
(ff/find-file-in-vm-mail-mode filename))
ad-do-it
))
(setq ff/vm-mail-draft-directory "~/private/drafts")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Check there are no missing attachment (the idea comes from
;; http://home.cc.gatech.edu/eaganj/MailApp) and no leading "From"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defcustom ff/check-vm-attachement-regexp "attach"
"*A mail whose body matches this regular expression should contain
an attachment")
(defun ff/check-vm-attachment ()
(when (save-excursion
(goto-char (point-min))
(and (re-search-forward "\\[ATTACHMENT" nil t)
(not (get-text-property (point) 'vm-mime-object))))
(error "Buggy attachment"))
(if (and
(save-excursion (goto-char (point-min))
(re-search-forward ff/check-vm-attachement-regexp nil t))
(not (save-excursion (goto-char (point-min))
(re-search-forward "\\[ATTACHMENT" nil t)))
(not (y-or-n-p "An attachment seems to be missing, send message ? ")))
(error "You refer to an unexisting attachment."))
)
;; You can not have a line starting with "From:" in a pure text
;; mail. The smtp server would add a leading character to prevent it.
(defun ff/check-no-leading-from ()
(and (let ((case-fold-search nil))
(save-excursion
(goto-char (point-min))
(re-search-forward (concat "^" mail-header-separator "$"))
(re-search-forward "^From " nil t)))
(not (y-or-n-p "There is a leading ``From '', send message ? "))
(error "There is a leading ``From ''.")))
;; An attempt at limiting excess wording in sent mails
(defface ff/strong-words
'((t (:background "red")))
"The face to highlight upper caps, exclamation marks and such.")
(defun ff/max-in-a-row (overlay regexp max)
(let ((case-fold-search nil))
(save-excursion
(goto-char (point-min))
(re-search-forward (concat "^" mail-header-separator "$"))
(when (and (re-search-forward regexp nil t nil)
(>= (- (match-end 0) (match-beginning 0)) max))
(move-overlay overlay (match-beginning 0) (match-end 0))
t))))
(defun ff/check-no-excess-wording () (interactive)
(let ((overlay (make-overlay 0 0)))
(overlay-put overlay 'face 'media/current-tune-face)
(let ((err (and
(or (ff/max-in-a-row overlay "[A-Z\?\!][A-Z\?\! ]+[A-Z\?\!]" 6)
(ff/max-in-a-row overlay "[\?\!]+" 2)
)
(not (y-or-n-p "That does not look good. Send message ? ")))))
(delete-overlay overlay)
(when err (error "Good idea. Chill out a bit.")))
))
(defun ff/check-badly-encoded-address () (interactive)
(let (bodysep bad-adr)
(save-excursion
(goto-char (point-min))
(search-forward mail-header-separator)
(setq bodysep (vm-marker (match-beginning 0)))
(goto-char (point-min))
(setq bad-adr (re-search-forward "[^ (length alias) 1)
;; Do not overwrite an existing alias
(not (and mail-abbrevs (intern-soft alias mail-abbrevs))))
(define-mail-abbrev alias email))
(setq records (cdr records))))))
(when (>= emacs-major-version 22)
(bbdb-insinuate-vm)
(ff/mail-aliases-from-bbdb))
)
(defun ff/pipe-to-tmp () (interactive)
(let ((link "/tmp/attach")
(dir (format-time-string "/tmp/attach-%Y%m%d-%H%M%S" (current-time))))
(mkdir dir)
(when (file-symlink-p link) (delete-file link))
(unless (file-exists-p link)
(make-symbolic-link dir "/tmp/attach" 1))
(vm-pipe-message-to-command (concat "munpack -C " dir))
(message "Wrote files to %s" dir)
)
)
(defun ff/make-nonexisting-filename (filename)
(let ((root filename)
(extension "")
(result filename))
(when (file-exists-p result)
(when (string-match "^\\(.*\\)\\(\\.[^\\.]*\\)$" filename)
(setq root (match-string 1 filename)
extension (match-string 2 filename)))
(let ((n 0))
(while (file-exists-p (setq result (format "%s_%03d%s" root n extension)))
(setq n (+ n 1)))))
result))
(defun ff/vm-mime-save-all-attachments (&optional count
directory
no-delete-after-saving)
"Save all attachments in the next COUNT messages or marked
messages. For the purpose of this function, an \"attachment\" is
a mime part part which has \"attachment\" as its disposition or
simply has an associated filename. Any mime types that match
`vm-mime-savable-types' but not `vm-mime-savable-type-exceptions'
are also included.
The attachments are saved to the specified DIRECTORY. The
variables `vm-all-attachments-directory' or
`vm-mime-attachment-save-directory' can be used to set the
default location. When directory does not exist it will be
created."
(interactive
(list current-prefix-arg
(vm-read-file-name
"Attachment directory: "
(or vm-mime-all-attachments-directory
vm-mime-attachment-save-directory
default-directory)
(or vm-mime-all-attachments-directory
vm-mime-attachment-save-directory
default-directory)
nil nil
vm-mime-save-all-attachments-history)))
(vm-check-for-killed-summary)
(if (interactive-p) (vm-follow-summary-cursor))
(let ((n 0))
(vm-mime-action-on-all-attachments
count
;; the action to be performed BEGIN
(lambda (msg layout type file)
(let ((directory (if (functionp directory)
(funcall directory msg)
directory)))
(setq file
(if file
(expand-file-name (file-name-nondirectory file) directory)
(vm-read-file-name
(format "Save %s to file: " type)
(or directory
vm-mime-all-attachments-directory
vm-mime-attachment-save-directory)
(or directory
vm-mime-all-attachments-directory
vm-mime-attachment-save-directory)
nil nil
vm-mime-save-all-attachments-history)
))
(setq file (ff/make-nonexisting-filename file))
(when file
(message "Saving `%s%s" type (if file (format " (%s)" file) ""))
(make-directory (file-name-directory file) t)
(vm-mime-send-body-to-file layout file file)
(if vm-mime-delete-after-saving
(let ((vm-mime-confirm-delete nil))
(vm-mime-discard-layout-contents
layout (expand-file-name file))))
(setq n (+ 1 n)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; the action to be performed END
;; attachment filters
vm-mime-savable-types
vm-mime-savable-type-exceptions)
(when (interactive-p)
(vm-discard-cached-data)
(vm-preview-current-message))
(if (> n 0)
(message "%d attachment%s saved" n (if (= n 1) "" "s"))
(message "No attachments to be saved!"))))
(define-key vm-summary-mode-map [(control c) (control s)] 'ff/vm-mime-save-all-attachments)