Re: [PATCH v2] emacs: address completion, allow sender/recipient and filters
authorMichal Sojka <sojkam1@fel.cvut.cz>
Fri, 13 May 2016 21:41:58 +0000 (23:41 +0200)
committerW. Trevor King <wking@tremily.us>
Sat, 20 Aug 2016 23:21:50 +0000 (16:21 -0700)
be/52c846d7448babdfd41be89c3c2f924a306afa [new file with mode: 0644]

diff --git a/be/52c846d7448babdfd41be89c3c2f924a306afa b/be/52c846d7448babdfd41be89c3c2f924a306afa
new file mode 100644 (file)
index 0000000..5a595a4
--- /dev/null
@@ -0,0 +1,307 @@
+Return-Path: <sojkam1@fel.cvut.cz>\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 1DB6F6DE02B2\r
+ for <notmuch@notmuchmail.org>; Fri, 13 May 2016 14:42:12 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at cworth.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -1.635\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-1.635 tagged_above=-999 required=5 tests=[AWL=0.675,\r
+  RCVD_IN_DNSWL_MED=-2.3, T_RP_MATCHES_RCVD=-0.01] 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 osPu82yF95Js for <notmuch@notmuchmail.org>;\r
+ Fri, 13 May 2016 14:42:03 -0700 (PDT)\r
+Received: from max.feld.cvut.cz (max.feld.cvut.cz [147.32.192.36])\r
+ by arlo.cworth.org (Postfix) with ESMTP id 2C9646DE0317\r
+ for <notmuch@notmuchmail.org>; Fri, 13 May 2016 14:42:03 -0700 (PDT)\r
+Received: from localhost (unknown [192.168.200.7])\r
+ by max.feld.cvut.cz (Postfix) with ESMTP id 977F45CD662;\r
+ Fri, 13 May 2016 23:42:01 +0200 (CEST)\r
+X-Virus-Scanned: IMAP STYX AMAVIS\r
+Received: from max.feld.cvut.cz ([192.168.200.1])\r
+ by localhost (styx.feld.cvut.cz [192.168.200.7]) (amavisd-new, port 10044)\r
+ with ESMTP id oFXwixJwOxl4; Fri, 13 May 2016 23:41:59 +0200 (CEST)\r
+Received: from imap.feld.cvut.cz (imap.feld.cvut.cz [147.32.192.34])\r
+ by max.feld.cvut.cz (Postfix) with ESMTP id 974735CD275;\r
+ Fri, 13 May 2016 23:41:59 +0200 (CEST)\r
+Received: from wsh by steelpick.2x.cz with local (Exim 4.87)\r
+ (envelope-from <sojkam1@fel.cvut.cz>)\r
+ id 1b1KqM-00014s-Va; Fri, 13 May 2016 23:41:58 +0200\r
+From: Michal Sojka <sojkam1@fel.cvut.cz>\r
+To: Mark Walters <markwalters1009@gmail.com>, notmuch@notmuchmail.org\r
+Subject: Re: [PATCH v2] emacs: address completion,\r
+ allow sender/recipient and filters\r
+In-Reply-To: <1463153337-11381-1-git-send-email-markwalters1009@gmail.com>\r
+References: <874mf2yeq7.fsf@steelpick.2x.cz>\r
+ <1463153337-11381-1-git-send-email-markwalters1009@gmail.com>\r
+User-Agent: Notmuch/0.22 (http://notmuchmail.org) Emacs/24.5.1\r
+ (x86_64-pc-linux-gnu)\r
+Date: Fri, 13 May 2016 23:41:58 +0200\r
+Message-ID: <87h9e11vq1.fsf@steelpick.2x.cz>\r
+MIME-Version: 1.0\r
+Content-Type: text/plain\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: Fri, 13 May 2016 21:42:12 -0000\r
+\r
+Hi Mark,\r
+\r
+On Fri, May 13 2016, Mark Walters wrote:\r
+> This commit lets the user customize the address completion.\r
+>\r
+> The first change controls whether to build the address completion list\r
+> based on messages you have sent or you have received (the latter is\r
+> much faster).\r
+>\r
+> The second change add a possible filter query to limit the messages\r
+> used -- for example, setting this to date:1y..  would limit the\r
+> address completions to addresses used in the last year. This speeds up\r
+> the address harvest and may also make the search less cluttered as old\r
+> addresses may well no longer be valid.\r
+> ---\r
+>\r
+> I have fixed the bug pointed out by Michal in his review. It seems to\r
+> work, but there are a lot of possible configurations,\r
+>\r
+> I also don't like my docstring for the notmuch-address-command\r
+> defcustom, so any suggestions gratefully received.\r
+>\r
+> Also, I am not sure whether this is all to much complexity for this\r
+> feature.\r
+\r
+A big part of the patch is moving variable definitions and I'd say the\r
+rest is OK (complexity-wise). Other comments below.\r
+\r
+>  emacs/notmuch-address.el | 104 +++++++++++++++++++++++++++++++----------------\r
+>  emacs/notmuch-company.el |   2 +-\r
+>  2 files changed, 71 insertions(+), 35 deletions(-)\r
+>\r
+> diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el\r
+> index aafbe5f..8b84a4c 100644\r
+> --- a/emacs/notmuch-address.el\r
+> +++ b/emacs/notmuch-address.el\r
+> @@ -28,15 +28,50 @@\r
+>  ;;\r
+>  (declare-function company-manual-begin "company")\r
+>\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 `internal' uses built-in address\r
+> -completion."\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
+> +(defcustom notmuch-address-command '(sent nil)\r
+> +  "The command which generates possible addresses.\r
+> +\r
+> +It can be a (non-nil) list, in which case internal completion is\r
+> +used; in this case the first list item 'sent/'received specifies\r
+> +whether you match message sent by the user or received by the\r
+> +user (note received by is much faster), and the second list item\r
+> +should be nil or a filter-string, such as \"date:1y..\" to append\r
+> +to the query.\r
+> +\r
+> +If this variable is nil then address completion is disabled.\r
+> +\r
+> +If it is a string then that string should be an external program\r
+> +which must take a single argument and output a list of possible\r
+> +matches, one per line."\r
+\r
+What about the following? (I think that the string value should be\r
+described first because it corresponds to the name of the variable)\r
+\r
+     "Determines how to generate address completion candidates.\r
+\r
+   If it is a string then that string should be an external program\r
+   which must take a single argument (searched string) and output a list\r
+   of completion candidates, one per line.\r
+\r
+   Alternatively, it can be a (non-nil) list, in which case internal\r
+   completion is used; in this case the list should have form\r
+   '(DIRECTION FILTER), where DIRECTION is either sent or received and\r
+   specifies whether the candidates are searched in messages sent by the\r
+   user or received by the user (note received by is much faster), and\r
+   FILTER is either nil or a filter-string, such as \"date:1y..\" to\r
+   append to the query.\r
+\r
+   If this variable is nil then address completion is disabled."\r
+\r
+>    :type '(radio\r
+> -      (const :tag "Use internal address completion" internal)\r
+> +      (list :tag "Use internal address completion"\r
+> +            (radio\r
+> +             :tag "Build list based on messages you have"\r
+\r
+What about "Base completion on messages you have"?\r
+\r
+> +             :value sent\r
+> +             (const :tag "sent" sent)\r
+> +             (const :tag "received" received))\r
+> +            (radio :tag "Filter messages used for completion"\r
+> +                   (const :tag "Use all messages" nil)\r
+> +                   (string :tag "Filter query")))\r
+>        (const :tag "Disable address completion" nil)\r
+> -      (string :tag "Use external completion command" "notmuch-addresses"))\r
+> +      (string :tag "Use external completion command"))\r
+> +  ;; We override set so that we can clear the cache when this changes\r
+> +  :set (lambda (symbol value)\r
+> +     (set-default symbol value)\r
+> +     (setq notmuch-address-last-harvest 0)\r
+> +     (setq notmuch-address-completions (clrhash notmuch-address-completions))\r
+> +     (setq notmuch-address-full-harvest-finished nil))\r
+>    :group 'notmuch-send\r
+>    :group 'notmuch-external)\r
+>\r
+> @@ -51,17 +86,6 @@ 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
+> @@ -83,7 +107,8 @@ finished")\r
+>\r
+>  (defun notmuch-address-setup ()\r
+>    (let* ((use-company (and notmuch-address-use-company\r
+> -                       (eq notmuch-address-command 'internal)\r
+> +                       notmuch-address-command\r
+> +                       (listp notmuch-address-command)\r
+>                         (require 'company nil t)))\r
+>       (pair (cons notmuch-address-completion-headers-regexp\r
+>                   (if use-company\r
+> @@ -111,11 +136,11 @@ The candidates are taken from `notmuch-address-completions'."\r
+>  elisp-based implementation or older implementation requiring\r
+>  external commands."\r
+>    (cond\r
+> -   ((eq notmuch-address-command 'internal)\r
+> +   ((and notmuch-address-command (listp notmuch-address-command))\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
+> +      (notmuch-address-harvest original t))\r
+>      (prog1 (notmuch-address-matching original)\r
+>        ;; Then start the (potentially long-running) full asynchronous harvest if necessary\r
+>        (notmuch-address-harvest-trigger)))\r
+> @@ -191,21 +216,32 @@ external commands."\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
+> +(defun notmuch-address-harvest (&optional filter-string 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
+> +notmuch database for messages sent/received by the user\r
+> +optionally with to/from matching FILTER-STRING (if not nil). It\r
+> +collects the destination addresses from those messages and stores\r
+> +them in `notmuch-address-completions'. Address harvesting may\r
+> +take some time so the address collection runs asynchronously\r
+> +unless SYNCHRONOUS is t. In case of asynchronous execution,\r
+> +CALLBACK is called when harvesting finishes."\r
+\r
+I'm a bit lost in the names of variables here so I suggest more\r
+descriptive names below. Also the doc string could be IMHO better.\r
+\r
+(defun notmuch-address-harvest (&optional addr-prefix synchronous callback)\r
+      "Collect addresses completion candidates.\r
+\r
+    It queries the notmuch database for messages sent/received (as\r
+    configured with `notmuch-address-command`) by the user, collects\r
+    destination/source addresses from those messages and stores them in\r
+    `notmuch-address-completions'.\r
+\r
+    If ADDR-PREFIX is not nil, only messages with to/from addresses\r
+    matching ADDR-PREFIX*' are queried.\r
+\r
+    Address harvesting may take some time so the address collection runs\r
+    asynchronously unless SYNCHRONOUS is t. In case of asynchronous\r
+    execution, CALLBACK is called when harvesting finishes."\r
+\r
+> +  (let* ((sent (eq (car notmuch-address-command) 'sent))\r
+> +     (user-query (cadr notmuch-address-command))\r
+\r
+config-query\r
+\r
+> +     (filter-query (when filter-string\r
+> +                     (format "%s:%s*" (if sent "to" "from") filter-string)))\r
+\r
+prefix-query\r
+\r
+> +     (from-or-to-me-query\r
+> +      (mapconcat (lambda (x)\r
+> +                   (concat (if sent "from:" "to:") x))\r
+> +                 (notmuch-user-emails) " or "))\r
+> +     (query (if (or filter-query user-query)\r
+> +                (concat (format "(%s)" from-or-to-me-query)\r
+> +                        (when filter-query\r
+> +                          (format " and (%s)" filter-query))\r
+> +                        (when user-query\r
+> +                          (format " and (%s)" user-query)))\r
+> +              from-or-to-me-query))\r
+>       (args `("address" "--format=sexp" "--format-version=2"\r
+> -             "--output=recipients"\r
+> +             ,(if sent "--output=recipients" "--output=sender")\r
+>               "--deduplicate=address"\r
+>               ,query)))\r
+>      (if synchronous\r
+> diff --git a/emacs/notmuch-company.el b/emacs/notmuch-company.el\r
+> index b881d6d..dcb59cd 100644\r
+> --- a/emacs/notmuch-company.el\r
+> +++ b/emacs/notmuch-company.el\r
+> @@ -72,7 +72,7 @@\r
+>                        (lambda (callback)\r
+>                          ;; First run quick asynchronous harvest based on what the user entered so far\r
+>                          (notmuch-address-harvest\r
+> -                         (format "to:%s*" arg) nil\r
+> +                         arg nil\r
+>                           (lambda (_proc _event)\r
+>                             (funcall callback (notmuch-address-matching arg))\r
+>                             ;; Then start the (potentially long-running) full asynchronous harvest if necessary\r
+> --\r
+> 2.1.4\r
+\r
+Cheers\r
+-Michal\r