From 48643153962a0eb736622486d34131e758f6446a Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Fri, 20 May 2016 21:13:02 +0100 Subject: [PATCH] [PATCH 1/2] emacs: address completion, allow sender/recipient and filters --- 1b/a128d18ed84742fbf5f4383366fbe36f64aafb | 332 ++++++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 1b/a128d18ed84742fbf5f4383366fbe36f64aafb diff --git a/1b/a128d18ed84742fbf5f4383366fbe36f64aafb b/1b/a128d18ed84742fbf5f4383366fbe36f64aafb new file mode 100644 index 000000000..2edb9ca50 --- /dev/null +++ b/1b/a128d18ed84742fbf5f4383366fbe36f64aafb @@ -0,0 +1,332 @@ +Return-Path: +X-Original-To: notmuch@notmuchmail.org +Delivered-To: notmuch@notmuchmail.org +Received: from localhost (localhost [127.0.0.1]) + by arlo.cworth.org (Postfix) with ESMTP id 77F8C6DE02B0 + for ; Fri, 20 May 2016 13:13:31 -0700 (PDT) +X-Virus-Scanned: Debian amavisd-new at cworth.org +X-Spam-Flag: NO +X-Spam-Score: -0.317 +X-Spam-Level: +X-Spam-Status: No, score=-0.317 tagged_above=-999 required=5 tests=[AWL=0.253, + DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, + FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_LOW=-0.7, + RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, SPF_PASS=-0.001] + autolearn=disabled +Received: from arlo.cworth.org ([127.0.0.1]) + by localhost (arlo.cworth.org [127.0.0.1]) (amavisd-new, port 10024) + with ESMTP id dLadyyo6R54u for ; + Fri, 20 May 2016 13:13:23 -0700 (PDT) +Received: from mail-wm0-f50.google.com (mail-wm0-f50.google.com + [74.125.82.50]) by arlo.cworth.org (Postfix) with ESMTPS id 0A9CF6DE01D0 for + ; Fri, 20 May 2016 13:13:23 -0700 (PDT) +Received: by mail-wm0-f50.google.com with SMTP id v200so2820578wmv.0 + for ; Fri, 20 May 2016 13:13:22 -0700 (PDT) +DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; + h=from:to:cc:subject:date:message-id:in-reply-to:references; + bh=nC40kPmLpCoVYninYfZb/9BJ9qzorpGmvAD8wxo1Xag=; + b=zvJXgVew/QHyC8m1brYMNY4F7qdfT0M3sP4VoRjNWoZTyj328WRdVcc4N7He7Z/7ld + qwa/tj4L9hMu3eeyK+7EHKZvxE5rEdxpHfkT8SMZNr75+LyCduV/ilvAzQ76bhUwfB89 + hUTiUIRFn6S20bV75TFYWiyY3uBjrg+fYs3Ab2Skw2TsPn9jRui5f/WYPjj5iYIvrCxX + DDu6UcY0p8nwp5BXqv35gcDPIVPdlEUmL5vEll0MhGR5tLmOwWaOB+d8qT7XQenkUgmk + WlnYGz+hP5WkLH12tJ1M6d+dIbqq6iaJ6YOjPK9wj5zlma+ffSznbIFprP998xM84doG + JOyg== +X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; + d=1e100.net; s=20130820; + h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to + :references; + bh=nC40kPmLpCoVYninYfZb/9BJ9qzorpGmvAD8wxo1Xag=; + b=fQI7uXWUWXHV92+pU7mBBXoyDqdmZITlpwU+VrkC4muYPHWSUkGO33RlGEi3561JHW + P3pXtVtePEJa3MKe6tZb9QgIXShczDocLinb7pYHWZaJMerE7SqcOjCjdSMyUtl8zXMx + ksGzxJU8wGJJdvgzXM5sRLM7iRBvDjBKEmPPgTF4hdGPSnn2IWncs2+9r8dp11p8p6h7 + h4/BaxtXOmPMOanT7D29UUsCOxRn5CaY+V0KQO7POBDLUbtjzG2SwuaGAuTMPa7AJojC + qsZsz3bTTdVwr4fVUELw27YSazgVTMqaSBtSEiDLei1OKefix+r3xN3h1PjoaxhH4Euv + LFBw== +X-Gm-Message-State: + AOPr4FVL1F8S2lVjE8bh9FDXopjV1v2AtvZ7zkEe3jnukdSVeMmRL+UG74eXIi4+ySpMyA== +X-Received: by 10.194.234.71 with SMTP id uc7mr5691932wjc.80.1463775201517; + Fri, 20 May 2016 13:13:21 -0700 (PDT) +Received: from localhost (5751dfa2.skybroadband.com. [87.81.223.162]) + by smtp.gmail.com with ESMTPSA id h4sm21374963wjz.20.2016.05.20.13.13.16 + (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); + Fri, 20 May 2016 13:13:17 -0700 (PDT) +From: Mark Walters +To: notmuch@notmuchmail.org +Subject: [PATCH 1/2] emacs: address completion, + allow sender/recipient and filters +Date: Fri, 20 May 2016 21:13:02 +0100 +Message-Id: <1463775183-16234-2-git-send-email-markwalters1009@gmail.com> +X-Mailer: git-send-email 2.1.4 +In-Reply-To: <1463775183-16234-1-git-send-email-markwalters1009@gmail.com> +References: <1463775183-16234-1-git-send-email-markwalters1009@gmail.com> +X-BeenThere: notmuch@notmuchmail.org +X-Mailman-Version: 2.1.20 +Precedence: list +List-Id: "Use and development of the notmuch mail system." + +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +X-List-Received-Date: Fri, 20 May 2016 20:13:31 -0000 + +This commit lets the user customize the address completion. It makes +two changes. + +The first change controls whether to build the address completion list +based on messages you have sent or you have received (the latter is +much faster). + +The second change add a possible filter query to limit the messages +used -- for example, setting this to date:1y.. would limit the +address completions to addresses used in the last year. This speeds up +the address harvest and may also make the search less cluttered as old +addresses may well no longer be valid. +--- + emacs/notmuch-address.el | 135 +++++++++++++++++++++++++++++++++-------------- + emacs/notmuch-company.el | 2 +- + emacs/notmuch-mua.el | 3 +- + 3 files changed, 98 insertions(+), 42 deletions(-) + +diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el +index aafbe5f..3e7bdab 100644 +--- a/emacs/notmuch-address.el ++++ b/emacs/notmuch-address.el +@@ -28,15 +28,62 @@ + ;; + (declare-function company-manual-begin "company") + ++(defvar notmuch-address-last-harvest 0 ++ "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'.") ++ ++(defvar notmuch-address-full-harvest-finished nil ++ "t indicates that full completion address harvesting has been ++finished") ++ + (defcustom notmuch-address-command 'internal +- "The command which generates possible addresses. It must take a +-single argument and output a list of possible matches, one per +-line. The default value of `internal' uses built-in address +-completion." ++ "Determines how address completion candidates are generated. ++ ++If it is a string then that string should be an external program ++which must take a single argument (searched string) and output a ++list of completion candidates, one per line. ++ ++Alternatively, it can be the symbol 'internal, in which case ++internal completion is used; the variable ++`notmuch-address-internal-completion` can be used to customize ++this case. ++ ++Finally, if this variable is nil then address completion is ++disabled." + :type '(radio + (const :tag "Use internal address completion" internal) + (const :tag "Disable address completion" nil) +- (string :tag "Use external completion command" "notmuch-addresses")) ++ (string :tag "Use external completion command")) ++ :group 'notmuch-send ++ :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." ++ :type '(list :tag "Use internal address completion" ++ (radio ++ :tag "Base completion on messages you have" ++ :value sent ++ (const :tag "sent (more accurate)" sent) ++ (const :tag "received (faster)" received)) ++ (radio :tag "Filter messages used for completion" ++ (const :tag "Use all messages" nil) ++ (string :tag "Filter query"))) ++ ;; We override set so that we can clear the cache when this changes ++ :set (lambda (symbol value) ++ (set-default symbol value) ++ (setq notmuch-address-last-harvest 0) ++ (setq notmuch-address-completions (clrhash notmuch-address-completions)) ++ (setq notmuch-address-full-harvest-finished nil)) + :group 'notmuch-send + :group 'notmuch-external) + +@@ -51,17 +98,6 @@ to know how address selection is made by default." + :group 'notmuch-send + :group 'notmuch-external) + +-(defvar notmuch-address-last-harvest 0 +- "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'.") +- +-(defvar notmuch-address-full-harvest-finished nil +- "t indicates that full completion address harvesting has been +-finished") +- + (defun notmuch-address-selection-function (prompt collection initial-input) + "Call (`completing-read' + PROMPT COLLECTION nil nil INITIAL-INPUT 'notmuch-address-history)" +@@ -86,9 +122,7 @@ finished") + (eq notmuch-address-command 'internal) + (require 'company nil t))) + (pair (cons notmuch-address-completion-headers-regexp +- (if use-company +- #'company-manual-begin +- #'notmuch-address-expand-name)))) ++ #'notmuch-address-expand-name))) + (when use-company + (notmuch-company-setup)) + (unless (memq pair message-completion-alist) +@@ -115,7 +149,7 @@ external commands." + (when (not notmuch-address-full-harvest-finished) + ;; First, run quick synchronous harvest based on what the user + ;; entered so far +- (notmuch-address-harvest (format "to:%s*" original) t)) ++ (notmuch-address-harvest original t)) + (prog1 (notmuch-address-matching original) + ;; Then start the (potentially long-running) full asynchronous harvest if necessary + (notmuch-address-harvest-trigger))) +@@ -123,7 +157,12 @@ external commands." + (process-lines notmuch-address-command original)))) + + (defun notmuch-address-expand-name () +- (when notmuch-address-command ++ (cond ++ ((and (eq notmuch-address-command 'internal) ++ notmuch-address-use-company ++ (bound-and-true-p company-mode)) ++ (company-manual-begin)) ++ (notmuch-address-command + (let* ((end (point)) + (beg (save-excursion + (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*") +@@ -149,7 +188,8 @@ external commands." + (delete-region beg end) + (insert chosen)) + (message "No matches.") +- (ding))))) ++ (ding)))) ++ (t nil))) + + ;; Copied from `w3m-which-command'. + (defun notmuch-address-locate-command (command) +@@ -191,32 +231,49 @@ external commands." + + The car is a partial harvest, and the cdr is a full harvest") + +-(defun notmuch-address-harvest (&optional filter-query synchronous callback) +- "Collect addresses completion candidates. It queries the +-notmuch database for all messages sent by the user optionally +-matching FILTER-QUERY (if not nil). It collects the destination +-addresses from those messages and stores them in +-`notmuch-address-completions'. Address harvesting may take some +-time so the address collection runs asynchronously unless +-SYNCHRONOUS is t. In case of asynchronous execution, CALLBACK is +-called when harvesting finishes." +- (let* ((from-me-query (mapconcat (lambda (x) (concat "from:" x)) (notmuch-user-emails) " or ")) +- (query (if filter-query +- (format "(%s) and (%s)" from-me-query filter-query) +- from-me-query)) ++(defun notmuch-address-harvest (&optional addr-prefix synchronous callback) ++ "Collect addresses completion candidates. ++ ++It queries the notmuch database for messages sent/received (as ++configured with `notmuch-address-command`) by the user, collects ++destination/source addresses from those messages and stores them ++in `notmuch-address-completions'. ++ ++If ADDR-PREFIX is not nil, only messages with to/from addresses ++matching ADDR-PREFIX*' are queried. ++ ++Address harvesting may take some time so the address collection runs ++asynchronously unless SYNCHRONOUS is t. In case of asynchronous ++execution, CALLBACK is called when harvesting finishes." ++ ++ (let* ((sent (eq (car notmuch-address-internal-completion) 'sent)) ++ (config-query (cadr notmuch-address-internal-completion)) ++ (prefix-query (when addr-prefix ++ (format "%s:%s*" (if sent "to" "from") addr-prefix))) ++ (from-or-to-me-query ++ (mapconcat (lambda (x) ++ (concat (if sent "from:" "to:") x)) ++ (notmuch-user-emails) " or ")) ++ (query (if (or prefix-query config-query) ++ (concat (format "(%s)" from-or-to-me-query) ++ (when prefix-query ++ (format " and (%s)" prefix-query)) ++ (when config-query ++ (format " and (%s)" config-query))) ++ from-or-to-me-query)) + (args `("address" "--format=sexp" "--format-version=2" +- "--output=recipients" ++ ,(if sent "--output=recipients" "--output=sender") + "--deduplicate=address" + ,query))) + (if synchronous + (mapc #'notmuch-address-harvest-addr + (apply 'notmuch-call-notmuch-sexp args)) + ;; Asynchronous +- (let* ((current-proc (if filter-query ++ (let* ((current-proc (if addr-prefix + (car notmuch-address-harvest-procs) + (cdr notmuch-address-harvest-procs))) + (proc-name (format "notmuch-address-%s-harvest" +- (if filter-query "partial" "full"))) ++ (if addr-prefix "partial" "full"))) + (proc-buf (concat " *" proc-name "*"))) + ;; Kill any existing process + (when current-proc +@@ -228,7 +285,7 @@ called when harvesting finishes." + args)) + (set-process-filter current-proc 'notmuch-address-harvest-filter) + (set-process-query-on-exit-flag current-proc nil) +- (if filter-query ++ (if addr-prefix + (setcar notmuch-address-harvest-procs current-proc) + (setcdr notmuch-address-harvest-procs current-proc))))) + ;; return value +diff --git a/emacs/notmuch-company.el b/emacs/notmuch-company.el +index b881d6d..dcb59cd 100644 +--- a/emacs/notmuch-company.el ++++ b/emacs/notmuch-company.el +@@ -72,7 +72,7 @@ + (lambda (callback) + ;; First run quick asynchronous harvest based on what the user entered so far + (notmuch-address-harvest +- (format "to:%s*" arg) nil ++ arg nil + (lambda (_proc _event) + (funcall callback (notmuch-address-matching arg)) + ;; Then start the (potentially long-running) full asynchronous harvest if necessary +diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el +index 399e138..93a4d4b 100644 +--- a/emacs/notmuch-mua.el ++++ b/emacs/notmuch-mua.el +@@ -276,8 +276,7 @@ mutiple parts get a header." + + (define-derived-mode notmuch-message-mode message-mode "Message[Notmuch]" + "Notmuch message composition mode. Mostly like `message-mode'" +- (when notmuch-address-command +- (notmuch-address-setup))) ++ (notmuch-address-setup)) + + (put 'notmuch-message-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify) + +-- +2.1.4 + -- 2.26.2