* emacs updates

This commit is contained in:
Dirk-Jan C. Binnema
2011-08-03 08:18:11 +03:00
parent b39b33b82d
commit ef086db2a7
6 changed files with 595 additions and 489 deletions

View File

@ -1,7 +1,7 @@
VERSION=$(shell git describe --tags --dirty) VERSION=$(shell git describe --tags --dirty)
EMACS=emacs EMACS=emacs
PREFIX=/usr/local PREFIX=/usr/local
ELS=mu.el mu-common.el mu-view.el mu-find.el ELS=mu.el mu-common.el mu-view.el mu-headers.el
ELCS=$(ELS:.el=.elc) ELCS=$(ELS:.el=.elc)
.PHONY=install .PHONY=install

View File

@ -53,19 +53,16 @@ notation) for the mail view and in replied/forwarded message quotations")
(defface mu-body-face '((t (:foreground "#8cd0d3"))) "") (defface mu-body-face '((t (:foreground "#8cd0d3"))) "")
(defface mu-header-face '((t (:foreground "#7f9f7f"))) "") (defface mu-header-face '((t (:foreground "#7f9f7f"))) "")
(defface mu-size-face '((t (:foreground "#889f7f"))) "") (defface mu-size-face '((t (:foreground "#889f7f"))) "")
(defface mu-body-face '((t (:foreground "#dcdccc"))) "")
(defface mu-flag-face '((t (:foreground "#dc56cc"))) "") (defface mu-flag-face '((t (:foreground "#dc56cc"))) "")
(defface mu-flag-face '((t (:foreground "#7f6677"))) "") (defface mu-path-face '((t (:foreground "#dc56cc"))) "")
(defface mu-unread-face '((t (:bold t))) "") (defface mu-unread-face '((t (:bold t))) "")
(defface mu-face '((t (:foreground "Gray" :italic t))) "") (defface mu-face '((t (:foreground "Gray" :italic t))) "")
(defvar mu-own-address "djcb" "regexp matching my own address") (defvar mu-own-address "djcb" "regexp matching my own address")
;;; internal stuff ;;; internal stuff
(defvar mu-parent-buf nil "the parent buffer for a (defvar mu-parent-buffer nil "the parent buffer for a
buffer (buffer-local), i.e., the buffer we'll return to when this buffer (buffer-local), i.e., the buffer we'll return to when this
buffer is killed") buffer is killed")
@ -89,14 +86,14 @@ buffer is killed")
etc.)" etc.)"
(propertize str 'face 'mu-face 'intangible t)) (propertize str 'face 'mu-face 'intangible t))
(setq mu-find-fields (setq mu-headers-fields
'( '(
(:date . 20) (:date . 20)
(:flags . 4) (:flags . 4)
(:from-or-to . 22) (:from-or-to . 22)
(:size . 8) (:size . 8)
(:subject . 40))) (:subject . 40)))
(setq mu-find-date-format "%x %X") (setq mu-headers-date-format "%x %X")
(setq mu-header-fields (setq mu-header-fields
'( :from '( :from
@ -176,102 +173,16 @@ Lisp data as a plist. Returns nil in case of error"
(defun mu-quit-buffer () (defun mu-quit-buffer ()
"kill this buffer, and switch to it's parentbuf if it is alive" "kill this buffer, and switch to it's parentbuf if it is alive"
(interactive) (interactive)
(let ((parentbuf (mu-parent-buf))) (let ((parentbuf mu-parent-buffer))
(kill-buffer) (kill-buffer)
(when (buffer-live-p parentbuf) (when (and parentbuf (buffer-live-p parentbuf))
(switch-to-buffer parentbuf)))) (switch-to-buffer parentbuf))))
(defun mu-get-marked () (defun mu-get-new-buffer (bufname)
"get all marked messages as a list; each element is a cell; "return a new buffer BUFNAME; if such already exists, kill the
with 'action', 'source' , 'target'). ie one of three: old one first"
('delete <path>) (when (get-buffer bufname)
('trash <path> <target>) (kill-buffer bufname))
('move <path> <target>)" (get-buffer-create bufname))
(let ((lst))
(with-current-buffer mu-find-buffer-name
(save-excursion
(goto-char (point-min))
(while (re-search-forward "^." nil t)
(let* ((char0 (match-string 0))
(action (get-text-property 0 'action char0))
(path (get-text-property 0 'path char0))
(target (get-text-property 0 'target char0)))
(cond
((eq action 'trash)
(setq lst (cons (list 'trash path target) lst)))
((eq action 'delete)
(setq lst (cons (list 'delete path) lst)))
((eq action 'move)
(setq lst (cons (list 'move path target) lst))))))))
lst))
(defun mu-execute ()
"execute marked actions on messages"
(interactive)
(let* ((markedcount (mu-count-marked))
(movenum (nth 0 markedcount)) (trashnum (nth 1 markedcount))
(deletenum (nth 2 markedcount)))
(if (= 0 (apply '+ markedcount))
(message "No messages are marked")
(if (and (< 0 movenum)
(y-or-n-p (format "Do you want to move %d message(s)?" movenum)))
(message "Moving message(s)"))
(if (and (< 0 trashnum)
(y-or-n-p (format "Do you want to move %d message(s) to trash?" trashnum)))
(message "Trashing message(s)"))
(if (and (< 0 deletenum)
(yes-or-no-p (format "Do you want to permanently delete %d message(s)?"
deletenum)))
(message "Deleting message(s)"))
(mu-find-refresh))))
(defun mu-foreach-marked (func)
"call FUNC for each marked message; the argument to FUNC is a list, either:
with 'action', 'source' , 'target'). ie one of three:
('delete <path>)
('trash <path> <target>)
('move <path> <target>)"
(with-current-buffer mu-find-buffer-name
(save-excursion
(goto-char (point-min))
(while (re-search-forward "^." nil t)
(move-beginning-of-line 1)
(let* ((char0 (match-string 0))
(action (get-text-property 0 'action char0))
(path (get-text-property 0 'path char0))
(target (get-text-property 0 'target char0)))
(cond
((eq action 'trash) (funcall func (list 'trash path target)))
((eq action 'delete) (funcall func (list 'delete path)))
((eq action 'move) (funcall func (list 'move path target)))))
(move-end-of-line 1)))))
(defun mu-count-marked ()
"return a vector with three items (marked-move marked-trash
marked-delete) which are the number of messages marked for each
of those"
(let ((result (make-vector 3 0)))
(mu-foreach-marked
(lambda (cell)
(case (car cell)
('move (aset result 0 (+ 1 (aref result 0))))
('trash (aset result 1 (+ 1 (aref result 1))))
('delete (aset result 2 (+ 1 (aref result 2)))))))
(append result nil))) ;; convert to list
(defun mu-unmark-all ()
"unmark all messages"
(interactive)
(let ((marked 0))
(mu-foreach-marked (lambda(cell) (setq marked (+ 1 marked))))
(if (= 0 marked)
(message "No messages are marked")
(when (y-or-n-p (format "Unmark %d message(s)?" marked))
(mu-foreach-marked
(lambda(cell)
(let ((inhibit-read-only t))
(delete-char 1)
(insert-and-inherit " "))))))))
(provide 'mu-common) (provide 'mu-common)

View File

@ -1,349 +0,0 @@
;;; mu-find.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)
(defvar mu-find-fields
'( (:date . 25)
(:from-or-to . 22)
(:subject . 40))
"a list of fields and their widths")
(defvar mu-find-sort-field "date"
"shortcut of the field to sort on (see mu-find (1))")
(defvar mu-find-sort-descending nil
"whether to sort in descending order")
;; internal stuff
(defconst mu-find-buffer-name " *mu-find*" "name of the mu
results buffer; name should start with a space")
(defvar mu-find-process nil "the possibly running find process")
(defconst mu-find-process-name "*<mu-find-process>*" "name of the mu
results buffer; name should start with a space")
(defconst mu-eom "\n;;eom\n" "marker for the end of message in
the mu find output")
(defvar mu-find-expression nil
"search expression for the current find buffer")
(defvar mu-buf "" "buffer for results data")
(defun mu-find-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"
(with-current-buffer mu-find-buffer-name
(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))
(save-match-data (insert (mu-find-header msg) ?\n)))
(setq mu-buf (substring mu-buf (match-end 0)))
(setq eom (string-match mu-eom mu-buf)))))))
(defun mu-find-process-sentinel (proc msg)
"Check the mu-find process upon completion"
(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-find returned %d"
exit-status))))
(t "Unknown status")))) ;; shouldn't happen
(when (get-buffer mu-find-buffer-name)
(with-current-buffer mu-find-buffer-name
(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-find output comes from
;; the database rather than file, and does _not_ contain the message body
(defun mu-find (expr)
"search in the mu database"
(interactive "s[mu] match expr: ")
(let* ((output (get-buffer mu-find-buffer-name)))
(when output (kill-buffer output))
(setq output (get-buffer-create mu-find-buffer-name) mu-buf "")
(let* ((dummy-arg "--fields=\"dummy\"") ;; ignored
(proc
(start-process mu-find-process-name mu-find-process-name
mu-binary
"find"
(if mu-home
(concat "--muhome=" mu-home) dummy-arg)
(if mu-find-sort-field
(concat "--sortfield=" mu-find-sort-field) dummy-arg)
(if mu-find-sort-descending "--descending" dummy-arg)
"--format=sexp"
"--quiet"
expr)))
(set-process-filter proc 'mu-find-process-filter)
(set-process-sentinel proc 'mu-find-process-sentinel)
(setq mu-find-process proc)
(switch-to-buffer output)
(setq mu-find-expression expr)
;; (make-variable-buffer-local mu-find-expression)
(mu-find-mode))))
(defun mu-find-display-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-find-display-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-find-display-contact tolst (- width 3) to-face))
(mu-find-display-contact fromlst width from-face)))
(make-string width ?\s)))
(defun mu-find-display-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-find-display-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-find-display-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-find-header (msg)
"convert a message s-expression into a header for display"
(let ((hdr (concat " " (mapconcat
(lambda (fieldinfo)
(let ((field (car fieldinfo)) (width (cdr fieldinfo)))
(case field
(:date
(mu-find-display-str (format-time-string mu-date-format-short
(plist-get msg :date)) width 'mu-date-face))
(:from
(mu-find-display-contact (plist-get msg :from) width 'mu-from-face))
(:to
(mu-find-display-contact (plist-get msg :to) width 'mu-to-face))
(:cc
(mu-find-display-contact (plist-get msg :cc) width 'mu-cc-face))
(:bcc
(mu-find-display-contact (plist-get msg :bcc) width 'mu-bcc-face))
(:flags
(mu-find-display-flags (plist-get msg :flags) width 'mu-flag-face))
(:size
(mu-find-display-size (plist-get msg :size) width 'mu-size-face))
(:from-or-to
(mu-find-display-from-or-to (plist-get msg :from)
(plist-get msg :to) width 'mu-from-face 'mu-to-face))
(:subject
(mu-find-display-str (plist-get msg :subject) width
'mu-subject-face)))))
mu-find-fields " "))))
(setq hdr (mu-find-set-props-for-flags hdr (plist-get msg :flags)))
(propertize hdr 'path (plist-get msg :path) 'front-sticky t)))
(defun mu-find-set-props-for-flags (hdr flags)
"set text properties/faces based on flags"
(if (memq 'unread flags)
(add-text-properties 0 (- (length hdr) 1) '(face (:weight bold)) hdr))
hdr)
(defun mu-find-mode ()
"major mode for displaying search results"
(interactive)
(kill-all-local-variables)
(use-local-map mu-find-mode-map)
(setq
major-mode 'mu-find-mode mode-name "*headers*"
truncate-lines t buffer-read-only t
overwrite-mode 'overwrite-mode-binary))
(defvar mu-find-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "q" 'mu-quit-buffer)
(define-key map "s" 'mu-find-change-sort)
(define-key map "g" 'mu-find-refresh)
;; marking/unmarking
(define-key map "m" 'mu-find-mark-for-move)
(define-key map "d" 'mu-find-mark-for-trash)
(define-key map "D" 'mu-find-mark-for-deletion)
(define-key map "u" 'mu-find-unmark)
;; message composition
(define-key map "r" 'mu-reply)
(define-key map "f" 'mu-forward)
(define-key map (kbd "RET") 'mu-find-view)
map)
"Keymap for \"mu-find\" buffers.")
(fset 'mu-find-mode-map mu-find-mode-map)
(defun mu-find-view ()
"display the message at the current line"
(interactive)
(let ((path (mu-get-path)))
(when path (mu-view path))))
(defun mu-find-next ()
"go to the next line; t if it worked, nil otherwise"
(interactive)
(if (or (/= 0 (forward-line 1)) (not (mu-get-path)))
(progn (message "No message after this one") nil)
t))
(defun mu-find-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-find-refresh ()
"re-run the query for the current search expression"
(interactive)
(unless (and mu-find-process
(eq (process-status mu-find-process) 'run))
(when mu-find-expression
(mu-find mu-find-expression))))
(defun mu-find-change-sort-order (fieldchar)
"change the sortfield to FIELDCHAR"
(interactive"cField to sort by ('d', 's', etc.; see mu-find(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-find-sort-field field)
(message "Invalid sort-field; use one of bcdfimpstz (see mu-find(1)"))
field))
(defun mu-find-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-find-sort-descending t) t)
(?a (setq mu-find-sort-descending nil) t)
(t (message
"Invalid sort-direction; choose either [a]scending or [d]escending") nil)))
(defun mu-find-mark (what)
"mark the current msg for 'trash, 'move, 'none"
(when (mu-get-path)
(move-beginning-of-line 1)
(let ((inhibit-read-only t) (overwrite-mode nil))
(if (and (not (eq what 'none)) (get-text-property (point) 'action))
(message "Message at pooint is already marked")
(progn
(delete-char 1)
(case what
('trash (insert-and-inherit
(mu-str (propertize "d" 'action what 'target "/foo/bar"))))
('delete (insert-and-inherit
(mu-str (propertize "D" 'action what 'target "/foo/bar"))))
('move (insert-and-inherit
(mu-str (propertize "m" 'action what 'target "/foo/bar"))))
('none (insert-and-inherit " ")))
(forward-line))))))
(defun mu-find-mark-for-trash ()
(interactive)
(mu-find-mark 'trash))
(defun mu-find-mark-for-deletion ()
(interactive)
(mu-find-mark 'delete))
(defun mu-find-mark-for-move ()
(interactive)
(mu-find-mark 'move))
(defun mu-find-unmark ()
(interactive)
(mu-find-mark 'none))
(defun mu-find-change-sort ()
"change sort field and direction"
(interactive)
(and (call-interactively 'mu-find-change-sort-order)
(call-interactively 'mu-find-change-sort-direction)))
(provide 'mu-find)

465
emacs/mu-headers.el Normal file
View File

@ -0,0 +1,465 @@
;;; 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'.
(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 "" "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"
(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))
(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"
(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 (expr)
"search in the mu database"
(interactive "s[mu] messages to find: ")
(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)))
(switch-to-buffer buf)
(set-process-filter proc 'mu-headers-process-filter)
(set-process-sentinel proc 'mu-headers-process-sentinel)
(setq mu-headers-process proc)
(set (make-local-variable 'mu-headers-expression) expr)
(mu-headers-mode)))
(defun mu-headers-display-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-display-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-display-contact tolst (- width 3) to-face))
(mu-headers-display-contact fromlst width from-face)))
(make-string width ?\s)))
(defun mu-headers-display-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-display-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-display-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-header (msg)
"convert a message s-expression into a header for display"
(let
((hdr
(concat " "
(mapconcat
(lambda (fieldinfo)
(let ((field (car fieldinfo)) (width (cdr fieldinfo)))
(case field
(:date
(mu-headers-display-str (format-time-string mu-date-format-short
(plist-get msg :date)) width 'mu-date-face))
(:from
(mu-headers-display-contact (plist-get msg :from) width 'mu-from-face))
(:to
(mu-headers-display-contact (plist-get msg :to) width 'mu-to-face))
(:cc
(mu-headers-display-contact (plist-get msg :cc) width 'mu-cc-face))
(:bcc
(mu-headers-display-contact (plist-get msg :bcc) width 'mu-bcc-face))
(:flags
(mu-headers-display-flags (plist-get msg :flags) width 'mu-flag-face))
(:size
(mu-headers-display-size (plist-get msg :size) width 'mu-size-face))
(:from-or-to
(mu-headers-display-from-or-to (plist-get msg :from)
(plist-get msg :to) width 'mu-from-face 'mu-to-face))
(:subject
(mu-headers-display-str (plist-get msg :subject) width
'mu-subject-face)))))
mu-headers-fields " "))))
(setq hdr (mu-headers-set-props-for-flags hdr (plist-get msg :flags)))
(propertize hdr 'path (plist-get msg :path) 'front-sticky t)))
(defun mu-headers-set-props-for-flags (hdr flags)
"set text properties/faces based on flags"
(if (memq 'unread flags)
(add-text-properties 0 (- (length hdr) 1) '(face (:weight bold)) hdr))
hdr)
(defun mu-headers-mode ()
"major mode for displaying search results"
(interactive)
(kill-all-local-variables)
(use-local-map mu-headers-mode-map)
(make-variable-buffer-local 'mu-parent-buffer)
(make-variable-buffer-local 'mu-headers-expression)
(make-variable-buffer-local 'mu-headers-process)
(setq
major-mode 'mu-headers-mode mode-name "*headers*"
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 "q" 'mu-quit-buffer)
(define-key map "s" 'mu-headers-change-sort)
(define-key map "g" 'mu-headers-refresh)
;; 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 "x" 'mu-headers-mark-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-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-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-refresh ()
"re-run the query for the current search expression"
(interactive)
(unless (and mu-headers-process
(eq (process-status mu-headers-process) 'run))
(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)))
;; message are 'marked' for moving, deletion etc. by have a special propertized
;; character at the start of the line; this propertized character holds an
;; 'action property, which tells what to do with this one (e.g.,'d'-> trash,
;; 'D'->delete, 'm'->'move'). 'u' (unmark) removes this mark, 'U' removes
;; all-marks. 'x'->mu-headers-execute removes all marks
(defun mu-headers-mark (what)
"mark the current msg for 'trash, 'move, 'none; return t if it
worked, nil otherwise"
(when (mu-get-path)
(move-beginning-of-line 1)
(let ((inhibit-read-only t) (overwrite-mode nil))
(if (and (not (eq what 'none)) (get-text-property (point) 'action))
(progn (message "Message at point is already marked") nil)
(progn
(delete-char 1)
(case what
('trash (insert-and-inherit
(mu-str (propertize "d" 'action what 'target "/foo/bar"))))
('delete (insert-and-inherit
(mu-str (propertize "D" 'action what 'target "/foo/bar"))))
('move (insert-and-inherit
(mu-str (propertize "m" 'action what 'target "/foo/bar"))))
('none (insert-and-inherit " ")))
(forward-line)
t)))))
(defun mu-headers-get-marked ()
"get all marked messages in the current buffer as a list; each
element is a cell; with 'action', 'source' , 'target'). ie one of
three:
('delete <path>)
('trash <path> <target>)
('move <path> <target>)"
(let ((lst))
(save-excursion
(goto-char (point-min))
(while (re-search-forward "^." nil t)
(let* ((char0 (match-string 0))
(action (get-text-property 0 'action char0))
(path (get-text-property 0 'path char0))
(target (get-text-property 0 'target char0)))
(cond
((eq action 'trash)
(setq lst (cons (list 'trash path target) lst)))
((eq action 'delete)
(setq lst (cons (list 'delete path) lst)))
((eq action 'move)
(setq lst (cons (list 'move path target) lst)))))))
lst))
(defun mu-headers-marks-execute ()
"execute marked actions on messages in the current buffer"
(interactive)
(let* ((markedcount (mu-headers-count-marked))
(movenum (nth 0 markedcount)) (trashnum (nth 1 markedcount))
(deletenum (nth 2 markedcount)))
(if (= 0 (apply '+ markedcount))
(message "No messages are marked")
(if (and (< 0 movenum)
(y-or-n-p (format "Do you want to move %d message(s)?" movenum)))
(message "Moving message(s)"))
(if (and (< 0 trashnum)
(y-or-n-p (format "Do you want to move %d message(s) to trash?" trashnum)))
(message "Trashing message(s)"))
(if (and (< 0 deletenum)
(yes-or-no-p (format "Do you want to permanently delete %d message(s)?"
deletenum)))
(message "Deleting message(s)")))))
(defun mu-headers-foreach-marked (func)
"call FUNC for each marked message in BUFFER; the argument
to FUNC is a list, either: with 'action', 'source' ,
'target'). ie one of three:
('delete <path>)
('trash <path> <target>)
('move <path> <target>)"
(save-excursion
(goto-char (point-min))
(while (re-search-forward "^." nil t)
(move-beginning-of-line 1)
(let* ((char0 (match-string 0))
(action (get-text-property 0 'action char0))
(path (get-text-property 0 'path char0))
(target (get-text-property 0 'target char0)))
(cond
((eq action 'trash) (funcall func (list 'trash path target)))
((eq action 'delete) (funcall func (list 'delete path)))
((eq action 'move) (funcall func (list 'move path target)))))
(move-end-of-line 1))))
(defun mu-headers-count-marked ()
"return a vector with three items (marked-move marked-trash
marked-delete) which are the number of messages marked for each
of those in the current buffer"
(let ((result (make-vector 3 0)))
(mu-foreach-marked
(lambda (cell)
(case (car cell)
('move (aset result 0 (+ 1 (aref result 0))))
('trash (aset result 1 (+ 1 (aref result 1))))
('delete (aset result 2 (+ 1 (aref result 2)))))))
(append result nil))) ;; convert to list
(defun mu-headers-unmark-all ()
"unmark all messages in the current buffer"
(interactive)
(let ((marked 0))
(mu-foreach-marked
(lambda(cell) (setq marked (+ 1 marked))))
(if (= 0 marked)
(message "No messages are marked")
(when (y-or-n-p (format "Unmark %d message(s)?" marked))
(mu-foreach-marked
(lambda(cell)
(let ((inhibit-read-only t))
(delete-char 1)
(insert-and-inherit " "))))))))
(defun mu-headers-mark-for-trash ()
(interactive)
(when (mu-headers-mark 'trash)
(message "Message marked for trashing")))
(defun mu-headers-mark-for-deletion ()
(interactive)
(when (mu-headers-mark 'delete)
(message "Message marked for deletion")))
(defun mu-headers-mark-for-move ()
(interactive)
(when (mu-headers-mark 'move)
(message "Message marked for moving")))
(defun mu-headers-unmark ()
(interactive)
(when (mu-headers-mark 'none)
(message "Message unmarked")))
(provide 'mu-headers)

View File

@ -23,7 +23,7 @@
;;; Commentary: ;;; Commentary:
;; mu message has functions to display a message ;; mu message has functions to display a single message
;;; Code: ;;; Code:
@ -38,6 +38,8 @@
"list of header fields to display in the message view") "list of header fields to display in the message view")
(defconst mu-view-buffer-name " *mu-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) (defun mu-view-header (field val val-face)
"get a header string (like 'Subject: foo')" "get a header string (like 'Subject: foo')"
@ -94,32 +96,53 @@
(mu-view-body msg 'mu-body-face) (mu-view-body msg 'mu-body-face)
)))) ))))
(defun mu-view (path) ;; 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 "display message at PATH in a new buffer; note that the action
of viewing a message may cause it to be moved/renamed; this of viewing a message may cause it to be moved/renamed; this
function returns the resulting name" function returns the resulting name. PARENTBUF refers to the
(interactive) 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)) (let ((str (mu-view-message path))
(buf (get-buffer mu-view-buffer-name))) (buf (mu-get-new-buffer mu-view-buffer-name)))
(when str (when str
(when buf (kill-buffer buf)) (with-current-buffer buf
(get-buffer-create mu-view-buffer-name)
(with-current-buffer mu-view-buffer-name
(let ((inhibit-read-only t)) (let ((inhibit-read-only t))
;; note, we set the path as a text-property ;; note, we set the path as a text-property
(insert (propertize str 'path path))) (insert (propertize str 'path path))))
(switch-to-buffer mu-view-buffer-name)
(mu-view-mode) (switch-to-buffer buf)
(goto-char (point-min)))))) (mu-view-mode)
;; these are buffer-local
(setq mu-parent-buffer parentbuf)
(setq mu-view-headers-buffer parentbuf)
(goto-char (point-min)))))
(defvar mu-view-mode-map (defvar mu-view-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
(define-key map "q" 'mu-quit-buffer) (define-key map "q" 'mu-quit-buffer)
(define-key map "s" 'mu-find) (define-key map "s" 'mu-headers)
(define-key map "f" 'mu-forward) (define-key map "f" 'mu-forward)
(define-key map "r" 'mu-reply) (define-key map "r" 'mu-reply)
;; navigation between messages
(define-key map "n" 'mu-view-next) (define-key map "n" 'mu-view-next)
(define-key map "p" 'mu-view-prev) (define-key map "p" 'mu-view-prev)
;; marking/unmarking
(define-key map "d" 'mu-view-mark-for-trash)
(define-key map "D" 'mu-view-mark-for-deletion)
(define-key map "m" 'mu-view-mark-for-move)
(define-key map "u" 'mu-view-unmark)
(define-key map "x" 'mu-view-marks-execute)
map) map)
"Keymap for \"mu-view\" buffers.") "Keymap for \"mu-view\" buffers.")
(fset 'mu-view-mode-map mu-view-mode-map) (fset 'mu-view-mode-map mu-view-mode-map)
@ -129,19 +152,66 @@ function returns the resulting name"
(interactive) (interactive)
(kill-all-local-variables) (kill-all-local-variables)
(use-local-map mu-view-mode-map) (use-local-map mu-view-mode-map)
(make-variable-buffer-local 'mu-parent-buffer)
(make-variable-buffer-local 'mu-headers-buffer)
(setq major-mode 'mu-view-mode mode-name "*mu-view*") (setq major-mode 'mu-view-mode mode-name "*mu-view*")
(setq truncate-lines t buffer-read-only t)) (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 () (defun mu-view-next ()
"move to the next message"
(interactive) (interactive)
(with-current-buffer mu-find-buffer-name (with-current-headers-buffer
(when (mu-find-next) (when (mu-headers-next)
(mu-view (mu-get-path))))) (mu-view (mu-get-path) (current-buffer)))))
(defun mu-view-prev () (defun mu-view-prev ()
"move to the previous message"
(interactive) (interactive)
(with-current-buffer mu-find-buffer-name (with-current-headers-buffer
(when (mu-find-prev) (when (mu-headers-prev)
(mu-view (mu-get-path))))) (mu-view (mu-get-path) (current-buffer)))))
(defun mu-view-mark-for-trash ()
"mark for thrashing"
(interactive)
(with-current-headers-buffer
(mu-headers-mark 'trash)))
(defun mu-view-mark-for-deletion ()
"mark for deletion"
(interactive)
(with-current-headers-buffer
(mu-headers-mark 'delete)))
(defun mu-view-mark-for-move ()
"mark for moving"
(interactive)
(with-current-headers-buffer
(mu-headers-mark 'move)))
(defun mu-view-unmark ()
"unmark this message"
(interactive)
(with-current-headers-buffer
(mu-headers-mark 'none)))
;; we don't allow executing marks from the view buffer, to protect user from
;; accidentally deleting stuff...
(defun mu-view-marks-execute ()
"give user a warning"
(interactive)
(message "Please go back to the headers list to execute your marks"))
(provide 'mu-view) (provide 'mu-view)

View File

@ -28,23 +28,23 @@
(require 'mu-view) (require 'mu-view)
(require 'mu-message) (require 'mu-message)
(define-key mu-find-mode-map "q" 'mu-quit-buffer) (define-key mu-headers-mode-map "q" 'mu-quit-buffer)
(define-key mu-find-mode-map "f" 'mu-find) (define-key mu-headers-mode-map "f" 'mu-headers)
(define-key mu-find-mode-map (kbd "<up>") 'mu-find-prev) (define-key mu-headers-mode-map (kbd "<up>") 'mu-headers-prev)
(define-key mu-find-mode-map (kbd "<down>") 'mu-find-next) (define-key mu-headers-mode-map (kbd "<down>") 'mu-headers-next)
(define-key mu-find-mode-map (kbd "RET") 'mu-find-view) (define-key mu-headers-mode-map (kbd "RET") 'mu-headers-view)
(define-key mu-find-mode-map "n" 'mu-find-next) (define-key mu-headers-mode-map "n" 'mu-headers-next)
(define-key mu-find-mode-map "p" 'mu-find-prev) (define-key mu-headers-mode-map "p" 'mu-headers-prev)
(define-key mu-find-mode-map "o" 'mu-find-change-sort) (define-key mu-headers-mode-map "o" 'mu-headers-change-sort)
(define-key mu-find-mode-map "g" 'mu-find-refresh) (define-key mu-headers-mode-map "g" 'mu-headers-refresh)
(define-key mu-find-mode-map "m" 'mu-find-mark-for-move) (define-key mu-headers-mode-map "m" 'mu-headers-mark-for-move)
(define-key mu-find-mode-map "d" 'mu-find-mark-for-trash) (define-key mu-headers-mode-map "d" 'mu-headers-mark-for-trash)
(define-key mu-find-mode-map "D" 'mu-find-mark-for-deletion) (define-key mu-headers-mode-map "D" 'mu-headers-mark-for-deletion)
(define-key mu-find-mode-map "u" 'mu-find-unmark) (define-key mu-headers-mode-map "u" 'mu-headers-unmark)
(define-key mu-find-mode-map "U" 'mu-unmark-all) (define-key mu-headers-mode-map "U" 'mu-headers-unmark-all)
(define-key mu-find-mode-map "r" 'mu-reply) (define-key mu-headers-mode-map "r" 'mu-headers-reply)
(define-key mu-find-mode-map "f" 'mu-forward) (define-key mu-headers-mode-map "f" 'mu-headers-forward)
(define-key mu-find-mode-map "x" 'mu-execute) (define-key mu-headers-mode-map "x" 'mu-headers-execute)
(define-key mu-view-mode-map "q" 'mu-quit-buffer) (define-key mu-view-mode-map "q" 'mu-quit-buffer)
@ -54,6 +54,15 @@
(define-key mu-view-mode-map "r" 'mu-reply) (define-key mu-view-mode-map "r" 'mu-reply)
(define-key mu-view-mode-map "f" 'mu-forward) (define-key mu-view-mode-map "f" 'mu-forward)
(define-key mu-view-mode-map "x" 'mu-execute) (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-execute)
(provide 'mu) (provide 'mu)