X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=emacs%2Fnotmuch-address.el;h=0d56fba70e2f3c14d3f8bd305165b6307ca3d79b;hp=10eaab196267bbad86c967e1318ea6083a577b44;hb=ed40579ad3882e6f9bbe9b1ba5e707ab289ca203;hpb=827c28a04effc58f29e751788690d2f4b434902d diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index 10eaab19..0d56fba7 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -29,15 +29,23 @@ (declare-function company-manual-begin "company") (defvar notmuch-address-last-harvest 0 - "Time of last address harvest") + "Time of last address harvest.") (defvar notmuch-address-completions (make-hash-table :test 'equal) "Hash of email addresses for completion during email composition. - This variable is set by calling `notmuch-address-harvest'.") +This variable is set by calling `notmuch-address-harvest'.") (defvar notmuch-address-full-harvest-finished nil "t indicates that full completion address harvesting has been -finished") +finished. Use notmuch-address--harvest-ready to access as that +will load a saved hash if necessary (and available).") + +(defun notmuch-address--harvest-ready () + "Return t if there is a full address hash available. + +If the hash is not present it attempts to load a saved hash." + (or notmuch-address-full-harvest-finished + (notmuch-address--load-address-hash))) (defcustom notmuch-address-command 'internal "Determines how address completion candidates are generated. @@ -58,17 +66,18 @@ disabled." (const :tag "Disable address completion" nil) (string :tag "Use external completion command")) :group 'notmuch-send + :group 'notmuch-address :group 'notmuch-external) (defcustom notmuch-address-internal-completion '(sent nil) "Determines how internal address completion generates candidates. This should be a list of the form '(DIRECTION FILTER), where - DIRECTION is either sent or received and specifies whether the - candidates are searched in messages sent by the user or received - by the user (note received by is much faster), and FILTER is - either nil or a filter-string, such as \"date:1y..\" to append - to the query." +DIRECTION is either sent or received and specifies whether the +candidates are searched in messages sent by the user or received +by the user (note received by is much faster), and FILTER is +either nil or a filter-string, such as \"date:1y..\" to append +to the query." :type '(list :tag "Use internal address completion" (radio :tag "Base completion on messages you have" @@ -85,6 +94,19 @@ This should be a list of the form '(DIRECTION FILTER), where (setq notmuch-address-completions (clrhash notmuch-address-completions)) (setq notmuch-address-full-harvest-finished nil)) :group 'notmuch-send + :group 'notmuch-address + :group 'notmuch-external) + +(defcustom notmuch-address-save-filename nil + "Filename to save the cached completion addresses. + +All the addresses notmuch uses for address completion will be +cached in this file. This has obvious privacy implications so you +should make sure it is not somewhere publicly readable." + :type '(choice (const :tag "Off" nil) + (file :tag "Filename")) + :group 'notmuch-send + :group 'notmuch-address :group 'notmuch-external) (defcustom notmuch-address-selection-function 'notmuch-address-selection-function @@ -96,8 +118,19 @@ See documentation of function `notmuch-address-selection-function' to know how address selection is made by default." :type 'function :group 'notmuch-send + :group 'notmuch-address :group 'notmuch-external) +(defcustom notmuch-address-post-completion-functions nil + "Functions called after completing address. + +The completed address is passed as an argument to each function. +Note that this hook will be invoked for completion in headers +matching `notmuch-address-completion-headers-regexp'." + :type 'hook + :group 'notmuch-address + :group 'notmuch-hooks) + (defun notmuch-address-selection-function (prompt collection initial-input) "Call (`completing-read' PROMPT COLLECTION nil nil INITIAL-INPUT 'notmuch-address-history)" @@ -113,9 +146,10 @@ to know how address selection is made by default." (message "calling notmuch-address-message-insinuate is no longer needed")) (defcustom notmuch-address-use-company t - "If available, use company mode for address completion" + "If available, use company mode for address completion." :type 'boolean - :group 'notmuch-send) + :group 'notmuch-send + :group 'notmuch-address) (defun notmuch-address-setup () (let* ((setup-company (and notmuch-address-use-company @@ -124,7 +158,7 @@ to know how address selection is made by default." #'notmuch-address-expand-name))) (when setup-company (notmuch-company-setup)) - (unless (memq pair message-completion-alist) + (unless (member pair message-completion-alist) (setq message-completion-alist (push pair message-completion-alist))))) @@ -136,11 +170,11 @@ toggles the setting in this buffer." (interactive) (if (local-variable-p 'notmuch-address-command) (kill-local-variable 'notmuch-address-command) - (setq-local notmuch-address-command 'internal)) + (notmuch-setq-local notmuch-address-command 'internal)) (if (boundp 'company-idle-delay) (if (local-variable-p 'company-idle-delay) (kill-local-variable 'company-idle-delay) - (setq-local company-idle-delay nil)))) + (notmuch-setq-local company-idle-delay nil)))) (defun notmuch-address-matching (substring) "Returns a list of completion candidates matching SUBSTRING. @@ -159,7 +193,7 @@ elisp-based implementation or older implementation requiring external commands." (cond ((eq notmuch-address-command 'internal) - (when (not notmuch-address-full-harvest-finished) + (unless (notmuch-address--harvest-ready) ;; First, run quick synchronous harvest based on what the user ;; entered so far (notmuch-address-harvest original t)) @@ -194,12 +228,20 @@ external commands." (t (funcall notmuch-address-selection-function (format "Address (%s matches): " num-options) - (cdr options) (car options)))))) + ;; We put the first match as the initial + ;; input; we put all the matches as + ;; possible completions, moving the + ;; first match to the end of the list + ;; makes cursor up/down in the list work + ;; better. + (append (cdr options) (list (car options))) + (car options)))))) (if chosen (progn (push chosen notmuch-address-history) (delete-region beg end) - (insert chosen)) + (insert chosen) + (run-hook-with-args 'notmuch-address-post-completion-functions chosen)) (message "No matches.") (ding)))) (t nil))) @@ -242,7 +284,7 @@ external commands." (defvar notmuch-address-harvest-procs '(nil . nil) "The currently running harvests. -The car is a partial harvest, and the cdr is a full harvest") +The car is a partial harvest, and the cdr is a full harvest.") (defun notmuch-address-harvest (&optional addr-prefix synchronous callback) "Collect addresses completion candidates. @@ -274,7 +316,7 @@ execution, CALLBACK is called when harvesting finishes." (when config-query (format " and (%s)" config-query))) from-or-to-me-query)) - (args `("address" "--format=sexp" "--format-version=2" + (args `("address" "--format=sexp" "--format-version=4" ,(if sent "--output=recipients" "--output=sender") "--deduplicate=address" ,query))) @@ -304,6 +346,64 @@ execution, CALLBACK is called when harvesting finishes." ;; return value nil) +(defvar notmuch-address--save-hash-version 1 + "Version format of the save hash.") + +(defun notmuch-address--get-address-hash () + "Returns the saved address hash as a plist. + +Returns nil if the save file does not exist, or it does not seem +to be a saved address hash." + (when notmuch-address-save-filename + (condition-case nil + (with-temp-buffer + (insert-file-contents notmuch-address-save-filename) + (let ((name (read (current-buffer))) + (plist (read (current-buffer)))) + ;; We do two simple sanity checks on the loaded file. We just + ;; check a version is specified, not that it is the current + ;; version, as we are allowed to over-write and a save-file with + ;; an older version. + (when (and (string= name "notmuch-address-hash") + (plist-get plist :version)) + plist))) + ;; The error case catches any of the reads failing. + (error nil)))) + +(defun notmuch-address--load-address-hash () + "Read the saved address hash and set the corresponding variables." + (let ((load-plist (notmuch-address--get-address-hash))) + (when (and load-plist + ;; If the user's setting have changed, or the version + ;; has changed, return nil to make sure the new settings + ;; take effect. + (equal (plist-get load-plist :completion-settings) + notmuch-address-internal-completion) + (equal (plist-get load-plist :version) + notmuch-address--save-hash-version)) + (setq notmuch-address-last-harvest (plist-get load-plist :last-harvest) + notmuch-address-completions (plist-get load-plist :completions) + notmuch-address-full-harvest-finished t) + ;; Return t to say load was successful. + t))) + +(defun notmuch-address--save-address-hash () + (when notmuch-address-save-filename + (if (or (not (file-exists-p notmuch-address-save-filename)) + ;; The file exists, check it is a file we saved + (notmuch-address--get-address-hash)) + (with-temp-file notmuch-address-save-filename + (let ((save-plist (list :version notmuch-address--save-hash-version + :completion-settings notmuch-address-internal-completion + :last-harvest notmuch-address-last-harvest + :completions notmuch-address-completions))) + (print "notmuch-address-hash" (current-buffer)) + (print save-plist (current-buffer)))) + (message "\ +Warning: notmuch-address-save-filename %s exists but doesn't +appear to be an address savefile. Not overwriting." + notmuch-address-save-filename)))) + (defun notmuch-address-harvest-trigger () (let ((now (float-time))) (when (> (- now notmuch-address-last-harvest) 86400) @@ -314,7 +414,9 @@ execution, CALLBACK is called when harvesting finishes." ;; again when the trigger is next ;; called (if (string= event "finished\n") - (setq notmuch-address-full-harvest-finished t) + (progn + (notmuch-address--save-address-hash) + (setq notmuch-address-full-harvest-finished t)) (setq notmuch-address-last-harvest 0))))))) ;;