* 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
:group 'mu4e-compose)
(defcustom mu4e-compose-keep-self-cc nil
"Non-nil means your e-mail address is kept on the CC list when
replying to messages."
@ -250,6 +249,7 @@ separator is never written to file. Also see
(let ((sepa (propertize mail-header-separator
'intangible t
'read-only "Can't touch this"
'rear-nonsticky t
'font-lock-face 'mu4e-system-face)))
(goto-char (point-min))
;; search for the first empty line
@ -367,25 +367,58 @@ You can append flags."
(mu4e~compose-common-construct)))
(defun mu4e~compose-open-new-draft-file (compose-type &optional msg)
"Open a draft file for a new message, creating it if it does not
already exist, and optionally fill it with STR. Function also adds
the new message to the database. When the draft message is added to
the database, `mu4e-path-docid-map' will be updated, so that we can
use the new docid. Returns the full path to the new message."
(let* ((draft
(concat mu4e-maildir mu4e-drafts-folder "/cur/"
(mu4e~compose-message-filename-construct "DS")))
(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
(defvar mu4e~compose-trash-folder nil
"The trash-folder for this compose buffer, based on
`mu4e-trash-folder', which will be evaluated once.")
(defvar mu4e~compose-sent-folder nil
"The sent-folder for this compose buffer, based on
`mu4e-sent-folder', which will be evaluated once.")
(defvar mu4e~compose-drafts-folder nil
"The drafts-folder for this compose buffer, based on
`mu4e-drafts-folder', which will be evaluated once.")
(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
;; what these 'Sent mail' folders are for!
@ -406,8 +439,8 @@ needed, set the Fcc header, and register the handler function."
(let* ((mdir
(case mu4e-sent-messages-behavior
(delete nil)
(trash mu4e-trash-folder)
(sent mu4e-sent-folder)
(trash mu4e~compose-trash-folder)
(sent mu4e~compose-sent-folder)
(otherwise
(mu4e-error "unsupported value '%S' `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"))
;; 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
;; hander after we've done our thing.
;; handler after we've done our thing.
(setq message-fcc-handler-function
(lexical-let ((maildir mdir) (old-handler message-fcc-handler-function))
(lambda (file)
@ -440,7 +473,7 @@ needed, set the Fcc header, and register the handler function."
(mu4e~compose-insert-mail-header-separator)
(set-buffer-modified-p nil)
;; 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
`("^References:" "^Face:" "^X-Face:"
@ -486,7 +519,6 @@ needed, set the Fcc header, and register the handler function."
\\{message-mode-map}."
(let ((message-hidden-headers mu4e~compose-hidden-headers))
(use-local-map mu4e-compose-mode-map)
;; we set this here explicitly, since (as it has happened) a wrong
;; value for this (such as "") breaks address completion and other things
(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
(lambda ()
(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
"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:"
"^User-Agent:" "^In-Reply-To:")
"List of regexps with message headers that are to be hidden.")
(defun mu4e~compose-handler (compose-type &optional original-msg includes)
"Create a new draft message, or open an existing one.
COMPOSE-TYPE determines the kind of message to compose and is a
symbol, either `reply', `forward', `edit', `new'. `edit' is for
editing existing messages.
When COMPOSE-TYPE is `reply' or `forward', MSG should be a message
plist. If COMPOSE-TYPE is `new', ORIGINAL-MSG should be nil.
editing existing messages. When COMPOSE-TYPE is `reply' or
`forward', MSG should be a message plist. If COMPOSE-TYPE is
`new', ORIGINAL-MSG should be nil.
Optionally (when forwarding, replying) ORIGINAL-MSG is the original
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>)
for the attachements to include; file-name refers to
a file which our backend has conveniently saved for us (as a
tempfile).
The name of the draft folder is constructed from the concatenation
of `mu4e-maildir' and `mu4e-drafts-folder' (these must be set).
The message file name is a unique name determined by
`mu4e-send-draft-file-name'.
The initial STR would be created from either
`mu4e~compose-reply-construct', ar`mu4e~compose-forward-construct'
or `mu4e~compose-newmsg-construct'. The editing buffer is using
Gnus' `message-mode'."
(unless mu4e-maildir (mu4e-error "mu4e-maildir not set"))
(unless mu4e-drafts-folder (mu4e-error "mu4e-drafts-folder not set"))
(let ((inhibit-read-only t)
(draft
(if (member compose-type '(reply forward new))
(mu4e~compose-open-new-draft-file compose-type original-msg)
(if (eq compose-type 'edit)
(plist-get original-msg :path)
(mu4e-error "unsupported compose-type %S" compose-type)))))
(find-file draft)
;; insert mail-header-separator, which is needed by message mode to separate
;; headers and body. will be removed before saving to disk
(mu4e~compose-insert-mail-header-separator)
(insert "\n") ;; insert a newline after header separator
;; include files -- e.g. when forwarding a message with attachments,
;; 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)))
tempfile)."
;; this opens (or re-opens) a messages with all the basic headers set.
(mu4e~compose-open-draft compose-type original-msg)
;; insert mail-header-separator, which is needed by message mode to separate
;; headers and body. will be removed before saving to disk
(mu4e~compose-insert-mail-header-separator)
(insert "\n") ;; insert a newline after header separator
;; include files -- e.g. when forwarding a message with attachments,
;; 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 signature (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
(mu4e-compose-mode))
(defun mu4e-sent-handler (docid path)
"Handler function, called with DOCID and PATH for the just-sent
message. For Forwarded ('Passed') and Replied messages, try to set
@ -698,6 +705,7 @@ for draft messages."
(when (and (eq compose-type 'edit)
(not (member 'draft (mu4e-message-field msg :flags))))
(mu4e-warn "Editing is only allowed for draft messages"))
;; run the hooks
(mu4e~compose-run-hooks compose-type)
@ -771,7 +779,7 @@ message."
(defun mu4e~compose-mail (&optional to subject other-headers continue
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)

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)
"Offer to create DIR if it does not exist yet. Return t if the
dir already existed, or has been created, nil otherwise."
@ -774,7 +814,6 @@ is ignored."
(insert-image img imgpath nil t))))
(defun mu4e-hide-other-mu4e-buffers ()
"Bury mu4e-buffers (main, headers, view) (and delete all windows
displaying it). Do _not_ bury the current buffer, though."

View File

@ -211,21 +211,27 @@ regexp."
(defcustom mu4e-sent-folder "/sent"
"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
:safe 'stringp
:group 'mu4e-folders)
(defcustom mu4e-drafts-folder "/drafts"
"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
:safe 'stringp
:group 'mu4e-folders)
(defcustom mu4e-trash-folder "/trash"
"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
:safe 'stringp
:group 'mu4e-folders)