* move emacs/ code to toys/mua; rename mu=>mua, many updates

This commit is contained in:
Dirk-Jan C. Binnema
2011-08-08 21:55:59 +03:00
parent 46c8e79fea
commit 74d00e26d4
11 changed files with 1306 additions and 1145 deletions

View File

@ -1,31 +0,0 @@
VERSION=$(shell git describe --tags --dirty)
EMACS=emacs
PREFIX=/usr/local
ELS=mu.el mu-common.el mu-view.el mu-headers.el
ELCS=$(ELS:.el=.elc)
.PHONY=install
BATCH=$(EMACS) -batch -q -no-site-file -eval \
"(setq load-path (cons (expand-file-name \".\") load-path))"
%.elc: %.el
$(BATCH) --eval '(byte-compile-file "$<")'
all: $(ELCS)
docs: mu.infogg
install_lisp:
mkdir -p $(DESTDIR)/$(PREFIX)/share/emacs/site-lisp
install -m 644 $(ELS) $(ELCS) $(DESTDIR)/$(PREFIX)/share/emacs/site-lisp
install_docs: docs
mkdir -p $(DESTDIR)/$(PREFIX)/share/info
install -m 644 mu.info $(DESTDIR)/$(PREFIX)/share/info
install-info --info-dir=$(DESTDIR)/$(PREFIX)/share/info $(DESTDIR)/$(PREFIX)/share/info/mu.info
install: install_lisp install_docs
clean:
rm -fr mu.info $(ELCS)

View File

