mu4e: support incremental contacts

This commit is contained in:
djcb 2019-05-11 13:35:22 +03:00
parent 9edcae0203
commit 7563b89c9c
4 changed files with 225 additions and 267 deletions

View File

@ -262,6 +262,45 @@ message. Return nil if there are no recipients for the particular field."
(otherwise (otherwise
(mu4e-error "Unsupported field"))))) (mu4e-error "Unsupported field")))))
;;; RFC2822 handling of phrases in mail-addresses
;;; The optional display-name contains a phrase, it sits before the angle-addr
;;; as specified in RFC2822 for email-addresses in header fields.
;;; contributed by jhelberg
(defun mu4e~rfc822-phrase-type (ph)
"Return either atom, quoted-string, a corner-case or nil. This
checks for empty string first. Then quotes around the phrase
(returning 'rfc822-quoted-string). Then whether there is a quote
inside the phrase (returning 'rfc822-containing-quote).
The reverse of the RFC atext definition is then tested.
If it matches, nil is returned, if not, it is an 'rfc822-atom, which
is returned."
(cond
((= (length ph) 0) 'rfc822-empty)
((= (aref ph 0) ?\")
(if (string-match "\"\\([^\"\\\n]\\|\\\\.\\|\\\\\n\\)*\"" ph)
'rfc822-quoted-string
'rfc822-containing-quote)) ; starts with quote, but doesn't end with one
((string-match-p "[\"]" ph) 'rfc822-containing-quote)
((string-match-p "[\000-\037()\*<>@,;:\\\.]+" ph) nil)
(t 'rfc822-atom)))
(defun mu4e~rfc822-quoteit (ph)
"Quote RFC822 phrase only if necessary.
Atoms and quoted strings don't need quotes. The rest do. In
case a phrase contains a quote, it will be escaped."
(let ((type (mu4e~rfc822-phrase-type ph)))
(cond
((eq type 'rfc822-atom) ph)
((eq type 'rfc822-quoted-string) ph)
((eq type 'rfc822-containing-quote)
(format "\"%s\""
(replace-regexp-in-string "\"" "\\\\\"" ph)))
(t (format "\"%s\"" ph)))))
(defun mu4e~draft-from-construct () (defun mu4e~draft-from-construct ()
"Construct a value for the From:-field of the reply to MSG, "Construct a value for the From:-field of the reply to MSG,

View File

@ -187,7 +187,8 @@ The server output is as follows:
;; note: we use 'member', to match (:contacts nil) ;; note: we use 'member', to match (:contacts nil)
((plist-member sexp :contacts) ((plist-member sexp :contacts)
(funcall mu4e-contacts-func (funcall mu4e-contacts-func
(plist-get sexp :contacts))) (plist-get sexp :contacts)
(plist-get sexp :tstamp)))
;; something got moved/flags changed ;; something got moved/flags changed
((plist-get sexp :update) ((plist-get sexp :update)
@ -495,15 +496,17 @@ to a temporary file, then respond with
"Sends a ping to the mu server, expecting a (:pong ...) in response." "Sends a ping to the mu server, expecting a (:pong ...) in response."
(mu4e~proc-send-command "cmd:ping")) (mu4e~proc-send-command "cmd:ping"))
(defun mu4e~proc-contacts (personal after) (defun mu4e~proc-contacts (personal after tstamp)
"Sends the contacts command to the mu server. "Ask for contacts with PERSONAL AFTER TSTAMP.
A (:contacts (<list>)) is expected in response. If PERSONAL is S-expression (:contacts (<list>) :tstamp \"<tstamp>\") is expected in
non-nil, only get personal contacts, if AFTER is non-nil, get response. If PERSONAL is non-nil, only get personal contacts, if
only contacts seen AFTER (the time_t value)." AFTER is non-nil, get only contacts seen AFTER (the time_t
value)."
(mu4e~proc-send-command (mu4e~proc-send-command
"cmd:contacts personal:%s after:%d" "cmd:contacts personal:%s after:%d tstamp:%s"
(if personal "true" "false") (if personal "true" "false")
(or after 0))) (or after 0)
(or tstamp "0")))
(defun mu4e~proc-view (docid-or-msgid &optional images decrypt) (defun mu4e~proc-view (docid-or-msgid &optional images decrypt)
"Get a message DOCID-OR-MSGID. "Get a message DOCID-OR-MSGID.

View File

@ -1,7 +1,6 @@
;;; mu4e-utils.el -- part of mu4e, the mu mail user agent ;;; mu4e-utils.el -- part of mu4e, the mu mail user agent
;; ;;
;; Copyright (C) 2011-2017 Dirk-Jan C. Binnema ;; Copyright (C) 2011-2019 Dirk-Jan C. Binnema
;; Copyright (C) 2013 Tibor Simko
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
@ -666,7 +665,8 @@ process."
"Indexing completed; processed %d, updated %d, cleaned-up %d" "Indexing completed; processed %d, updated %d, cleaned-up %d"
(plist-get info :processed) (plist-get info :updated) (plist-get info :processed) (plist-get info :updated)
(plist-get info :cleaned-up)) (plist-get info :cleaned-up))
(unless (zerop (plist-get info :updated)) (unless (and (not (string= mu4e~contacts-tstamp "0"))
(zerop (plist-get info :updated)))
(mu4e~request-contacts-maybe) (mu4e~request-contacts-maybe)
(run-hooks 'mu4e-index-updated-hook))))) (run-hooks 'mu4e-index-updated-hook)))))
((plist-get info :message) ((plist-get info :message)
@ -680,121 +680,39 @@ process."
(t (error "Error %d: %s" errcode errmsg)))) (t (error "Error %d: %s" errcode errmsg))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; RFC2822 handling of phrases in mail-addresses (defvar mu4e~contacts-tstamp "0"
;;; The optional display-name contains a phrase, it sits before the angle-addr "Timestamp for the most recent contacts update." )
;;; as specified in RFC2822 for email-addresses in header fields.
;;; contributed by jhelberg
(defun mu4e~rfc822-phrase-type (ph) (defun mu4e~update-contacts (contacts &optional tstamp)
"Return either atom, quoted-string, a corner-case or nil. This "Rceive a sorted list of CONTACTS.
checks for empty string first. Then quotes around the phrase Each of the contacts has the form
(returning 'rfc822-quoted-string). Then whether there is a quote (FULL_EMAIL_ADDRESS . RANK) and fill the hash
inside the phrase (returning 'rfc822-containing-quote). `mu4e~contacts' with it, with each contact mapped to an integer
The reverse of the RFC atext definition is then tested. for their ranking.
If it matches, nil is returned, if not, it is an 'rfc822-atom, which
is returned."
(cond
((= (length ph) 0) 'rfc822-empty)
((= (aref ph 0) ?\")
(if (string-match "\"\\([^\"\\\n]\\|\\\\.\\|\\\\\n\\)*\"" ph)
'rfc822-quoted-string
'rfc822-containing-quote)) ; starts with quote, but doesn't end with one
((string-match-p "[\"]" ph) 'rfc822-containing-quote)
((string-match-p "[\000-\037()\*<>@,;:\\\.]+" ph) nil)
(t 'rfc822-atom)))
(defun mu4e~rfc822-quoteit (ph)
"Quote RFC822 phrase only if necessary.
Atoms and quoted strings don't need quotes. The rest do. In
case a phrase contains a quote, it will be escaped."
(let ((type (mu4e~rfc822-phrase-type ph)))
(cond
((eq type 'rfc822-atom) ph)
((eq type 'rfc822-quoted-string) ph)
((eq type 'rfc822-containing-quote)
(format "\"%s\""
(replace-regexp-in-string "\"" "\\\\\"" ph)))
(t (format "\"%s\"" ph)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defsubst mu4e~process-contact (contact)
"Process CONTACT, and either return nil when it should not be included,
or (rfc822-string . CONTACT) otherwise."
(when mu4e-contact-rewrite-function
(setq contact (funcall mu4e-contact-rewrite-function contact)))
(when contact
(let ((name (plist-get contact :name))
(mail (plist-get contact :mail))
(ignore-rx (or mu4e-compose-complete-ignore-address-regexp "$^")))
(when (and mail (not (string-match ignore-rx mail)))
(cons
(if name (format "%s <%s>" (mu4e~rfc822-quoteit name) mail) mail)
contact)))))
(defun mu4e~sort-contacts (contacts)
"Destructively sort contacts (only for cycling) in order of
'mostly likely contact'.t See the code for the detail"
(let* ((now (+ (float-time) 3600)) ;; allow for clock diffs
(recent (- (float-time) (* 15 24 3600))))
(cl-sort contacts
(lambda (c1 c2)
(let* ( (c1 (cdr c1)) (c2 (cdr c2))
(personal1 (plist-get c1 :personal))
(personal2 (plist-get c2 :personal))
;; note: freq, tstamp can only be missing if the rewrite
;; function removed them. If the rewrite function changed the
;; contact somehow, we guess it's important.
(freq1 (or (plist-get c1 :freq) 500))
(freq2 (or (plist-get c2 :freq) 500))
(tstamp1 (or (plist-get c1 :tstamp) now))
(tstamp2 (or (plist-get c2 :tstamp) now)))
;; only one is personal? if so, that one comes first
(if (not (equal personal1 personal2))
(if personal1 t nil)
;; only one is recent? that one comes first
(if (not (equal (> tstamp1 recent) (> tstamp2 recent)))
(> tstamp1 tstamp2)
;; otherwise, use the frequency
(> freq1 freq2))))))))
(defun mu4e~sort-contacts-for-completion (contacts)
"Takes CONTACTS, which is a list of RFC-822 addresses, and sort them based
on the ranking in `mu4e~contacts.'"
(cl-sort contacts
(lambda (c1 c2)
(let ((rank1 (gethash c1 mu4e~contacts))
(rank2 (gethash c2 mu4e~contacts)))
(< rank1 rank2)))))
;; start and stopping
(defun mu4e~fill-contacts (contact-data)
"We receive a list of contacts, which each contact of the form
(:me NAME :mail EMAIL :tstamp TIMESTAMP :freq FREQUENCY) and
fill the hash `mu4e~contacts' with it, with each contact mapped
to an integer for their ranking.
This is used by the completion function in mu4e-compose." This is used by the completion function in mu4e-compose."
(let ((contacts) (rank 0)) ;; We have our nicely sorted list, map them to a list
(dolist (contact contact-data) ;; of increasing integers. We use that map in the composer
(let ((contact-maybe (mu4e~process-contact contact))) ;; to sort them there. It would have been so much easier if emacs
;; note, this gives cells (rfc822-address . contact) ;; allowed us to use the sorted-list as-is, but no such luck.
(when contact-maybe (push contact-maybe contacts)))) (let ((n 0))
(setq contacts (mu4e~sort-contacts contacts)) (unless mu4e~contacts
;; now, we have our nicely sorted list, map them to a list (setq mu4e~contacts (make-hash-table :test 'equal :weakness nil
;; of increasing integers. We use that map in the composer :size (length contacts))))
;; to sort them there. It would have been so much easier if emacs
;; allowed us to use the sorted-list as-is, but no such luck.
(setq mu4e~contacts (make-hash-table :test 'equal :weakness nil
:size (length contacts)))
(dolist (contact contacts) (dolist (contact contacts)
(puthash (car contact) rank mu4e~contacts) (incf n)
(incf rank)) (let ((address
(mu4e-index-message "Contacts received: %d" (if (functionp mu4e-contact-process-function)
(hash-table-count mu4e~contacts)))) (funcall mu4e-contact-process-function (car contact))
(car contact))))
(when address
(puthash address (cdr contact) mu4e~contacts))))
(setq mu4e~contacts-tstamp (or tstamp "0"))
(mu4e-index-message "Contacts updated: %d; total %d"
n (hash-table-count mu4e~contacts))))
(defun mu4e~check-requirements () (defun mu4e~check-requirements ()
"Check for the settings required for running mu4e." "Check for the settings required for running mu4e."
@ -855,21 +773,21 @@ Checks whether the server process is live."
the list of contacts we use for autocompletion; otherwise, do the list of contacts we use for autocompletion; otherwise, do
nothing." nothing."
(when mu4e-compose-complete-addresses (when mu4e-compose-complete-addresses
(setq mu4e-contacts-func 'mu4e~fill-contacts) (setq mu4e-contacts-func 'mu4e~update-contacts)
(mu4e~proc-contacts (mu4e~proc-contacts
mu4e-compose-complete-only-personal mu4e-compose-complete-only-personal
(when mu4e-compose-complete-only-after (when mu4e-compose-complete-only-after
(float-time (float-time
(apply 'encode-time (apply 'encode-time
(mu4e-parse-time-string mu4e-compose-complete-only-after))))))) (mu4e-parse-time-string mu4e-compose-complete-only-after))))
mu4e~contacts-tstamp)))
(defun mu4e~start (&optional func) (defun mu4e~start (&optional func)
"If `mu4e-contexts' have been defined, but we don't have a "If `mu4e-contexts' have been defined, but we don't have a
context yet, switch to the matching one, or none matches, the context yet, switch to the matching one, or none matches, the
first. first. If mu4e is already running, execute function FUNC (if
If mu4e is already running, execute function FUNC (if non-nil). non-nil). Otherwise, check various requireme`'nts, then start mu4e.
Otherwise, check various requirements, then start mu4e. When When successful, call FUNC (if non-nil) afterwards."
successful, call FUNC (if non-nil) afterwards."
;; if we're already running, simply go to the main view ;; if we're already running, simply go to the main view
(if (mu4e-running-p) ;; already running? (if (mu4e-running-p) ;; already running?
(when func (funcall func)) ;; yes! run func if defined (when func (funcall func)) ;; yes! run func if defined
@ -905,7 +823,8 @@ successful, call FUNC (if non-nil) afterwards."
"Clear any cached resources." "Clear any cached resources."
(setq (setq
mu4e-maildir-list nil mu4e-maildir-list nil
mu4e~contacts nil)) mu4e~contacts nil
mu4e~contacts-tstamp "0"))
(defun mu4e~stop () (defun mu4e~stop ()
"Stop the mu4e session." "Stop the mu4e session."

View File

@ -1,6 +1,6 @@
;;; mu4e-vars.el -- part of mu4e, the mu mail user agent ;;; mu4e-vars.el -- part of mu4e, the mu mail user agent
;; ;;
;; Copyright (C) 2011-2018 Dirk-Jan C. Binnema ;; Copyright (C) 2011-2019 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
@ -37,7 +37,7 @@
"Location of the mu homedir, or nil for the default." "Location of the mu homedir, or nil for the default."
:group 'mu4e :group 'mu4e
:type '(choice (const :tag "Default location" nil) :type '(choice (const :tag "Default location" nil)
(directory :tag "Specify location")) (directory :tag "Specify location"))
:safe 'stringp) :safe 'stringp)
(defcustom mu4e-mu-binary (executable-find "mu") (defcustom mu4e-mu-binary (executable-find "mu")
@ -111,7 +111,7 @@ faster." :type 'boolean :group 'mu4e :safe 'booleanp)
If nil, don't update automatically. Note, changes in If nil, don't update automatically. Note, changes in
`mu4e-update-interval' only take effect after restarting mu4e." `mu4e-update-interval' only take effect after restarting mu4e."
:type '(choice (const :tag "No automatic update" nil) :type '(choice (const :tag "No automatic update" nil)
(integer :tag "Seconds")) (integer :tag "Seconds"))
:group 'mu4e :group 'mu4e
:safe 'integerp) :safe 'integerp)
@ -199,27 +199,27 @@ where QUERY is a string with a mu query, DESCRIPTION is a short
description of the query (this will show up in the UI), and KEY description of the query (this will show up in the UI), and KEY
is a shortcut key for the query." is a shortcut key for the query."
:type '(repeat (list (string :tag "Query") :type '(repeat (list (string :tag "Query")
(string :tag "Description") (string :tag "Description")
character)) character))
:group 'mu4e) :group 'mu4e)
(defvar mu4e-bookmarks (defvar mu4e-bookmarks
`( ,(make-mu4e-bookmark `( ,(make-mu4e-bookmark
:name "Unread messages" :name "Unread messages"
:query "flag:unread AND NOT flag:trashed" :query "flag:unread AND NOT flag:trashed"
:key ?u) :key ?u)
,(make-mu4e-bookmark ,(make-mu4e-bookmark
:name "Today's messages" :name "Today's messages"
:query "date:today..now" :query "date:today..now"
:key ?t) :key ?t)
,(make-mu4e-bookmark ,(make-mu4e-bookmark
:name "Last 7 days" :name "Last 7 days"
:query "date:7d..now" :query "date:7d..now"
:key ?w) :key ?w)
,(make-mu4e-bookmark ,(make-mu4e-bookmark
:name "Messages with images" :name "Messages with images"
:query "mime:image/*" :query "mime:image/*"
:key ?p)) :key ?p))
"A list of pre-defined queries. "A list of pre-defined queries.
Each query is represented by a mu4e-bookmark structure with Each query is represented by a mu4e-bookmark structure with
parameters @t{:name} with the name of the bookmark, @t{:query} parameters @t{:name} with the name of the bookmark, @t{:query}
@ -236,15 +236,15 @@ A symbol which is either:
* `horizontal': split horizontally (headers on top) * `horizontal': split horizontally (headers on top)
* `vertical': split vertically (headers on the left). * `vertical': split vertically (headers on the left).
* `single-window': view and headers in one window (mu4e will try not to * `single-window': view and headers in one window (mu4e will try not to
touch your window layout), main view in minibuffer touch your window layout), main view in minibuffer
* anything else: don't split (show either headers or messages, * anything else: don't split (show either headers or messages,
not both) not both)
Also see `mu4e-headers-visible-lines' Also see `mu4e-headers-visible-lines'
and `mu4e-headers-visible-columns'." and `mu4e-headers-visible-columns'."
:type '(choice (const :tag "Split horizontally" horizontal) :type '(choice (const :tag "Split horizontally" horizontal)
(const :tag "Split vertically" vertical) (const :tag "Split vertically" vertical)
(const :tag "Single window" single-window) (const :tag "Single window" single-window)
(const :tag "Don't split" nil)) (const :tag "Don't split" nil))
:group 'mu4e-headers) :group 'mu4e-headers)
(defcustom mu4e-view-max-specpdl-size 4096 (defcustom mu4e-view-max-specpdl-size 4096
@ -301,12 +301,12 @@ contexts match, we have the following choices:
Also see `mu4e-compose-context-policy'." Also see `mu4e-compose-context-policy'."
:type '(choice :type '(choice
(const :tag "Always ask what context to use, even if one matches" (const :tag "Always ask what context to use, even if one matches"
always-ask) always-ask)
(const :tag "Ask if none of the contexts match" ask) (const :tag "Ask if none of the contexts match" ask)
(const :tag "Ask when there's no context yet" ask-if-none) (const :tag "Ask when there's no context yet" ask-if-none)
(const :tag "Pick the first context if none match" pick-first) (const :tag "Pick the first context if none match" pick-first)
(const :tag "Don't change the context when none match" nil)) (const :tag "Don't change the context when none match" nil))
:group 'mu4e) :group 'mu4e)
;; crypto ;; crypto
@ -326,8 +326,8 @@ The setting is a symbol:
* `ask': ask before decrypting anything * `ask': ask before decrypting anything
* nil: don't try to decrypt anything." * nil: don't try to decrypt anything."
:type '(choice (const :tag "Try to decrypt automatically" t) :type '(choice (const :tag "Try to decrypt automatically" t)
(const :tag "Ask before decrypting anything" ask) (const :tag "Ask before decrypting anything" ask)
(const :tag "Don't try to decrypt anything" nil)) (const :tag "Don't try to decrypt anything" nil))
:group 'mu4e-crypto) :group 'mu4e-crypto)
;; completion; we put them here rather than in mu4e-compose, as mu4e-utils needs ;; completion; we put them here rather than in mu4e-compose, as mu4e-utils needs
@ -353,7 +353,7 @@ addresses)."
:type 'boolean :type 'boolean
:group 'mu4e-compose) :group 'mu4e-compose)
(defcustom mu4e-compose-complete-only-after "2010-01-01" (defcustom mu4e-compose-complete-only-after "2014-01-01"
"Consider only contacts last seen after this date. "Consider only contacts last seen after this date.
Date must be a string, in a format parseable by Date must be a string, in a format parseable by
`org-parse-time-string'. This excludes really old contacts. `org-parse-time-string'. This excludes really old contacts.
@ -370,42 +370,39 @@ Set to nil to not have any time-based restriction."
It is used as the identity function for converting contacts to It is used as the identity function for converting contacts to
their canonical counterpart; useful as an example." their canonical counterpart; useful as an example."
(let ((name (plist-get contact :name)) (let ((name (plist-get contact :name))
(mail (plist-get contact :mail))) (mail (plist-get contact :mail)))
(list :name name :mail mail))) (list :name name :mail mail)))
(defcustom mu4e-contact-rewrite-function nil (make-obsolete-variable 'mu4e-contacts-rewrite-function
"Function for rewriting or removing contacts. "mu4e-contact-process-function (see docstring)" "mu4e 1.3.2")
(make-obsolete-variable 'mu4e-compose-complete-ignore-address-regexp
"mu4e-contact-process-function (see docstring)" "mu4e 1.3.2")
If the function receives the contact as a list of the form (defcustom mu4e-contact-process-function nil
(:name NAME :mail EMAIL ... other properties ... ) "Function for processing contact information for use in auto-completion.
(other properties may be there as well)
The function receives the contact as a string, e.g
\"Foo Bar <foo.bar@example.com>\"
\"cuux@example.com\"
The function should return either: The function should return either:
- nil: remove this contact, or - nil: do not use this contact for completion
- the rewritten cell, or - the (possibly rewritten) address, which must be
- the existing cell as-is an RFC-2822-compatible e-mail address."
For rewriting, it is recommended to use `plist-put' to set the
changed parameters, so the other properties stay in place. Those
are needed for sorting the contacts."
:type 'function :type 'function
:group 'mu4e-compose) :group 'mu4e-compose)
(defcustom mu4e-compose-complete-ignore-address-regexp "no-?reply" (defcustom mu4e-compose-reply-ignore-address
"Ignore any e-mail addresses for completion if they match this regexp." message-dont-reply-to-names
:type 'string
:group 'mu4e-compose)
(defcustom mu4e-compose-reply-ignore-address message-dont-reply-to-names
"Addresses to prune when doing wide replies. "Addresses to prune when doing wide replies.
This can be a regexp matching the address, a list of regexps This can be a regexp matching the address, a list of regexps or a
or a predicate function. A value of nil keeps all the addresses." predicate function. A value of nil keeps all the addresses."
:type '(choice :type '(choice
(const nil) (const nil)
function function
string string
(repeat string)) (repeat string))
:group 'mu4e-compose) :group 'mu4e-compose)
(defcustom mu4e-compose-reply-to-address nil (defcustom mu4e-compose-reply-to-address nil
@ -448,8 +445,8 @@ parameter refers to the original message being replied to / being
forwarded / re-edited and is nil otherwise. `mu4e-drafts-folder' forwarded / re-edited and is nil otherwise. `mu4e-drafts-folder'
is only evaluated once." is only evaluated once."
:type '(choice :type '(choice
(string :tag "Folder name") (string :tag "Folder name")
(function :tag "Function return folder name")) (function :tag "Function return folder name"))
:group 'mu4e-folders) :group 'mu4e-folders)
(defcustom mu4e-refile-folder "/archive" (defcustom mu4e-refile-folder "/archive"
@ -459,8 +456,8 @@ function that takes a message (a msg plist, see
`mu4e-message-field'), and returns a folder. Note that the `mu4e-message-field'), and returns a folder. Note that the
message parameter refers to the message-at-point." message parameter refers to the message-at-point."
:type '(choice :type '(choice
(string :tag "Folder name") (string :tag "Folder name")
(function :tag "Function return folder name")) (function :tag "Function return folder name"))
:group 'mu4e-folders) :group 'mu4e-folders)
(defcustom mu4e-sent-folder "/sent" (defcustom mu4e-sent-folder "/sent"
@ -471,8 +468,8 @@ function that takes a message (a msg plist, see
message parameter refers to the original message being replied to message parameter refers to the original message being replied to
/ being forwarded / re-edited, and is nil otherwise." / being forwarded / re-edited, and is nil otherwise."
:type '(choice :type '(choice
(string :tag "Folder name") (string :tag "Folder name")
(function :tag "Function return folder name")) (function :tag "Function return folder name"))
:group 'mu4e-folders) :group 'mu4e-folders)
(defcustom mu4e-trash-folder "/trash" (defcustom mu4e-trash-folder "/trash"
@ -487,8 +484,8 @@ message-at-point. When using it when composing a message (see
message being replied to / being forwarded / re-edited, and is message being replied to / being forwarded / re-edited, and is
nil otherwise." nil otherwise."
:type '(choice :type '(choice
(string :tag "Folder name") (string :tag "Folder name")
(function :tag "Function return folder name")) (function :tag "Function return folder name"))
:group 'mu4e-folders) :group 'mu4e-folders)
(defcustom mu4e-maildir-shortcuts nil (defcustom mu4e-maildir-shortcuts nil
@ -714,113 +711,113 @@ mu4e-compose-mode."
(defconst mu4e-header-info (defconst mu4e-header-info
'( (:attachments . '( (:attachments .
( :name "Attachments" ( :name "Attachments"
:shortname "Atts" :shortname "Atts"
:help "Message attachments" :help "Message attachments"
:require-full t :require-full t
:sortable nil)) :sortable nil))
(:bcc . (:bcc .
( :name "Bcc" ( :name "Bcc"
:shortname "Bcc" :shortname "Bcc"
:help "Blind Carbon-Copy recipients for the message" :help "Blind Carbon-Copy recipients for the message"
:sortable t)) :sortable t))
(:cc . (:cc .
( :name "Cc" ( :name "Cc"
:shortname "Cc" :shortname "Cc"
:help "Carbon-Copy recipients for the message" :help "Carbon-Copy recipients for the message"
:sortable t)) :sortable t))
(:date . (:date .
( :name "Date" ( :name "Date"
:shortname "Date" :shortname "Date"
:help "Date/time when the message was written" :help "Date/time when the message was written"
:sortable t)) :sortable t))
(:human-date . (:human-date .
( :name "Date" ( :name "Date"
:shortname "Date" :shortname "Date"
:help "Date/time when the message was written." :help "Date/time when the message was written."
:sortable :date)) :sortable :date))
(:flags . (:flags .
( :name "Flags" ( :name "Flags"
:shortname "Flgs" :shortname "Flgs"
:help "Flags for the message" :help "Flags for the message"
:sortable nil)) :sortable nil))
(:from . (:from .
( :name "From" ( :name "From"
:shortname "From" :shortname "From"
:help "The sender of the message" :help "The sender of the message"
:sortable t)) :sortable t))
(:from-or-to . (:from-or-to .
( :name "From/To" ( :name "From/To"
:shortname "From/To" :shortname "From/To"
:help "Sender of the message if it's not me; otherwise the recipient" :help "Sender of the message if it's not me; otherwise the recipient"
:sortable nil)) :sortable nil))
(:maildir . (:maildir .
( :name "Maildir" ( :name "Maildir"
:shortname "Maildir" :shortname "Maildir"
:help "Maildir for this message" :help "Maildir for this message"
:sortable t)) :sortable t))
(:list . (:list .
( :name "List-Id" ( :name "List-Id"
:shortname "List" :shortname "List"
:help "Mailing list id for this message" :help "Mailing list id for this message"
:sortable t)) :sortable t))
(:mailing-list . (:mailing-list .
( :name "List" ( :name "List"
:shortname "List" :shortname "List"
:help "Mailing list friendly name for this message" :help "Mailing list friendly name for this message"
:sortable :list)) :sortable :list))
(:message-id . (:message-id .
( :name "Message-Id" ( :name "Message-Id"
:shortname "MsgID" :shortname "MsgID"
:help "Message-Id for this message" :help "Message-Id for this message"
:sortable nil)) :sortable nil))
(:path . (:path .
( :name "Path" ( :name "Path"
:shortname "Path" :shortname "Path"
:help "Full filesystem path to the message" :help "Full filesystem path to the message"
:sortable t)) :sortable t))
(:signature . (:signature .
( :name "Signature" ( :name "Signature"
:shortname "Sgn" :shortname "Sgn"
:help "Check for the cryptographic signature" :help "Check for the cryptographic signature"
:require-full t :require-full t
:sortable nil)) :sortable nil))
(:decryption . (:decryption .
( :name "Decryption" ( :name "Decryption"
:shortname "Dec" :shortname "Dec"
:help "Check the cryptographic decryption status" :help "Check the cryptographic decryption status"
:require-full t :require-full t
:sortable nil)) :sortable nil))
(:size . (:size .
( :name "Size" ( :name "Size"
:shortname "Size" :shortname "Size"
:help "Size of the message" :help "Size of the message"
:sortable t)) :sortable t))
(:subject . (:subject .
( :name "Subject" ( :name "Subject"
:shortname "Subject" :shortname "Subject"
:help "Subject of the message" :help "Subject of the message"
:sortable t)) :sortable t))
(:tags . (:tags .
( :name "Tags" ( :name "Tags"
:shortname "Tags" :shortname "Tags"
:help "Tags for the message" :help "Tags for the message"
:sortable nil)) :sortable nil))
(:thread-subject . (:thread-subject .
( :name "Subject" ( :name "Subject"
:shortname "Subject" :shortname "Subject"
:help "Subject of the thread" :help "Subject of the thread"
:sortable :subject)) :sortable :subject))
(:to . (:to .
( :name "To" ( :name "To"
:shortname "To" :shortname "To"
:help "Recipient of the message" :help "Recipient of the message"
:sortable t)) :sortable t))
(:user-agent . (:user-agent .
( :name "User-Agent" ( :name "User-Agent"
:shortname "UA" :shortname "UA"
:help "Program used for writing this message" :help "Program used for writing this message"
:require-full t :require-full t
:sortable t))) :sortable t)))
"An alist of all possible header fields and information about them. "An alist of all possible header fields and information about them.
This is used in the user-interface (the column headers in the header list, and This is used in the user-interface (the column headers in the header list, and
the fields the message view). the fields the message view).
@ -845,13 +842,13 @@ Note, `:sortable' is not supported for custom header fields.")
(defvar mu4e-header-info-custom (defvar mu4e-header-info-custom
'( (:recipnum . '( (:recipnum .
( :name "Number of recipients" ( :name "Number of recipients"
:shortname "Recip#" :shortname "Recip#"
:help "Number of recipients for this message" :help "Number of recipients for this message"
:function :function
(lambda (msg) (lambda (msg)
(format "%d" (format "%d"
(+ (length (mu4e-message-field msg :to)) (+ (length (mu4e-message-field msg :to))
(length (mu4e-message-field msg :cc)))))))) (length (mu4e-message-field msg :cc))))))))
"A list of custom (user-defined) headers. "A list of custom (user-defined) headers.
The format is similar The format is similar
to `mu4e-header-info', but adds a :function property, which to `mu4e-header-info', but adds a :function property, which