[PATCH v2] emacs: address completion, allow sender/recipient and filters
[notmuch-archives.git] / 7f / 6af000920ec8c82888adc6040637a9052c94df
diff --git a/7f/6af000920ec8c82888adc6040637a9052c94df b/7f/6af000920ec8c82888adc6040637a9052c94df
new file mode 100644 (file)
index 0000000..0dda2dd
--- /dev/null
@@ -0,0 +1,272 @@
+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 E32486DE02B0\r
+ for <notmuch@notmuchmail.org>; Fri, 13 May 2016 08:29:15 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at cworth.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -0.309\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-0.309 tagged_above=-999 required=5 tests=[AWL=0.261,\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, RCVD_IN_DNSWL_LOW=-0.7,\r
+ RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, SPF_PASS=-0.001]\r
+ 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 47IzmQAm77_x for <notmuch@notmuchmail.org>;\r
+ Fri, 13 May 2016 08:29:07 -0700 (PDT)\r
+Received: from mail-wm0-f45.google.com (mail-wm0-f45.google.com\r
+ [74.125.82.45]) by arlo.cworth.org (Postfix) with ESMTPS id 21DDC6DE02AF for\r
+ <notmuch@notmuchmail.org>; Fri, 13 May 2016 08:29:07 -0700 (PDT)\r
+Received: by mail-wm0-f45.google.com with SMTP id g17so36146169wme.1\r
+ for <notmuch@notmuchmail.org>; Fri, 13 May 2016 08:29:07 -0700 (PDT)\r
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r
+ h=from:to:cc:subject:date:message-id:in-reply-to:references;\r
+ bh=h/6jiv7ipi+ShuQNmMvVeMMXx/sAp8QZYhKKkDxCkIY=;\r
+ b=Mqfa1/q8c3Bhr6NAayPMWhBLBEBf5mh3mgOSDo69ED5Q3MCAhTvtKa4naMOK8YmeiE\r
+ qHcE936WVDKKLfYoxaWey2oUWZQjWk7cDxa7ir67nPbgE3mKvgGn3XgU+9BCzlW/PQ2q\r
+ ex3IUGXfFRgOF612Z2xVS1vcMkvjjwTfCNAj5nvkkdaPf2o5BGd48q1JUsNXYti23PGq\r
+ KyVa2ftn1fQ0d6KXW8TKeJXzy84u/Ajo715oYCUthcUsE7jqMLOqwiTJ34PxcWoxNVJW\r
+ 9iibxLSfPFX8n7Uo4lorGK2t5W0V5ISLDge4HwayEgPnXHfltT1oiDTLbKYMO20EqFIJ\r
+ UNvg==\r
+X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r
+ d=1e100.net; s=20130820;\r
+ h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to\r
+ :references;\r
+ bh=h/6jiv7ipi+ShuQNmMvVeMMXx/sAp8QZYhKKkDxCkIY=;\r
+ b=WCHvI6B/qYz9t/z5ZIlzTEFzIpiiBzcuNVY7pORYMn/VNC8DoCBdwB6+UrIauJkGDb\r
+ DoNg8fPheA9f6TqlKF8uMFaQpxnMkUv5U2eVN/D/KWC1TEAkSkDdkauM1KBhqbzMKGp7\r
+ UXgy5k3FwCJON+UnluwpVc5DMuN/2FHukMhGJvOwmX/bFHYHLun/sjF0EPiiyrBgVLUp\r
+ dwViYx+GzFeKWnFgYMzXcDNKPRHCqYhDz/rhzQmuSEBgmGWRB/gwMDza5M8bGTN4YV9v\r
+ bHIpBlxIkT5MRMuNj7tiWVRxxNcWzm+SdLCJSCLE2TdK6SBPSa4wndiNkSv6AAnGLtMQ\r
+ 6u9A==\r
+X-Gm-Message-State:\r
+ AOPr4FUHGXnGM2FrnB3ZLylADmqRsl2HmPIyBLy2/FybLavgSVwGX1N+sZT23LC46MGfsQ==\r
+X-Received: by 10.194.166.3 with SMTP id zc3mr16877941wjb.104.1463153345262;\r
+ Fri, 13 May 2016 08:29:05 -0700 (PDT)\r
+Received: from localhost (5751dfa2.skybroadband.com. [87.81.223.162])\r
+ by smtp.gmail.com with ESMTPSA id xt9sm19126330wjb.17.2016.05.13.08.29.04\r
+ (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\r
+ Fri, 13 May 2016 08:29:04 -0700 (PDT)\r
+From: Mark Walters <markwalters1009@gmail.com>\r
+To: notmuch@notmuchmail.org\r
+Subject: [PATCH v2] emacs: address completion,\r
+ allow sender/recipient and filters\r
+Date: Fri, 13 May 2016 16:28:57 +0100\r
+Message-Id: <1463153337-11381-1-git-send-email-markwalters1009@gmail.com>\r
+X-Mailer: git-send-email 2.1.4\r
+In-Reply-To: <874mf2yeq7.fsf@steelpick.2x.cz>\r
+References: <874mf2yeq7.fsf@steelpick.2x.cz>\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 15:29:16 -0000\r
+\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
+Best wishes\r
+\r
+Mark\r
+\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
+   :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
++               :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
++  (let* ((sent (eq (car notmuch-address-command) 'sent))\r
++       (user-query (cadr notmuch-address-command))\r
++       (filter-query (when filter-string\r
++                       (format "%s:%s*" (if sent "to" "from") filter-string)))\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