* remove the mua toy emacs ui -- the action now happens in 'mm'
This commit is contained in:
@ -1,41 +0,0 @@
|
||||
EMACS=emacs
|
||||
PREFIX=/usr/local
|
||||
ELS=mua.el mua-common.el mua-view.el mua-hdrs.el mua-msg.el
|
||||
ELCS=$(ELS:.el=.elc)
|
||||
|
||||
.PHONY=install
|
||||
|
||||
top_srcdir=/home/djcb/src/mu/
|
||||
|
||||
|
||||
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)
|
||||
|
||||
BUILT_SOURCES=mu-errors.el
|
||||
|
||||
mu-errors.el: ${top_srcdir}/src/mu-util.h
|
||||
@cat ${top_srcdir}/src/mu-util.h \
|
||||
|
||||
|
||||
|
||||
|
||||
docs: mua.info
|
||||
|
||||
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 mua.info $(ELCS)
|
||||
@ -1,32 +0,0 @@
|
||||
* TODO
|
||||
|
||||
[ ] message un-new in find/view
|
||||
[ ] set 'Replied' flag on source when message is replied
|
||||
[ ] save message to draft, sent items
|
||||
[ ] attachment handling (open, play) in view
|
||||
|
||||
[ ] fix flags in src/
|
||||
[ ] version check
|
||||
|
||||
[ ] make add, remove async (use async buffer)
|
||||
|
||||
[ ] threads support
|
||||
[ ] expandable recipients list in view
|
||||
[ ] additive font props in mu find
|
||||
[ ] fix headers/view interaction
|
||||
|
||||
|
||||
[ ] region commands
|
||||
[ ] menu
|
||||
[ ] mua-dashboard
|
||||
|
||||
|
||||
|
||||
|
||||
# Local Variables:
|
||||
# mode: org; org-startup-folded: nil
|
||||
# End:
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,88 +0,0 @@
|
||||
;;; mua-common.el -- part of mua, the mu mail user agent
|
||||
;;
|
||||
;; 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:
|
||||
|
||||
;; mua-common contains common utility functions for mua
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(require 'ido)
|
||||
|
||||
(defconst mua/log-buffer-name "*mua-log*" "name of the logging buffer")
|
||||
|
||||
(defun mua/warn (frm &rest args)
|
||||
"warn user in echo-area, return nil"
|
||||
(let ((str (apply 'format frm args)))
|
||||
(message str)
|
||||
nil))
|
||||
|
||||
(defun mua/log (frm &rest args)
|
||||
"write something in the *mua-log* buffer - mainly useful for debugging"
|
||||
(with-current-buffer (get-buffer-create mua/log-buffer-name)
|
||||
(goto-char (point-max))
|
||||
(insert (apply 'format (concat (format-time-string "%x %X " (current-time))
|
||||
frm "\n") args))))
|
||||
|
||||
(defun mua/warn-and-log (frm &rest args)
|
||||
"log and warn (ie., mua/warn + mua/log); return nil"
|
||||
(apply 'mua/log frm args)
|
||||
(apply 'mua/warn frm args)
|
||||
nil)
|
||||
|
||||
(defun mua/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 mua/message (frm &rest args)
|
||||
"print a message at point"
|
||||
(let ((str (apply 'format frm args)) (inhibit-read-only t))
|
||||
(insert (propertize str 'face 'italic))))
|
||||
|
||||
(defun mua/quit-buffer ()
|
||||
"kill this buffer, and switch to it's parentbuf if it is alive"
|
||||
(interactive)
|
||||
(let ((parentbuf mua/parent-buffer))
|
||||
(kill-buffer)
|
||||
(when (and parentbuf (buffer-live-p parentbuf))
|
||||
(switch-to-buffer parentbuf))))
|
||||
|
||||
(defun mua/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
|
||||
(append (list mua/inbox-folder mua/drafts-folder mua/sent-folder)
|
||||
mua/working-folders))
|
||||
(chosen (ido-completing-read prompt showfolders)))
|
||||
(concat (if fullpath mua/maildir "") chosen)))
|
||||
|
||||
|
||||
(provide 'mua-common)
|
||||
@ -1,491 +0,0 @@
|
||||
;;; mua-hdrs.el -- part of mua, the mu mail user agent
|
||||
;;
|
||||
;; 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:
|
||||
|
||||
;; In this file are function related to creating the list of one-line
|
||||
;; descriptions of emails, aka 'headers' (not to be confused with headers like
|
||||
;; 'To:' or 'Subject:')
|
||||
|
||||
;; mu
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(require 'mua-common)
|
||||
(require 'mua-msg)
|
||||
|
||||
;; note: these next two are *not* buffer-local, so they persist during a session
|
||||
(defvar mua/hdrs-sortfield nil
|
||||
"*internal* Field to sort headers by")
|
||||
(defvar mua/hdrs-sort-descending nil
|
||||
"*internal Whether to sort in descending order")
|
||||
|
||||
(defvar mua/hdrs-fields
|
||||
'( (:date . 25)
|
||||
(:from-or-to . 22)
|
||||
(:subject . 40))
|
||||
"A list of header fields and their character widths")
|
||||
|
||||
;; internal stuff
|
||||
(defvar mua/buf ""
|
||||
"*internal* Buffer for results data.")
|
||||
(defvar mua/last-expression nil
|
||||
"*internal* The most recent search expression.")
|
||||
(defvar mua/hdrs-proc nil
|
||||
"*internal* The mu-find process.")
|
||||
|
||||
(defconst mua/eom-mark "\n;;eom\n"
|
||||
"*internal* Marker for the end of message in the mu find
|
||||
output.")
|
||||
(defconst mua/hdrs-buffer-name "*mua-headers*"
|
||||
"*internal* Name of the mua headers buffer.")
|
||||
|
||||
(defun mua/hdrs-proc-filter (proc str)
|
||||
"A 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."
|
||||
(setq mua/buf (concat mua/buf str)) ;; update our buffer
|
||||
(let ((buf (process-buffer proc))) ;; check the buffer
|
||||
(unless (buffer-live-p buf)
|
||||
(error "No live buffer for process filter"))
|
||||
(while ;; for-each-sex
|
||||
;; Process the sexp in `mua/buf', and remove it if it worked and return
|
||||
;; t. If no complete sexp is found, return nil."
|
||||
(let ((eom (string-match mua/eom-mark mua/buf))
|
||||
(after-eom (match-end 0)) (inhibit-read-only t))
|
||||
(when (numberp eom) ;; was the marker found?
|
||||
(with-current-buffer buf
|
||||
(mua/hdrs-append-message (mua/msg-from-string
|
||||
(substring mua/buf 0 eom))))
|
||||
(setq mua/buf (substring mua/buf after-eom)) t)))))
|
||||
|
||||
|
||||
(defun mua/hdrs-proc-sentinel (proc msg)
|
||||
"Sentinel funtion for the mu-find process -- ie., will be called upon its ."
|
||||
(let ((procbuf (process-buffer proc))
|
||||
(status (process-status proc))
|
||||
(exit-status (process-exit-status proc)))
|
||||
(when (and (buffer-live-p procbuf) (memq status '(exit signal)))
|
||||
(let ((msg
|
||||
(case status
|
||||
('signal "Search process killed (results incomplete)")
|
||||
('exit
|
||||
(if (= 0 exit-status)
|
||||
"End of search results"
|
||||
(mua/mu-error exit-status))))))
|
||||
(with-current-buffer procbuf
|
||||
(save-excursion
|
||||
(goto-char (point-max))
|
||||
(mua/message "%s" msg)))))))
|
||||
|
||||
(defun mua/hdrs-search-execute (expr)
|
||||
"Search in the mu database, and output the results in the current
|
||||
buffer."
|
||||
(let* ((argl
|
||||
(remove-if 'not
|
||||
(list "find" "--format=sexp" "--threads"
|
||||
(when mua/mu-home (concat "--muhome=" mua/mu-home))
|
||||
(when mua/hdrs-sortfield
|
||||
(concat "--sortfield=" mua/hdrs-sortfield))
|
||||
(when mua/hdrs-sort-descending "--descending")
|
||||
expr)))
|
||||
(mua/buf "")
|
||||
;; start the process
|
||||
(proc (apply 'start-process
|
||||
mua/hdrs-buffer-name (current-buffer) mua/mu-binary argl)))
|
||||
(setq mua/hdrs-proc proc)
|
||||
(set-process-filter proc 'mua/hdrs-proc-filter)
|
||||
(set-process-sentinel proc 'mua/hdrs-proc-sentinel)
|
||||
(mua/log (concat mua/mu-binary " " (mapconcat 'identity argl " ")))))
|
||||
|
||||
;; 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 mua/hdrs-search (expr)
|
||||
"Search in the mu database for EXPR, and switch to the output
|
||||
buffer for the results."
|
||||
(interactive "s[mu] search for: ")
|
||||
;; kill a running process if needed
|
||||
(when (and mua/hdrs-proc (eq (process-status mua/hdrs-proc) 'run))
|
||||
(kill-process mua/hdrs-proc))
|
||||
(let ((buf (mua/new-buffer mua/hdrs-buffer-name)))
|
||||
(switch-to-buffer buf)
|
||||
(mua/hdrs-mode)
|
||||
(mua/hdrs-search-execute expr)))
|
||||
|
||||
|
||||
(defun mua/hdrs-mode ()
|
||||
"Major mode for displaying mua search results."
|
||||
(interactive)
|
||||
(kill-all-local-variables)
|
||||
(use-local-map mua/hdrs-mode-map)
|
||||
|
||||
(make-local-variable 'mua/buf)
|
||||
(make-local-variable 'mua/last-expression)
|
||||
(make-local-variable 'mua/hdrs-proc)
|
||||
(make-local-variable 'mua/hdrs-hash)
|
||||
(make-local-variable 'mua/hdrs-marks-hash)
|
||||
|
||||
(setq
|
||||
mua/last-expression expr
|
||||
mua/hdrs-marks-hash (make-hash-table :size 16 :rehash-size 2)
|
||||
major-mode 'mua/mua/hdrs-mode mode-name "*mua-headers*"
|
||||
truncate-lines t
|
||||
buffer-read-only t
|
||||
overwrite-mode 'overwrite-mode-binary))
|
||||
|
||||
(defun mua/hdrs-line (msg)
|
||||
"Return line describing a message (ie., a header line)."
|
||||
(mapconcat
|
||||
(lambda(fieldpair)
|
||||
(let ((field (car fieldpair)) (width (cdr fieldpair)))
|
||||
(case field
|
||||
(:subject (mua/hdrs-header msg :subject width))
|
||||
(:to (mua/hdrs-contact msg field width))
|
||||
(:from (mua/hdrs-contact msg field width))
|
||||
;;(:from-or-to (mua/msg-header-header-from-or-to msg width 'mua/header-face))
|
||||
(:cc (mua/hdrs-contact msg field width))
|
||||
(:bcc (mua/hdrs-contact msg field width))
|
||||
(:date (mua/hdrs-date msg width))
|
||||
(:flags (mua/hdrs-flags msg width))
|
||||
(:size (mua/hdrs-size msg width))
|
||||
(t (error "Unsupported field: %S" field)))))
|
||||
mua/header-fields " "))
|
||||
|
||||
;;
|
||||
;; Note: we maintain a hash table to remember what message-path corresponds to a
|
||||
;; certain line in the buffer. (mua/hdrs-set-path, mua/hdrs-get-path)
|
||||
;;
|
||||
;; 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
|
||||
;;
|
||||
(defun mua/hdrs-get-uid ()
|
||||
"Get the uid for the message header at point."
|
||||
(get-text-property (point) 'uid))
|
||||
|
||||
(defun mua/hdrs-get-path ()
|
||||
"Get the current path for the header at point."
|
||||
(mua/msg-map-get-path (mua/hdrs-get-uid)))
|
||||
|
||||
(defun mua/hdrs-append-message (msg)
|
||||
"Append a one-line description of MSG to the buffer, and register
|
||||
it with `mua/msg-map-add' to `mua/msg-map'; add the uid for this
|
||||
message as a text-property `uid'."
|
||||
(let* ((uid (mua/msg-map-add (mua/msg-field msg :path)))
|
||||
(line (propertize (concat " " (mua/hdrs-line msg) "\n") 'uid uid))
|
||||
(inhibit-read-only t))
|
||||
(save-excursion
|
||||
(goto-char (point-max))
|
||||
(insert line))))
|
||||
|
||||
|
||||
|
||||
;; Now follow a bunch of function to turn some message field in a
|
||||
;; string for display
|
||||
|
||||
(defun mua/hdrs-header (msg field width)
|
||||
"Get a string at WIDTH (truncate or ' '-pad) for display as a
|
||||
header."
|
||||
(let* ((str (mua/msg-field msg field)) (str (if str str "")))
|
||||
(propertize (truncate-string-to-width str width 0 ?\s t)
|
||||
'face 'mua/header-face)))
|
||||
|
||||
(defun mua/hdrs-contact (msg field width)
|
||||
"get display string for a list of contacts in a header, truncated for
|
||||
fitting in WIDTH"
|
||||
(unless (member field '(:to :from :bcc :cc))
|
||||
(error "Illegal type for contact"))
|
||||
(let* ((lst (mua/msg-field msg field))
|
||||
(str (mapconcat
|
||||
(lambda (ctc)
|
||||
(let ((name (car ctc)) (email (cdr ctc)))
|
||||
(or name email "?"))) lst ",")))
|
||||
(propertize (truncate-string-to-width str width 0 ?\s t)
|
||||
'face 'mua/contacts-face)))
|
||||
|
||||
|
||||
(defun mua/hdrs-size (msg width)
|
||||
"return a string for size of MSG of WIDTH"
|
||||
(let* ((size (mua/msg-field msg :size))
|
||||
((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 'mua/header-face))))
|
||||
|
||||
|
||||
(defun mua/hdrs-date (msg width)
|
||||
"Return a string for the date of MSG of WIDTH."
|
||||
(let* ((date (mua/msg-field msg :date)))
|
||||
(if date
|
||||
(propertize (truncate-string-to-width (format-time-string "%x %X" date)
|
||||
width 0 ?\s) 'face 'mua/date-face))))
|
||||
|
||||
(defun mua/hdrs-flags (msg width)
|
||||
"Return a string describing the flags of MSG at WIDTH."
|
||||
(let ((flagstr (mua/msg-flags-to-string (mua/msg-field msg :flags))))
|
||||
(propertize (truncate-string-to-width flagstr width 0 ?\s)
|
||||
'face 'mua/header-face)))
|
||||
|
||||
|
||||
;; some keybinding / functions for basic navigation
|
||||
|
||||
(defvar mua/hdrs-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
|
||||
(define-key map "s" 'mua/hdrs-search)
|
||||
(define-key map "q" 'mua/quit-buffer)
|
||||
(define-key map "o" 'mua/hdrs-change-sort)
|
||||
(define-key map "g" 'mua/hdrs-refresh)
|
||||
|
||||
;; navigation
|
||||
(define-key map "n" 'mua/hdrs-next)
|
||||
(define-key map "p" 'mua/hdrs-prev)
|
||||
(define-key map "j" 'mua/hdrs-jump-to-maildir)
|
||||
|
||||
;; marking/unmarking/executing
|
||||
(define-key map "m" (lambda()(interactive)(mua/hdrs-mark 'move)))
|
||||
(define-key map "d" (lambda()(interactive)(mua/hdrs-mark 'trash)))
|
||||
(define-key map "D" (lambda()(interactive)(mua/hdrs-mark 'delete)))
|
||||
(define-key map "u" (lambda()(interactive)(mua/hdrs-mark 'unmark)))
|
||||
(define-key map "U" (lambda()(interactive)(mua/hdrs-mark 'unmark-all)))
|
||||
(define-key map "x" 'mua/hdrs-marks-execute)
|
||||
|
||||
;; message composition
|
||||
(define-key map "r" 'mua/hdrs-reply)
|
||||
(define-key map "f" 'mua/hdrs-forward)
|
||||
(define-key map "c" 'mua/hdrs-compose)
|
||||
|
||||
(define-key map (kbd "RET") 'mua/hdrs-view)
|
||||
map)
|
||||
"Keymap for *mua-headers* buffers.")
|
||||
(fset 'mua/hdrs-mode-map mua/hdrs-mode-map)
|
||||
|
||||
(defun mua/hdrs-next ()
|
||||
"go to the next line; t if it worked, nil otherwise"
|
||||
(interactive) ;; TODO: check if next line has path, if not, don't go there
|
||||
(if (or (/= 0 (forward-line 1)) (not (mua/hdrs-get-path)))
|
||||
(mua/warn "No message after this one")
|
||||
t))
|
||||
|
||||
(defun mua/hdrs-prev ()
|
||||
"Go to the previous line; t if it worked, nil otherwise."
|
||||
(when (buffer-live-p mua/hdrs-buffer)
|
||||
(with-current-buffer mua/hdrs-buffer
|
||||
(if (or (/= 0 (forward-line -1)) (not (mua/hdrs-get-uid)))
|
||||
(mua/warn "No message before this one")))
|
||||
(when mua/view-uid ;; are we in view buffer?
|
||||
(mua/view (mua/hdrs-get-uid) mua/hdrs-buffer))))
|
||||
|
||||
(defun mua/hdrs-view ()
|
||||
(interactive)
|
||||
(let ((uid (mua/hdrs-get-uid)))
|
||||
(if uid
|
||||
(mua/view uid (current-buffer))
|
||||
(mua/warn "No message at point"))))
|
||||
|
||||
(defun mua/hdrs-jump-to-maildir ()
|
||||
"Show the messages in one of the standard folders."
|
||||
(interactive)
|
||||
(let ((fld (mua/ask-maildir "Jump to maildir: ")))
|
||||
(mua/hdrs-search (concat "maildir:" fld))))
|
||||
|
||||
(defun mua/hdrs-refresh ()
|
||||
"Re-run the query for the current search expression, but only
|
||||
if the search process is not already running"
|
||||
(interactive)
|
||||
(when mua/last-expression
|
||||
(mua/hdrs-search mua/last-expression)))
|
||||
|
||||
|
||||
;;; functions for sorting
|
||||
(defun mua/hdrs-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 mua/hdrs-sortfield field)
|
||||
(mua/warn "Invalid sort-field; use one of bcdfimpstz (see mu-headers(1)"))
|
||||
field))
|
||||
|
||||
(defun mua/hdrs-change-sort-direction (dirchar)
|
||||
"Change the sort direction, either [a]scending or [d]escending."
|
||||
(interactive)
|
||||
(setq mua/hdrs-sort-descending
|
||||
(y-or-n-p "Set sorting direction to descending(y) or ascending(n)")))
|
||||
|
||||
|
||||
(defun mua/hdrs-change-sort ()
|
||||
"Change thee sort field and dirtrection."
|
||||
(interactive)
|
||||
(and (call-interactively 'mua/hdrs-change-sort-order)
|
||||
(call-interactively 'mua/hdrs-change-sort-direction)))
|
||||
|
||||
|
||||
|
||||
;;; functions for marking
|
||||
|
||||
(defvar mua/hdrs-marks-hash nil
|
||||
"*internal* The hash for marked messages. The hash maps
|
||||
bol (beginning-of-line) to a 3-tuple: [UID TARGET FLAGS], where UID is the
|
||||
the UID of the message file (see `mua/msg-map'), TARGET is the
|
||||
target maildir (ie., \"/inbox\", but can also be nil (for 'delete);
|
||||
and finally FLAGS is the flags to set when the message is moved.")
|
||||
|
||||
(defun mua/hdrs-set-mark-ui (bol action)
|
||||
"Display (or undisplay) the mark for BOL for action ACTION."
|
||||
(unless (member action '(delete trash move unmark))
|
||||
(error "Invalid action %S" action))
|
||||
(save-excursion
|
||||
(let ((inhibit-read-only t))
|
||||
(delete-char 2)
|
||||
(insert
|
||||
(case action
|
||||
(delete "d ")
|
||||
(trash "D ")
|
||||
(move "m ")
|
||||
(unmark " "))))))
|
||||
|
||||
(defun mua/hdrs-set-mark (bol uid &optional target flags)
|
||||
"Add a mark to `mua/hdrs-marks-hash', with BOL being the beginning of the line
|
||||
of the marked message and (optionally) TARGET the target for the trash or move,
|
||||
and FLAGS the flags to set for the message, either as a string or as a list (see
|
||||
`mua/msg-move' for a discussion of the format)."
|
||||
(if (gethash bol mua/hdrs-marks-hash)
|
||||
(mua/warn "Message is already marked")
|
||||
(let ((tuple `[,uid ,target ,flags]))
|
||||
(puthash bol tuple mua/hdrs-marks-hash) ;; add to the hash...
|
||||
(mua/hdrs-set-mark-ui bol action))))
|
||||
|
||||
(defun mua/hdrs-remove-mark (bol)
|
||||
"Remove the mark for the message at BOL from the markings
|
||||
hash. BOL must be the point at the beginning of the line."
|
||||
(if (not (gethash bol mua/hdrs-marks-hash))
|
||||
(mua/warn "Message is not marked")
|
||||
(progn
|
||||
(remhash bol mua/hdrs-marks-hash) ;; remove from the hash...
|
||||
(mua/hdrs-set-mark-ui bol 'unmark))))
|
||||
|
||||
(defun mua/hdrs-marks-execute ()
|
||||
"Execute the corresponding actions for all marked messages in
|
||||
`mua/hdrs-marks-hash'."
|
||||
(interactive)
|
||||
(let ((n-marked (hash-table-count mua/hdrs-marks-hash)))
|
||||
(if (= 0 n-marked)
|
||||
(mua/warn "No marked messages")
|
||||
(when (y-or-n-p
|
||||
(format "Execute actions for %d marked message(s)? " n-marked))
|
||||
(save-excursion
|
||||
(maphash
|
||||
(lambda(bol tuple)
|
||||
(let* ((uid (aref tuple 0)) (target (aref tuple 1))
|
||||
(flags (aref tuple 2)) (inhibit-read-only t))
|
||||
(when (mua/msg-move uid target flags)
|
||||
;; remember the updated path -- for now not too useful
|
||||
;; as we're hiding the header, but...
|
||||
(save-excursion
|
||||
(mua/hdrs-remove-mark bol)
|
||||
(goto-char bol)
|
||||
;; when it succeedes, hide msg..)
|
||||
(put-text-property (line-beginning-position 1)
|
||||
(line-beginning-position 2) 'invisible t)))))
|
||||
mua/hdrs-marks-hash))))))
|
||||
|
||||
(defun mua/hdrs-mark (action)
|
||||
"Mark the message at point BOL (the beginning of the line) with
|
||||
one of the symbols: move, delete, trash, unmark, unmark-all; the
|
||||
latter two are pseudo-markings."
|
||||
(let* ((bol (line-beginning-position 1)) (uid (mua/hdrs-get-uid)))
|
||||
(when uid
|
||||
(case action
|
||||
(move
|
||||
(mua/hdrs-set-mark bol uid (mua/ask-maildir "Target maildir: " t)))
|
||||
(trash
|
||||
(if (member 'trashed (mua/msg-flags-from-path (mua/hdrs-get-path)))
|
||||
(mua/warn "Message is already trashed")
|
||||
(mua/hdrs-set-mark bol uid (concat mua/maildir mua/trash-folder) "+T")))
|
||||
(delete
|
||||
(mua/hdrs-set-mark bol action uid "/dev/null"))
|
||||
(unmark
|
||||
(mua/hdrs-remove-mark bol))
|
||||
(unmark-all
|
||||
(when (y-or-n-p (format "Sure you want to remove all (%d) marks? "
|
||||
(hash-table-count mua/hdrs-marks-hash)))
|
||||
(save-excursion
|
||||
(maphash (lambda (k v) (goto-char k) (mua/hdrs-mark 'unmark))
|
||||
mua/hdrs-marks-hash))))
|
||||
(t (error "Unsupported mark type")))
|
||||
(move-beginning-of-line 2))))
|
||||
|
||||
|
||||
|
||||
;; functions for creating new message -- reply, forward, and new
|
||||
(defun mua/hdrs-reply ()
|
||||
"Reply to message at point."
|
||||
(interactive)
|
||||
(let* ((uid (mua/hdrs-get-uid))
|
||||
(path (mua/hdrs-get-path))
|
||||
(str (when path (mua/mu-view-sexp path)))
|
||||
(msg (and str (mua/msg-from-string str))))
|
||||
(if msg
|
||||
(mua/msg-reply msg uid)
|
||||
(mua/warn "No message at point"))))
|
||||
|
||||
(defun mua/hdrs-for-reply ()
|
||||
"Forward the message at point."
|
||||
(interactive)
|
||||
(let* ((uid (mua/hdrs-get-uid))
|
||||
(path (mua/hdrs-get-path))
|
||||
(str (when path (mua/mu-view-sexp path)))
|
||||
(msg (and str (mua/msg-from-string str))))
|
||||
(if msg
|
||||
(mua/msg-reply msg uid)
|
||||
(mua/warn "No message at point"))))
|
||||
|
||||
(defun mua/hdrs-compose ()
|
||||
"Create a new message."
|
||||
(interactive)
|
||||
(mua/msg-compose-new))
|
||||
|
||||
|
||||
(provide 'mua-hdrs)
|
||||
@ -1,227 +0,0 @@
|
||||
;;; mua-msg.el -- part of mua, the mu mail user agent
|
||||
;;
|
||||
;; 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:
|
||||
|
||||
;; mua
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
|
||||
(defvar mua/msg-map nil
|
||||
"*internal* a map of uid->message.
|
||||
|
||||
This map adds a level of indirection for message files; many
|
||||
actions (such moving, responding to or even reading a message)
|
||||
cause the file names to change. Here we map the initial file to a
|
||||
uid, the latter which stays constant over the lifetime of a
|
||||
message in the system (in practice, the lifetime of a particular
|
||||
headers buffer).
|
||||
|
||||
When creating the headers buffer, the file names are registered
|
||||
with `mua/msg-map-add'.
|
||||
|
||||
All operation that change file names ultimately (should) end up
|
||||
in `mua/msg-move', which will update the map after the
|
||||
moving (using `mua/msg-map-update')
|
||||
|
||||
Other places of the code can use the uid to get the *current*
|
||||
path of the file using `mua/msg-map-get-path'.
|
||||
")
|
||||
|
||||
(defun mua/msg-map-add (path)
|
||||
"Add a message PATH to the `mua/msg-map', and return the uid
|
||||
for it."
|
||||
(unless mua/msg-map
|
||||
(setq mua/msg-map (make-hash-table :size 256 :rehash-size 2 :weakness t)))
|
||||
(let ((uid (sha1 path)))
|
||||
(puthash uid path mua/msg-map)
|
||||
uid))
|
||||
|
||||
(defun mua/msg-map-update (uid path)
|
||||
"Set the new path for the message identified by UID to PATH."
|
||||
(if (gethash uid mua/msg-map)
|
||||
(puthash uid path mua/msg-map)
|
||||
(mua/warn "No message file registered for uid")))
|
||||
|
||||
(defun mua/msg-map-get-path (uid)
|
||||
"Get the current path for the message identified by UID."
|
||||
(gethash uid mua/msg-map))
|
||||
|
||||
(defun mua/msg-move (uid &optional targetdir flags ignore-already)
|
||||
"Move message identified by UID to TARGETDIR using 'mu mv', and
|
||||
update the database with the new situation. SRC must be the full,
|
||||
absolute path to a message file, while TARGETDIR must be a
|
||||
maildir - that is, the part _without_ cur/ or new/. 'mu mv' will
|
||||
calculate the target directory and the exact file name. See
|
||||
`mua/msg-map' for a discussion about UID.
|
||||
|
||||
After the file system move (rename) has been done, 'mu remove'
|
||||
and/or 'mu add' are invoked asynchronously to update the database
|
||||
with the changes.
|
||||
|
||||
Optionally, you can specify the FLAGS for the new file. The FLAGS
|
||||
parameter can have the following forms:
|
||||
1. a list of flags such as '(passed replied seen)
|
||||
2. a string containing the one-char versions of the flags, e.g. \"PRS\"
|
||||
3. a delta-string specifying the changes with +/- and the one-char flags,
|
||||
e.g. \"+S-N\" to set Seen and remove New.
|
||||
|
||||
The flags are any of `deleted', `flagged', `new', `passed', `replied' `seen' or
|
||||
`trashed', or the corresponding \"DFNPRST\" as defined in [1]. See
|
||||
`mua/msg-string-to-flags' and `mua/msg-flags-to-string'.
|
||||
|
||||
If TARGETDIR is '/dev/null', remove SRC. After the file system
|
||||
move, the database will be updated as well, using the 'mu add'
|
||||
and 'mu remove' commands.
|
||||
|
||||
If IGNORE-ALREADY is non-nil, don't consider it an error when the target file is
|
||||
the same as the source file.
|
||||
|
||||
Function returns t the move succeeds, in other cases, it returns
|
||||
nil.
|
||||
|
||||
\[1\] URL `http://cr.yp.to/proto/maildir.html'."
|
||||
(condition-case err
|
||||
(let ((src (mua/msg-map-get-path uid)))
|
||||
(unless src (error "Source path not registered for %S" uid))
|
||||
(unless (or targetdir src) (error "Either targetdir or flags required"))
|
||||
(unless (file-readable-p src) (error "Source is unreadable (%S)" src))
|
||||
(let* ((flagstr
|
||||
(if (stringp flags) flags (mua/msg-flags-to-string flags)))
|
||||
(argl (append ;; build-up the command line
|
||||
'("mv" "--print-target" "--ignore-dups")
|
||||
(when flagstr (list (concat "--flags=" flagstr)))
|
||||
(list src)
|
||||
(when targetdir (list targetdir))))
|
||||
;; execute it, and get the results
|
||||
(rv (apply 'mua/mu-run argl))
|
||||
(code (car rv)) (output (cdr rv)))
|
||||
(unless (= 0 code)
|
||||
(error "Moving message failed: %S" output))
|
||||
|
||||
;; success!
|
||||
(let ((targetpath (substring output 0 -1)))
|
||||
|
||||
(when (and targetpath (not (string= src targetpath)))
|
||||
;; update the UID-map
|
||||
(mua/msg-map-update uid targetpath)
|
||||
;; remove the src file
|
||||
(mua/mu-remove-async src)
|
||||
;; and add the target file, unless it's dead now
|
||||
(unless (string= targetdir "/dev/null")
|
||||
(mua/mu-add-async targetpath)))
|
||||
t)))
|
||||
|
||||
(error (mua/warn "error: %s" (error-message-string err)))))
|
||||
|
||||
|
||||
(defun mua/msg-flags-from-path (path)
|
||||
"Get the flags for the message at PATH, which does not have to exist.
|
||||
The flags are returned as a list consisting of one or more of
|
||||
DFNPRST, mean resp. Deleted, Flagged, New, Passed Replied, Seen
|
||||
and Trash, as defined in [1]. See `mua/msg-string-to-flags'
|
||||
and `mua/msg-flags-to-string'.
|
||||
\[1\] http://cr.yp.to/proto/maildir.html."
|
||||
(when (string-match ",\\(\[A-Z\]*\\)$" path)
|
||||
(mua/msg-string-to-flags (match-string 1 path))))
|
||||
|
||||
|
||||
(defun mua/msg-maildir-from-path (path &optional dont-strip-prefix)
|
||||
"Get the maildir from PATH; in this context, 'maildir' is the
|
||||
part between the `mua/maildir' and the /cur or /new; so
|
||||
e.g. \"/home/user/Maildir/foo/bar/cur/12345:2,S\" would have
|
||||
\"/foo/bar\" as its maildir. If DONT-STRIP-PREFIX is non-nil,
|
||||
function will instead _not_ remove the `mua/maildir' from the
|
||||
front - so in that case, the example would return
|
||||
\"/home/user/Maildir/foo/bar/\". If the maildir cannot be
|
||||
determined, return `nil'."
|
||||
(when (and (string-match "^\\(.*\\)/\\(cur\\|new\\)/\[^/\]*$" path))
|
||||
(let ((mdir (match-string 1 path)))
|
||||
(when (and (< (length mua/maildir) (length mdir))
|
||||
(string= (substring mdir 0 (length mua/maildir)) mua/maildir))
|
||||
(if dont-strip-prefix
|
||||
mdir
|
||||
(substring mdir (length mua/maildir)))))))
|
||||
|
||||
(defun mua/msg-flags-to-string (flags)
|
||||
"Remove duplicates and sort the output of `mua/msg-flags-to-string-1'."
|
||||
(concat
|
||||
(sort (remove-duplicates
|
||||
(append (mua/msg-flags-to-string-1 flags) nil)) '>)))
|
||||
|
||||
(defun mua/msg-flags-to-string-1 (flags)
|
||||
"Convert a list of flags into a string as seen in Maildir
|
||||
message files; flags are symbols draft, flagged, new, passed,
|
||||
replied, seen, trashed and the string is the concatenation of the
|
||||
uppercased first letters of these flags, as per [1]. Other flags
|
||||
than the ones listed here are ignored.
|
||||
|
||||
Also see `mua/msg-string-to-flags'.
|
||||
|
||||
\[1\]: http://cr.yp.to/proto/maildir.html"
|
||||
(when flags
|
||||
(let ((kar (case (car flags)
|
||||
('draft ?D)
|
||||
('flagged ?F)
|
||||
('new ?N)
|
||||
('passed ?P)
|
||||
('replied ?R)
|
||||
('seen ?S)
|
||||
('trashed ?T)
|
||||
('encrypted ?x)
|
||||
('signed ?s)
|
||||
('unread ?u))))
|
||||
(concat (and kar (string kar))
|
||||
(mua/msg-flags-to-string-1 (cdr flags))))))
|
||||
|
||||
|
||||
(defun mua/msg-string-to-flags (str)
|
||||
"Remove duplicates from the output of `mua/msg-string-to-flags-1'"
|
||||
(remove-duplicates (mua/msg-string-to-flags-1 str)))
|
||||
|
||||
(defun mua/msg-string-to-flags-1 (str)
|
||||
"Convert a string with message flags as seen in Maildir
|
||||
messages into a list of flags in; flags are symbols draft,
|
||||
flagged, new, passed, replied, seen, trashed and the string is
|
||||
the concatenation of the uppercased first letters of these flags,
|
||||
as per [1]. Other letters than the ones listed here are ignored.
|
||||
Also see `mua/msg-flags-to-string'.
|
||||
|
||||
\[1\]: http://cr.yp.to/proto/maildir.html"
|
||||
(when (/= 0 (length str))
|
||||
(let ((flag
|
||||
(case (string-to-char str)
|
||||
(?D 'draft)
|
||||
(?F 'flagged)
|
||||
(?P 'passed)
|
||||
(?R 'replied)
|
||||
(?S 'seen)
|
||||
(?T 'trashed))))
|
||||
(append (when flag (list flag))
|
||||
(mua/msg-string-to-flags-1 (substring str 1))))))
|
||||
|
||||
(provide 'mua-msg-file)
|
||||
@ -1,455 +0,0 @@
|
||||
;;; mua-msg.el -- part of mua, the mu mail user agent
|
||||
;;
|
||||
;; 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:
|
||||
|
||||
;; mua
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
;; we use some stuff from gnus...
|
||||
(require 'message)
|
||||
(require 'mail-parse)
|
||||
|
||||
(require 'html2text)
|
||||
(require 'mua-common)
|
||||
|
||||
(defun mua/msg-from-string (str)
|
||||
"Get the plist describing an email message, from STR containing
|
||||
a message sexp.
|
||||
|
||||
a 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
|
||||
:references (\"200208121222.g7CCMdb80690@msg.id\")
|
||||
:in-reply-to \"200208121222.g7CCMdb80690@msg.id\"
|
||||
:message-id \"foobar32423847ef23@pluto.net\"
|
||||
:maildir: \"/archive\"
|
||||
:path \"/home/mickey/Maildir/inbox/cur/1312254065_3.32282.pluto,4cd5bd4e9:2,\"
|
||||
:priority high
|
||||
:flags (new unread)
|
||||
:attachments ((2 \"hello.jpg\" \"image/jpeg\") (3 \"laah.mp3\" \"audio/mp3\"))
|
||||
:body-txt \" <message body>\"
|
||||
\)
|
||||
other fields are :cc, :bcc, :body-html
|
||||
|
||||
When the s-expression comes from the database ('mu find'), the
|
||||
fields :attachments, :body-txt, :body-html, :references, :in-reply-to
|
||||
are missing (because that information is not stored in the
|
||||
database -- at least not in a usable way."
|
||||
(condition-case nil
|
||||
(car (read-from-string str));; read-from-string returns a cons
|
||||
(error "Failed to parse message")))
|
||||
|
||||
|
||||
(defun mua/msg-body-txt-or-html (msg)
|
||||
"Get :body-txt, or if not available, :body-html converted to
|
||||
text, using `html2text'."
|
||||
(let ((body (mua/msg-field msg :body-txt)))
|
||||
(unless body
|
||||
(setq body (mua/msg-field msg :body-html))
|
||||
(when body
|
||||
(setq body (with-temp-buffer
|
||||
(insert body)
|
||||
(html2text)
|
||||
(buffer-string)))))
|
||||
body))
|
||||
|
||||
(defun mua/msg-field (msg field)
|
||||
"Get a field from this message, or nil. The fields are the
|
||||
fields of the message, which are the various items of the plist
|
||||
as described in `mua/msg-from-string'
|
||||
|
||||
There is also the special field :body (which is either :body-txt,
|
||||
or if not available, :body-html converted to text)."
|
||||
(case field
|
||||
(:body
|
||||
(mua/msg-body-txt-or-html msg))
|
||||
(:maildir ;; messages gotten from mu-view don't have their maildir set...
|
||||
(or (plist-get msg :maildir)
|
||||
(mua/msg-maildir-from-path (mua/msg-field msg :path))))
|
||||
(t (plist-get msg field))))
|
||||
|
||||
|
||||
;; functions for composing new messages (forward, reply and new)
|
||||
|
||||
(defvar mua/msg-citation-prefix "> "
|
||||
"String to prefix cited message parts with.")
|
||||
|
||||
(defvar mua/msg-reply-prefix "Re: "
|
||||
"String to prefix the subject of replied messages with.")
|
||||
|
||||
(defvar mua/msg-forward-prefix "Fwd: "
|
||||
"String to prefix the subject of forwarded messages with.")
|
||||
|
||||
(defconst mua/msg-draft-name "*mua-draft*"
|
||||
"Name for draft messages.")
|
||||
|
||||
(defun mua/msg-user-agent ()
|
||||
"Return the User-Agent string for mua. This is either the value
|
||||
of `mua/user-agent', or, if not set, a string based on the
|
||||
version of mua and emacs."
|
||||
(or mua/user-agent
|
||||
(format "mu %s; emacs %s" (mua/mu-binary-version) emacs-version)))
|
||||
|
||||
(defun mua/msg-cite-original (msg)
|
||||
"Cite the body text of MSG, with a \"On %s, %s wrote:\"
|
||||
line (with the %s's replaced with the date of MSG and the name
|
||||
or e-mail address of its sender (or 'someone' if nothing
|
||||
else)), followed of the quoted body of MSG, constructed by by
|
||||
prepending `mua/msg-citation-prefix' to each line. If there is
|
||||
no body in MSG, return nil."
|
||||
(let* ((from (mua/msg-field msg :from))
|
||||
(body (mua/msg-body-txt-or-html msg)))
|
||||
(when body
|
||||
(concat
|
||||
(format "On %s, %s wrote:"
|
||||
(format-time-string "%c" (mua/msg-field msg :date))
|
||||
(if (and from (car from)) ;; a list ((<name> . <email>))
|
||||
(or (caar from) (cdar from) "someone")
|
||||
"someone"))
|
||||
"\n\n"
|
||||
(replace-regexp-in-string "^" " > " body)))))
|
||||
|
||||
|
||||
(defun mua/msg-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)
|
||||
(let ((email (cdr name-email)))
|
||||
(when email (string= email-to-remove (downcase email))))) lst))
|
||||
|
||||
(defun mua/msg-recipients-to-string (lst)
|
||||
"Convert a recipient list (of form '( (\"A\"
|
||||
. \"a@example.com\") (\"B\" . \"B@example.com\") (nil
|
||||
. \"c@example.com\")) into a string of form \"A <@aexample.com>,
|
||||
B <b@example.com>, c@example.com\."
|
||||
(mapconcat
|
||||
(lambda (recip)
|
||||
(let ((name (car recip)) (email (cdr recip)))
|
||||
(if name
|
||||
(format "%s <%s>" name email)
|
||||
(format "%s" email)))) lst ", "))
|
||||
|
||||
(defun mua/msg-hidden-header (hdr val)
|
||||
"Return user-invisible header to the message (HDR: VAL\n)."
|
||||
;; (format "%s: %s\n" hdr val))
|
||||
(propertize (format "%s: %s\n" hdr val) 'invisible t))
|
||||
|
||||
(defun mua/msg-header (hdr val)
|
||||
"Return a header line of the form HDR: VAL\n. If VAL is nil,
|
||||
return nil."
|
||||
(when val (format "%s: %s\n" hdr val)))
|
||||
|
||||
(defun mua/msg-references-create (msg)
|
||||
"Construct the value of the References: header based on MSG as
|
||||
a comma-separated string. Normally, this the concatenation of the
|
||||
existing References (which may be empty) and the message-id. If
|
||||
the message-id is empty, returns the old References. If both are
|
||||
empty, return nil."
|
||||
(let ((refs (mua/msg-field msg :references))
|
||||
(msgid (mua/msg-field msg :message-id)))
|
||||
(if msgid ;; every received message should have one...
|
||||
(mapconcat 'identity (append refs (list msgid)) ",")
|
||||
(mapconcat 'identity refs ","))))
|
||||
|
||||
(defun mua/msg-to-create (msg reply-all)
|
||||
"Construct the To: header for a reply-message based on some
|
||||
message MSG. If REPLY-ALL is nil, this the the Reply-To addresss
|
||||
of MSG if it exist, or the From:-address othewise. If reply-all
|
||||
is non-nil, the To: is what was in the old To: with either the
|
||||
Reply-To: or From: appended, and then the
|
||||
receiver (i.e. `user-mail-address') removed.
|
||||
|
||||
So:
|
||||
reply-all nil: Reply-To: or From: of MSG
|
||||
reply-all t : Reply-To: or From: of MSG + To: of MSG - `user-mail-address'
|
||||
|
||||
The result is either nil or a string which can be used for the To:-field."
|
||||
(let ((to-lst (mua/msg-field msg :to))
|
||||
(reply-to (mua/msg-field msg :reply-to))
|
||||
(from (mua/msg-field msg :from)))
|
||||
|
||||
(if reply-all
|
||||
(progn ;; reply-all
|
||||
(setq to-lst ;; append Reply-To:, or if not set, From: if set
|
||||
(if reply-to (cons `(nil . ,reply-to) to-lst)
|
||||
(if from (append to-lst from)
|
||||
to-lst)))
|
||||
|
||||
;; and remove myself from To:
|
||||
(setq to-lst (mua/msg-recipients-remove to-lst user-mail-address))
|
||||
(mua/msg-recipients-to-string to-lst))
|
||||
|
||||
;; reply single
|
||||
(progn
|
||||
(or reply-to (mua/msg-recipients-to-string from))))))
|
||||
|
||||
(defconst mua/msg-separator "--text follows this line--\n\n"
|
||||
"separator between headers and body, needed for `message-mode'")
|
||||
|
||||
(defun mua/msg-cc-create (msg reply-all)
|
||||
"Get the list of Cc-addresses for the reply to MSG. If
|
||||
REPLY-ALL is nil this is simply empty, otherwise it is the same
|
||||
list as the one in MSG, minus `user-mail-address'. The result of
|
||||
this function is either nil or a string to be used for the Cc:
|
||||
field."
|
||||
(let ((cc-lst (mua/msg-field msg :cc)))
|
||||
(when (and reply-all cc-lst)
|
||||
(mua/msg-recipients-to-string
|
||||
(mua/msg-recipients-remove cc-lst
|
||||
user-mail-address)))))
|
||||
|
||||
(defun mua/msg-from-create ()
|
||||
"Construct a value for the From:-field of the reply to MSG,
|
||||
based on `user-full-name' and `user-mail-address'; if the latter
|
||||
is nil, function returns nil."
|
||||
(when user-mail-address
|
||||
(if user-full-name
|
||||
(format "%s <%s>" user-full-name user-mail-address)
|
||||
(format "%s" user-mail-address))))
|
||||
|
||||
(defun mua/msg-create-reply (msg reply-all)
|
||||
"Create a draft message as a reply to MSG; if REPLY-ALL is
|
||||
non-nil, reply to all recipients.
|
||||
|
||||
A reply message has fields:
|
||||
From: - see `mu-msg-from-create'
|
||||
To: - see `mua/msg-to-create'
|
||||
Cc: - see `mua/msg-cc-create'
|
||||
Subject: - `mua/msg-reply-prefix' + subject of MSG
|
||||
|
||||
then, the following fields, normally hidden from user:
|
||||
Reply-To: - if `mail-reply-to' has been set
|
||||
References: - see `mua/msg-references-create'
|
||||
In-Reply-To: - message-id of MSG
|
||||
User-Agent - see `mua/msg-user-agent'
|
||||
|
||||
Then follows `mua/msg-separator' (for `message-mode' to separate
|
||||
body from headers)
|
||||
|
||||
And finally, the cited body of MSG, as per `mua/msg-cite-original'."
|
||||
(concat
|
||||
(mua/msg-header "From" (or (mua/msg-from-create) ""))
|
||||
(when (boundp 'mail-reply-to)
|
||||
(mua/msg-header "Reply-To" mail-reply-to))
|
||||
|
||||
(mua/msg-header "To" (or (mua/msg-to-create msg reply-all) ""))
|
||||
(mua/msg-header "Cc" (mua/msg-cc-create msg reply-all))
|
||||
|
||||
(mua/msg-hidden-header "User-agent" (mua/msg-user-agent))
|
||||
(mua/msg-hidden-header "References" (mua/msg-references-create msg))
|
||||
|
||||
(mua/msg-hidden-header "In-reply-to" (mua/msg-field msg :message-id))
|
||||
|
||||
(mua/msg-header"Subject"
|
||||
(concat mua/msg-reply-prefix (mua/msg-field msg :subject)))
|
||||
|
||||
mua/msg-separator
|
||||
|
||||
(mua/msg-cite-original msg)))
|
||||
|
||||
;; TODO: attachments
|
||||
(defun mua/msg-create-forward (msg)
|
||||
"Create a draft forward message for MSG.
|
||||
|
||||
A forward message has fields:
|
||||
From: - see `mu-msg-from-create'
|
||||
To: - empty
|
||||
Subject: - `mua/msg-forward-prefix' + subject of MSG
|
||||
|
||||
then, the following fields, normally hidden from user:
|
||||
Reply-To: - if `mail-reply-to' has been set
|
||||
References: - see `mua/msg-references-create'
|
||||
User-Agent - see `mua/msg-user-agent'
|
||||
|
||||
Then follows `mua-msg-separator' (for `message-mode' to separate
|
||||
body from headers)
|
||||
|
||||
And finally, the cited body of MSG, as per `mua/msg-cite-original'."
|
||||
(concat
|
||||
(mua/msg-header "From" (or (mua/msg-from-for-new) ""))
|
||||
(when (boundp 'mail-reply-to)
|
||||
(mua/msg-header "Reply-To" mail-reply-to))
|
||||
|
||||
(mua/msg-header "To" "")
|
||||
(mua/msg-hidden-header "User-agent" (mua/msg-user-agent))
|
||||
(mua/msg-hidden-header "References" (mua/msg-references-for-reply msg))
|
||||
(mua/msg-header"Subject"
|
||||
(concat mua/msg-forward-prefix (mua/msg-field msg :subject)))
|
||||
|
||||
mua/msg-separator
|
||||
|
||||
(mua/msg-cite-original msg)))
|
||||
|
||||
(defun mua/msg-create-new ()
|
||||
"Create a new message.
|
||||
|
||||
A new draft message has fields:
|
||||
From: - see `mu-msg-from-create'
|
||||
To: - empty
|
||||
Subject: - empty
|
||||
|
||||
then, the following fields, normally hidden from user:
|
||||
Reply-To: - if `mail-reply-to' has been set
|
||||
User-Agent - see `mua/msg-user-agent'
|
||||
|
||||
Then follows `mua-msg-separator' (for `message-mode' to separate
|
||||
body from headers)."
|
||||
(concat
|
||||
(mua/msg-header "From" (or (mua/msg-from-create) ""))
|
||||
(when (boundp 'mail-reply-to)
|
||||
(mua/msg-header "Reply-To" mail-reply-to))
|
||||
|
||||
(mua/msg-header "To" "")
|
||||
(mua/msg-hidden-header "User-agent" (mua/msg-user-agent))
|
||||
(mua/msg-header "Subject" "")
|
||||
mua/msg-separator))
|
||||
|
||||
(defconst mua/msg-prefix "mua" "prefix for mua-generated
|
||||
mail files; we use this to ensure that our hooks don't mess
|
||||
with non-mua-generated messages")
|
||||
|
||||
(defun mua/msg-draft-file-name ()
|
||||
"Create a Maildir-compatible[1], unique file name for a draft
|
||||
message.
|
||||
[1]: see http://cr.yp.to/proto/maildir.html"
|
||||
(format "%s-%s-%x.%s:2,D" ;; 'D': rarely used, but hey, it's available
|
||||
mua/msg-prefix
|
||||
(format-time-string "%Y%m%d" (current-time))
|
||||
(emacs-pid)
|
||||
(random t)
|
||||
(replace-regexp-in-string "[:/]" "_" (system-name))))
|
||||
|
||||
(defvar mua/msg-reply-uid nil "UID of the message this is a reply to.")
|
||||
(defvar mua/msg-forward-uid nil "UID of the message being forwarded.")
|
||||
|
||||
(defun mua/msg-compose (str)
|
||||
"Create a new draft message in the drafts folder with STR as
|
||||
its contents, and open this message file for editing. Optionally
|
||||
specify PARENT-UID,
|
||||
|
||||
The name of the draft folder is constructed from the concatenation of
|
||||
`mua/maildir' and `mua/drafts-folder' (therefore, these must be set).
|
||||
|
||||
The message file name is a unique name determined by
|
||||
`mua/msg-draft-file-name'.
|
||||
|
||||
The initial STR would be created from either `mua/msg-create-reply',
|
||||
`mua/msg-create-forward' or `mua/msg-create-new'. The editing buffer is
|
||||
using Gnus' `message-mode'."
|
||||
(unless mua/maildir (error "mua/maildir not set"))
|
||||
(unless mua/drafts-folder (error "mua/drafts-folder not set"))
|
||||
|
||||
;; write our draft message to the the drafts folder
|
||||
(let ((draftfile (concat mua/maildir "/" mua/drafts-folder "/cur/"
|
||||
(mua/msg-draft-file-name))))
|
||||
(with-temp-file draftfile (insert str))
|
||||
(find-file draftfile) (rename-buffer mua/msg-draft-name t)
|
||||
(message-mode)
|
||||
(make-local-variable 'mua/msg-forward-uid)
|
||||
|
||||
(message-goto-body)))
|
||||
|
||||
(defun mua/msg-reply (msg &optional reply-uid)
|
||||
"Create a draft reply to MSG, and swith to an edit buffer with
|
||||
the draft message. PARENT-UID refers to the UID of the message wer"
|
||||
(let* ((recipnum (+ (length (mua/msg-field msg :to))
|
||||
(length (mua/msg-field msg :cc))))
|
||||
(replyall (when (> recipnum 1)
|
||||
(yes-or-no-p (format "Reply to all ~%d recipients? "
|
||||
(+ recipnum))))))
|
||||
;; exact num depends on some more things
|
||||
(when (mua/msg-compose (mua/msg-create-reply msg replyall))
|
||||
(when reply-uid (setq mua/msg-reply-uid reply-uid))
|
||||
(message-goto-body))))
|
||||
|
||||
(defun mua/msg-forward (msg &optional forward-uid)
|
||||
"Create a draft forward for MSG, and swith to an edit buffer with
|
||||
the draft message."
|
||||
(when (mua/msg-compose (mua/msg-create-forward msg))
|
||||
(when forward-uid (setq mua/msg-forward-uid forward-uid))
|
||||
(message-goto-to)))
|
||||
|
||||
(defun mua/msg-compose-new ()
|
||||
"Create a draft message, and swith to an edit buffer with the
|
||||
draft message."
|
||||
(when (mua/msg-compose (mua/msg-create-new))
|
||||
(message-goto-to)))
|
||||
|
||||
|
||||
|
||||
(defun mua/msg-save-to-sent ()
|
||||
"Move the message in this buffer to the sent folder. This is
|
||||
meant to be called from message mode's `message-sent-hook'."
|
||||
(if (mua/msg-is-mua-message) ;; only if we are mua
|
||||
(unless mua/sent-folder (error "mua/sent-folder not set"))
|
||||
(let* ;; TODO: remove duplicate flags
|
||||
((newflags ;; remove Draft; maybe set 'Seen' as well?
|
||||
(delq 'draft (mua/msg-flags-from-path (buffer-file-name))))
|
||||
;; so, we register path => uid, then we move uid, then check the name
|
||||
;; uid is referring to
|
||||
(uid (mua/msg-register (buffer-file-name)))
|
||||
(if (mua/msg-move uid
|
||||
(concat mua/maildir mua/sent-folder)
|
||||
(mua/msg-flags-to-string newflags))
|
||||
(set-visited-file-name (mua/msg-get-path uid) t t)
|
||||
(mua/warn "Failed to save message to the Sent-folder"))))))
|
||||
|
||||
|
||||
(defun mua/msg-set-replied-or-passed-flag ()
|
||||
"Set the 'replied' flag on messages we replied to, and the
|
||||
'passed' flag on message we have forwarded. This uses
|
||||
`mua/msg-reply-uid' and `mua/msg-forward-uid', repectively.
|
||||
|
||||
NOTE: This does not handle the case yet of message which are
|
||||
edited from drafts. That case could be solved by searching for
|
||||
the In-Reply-To message-id for replies.
|
||||
|
||||
This is meant to be called from message mode's
|
||||
`message-sent-hook'."
|
||||
;; handle the replied-to message
|
||||
(when mua/msg-reply-uid
|
||||
(unless (mua/msg-move mua/msg-reply-uid nil "+R")
|
||||
(mua/warn "Failed to marked parent message as 'Replied'")))
|
||||
|
||||
;; handle the forwarded message
|
||||
(when mua/msg-forward-uid
|
||||
(unless (mua/msg-move mua/msg-forward-uid nil "+P")
|
||||
(mua/warn "Failed to marked parent message as 'Passed'"))))
|
||||
|
||||
|
||||
;; hook our functions up with sending of the message
|
||||
(add-hook 'message-sent-hook 'mua/msg-save-to-sent)
|
||||
(add-hook 'message-sent-hook 'mua/msg-set-replied-or-passed-flag)
|
||||
|
||||
|
||||
(provide 'mua-msg)
|
||||
@ -1,144 +0,0 @@
|
||||
;;; mua-mu.el -- part of mua, the mu mail user agent
|
||||
;;
|
||||
;; 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:
|
||||
|
||||
;; mua-mu contains common functions that interact with the mu program
|
||||
|
||||
;;; Code:
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
|
||||
(defun mua/mu-run (&rest args)
|
||||
"Run 'mu' synchronously with ARGS as command-line argument;,
|
||||
where <exit-code> is the exit code of the program, or 1 if the
|
||||
process was killed. <str> contains whatever the command wrote on
|
||||
standard output/error, or nil if there was none or in case of
|
||||
error. Basically, `mua/mu-run' is like `shell-command-to-string',
|
||||
but with better possibilities for error handling. The --muhome=
|
||||
parameter is added automatically if `mua/mu-home' is non-nil."
|
||||
(let* ((rv)
|
||||
(args (append args (when mua/mu-home
|
||||
(list (concat "--muhome=" mua/mu-home)))))
|
||||
(cmdstr (concat mua/mu-binary " " (mapconcat 'identity args " ")))
|
||||
(str (with-output-to-string
|
||||
(with-current-buffer standard-output ;; but we also get stderr...
|
||||
(setq rv (apply 'call-process mua/mu-binary nil t nil
|
||||
args))))))
|
||||
(when (and (numberp rv) (/= 0 rv))
|
||||
(mua/log "mua error: %s" (mua/mu-error rv)))
|
||||
(mua/log "%s => %S" cmdstr rv)
|
||||
`(,(if (numberp rv) rv 1) . ,str)))
|
||||
|
||||
(defun mua/mu-binary-version ()
|
||||
"Get the version string of the mu binary, or nil if we failed
|
||||
to get it"
|
||||
(let ((rv (mua/mu-run "--version")))
|
||||
(if (and (= (car rv) 0) (string-match "version \\(.*\\)$" (cdr rv)))
|
||||
(match-string 1 (cdr rv))
|
||||
(mua/warn "Failed to get version string"))))
|
||||
|
||||
|
||||
(defun mua/mu-view-sexp (path)
|
||||
"Return a string with an s-expression representing the message
|
||||
at PATH; the format is described in `mua/msg-from-string', and
|
||||
that function converts the string into a Lisp object (plist)"
|
||||
(if (not (file-readable-p path))
|
||||
(mua/warn "Cannot view unreadable file %s" path)
|
||||
(let* ((rv (mua/mu-run "view" "--format=sexp" path))
|
||||
(code (car rv)) (str (cdr rv)))
|
||||
(if (= code 0)
|
||||
str
|
||||
(mua/warn "mu view failed (%d): %s"
|
||||
code (if str str "error"))))))
|
||||
|
||||
|
||||
(defvar mua/db-update-proc nil "*internal* process for db updates")
|
||||
(defvar mua/db-update-name "*mua-db-update*"
|
||||
"*internal* name of the db-update process")
|
||||
(defvar mua/db-add-paths nil "list of paths to add to database")
|
||||
(defvar mua/db-remove-paths nil "list of paths to remove from database")
|
||||
|
||||
(defun mua/db-update-proc-sentinel (proc msg)
|
||||
"Check the process upon completion"
|
||||
(let ((procbuf (process-buffer proc))
|
||||
(status (process-status proc))
|
||||
(exit-status (process-exit-status proc)))
|
||||
(when (and (buffer-live-p procbuf) (memq status '(exit signal)))
|
||||
(case status
|
||||
('signal (mua/warn "Process killed"))
|
||||
('exit
|
||||
(case exit-status
|
||||
(mua/warn "Result: %s" (mua/mu-log exit-status))))))
|
||||
(mua/mu-db-update-execute)))
|
||||
|
||||
(defun mua/mu-db-update-execute ()
|
||||
"Update the database; remove paths in `mua/db-remove-paths',
|
||||
and add paths in `mua/db-add-paths'. Updating is ansynchronous."
|
||||
|
||||
;; when it's already running, do nothing
|
||||
(unless (and mua/db-update-proc (eq (process-status mua/db-update-proc) 'run))
|
||||
(when mua/db-remove-paths
|
||||
(let ((remove-paths (copy-list mua/db-remove-paths)))
|
||||
(mua/log (concat mua/mu-binary " remove "
|
||||
(mapconcat 'identity remove-paths " ")))
|
||||
(setq mua/db-remove-paths nil) ;; clear the old list
|
||||
(setq mua/db-update-proc
|
||||
(apply 'start-process mua/db-update-name mua/db-update-name mua/mu-binary
|
||||
"remove" remove-paths))
|
||||
(set-process-sentinel mua/db-update-proc 'mua/db-update-proc-sentinel))))
|
||||
|
||||
;; when it's already running, do nothing
|
||||
(unless (and mua/db-update-proc
|
||||
(eq (process-status mua/db-update-proc) 'run))
|
||||
(when mua/db-add-paths
|
||||
(let ((add-paths (copy-list mua/db-add-paths)))
|
||||
(mua/log (concat mua/mu-binary " add " (mapconcat 'identity add-paths " ")))
|
||||
(setq mua/db-add-paths nil) ;; clear the old list
|
||||
(setq mua/db-update-proc
|
||||
(apply 'start-process mua/db-update-name mua/db-update-name mua/mu-binary
|
||||
"add" add-paths))
|
||||
(set-process-sentinel mua/db-update-proc 'mua/db-update-proc-sentinel)))))
|
||||
|
||||
|
||||
(defun mua/mu-add-async (path-or-paths)
|
||||
"Asynchronously add msg at PATH-OR-PATHS to
|
||||
database. PATH-OR-PATHS is either a single path or a list of
|
||||
them."
|
||||
(setq mua/db-add-paths
|
||||
(append mua/db-add-paths
|
||||
(if (listp path-or-paths) path-or-paths `(,path-or-paths))))
|
||||
(mua/mu-db-update-execute))
|
||||
|
||||
(defun mua/mu-remove-async (path-or-paths)
|
||||
"Asynchronously remove msg at PATH-OR-PATHS from
|
||||
database. PATH-OR-PATHS is either a single path or a list of
|
||||
them."
|
||||
(setq mua/db-remove-paths
|
||||
(append mua/db-remove-paths
|
||||
(if (listp path-or-paths) path-or-paths `(,path-or-paths))))
|
||||
(mua/mu-db-update-execute))
|
||||
|
||||
|
||||
(provide 'mua-mu)
|
||||
@ -1,255 +0,0 @@
|
||||
;;; mua-view.el -- part of mua, the mu mail user agent
|
||||
;;
|
||||
;; 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
|
||||
|
||||
;;; Code:
|
||||
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(require 'mua-common)
|
||||
(require 'mua-msg)
|
||||
|
||||
(defconst mua/view-buffer-name " *mua-view*"
|
||||
"buffer name for mua/view buffers")
|
||||
|
||||
(defvar mua/view-headers
|
||||
'(:from :to :cc :subject :flags :date :maildir :path :attachments)
|
||||
"Fields to display in the message view buffer.")
|
||||
|
||||
(defvar mua/hdrs-buffer nil
|
||||
"*internal* Headers buffer for the view in this buffer.")
|
||||
|
||||
(defvar mua/view-uid nil
|
||||
"*internal* The UID for the message being viewed in this buffer.")
|
||||
|
||||
|
||||
(defun mua/view (uid hdrsbuf)
|
||||
"Display the message identified by UID in a new buffer, and mark
|
||||
is as no longer unread, -- 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.
|
||||
|
||||
For the reasoning to use UID here instead of just the path, see
|
||||
`mua/msg-map'."
|
||||
(condition-case err
|
||||
(let* ((path (mua/msg-map-get-path uid))
|
||||
(sexp (mua/mu-view-sexp path))
|
||||
(msg (and sexp (mua/msg-from-string sexp))))
|
||||
(unless (buffer-live-p hdrsbuf) (error "Headers buffer is dead"))
|
||||
(unless msg (error "Cannot view message %S" path))
|
||||
(let ((buf (get-buffer-create mua/view-buffer-name))
|
||||
(inhibit-read-only t))
|
||||
;; fill buffer with the message
|
||||
(erase-buffer)
|
||||
(insert (mua/view-message msg))
|
||||
(mua/view-mode)
|
||||
(goto-char (point-min))
|
||||
|
||||
(setq ;; these are buffer-local
|
||||
mua/view-uid uid
|
||||
mua/hdrs-buffer hdrsbuf
|
||||
mua/parent-buffer hdrsbuf)
|
||||
|
||||
(unless (mua/msg-move uid nil "+S-N" t) ;; mark as read
|
||||
(error "Failed to mark message as read"))))
|
||||
(debug (error))));; (mua/warn "error: %s" (error-message-string err)))))
|
||||
|
||||
|
||||
|
||||
(defun mua/view-message (msg)
|
||||
"construct a display string for the message"
|
||||
(let ((hdrs
|
||||
(mapconcat
|
||||
(lambda (field)
|
||||
(case field
|
||||
(:subject (mua/view-header msg "Subject" :subject))
|
||||
(:path (mua/view-header msg "Path" :path))
|
||||
(:to (mua/view-contacts msg field))
|
||||
(:from (mua/view-contacts msg field))
|
||||
(:cc (mua/view-contacts msg field))
|
||||
(:bcc (mua/view-contacts msg field))
|
||||
(:date (mua/view-date msg))
|
||||
(:flags (mua/view-flags msg))
|
||||
(:maildir (mua/view-header msg "Maildir" :maildir))
|
||||
(:size (mua/view-size msg))
|
||||
(:attachments (mua/view-attachments msg))
|
||||
(t (error "Unsupported field: %S" field))))
|
||||
mua/view-headers ""))
|
||||
(body (mua/msg-body-txt-or-html msg)))
|
||||
(concat hdrs "\n" body)))
|
||||
|
||||
(defun mua/view-header-string (key val face)
|
||||
(if val
|
||||
(concat
|
||||
(propertize key 'face 'mua/header-title-face) ": "
|
||||
(propertize val 'face face) "\n")
|
||||
""))
|
||||
|
||||
(defun mua/view-header (msg key field)
|
||||
"show header FIELD for MSG with KEY. ie. <KEY>: value-of-FIELD\n"
|
||||
(mua/view-header-string key (mua/msg-field msg field) 'mua/header-face))
|
||||
|
||||
(defun mua/view-contacts (msg field)
|
||||
(unless (member field '(:to :from :bcc :cc))
|
||||
(error "Illegal type for contact"))
|
||||
(let* ((lst (mua/msg-field msg field))
|
||||
(contacts
|
||||
(when lst
|
||||
(mapconcat
|
||||
(lambda(c) (let ((name (car c)) (email (cdr c)))
|
||||
(if name
|
||||
(format "%s <%s>" name email)
|
||||
(format "%s" email)))) lst ", "))))
|
||||
(if contacts
|
||||
(mua/view-header-string
|
||||
(case field (:to "To") (:from "From") (:bcc "Bcc") (:cc "Cc"))
|
||||
contacts 'mua/contacts-face)
|
||||
"")))
|
||||
|
||||
(defun mua/view-date (msg)
|
||||
(let* ((date (mua/msg-field msg :date))
|
||||
(datestr (when date (format-time-string "%c" date))))
|
||||
(mua/view-header-string "Date" datestr 'mua/header-face)))
|
||||
|
||||
(defun mua/view-size (msg)
|
||||
(let* ((size (mua/msg-field msg :size))
|
||||
(sizestr (when size (format "%d bytes"))))
|
||||
(mua/view-header-string "Size" sizestr 'mua-header-face)))
|
||||
|
||||
(defun mua/view-flags (msg)
|
||||
""
|
||||
"" ;; todo
|
||||
)
|
||||
|
||||
(defun mua/view-attachments (msg)
|
||||
""
|
||||
"" ;; todo
|
||||
)
|
||||
|
||||
|
||||
(defvar mua/view-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map "q" 'mua/quit-buffer)
|
||||
(define-key map "s" 'mua/view-search)
|
||||
|
||||
(define-key map "f" 'mua/view-forward)
|
||||
(define-key map "r" 'mua/view-reply)
|
||||
(define-key map "c" 'mua/view-compose)
|
||||
|
||||
;; navigation between messages
|
||||
(define-key map "n" 'mua/view-next)
|
||||
(define-key map "p" 'mua/view-prev)
|
||||
|
||||
;; marking/unmarking
|
||||
(define-key map "d" '(lambda()(interactive)(mua/view-mark 'trash)))
|
||||
(define-key map "D" '(lambda()(interactive)(mua/view-mark 'delete)))
|
||||
(define-key map "m" '(lambda()(interactive)(mua/view-mark 'move)))
|
||||
(define-key map "u" '(lambda()(interactive)(mua/view-mark 'unmark)))
|
||||
(define-key map "x" 'mua/view-marked-execute)
|
||||
map)
|
||||
"Keymap for \"*mua-view*\" buffers.")
|
||||
(fset 'mua/view-mode-map mua/view-mode-map)
|
||||
|
||||
(defun mua/view-mode ()
|
||||
"major mode for viewing an e-mail message"
|
||||
(interactive)
|
||||
(kill-all-local-variables)
|
||||
(use-local-map mua/view-mode-map)
|
||||
|
||||
(make-local-variable 'mua/parent-buffer)
|
||||
(make-local-variable 'mua/hdrs-buffer)
|
||||
(make-local-variable 'mua/view-uid)
|
||||
|
||||
(setq major-mode 'mua/view-mode mode-name "*mu-view*")
|
||||
(setq truncate-lines t buffer-read-only t))
|
||||
|
||||
|
||||
(defmacro mua/with-hdrs-buffer (&rest body)
|
||||
"Execute the forms in BODY with the mua/hdrs-buffer temporarily current.
|
||||
Note that this actually switches the buffer, and changes to point
|
||||
etc. persist."
|
||||
(declare (indent 1) (debug t))
|
||||
`(let ((oldbuf (current-buffer)))
|
||||
(if (buffer-live-p mua/hdrs-buffer)
|
||||
(progn
|
||||
(set-buffer mua/hdrs-buffer)
|
||||
(progn ,@body)
|
||||
(set-buffer oldbuf))
|
||||
(mua/warn "hdrs buffer is dead"))))
|
||||
|
||||
|
||||
(defun mua/view-mark (action)
|
||||
"Set/unset marks for the current message."
|
||||
(interactive)
|
||||
(mua/with-hdrs-buffer (mua/hdrs-mark action)))
|
||||
|
||||
(defun mua/view-marked-execute ()
|
||||
"Warn user that marks cannot be executed from here (for his/her
|
||||
own safety)."
|
||||
(interactive)
|
||||
(mua/warn "You cannot execute marks from here"))
|
||||
|
||||
|
||||
(defun mua/view-search()
|
||||
"Start a new search."
|
||||
(interactive)
|
||||
(mua/with-hdrs-buffer
|
||||
(call-interactively 'mua/hdrs-search)))
|
||||
|
||||
(defun mua/view-next ()
|
||||
"move to the next message; note, this will replace the current
|
||||
buffer"
|
||||
(interactive)
|
||||
(with-current-buffer mua/hdrs-buffer
|
||||
(when (mua/hdrs-next) (mua/hdrs-view))))
|
||||
|
||||
(defun mua/view-prev ()
|
||||
"move to the previous message; note, this will replace the
|
||||
current buffer"
|
||||
(interactive)
|
||||
(mua/with-hdrs-buffer
|
||||
(when (mua/hdrs-prev) (mua/hdrs-view))))
|
||||
|
||||
(defun mua/view-reply ()
|
||||
"Reply to the current message."
|
||||
(interactive) (mua/with-hdrs-buffer (mua/hdrs-reply)))
|
||||
|
||||
(defun mua/view-forward ()
|
||||
"Reply to the current message."
|
||||
(interactive) (mua/with-hdrs-buffer (mua/hdrs-forward)))
|
||||
|
||||
(defun mua/view-compose ()
|
||||
"Write a new message."
|
||||
(interactive) (mua/with-hdrs-buffer (mua/hdrs-compose)))
|
||||
|
||||
|
||||
(provide 'mua-view)
|
||||
177
toys/mua/mua.el
177
toys/mua/mua.el
@ -1,177 +0,0 @@
|
||||
;;; mua.el -- part of mua, the mu mail user agent
|
||||
;;
|
||||
;; 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
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(require 'mua-common)
|
||||
(require 'mua-mu)
|
||||
(require 'mua-msg)
|
||||
(require 'mua-hdrs)
|
||||
(require 'mua-view)
|
||||
(require 'mua-msg-file)
|
||||
|
||||
|
||||
(defvar mua/mu-home nil "location of the mu homedir, or nil for
|
||||
the default")
|
||||
(defvar mua/mu-binary "mu" "name/path of the mu binary")
|
||||
(setq mua/mu-binary "/home/djcb/src/mu/src/mu")
|
||||
|
||||
(defvar mua/user-agent nil "User-specified User-Agent string")
|
||||
|
||||
(defvar mua/parent-buffer nil "parent buffer; if a buffer is
|
||||
quitted, it switches back to its parent buffer")
|
||||
|
||||
(defvar mua/maildir nil "our maildir")
|
||||
|
||||
(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")
|
||||
(defvar mu/drafts-folder nil "location of your drafts folder")
|
||||
|
||||
(setq
|
||||
mua/maildir "/home/djcb/Maildir"
|
||||
mua/inbox-folder "/inbox"
|
||||
mua/outbox-folder "/outbox"
|
||||
mua/sent-folder "/sent"
|
||||
mua/drafts-folder "/drafts"
|
||||
mua/trash-folder "/trash")
|
||||
|
||||
(defvar mua/working-folders nil)
|
||||
|
||||
(setq mua/working-folders
|
||||
'("/bulk" "/archive" "/bulkarchive" "/todo"))
|
||||
|
||||
(setq mua/header-fields
|
||||
'( (:date . 25)
|
||||
(:flags . 6)
|
||||
(:from . 22)
|
||||
(:subject . 40)))
|
||||
|
||||
|
||||
(defface mua/date-face '((t (:foreground "#8c5353"))) "")
|
||||
(defface mua/header-title-face '((t (:foreground "#df558f"))) "")
|
||||
(defface mua/header-face '((t (:foreground "#dfaf8f"))) "")
|
||||
(defface mua/contacts-face '((t (:foreground "#7f9f7f"))) "")
|
||||
(defface mua/body-face '((t (:foreground "#8cd0d3"))) "")
|
||||
|
||||
|
||||
|
||||
(setq mua/hdrs-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
|
||||
(define-key map "s" 'mua/hdrs-search)
|
||||
(define-key map "q" 'mua/quit-buffer)
|
||||
(define-key map "o" 'mua/hdrs-change-sort)
|
||||
(define-key map "g" 'mua/hdrs-refresh)
|
||||
|
||||
;; navigation
|
||||
(define-key map "n" 'mua/hdrs-next)
|
||||
(define-key map "p" 'mua/hdrs-prev)
|
||||
(define-key map (kbd "<down>") 'mua/hdrs-next)
|
||||
(define-key map (kbd "<up>") 'mua/hdrs-prev)
|
||||
|
||||
(define-key map (kbd "<SPC>") 'scroll-up)
|
||||
|
||||
(define-key map "j" 'mua/hdrs-jump-to-maildir)
|
||||
|
||||
;; marking/unmarking/executing
|
||||
(define-key map "m" (lambda()(interactive)(mua/hdrs-mark 'move)))
|
||||
(define-key map "d" (lambda()(interactive)(mua/hdrs-mark 'trash)))
|
||||
(define-key map "D" (lambda()(interactive)(mua/hdrs-mark 'delete)))
|
||||
(define-key map "u" (lambda()(interactive)(mua/hdrs-mark 'unmark)))
|
||||
(define-key map "U" (lambda()(interactive)(mua/hdrs-mark 'unmark-all)))
|
||||
(define-key map "x" 'mua/hdrs-marks-execute)
|
||||
|
||||
;; message composition
|
||||
(define-key map "r" 'mua/hdrs-reply)
|
||||
(define-key map "f" 'mua/hdrs-forward)
|
||||
(define-key map "c" 'mua/hdrs-compose)
|
||||
|
||||
(define-key map (kbd "RET") 'mua/hdrs-view)
|
||||
map))
|
||||
(fset 'mua/hdrs-mode-map mua/hdrs-mode-map)
|
||||
|
||||
(defconst mua/buffer-name "*mua*"
|
||||
"Name of the top-level mua buffer")
|
||||
|
||||
(defun mua()
|
||||
"Start mua, the mu e-mail client with an impressive dashboard."
|
||||
(interactive)
|
||||
(let ((buf (mua/new-buffer mua/buffer-name)))
|
||||
(with-current-buffer buf
|
||||
(insert (propertize "mua" 'face 'highlight)
|
||||
(propertize " version: " 'face 'mua/header-title-face)
|
||||
(propertize (mua/mu-binary-version) 'face 'mua/header-face)
|
||||
(propertize " maildir: " 'face 'mua/header-title-face)
|
||||
(propertize mua/maildir 'face 'mua/header-face)
|
||||
"\n\n\n"
|
||||
(propertize "* quick jump folders" 'face 'mua/header-title-face)
|
||||
" (use " (propertize "j" 'face 'highlight) ")\n"
|
||||
" " (mapconcat 'identity
|
||||
(append (list mua/inbox-folder mua/sent-folder mua/drafts-folder)
|
||||
mua/working-folders) " ") "\n\n"
|
||||
|
||||
(propertize "* search" 'face 'mua/header-title-face)
|
||||
" (use " (propertize "s" 'face 'highlight) ")\n\n"
|
||||
|
||||
(propertize "* compose a new message" 'face 'mua/header-title-face)
|
||||
" (use " (propertize "c" 'face 'highlight) ")\n\n"
|
||||
))
|
||||
(switch-to-buffer buf)
|
||||
(mua/mua-mode)))
|
||||
|
||||
(defvar mua/mua-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
|
||||
(define-key map "s" 'mua/hdrs-search)
|
||||
(define-key map "q" 'mua/quit-buffer)
|
||||
(define-key map "j" 'mua/hdrs-jump-to-maildir)
|
||||
(define-key map "c" 'mua/hdrs-compose)
|
||||
|
||||
map)
|
||||
"Keymap for *mua-headers* buffers.")
|
||||
(fset 'mua/mua-mode-map mua/mua-mode-map)
|
||||
|
||||
(defun mua/mua-mode ()
|
||||
"Major mode for the mua dashboard screen."
|
||||
(interactive)
|
||||
(kill-all-local-variables)
|
||||
(use-local-map mua/mua-mode-map)
|
||||
(make-local-variable 'mua/buf)
|
||||
|
||||
(setq
|
||||
major-mode 'mua/mua-mode mode-name "*mua*"
|
||||
truncate-lines t buffer-read-only t
|
||||
overwrite-mode 'overwrite-mode-binary))
|
||||
|
||||
|
||||
(provide 'mua)
|
||||
Reference in New Issue
Block a user