unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
* [PATCH v1 0/3] Address completion entirely in elisp.
@ 2014-09-05 12:59 David Edmondson
  2014-09-05 12:59 ` [PATCH v1 1/3] emacs: Allow query to exclude the entire thread and body David Edmondson
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: David Edmondson @ 2014-09-05 12:59 UTC (permalink / raw)
  To: notmuch


Address completion entirely in elisp.

I grew frustrated with having to use an external command to provide
address completion, as they all had annoyances (up front scanning,
requiring python bindings, etc.). This is an attempt to provide
something similar to jkr's notmuch-addresses.py (which I was
previously using) entirely in elisp, relying only on the `notmuch'
command.


David Edmondson (3):
  emacs: Allow query to exclude the entire thread and body.
  emacs: Add `notmuch-user-all-email'.
  emacs: Add an address completer in elisp.

 emacs/notmuch-address.el | 49 +++++++++++++++++++++++++++++++++++++++++++-----
 emacs/notmuch-lib.el     |  4 ++++
 emacs/notmuch-query.el   |  7 ++++++-
 3 files changed, 54 insertions(+), 6 deletions(-)

-- 
1.8.5.2 (Apple Git-48)

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v1 1/3] emacs: Allow query to exclude the entire thread and body.
  2014-09-05 12:59 [PATCH v1 0/3] Address completion entirely in elisp David Edmondson
@ 2014-09-05 12:59 ` David Edmondson
  2014-09-05 12:59 ` [PATCH v1 2/3] emacs: Add `notmuch-user-all-email' David Edmondson
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 8+ messages in thread
From: David Edmondson @ 2014-09-05 12:59 UTC (permalink / raw)
  To: notmuch

Callers to `notmuch-query-get-threads' can optionally choose not to
receive either the entire thread and/or the body of messages.

This is intended to reduce the amount of output in cases where one or
both of these items is not useful.
---
 emacs/notmuch-query.el | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/emacs/notmuch-query.el b/emacs/notmuch-query.el
index d1daffc..f83ca9b 100644
--- a/emacs/notmuch-query.el
+++ b/emacs/notmuch-query.el
@@ -21,7 +21,8 @@
 
 (require 'notmuch-lib)
 
-(defun notmuch-query-get-threads (search-terms)
+(defun notmuch-query-get-threads (search-terms &optional exclude-entire-thread
+					       exclude-body)
   "Return a list of threads of messages matching SEARCH-TERMS.
 
 A thread is a forest or list of trees. A tree is a two element
@@ -31,6 +32,10 @@ is a possibly empty forest of replies.
   (let ((args '("show" "--format=sexp" "--format-version=1")))
     (if notmuch-show-process-crypto
 	(setq args (append args '("--decrypt"))))
+    (if exclude-entire-thread
+	(setq args (append args '("--entire-thread=false"))))
+    (if exclude-body
+	(setq args (append args '("--body=false"))))
     (setq args (append args search-terms))
     (apply #'notmuch-call-notmuch-sexp args)))
 
-- 
1.8.5.2 (Apple Git-48)

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v1 2/3] emacs: Add `notmuch-user-all-email'.
  2014-09-05 12:59 [PATCH v1 0/3] Address completion entirely in elisp David Edmondson
  2014-09-05 12:59 ` [PATCH v1 1/3] emacs: Allow query to exclude the entire thread and body David Edmondson
@ 2014-09-05 12:59 ` David Edmondson
  2014-09-05 12:59 ` [PATCH v1 3/3] emacs: Add an address completer in elisp David Edmondson
  2014-09-05 15:29 ` [PATCH v1 0/3] Address completion entirely " Mark Walters
  3 siblings, 0 replies; 8+ messages in thread
From: David Edmondson @ 2014-09-05 12:59 UTC (permalink / raw)
  To: notmuch

