--- /dev/null
+Return-Path: <markwalters1009@gmail.com>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+ by arlo.cworth.org (Postfix) with ESMTP id BF8A76DE1479\r
+ for <notmuch@notmuchmail.org>; Sun, 25 Oct 2015 10:28:58 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at cworth.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: 0.25\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=0.25 tagged_above=-999 required=5 tests=[AWL=0.076,\r
+ DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,\r
+ FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001,\r
+ RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H3=-0.01,\r
+ RCVD_IN_MSPIKE_WL=-0.01, SPF_PASS=-0.001, URIBL_SBL=0.644,\r
+ URIBL_SBL_A=0.1] autolearn=disabled\r
+Received: from arlo.cworth.org ([127.0.0.1])\r
+ by localhost (arlo.cworth.org [127.0.0.1]) (amavisd-new, port 10024)\r
+ with ESMTP id cQJCuIO_PvI6 for <notmuch@notmuchmail.org>;\r
+ Sun, 25 Oct 2015 10:28:56 -0700 (PDT)\r
+Received: from mail-wi0-f181.google.com (mail-wi0-f181.google.com\r
+ [209.85.212.181])\r
+ by arlo.cworth.org (Postfix) with ESMTPS id D37CD6DE103A\r
+ for <notmuch@notmuchmail.org>; Sun, 25 Oct 2015 10:28:55 -0700 (PDT)\r
+Received: by wicfx6 with SMTP id fx6so86070720wic.1\r
+ for <notmuch@notmuchmail.org>; Sun, 25 Oct 2015 10:28:54 -0700 (PDT)\r
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r
+ h=from:to:subject:in-reply-to:references:user-agent:date:message-id\r
+ :mime-version:content-type;\r
+ bh=RW3haxCKEIU+sZJGWEqrLjHRBKyelkL35i6t2K4o+nw=;\r
+ b=KuXhunkumEmms/6/Bs1NE15Jwj0r3Q5ase++ux03zRu89zc41ESFqUDqahQnrVenTW\r
+ zsWMvOZ80kVOhNDi0r0HCfcqUGMIdUONjBlZLzXIK7VJT4G9N61IZH+ZBif26WQobS1g\r
+ XQ0ncGfQFMiuHpc6nDbak63c8xf9lqNpEqS6t7a+D0/r4YInaniygll6N9g7htuRswoT\r
+ xFm1X8OUIp65aEq+cd2BcsRhAGhOcu0Oqrzpz+tomn9p346VX9yI2YEwg6UJhLz/B1cy\r
+ wn4BuZ6fqXGrnLTEC92hG8pS/tFZw7pRRBspreFdb4WBIs9TmwofuLsboVzgpeqNk1MG\r
+ eEfg==\r
+X-Received: by 10.194.7.97 with SMTP id i1mr39451402wja.87.1445794134012;\r
+ Sun, 25 Oct 2015 10:28:54 -0700 (PDT)\r
+Received: from localhost (5751dfa2.skybroadband.com. [87.81.223.162])\r
+ by smtp.gmail.com with ESMTPSA id kr10sm34477258wjc.25.2015.10.25.10.28.52\r
+ (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\r
+ Sun, 25 Oct 2015 10:28:53 -0700 (PDT)\r
+From: Mark Walters <markwalters1009@gmail.com>\r
+To: David Bremner <david@tethera.net>, notmuch@notmuchmail.org\r
+Subject: Re: [Patch v7 2/3] Emacs: Add address completion mechanism\r
+ implemented in elisp\r
+In-Reply-To: <1445786638-13763-3-git-send-email-david@tethera.net>\r
+References: <1445786638-13763-1-git-send-email-david@tethera.net>\r
+ <1445786638-13763-3-git-send-email-david@tethera.net>\r
+User-Agent: Notmuch/0.21~rc1+29~g057f24d (http://notmuchmail.org) Emacs/23.4.1\r
+ (x86_64-pc-linux-gnu)\r
+Date: Sun, 25 Oct 2015 17:28:51 +0000\r
+Message-ID: <878u6qdfho.fsf@qmul.ac.uk>\r
+MIME-Version: 1.0\r
+Content-Type: text/plain; charset=us-ascii\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.20\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+ <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <https://notmuchmail.org/mailman/options/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch/>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <https://notmuchmail.org/mailman/listinfo/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Sun, 25 Oct 2015 17:28:58 -0000\r
+\r
+On Sun, 25 Oct 2015, David Bremner <david@tethera.net> wrote:\r
+> From: Michal Sojka <sojkam1@fel.cvut.cz>\r
+>\r
+> Currently, notmuch has an address completion mechanism that requires\r
+> external command to provide completion candidates. This patch adds a\r
+> completion mechanism inspired by https://github.com/tjim/nevermore,\r
+> which is implemented in Emacs lisp only.\r
+>\r
+> The preexisting address completion mechanism, activated by pressing TAB\r
+> on To/Cc lines, is extended to use the new mechanism when no external\r
+> command is configured, i.e. when notmuch-address-command to nil, which\r
+> is the new default.\r
+>\r
+> The core of the new mechanism is the function notmuch-address-harvest,\r
+> which collects the completion candidates from the notmuch database and\r
+> stores them in notmuch-address-completions variable. The address\r
+> harvesting can run either synchronously (same as with the previous\r
+> mechanism) or asynchronously. When the user presses TAB for the first\r
+> time, synchronous harvesting limited to user entered text is performed.\r
+> If the entered text is reasonably long, this operation is relatively\r
+> fast. Then, asynchronous harvesting over the full database is triggered.\r
+> This operation may take long time (minutes on rotating disk). After it\r
+> finishes, no harvesting is normally performed again and subsequent\r
+> completion requests use the harvested data cached in memory. Completion\r
+> cache is updated after 24 hours.\r
+>\r
+> Note that this commit restores (different) completion functionality for\r
+> users when the user used external command named "notmuch-addresses",\r
+> i.e. the old default. The result will be that the user will use\r
+> the new mechanism instead of this command. I believe that many users may\r
+> not even recognize this because the new mechanism works the same as\r
+> http://commonmeasure.org/~jkr/git/notmuch_addresses.git and perhaps also\r
+> as other commands suggested at\r
+> http://notmuchmail.org/emacstips/#address_completion.\r
+> ---\r
+> emacs/notmuch-address.el | 192 ++++++++++++++++++++++++++++++++++++++---------\r
+> emacs/notmuch-lib.el | 3 +\r
+> 2 files changed, 159 insertions(+), 36 deletions(-)\r
+>\r
+> diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el\r
+> index e2af879..aa6228d 100644\r
+> --- a/emacs/notmuch-address.el\r
+> +++ b/emacs/notmuch-address.el\r
+> @@ -20,14 +20,17 @@\r
+> ;; Authors: David Edmondson <dme@dme.org>\r
+> \r
+> (require 'message)\r
+> -\r
+> +(require 'notmuch-parser)\r
+> +(require 'notmuch-lib)\r
+> ;;\r
+> \r
+> -(defcustom notmuch-address-command nil\r
+> +(defcustom notmuch-address-command 'internal\r
+> "The command which generates possible addresses. It must take a\r
+> single argument and output a list of possible matches, one per\r
+> -line. The default value of nil disables address completion."\r
+> +line. The default value of `internal' uses built-in address\r
+> +completion."\r
+> :type '(radio\r
+> + (const :tag "Use internal address completion" internal)\r
+> (const :tag "Disable address completion" nil)\r
+> (string :tag "Use external completion command" "notmuch-addresses"))\r
+> :group 'notmuch-send\r
+> @@ -44,15 +47,25 @@ to know how address selection is made by default."\r
+> :group 'notmuch-send\r
+> :group 'notmuch-external)\r
+> \r
+> +(defvar notmuch-address-last-harvest 0\r
+> + "Time of last address harvest")\r
+> +\r
+> +(defvar notmuch-address-completions (make-hash-table :test 'equal)\r
+> + "Hash of email addresses for completion during email composition.\r
+> + This variable is set by calling `notmuch-address-harvest'.")\r
+> +\r
+> +(defvar notmuch-address-full-harvest-finished nil\r
+> + "t indicates that full completion address harvesting has been\r
+> +finished")\r
+> +\r
+> (defun notmuch-address-selection-function (prompt collection initial-input)\r
+> "Call (`completing-read'\r
+> PROMPT COLLECTION nil nil INITIAL-INPUT 'notmuch-address-history)"\r
+> (completing-read\r
+> prompt collection nil nil initial-input 'notmuch-address-history))\r
+> \r
+> -(defvar notmuch-address-message-alist-member\r
+> - '("^\\(Resent-\\)?\\(To\\|B?Cc\\|Reply-To\\|From\\|Mail-Followup-To\\|Mail-Copies-To\\):"\r
+> - . notmuch-address-expand-name))\r
+> +(defvar notmuch-address-completion-headers-regexp\r
+> + "^\\(Resent-\\)?\\(To\\|B?Cc\\|Reply-To\\|From\\|Mail-Followup-To\\|Mail-Copies-To\\):")\r
+> \r
+> (defvar notmuch-address-history nil)\r
+> \r
+> @@ -60,39 +73,67 @@ to know how address selection is made by default."\r
+> (message "calling notmuch-address-message-insinuate is no longer needed"))\r
+> \r
+> (defun notmuch-address-setup ()\r
+> - (unless (memq notmuch-address-message-alist-member message-completion-alist)\r
+> - (setq message-completion-alist\r
+> - (push notmuch-address-message-alist-member message-completion-alist))))\r
+> + (let ((pair (cons notmuch-address-completion-headers-regexp\r
+> + #'notmuch-address-expand-name)))\r
+> + (unless (memq pair message-completion-alist)\r
+> + (setq message-completion-alist\r
+> + (push pair message-completion-alist)))))\r
+> +\r
+> +(defun notmuch-address-matching (substring)\r
+> + "Returns a list of completion candidates matching SUBSTRING.\r
+> +The candidates are taked form `notmuch-address-completions'."\r
+\r
+Just one minor typo: "taked form" should be "taken from".\r
+\r
+Best wishes\r
+\r
+Mark\r
+\r
+> + (let ((candidates)\r
+> + (re (regexp-quote substring)))\r
+> + (maphash (lambda (key val)\r
+> + (when (string-match re key)\r
+> + (push key candidates)))\r
+> + notmuch-address-completions)\r
+> + candidates))\r
+> +\r
+> (defun notmuch-address-options (original)\r
+> - (process-lines notmuch-address-command original))\r
+> + "Returns a list of completion candidates. Uses either\r
+> +elisp-based implementation or older implementation requiring\r
+> +external commands."\r
+> + (cond\r
+> + ((eq notmuch-address-command 'internal)\r
+> + (when (not notmuch-address-full-harvest-finished)\r
+> + ;; First, run quick synchronous harvest based on what the user\r
+> + ;; entered so far\r
+> + (notmuch-address-harvest (format "to:%s*" original) t))\r
+> + (prog1 (notmuch-address-matching original)\r
+> + ;; Then (re)start potentially long-running full asynchronous harvesting\r
+> + (notmuch-address-harvest-trigger)))\r
+> + (t\r
+> + (process-lines notmuch-address-command original))))\r
+> \r
+> (defun notmuch-address-expand-name ()\r
+> - (let* ((end (point))\r
+> - (beg (save-excursion\r
+> - (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*")\r
+> - (goto-char (match-end 0))\r
+> - (point)))\r
+> - (orig (buffer-substring-no-properties beg end))\r
+> - (completion-ignore-case t)\r
+> - (options (with-temp-message "Looking for completion candidates..."\r
+> - (notmuch-address-options orig)))\r
+> - (num-options (length options))\r
+> - (chosen (cond\r
+> - ((eq num-options 0)\r
+> - nil)\r
+> - ((eq num-options 1)\r
+> - (car options))\r
+> - (t\r
+> - (funcall notmuch-address-selection-function\r
+> - (format "Address (%s matches): " num-options)\r
+> - (cdr options) (car options))))))\r
+> - (if chosen\r
+> - (progn\r
+> - (push chosen notmuch-address-history)\r
+> - (delete-region beg end)\r
+> - (insert chosen))\r
+> - (message "No matches.")\r
+> - (ding))))\r
+> + (when notmuch-address-command\r
+> + (let* ((end (point))\r
+> + (beg (save-excursion\r
+> + (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*")\r
+> + (goto-char (match-end 0))\r
+> + (point)))\r
+> + (orig (buffer-substring-no-properties beg end))\r
+> + (completion-ignore-case t)\r
+> + (options (with-temp-message "Looking for completion candidates..."\r
+> + (notmuch-address-options orig)))\r
+> + (num-options (length options))\r
+> + (chosen (cond\r
+> + ((eq num-options 0)\r
+> + nil)\r
+> + ((eq num-options 1)\r
+> + (car options))\r
+> + (t\r
+> + (funcall notmuch-address-selection-function\r
+> + (format "Address (%s matches): " num-options)\r
+> + (cdr options) (car options))))))\r
+> + (if chosen\r
+> + (progn\r
+> + (push chosen notmuch-address-history)\r
+> + (delete-region beg end)\r
+> + (insert chosen))\r
+> + (message "No matches.")\r
+> + (ding)))))\r
+> \r
+> ;; Copied from `w3m-which-command'.\r
+> (defun notmuch-address-locate-command (command)\r
+> @@ -113,4 +154,83 @@ to know how address selection is made by default."\r
+> (not (file-directory-p bin))))\r
+> (throw 'found-command bin))))))))\r
+> \r
+> +(defun notmuch-address-harvest-addr (result)\r
+> + (let ((name-addr (plist-get result :name-addr)))\r
+> + (puthash name-addr t notmuch-address-completions)))\r
+> +\r
+> +(defun notmuch-address-harvest-handle-result (obj)\r
+> + (notmuch-address-harvest-addr obj))\r
+> +\r
+> +(defun notmuch-address-harvest-filter (proc string)\r
+> + (when (buffer-live-p (process-buffer proc))\r
+> + (with-current-buffer (process-buffer proc)\r
+> + (save-excursion\r
+> + (goto-char (point-max))\r
+> + (insert string))\r
+> + (notmuch-sexp-parse-partial-list\r
+> + 'notmuch-address-harvest-handle-result (process-buffer proc)))))\r
+> +\r
+> +(defvar notmuch-address-harvest-procs '(nil . nil)\r
+> + "The currently running harvests.\r
+> +\r
+> +The car is a partial harvest, and the cdr is a full harvest")\r
+> +\r
+> +(defun notmuch-address-harvest (&optional filter-query synchronous callback)\r
+> + "Collect addresses completion candidates. It queries the\r
+> +notmuch database for all messages sent by the user optionally\r
+> +matching FILTER-QUERY (if not nil). It collects the destination\r
+> +addresses from those messages and stores them in\r
+> +`notmuch-address-completions'. Address harvesting may take some\r
+> +time so the address collection runs asynchronously unless\r
+> +SYNCHRONOUS is t. In case of asynchronous execution, CALLBACK is\r
+> +called when harvesting finishes."\r
+> + (let* ((from-me-query (mapconcat (lambda (x) (concat "from:" x)) (notmuch-user-emails) " or "))\r
+> + (query (if filter-query\r
+> + (format "(%s) and (%s)" from-me-query filter-query)\r
+> + from-me-query))\r
+> + (args `("address" "--format=sexp" "--format-version=2"\r
+> + "--output=recipients"\r
+> + "--deduplicate=address"\r
+> + ,query)))\r
+> + (if synchronous\r
+> + (mapc #'notmuch-address-harvest-addr\r
+> + (apply 'notmuch-call-notmuch-sexp args))\r
+> + ;; Asynchronous\r
+> + (let* ((current-proc (if filter-query\r
+> + (car notmuch-address-harvest-procs)\r
+> + (cdr notmuch-address-harvest-procs)))\r
+> + (proc-name (format "notmuch-address-%s-harvest"\r
+> + (if filter-query "partial" "full")))\r
+> + (proc-buf (concat " *" proc-name "*")))\r
+> + ;; Kill any existing process\r
+> + (when current-proc\r
+> + (kill-buffer (process-buffer current-proc))) ; this also kills the process\r
+> +\r
+> + (setq current-proc\r
+> + (apply 'notmuch-start-notmuch proc-name proc-buf\r
+> + callback ; process sentinel\r
+> + args))\r
+> + (set-process-filter current-proc 'notmuch-address-harvest-filter)\r
+> + (set-process-query-on-exit-flag current-proc nil)\r
+> + (if filter-query\r
+> + (setcar notmuch-address-harvest-procs current-proc)\r
+> + (setcdr notmuch-address-harvest-procs current-proc)))))\r
+> + ;; return value\r
+> + nil)\r
+> +\r
+> +(defun notmuch-address-harvest-trigger ()\r
+> + (let ((now (float-time)))\r
+> + (when (> (- now notmuch-address-last-harvest) 86400)\r
+> + (setq notmuch-address-last-harvest now)\r
+> + (notmuch-address-harvest nil nil\r
+> + (lambda (proc event)\r
+> + ;; If harvest fails, we want to try\r
+> + ;; again when the trigger is next\r
+> + ;; called\r
+> + (if (string= event "finished\n")\r
+> + (setq notmuch-address-full-harvest-finished t)\r
+> + (setq notmuch-address-last-harvest 0)))))))\r
+> +\r
+> +;;\r
+> +\r
+> (provide 'notmuch-address)\r
+> diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el\r
+> index 201d7ec..1c3a9fe 100644\r
+> --- a/emacs/notmuch-lib.el\r
+> +++ b/emacs/notmuch-lib.el\r
+> @@ -232,6 +232,9 @@ on the command line, and then retry your notmuch command")))\r
+> "Return the user.other_email value (as a list) from the notmuch configuration."\r
+> (split-string (notmuch-config-get "user.other_email") "\n" t))\r
+> \r
+> +(defun notmuch-user-emails ()\r
+> + (cons (notmuch-user-primary-email) (notmuch-user-other-email)))\r
+> +\r
+> (defun notmuch-poll ()\r
+> "Run \"notmuch new\" or an external script to import mail.\r
+> \r
+> -- \r
+> 2.6.1\r
+>\r
+> _______________________________________________\r
+> notmuch mailing list\r
+> notmuch@notmuchmail.org\r
+> https://notmuchmail.org/mailman/listinfo/notmuch\r