* mua updates
This commit is contained in:
@ -30,6 +30,8 @@
|
|||||||
|
|
||||||
(eval-when-compile (require 'cl))
|
(eval-when-compile (require 'cl))
|
||||||
|
|
||||||
|
(require 'ido)
|
||||||
|
|
||||||
(defconst mua/log-buffer-name "*mua-log*" "name of the logging buffer")
|
(defconst mua/log-buffer-name "*mua-log*" "name of the logging buffer")
|
||||||
|
|
||||||
(defun mua/warn (frm &rest args)
|
(defun mua/warn (frm &rest args)
|
||||||
@ -82,79 +84,5 @@ maildir."
|
|||||||
(chosen (ido-completing-read prompt showfolders)))
|
(chosen (ido-completing-read prompt showfolders)))
|
||||||
(concat (if fullpath mua/maildir "") chosen)))
|
(concat (if fullpath mua/maildir "") chosen)))
|
||||||
|
|
||||||
(defun mua/maildir-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/maildir-string-to-flags'
|
|
||||||
and `mua/maildir-flags-to-string'.
|
|
||||||
\[1\] http://cr.yp.to/proto/maildir.html."
|
|
||||||
(when (string-match ",\\(\[A-Z\]*\\)$" path)
|
|
||||||
(mua/maildir-string-to-flags (match-string 1 path))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defun mua/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)))))))
|
|
||||||
|
|
||||||
;; TODO: ensure flag string have the chars in ASCII-order (as per maildir spec)
|
|
||||||
;; TODO: filter-out duplicate flags
|
|
||||||
|
|
||||||
(defun mua/maildir-flags-to-string (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/maildir-string-to-flags'.
|
|
||||||
|
|
||||||
\[1\]: http://cr.yp.to/proto/maildir.html"
|
|
||||||
(when flags
|
|
||||||
(let ((kar
|
|
||||||
(case (car flags)
|
|
||||||
('draft ?D)
|
|
||||||
('flagged ?F)
|
|
||||||
('passed ?P)
|
|
||||||
('replied ?R)
|
|
||||||
('seen ?S)
|
|
||||||
('trashed ?T))))
|
|
||||||
(concat (and kar (string kar))
|
|
||||||
(mua/maildir-flags-to-string (cdr flags))))))
|
|
||||||
|
|
||||||
(defun mua/maildir-string-to-flags (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/maildir-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/maildir-string-to-flags (substring str 1))))))
|
|
||||||
|
|
||||||
(provide 'mua-common)
|
(provide 'mua-common)
|
||||||
|
|||||||
@ -51,15 +51,15 @@
|
|||||||
(defvar mua/hdrs-hash nil "the bol->path hash")
|
(defvar mua/hdrs-hash nil "the bol->path hash")
|
||||||
(defvar mua/hdrs-marks-hash nil "the hash for marked messages")
|
(defvar mua/hdrs-marks-hash nil "the hash for marked messages")
|
||||||
|
|
||||||
(defconst mua/eom "\n;;eom\n" "marker for the end of message in
|
(defconst mua/eom "\n;;eom\n" "*internal* Marker for the end of message in
|
||||||
the mu find output")
|
the mu find output.")
|
||||||
(defconst mua/hdrs-buffer-name "*mua-headers*"
|
(defconst mua/hdrs-buffer-name "*mua-headers*"
|
||||||
"name of the mua headers buffer")
|
"*internal* Name of the mua headers buffer.")
|
||||||
|
|
||||||
(defun mua/hdrs-proc-filter (proc str)
|
(defun mua/hdrs-proc-filter (proc str)
|
||||||
"process-filter for the 'mu find --format=sexp output; it
|
"A process-filter for the 'mu find --format=sexp output; it
|
||||||
accumulates the strings into valid sexps by checking of the
|
accumulates the strings into valid sexps by checking of the
|
||||||
';;eom' end-of-msg marker, and then evaluating them"
|
';;eom' end-of-msg marker, and then evaluating them."
|
||||||
(let ((procbuf (process-buffer proc)))
|
(let ((procbuf (process-buffer proc)))
|
||||||
(when (buffer-live-p procbuf)
|
(when (buffer-live-p procbuf)
|
||||||
(with-current-buffer procbuf
|
(with-current-buffer procbuf
|
||||||
@ -73,7 +73,7 @@ the mu find output")
|
|||||||
(setq eom (string-match mua/eom mua/buf))))))))))
|
(setq eom (string-match mua/eom mua/buf))))))))))
|
||||||
|
|
||||||
(defun mua/hdrs-proc-sentinel (proc msg)
|
(defun mua/hdrs-proc-sentinel (proc msg)
|
||||||
"Check the process upon completion"
|
"Check the process upon completion."
|
||||||
(let ((procbuf (process-buffer proc))
|
(let ((procbuf (process-buffer proc))
|
||||||
(status (process-status proc))
|
(status (process-status proc))
|
||||||
(exit-status (process-exit-status proc)))
|
(exit-status (process-exit-status proc)))
|
||||||
@ -89,7 +89,7 @@ the mu find output")
|
|||||||
(with-current-buffer procbuf
|
(with-current-buffer procbuf
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(goto-char (point-max))
|
(goto-char (point-max))
|
||||||
(mua/message msg)))))))
|
(mua/message "%s" msg)))))))
|
||||||
|
|
||||||
(defun mua/hdrs-search-execute (expr buf)
|
(defun mua/hdrs-search-execute (expr buf)
|
||||||
"search in the mu database; output the results in buffer BUF"
|
"search in the mu database; output the results in buffer BUF"
|
||||||
@ -100,8 +100,7 @@ the mu find output")
|
|||||||
(add-to-list args (concat "--sortfield=" mua/hdrs-sortfield)))
|
(add-to-list args (concat "--sortfield=" mua/hdrs-sortfield)))
|
||||||
(when mua/hdrs-sort-descending
|
(when mua/hdrs-sort-descending
|
||||||
(add-to-list args "--descending"))
|
(add-to-list args "--descending"))
|
||||||
(mua/log (concat mua/mu-binary " find " expr
|
(mua/log (concat mua/mu-binary " " (mapconcat 'identity args " ")))
|
||||||
(mapconcat 'identity args " ")))
|
|
||||||
;; now, do it!
|
;; now, do it!
|
||||||
(let ((proc (apply 'start-process "*mua-headers*" buf mua/mu-binary args)))
|
(let ((proc (apply 'start-process "*mua-headers*" buf mua/mu-binary args)))
|
||||||
(setq
|
(setq
|
||||||
@ -147,7 +146,7 @@ the mu find output")
|
|||||||
(make-local-variable 'mua/hdrs-marks-hash)
|
(make-local-variable 'mua/hdrs-marks-hash)
|
||||||
|
|
||||||
(setq
|
(setq
|
||||||
major-mode 'mua/mua-hdrs-mode mode-name "*mua-headers*"
|
major-mode 'mua/mua/hdrs-mode mode-name "*mua-headers*"
|
||||||
truncate-lines t buffer-read-only t
|
truncate-lines t buffer-read-only t
|
||||||
overwrite-mode 'overwrite-mode-binary))
|
overwrite-mode 'overwrite-mode-binary))
|
||||||
|
|
||||||
@ -185,12 +184,21 @@ the mu find output")
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
(defun mua/hdrs-set-path (path)
|
(defun mua/hdrs-set-path (path)
|
||||||
"map the bol of the current header to a path"
|
"Map the bol of the current header to an entry in
|
||||||
(puthash (line-beginning-position 1) path mua/hdrs-hash))
|
`mua/msg-file-map', and return the uid"
|
||||||
|
(let ((uid (mua/msg-file-register path)))
|
||||||
|
(puthash (line-beginning-position 1) uid mua/hdrs-hash)
|
||||||
|
uid))
|
||||||
|
|
||||||
|
(defun mua/hdrs-get-uid ()
|
||||||
|
"Get the uid for the message header at point."
|
||||||
|
(gethash (line-beginning-position 1) mua/hdrs-hash))
|
||||||
|
|
||||||
(defun mua/hdrs-get-path ()
|
(defun mua/hdrs-get-path ()
|
||||||
"get the path for the header at point"
|
"Get the current path for the header at point."
|
||||||
(gethash (line-beginning-position 1) mua/hdrs-hash))
|
(let ((uid (mua/hdrs-get-uid)))
|
||||||
|
(mua/msg-file-get-path uid)))
|
||||||
|
|
||||||
|
|
||||||
(defun mua/hdrs-append-message (msg)
|
(defun mua/hdrs-append-message (msg)
|
||||||
"append a message line to the buffer and register the message"
|
"append a message line to the buffer and register the message"
|
||||||
@ -309,9 +317,9 @@ fitting in WIDTH"
|
|||||||
|
|
||||||
(defun mua/hdrs-view ()
|
(defun mua/hdrs-view ()
|
||||||
(interactive)
|
(interactive)
|
||||||
(let ((path (mua/hdrs-get-path)))
|
(let ((uid (mua/hdrs-get-uid)))
|
||||||
(if path
|
(if uid
|
||||||
(mua/view path (current-buffer))
|
(mua/view uid (current-buffer))
|
||||||
(mua/warn "No message at point"))))
|
(mua/warn "No message at point"))))
|
||||||
|
|
||||||
(defun mua/hdrs-jump-to-maildir ()
|
(defun mua/hdrs-jump-to-maildir ()
|
||||||
@ -365,12 +373,12 @@ if the search process is not already running"
|
|||||||
|
|
||||||
;;; functions for marking
|
;;; functions for marking
|
||||||
|
|
||||||
(defun mua/hdrs-add-marked (src &optional dst)
|
(defun mua/hdrs-add-marked (uid &optional dst)
|
||||||
"Add the message at point to the markings hash"
|
"Add the message at point to the markings hash"
|
||||||
(let ((bol (line-beginning-position 1)))
|
(let ((bol (line-beginning-position 1)))
|
||||||
(if (gethash bol mua/hdrs-marks-hash)
|
(if (gethash bol mua/hdrs-marks-hash)
|
||||||
(mua/warn "Message is already marked")
|
(mua/warn "Message is already marked")
|
||||||
(progn (puthash bol (cons src dst) mua/hdrs-marks-hash) t))))
|
(progn (puthash bol (cons uid dst) mua/hdrs-marks-hash) t))))
|
||||||
|
|
||||||
(defun mua/hdrs-remove-marked ()
|
(defun mua/hdrs-remove-marked ()
|
||||||
"Remove the message at point from the markings hash"
|
"Remove the message at point from the markings hash"
|
||||||
@ -390,19 +398,19 @@ if the search process is not already running"
|
|||||||
"Mark the message at point with one of the symbols: move,
|
"Mark the message at point with one of the symbols: move,
|
||||||
delete, trash, unmark, unmark-all; the latter two are
|
delete, trash, unmark, unmark-all; the latter two are
|
||||||
pseudo-markings."
|
pseudo-markings."
|
||||||
(let ((target) (src (mua/hdrs-get-path)))
|
(let ((uid (mua/hdrs-get-uid)))
|
||||||
(when src
|
(when uid
|
||||||
(case action
|
(case action
|
||||||
(move
|
(move
|
||||||
(when (mua/hdrs-add-marked src
|
(when (mua/hdrs-add-marked uid
|
||||||
(mua/ask-maildir "Target maildir: " t)) ;; t->return fullpath
|
(mua/ask-maildir "Target maildir: " t)) ;; t->return fullpath
|
||||||
(mua/hdrs-set-marker ?m)))
|
(mua/hdrs-set-marker ?m)))
|
||||||
(trash
|
(trash
|
||||||
(when (mua/hdrs-add-marked src
|
(when (mua/hdrs-add-marked uid
|
||||||
(concat mua/maildir mua/trash-folder))
|
(concat mua/maildir mua/trash-folder))
|
||||||
(mua/hdrs-set-marker ?d)))
|
(mua/hdrs-set-marker ?d)))
|
||||||
(delete
|
(delete
|
||||||
(when (mua/hdrs-add-marked src "/dev/null")
|
(when (mua/hdrs-add-marked uid "/dev/null")
|
||||||
(mua/hdrs-set-marker ?D)))
|
(mua/hdrs-set-marker ?D)))
|
||||||
(unmark
|
(unmark
|
||||||
(when (mua/hdrs-remove-marked)
|
(when (mua/hdrs-remove-marked)
|
||||||
@ -427,12 +435,10 @@ pseudo-markings."
|
|||||||
(save-excursion
|
(save-excursion
|
||||||
(maphash
|
(maphash
|
||||||
(lambda(bol v)
|
(lambda(bol v)
|
||||||
(let* ((src (car v)) (target (cdr v)) (inhibit-read-only t)
|
(let* ((uid (car v)) (target (cdr v)) (inhibit-read-only t))
|
||||||
(newpath (mua/msg-move src target)))
|
(when (mua/msg-file-move-uid uid target)
|
||||||
(when newpath
|
|
||||||
;; remember the updated path -- for now not too useful
|
;; remember the updated path -- for now not too useful
|
||||||
;; as we're hiding the header, but...
|
;; as we're hiding the header, but...
|
||||||
(mua/hdrs-set-path newpath)
|
|
||||||
(goto-char bol)
|
(goto-char bol)
|
||||||
(mua/hdrs-remove-marked)
|
(mua/hdrs-remove-marked)
|
||||||
(put-text-property (line-beginning-position 1)
|
(put-text-property (line-beginning-position 1)
|
||||||
@ -446,20 +452,23 @@ pseudo-markings."
|
|||||||
(defun mua/hdrs-reply ()
|
(defun mua/hdrs-reply ()
|
||||||
"Reply to message at point."
|
"Reply to message at point."
|
||||||
(interactive)
|
(interactive)
|
||||||
(let* ((path (mua/hdrs-get-path))
|
(let* ((uid (mua/hdrs-get-uid))
|
||||||
|
(path (mua/hdrs-get-path))
|
||||||
(str (when path (mua/mu-view-sexp path)))
|
(str (when path (mua/mu-view-sexp path)))
|
||||||
(msg (and str (mua/msg-from-string str))))
|
(msg (and str (mua/msg-from-string str))))
|
||||||
(if msg
|
(if msg
|
||||||
(mua/msg-reply msg)
|
(mua/msg-reply msg uid)
|
||||||
(mua/warn "No message at point"))))
|
(mua/warn "No message at point"))))
|
||||||
|
|
||||||
(defun mua/hdrs-forward ()
|
(defun mua/hdrs-for ()
|
||||||
"Forward the message at point."
|
"Forward the message at point."
|
||||||
(interactive)
|
(interactive)
|
||||||
(let* ((path (mua/hdrs-get-path))
|
(let* ((uid (mua/hdrs-get-uid))
|
||||||
(msg (when path (mua/msg-from-path path))))
|
(path (mua/hdrs-get-path))
|
||||||
|
(str (when path (mua/mu-view-sexp path)))
|
||||||
|
(msg (and str (mua/msg-from-string str))))
|
||||||
(if msg
|
(if msg
|
||||||
(mua/msg-forward msg)
|
(mua/msg-reply msg uid)
|
||||||
(mua/warn "No message at point"))))
|
(mua/warn "No message at point"))))
|
||||||
|
|
||||||
(defun mua/hdrs-compose ()
|
(defun mua/hdrs-compose ()
|
||||||
|
|||||||
211
toys/mua/mua-msg-file.el
Normal file
211
toys/mua/mua-msg-file.el
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
;;; 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-file-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-file-register'.
|
||||||
|
|
||||||
|
All operation that change file names ultimately (should) end up
|
||||||
|
in `mua/msg-file-move', which will update the map after the
|
||||||
|
moving (using `mua/msg-file-update')
|
||||||
|
|
||||||
|
Other places of the code can use the uid to get the *current*
|
||||||
|
path of the file using `mua/msg-file-get-path'.
|
||||||
|
")
|
||||||
|
|
||||||
|
(defun mua/msg-file-register (path)
|
||||||
|
"Register a message PATH in the `mua/msg-file-map', and return
|
||||||
|
the uid for it."
|
||||||
|
(unless mua/msg-file-map
|
||||||
|
(setq mua/msg-file-map (make-hash-table :size 256 :rehash-size 2)))
|
||||||
|
(let ((uid (sha1 path)))
|
||||||
|
(puthash uid path mua/msg-file-map)
|
||||||
|
uid))
|
||||||
|
|
||||||
|
(defun mua/msg-file-update (uid path)
|
||||||
|
"Set the new path for the message identified by UID to
|
||||||
|
PATH."
|
||||||
|
(if (gethash uid mua/msg-file-map)
|
||||||
|
(puthash uid path mua/msg-file-map)
|
||||||
|
(mua/warn "No message file registered for uid")))
|
||||||
|
|
||||||
|
(defun mua/msg-file-get-path (uid)
|
||||||
|
"Get the current path for the message identified by UID."
|
||||||
|
(gethash uid mua/msg-file-map))
|
||||||
|
|
||||||
|
(defun mua/msg-file-move-uid (uid targetdir &optional flags)
|
||||||
|
"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-file-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; this must
|
||||||
|
be a list consisting of one or more of DFNPRST, mean
|
||||||
|
resp. Deleted, Flagged, New, Passed Replied, Seen and g, as
|
||||||
|
defined in [1]. See `mua/msg-file-string-to-flags' and
|
||||||
|
`mua/msg-file-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.
|
||||||
|
|
||||||
|
Function returns t the move succeeds, in other cases, it returns
|
||||||
|
`nil'.
|
||||||
|
|
||||||
|
\[1\] http://cr.yp.to/proto/maildir.html."
|
||||||
|
(let ((src (mua/msg-file-get-path uid)))
|
||||||
|
(unless src (error "Source path not registered."))
|
||||||
|
(let ((fulltarget (mua/mu-mv src targetdir flags)))
|
||||||
|
(when (and fulltarget (not (string= src fulltarget)))
|
||||||
|
(mua/msg-file-update uid fulltarget) ;; update the path
|
||||||
|
(mua/mu-remove-async src)
|
||||||
|
(unless (string= targetdir "/dev/null")
|
||||||
|
(mua/mu-add-async fulltarget)))))
|
||||||
|
t)
|
||||||
|
|
||||||
|
|
||||||
|
(defun mua/msg-file-mark-as-read (uid)
|
||||||
|
"Mark the message identified by UID as read if it is not so
|
||||||
|
already. In Maildir terms, this means moving the message from
|
||||||
|
\"new/\" to \"cur/\" (if it's not yet there), and setting the
|
||||||
|
\"S\" flag."
|
||||||
|
(let* ((path (mua/msg-file-get-path uid))
|
||||||
|
(flags (and path (mua/msg-file-flags-from-path path))))
|
||||||
|
(when (or (member 'new flags) (not (member 'seen flags)))
|
||||||
|
(let* ((newflags (delq 'new (cons 'seen flags)))
|
||||||
|
(target (mua/msg-file-maildir-from-path path t)))
|
||||||
|
(unless (mua/msg-file-move-uid uid target newflags)
|
||||||
|
(mua/warn "Failed to mark message as read"))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defun mua/msg-file-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-file-string-to-flags'
|
||||||
|
and `mua/msg-file-flags-to-string'.
|
||||||
|
\[1\] http://cr.yp.to/proto/maildir.html."
|
||||||
|
(when (string-match ",\\(\[A-Z\]*\\)$" path)
|
||||||
|
(mua/msg-file-string-to-flags (match-string 1 path))))
|
||||||
|
|
||||||
|
|
||||||
|
(defun mua/msg-file-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-file-flags-to-string (flags)
|
||||||
|
"Remove duplicates and sort the output of `mua/msg-file-flags-to-string-1'"
|
||||||
|
(concat
|
||||||
|
(sort
|
||||||
|
(remove-duplicates
|
||||||
|
(append (mua/msg-file-flags-to-string-1 flags) nil)) '>)))
|
||||||
|
|
||||||
|
(defun mua/msg-file-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-file-string-to-flags'.
|
||||||
|
|
||||||
|
\[1\]: http://cr.yp.to/proto/maildir.html"
|
||||||
|
(when flags
|
||||||
|
(let ((kar
|
||||||
|
(case (car flags)
|
||||||
|
('draft ?D)
|
||||||
|
('flagged ?F)
|
||||||
|
('passed ?P)
|
||||||
|
('replied ?R)
|
||||||
|
('seen ?S)
|
||||||
|
('trashed ?T))))
|
||||||
|
(concat (and kar (string kar))
|
||||||
|
(mua/msg-file-flags-to-string-1 (cdr flags))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defun mua/msg-file-string-to-flags (str)
|
||||||
|
"Remove duplicates from the output of `mua/msg-file-string-to-flags-1'"
|
||||||
|
(remove-duplicates (mua/msg-file-string-to-flags-1 str)))
|
||||||
|
|
||||||
|
(defun mua/msg-file-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-file-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-file-string-to-flags-1 (substring str 1))))))
|
||||||
|
|
||||||
|
(provide 'mua-msg-file)
|
||||||
@ -94,38 +94,9 @@ or if not available, :body-html converted to text)."
|
|||||||
(mua/msg-body-txt-or-html msg))
|
(mua/msg-body-txt-or-html msg))
|
||||||
(:maildir ;; messages gotten from mu-view don't have their maildir set...
|
(:maildir ;; messages gotten from mu-view don't have their maildir set...
|
||||||
(or (plist-get msg :maildir)
|
(or (plist-get msg :maildir)
|
||||||
(mua/maildir-from-path (mua/msg-field msg :path))))
|
(mua/msg-file-maildir-from-path (mua/msg-field msg :path))))
|
||||||
(t (plist-get msg field))))
|
(t (plist-get msg field))))
|
||||||
|
|
||||||
|
|
||||||
(defun mua/msg-move (src targetdir &optional flags)
|
|
||||||
"Move message at SRC to TARGETDIR using 'mu mv'; 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.
|
|
||||||
|
|
||||||
Optionally, you can specify the FLAGS for the new file; this must
|
|
||||||
be a list consisting of one or more of DFNPRST, mean
|
|
||||||
resp. Deleted, Flagged, New, Passed Replied, Seen and g, as
|
|
||||||
defined in [1]. See `mua/maildir-string-to-flags' and
|
|
||||||
`mua/maildir-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.
|
|
||||||
|
|
||||||
Function returns the target filename if the move succeeds, or
|
|
||||||
/dev/null if TARGETDIR was /dev/null; in other cases, it returns
|
|
||||||
`nil'.
|
|
||||||
|
|
||||||
\[1\] http://cr.yp.to/proto/maildir.html."
|
|
||||||
(let ((fulltarget (mua/mu-mv src targetdir flags)))
|
|
||||||
(when fulltarget
|
|
||||||
(mua/mu-remove-async src)
|
|
||||||
(unless (string= targetdir "/dev/null")
|
|
||||||
(mua/mu-add-async fulltarget)))
|
|
||||||
fulltarget))
|
|
||||||
|
|
||||||
|
|
||||||
;; functions for composing new messages (forward, reply and new)
|
;; functions for composing new messages (forward, reply and new)
|
||||||
|
|
||||||
@ -378,10 +349,13 @@ message.
|
|||||||
(random t)
|
(random t)
|
||||||
(replace-regexp-in-string "[:/]" "_" (system-name))))
|
(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)
|
(defun mua/msg-compose (str)
|
||||||
"Create a new draft message in the drafts folder with STR as
|
"Create a new draft message in the drafts folder with STR as
|
||||||
its contents, and open this message file for editing
|
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
|
The name of the draft folder is constructed from the concatenation of
|
||||||
`mua/maildir' and `mua/drafts-folder' (therefore, these must be set).
|
`mua/maildir' and `mua/drafts-folder' (therefore, these must be set).
|
||||||
@ -399,14 +373,15 @@ using Gnus' `message-mode'."
|
|||||||
(let ((draftfile (concat mua/maildir "/" mua/drafts-folder "/cur/"
|
(let ((draftfile (concat mua/maildir "/" mua/drafts-folder "/cur/"
|
||||||
(mua/msg-draft-file-name))))
|
(mua/msg-draft-file-name))))
|
||||||
(with-temp-file draftfile (insert str))
|
(with-temp-file draftfile (insert str))
|
||||||
(find-file draftfile)
|
(find-file draftfile) (rename-buffer mua/msg-draft-name t)
|
||||||
(rename-buffer mua/msg-draft-name t)
|
|
||||||
(message-mode)
|
(message-mode)
|
||||||
|
(make-local-variable 'mua/msg-forward-uid)
|
||||||
|
|
||||||
(message-goto-body)))
|
(message-goto-body)))
|
||||||
|
|
||||||
(defun mua/msg-reply (msg)
|
(defun mua/msg-reply (msg &optional reply-uid)
|
||||||
"Create a draft reply to MSG, and swith to an edit buffer with
|
"Create a draft reply to MSG, and swith to an edit buffer with
|
||||||
the draft message."
|
the draft message. PARENT-UID refers to the UID of the message wer"
|
||||||
(let* ((recipnum (+ (length (mua/msg-field msg :to))
|
(let* ((recipnum (+ (length (mua/msg-field msg :to))
|
||||||
(length (mua/msg-field msg :cc))))
|
(length (mua/msg-field msg :cc))))
|
||||||
(replyall (when (> recipnum 1)
|
(replyall (when (> recipnum 1)
|
||||||
@ -414,12 +389,14 @@ the draft message."
|
|||||||
(+ recipnum))))))
|
(+ recipnum))))))
|
||||||
;; exact num depends on some more things
|
;; exact num depends on some more things
|
||||||
(when (mua/msg-compose (mua/msg-create-reply msg replyall))
|
(when (mua/msg-compose (mua/msg-create-reply msg replyall))
|
||||||
|
(when reply-uid (setq mua/msg-reply-uid reply-uid))
|
||||||
(message-goto-body))))
|
(message-goto-body))))
|
||||||
|
|
||||||
(defun mua/msg-forward (msg)
|
(defun mua/msg-forward (msg &optional forward-uid)
|
||||||
"Create a draft forward for MSG, and swith to an edit buffer with
|
"Create a draft forward for MSG, and swith to an edit buffer with
|
||||||
the draft message."
|
the draft message."
|
||||||
(when (mua/msg-compose (mua/msg-create-forward msg))
|
(when (mua/msg-compose (mua/msg-create-forward msg))
|
||||||
|
(when forward-uid (setq mua/msg-forward-uid forward-uid))
|
||||||
(message-goto-to)))
|
(message-goto-to)))
|
||||||
|
|
||||||
(defun mua/msg-compose-new ()
|
(defun mua/msg-compose-new ()
|
||||||
@ -430,16 +407,6 @@ draft message."
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defun mua/msg-is-mua-message ()
|
|
||||||
"Check whether the current buffer refers a mua-message based on
|
|
||||||
the buffer file name; this is used in hooks we install on
|
|
||||||
message-mode to ensure we only do things with mua-generated
|
|
||||||
messages (mua is not the only user of `message-mode' after all)"
|
|
||||||
(let* ((fname (buffer-file-name))
|
|
||||||
(match (and fname (string-match mua/msg-file-prefix fname))))
|
|
||||||
(and (numberp match) (= 0 match))))
|
|
||||||
;; we simply check if file starts with `mu-msg-file-prefix'
|
|
||||||
|
|
||||||
(defun mua/msg-save-to-sent ()
|
(defun mua/msg-save-to-sent ()
|
||||||
"Move the message in this buffer to the sent folder. This is
|
"Move the message in this buffer to the sent folder. This is
|
||||||
meant to be called from message mode's `message-sent-hook'."
|
meant to be called from message mode's `message-sent-hook'."
|
||||||
@ -447,33 +414,43 @@ meant to be called from message mode's `message-sent-hook'."
|
|||||||
(unless mua/sent-folder (error "mua/sent-folder not set"))
|
(unless mua/sent-folder (error "mua/sent-folder not set"))
|
||||||
(let* ;; TODO: remove duplicate flags
|
(let* ;; TODO: remove duplicate flags
|
||||||
((newflags ;; remove Draft; maybe set 'Seen' as well?
|
((newflags ;; remove Draft; maybe set 'Seen' as well?
|
||||||
(delq 'draft (mua/maildir-flags-from-path (buffer-file-name))))
|
(delq 'draft (mua/msg-file-flags-from-path (buffer-file-name))))
|
||||||
(sent-msg
|
;; so, we register path => uid, then we move uid, then check the name
|
||||||
(mua/msg-move (buffer-file-name)
|
;; uid is referring to
|
||||||
(concat mua/maildir mua/sent-folder) ;; mua-sent-folder is only eg. "/sent"
|
(uid (mua/msg-file-register (buffer-file-name)))
|
||||||
(mua/maildir-flags-to-string newflags))))
|
(if (mua/msg-move uid
|
||||||
(if sent-msg ;; change our buffer file-name
|
(concat mua/maildir mua/sent-folder)
|
||||||
(set-visited-file-name sent-msg t t)
|
(mua/msg-file-flags-to-string newflags))
|
||||||
(mua/warn "Failed to save message to the Sent-folder")))))
|
(set-visited-file-name (mua/msg-file-get-path uid) t t)
|
||||||
|
(mua/warn "Failed to save message to the Sent-folder"))))))
|
||||||
|
|
||||||
|
|
||||||
(defun mua/msg-set-replied-flag ()
|
(defun mua/msg-set-replied-or-passed-flag ()
|
||||||
"Find the message we replied to, and set its 'Replied'
|
"Set the 'replied' flag on messages we replied to, and the
|
||||||
flag. This is meant to be called from message mode's
|
'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'."
|
`message-sent-hook'."
|
||||||
(if (mua/msg-is-mua-message) ;; only if we are mua
|
;; handle the replied-to message
|
||||||
(let ((msgid (mail-header-parse-addresses
|
(when mua/msg-reply-uid
|
||||||
(message-field-value "In-Reply-To")))
|
(let* ((oldflags (mua/msg-file-flags-from-path (mua/msg-file-get-path uid)))
|
||||||
(path (and msgid (mua/mu-run ;; TODO: check we only get one msgid back
|
(newflags (cons 'replied oldflags)))
|
||||||
"find" (concat "msgid:" msgid) "--exec=echo"))))
|
(mua/msg-file-move uid nil newflags)))
|
||||||
(if path
|
;; handle the forwarded message
|
||||||
(let ((newflags (cons 'replied (mua/maildir-flags-from-path path))))
|
(when mua/msg-forward-uid
|
||||||
(mua/msg-move path (mua/maildir-from-path path t) newflags))))))
|
(let* ((oldflags (mua/msg-file-flags-from-path (mua/msg-file-get-path uid)))
|
||||||
|
(newflags (cons 'passed oldflags)))
|
||||||
|
(mua/msg-file-move uid nil newflags))))
|
||||||
|
|
||||||
|
|
||||||
;; hook our functions up with sending of the message
|
;; 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-save-to-sent)
|
||||||
(add-hook 'message-sent-hook 'mua/msg-set-replied-flag)
|
(add-hook 'message-sent-hook 'mua/msg-set-replied-or-passed-flag)
|
||||||
|
|
||||||
|
|
||||||
(provide 'mua-msg)
|
(provide 'mua-msg)
|
||||||
|
|||||||
@ -59,36 +59,53 @@ to get it"
|
|||||||
(match-string 1 (cdr rv))
|
(match-string 1 (cdr rv))
|
||||||
(mua/warn "Failed to get version string"))))
|
(mua/warn "Failed to get version string"))))
|
||||||
|
|
||||||
(defun mua/mu-mv (src target &optional flags)
|
(defun mua/mu-mv (src target flags)
|
||||||
"Move a message at PATH to TARGET using 'mu mv'. SRC must be
|
"Move a message at PATH to TARGET using 'mu mv'. SRC must be
|
||||||
the full, absolute path to a message file, while TARGET must
|
the full, absolute path to a message file, while TARGET must be a
|
||||||
be a maildir - that is, the part _without_ cur/ or new/. 'mu mv'
|
maildir - that is, the part _without_ cur/ or new/. FLAGS sets
|
||||||
will calculate the target directory and the exact file name.
|
the flags of the message.
|
||||||
|
|
||||||
Optionally, you can specify the FLAGS for the new file; this must
|
TARGET can be nil, in which case only the flags are
|
||||||
be a list consisting of one or more of DFNPRST, mean
|
changed (which on the file-system level still implies a rename or
|
||||||
resp. Deleted, Flagged, New, Passed Replied, Seen and Trash, as
|
even a move if directory if the 'new' flags is added or
|
||||||
defined in [1]. See `mua/maildir-string-to-flags' and
|
removed). FLAGS can also be nil, in which they are not changed.
|
||||||
`mua/maildir-flags-to-string'.
|
If both TARGET and FLAGS are nil, nothing happens.
|
||||||
|
|
||||||
|
'mu mv' will calculate the full path to target directory and file
|
||||||
|
based on SRC, TARGET and FLAGS.
|
||||||
|
|
||||||
|
FLAGS must be either nil or 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-file-string-to-flags'
|
||||||
|
and `mua/msg-file-flags-to-string'.
|
||||||
|
|
||||||
Function returns the target filename if the move succeeds, or
|
Function returns the target filename if the move succeeds, or
|
||||||
/dev/null if TARGETDIR was /dev/null; in other cases, it returns
|
/dev/null if TARGETDIR was /dev/null; in other cases, it returns
|
||||||
`nil'.
|
`nil'.
|
||||||
|
|
||||||
\[1\] http://cr.yp.to/proto/maildir.html."
|
\[1\] http://cr.yp.to/proto/maildir.html."
|
||||||
(let ((flagstr
|
|
||||||
(and flags (mua/maildir-flags-to-string flags))))
|
;; precondition
|
||||||
(if (not (file-readable-p src))
|
(unless (or target flags) (error "Either target or flags must
|
||||||
(mua/warn "Cannot move unreadable file %s" src)
|
be provided."))
|
||||||
(let* ((rv (if flagstr
|
|
||||||
(mua/mu-run "mv" "--printtarget"
|
(if (not (file-readable-p src))
|
||||||
(concat "--flags=" flagstr) src target)
|
(mua/warn "Cannot move unreadable file %s" src)
|
||||||
(mua/mu-run "mv" "--printtarget" src target)))
|
(let ((argl '("mv" "--printtarget")))
|
||||||
|
(when flags (add-to-list 'argl (concat "--flags="
|
||||||
|
(mua/msg-file-flags-to-string flags)) t))
|
||||||
|
(add-to-list 'argl src t)
|
||||||
|
(when target (add-to-list 'argl target t))
|
||||||
|
(let* ((rv (apply 'mua/mu-run argl))
|
||||||
(code (car rv)) (output (cdr rv)))
|
(code (car rv)) (output (cdr rv)))
|
||||||
(if (/= 0 code)
|
;; we ignore the error where the target file already exists, as it is
|
||||||
|
;; likely due to the database not being fully up-to-date and/or sync'ed
|
||||||
|
;; with what we have on the screen
|
||||||
|
(if (not (member code `(0 ,mu-error-file-target-equals-source)))
|
||||||
(mua/warn "Moving message file failed: %s" (if output output "error"))
|
(mua/warn "Moving message file failed: %s" (if output output "error"))
|
||||||
(substring output 0 -1)))))) ;; the full target path, minus the \n
|
(substring output 0 -1)))))) ;; the full target path, minus the \n
|
||||||
|
|
||||||
|
|
||||||
(defun mua/mu-view-sexp (path)
|
(defun mua/mu-view-sexp (path)
|
||||||
"Return a string with an s-expression representing the message
|
"Return a string with an s-expression representing the message
|
||||||
at PATH; the format is described in `mua/msg-from-string', and
|
at PATH; the format is described in `mua/msg-from-string', and
|
||||||
@ -198,36 +215,38 @@ them."
|
|||||||
(defconst mu-error-file-stat-failed 77)
|
(defconst mu-error-file-stat-failed 77)
|
||||||
(defconst mu-error-file-readdir-failed 78)
|
(defconst mu-error-file-readdir-failed 78)
|
||||||
(defconst mu-error-file-invalid-source 79)
|
(defconst mu-error-file-invalid-source 79)
|
||||||
|
(defconst mu-error-file-target-equals-source 80)
|
||||||
|
|
||||||
|
|
||||||
(defun mua/mu-error (err)
|
(defun mua/mu-error (err)
|
||||||
"Convert an exit code from mu into a string."
|
"Convert an exit code from mu into a string."
|
||||||
(case err
|
(cond
|
||||||
(mu-error "General error")
|
((eql err mu-error) "General error")
|
||||||
(mu-error-in-parameters "Error in parameters")
|
((eql err mu-error-in-parameters) "Error in parameters")
|
||||||
(mu-error-internal "Internal error")
|
((eql err mu-error-internal) "Internal error")
|
||||||
(mu-error-no-matches "No matches")
|
((eql err mu-error-no-matches) "No matches")
|
||||||
(mu-error-xapian "Xapian error")
|
((eql err mu-error-xapian) "Xapian error")
|
||||||
(mu-error-xapian-query "Error in query")
|
((eql err mu-error-xapian-query) "Error in query")
|
||||||
(mu-error-xapian-dir-not-accessible "Database dir is not accessible")
|
((eql err mu-error-xapian-dir-not-accessible) "Database dir not accessible")
|
||||||
(mu-error-xapian-not-up-to-date "Database is not up-to-date")
|
((eql err mu-error-xapian-not-up-to-date) "Database is not up-to-date")
|
||||||
(mu-error-xapian-missing-data "Missing data")
|
((eql err mu-error-xapian-missing-data) "Missing data")
|
||||||
(mu-error-xapian-corruption "Database seems to be corrupted")
|
((eql err mu-error-xapian-corruption) "Database seems to be corrupted")
|
||||||
(mu-error-xapian-cannot-get-writelock "Database is locked")
|
((eql err mu-error-xapian-cannot-get-writelock) "Database is locked")
|
||||||
(mu-error-gmime "GMime-related error")
|
((eql err mu-error-gmime) "GMime-related error")
|
||||||
(mu-error-contacts "Contacts-related error")
|
((eql err mu-error-contacts) "Contacts-related error")
|
||||||
(mu-error-contacts-cannot-retrieve "Failed to retrieve contacts-cache")
|
((eql err mu-error-contacts-cannot-retrieve) "Failed to retrieve contacts")
|
||||||
(mu-error-file "File error")
|
((eql err mu-error-file) "File error")
|
||||||
(mu-error-file-invalid-name "Invalid file name")
|
((eql err mu-error-file-invalid-name) "Invalid file name")
|
||||||
(mu-error-file-cannot-link "Failed to link file")
|
((eql err mu-error-file-cannot-link) "Failed to link file")
|
||||||
(mu-error-file-cannot-open "Cannot open file")
|
((eql err mu-error-file-cannot-open) "Cannot open file")
|
||||||
(mu-error-file-cannot-read "Cannot read file")
|
((eql err mu-error-file-cannot-read) "Cannot read file")
|
||||||
(mu-error-file-cannot-create "Cannot create file")
|
((eql err mu-error-file-cannot-create) "Cannot create file")
|
||||||
(mu-error-file-cannot-mkdir "mu-mkdir failed")
|
((eql err mu-error-file-cannot-mkdir) "mu-mkdir failed")
|
||||||
(mu-error-file-stat-failed "stat(2) failed")
|
((eql err mu-error-file-stat-failed) "stat(2) failed")
|
||||||
(mu-error-file-readdir-failed "readdir failed")
|
((eql err mu-error-file-readdir-failed) "readdir failed")
|
||||||
(mu-error-file-invalid-source "Invalid source file")
|
((eql err mu-error-file-invalid-source) "Invalid source file")
|
||||||
(t "Unknown error")))
|
((eql err mu-error-file-target-equals-source) "Source is same as target")
|
||||||
|
(t (format "Unknown error (%d)" err))))
|
||||||
|
|
||||||
|
|
||||||
(provide 'mua-mu)
|
(provide 'mua-mu)
|
||||||
|
|||||||
@ -38,53 +38,49 @@
|
|||||||
"buffer name for mua/view buffers")
|
"buffer name for mua/view buffers")
|
||||||
|
|
||||||
(defvar mua/view-headers
|
(defvar mua/view-headers
|
||||||
'(:from :to :cc :subject :flags :date :maildir :attachments)
|
'(:from :to :cc :subject :flags :date :maildir :path :attachments)
|
||||||
"fields to display in the message view")
|
"Fields to display in the message view buffer.")
|
||||||
|
|
||||||
(defvar mua/hdrs-buffer nil
|
(defvar mua/hdrs-buffer nil
|
||||||
"headers buffer for the view")
|
"Headers buffer for the view in this buffer.")
|
||||||
|
|
||||||
(defun mua/view (path headersbuf)
|
(defvar mua/view-uid nil
|
||||||
"display message at PATH in a new buffer; note that the action
|
"The UID for the message being viewed in this buffer.")
|
||||||
of viewing a message may cause it to be moved/renamed; this
|
|
||||||
function returns the resulting name. PARENTBUF refers to the
|
|
||||||
|
(defun mua/view (uid headersbuf)
|
||||||
|
"display message identified by UID in a new buffer. Note that
|
||||||
|
the action of viewing a message may cause it to be moved/renamed;
|
||||||
|
this function returns the resulting name. PARENTBUF refers to the
|
||||||
buffer who invoked this view; this allows us to return there when
|
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.,
|
we quit from this view. Also, if PARENTBUF is a find buffer (ie.,
|
||||||
has mu-headers-mode as its major mode), this allows various
|
has mu-headers-mode as its major mode), this allows various
|
||||||
commands (navigation, marking etc.) to be applied to this
|
commands (navigation, marking etc.) to be applied to this
|
||||||
buffer."
|
buffer.
|
||||||
(let* ((sexp (mua/mu-view-sexp path))
|
|
||||||
|
For the reasoning to use UID here instead of just the path, see
|
||||||
|
`mua/msg-file-map'.
|
||||||
|
"
|
||||||
|
(let* ((path (mua/msg-file-get-path uid))
|
||||||
|
(sexp (and path (mua/mu-view-sexp path)))
|
||||||
(msg (and sexp (mua/msg-from-string sexp))))
|
(msg (and sexp (mua/msg-from-string sexp))))
|
||||||
(when msg
|
(if (not msg)
|
||||||
(switch-to-buffer (get-buffer-create mua/view-buffer-name))
|
(mua/warn "Cannot view message %S %S" uid path)
|
||||||
(let ((inhibit-read-only t))
|
(progn
|
||||||
(erase-buffer)
|
(switch-to-buffer (get-buffer-create mua/view-buffer-name))
|
||||||
(insert (mua/view-message msg)))
|
(let ((inhibit-read-only t))
|
||||||
|
(erase-buffer)
|
||||||
|
(insert (mua/view-message msg)))
|
||||||
|
|
||||||
(mua/view-mode)
|
(mua/view-mode)
|
||||||
|
|
||||||
(setq ;; these are buffer-local
|
(setq ;; these are buffer-local
|
||||||
mua/hdrs-buffer headersbuf
|
mua/view-uid uid
|
||||||
mua/parent-buffer headersbuf)
|
mua/hdrs-buffer headersbuf
|
||||||
(goto-char (point-min))
|
mua/parent-buffer headersbuf)
|
||||||
(mua/view-mark-as-read path))))
|
|
||||||
|
|
||||||
(defun mua/view-mark-as-read (path)
|
(goto-char (point-min))
|
||||||
"Mark the currently viewed as read if it is not so already. In
|
(mua/msg-file-mark-as-read uid)))))
|
||||||
Maildir terms, this means moving the message from \"new/\" to
|
|
||||||
\"cur/\" (if it's not yet there), and setting the \"S\" flag."
|
|
||||||
(let ((flags (mua/maildir-flags-from-path path)))
|
|
||||||
(unless (member 'seen flags) ;; do we need to do something?
|
|
||||||
(let* ((newflags (delq 'new (cons 'seen flags)))
|
|
||||||
(target (mua/maildir-from-path path t))
|
|
||||||
(newpath (mua/msg-move path target flags)))
|
|
||||||
;; now, attempt to update our parent header list...
|
|
||||||
(if newpath
|
|
||||||
(mua/with-hdrs-buffer
|
|
||||||
(if (string= (mua/hdrs-get-path) path) ;; doublecheck we have the right one
|
|
||||||
(mua/hdrs-set-path newpath)
|
|
||||||
(mua/warn "Headers buffer not point at correct message")))
|
|
||||||
(mua/warn "Failed to mark message as read"))))))
|
|
||||||
|
|
||||||
(defun mua/view-message (msg)
|
(defun mua/view-message (msg)
|
||||||
"construct a display string for the message"
|
"construct a display string for the message"
|
||||||
@ -157,7 +153,6 @@ buffer."
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defvar mua/view-mode-map
|
(defvar mua/view-mode-map
|
||||||
(let ((map (make-sparse-keymap)))
|
(let ((map (make-sparse-keymap)))
|
||||||
(define-key map "q" 'mua/quit-buffer)
|
(define-key map "q" 'mua/quit-buffer)
|
||||||
@ -172,10 +167,10 @@ buffer."
|
|||||||
(define-key map "p" 'mua/view-prev)
|
(define-key map "p" 'mua/view-prev)
|
||||||
|
|
||||||
;; marking/unmarking
|
;; marking/unmarking
|
||||||
(define-key map "d" '(lambda (mua/view-mark 'trash)))
|
(define-key map "d" '(lambda() (mua/view-mark 'trash)))
|
||||||
(define-key map "D" '(lambda (mua/view-mark 'delete)))
|
(define-key map "D" '(lambda() (mua/view-mark 'delete)))
|
||||||
(define-key map "m" '(lambda (mua/view-mark 'move)))
|
(define-key map "m" '(lambda() (mua/view-mark 'move)))
|
||||||
(define-key map "u" '(lambda (mua/view-mark 'unmark)))
|
(define-key map "u" '(lambda() (mua/view-mark 'unmark)))
|
||||||
(define-key map "x" 'mua/view-marked-execute)
|
(define-key map "x" 'mua/view-marked-execute)
|
||||||
map)
|
map)
|
||||||
"Keymap for \"*mua-view*\" buffers.")
|
"Keymap for \"*mua-view*\" buffers.")
|
||||||
@ -189,7 +184,7 @@ buffer."
|
|||||||
|
|
||||||
(make-local-variable 'mua/parent-buffer)
|
(make-local-variable 'mua/parent-buffer)
|
||||||
(make-local-variable 'mua/hdrs-buffer)
|
(make-local-variable 'mua/hdrs-buffer)
|
||||||
(make-local-variable 'mua/path)
|
(make-local-variable 'mua/view-uid)
|
||||||
|
|
||||||
(setq major-mode 'mua/view-mode mode-name "*mu-view*")
|
(setq major-mode 'mua/view-mode mode-name "*mu-view*")
|
||||||
(setq truncate-lines t buffer-read-only t))
|
(setq truncate-lines t buffer-read-only t))
|
||||||
@ -208,6 +203,25 @@ etc. persist."
|
|||||||
(set-buffer oldbuf))
|
(set-buffer oldbuf))
|
||||||
(mua/warn "hdrs buffer is dead"))))
|
(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 ()
|
(defun mua/view-next ()
|
||||||
"move to the next message; note, this will replace the current
|
"move to the next message; note, this will replace the current
|
||||||
buffer"
|
buffer"
|
||||||
|
|||||||
@ -35,6 +35,8 @@
|
|||||||
(require 'mua-msg)
|
(require 'mua-msg)
|
||||||
(require 'mua-hdrs)
|
(require 'mua-hdrs)
|
||||||
(require 'mua-view)
|
(require 'mua-view)
|
||||||
|
(require 'mua-msg-file)
|
||||||
|
|
||||||
|
|
||||||
(defvar mua/mu-home nil "location of the mu homedir, or nil for
|
(defvar mua/mu-home nil "location of the mu homedir, or nil for
|
||||||
the default")
|
the default")
|
||||||
@ -82,6 +84,7 @@ quitted, it switches back to its parent buffer")
|
|||||||
(defface mua/body-face '((t (:foreground "#8cd0d3"))) "")
|
(defface mua/body-face '((t (:foreground "#8cd0d3"))) "")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(setq mua/hdrs-mode-map
|
(setq mua/hdrs-mode-map
|
||||||
(let ((map (make-sparse-keymap)))
|
(let ((map (make-sparse-keymap)))
|
||||||
|
|
||||||
@ -146,7 +149,6 @@ quitted, it switches back to its parent buffer")
|
|||||||
(switch-to-buffer buf)
|
(switch-to-buffer buf)
|
||||||
(mua/mua-mode)))
|
(mua/mua-mode)))
|
||||||
|
|
||||||
|
|
||||||
(defvar mua/mua-mode-map
|
(defvar mua/mua-mode-map
|
||||||
(let ((map (make-sparse-keymap)))
|
(let ((map (make-sparse-keymap)))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user