mirror of https://github.com/djcb/mu.git
mu4e: Contact completion - better sorting / display
Improve the contact-sorting algorithm, and make it better cooperate with completion-at-point functions. Also deal better with broken rewritten contacts, and document how rewriting should done (docstrings and reference doc).
This commit is contained in:
parent
25da1fdc7b
commit
144e2a8f1b
|
@ -1,7 +1,7 @@
|
||||||
;; -*-mode: emacs-lisp; tab-width: 8; indent-tabs-mode: t -*-
|
;; -*-mode: emacs-lisp; tab-width: 8; indent-tabs-mode: t -*-
|
||||||
;; mu4e-compose.el -- part of mu4e, the mu mail user agent for emacs
|
;; mu4e-compose.el -- part of mu4e, the mu mail user agent for emacs
|
||||||
;;
|
;;
|
||||||
;; Copyright (C) 2011-2012 Dirk-Jan C. Binnema
|
;; Copyright (C) 2011-2016 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>
|
||||||
|
@ -260,15 +260,15 @@ appear on disk."
|
||||||
(defun mu4e~compose-complete-handler (str pred action)
|
(defun mu4e~compose-complete-handler (str pred action)
|
||||||
(cond
|
(cond
|
||||||
((eq action nil)
|
((eq action nil)
|
||||||
(try-completion str mu4e~contacts-for-completion pred))
|
(try-completion str mu4e~contacts pred))
|
||||||
((eq action t)
|
((eq action t)
|
||||||
(all-completions str mu4e~contacts-for-completion pred))
|
(all-completions str mu4e~contacts pred))
|
||||||
((eq action 'metadata)
|
((eq action 'metadata)
|
||||||
;; our contacts are already sorted - just need to tell the
|
;; our contacts are already sorted - just need to tell the
|
||||||
;; completion machinery not to try to undo that...
|
;; completion machinery not to try to undo that...
|
||||||
'(metadata
|
'(metadata
|
||||||
(display-sort-function . identity) ;; i.e., alphabetically
|
(display-sort-function . mu4e~sort-contacts-for-completion)
|
||||||
(cycle-sort-function . identity)))))
|
(cycle-sort-function . mu4e~sort-contacts-for-completion)))))
|
||||||
|
|
||||||
(defun mu4e~compose-complete-contact (&optional start)
|
(defun mu4e~compose-complete-contact (&optional start)
|
||||||
"Complete the text at START with a contact.
|
"Complete the text at START with a contact.
|
||||||
|
@ -672,8 +672,6 @@ end of the buffer."
|
||||||
(define-key mu4e-compose-mode-map
|
(define-key mu4e-compose-mode-map
|
||||||
(vector 'remap 'end-of-buffer) 'mu4e-compose-goto-bottom)
|
(vector 'remap 'end-of-buffer) 'mu4e-compose-goto-bottom)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(provide 'mu4e-compose)
|
(provide 'mu4e-compose)
|
||||||
|
|
||||||
;; Load mu4e completely even when this file was loaded through
|
;; Load mu4e completely even when this file was loaded through
|
||||||
|
|
|
@ -1,6 +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-2014 Dirk-Jan C. Binnema
|
;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema
|
||||||
;; Copyright (C) 2013 Tibor Simko
|
;; Copyright (C) 2013 Tibor Simko
|
||||||
|
|
||||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||||
|
@ -347,7 +347,7 @@ maildirs under `mu4e-maildir'."
|
||||||
(mapconcat
|
(mapconcat
|
||||||
(lambda (item)
|
(lambda (item)
|
||||||
(concat
|
(concat
|
||||||
"["
|
q "["
|
||||||
(propertize (make-string 1 (cdr item))
|
(propertize (make-string 1 (cdr item))
|
||||||
'face 'mu4e-highlight-face)
|
'face 'mu4e-highlight-face)
|
||||||
"]"
|
"]"
|
||||||
|
@ -657,53 +657,66 @@ or (rfc822-string . CONTACT) otherwise."
|
||||||
(if name (format "%s <%s>" (mu4e~rfc822-quoteit name) mail) mail)
|
(if name (format "%s <%s>" (mu4e~rfc822-quoteit name) mail) mail)
|
||||||
contact)))))
|
contact)))))
|
||||||
|
|
||||||
(defsubst mu4e~sort-contacts (contacts)
|
(defun mu4e~sort-contacts (contacts)
|
||||||
"Destructively sort contacts (only for cycling). Sort by
|
"Destructively sort contacts (only for cycling) in order of
|
||||||
last-use when that is at most 10 days old. Otherwise, sort by
|
'mostly likely contact'.t See the code for the detail"
|
||||||
frequency."
|
|
||||||
(let* ((now (+ (float-time) 3600)) ;; allow for clock diffs
|
(let* ((now (+ (float-time) 3600)) ;; allow for clock diffs
|
||||||
(recent (- (float-time) (* 30 24 3600))))
|
(recent (- (float-time) (* 15 24 3600))))
|
||||||
(sort* contacts
|
(sort* contacts
|
||||||
(lambda (c1 c2)
|
(lambda (c1 c2)
|
||||||
(let* ( (c1 (cdr c1)) (c2 (cdr c2))
|
(let* ( (c1 (cdr c1)) (c2 (cdr c2))
|
||||||
(personal1 (plist-get c1 :personal))
|
(personal1 (plist-get c1 :personal))
|
||||||
(personal2 (plist-get c2 :personal))
|
(personal2 (plist-get c2 :personal))
|
||||||
(freq1 (plist-get c1 :freq))
|
;; note: freq, tstamp can only be missing if the rewrite
|
||||||
(freq2 (plist-get c2 :freq))
|
;; function removed them. If the rewrite function changed the
|
||||||
(tstamp1 (plist-get c1 :tstamp))
|
;; contact somehow, we guess it's important.
|
||||||
(tstamp2 (plist-get c2 :tstamp)))
|
(freq1 (or (plist-get c1 :freq) 500))
|
||||||
;; personal contacts come first
|
(freq2 (or (plist-get c2 :freq) 500))
|
||||||
(if (or personal1 personal2)
|
(tstamp1 (or (plist-get c1 :tstamp) now))
|
||||||
(if (not (and personal1 personal2))
|
(tstamp2 (or (plist-get c2 :tstamp) now)))
|
||||||
;; if only one is personal, that one comes first
|
;; only one is personal? if so, that one comes first
|
||||||
(if personal1 t nil)
|
(if (not (equal personal1 personal2))
|
||||||
;; then come recently seen ones; but only if they're not in
|
(if personal1 t nil)
|
||||||
;; the future (as seen in spams)
|
;; only one is recent? that one comes first
|
||||||
(if (and (<= tstamp1 now) (<= tstamp2 now)
|
(if (not (equal (> tstamp1 recent) (> tstamp2 recent)))
|
||||||
(or (> tstamp1 recent) (> tstamp2 recent)))
|
(> tstamp1 tstamp2)
|
||||||
(> tstamp1 tstamp2)
|
;; otherwise, use the frequency
|
||||||
;; otherwise, use the frequency
|
(> freq1 freq2))))))))
|
||||||
(> 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.'"
|
||||||
|
(sort* contacts
|
||||||
|
(lambda (c1 c2)
|
||||||
|
(let ((rank1 (gethash c1 mu4e~contacts))
|
||||||
|
(rank2 (gethash c2 mu4e~contacts)))
|
||||||
|
(< rank1 rank2)))))
|
||||||
|
|
||||||
;; start and stopping
|
;; start and stopping
|
||||||
(defun mu4e~fill-contacts (contacts)
|
(defun mu4e~fill-contacts (contact-data)
|
||||||
"We receive a list of contacts, which each contact of the form
|
"We receive a list of contacts, which each contact of the form
|
||||||
(:name NAME :mail EMAIL :tstamp TIMESTAMP :freq FREQUENCY)
|
(:me NAME :mail EMAIL :tstamp TIMESTAMP :freq FREQUENCY)
|
||||||
and fill the list `mu4e~contacts-for-completion' with it, with
|
and fill the hash `mu4e~contacts-for-completion' with it, with
|
||||||
each element looking like
|
each contact mapped to an integer for their ranking.
|
||||||
name <email>
|
|
||||||
This is used by the completion function in mu4e-compose."
|
|
||||||
(setq mu4e~contacts-for-completion nil)
|
|
||||||
(dolist (contact contacts)
|
|
||||||
(let ((contact (mu4e~process-contact contact)))
|
|
||||||
;; note, this gives cells (rfc822-address . contact)
|
|
||||||
(when contact (push contact mu4e~contacts-for-completion))))
|
|
||||||
(setq mu4e~contacts-for-completion
|
|
||||||
(mapcar 'car ;; strip off the other stuff again
|
|
||||||
(mu4e~sort-contacts mu4e~contacts-for-completion)))
|
|
||||||
(mu4e-index-message "Contacts received: %d"
|
|
||||||
(length mu4e~contacts-for-completion)))
|
|
||||||
|
|
||||||
|
This is used by the completion function in mu4e-compose."
|
||||||
|
(let ((contacts) (rank 0))
|
||||||
|
(dolist (contact contact-data)
|
||||||
|
(let ((contact-maybe (mu4e~process-contact contact)))
|
||||||
|
;; note, this gives cells (rfc822-address . contact)
|
||||||
|
(when contact-maybe (push contact-maybe contacts))))
|
||||||
|
(setq contacts (mu4e~sort-contacts contacts))
|
||||||
|
;; now, we have our nicely sorted list, map them to a list
|
||||||
|
;; of increasing integers. We use that map in the composer
|
||||||
|
;; 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)
|
||||||
|
(puthash (car contact) rank mu4e~contacts)
|
||||||
|
(incf rank))
|
||||||
|
(mu4e-index-message "Contacts received: %d"
|
||||||
|
(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."
|
||||||
|
@ -812,7 +825,7 @@ 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-for-completion nil))
|
mu4e~contacts nil))
|
||||||
|
|
||||||
(defun mu4e~stop ()
|
(defun mu4e~stop ()
|
||||||
"Stop the mu4e session."
|
"Stop the mu4e session."
|
||||||
|
@ -1194,6 +1207,5 @@ the view and compose modes."
|
||||||
(add-text-properties p (point-max) '(face mu4e-footer-face)))))))
|
(add-text-properties p (point-max) '(face mu4e-footer-face)))))))
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
|
||||||
(provide 'mu4e-utils)
|
(provide 'mu4e-utils)
|
||||||
;;; End of mu4e-utils.el
|
;;; End of mu4e-utils.el
|
||||||
|
|
|
@ -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-2015 Dirk-Jan C. Binnema
|
;; Copyright (C) 2011-2016 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>
|
||||||
|
@ -312,13 +312,17 @@ their canonical counterpart; useful as an example."
|
||||||
contacts and rewrite them or remove them altogether.
|
contacts and rewrite them or remove them altogether.
|
||||||
|
|
||||||
If the function receives the contact as a list of the form
|
If the function receives the contact as a list of the form
|
||||||
(:name NAME :mail EMAIL)
|
(:name NAME :mail EMAIL ... other properties ... )
|
||||||
(other properties may be there as well)
|
(other properties may be there as well)
|
||||||
|
|
||||||
The function should return either:
|
The function should return either:
|
||||||
- nil: remove this contact
|
- nil: remove this contact, or
|
||||||
- a possible rewritten cell (:name NAME :mail EMAIL), or simply return
|
- the rewritten cell, or
|
||||||
the functions parameter."
|
- the existing cell as-is
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
@ -769,10 +773,11 @@ for an example.")
|
||||||
(defvar mu4e~view-headers-buffer nil
|
(defvar mu4e~view-headers-buffer nil
|
||||||
"The headers buffer connected to this view.")
|
"The headers buffer connected to this view.")
|
||||||
|
|
||||||
(defvar mu4e~contacts-for-completion nil
|
(defvar mu4e~contacts nil
|
||||||
"List of contacts (ie. 'name <e-mail>').
|
"Hash of that maps contacts (ie. 'name <e-mail>') to an integer
|
||||||
This is used by the completion functions in mu4e-compose, filled
|
with their sort order. We need to keep this information around to
|
||||||
when mu4e starts.")
|
quickly re-sort subsets of the contacts in the completions function in
|
||||||
|
mu4e-compose.")
|
||||||
|
|
||||||
(defvar mu4e~server-props nil
|
(defvar mu4e~server-props nil
|
||||||
"Properties we receive from the mu4e server process.
|
"Properties we receive from the mu4e server process.
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
@c %**end of header
|
@c %**end of header
|
||||||
|
|
||||||
@copying
|
@copying
|
||||||
Copyright @copyright{} 2012-2015 Dirk-Jan C. Binnema
|
Copyright @copyright{} 2012-2016 Dirk-Jan C. Binnema
|
||||||
|
|
||||||
@quotation
|
@quotation
|
||||||
Permission is granted to copy, distribute and/or modify this document
|
Permission is granted to copy, distribute and/or modify this document
|
||||||
|
@ -2885,7 +2885,11 @@ return either the possibly rewritten contact or @code{nil} to remove the
|
||||||
contact from the list - note that the latter can also be achieved using
|
contact from the list - note that the latter can also be achieved using
|
||||||
@code{mu4e-compose-complete-ignore-address-regexp}.
|
@code{mu4e-compose-complete-ignore-address-regexp}.
|
||||||
|
|
||||||
Let's look at an example.
|
Each of the contacts are property-lists ('plists'), with properties
|
||||||
|
@code{:name} (which may be @code{nil}), and @code{:mail}, and a number
|
||||||
|
of other properties which you should return unchanged.
|
||||||
|
|
||||||
|
Let's look at an example:
|
||||||
|
|
||||||
@lisp
|
@lisp
|
||||||
(defun my-rewrite-function (contact)
|
(defun my-rewrite-function (contact)
|
||||||
|
@ -2893,7 +2897,9 @@ Let's look at an example.
|
||||||
(mail (plist-get contact :mail)))
|
(mail (plist-get contact :mail)))
|
||||||
(cond
|
(cond
|
||||||
;; jonh smiht --> John Smith
|
;; jonh smiht --> John Smith
|
||||||
((string= "jonh smiht" name) (list :name "John Smith" :mail mail))
|
((string= "jonh smiht" name)
|
||||||
|
(plist-put contact :name "John C. Smith")
|
||||||
|
contact)
|
||||||
;; remove evilspammer from the contacts list
|
;; remove evilspammer from the contacts list
|
||||||
((string= "evilspammer@@example.com" mail) nil)
|
((string= "evilspammer@@example.com" mail) nil)
|
||||||
;; others stay as the are
|
;; others stay as the are
|
||||||
|
|
Loading…
Reference in New Issue