`notmuch-user-all-email' returns a list of the user's primary and
secondary email addresses.
---
 emacs/notmuch-lib.el | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el
index 19269e3..685469c 100644
--- a/emacs/notmuch-lib.el
+++ b/emacs/notmuch-lib.el
@@ -228,6 +228,10 @@ on the command line, and then retry your notmuch command")))
   "Return the user.other_email value (as a list) from the notmuch configuration."
   (split-string (notmuch-config-get "user.other_email") "\n" t))
 
+(defun notmuch-user-all-email ()
+  "Return all email addresses for the user from the notmuch configuration."
+  (append (list (notmuch-user-primary-email)) (notmuch-user-other-email)))
+
 (defun notmuch-poll ()
   "Run \"notmuch new\" or an external script to import mail.
 
-- 
1.8.5.2 (Apple Git-48)

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v1 3/3] emacs: Add an address completer in elisp.
  2014-09-05 12:59 [PATCH v1 0/3] Address completion entirely in elisp David Edmondson
  2014-09-05 12:59 ` [PATCH v1 1/3] emacs: Allow query to exclude the entire thread and body David Edmondson
  2014-09-05 12:59 ` [PATCH v1 2/3] emacs: Add `notmuch-user-all-email' David Edmondson
@ 2014-09-05 12:59 ` David Edmondson
  2014-09-05 15:29 ` [PATCH v1 0/3] Address completion entirely " Mark Walters
  3 siblings, 0 replies; 8+ messages in thread
From: David Edmondson @ 2014-09-05 12:59 UTC (permalink / raw)
  To: notmuch

Rather than relying on an external comment to provide address
completion in composition mode, provide a solution purely in elisp.

