diff --git a/emacs/mu4e-compose.el b/emacs/mu4e-compose.el index ef5a6fc3..bbfc6900 100644 --- a/emacs/mu4e-compose.el +++ b/emacs/mu4e-compose.el @@ -71,6 +71,19 @@ sent folder." replying to messages." :type 'boolean :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) (goto-char (point-min)) (push-mark (point-max)) - (funcall message-cite-function) + (funcall message-cite-function) (pop-mark) (buffer-string)))) @@ -444,7 +457,6 @@ needed, set the Fcc header, and register the handler function." (let ((message-hidden-headers mu4e~compose-hidden-headers)) (use-local-map mu4e-compose-mode-map) (make-local-variable 'message-default-charset) - ;; if the default charset is not set, use UTF-8 (unless message-default-charset (setq message-default-charset 'utf-8)) @@ -457,6 +469,15 @@ needed, set the Fcc header, and register the handler function." ;; mail files lives in... (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 (add-hook 'message-send-hook (lambda () @@ -552,7 +573,7 @@ Gnus' `message-mode'." ;; hide some headers (let ((message-hidden-headers mu4e~compose-hidden-headers)) - (message-hide-headers)) + (message-hide-headers)) ;; set compose mode -- so now hooks can run (mu4e-compose-mode) @@ -560,7 +581,7 @@ Gnus' `message-mode'." ;; buffer is not user-modified yet (mu4e~compose-set-friendly-buffer-name compose-type) (set-buffer-modified-p nil) - + ;; now jump to some use positions, and start writing that mail! (if (member compose-type '(new forward)) (message-goto-to) @@ -674,6 +695,36 @@ message." (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 +' 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 diff --git a/emacs/mu4e-proc.el b/emacs/mu4e-proc.el index a4177dbe..b62be5cb 100644 --- a/emacs/mu4e-proc.el +++ b/emacs/mu4e-proc.el @@ -213,11 +213,16 @@ The server output is as follows: (plist-get sexp :docid) (plist-get sexp :path))) - ;; receive a pong message + ;; received a pong message ((plist-get sexp :pong) (funcall mu4e-pong-func (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 ((plist-get sexp :update) (funcall mu4e-update-func @@ -433,6 +438,17 @@ mean: response." (mu4e~proc-send-command "ping")) +(defun mu4e~proc-contacts (only-personal newer-than) + "Sends the contacts command to the mu server, expecting +a (:contacts ()) 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) "Get one particular message based on its DOCID-OR-MSGID (keyword argument). Optionally, if IMAGES is non-nil, backend will any diff --git a/emacs/mu4e-utils.el b/emacs/mu4e-utils.el index fea1d660..a19e1d98 100644 --- a/emacs/mu4e-utils.el +++ b/emacs/mu4e-utils.el @@ -43,7 +43,6 @@ recommended you use \"html2text -utf8 -width 72\"." :group 'mu4e-view :safe 'stringp) - (defcustom mu4e-view-prefer-html nil "Whether to base the body display on the HTML-version of the e-mail message (if there is any." @@ -542,6 +541,27 @@ split-window." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 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 +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 () "Check for the settings required for running mu4e." @@ -583,6 +603,7 @@ FUNC (if non-nil) afterwards." ;; better to check for specific features (unless (>= emacs-major-version 23) (error "Emacs >= 23.x is required for mu4e")) + ;; set up the 'pong' handler func (lexical-let ((func func)) (setq mu4e-pong-func @@ -597,8 +618,17 @@ FUNC (if non-nil) afterwards." 0 mu4e-update-interval 'mu4e-update-mail))) (mu4e-message "Started mu4e with %d message%s in store" doccount (if (= doccount 1) "" "s"))))) - ;; send the ping - (mu4e~proc-ping))) + ;; send the 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 () "Stop the mu4e session." @@ -779,7 +809,7 @@ is ignored." (newline) (insert-image img imgpath nil t)))) - + (defun mu4e-hide-other-mu4e-buffers () "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) (with-current-buffer (window-buffer win) (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) (delete-window win)))))) nil t))) diff --git a/emacs/mu4e-vars.el b/emacs/mu4e-vars.el index 431be04a..e01d53d0 100644 --- a/emacs/mu4e-vars.el +++ b/emacs/mu4e-vars.el @@ -112,6 +112,39 @@ else: don't split (show either headers or messages, not both) Also see `mu4e-headers-visible-lines' and `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 (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~contacts-for-completion nil + "List of contacts (ie. 'name '), +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 the server process.") +(defvar mu4e-contacts-func 'mu4e~default-handler + "A function called for each (:contacts () sexp +received from the server process.") + (defvar mu4e-temp-func 'mu4e~default-handler "A function called for each (:temp ) sexp received from the server process.")