Even better email contact completion in mu4e

I have written previously about my tweaks to improve contact completion when composing emails with mu4e. Thanks to some help from abo-abo, the author of the fantastic ivy completion library, with the code below you can hit a comma to complete the current choice of email address and start searching for the next one. This matches the behaviour of many other email clients like Gmail or Thunderbird.

This won’t change anybody’s world, but gives you a nice little thrill of efficiency when entering several recipients to an email!

Here is the updated code (see my previous post for more details):

;;need this for hash access
(require 'subr-x)

;;my favourite contacts - these will be put at front of list
(setq bjm/contact-file "/homeb/bjm/docs/fave-contacts.txt")

(defun bjm/read-contact-list ()
  "Return a list of email addresses"
  (with-temp-buffer
    (insert-file-contents bjm/contact-file)
    (split-string (buffer-string) "\n" t)))

;; code from https://github.com/abo-abo/swiper/issues/596
(defun bjm/counsel-email-action (contact)
  (with-ivy-window
    (insert contact)))

;; bind comma to launch new search
(defvar bjm/counsel-email-map
  (let ((map (make-sparse-keymap)))
    (define-key map "," 'bjm/counsel-email-more)
    map))

(defun bjm/counsel-email-more ()
  "Insert email address and prompt for another."
  (interactive)
  (ivy-call)
  (with-ivy-window
    (insert ", "))
  (delete-minibuffer-contents)
  (setq ivy-text ""))

;; ivy contacts
;; based on http://kitchingroup.cheme.cmu.edu/blog/2015/03/14/A-helm-mu4e-contact-selector/
(defun bjm/ivy-select-and-insert-contact (&optional start)
  (interactive)
  ;; make sure mu4e contacts list is updated - I was having
  ;; intermittent problems that this was empty but couldn't see why
  (mu4e~request-contacts)
  (let ((eoh ;; end-of-headers
         (save-excursion
           (goto-char (point-min))
           (search-forward-regexp mail-header-separator nil t)))
        ;; append full sorted contacts list to favourites and delete duplicates
        (contacts-list
         (delq nil (delete-dups (append (bjm/read-contact-list) (mu4e~sort-contacts-for-completion (hash-table-keys mu4e~contacts)))))))

    ;; only run if we are in the headers section
    (when (and eoh (> 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))))
           (initial-input (buffer-substring-no-properties start end)))

      (delete-region start end)

      (ivy-read "Contact: "
                contacts-list
                :re-builder #'ivy--regex
                :sort nil
                :initial-input initial-input
                :action 'bjm/counsel-email-action
                :keymap bjm/counsel-email-map)
      ))))
  • Andrej

    Very nice! Thank you for sharing! Would it be possible to slightly modify it so that an email address from a clickable malito-link in an org-file gets inserted automatically as the first adress?

    • I’m not sure how I’d do this. I suggest asking on the mu4e google group – they are very friendly on there!

  • Richard Garner

    Thanks, very helpful!

    Can I suggest replacing (kill-region start end) by (delete-region start end) in bjm/ivy-select-and-insert-contact? At the moment it pushes a (usually empty) item to the top of the kill ring, which is often confusing.