@ -1,227 +0,0 @@
;;; mu-common.el -- part of mu
;;
;; Copyright (C) 2011 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Keywords: email
;; Version: 0.0
;; This file is not part of GNU Emacs.
;;
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; mu message has functions to display a message
;;; Code:
(require 'cl)
(defvar mu-binary "/home/djcb/src/mu/src/mu" "name/path of the mu executable")
(defvar mu-muile-binary "/home/djcb/src/mu/toys/muile/muile"
"name/path of the muile executable")
(defvar mu-home nil
"path where mu stores it's data or nil for the defaults (typically, ~/.mu)")
(defvar mu-date-format-short "%x %X" "date format (in strftime(2)
notation) e.g. for mail headers")
(defvar mu-date-format-long "%c" "date format (in strftime(2)
notation) for the mail view and in replied/forwarded message quotations")
(defvar mu-folder-draft "/home/djcb/Maildir/")
(defface mu-date-face '((t (:foreground "#8c5353"))) "")
(defface mu-subject-face '((t (:foreground "#dfaf8f"))) "")
(defface mu-from-face '((t (:foreground "#7f9f7f"))) "")
(defface mu-to-face '((t (:foreground "#7f6655"))) "")
(defface mu-cc-face '((t (:foreground "#7f6666"))) "")
(defface mu-bcc-face '((t (:foreground "#7f6677"))) "")
(defface mu-body-face '((t (:foreground "#8cd0d3"))) "")
(defface mu-header-face '((t (:foreground "#7f9f7f"))) "")
(defface mu-size-face '((t (:foreground "#889f7f"))) "")
(defface mu-flag-face '((t (:foreground "#dc56cc"))) "")
(defface mu-path-face '((t (:foreground "#dc56cc"))) "")
(defface mu-unread-face '((t (:bold t))) "")
(defface mu-face '((t (:foreground "Gray" :italic t))) "")
(defvar mu-own-address "djcb" "regexp matching my own address")
;;; internal stuff
(defvar mu-parent-buffer nil "the parent buffer for a
buffer (buffer-local), i.e., the buffer we'll return to when this
buffer is killed")
(defun mu-binary-version ()
"get the version of the mu binary"
(let ((cmd (concat mu-binary
" --version | head -1 | sed 's/.*version //'")))
(substring (shell-command-to-string cmd) 0 -1)))
(defun mu-inspect (path)
"inspect message in a guile environment"
(let ((cmd (concat mu-muile-binary " --msg='" path "'")))
(ansi-term cmd "*mu-inspect")))
;; (defalias mu-find mu-headers-find)
;; (defalias mu-display mu-message-display)
(defun mu-str (str)
"return STR propertized as a mu string (for info, warnings
etc.)"
(propertize str 'face 'mu-face 'intangible t))
(setq mu-headers-fields
'(
(:date . 20)
(:flags . 4)
(:from-or-to . 22)
(:size . 8)
(:subject . 40)))
(setq mu-headers-date-format "%x %X")
(setq mu-header-fields
'( :from
:to
:subject
:date
:path))
(setq mu-own-address-regexp "djcb\\|diggler\\|bulkmeel")
(defvar mu-maildir nil "our maildir")
(defvar mu-folder nil "our list of special folders for jumping,
moving")
(defvar mu-maildir nil "location of your maildir, typically ~/Maildir")
(defvar mu-inbox-folder nil "location of your inbox folder")
(defvar mu-outbox-folder nil "location of your outbox folder")
(defvar mu-sent-folder nil "location of your sent folder")
(defvar mu-trash-folder nil "location of your trash-folder folder")
(setq
mu-maildir "/home/djcb/Maildir"
mu-inbox-folder "/inbox"
mu-outbox-folder "/outbox"
mu-sent-folder "/sent"
mu-trash-folder "/trash")
(defvar mu-quick-folders nil)
(setq mu-quick-folders
'("/archive" "/bulkarchive" "/todo"))
(defun mu-ask-maildir (prompt &optional fullpath)
"ask user with PROMPT for a maildir name, if fullpath is
non-nill, return the fulpath (ie, mu-maildir prepended to the
maildir"
(interactive)
(let*
((showfolders
(delete-dups
(append (list mu-inbox-folder mu-sent-folder) mu-quick-folders)))
(chosen (ido-completing-read prompt showfolders)))
(concat (if fullpath mu-maildir "") chosen)))
(defun mu-ask-key (prompt)
"Get a char from user, only accepting characters marked with [x] in prompt,
e.g. 'Reply to [a]ll or [s]ender only; returns the character chosen"
(let ((match 0) (kars '()))
(while match
(setq match (string-match "\\[\\(.\\)\\]" prompt match))
(when match
(setq kars (cons (match-string 1 prompt) kars))
(setq match (+ 1 match))))
(let ((kar)
(prompt (replace-regexp-in-string
"\\[\\(.\\)\\]"
(lambda(s)
(concat "[" (propertize (substring s 1 -1) 'face 'highlight) "]"))
prompt)))
(while (not kar)
(setq kar (read-char-exclusive prompt))
(unless (member (string kar) kars)
(setq kar nil)))
kar)))
;; both in mu-find.el and mu-view.el we have the path as a text property; in the
;; latter case we could have use a buffer-local variable, but using a
;; text-property makes this function work for both
(defun mu-get-path ()
"get the path (a string) of the message at point or nil if it
is not found; this works both for the header list and when
viewing a message"
(let ((path (get-text-property (point) 'path)))
(unless path (message "No message at point"))
path))
;; The message sexp looks something like:
;; (
;; :from (("Donald Duck" . "donald@example.com"))
;; :to (("Mickey Mouse" . "mickey@example.com"))
;; :subject "Wicked stuff"
;; :date (20023 26572 0)
;; :size 15165
;; :msgid "foobar32423847ef23@pluto.net"
;; :path "/home/mickey/Maildir/inbox/cur/1312254065_3.32282.pluto,4cd5bd4e9:2,"
;; :priority high
;; :flags (new unread)
;; :body-txt " <message body>"
;; )
(defun mu-get-message (path)
"use 'mu view --format=sexp' to get the message at PATH in the
form of an s-expression; parse this s-expression and return the
Lisp data as a plist. Returns nil in case of error"
(if (not (file-readable-p path))
(progn (message "Message is not readable") nil)
(let* ((cmd (concat mu-binary " view --format=sexp " path))
(str (shell-command-to-string cmd))
(msglst (read-from-string str)))
(if msglst
(car msglst)
(progn (message "Failed to parse message") nil)))))
(defun mu-quit-buffer ()
"kill this buffer, and switch to it's parentbuf if it is alive"
(interactive)
(let ((parentbuf mu-parent-buffer))
(kill-buffer)
(when (and parentbuf (buffer-live-p parentbuf))
(switch-to-buffer parentbuf))))
(defun mu-get-new-buffer (bufname)
"return a new buffer BUFNAME; if such already exists, kill the
old one first"
(when (get-buffer bufname)
(kill-buffer bufname))
(get-buffer-create bufname))
(defun mu-log (frm &rest args)
(with-current-buffer (get-buffer-create "*mu-log*")
(goto-char (point-max))
(insert (apply 'format
(concat
(format-time-string "%x %X " (current-time))
frm "\n") args))))
(provide 'mu-common)

View File

@ -1,464 +0,0 @@
;;; mu-headers.el -- use `mu' from emacs
;; Copyright (C) 2011 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Keywords: email
;; Version: 0.0
;; This file is not part of GNU Emacs.
;;
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'mu-common)
;;; mu-headers has functions for displaying/manipulating a list of headers (ie.,
;;; one line descriptions of an e-mail message), based on the output of 'mu
;;; find'.
;; data is stored like the following: for each header-line, we take the (point)
;; at beginning-of-line (bol) and use that as the key in the mu-headers-hash
;; hash, which does
;;
;; point-of-bol -> path
;;
;; then, marks are stored in a seperate hash 'mu-headers-marks-hash, using
;;
;; point-of-bol -> (src . target)
;;
;; and note both 'delete' (target=/dev/null), trash (target=trash-folder), and
;; move can be expressed by that
;;
;; after the marks have been 'executed', the lines will be marked a *invisible*
;; instead of deleting them; that way, the 'point-of-bol' stays valid.
(defvar mu-headers-hash nil "internal: buffer-local hash table
which maps bol->path")
(defvar mu-headers-marks-hash nil "internal: buffer-local hash table
which maps bol->(src . target) for marked lines")
(defun mu-headers-set-path (path)
"map the bol of the current header to a path"
(puthash (line-beginning-position 1) path mu-headers-hash))
(defun mu-headers-get-path ()
"get the path for the header at point"
(gethash (line-beginning-position 1) mu-headers-hash))
(defvar mu-headers-fields
'( (:date . 25)
(:from-or-to . 22)
(:subject . 40))
"a list of fields and their widths")
(defvar mu-headers-sort-field "date"
"shortcut of the field to sort on (see mu-headers (1))")
(defvar mu-headers-sort-descending nil
"whether to sort in descending order")
;; internal stuff
(defconst mu-headers-buffer-name " *mu-headers*" "name of the mu
results buffer; name should start with a space")
(defvar mu-headers-process nil "the possibly running find process")
(defconst mu-eom "\n;;eom\n" "marker for the end of message in
the mu find output")
(defvar mu-headers-expression nil
"search expression for the current find buffer")
(defvar mu-buf "" "internal: buffer for results data")
(defun mu-headers-process-filter (proc str)
"process-filter for the 'mu find --format=sexp output; it
accumulates the strings into valid sexps by checking of the
';;eom' end-of-msg marker, and then evaluating them"
(when (buffer-live-p (process-buffer proc))
(with-current-buffer (process-buffer proc)
(save-excursion
(setq mu-buf (concat mu-buf str))
(let ((eom (string-match mu-eom mu-buf)))
(while (numberp eom)
(let* ((msg (car (read-from-string (substring mu-buf 0 eom))))
(inhibit-read-only t))
(goto-char (point-max))
(mu-headers-set-path (plist-get msg :path))
(save-match-data (insert (mu-headers-header msg) ?\n)))
(setq mu-buf (substring mu-buf (match-end 0)))
(setq eom (string-match mu-eom mu-buf))))))))
(defun mu-headers-process-sentinel (proc msg)
"Check the mu-headers process upon completion"
(when (buffer-live-p (process-buffer proc))
(with-current-buffer (process-buffer proc)
(let ((status (process-status proc))
(exit-status (process-exit-status proc)))
(if (memq status '(exit signal))
(let ((inhibit-read-only t)
(text
(cond
((eq status 'signal)
"Search process killed (results incomplete)")
((eq status 'exit)
(cond
((= 0 exit-status) "End of search results")
((= 2 exit-status) "No matches found")
((= 4 exit-status) "Database problem; try running 'mu index'")
(t (format "Some error occured; mu-headers returned %d"
exit-status))))
(t "Unknown status")))) ;; shouldn't happen
(save-excursion
(goto-char (point-max))
(insert (mu-str text)))))))))
;; Note, the 'mu find --format=sexp' sexp is almost the same as the ones that
;; 'mu view --format=sexp' produces (see mu-get-message), with the difference
;; that former may give more than one result, and that mu-headers output comes
;; from the database rather than file, and does _not_ contain the message body
(defun mu-headers-search (expr)
"search in the mu database"
(interactive "s[mu] search for: ")
(let* ((buf (mu-get-new-buffer mu-headers-buffer-name))
(dummy-arg "--fields=\"dummy\"") ;; ignored
(proc (start-process mu-headers-buffer-name buf
mu-binary
"find"
(if mu-home
(concat "--muhome=" mu-home) dummy-arg)
(if mu-headers-sort-field
(concat "--sortfield=" mu-headers-sort-field) dummy-arg)
(if mu-headers-sort-descending "--descending" dummy-arg)
"--format=sexp"
"--quiet"
expr)))
(mu-log "search: '%s'" expr)
(switch-to-buffer buf)
(mu-headers-mode)
(setq
mu-buf "" ;; if the last query went wrong...
mu-headers-expression expr
mu-headers-process proc
mu-headers-hash (make-hash-table :size 256 :rehash-size 2)
mu-headers-marks-hash (make-hash-table :size 16 :rehash-size 2))
(set-process-filter proc 'mu-headers-process-filter)
(set-process-sentinel proc 'mu-headers-process-sentinel)))
(defun mu-headers-field-contact (lst width face)
"display a list of contacts, truncated for fitting in WIDTH"
(if lst
(let* ((len (length lst))
(str (if (= len 0) "<none>"
;; try name -> email -> ?
(or (car(car lst)) (cdr(car lst)) "?")))
(others (if (> len 1) (mu-str (format " [+%d]" (- len 1))) "")))
(truncate-string-to-width
(concat(propertize (truncate-string-to-width str
(- width (length others)) 0 ?\s "...") 'face face) others)
width 0 ?\s))
(make-string width ?\s)))
(defun mu-headers-field-from-or-to (fromlst tolst width from-face to-face)
"return a propertized string for FROM unless TO matches
mu-own-address, in which case it returns TO, prefixed with To:"
(if (and fromlst tolst)
(let ((fromaddr (cdr(car fromlst))))
(if (and fromaddr (string-match mu-own-address fromaddr))
(concat (mu-str "To ") (mu-headers-field-contact tolst (- width 3) to-face))
(mu-headers-field-contact fromlst width from-face)))
(make-string width ?\s)))
(defun mu-headers-field-size (size width face)
"return a string for SIZE of WIDTH with FACE"
(let* ((str
(cond
((>= size 1000000) (format "%2.1fM" (/ size 1000000.0)))
((and (>= size 1000) (< size 1000000)) (format "%2.1fK" (/ size 1000.0)))
((< size 1000) (format "%d" size)))))
(propertize (truncate-string-to-width str width 0 ?\s) 'face face)))
(defun mu-headers-field-str (str width face)
"print a STR, at WIDTH (truncate or ' '-pad) with FACE"
(let ((str (if str str "")))
(propertize (truncate-string-to-width str width 0 ?\s t) 'face face)))
(defun mu-headers-field-flags (flags width face)
(let ((str
(mapconcat
(lambda(flag)
(let ((flagname (symbol-name flag)))
(cond
((string= flagname "unread") "U")
((string= flagname "seen") "S")
((string= flagname "replied") "R")
((string= flagname "attach") "a")
((string= flagname "encrypted") "x")
((string= flagname "signed") "s")))) flags "")))
(propertize (truncate-string-to-width str width 0 ?\s) 'face face)))
(defun mu-headers-field (msg fieldinfo)
"determine a field based on FIELDINFO in the header for MSG"
(let* ((field (car fieldinfo))
(width (cdr fieldinfo))
(val (plist-get msg field)) ;; note: header-field maps msg-field in
(str (case field ;; most cases..
(:date (mu-headers-field-str (format-time-string mu-date-format-short
val) width 'mu-date-face))
(:from (mu-headers-field-contact val width 'mu-from-face))
(:to (mu-headers-field-contact val width 'mu-to-face))
(:cc (mu-headers-field-contact val width 'mu-cc-face))
(:bcc (mu-headers-field-contact val width 'mu-bcc-face))
(:flags (mu-headers-field-flags val width 'mu-flag-face))
(:size (mu-headers-field-size val width 'mu-size-face))
(:subject (mu-headers-field-str val width 'mu-subject-face))
(:from-or-to ;; this one is special
(mu-headers-field-from-or-to (plist-get msg :from)
(plist-get msg :to) width 'mu-from-face 'mu-to-face)))))
str))
(defun mu-headers-header (msg)
"convert a message s-expression into a header for display, and
set text property 'path"
(concat " "
(mapconcat
(lambda (fieldinfo)
(mu-headers-field msg fieldinfo)) mu-headers-fields " ")))
(defun mu-headers-mode ()
"major mode for displaying search results"
(interactive)
(kill-all-local-variables)
(use-local-map mu-headers-mode-map)
(make-local-variable 'mu-buf)
(make-local-variable 'mu-parent-buffer)
(make-local-variable 'mu-headers-expression)
(make-local-variable 'mu-headers-process)
(make-local-variable 'mu-headers-hash)
(make-local-variable 'mu-headers-marks-hash)
(setq
major-mode 'mu-headers-mode mode-name "*headers*"
mu-buf ""
truncate-lines t buffer-read-only t
overwrite-mode 'overwrite-mode-binary))
(defvar mu-headers-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "s" 'mu-headers-search)
(define-key map "q" 'mu-quit-buffer)
(define-key map "s" 'mu-headers-change-sort)
(define-key map "g" 'mu-headers-refresh)
;; navigation
(define-key map "n" 'mu-headers-next)
(define-key map "p" 'mu-headers-previous)
(define-key map "j" 'mu-headers-jump-to-maildir)
;; marking/unmarking/executing
(define-key map "m" 'mu-headers-mark-for-move)
(define-key map "d" 'mu-headers-mark-for-trash)
(define-key map "D" 'mu-headers-mark-for-deletion)
(define-key map "u" 'mu-headers-unmark)
(define-key map "U" 'mu-headers-unmark-all)
(define-key map "x" 'mu-headers-marked-execute)
;; message composition
(define-key map "r" 'mu-reply)
(define-key map "f" 'mu-forward)
(define-key map (kbd "RET") 'mu-headers-view)
map)
"Keymap for \"mu-headers\" buffers.")
(fset 'mu-headers-mode-map mu-headers-mode-map)
(defun mu-headers-view ()
"display the message at the current line"
(interactive)
(let ((path (mu-headers-get-path)))
(when path (mu-view path (current-buffer)))))
(defun mu-headers-next ()
"go to the next line; t if it worked, nil otherwise"
(interactive)
(if (or (/= 0 (forward-line 1)) (not (mu-headers-get-path)))
(progn (message "No message after this one") nil)
t))
(defun mu-headers-prev ()
"go to the next line; t if it worked, nil otherwise"
(interactive)
(if (/= 0 (forward-line -1))
(progn (message "No message before this one") nil)
t))
(defun mu-headers-jump-to-maildir ()
"show the messages in one of the standard folders"
(interactive)
(let ((fld (mu-ask-maildir "Jump to maildir: ")))
(mu-headers-search (concat "maildir:" fld))))
(defun mu-headers-refresh ()
"re-run the query for the current search expression, but only
if the search process is not already running"
(interactive)
(message "REFRESH %s" mu-headers-expression)
(if (and mu-headers-process (eq (process-status mu-headers-process) 'run))
(message "Can't refresh while running")
(when mu-headers-expression (mu-headers mu-headers-expression))))
;; create a new query based on the old one, but with a changed sort order
(defun mu-headers-change-sort-order (fieldchar)
"change the sortfield to FIELDCHAR"
(interactive"cField to sort by ('d', 's', etc.; see mu-headers(1)):\n")
(let
((field
(case fieldchar
(?b "bcc")
(?c "cc")
(?d "date")
(?f "from")
(?i "msgid")
(?m "maildir")
(?p "prio")
(?s "subject")
(?t "to")
(?z "size"))))
(if field
(setq mu-headers-sort-field field)
(message "Invalid sort-field; use one of bcdfimpstz (see mu-headers(1)"))
field))
(defun mu-headers-change-sort-direction (dirchar)
"change the sort direction, either [a]scending or [d]escending"
(interactive
"cSorting direction ([a]scending or [d]escending):")
(cond
(?d (setq mu-headers-sort-descending t) t)
(?a (setq mu-headers-sort-descending nil) t)
(t (message
"Invalid sort-direction; choose either [a]scending or [d]escending") nil)))
(defun mu-headers-change-sort ()
"change sort field and direction"
(interactive)
(and (call-interactively 'mu-headers-change-sort-order)
(call-interactively 'mu-headers-change-sort-direction)))
(defun mu-headers-add-marked (src &optional dst)
(let ((bol (line-beginning-position 1)))
(if (gethash bol mu-headers-marks-hash)
(progn (message "Message is already marked") nil)
(progn (puthash bol (cons src dst) mu-headers-marks-hash) t))))
(defun mu-headers-remove-marked ()
(let ((bol (line-beginning-position 1)))
(if (not (gethash bol mu-headers-marks-hash))
(progn (message "Message is not marked") nil)
(progn (remhash bol mu-headers-marks-hash) t))))
(defun mu-headers-set-marker (kar)
"set the marker at the beginning of this line"
(beginning-of-line 1)
(let ((inhibit-read-only t))
(delete-char 2)
(insert (if kar kar " ") " ")))
(defun mu-headers-mark (action)
"mark the current msg for something: move, delete, trash, unmark"
(let ((target) (src (mu-headers-get-path)))
(when src
(case action
(move
(when (mu-headers-add-marked src
(mu-ask-maildir "Target maildir: " t))
(mu-headers-set-marker ?m)))
(trash
(when (mu-headers-add-marked src
(concat mu-maildir mu-trash-folder))
(mu-headers-set-marker ?d)))
(delete
(when (mu-headers-add-marked src "/dev/null")
(mu-headers-set-marker ?D)))
(unmark
(when (mu-headers-remove-marked)
(mu-headers-set-marker nil)))
(unmark-all
(when (y-or-n-p (format "Sure you want to remove all (%d) marks? "
(hash-table-count mu-headers-marks-hash)))
(save-excursion
(maphash (lambda (k v) (goto-char k) (mu-headers-mark 'unmark))
mu-headers-marks-hash)))
(t (message "Unsupported mark type"))))
(move-beginning-of-line 2))))
(defun mu-headers-marks-execute ()
"execute the actions for all marked messages"
(interactive)
(let ((n-marked (hash-table-count mu-headers-marks-hash)))
(if (= 0 n-marked)
(message "No marked messages")
(when (y-or-n-p
(format "Execute actions for %d marked message(s)? " n-marked))
(save-excursion
(maphash
(lambda(bol v)
(let ((src (car v)) (target (cdr v)) (inhibit-read-only t))
(when (mu-message-move src target)
(goto-char bol)
(mu-headers-remove-marked)
(put-text-property (line-beginning-position 1)
(line-beginning-position 2)
'invisible t)))) ;; when it succeedes, hide msg..)
mu-headers-marks-hash))
(message "Done")
))))
(defun mu-headers-mark-for-move () (interactive) (mu-headers-mark 'move))
(defun mu-headers-mark-for-trash () (interactive) (mu-headers-mark 'trash))
(defun mu-headers-mark-for-delete () (interactive) (mu-headers-mark 'delete))
(defun mu-headers-mark-for-deletion () (interactive) (mu-headers-mark 'delete))
(defun mu-headers-unmark () (interactive) (mu-headers-mark 'unmark))
(defun mu-headers-unmark-all () (interactive) (mu-headers-mark 'unmark-all))
(defun mu-headers-reply ()
"Reply to the message at point"
(interactive)
(let ((path (mu-headers-get-path)))
(if path
(mu-message-reply path)
(message "No message at point"))))
(defun mu-headers-forward ()
"Reply to the message at point"
(interactive)
(let ((path (mu-headers-get-path)))
(if path
(mu-message-forward path)
(message "No message at point"))))
(provide 'mu-headers)

View File

@ -1,165 +0,0 @@
;;; mu-message.el -- use `mu' from emacs
;; Copyright (C) 2011 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Keywords: email
;; Version: 0.0
;; This file is not part of GNU Emacs.
;;
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; mu-message contains code to generate a message for composing, replying or
;; forwarding
;;; Code:
(require 'mu-common)
(defvar mu-message-citation-prefix "> "
"string to prefix cited message parts with")
(defvar mu-message-reply-prefix "Re:"
"string to prefix the subject of replied messages with")
(defvar mu-message-forward-prefix "Fwd:"
"string to prefix the subject of forwarded messages with")
(defun mu-message-user-agent ()
(format "mu %s; emacs %s" (mu-binary-version) emacs-version))
(defun mu-message-attribution (msg)
"get an attribution line for a quoted message"
(format "On %s, %s wrote:\n"
(format-time-string mu-date-format-long (plist-get msg :date))
(cdr (car (plist-get msg :from)))))
(defun mu-message-cite (msg)
"cite an existing message"
(let ((body
(or (plist-get msg :body-txt)
(let ((html (plist-get msg :body-html)))
(when html
(with-temp-buffer (insert html) (html2text) (buffer-string))))
"")))
(replace-regexp-in-string "^" " > " body)))
(defun mu-message-recipients-remove (lst email-to-remove)
"remove the recipient with EMAIL from the recipient list (of
form '( (\"A\" . \"a@example.com\") (\"B\" . \"B@example.com\"))"
(remove-if (lambda (name-email)
(string= email-to-remove (downcase (cdr name-email))))
lst))
(defun mu-message-recipients-to-string (lst)
"convert a recipient list (of form '( (\"A\"
. \"a@example.com\") (\"B\" . \"B@example.com\") into a string
useful for from/to headers"
(message "recips: %S" lst)
(mapconcat
(lambda (recip)
(let ((name (car recip)) (email (cdr recip)))
(format "%s <%s>" (or name "") email))) lst ", "))
(defun mu-message-hidden-header (hdr val)
"return user-invisible header to the message (HDR: VAL\n)"
(propertize (format "%s: %s\n" hdr val) 'invisible t))
(defun mu-message-reply (path)
"create a reply to the message at PATH. After creation, switch
to the message editor"
(let* ((cmd (concat mu-binary " view --format=sexp " path))
(str (shell-command-to-string cmd))
(msg (car (read-from-string str)))
(buf (get-buffer-create
(generate-new-buffer-name "*mu-draft*")))
(to-lst (mu-message-recipients-remove
(append (plist-get msg :from) (plist-get msg :to))
user-mail-address))
(cc-lst (mu-message-recipients-remove (plist-get msg :cc)
user-mail-address)))
(with-current-buffer buf
(insert
(format "From: %s <%s>\n" user-full-name user-mail-address)
(mu-message-hidden-header "User-agent" (mu-message-user-agent))
(if (boundp 'mail-reply-to) (insert (format "Reply-To: %s\n"
mail-reply-to)) "")
(format "To: %s\n" (if to-lst (mu-message-recipients-to-string to-lst) ""))
(if cc-lst
(format "Cc: %s\n" (mu-message-recipients-to-string cc-lst)))
"Subject: " mu-message-reply-prefix (plist-get msg :subject) "\n"
"--text follows this line--\n\n"
(mu-message-attribution msg)
(mu-message-cite msg)))
(switch-to-buffer buf)
(message-mode)
(message-goto-body)))
(defun mu-message-forward (path)
"create a forward to the message at PATH. After creation, switch
to the message editor"
(let* ((cmd (concat mu-binary " view --format=sexp " path))
(str (shell-command-to-string cmd))
(msg (car (read-from-string str)))
(buf (get-buffer-create
(generate-new-buffer-name "*mu-draft*"))))
(with-current-buffer buf
(insert
(format "From: %s <%s>\n" user-full-name user-mail-address)
(mu-message-hidden-header "User-agent" (mu-message-user-agent))
"To: \n"
"Subject: " mu-message-forward-prefix (plist-get msg :subject) "\n"
"--text follows this line--\n\n"
(mu-message-attribution msg)
(mu-message-cite msg)))
(switch-to-buffer buf)
(message-mode)
(message-goto-to)))
(defun mu-message-move (src targetdir)
"move message at PATH using 'mu mv'; if targetdir is
'/dev/null', move immediately. Return t if succeeded, nil
otherwise"
(let* ((cmd (concat
mu-binary " mv --printtarget "
(shell-quote-argument src) " "
(shell-quote-argument targetdir)))
(fulltarget (shell-command-to-string cmd)))
(mu-log cmd)
(mu-log
(if fulltarget (concat "Message has been moved to " fulltarget)
"Message moving failed"))
;; now, if saving worked, anynchronously try to update the database
(when fulltarget
(mu-log "Removing from database: %s" src)
(start-process " *mu-remove*" nil mu-binary "remove" src)
(if (string= targetdir "/dev/null")
t
(mu-log "Adding to database: %s" fulltarget)
(start-process " *mu-add*" nil mu-binary "add" fulltarget) t))))
;; note, we don't check the result of the db output
(provide 'mu-message)

View File

@ -1,221 +0,0 @@
;;; mu-view.el -- use `mu' from emacs
;; Copyright (C) 2011 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Keywords: email
;; Version: 0.0
;; This file is not part of GNU Emacs.
;;
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs 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 theq
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; mu message has functions to display a single message
;;; Code:
(require 'mu-common)
(defvar mu-view-header-fields
'( :from
:to
:subject
:date
:attachments
:path)
"list of header fields to display in the message view")
(defconst mu-view-buffer-name " *mu-view*")
(defvar mu-view-headers-buffer nil "the headers buffer (if any)
from which this buffer was invoked (buffer local)")
(defun mu-view-header (field val val-face)
"get a header string (like 'Subject: foo')"
(when val
(concat (propertize field 'face 'mu-header-face) ": "
(propertize val 'face val-face) "\n")))
(defun mu-view-header-contact (field lst face)
(when lst
(let* ((header (concat (propertize field 'face 'mu-header-face) ": "))
(val (mapconcat (lambda(c)
(propertize (or (car c) (cdr c) "?") 'face face))
lst ",")))
(concat header val "\n"))))
(defun mu-view-header-contact (field lst face)
(when lst
(let* ((header (concat (propertize field 'face 'mu-header-face) ": "))
(val (mapconcat (lambda(c)
(propertize (or (car c) (cdr c) "?") 'face face))
lst ", ")))
(concat header val "\n"))))
(defun mu-view-header-attachments (field lst face)
(when lst
(let* ((header (concat (propertize field 'face 'mu-header-face) ": "))
(val (mapconcat
(lambda(att)
(let ((idx (nth 0 att)) (fname (nth 1 att)) (ctype (nth 2 att)))
(propertize fname 'face face)))
lst ", ")))
(concat header val "\n"))))
(defun mu-view-body (msg face)
"view the body; try text first, if that does not work, try html"
(cond
((plist-get msg :body-txt) (propertize (plist-get msg :body-txt) 'face face))
((plist-get msg :body-html)
(propertize
(with-temp-buffer
(insert (plist-get msg :body-html))
(html2text)
(buffer-string)) 'face face))
(t "")))
(defun mu-view-message (path)
"display the email message at PATH"
(let ((msg (mu-get-message path)))
(when msg
(concat
(mapconcat
(lambda (field)
(case field
(:from (mu-view-header-contact "From"
(plist-get msg :from) 'mu-from-face))
(:to
(mu-view-header-contact "To" (plist-get msg :to) 'mu-to-face))
(:cc
(mu-view-header-contact "Cc" (plist-get msg :cc) 'mu-to-face))
(:bcc
(mu-view-header-contact "Bcc" (plist-get msg :bcc) 'mu-to-face))
(:subject
(mu-view-header "Subject" (plist-get msg :subject) 'mu-subject-face))
(:path
(mu-view-header "Path" (plist-get msg :path) 'mu-path-face))
(:date
(mu-view-header "Date"
(format-time-string mu-date-format-long
(plist-get msg :date)) 'mu-date-face))
(:attachments
(mu-view-header-attachments "Attachments" (plist-get msg :attachments)
'mu-path-face)
)))
mu-view-header-fields "")
"\n"
(mu-view-body msg 'mu-body-face)
))))
;; note: mu-view sets path as a text-property ('path) for the whole buffer, just
;; like mu-headers does it per-header
(defun mu-view (path parentbuf)
"display message at PATH in a new buffer; note that the action
of viewing a message may cause it to be moved/renamed; this
function returns the resulting name. PARENTBUF refers to the
buffer who invoked this view; this allows us to return there when
we quit from this view. Also, if PARENTBUF is a find buffer (ie.,
has mu-headers-mode as its major mode), this allows various
commands (navigation, marking etc.) to be applied to this
buffer."
(let ((str (mu-view-message path))
(buf (mu-get-new-buffer mu-view-buffer-name)))
(when str
(switch-to-buffer buf)
(insert str))
(mu-view-mode)
(setq ;; these are buffer-local
mu-parent-buffer parentbuf
mu-view-headers-buffer parentbuf
mu-path path)
(goto-char (point-min))))
(defvar mu-view-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "q" 'mu-quit-buffer)
(define-key map "s" 'mu-headers)
(define-key map "f" 'mu-forward)
(define-key map "r" 'mu-reply)
;; navigation between messages
(define-key map "n" 'mu-view-next)
(define-key map "p" 'mu-view-prev)
;; marking/unmarking
(define-key map "d" '(lambda (mu-view-mark 'trash)))
(define-key map "D" '(lambda (mu-view-mark 'delete)))
(define-key map "m" '(lambda (mu-view-mark 'move)))
(define-key map "u" '(lambda (mu-view-mark 'unmark)))
(define-key map "x" 'mu-view-marked-execute)
map)
"Keymap for \"mu-view\" buffers.")
(fset 'mu-view-mode-map mu-view-mode-map)
(defun mu-view-mode ()
"major mode for viewing an e-mail message"
(interactive)
(kill-all-local-variables)
(use-local-map mu-view-mode-map)
(make-local-variable 'mu-parent-buffer)
(make-local-variable 'mu-headers-buffer)
(make-local-variable 'mu-path)
(setq major-mode 'mu-view-mode mode-name "*mu-view*")
(setq truncate-lines t buffer-read-only t))
(defmacro with-current-headers-buffer (&rest body)
"Execute the forms in BODY with BUFFER-OR-NAME temporarily current.
BUFFER-OR-NAME must be a buffer or the name of an existing buffer.
The value returned is the value of the last form in BODY. See
also `with-temp-buffer'."
(declare (indent 1) (debug t))
`(if (and mu-view-headers-buffer (buffer-live-p mu-view-headers-buffer))
(save-current-buffer
(set-buffer mu-view-headers-buffer)
,@body)
(message "No headers-buffer connected")))
(defun mu-view-next ()
"move to the next message"
(interactive)
(with-current-headers-buffer
(when (mu-headers-next)
(mu-view (mu-headers-get-path) (current-buffer)))))
(defun mu-view-prev ()
"move to the previous message"
(interactive)
(with-current-headers-buffer
(when (mu-headers-prev)
(mu-view (mu-headers-get-path) (current-buffer)))))
(defun mu-view-mark (mark)
"mark for MARK"
(interactive)
(with-current-headers-buffer (mu-headers-mark mark)))
;; we don't allow executing marks from the view buffer, to protect user from
;; accidentally deleting stuff...
(defun mu-view-marked-execute ()
"give user a warning"
(interactive)
(message "Please go back to the headers list to execute your marks"))
(provide 'mu-view)

View File

@ -1,68 +0,0 @@
;;; mu.el -- use `mu' from emacs
;; Copyright (C) 2011 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Keywords: email
;; Version: 0.0
;; This file is not part of GNU Emacs.
;;
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'mu-view)
(require 'mu-headers)
(require 'mu-message)
(define-key mu-headers-mode-map "q" 'mu-quit-buffer)
(define-key mu-headers-mode-map "f" 'mu-headers)
(define-key mu-headers-mode-map (kbd "<up>") 'mu-headers-prev)
(define-key mu-headers-mode-map (kbd "<down>") 'mu-headers-next)
(define-key mu-headers-mode-map (kbd "RET") 'mu-headers-view)
(define-key mu-headers-mode-map "n" 'mu-headers-next)
(define-key mu-headers-mode-map "p" 'mu-headers-prev)
(define-key mu-headers-mode-map "o" 'mu-headers-change-sort)
(define-key mu-headers-mode-map "g" 'mu-headers-refresh)
(define-key mu-headers-mode-map "m" 'mu-headers-mark-for-move)
(define-key mu-headers-mode-map "d" 'mu-headers-mark-for-trash)
(define-key mu-headers-mode-map "D" 'mu-headers-mark-for-deletion)
(define-key mu-headers-mode-map "u" 'mu-headers-unmark)
(define-key mu-headers-mode-map "U" 'mu-headers-unmark-all)
(define-key mu-headers-mode-map "r" 'mu-headers-reply)
(define-key mu-headers-mode-map "f" 'mu-headers-forward)
(define-key mu-headers-mode-map "x" 'mu-headers-marks-execute)
(define-key mu-view-mode-map "q" 'mu-quit-buffer)
(define-key mu-view-mode-map "f" 'mu-view-find)
(define-key mu-view-mode-map "n" 'mu-view-next)
(define-key mu-view-mode-map "p" 'mu-view-prev)
(define-key mu-view-mode-map "r" 'mu-reply)
(define-key mu-view-mode-map "f" 'mu-forward)
(define-key mu-view-mode-map "x" 'mu-execute)
(define-key mu-view-mode-map "m" 'mu-view-mark-for-move)
(define-key mu-view-mode-map "d" 'mu-view-mark-for-trash)
(define-key mu-view-mode-map "D" 'mu-view-mark-for-deletion)
(define-key mu-view-mode-map "u" 'mu-view-unmark)
(define-key mu-view-mode-map "U" 'mu-view-unmark-all)
(define-key mu-view-mode-map "r" 'mu-view-reply)
(define-key mu-view-mode-map "f" 'mu-view-forward)
(define-key mu-view-mode-map "x" 'mu-view-marked-execute)
(provide 'mu)