mirror of https://github.com/djcb/mu.git
* mu4e: basic support for auto-completing contacts when composing messages
This commit is contained in:
parent
635e049ff7
commit
0aeb9f71c8
|
@ -71,6 +71,19 @@ sent folder."
|
||||||
replying to messages."
|
replying to messages."
|
||||||
:type 'boolean
|
:type 'boolean
|
||||||
:group 'mu4e-compose)
|
:group 'mu4e-compose)
|
||||||
|
|
||||||
|
|
||||||
|
(defcustom mu4e-compose-completion-styles '(substring)
|
||||||
|
"How to do matching for contacts-completion; see
|
||||||
|
`completion-styles'."
|
||||||
|
:type 'list
|
||||||
|
:group 'mu4e-compose)
|
||||||
|
|
||||||
|
(defcustom mu4e-compose-cycle-threshold 5
|
||||||
|
"Number of completion matches below which you can cycle through
|
||||||
|
them; see `completion-cycle-threshold'."
|
||||||
|
:type 'list
|
||||||
|
:group 'mu4e-compose)
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
|
||||||
|
@ -110,7 +123,7 @@ such all its settings apply."
|
||||||
(message-yank-original)
|
(message-yank-original)
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(push-mark (point-max))
|
(push-mark (point-max))
|
||||||
(funcall message-cite-function)
|
(funcall message-cite-function)
|
||||||
(pop-mark)
|
(pop-mark)
|
||||||
(buffer-string))))
|
(buffer-string))))
|
||||||
|
|
||||||
|
@ -444,7 +457,6 @@ needed, set the Fcc header, and register the handler function."
|
||||||
(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)
|
||||||
(make-local-variable 'message-default-charset)
|
(make-local-variable 'message-default-charset)
|
||||||
|
|
||||||
;; if the default charset is not set, use UTF-8
|
;; if the default charset is not set, use UTF-8
|
||||||
(unless message-default-charset
|
(unless message-default-charset
|
||||||
(setq message-default-charset 'utf-8))
|
(setq message-default-charset 'utf-8))
|
||||||
|
@ -457,6 +469,15 @@ needed, set the Fcc header, and register the handler function."
|
||||||
;; mail files lives in...
|
;; mail files lives in...
|
||||||
(setq default-directory (expand-file-name "~/"))
|
(setq default-directory (expand-file-name "~/"))
|
||||||
|
|
||||||
|
;; offer completion for e-mail addresses
|
||||||
|
(when mu4e-compose-complete-addresses
|
||||||
|
(make-local-variable 'completion-styles)
|
||||||
|
(make-local-variable 'completion-cycle-threshold)
|
||||||
|
(setq
|
||||||
|
completion-cycle-threshold mu4e-compose-cycle-threshold
|
||||||
|
completion-styles mu4e-compose-completion-styles)
|
||||||
|
(add-to-list 'completion-at-point-functions 'mu4e~compose-complete-contact))
|
||||||
|
|
||||||
;; setup the fcc-stuff, if needed
|
;; setup the fcc-stuff, if needed
|
||||||
(add-hook 'message-send-hook
|
(add-hook 'message-send-hook
|
||||||
(lambda ()
|
(lambda ()
|
||||||
|
@ -552,7 +573,7 @@ Gnus' `message-mode'."
|
||||||
|
|
||||||
;; hide some headers
|
;; hide some headers
|
||||||
(let ((message-hidden-headers mu4e~compose-hidden-headers))
|
(let ((message-hidden-headers mu4e~compose-hidden-headers))
|
||||||
(message-hide-headers))
|
(message-hide-headers))
|
||||||
|
|
||||||
;; set compose mode -- so now hooks can run
|
;; set compose mode -- so now hooks can run
|
||||||
(mu4e-compose-mode)
|
(mu4e-compose-mode)
|
||||||
|
@ -560,7 +581,7 @@ Gnus' `message-mode'."
|
||||||
;; buffer is not user-modified yet
|
;; buffer is not user-modified yet
|
||||||
(mu4e~compose-set-friendly-buffer-name compose-type)
|
(mu4e~compose-set-friendly-buffer-name compose-type)
|
||||||
(set-buffer-modified-p nil)
|
(set-buffer-modified-p nil)
|
||||||
|
|
||||||
;; now jump to some use positions, and start writing that mail!
|
;; now jump to some use positions, and start writing that mail!
|
||||||
(if (member compose-type '(new forward))
|
(if (member compose-type '(new forward))
|
||||||
(message-goto-to)
|
(message-goto-to)
|
||||||
|
@ -674,6 +695,36 @@ message."
|
||||||
(mu4e-compose 'new))
|
(mu4e-compose 'new))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; address completion; inspired by org-contacts.el
|
||||||
|
(defun mu4e~compose-complete-contact (&optional start)
|
||||||
|
"Complete the text at START with a contact (ie. either 'name
|
||||||
|
<email>' or 'email')."
|
||||||
|
(interactive)
|
||||||
|
(let ((mail-abbrev-mode-regexp
|
||||||
|
(concat
|
||||||
|
"^\\(Resent-To\\|To\\|B?Cc\\|Reply-To\\|From"
|
||||||
|
"\\|Mail-Followup-To\\|Mail-Copies-To"
|
||||||
|
"\\|Disposition-Notification-To\\|Return-Receipt-To\\):"))
|
||||||
|
(eoh ;; end-of-headers
|
||||||
|
(save-excursion
|
||||||
|
(goto-char (point-min))
|
||||||
|
(search-forward-regexp mail-header-separator nil t))))
|
||||||
|
;; try to complete only when we're in the headers area,
|
||||||
|
;; looking at an address field.
|
||||||
|
(when (and (> eoh (point)) (mail-abbrev-in-expansion-header-p))
|
||||||
|
(let* ((end (point))
|
||||||
|
(start
|
||||||
|
(or start
|
||||||
|
(save-excursion
|
||||||
|
(re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*")
|
||||||
|
(goto-char (match-end 0))
|
||||||
|
(point))))
|
||||||
|
(orig (buffer-substring-no-properties start end))
|
||||||
|
(completion-ignore-case t))
|
||||||
|
(list start end mu4e~contacts-for-completion)))))
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; mu4e-compose-func and mu4e-send-func are wrappers so we can set ourselves
|
;; mu4e-compose-func and mu4e-send-func are wrappers so we can set ourselves
|
||||||
|
|
|
@ -213,11 +213,16 @@ The server output is as follows:
|
||||||
(plist-get sexp :docid)
|
(plist-get sexp :docid)
|
||||||
(plist-get sexp :path)))
|
(plist-get sexp :path)))
|
||||||
|
|
||||||
;; receive a pong message
|
;; received a pong message
|
||||||
((plist-get sexp :pong)
|
((plist-get sexp :pong)
|
||||||
(funcall mu4e-pong-func
|
(funcall mu4e-pong-func
|
||||||
(plist-get sexp :version) (plist-get sexp :doccount)))
|
(plist-get sexp :version) (plist-get sexp :doccount)))
|
||||||
|
|
||||||
|
;; received a contacts message
|
||||||
|
((plist-get sexp :contacts)
|
||||||
|
(funcall mu4e-contacts-func
|
||||||
|
(plist-get sexp :contacts)))
|
||||||
|
|
||||||
;; something got moved/flags changed
|
;; something got moved/flags changed
|
||||||
((plist-get sexp :update)
|
((plist-get sexp :update)
|
||||||
(funcall mu4e-update-func
|
(funcall mu4e-update-func
|
||||||
|
@ -433,6 +438,17 @@ mean:
|
||||||
response."
|
response."
|
||||||
(mu4e~proc-send-command "ping"))
|
(mu4e~proc-send-command "ping"))
|
||||||
|
|
||||||
|
(defun mu4e~proc-contacts (only-personal newer-than)
|
||||||
|
"Sends the contacts command to the mu server, expecting
|
||||||
|
a (:contacts (<list>)) in response. If ONLY-PERSONAL is non-nil,
|
||||||
|
only get personal contacts, if newer-than is non-nil, get only
|
||||||
|
contacts seen after NEWER-THAN (the time_t value)."
|
||||||
|
(mu4e~proc-send-command
|
||||||
|
"contacts only-personal:%s newer-than:%d"
|
||||||
|
(if only-personal "true" "false")
|
||||||
|
(if newer-than newer-than 0)))
|
||||||
|
|
||||||
|
|
||||||
(defun mu4e~proc-view (docid-or-msgid &optional images)
|
(defun mu4e~proc-view (docid-or-msgid &optional images)
|
||||||
"Get one particular message based on its DOCID-OR-MSGID (keyword
|
"Get one particular message based on its DOCID-OR-MSGID (keyword
|
||||||
argument). Optionally, if IMAGES is non-nil, backend will any
|
argument). Optionally, if IMAGES is non-nil, backend will any
|
||||||
|
|
|
@ -43,7 +43,6 @@ recommended you use \"html2text -utf8 -width 72\"."
|
||||||
:group 'mu4e-view
|
:group 'mu4e-view
|
||||||
:safe 'stringp)
|
:safe 'stringp)
|
||||||
|
|
||||||
|
|
||||||
(defcustom mu4e-view-prefer-html nil
|
(defcustom mu4e-view-prefer-html nil
|
||||||
"Whether to base the body display on the HTML-version of the
|
"Whether to base the body display on the HTML-version of the
|
||||||
e-mail message (if there is any."
|
e-mail message (if there is any."
|
||||||
|
@ -542,6 +541,27 @@ split-window."
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; start and stopping
|
;; start and stopping
|
||||||
|
(defun mu4e~fill-contacts (contacts)
|
||||||
|
"We receive a list of contacts, which each contact of the form
|
||||||
|
(:name NAME :mail EMAIL)
|
||||||
|
and fill the list `mu4e~contacts-for-completion' with it, with
|
||||||
|
each element looking like
|
||||||
|
name <email>
|
||||||
|
This is used by the completion function in mu4e-compose."
|
||||||
|
(let ((lst))
|
||||||
|
(dolist (contact contacts)
|
||||||
|
(let ((name (plist-get contact :name))
|
||||||
|
(mail (plist-get contact :mail)))
|
||||||
|
;;(message "N:%S M:%S" name mail)
|
||||||
|
(when mail
|
||||||
|
(add-to-list 'lst
|
||||||
|
(if name
|
||||||
|
(format "%s <%s>" name mail)
|
||||||
|
mail)))))
|
||||||
|
(setq mu4e~contacts-for-completion lst)
|
||||||
|
(mu4e-message "Contacts received: %d"
|
||||||
|
(length mu4e~contacts-for-completion))))
|
||||||
|
|
||||||
|
|
||||||
(defun mu4e~check-requirements ()
|
(defun mu4e~check-requirements ()
|
||||||
"Check for the settings required for running mu4e."
|
"Check for the settings required for running mu4e."
|
||||||
|
@ -583,6 +603,7 @@ FUNC (if non-nil) afterwards."
|
||||||
;; better to check for specific features
|
;; better to check for specific features
|
||||||
(unless (>= emacs-major-version 23)
|
(unless (>= emacs-major-version 23)
|
||||||
(error "Emacs >= 23.x is required for mu4e"))
|
(error "Emacs >= 23.x is required for mu4e"))
|
||||||
|
|
||||||
;; set up the 'pong' handler func
|
;; set up the 'pong' handler func
|
||||||
(lexical-let ((func func))
|
(lexical-let ((func func))
|
||||||
(setq mu4e-pong-func
|
(setq mu4e-pong-func
|
||||||
|
@ -597,8 +618,17 @@ FUNC (if non-nil) afterwards."
|
||||||
0 mu4e-update-interval 'mu4e-update-mail)))
|
0 mu4e-update-interval 'mu4e-update-mail)))
|
||||||
(mu4e-message "Started mu4e with %d message%s in store"
|
(mu4e-message "Started mu4e with %d message%s in store"
|
||||||
doccount (if (= doccount 1) "" "s")))))
|
doccount (if (= doccount 1) "" "s")))))
|
||||||
;; send the ping
|
;; send the ping
|
||||||
(mu4e~proc-ping)))
|
(mu4e~proc-ping)
|
||||||
|
|
||||||
|
;; get the address list
|
||||||
|
(when mu4e-compose-complete-addresses
|
||||||
|
(setq mu4e-contacts-func 'mu4e~fill-contacts)
|
||||||
|
(mu4e~proc-contacts
|
||||||
|
mu4e-compose-complete-only-newer-than
|
||||||
|
;; calculate time_t value -- now minus so-many days
|
||||||
|
(floor (- (float-time (current-time))
|
||||||
|
(* 3600 24 mu4e-compose-complete-only-newer-than)))))))
|
||||||
|
|
||||||
(defun mu4e~stop ()
|
(defun mu4e~stop ()
|
||||||
"Stop the mu4e session."
|
"Stop the mu4e session."
|
||||||
|
@ -779,7 +809,7 @@ is ignored."
|
||||||
(newline)
|
(newline)
|
||||||
(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
|
||||||
|
@ -790,7 +820,7 @@ displaying it). Do _not_ bury the current buffer, though."
|
||||||
(lambda (win)
|
(lambda (win)
|
||||||
(with-current-buffer (window-buffer win)
|
(with-current-buffer (window-buffer win)
|
||||||
(unless (eq curbuf (current-buffer))
|
(unless (eq curbuf (current-buffer))
|
||||||
(when (member major-mode '(mu4e-headers-mode mu4e-view-mode mu4e-main-mode))
|
(when (member major-mode '(mu4e-headers-mode mu4e-view-mode))
|
||||||
(unless (one-window-p t)
|
(unless (one-window-p t)
|
||||||
(delete-window win)))))) nil t)))
|
(delete-window win)))))) nil t)))
|
||||||
|
|
||||||
|
|
|
@ -112,6 +112,39 @@ else: don't split (show either headers or messages, not both) Also
|
||||||
see `mu4e-headers-visible-lines' and
|
see `mu4e-headers-visible-lines' and
|
||||||
`mu4e-headers-visible-columns'.")
|
`mu4e-headers-visible-columns'.")
|
||||||
|
|
||||||
|
;; completion; we put them here rather than in mu4e-compose, as mu4e-utils needs
|
||||||
|
;; the variables.
|
||||||
|
|
||||||
|
(defgroup mu4e-compose nil
|
||||||
|
"Message-composition related settings."
|
||||||
|
:group 'mu4e)
|
||||||
|
|
||||||
|
;; address completion
|
||||||
|
(defcustom mu4e-compose-complete-addresses t
|
||||||
|
"Whether to do auto-completion of e-mail addresses."
|
||||||
|
:type 'boolean
|
||||||
|
:group 'mu4e-compose)
|
||||||
|
|
||||||
|
(defcustom mu4e-compose-complete-only-personal t
|
||||||
|
"Whether to consider only 'personal' e-mail addresses,
|
||||||
|
i.e. addresses from messages where user was explicitly in one of
|
||||||
|
the address fields (this excludes mailing list messages)."
|
||||||
|
:type 'boolean
|
||||||
|
:group 'mu4e-compose)
|
||||||
|
|
||||||
|
(defcustom mu4e-compose-complete-only-newer-than 500
|
||||||
|
"Consider only contacts last seen less than so many *days*. This
|
||||||
|
excludes really old contacts. Set to nil to not have any time-based
|
||||||
|
restriction."
|
||||||
|
:type 'integer
|
||||||
|
:group 'mu4e-compose)
|
||||||
|
|
||||||
|
(defcustom mu4e-compose-my-email-addresses `(,user-mail-address)
|
||||||
|
"List of e-mail addresses to consider 'my email addresses',
|
||||||
|
ie. addresses whose presence in an email imply that it is a
|
||||||
|
personal message."
|
||||||
|
:type '(string)
|
||||||
|
:group 'mu4e-compose)
|
||||||
|
|
||||||
;; Folders
|
;; Folders
|
||||||
(defgroup mu4e-folders nil
|
(defgroup mu4e-folders nil
|
||||||
|
@ -368,6 +401,10 @@ view). Most fields should be self-explanatory. A special one is
|
||||||
|
|
||||||
(defvar mu4e~view-msg nil "The message being viewed in view mode.")
|
(defvar mu4e~view-msg nil "The message being viewed in view mode.")
|
||||||
|
|
||||||
|
(defvar mu4e~contacts-for-completion nil
|
||||||
|
"List of contacts (ie. 'name <e-mail>'),
|
||||||
|
used by the completion functions in mu4e-compose, and filled when
|
||||||
|
mu4e starts.")
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -433,6 +470,10 @@ the server process.")
|
||||||
"A function called for each (:pong type ....) sexp received from
|
"A function called for each (:pong type ....) sexp received from
|
||||||
the server process.")
|
the server process.")
|
||||||
|
|
||||||
|
(defvar mu4e-contacts-func 'mu4e~default-handler
|
||||||
|
"A function called for each (:contacts (<list-of-contacts>) sexp
|
||||||
|
received from the server process.")
|
||||||
|
|
||||||
(defvar mu4e-temp-func 'mu4e~default-handler
|
(defvar mu4e-temp-func 'mu4e~default-handler
|
||||||
"A function called for each (:temp <file> <cookie>) sexp received
|
"A function called for each (:temp <file> <cookie>) sexp received
|
||||||
from the server process.")
|
from the server process.")
|
||||||
|
|
Loading…
Reference in New Issue