* mu4e: allow functions for special folders (WIP), implement for msg composition

This commit is contained in:
djcb 2012-09-27 10:03:58 +03:00
parent 5c2025a12c
commit 58b6be1d97
3 changed files with 141 additions and 88 deletions

View File

@ -67,7 +67,6 @@ sent folder."
:safe 'symbolp :safe 'symbolp
:group 'mu4e-compose) :group 'mu4e-compose)
(defcustom mu4e-compose-keep-self-cc nil (defcustom mu4e-compose-keep-self-cc nil
"Non-nil means your e-mail address is kept on the CC list when "Non-nil means your e-mail address is kept on the CC list when
replying to messages." replying to messages."
@ -250,6 +249,7 @@ separator is never written to file. Also see
(let ((sepa (propertize mail-header-separator (let ((sepa (propertize mail-header-separator
'intangible t 'intangible t
'read-only "Can't touch this" 'read-only "Can't touch this"
'rear-nonsticky t
'font-lock-face 'mu4e-system-face))) 'font-lock-face 'mu4e-system-face)))
(goto-char (point-min)) (goto-char (point-min))
;; search for the first empty line ;; search for the first empty line
@ -367,25 +367,58 @@ You can append flags."
(mu4e~compose-common-construct))) (mu4e~compose-common-construct)))
(defun mu4e~compose-open-new-draft-file (compose-type &optional msg) (defvar mu4e~compose-trash-folder nil
"Open a draft file for a new message, creating it if it does not "The trash-folder for this compose buffer, based on
already exist, and optionally fill it with STR. Function also adds `mu4e-trash-folder', which will be evaluated once.")
the new message to the database. When the draft message is added to (defvar mu4e~compose-sent-folder nil
the database, `mu4e-path-docid-map' will be updated, so that we can "The sent-folder for this compose buffer, based on
use the new docid. Returns the full path to the new message." `mu4e-sent-folder', which will be evaluated once.")
(let* ((draft (defvar mu4e~compose-drafts-folder nil
(concat mu4e-maildir mu4e-drafts-folder "/cur/" "The drafts-folder for this compose buffer, based on
(mu4e~compose-message-filename-construct "DS"))) `mu4e-drafts-folder', which will be evaluated once.")
(str (case compose-type
(reply (mu4e~compose-reply-construct msg))
(forward (mu4e~compose-forward-construct msg))
(new (mu4e~compose-newmsg-construct))
(t (mu4e-error "unsupported compose-type %S" compose-type)))))
(when str
(with-current-buffer (find-file-noselect draft)
(insert str)))
draft)) ;; return the draft buffer file
(defmacro mu4e~compose-setup-folder (var func msg)
"Create a buffer-permanent local folder variable."
`(progn
(set (make-local-variable ,var) (,func ,msg))
(put ,var 'permanent-local t)))
(defun mu4e~compose-open-draft (compose-type &optional msg)
"Open a draft file for a new message (when COMPOSE-TYPE is reply, forward or new),
or open an existing draft (when COMPOSE-TYPE is edit).
The name of the draft folder is constructed from the concatenation
of `mu4e-maildir' and `mu4e-drafts-folder' (the latter will be
evaluated, and its value will be available through
`mu4e~compose-drafts-folder'). The message file name is a unique
name determined by `mu4e-send-draft-file-name'. The initial
contents will be created from either
`mu4e~compose-reply-construct', or `mu4e~compose-forward-construct'
or `mu4e~compose-newmsg-construct'.
Also sets `mu4e~compose-trash-folder',
`mu4e~compose-drafts-folder' and `mu4e~compose-sent-folder' as
buffer-local, permanent variables."
(unless mu4e-maildir (mu4e-error "mu4e-maildir not set"))
(if (eq compose-type 'edit)
(find-file (mu4e-message-field msg :path))
(let* ((draftdir (mu4e-get-drafts-folder msg))
(draftfile (mu4e~compose-message-filename-construct "DS"))
(draftpath (concat mu4e-maildir draftdir "/cur/" draftfile)))
(find-file draftpath)
(insert
(case compose-type
(reply (mu4e~compose-reply-construct msg))
(forward (mu4e~compose-forward-construct msg))
(new (mu4e~compose-newmsg-construct))
(t (mu4e-error "unsupported compose-type %S" compose-type))))))
;; now, or draft file has been setup. setup the buffer-local special dirs.
(mu4e~compose-setup-folder 'mu4e~compose-trash-folder mu4e-get-trash-folder msg)
(mu4e~compose-setup-folder 'mu4e~compose-drafts-folder mu4e-get-drafts-folder msg)
(mu4e~compose-setup-folder 'mu4e~compose-sent-folder mu4e-get-sent-folder msg))
;; 'fcc' refers to saving a copy of a sent message to a certain folder. that's ;; 'fcc' refers to saving a copy of a sent message to a certain folder. that's
;; what these 'Sent mail' folders are for! ;; what these 'Sent mail' folders are for!
@ -406,8 +439,8 @@ needed, set the Fcc header, and register the handler function."
(let* ((mdir (let* ((mdir
(case mu4e-sent-messages-behavior (case mu4e-sent-messages-behavior
(delete nil) (delete nil)
(trash mu4e-trash-folder) (trash mu4e~compose-trash-folder)
(sent mu4e-sent-folder) (sent mu4e~compose-sent-folder)
(otherwise (otherwise
(mu4e-error "unsupported value '%S' `mu4e-sent-messages-behavior'." (mu4e-error "unsupported value '%S' `mu4e-sent-messages-behavior'."
mu4e-sent-messages-behavior)))) mu4e-sent-messages-behavior))))
@ -419,7 +452,7 @@ needed, set the Fcc header, and register the handler function."
(message-add-header (concat "Fcc: " fccfile "\n")) (message-add-header (concat "Fcc: " fccfile "\n"))
;; sadly, we cannot define as 'buffer-local'... this will screw up gnus ;; sadly, we cannot define as 'buffer-local'... this will screw up gnus
;; etc. if you run it after mu4e so, (hack hack) we reset it to the old ;; etc. if you run it after mu4e so, (hack hack) we reset it to the old
;; hander after we've done our thing. ;; handler after we've done our thing.
(setq message-fcc-handler-function (setq message-fcc-handler-function
(lexical-let ((maildir mdir) (old-handler message-fcc-handler-function)) (lexical-let ((maildir mdir) (old-handler message-fcc-handler-function))
(lambda (file) (lambda (file)
@ -440,7 +473,7 @@ needed, set the Fcc header, and register the handler function."
(mu4e~compose-insert-mail-header-separator) (mu4e~compose-insert-mail-header-separator)
(set-buffer-modified-p nil) (set-buffer-modified-p nil)
;; update the file on disk -- ie., without the separator ;; update the file on disk -- ie., without the separator
(mu4e~proc-add (buffer-file-name) mu4e-drafts-folder)) nil t)) (mu4e~proc-add (buffer-file-name) mu4e~compose-drafts-folder)) nil t))
(defconst mu4e~compose-hidden-headers (defconst mu4e~compose-hidden-headers
`("^References:" "^Face:" "^X-Face:" `("^References:" "^Face:" "^X-Face:"
@ -486,7 +519,6 @@ needed, set the Fcc header, and register the handler function."
\\{message-mode-map}." \\{message-mode-map}."
(let ((message-hidden-headers mu4e~compose-hidden-headers)) (let ((message-hidden-headers mu4e~compose-hidden-headers))
(use-local-map mu4e-compose-mode-map) (use-local-map mu4e-compose-mode-map)
;; we set this here explicitly, since (as it has happened) a wrong ;; we set this here explicitly, since (as it has happened) a wrong
;; value for this (such as "") breaks address completion and other things ;; value for this (such as "") breaks address completion and other things
(set (make-local-variable 'mail-header-separator) (set (make-local-variable 'mail-header-separator)
@ -523,7 +555,7 @@ needed, set the Fcc header, and register the handler function."
(add-hook 'message-sent-hook (add-hook 'message-sent-hook
(lambda () (lambda ()
(setq mu4e-sent-func 'mu4e-sent-handler) (setq mu4e-sent-func 'mu4e-sent-handler)
(mu4e~proc-sent (buffer-file-name) mu4e-drafts-folder)) nil))) (mu4e~proc-sent (buffer-file-name) mu4e~compose-drafts-folder)) nil)))
(defconst mu4e~compose-buffer-max-name-length 30 (defconst mu4e~compose-buffer-max-name-length 30
"Maximum length of the mu4e-send-buffer-name.") "Maximum length of the mu4e-send-buffer-name.")
@ -546,16 +578,15 @@ needed, set the Fcc header, and register the handler function."
'("^References:" "^Face:" "^X-Face:" "^X-Draft-From:" '("^References:" "^Face:" "^X-Face:" "^X-Draft-From:"
"^User-Agent:" "^In-Reply-To:") "^User-Agent:" "^In-Reply-To:")
"List of regexps with message headers that are to be hidden.") "List of regexps with message headers that are to be hidden.")
(defun mu4e~compose-handler (compose-type &optional original-msg includes) (defun mu4e~compose-handler (compose-type &optional original-msg includes)
"Create a new draft message, or open an existing one. "Create a new draft message, or open an existing one.
COMPOSE-TYPE determines the kind of message to compose and is a COMPOSE-TYPE determines the kind of message to compose and is a
symbol, either `reply', `forward', `edit', `new'. `edit' is for symbol, either `reply', `forward', `edit', `new'. `edit' is for
editing existing messages. editing existing messages. When COMPOSE-TYPE is `reply' or
`forward', MSG should be a message plist. If COMPOSE-TYPE is
When COMPOSE-TYPE is `reply' or `forward', MSG should be a message `new', ORIGINAL-MSG should be nil.
plist. If COMPOSE-TYPE is `new', ORIGINAL-MSG should be nil.
Optionally (when forwarding, replying) ORIGINAL-MSG is the original Optionally (when forwarding, replying) ORIGINAL-MSG is the original
message we will forward / reply to. message we will forward / reply to.
@ -564,62 +595,38 @@ Optionally (when forwarding) INCLUDES contains a list of
(:file-name <filename> :mime-type <mime-type> :disposition <disposition>) (:file-name <filename> :mime-type <mime-type> :disposition <disposition>)
for the attachements to include; file-name refers to for the attachements to include; file-name refers to
a file which our backend has conveniently saved for us (as a a file which our backend has conveniently saved for us (as a
tempfile). tempfile)."
;; this opens (or re-opens) a messages with all the basic headers set.
The name of the draft folder is constructed from the concatenation (mu4e~compose-open-draft compose-type original-msg)
of `mu4e-maildir' and `mu4e-drafts-folder' (these must be set). ;; insert mail-header-separator, which is needed by message mode to separate
;; headers and body. will be removed before saving to disk
The message file name is a unique name determined by (mu4e~compose-insert-mail-header-separator)
`mu4e-send-draft-file-name'. (insert "\n") ;; insert a newline after header separator
;; include files -- e.g. when forwarding a message with attachments,
The initial STR would be created from either ;; we take those from the original.
`mu4e~compose-reply-construct', ar`mu4e~compose-forward-construct' (save-excursion
or `mu4e~compose-newmsg-construct'. The editing buffer is using (goto-char (point-max)) ;; put attachments at the end
Gnus' `message-mode'." (dolist (att includes)
(unless mu4e-maildir (mu4e-error "mu4e-maildir not set")) (mml-attach-file
(unless mu4e-drafts-folder (mu4e-error "mu4e-drafts-folder not set")) (plist-get att :file-name) (plist-get att :mime-type))))
(let ((inhibit-read-only t) ;; include the message signature (if it's set); but not when editing an
(draft ;; existing message.
(if (member compose-type '(reply forward new)) (unless (eq compose-type 'edit)
(mu4e~compose-open-new-draft-file compose-type original-msg) (message-insert-signature))
(if (eq compose-type 'edit) ;; hide some headers
(plist-get original-msg :path) (let ((message-hidden-headers mu4e~compose-hidden-headers))
(mu4e-error "unsupported compose-type %S" compose-type))))) (message-hide-headers))
(find-file draft) ;; buffer is not user-modified yet
;; insert mail-header-separator, which is needed by message mode to separate (mu4e~compose-set-friendly-buffer-name compose-type)
;; headers and body. will be removed before saving to disk (set-buffer-modified-p nil)
(mu4e~compose-insert-mail-header-separator) ;; now jump to some useful positions, and start writing that mail!
(insert "\n") ;; insert a newline after header separator (if (member compose-type '(new forward))
(message-goto-to)
;; include files -- e.g. when forwarding a message with attachments, (message-goto-body))
;; we take those from the original.
(save-excursion
(goto-char (point-max)) ;; put attachments at the end
(dolist (att includes)
(mml-attach-file
(plist-get att :file-name) (plist-get att :mime-type))))
;; include the message header (if it's set); but not when editing an
;; existing message.
(unless (eq compose-type 'edit)
(message-insert-signature))
;; hide some headers
(let ((message-hidden-headers mu4e~compose-hidden-headers))
(message-hide-headers))
;; buffer is not user-modified yet
(mu4e~compose-set-friendly-buffer-name compose-type)
(set-buffer-modified-p nil)
;; now jump to some useful positions, and start writing that mail!
(if (member compose-type '(new forward))
(message-goto-to)
(message-goto-body)))
;; switch on the mode ;; switch on the mode
(mu4e-compose-mode)) (mu4e-compose-mode))
(defun mu4e-sent-handler (docid path) (defun mu4e-sent-handler (docid path)
"Handler function, called with DOCID and PATH for the just-sent "Handler function, called with DOCID and PATH for the just-sent
message. For Forwarded ('Passed') and Replied messages, try to set message. For Forwarded ('Passed') and Replied messages, try to set
@ -698,6 +705,7 @@ for draft messages."
(when (and (eq compose-type 'edit) (when (and (eq compose-type 'edit)
(not (member 'draft (mu4e-message-field msg :flags)))) (not (member 'draft (mu4e-message-field msg :flags))))
(mu4e-warn "Editing is only allowed for draft messages")) (mu4e-warn "Editing is only allowed for draft messages"))
;; run the hooks ;; run the hooks
(mu4e~compose-run-hooks compose-type) (mu4e~compose-run-hooks compose-type)
@ -771,7 +779,7 @@ message."
(defun mu4e~compose-mail (&optional to subject other-headers continue (defun mu4e~compose-mail (&optional to subject other-headers continue
switch-function yank-action send-actions return-action) switch-function yank-action send-actions return-action)
"mu4e's implementation of `compose-mail'." "This is mu4e's implementation of `compose-mail'."
(mu4e~compose-run-hooks 'new) (mu4e~compose-run-hooks 'new)

View File

@ -63,6 +63,46 @@ hour and minute fields will be nil if not given."
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; the standard folders can be functions too
(defun mu4e~get-folder (foldervar msg)
"Get message folder FOLDER. If FOLDER is a string, return it, if
it is a function, evaluate this function with MSG as
parameter (which may be `nil'), and return the result."
(unless (member foldervar '(mu4e-sent-folder mu4e-drafts-folder
mu4e-trash-folder))
(mu4e-error "Folder must be either mu4e-sent-folder,
mu4e-drafts-folder or mu4e-trash-folder (not %S)" foldervar))
(let* ((folder (symbol-value foldervar))
(val
(cond
((stringp folder) folder)
((functionp folder) (funcall folder msg))
(t (error "unsupported type for %S" folder)))))
(or val (mu4e-error "%S not defined" foldervar))))
(defun mu4e-get-sent-folder (msg)
"Get the sent folder. See `mu4e-sent-folder'."
(mu4e~get-folder 'mu4e-sent-folder msg))
(defun mu4e-get-drafts-folder (msg)
"Get the sent folder. See `mu4e-drafts-folder'."
(mu4e~get-folder 'mu4e-drafts-folder msg))
(defun mu4e-get-trash-folder (msg)
"Get the sent folder. See `mu4e-trash-folder'."
(mu4e~get-folder 'mu4e-trash-folder msg))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun mu4e-create-maildir-maybe (dir) (defun mu4e-create-maildir-maybe (dir)
"Offer to create DIR if it does not exist yet. Return t if the "Offer to create DIR if it does not exist yet. Return t if the
dir already existed, or has been created, nil otherwise." dir already existed, or has been created, nil otherwise."
@ -774,7 +814,6 @@ is ignored."
(insert-image img imgpath nil t)))) (insert-image img imgpath nil t))))
(defun mu4e-hide-other-mu4e-buffers () (defun mu4e-hide-other-mu4e-buffers ()
"Bury mu4e-buffers (main, headers, view) (and delete all windows "Bury mu4e-buffers (main, headers, view) (and delete all windows
displaying it). Do _not_ bury the current buffer, though." displaying it). Do _not_ bury the current buffer, though."

View File

@ -211,21 +211,27 @@ regexp."
(defcustom mu4e-sent-folder "/sent" (defcustom mu4e-sent-folder "/sent"
"Your folder for sent messages, relative to `mu4e-maildir', "Your folder for sent messages, relative to `mu4e-maildir',
e.g. \"/Sent Items\"." e.g. \"/Sent Items\". Instead of a string, may also be a function
that takes a msg (see `mu4e-message-get-field'), and returns a
folder."
:type 'string :type 'string
:safe 'stringp :safe 'stringp
:group 'mu4e-folders) :group 'mu4e-folders)
(defcustom mu4e-drafts-folder "/drafts" (defcustom mu4e-drafts-folder "/drafts"
"Your folder for draft messages, relative to `mu4e-maildir', "Your folder for draft messages, relative to `mu4e-maildir',
e.g. \"/drafts\"" e.g. \"/drafts\". Instead of a string, may also be a function
that takes a msg (see `mu4e-message-get-field'), and returns a
folder."
:type 'string :type 'string
:safe 'stringp :safe 'stringp
:group 'mu4e-folders) :group 'mu4e-folders)
(defcustom mu4e-trash-folder "/trash" (defcustom mu4e-trash-folder "/trash"
"Your folder for trashed messages, relative to `mu4e-maildir', "Your folder for trashed messages, relative to `mu4e-maildir',
e.g. \"/trash\"." e.g. \"/trash\". Instead of a string, may also be a function that
takes a msg (see `mu4e-message-get-field'), and returns a
folder."
:type 'string :type 'string
:safe 'stringp :safe 'stringp
:group 'mu4e-folders) :group 'mu4e-folders)