Update `notmuch-address-command' to allow it to specify an external
command or a function, with the default remaining as an external
command called "notmuch-addresses".
---
 emacs/notmuch-address.el | 49 +++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 44 insertions(+), 5 deletions(-)

diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el
index fa65cd5..449fa54 100644
--- a/emacs/notmuch-address.el
+++ b/emacs/notmuch-address.el
@@ -24,10 +24,17 @@
 ;;
 
 (defcustom notmuch-address-command "notmuch-addresses"
-  "The command which generates possible addresses. It must take a
-single argument and output a list of possible matches, one per
-line."
-  :type 'string
+  "Command or function which generates possible addresses.
+
+A command must take a single argument and output a list of
+possible matches, one per line.
+
+A function must take a single argument and return a list of
+possible matches."
+  :type '(choice (string :tag "External command")
+		 (function :tag "Standard function"
+			   :value notmuch-address-option-generator)
+		 (function :tag "Custom function"))
   :group 'notmuch-send
   :group 'notmuch-external)
 
@@ -42,6 +49,32 @@ to know how address selection is made by default."
   :group 'notmuch-send
   :group 'notmuch-external)
 
+(defun notmuch-address-extractor (message)
+  "Return a list of addresses mentioned in `message'."
+  (let* ((headers (plist-get message :headers))
+	 (from (plist-get headers :From)))
+    from))
+
+(defun notmuch-address-option-generator (initial)
+  "Generate a set of possible address completions for `initial'."
+  (let* ((my-addresses (notmuch-user-all-email))
+	 (query (list (format "(%s) AND from:%s*"
+			      (mapconcat (lambda (a) (concat "to:" a))
+					 my-addresses " OR ")
+			      initial)))
+	 bare-results
+	 results)
+    (dolist (address
+	     (notmuch-query-map-threads 'notmuch-address-extractor
+					(notmuch-query-get-threads query t t)))
+      (when address
+	(let ((bare-address (cadr (std11-extract-address-components address))))
+	  (unless (or (member bare-address my-addresses)
+		      (member bare-address bare-results))
+	    (push bare-address bare-results)
+	    (push address results)))))
+    results))
+
 (defun notmuch-address-selection-function (prompt collection initial-input)
   "Call (`completing-read'
       PROMPT COLLECTION nil nil INITIAL-INPUT 'notmuch-address-history)"
@@ -60,7 +93,13 @@ to know how address selection is made by default."
 	  (push notmuch-address-message-alist-member message-completion-alist))))
 
 (defun notmuch-address-options (original)
-  (process-lines notmuch-address-command original))
+  (cond
+   ((stringp notmuch-address-command)
+    (process-lines notmuch-address-command original))
+   ((functionp notmuch-address-command)
+    (funcall notmuch-address-command original))
+   (t
+    (error "No address completion mechanism defined."))))
 
 (defun notmuch-address-expand-name ()
   (let* ((end (point))
-- 
1.8.5.2 (Apple Git-48)

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH v1 0/3] Address completion entirely in elisp.
  2014-09-05 12:59 [PATCH v1 0/3] Address completion entirely in elisp David Edmondson
                   ` (2 preceding siblings ...)
  2014-09-05 12:59 ` [PATCH v1 3/3] emacs: Add an address completer in elisp David Edmondson
@ 2014-09-05 15:29 ` Mark Walters
  2014-09-05 16:06   ` David Edmondson
  3 siblings, 1 reply; 8+ messages in thread
From: Mark Walters @ 2014-09-05 15:29 UTC (permalink / raw)
  To: David Edmondson, notmuch


Hi

On Fri, 05 Sep 2014, David Edmondson <dme@dme.org> wrote:
> Address completion entirely in elisp.
>
> I grew frustrated with having to use an external command to provide
> address completion, as they all had annoyances (up front scanning,
> requiring python bindings, etc.). This is an attempt to provide
> something similar to jkr's notmuch-addresses.py (which I was
> previously using) entirely in elisp, relying only on the `notmuch'
> command.

Just a few quick comments: the first is relevant to others trying this
patch.

1) You seem to be missing a (require 'std11) somewhere. I did this via M-:
and then it ran fine.

2) It is not quick on a spinning rust disk. This may not be relevant as
the delay is probably notmuch so would also be the case if I were using
notmuch-addresses.py (i normally just use a trivial script that parses
my .mailrc)

3) Have you tried
id:1407771091-12651-1-git-send-email-sojkam1@fel.cvut.cz and do you have
any comments on the comparison?

4) Finally, I wonder if we would be worth approaching the backend
notmuch use slightly differently: if we added a
notmuch_messages_collect_from function which was very similar to
notmuch_messages_collect_tags, and added a corresponding --output=from
to notmuch search then you would get the information you need very
quickly. I think it might be a lot faster as I think the from header is
stored in the database but some other headers are not, so that the
current method the show --body=false needs to look at the actually
messages

I should emphasise that none of the above means I am opposed to the
patch: having respectable built in address-completion support would be
very nice.

Best wishes

Mark




>
>
> David Edmondson (3):
>   emacs: Allow query to exclude the entire thread and body.
>   emacs: Add `notmuch-user-all-email'.
>   emacs: Add an address completer in elisp.
>
>  emacs/notmuch-address.el | 49 +++++++++++++++++++++++++++++++++++++++++++-----
>  emacs/notmuch-lib.el     |  4 ++++
>  emacs/notmuch-query.el   |  7 ++++++-
>  3 files changed, 54 insertions(+), 6 deletions(-)
>
> -- 
> 1.8.5.2 (Apple Git-48)
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v1 0/3] Address completion entirely in elisp.
  2014-09-05 15:29 ` [PATCH v1 0/3] Address completion entirely " Mark Walters
@ 2014-09-05 16:06   ` David Edmondson
  2014-09-06  8:14     ` [PATCH] cli: add --output=address-{from,to,all} to notmuch search Jani Nikula
  0 siblings, 1 reply; 8+ messages in thread
From: David Edmondson @ 2014-09-05 16:06 UTC (permalink / raw)
  To: Mark Walters, notmuch

On Fri, Sep 05 2014, Mark Walters wrote:
> On Fri, 05 Sep 2014, David Edmondson <dme@dme.org> wrote:
>> Address completion entirely in elisp.
>>
>> I grew frustrated with having to use an external command to provide
>> address completion, as they all had annoyances (up front scanning,
>> requiring python bindings, etc.). This is an attempt to provide
>> something similar to jkr's notmuch-addresses.py (which I was
>> previously using) entirely in elisp, relying only on the `notmuch'
>> command.
>
> Just a few quick comments: the first is relevant to others trying this
> patch.
>
> 1) You seem to be missing a (require 'std11) somewhere. I did this via M-:
> and then it ran fine.

My apologies. Will fix (and a compiler warning at the same time).

> 2) It is not quick on a spinning rust disk. This may not be relevant as
> the delay is probably notmuch so would also be the case if I were using
> notmuch-addresses.py (i normally just use a trivial script that parses
> my .mailrc)

It's not always as fast as I would like on SSD either :-) The mechanism
is very similar to the equivalent Python program, so I think that it's
probably about the same.

> 3) Have you tried
> id:1407771091-12651-1-git-send-email-sojkam1@fel.cvut.cz and do you have
> any comments on the comparison?

No, I will dig it out and look.

> 4) Finally, I wonder if we would be worth approaching the backend
> notmuch use slightly differently: if we added a
> notmuch_messages_collect_from function which was very similar to
> notmuch_messages_collect_tags, and added a corresponding --output=from
> to notmuch search then you would get the information you need very
> quickly. I think it might be a lot faster as I think the from header is
> stored in the database but some other headers are not, so that the
> current method the show --body=false needs to look at the actually
> messages

Extending notmuch to help with this was next on my list of things to
do. At the moment I just needed a solution that worked.

> I should emphasise that none of the above means I am opposed to the
> patch: having respectable built in address-completion support would be
> very nice.

Cool, thanks!

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v1 0/3] Address completion entirely in elisp.
@ 2014-09-06  1:36 Trevor Jim
  0 siblings, 0 replies; 8+ messages in thread
From: Trevor Jim @ 2014-09-06  1:36 UTC (permalink / raw)
  To: David Edmondson; +Cc: notmuch

[-- Attachment #1: Type: text/plain, Size: 245 bytes --]

You might want to take a look at the elisp-only completion I wrote for nevermore.
Address harvesting is pretty fast and anyway is asynchronous.

https://github.com/tjim/nevermore/blob/42febedb45da6158e26503359f864f772d332de0/nm.el#L561


[-- Attachment #2.1: Type: text/html, Size: 395 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH] cli: add --output=address-{from,to,all} to notmuch search
  2014-09-05 16:06   ` David Edmondson
@ 2014-09-06  8:14     ` Jani Nikula
  0 siblings, 0 replies; 8+ messages in thread
From: Jani Nikula @ 2014-09-06  8:14 UTC (permalink / raw)
  To: notmuch, David Edmondson, Mark Walters

address-from prints reply-to or from, address-to prints to, cc, and
bcc, and address-all prints all of them.

---

Mark, David -

I wrote most of this almost two years ago, but wasn't really happy
with it. There's address deduplication, but for large result sets that
might use lots of memory. Maybe the --duplicate option could be
overloaded for doing or not doing deduplication. I'd like to have some
way of picking the prettiest (that's subjective too) name part to go
with the address, now it's just the first encountered. And so on.

But maybe this will be useful for you, and you can pick some ideas. I
won't have the time to do much on this.

Cheers,
Jani.
---
 notmuch-search.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 113 insertions(+), 1 deletion(-)

diff --git a/notmuch-search.c b/notmuch-search.c
index bc9be4593ecc..33da90eaceec 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -26,6 +26,9 @@ typedef enum {
     OUTPUT_SUMMARY,
     OUTPUT_THREADS,
     OUTPUT_MESSAGES,
+    OUTPUT_ADDRESS_FROM,
+    OUTPUT_ADDRESS_TO,
+    OUTPUT_ADDRESS_ALL,
     OUTPUT_FILES,
     OUTPUT_TAGS
 } output_t;
@@ -214,6 +217,78 @@ do_search_threads (sprinter_t *format,
     return 0;
 }
 
+static void
+print_address_list (sprinter_t *format, GHashTable *addrs,
+		    InternetAddressList *list)
+{
+    InternetAddress *address;
+    int i;
+
+    for (i = 0; i < internet_address_list_length (list); i++) {
+	address = internet_address_list_get_address (list, i);
+	if (INTERNET_ADDRESS_IS_GROUP (address)) {
+	    InternetAddressGroup *group;
+	    InternetAddressList *group_list;
+
+	    group = INTERNET_ADDRESS_GROUP (address);
+	    group_list = internet_address_group_get_members (group);
+	    if (group_list == NULL)
+		continue;
+
+	    print_address_list (format, addrs, group_list);
+	} else {
+	    InternetAddressMailbox *mailbox;
+	    const char *name;
+	    const char *addr;
+	    char *full_address;
+
+	    mailbox = INTERNET_ADDRESS_MAILBOX (address);
+
+	    name = internet_address_get_name (address);
+	    addr = internet_address_mailbox_get_addr (mailbox);
+
+	    if (g_hash_table_lookup_extended (addrs, addr, NULL, NULL))
+		continue;
+
+	    g_hash_table_insert (addrs, talloc_strdup (NULL, addr), NULL);
+
+	    if (name && *name)
+		full_address = talloc_asprintf (NULL, "%s <%s>", name, addr);
+	    else
+		full_address = talloc_asprintf (NULL, "<%s>", addr);
+
+	    if (!full_address)
+		break;
+
+	    format->string (format, full_address);
+	    format->separator (format);
+
+	    talloc_free (full_address);
+	}
+    }
+}
+
+static void
+print_address_string (sprinter_t *format, GHashTable *addrs, const char *recipients)
+{
+    InternetAddressList *list;
+
+    if (recipients == NULL)
+	return;
+
+    list = internet_address_list_parse_string (recipients);
+    if (list == NULL)
+	return;
+
+    print_address_list (format, addrs, list);
+}
+
+static void
+_my_talloc_free_for_g_hash (void *ptr)
+{
+    talloc_free (ptr);
+}
+
 static int
 do_search_messages (sprinter_t *format,
 		    notmuch_query_t *query,
@@ -225,8 +300,14 @@ do_search_messages (sprinter_t *format,
     notmuch_message_t *message;
     notmuch_messages_t *messages;
     notmuch_filenames_t *filenames;
+    GHashTable *addresses = NULL;
     int i;
 
+    if (output == OUTPUT_ADDRESS_FROM || output == OUTPUT_ADDRESS_TO ||
+	output == OUTPUT_ADDRESS_ALL)
+	addresses = g_hash_table_new_full (g_str_hash, g_str_equal,
+				       _my_talloc_free_for_g_hash, NULL);
+
     if (offset < 0) {
 	offset += notmuch_query_count_messages (query);
 	if (offset < 0)
@@ -264,16 +345,41 @@ do_search_messages (sprinter_t *format,
 	    
 	    notmuch_filenames_destroy( filenames );
 
-	} else { /* output == OUTPUT_MESSAGES */
+	} else if (output == OUTPUT_MESSAGES) {
 	    format->set_prefix (format, "id");
 	    format->string (format,
 			    notmuch_message_get_message_id (message));
 	    format->separator (format);
+	} else {
+	    if (output == OUTPUT_ADDRESS_FROM || output == OUTPUT_ADDRESS_ALL) {
+		const char *addrs;
+
+		addrs = notmuch_message_get_header (message, "reply-to");
+
+		if (addrs == NULL || *addrs == '\0')
+		    addrs = notmuch_message_get_header (message, "from");
+
+		print_address_string (format, addresses, addrs);
+	    }
+
+	    if (output == OUTPUT_ADDRESS_TO || output == OUTPUT_ADDRESS_ALL) {
+		const char *hdrs[] = { "to", "cc", "bcc" };
+		const char *addrs;
+		size_t j;
+
+		for (j = 0; j < ARRAY_SIZE (hdrs); j++) {
+		    addrs = notmuch_message_get_header (message, hdrs[j]);
+		    print_address_string (format, addresses, addrs);
+		}
+	    }
 	}
 
 	notmuch_message_destroy (message);
     }
 
+    if (addresses)
+	g_hash_table_unref (addresses);
+
     notmuch_messages_destroy (messages);
 
     format->end (format);
@@ -368,6 +474,9 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 	  (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY },
 				  { "threads", OUTPUT_THREADS },
 				  { "messages", OUTPUT_MESSAGES },
+				  { "address-from", OUTPUT_ADDRESS_FROM },
+				  { "address-to", OUTPUT_ADDRESS_TO },
+				  { "address-all", OUTPUT_ADDRESS_ALL },
 				  { "files", OUTPUT_FILES },
 				  { "tags", OUTPUT_TAGS },
 				  { 0, 0 } } },
@@ -459,6 +568,9 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 	ret = do_search_threads (format, query, sort, output, offset, limit);
 	break;
     case OUTPUT_MESSAGES:
+    case OUTPUT_ADDRESS_FROM:
+    case OUTPUT_ADDRESS_TO:
+    case OUTPUT_ADDRESS_ALL:
     case OUTPUT_FILES:
 	ret = do_search_messages (format, query, output, offset, limit, dupe);
 	break;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2014-09-06  8:14 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-09-05 12:59 [PATCH v1 0/3] Address completion entirely in elisp David Edmondson
2014-09-05 12:59 ` [PATCH v1 1/3] emacs: Allow query to exclude the entire thread and body David Edmondson
2014-09-05 12:59 ` [PATCH v1 2/3] emacs: Add `notmuch-user-all-email' David Edmondson
2014-09-05 12:59 ` [PATCH v1 3/3] emacs: Add an address completer in elisp David Edmondson
2014-09-05 15:29 ` [PATCH v1 0/3] Address completion entirely " Mark Walters
2014-09-05 16:06   ` David Edmondson
2014-09-06  8:14     ` [PATCH] cli: add --output=address-{from,to,all} to notmuch search Jani Nikula
  -- strict thread matches above, loose matches on Subject: below --
2014-09-06  1:36 [PATCH v1 0/3] Address completion entirely in elisp Trevor Jim

Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).