diff --git a/notmuch-client.h b/notmuch-client.h index 50a30fe..eb5572f 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -123,6 +123,9 @@ notmuch_tag_command (void *ctx, int argc, char *argv[]); int notmuch_search_tags_command (void *ctx, int argc, char *argv[]); +int +notmuch_search_headers_command (void *ctx, int argc, char *argv[]); + const char * notmuch_time_relative_date (const void *ctx, time_t then); diff --git a/notmuch-search-tags.c b/notmuch-search-tags.c index 7a1305e..32e5afc 100644 --- a/notmuch-search-tags.c +++ b/notmuch-search-tags.c @@ -96,3 +96,99 @@ error: if (db) notmuch_database_close (db); return 1; } + +/* TODO: only take headers that match regexp? */ +/* TODO: limit the max number of queries? */ +void * +notmuch_messages_print_headers (notmuch_messages_t *messages, const char *header) +{ + notmuch_tags_t *tags, *msg_tags; + notmuch_message_t *msg; + GHashTable *htable; + GList *keys, *l; + const char *tag; + const char *msg_header; + + + htable = g_hash_table_new_full (g_str_hash, g_str_equal, free, NULL); + + while ((msg = notmuch_messages_get (messages))) { + msg_header = notmuch_message_get_header (msg, header); + g_hash_table_insert (htable, xstrdup (msg_header), NULL); + notmuch_message_destroy (msg); + notmuch_messages_advance (messages); + } + + keys = g_hash_table_get_keys (htable); + + /* TODO: sort by alphabetical order? */ + for (l = keys; l; l = l->next) { + printf("%s\n", (char *)l->data); + } + + g_list_free (keys); + g_hash_table_destroy (htable); +} + +int +notmuch_search_headers_command (void *ctx, int argc, char *argv[]) +{ + notmuch_messages_t *msgs; + notmuch_tags_t *tags; + notmuch_config_t *config; + notmuch_database_t *db; + notmuch_query_t *query; + char *query_str; + + tags = NULL; + config = NULL; + db = NULL; + query = NULL; + + if ((config = notmuch_config_open (ctx, NULL, NULL)) == NULL) { + goto error; + } + + db = notmuch_database_open (notmuch_config_get_database_path (config), + NOTMUCH_DATABASE_MODE_READ_ONLY); + if (db == NULL) { + goto error; + } + + if (argc < 3) { + fprintf( stderr, "Wrong number of arguments\n"); + fprintf( stderr, "Arguments:
\n"); + goto error; + } + + const char *header = argv[0]; + const char *regexp = argv[1]; // Ignored for now. + + if(( query_str = query_string_from_args (ctx, argc-2, &argv[2])) == NULL) { + fprintf (stderr, "Out of memory.\n"); + goto error; + } + + if (*query_str == '\0') { + fprintf (stderr, "Error: Invalid search string.\n"); + goto error; + } + + if ((query = notmuch_query_create (db, query_str)) == NULL) { + fprintf (stderr, "Out of memory\n"); + goto error; + } + + msgs = notmuch_query_search_messages (query); + if ((notmuch_messages_print_headers (msgs, header)) == NULL) goto error; + + notmuch_query_destroy (query); + notmuch_database_close (db); + return 0; + +error: + if (tags) notmuch_tags_destroy (tags); + if (query) notmuch_query_destroy (query); + if (db) notmuch_database_close (db); + return 1; +} diff --git a/notmuch.c b/notmuch.c index 2ac8a59..0921906 100644 --- a/notmuch.c +++ b/notmuch.c @@ -284,6 +284,11 @@ command_t commands[] = { "\t\tcontain tags only from messages that match the search-term(s).\n" "\n" "\t\tIn both cases the list will be alphabetically sorted." }, + { "search-headers", notmuch_search_headers_command, + "search-headers
[...]", + "\t\tList all headers
matching in messages" + " matching " + }, { "help", notmuch_help_command, "[]", "\t\tThis message, or more detailed help for the named command.", diff --git a/notmuch.el b/notmuch.el index 97914f2..8d9a3f9 100644 --- a/notmuch.el +++ b/notmuch.el @@ -1286,7 +1286,8 @@ characters as well as `_.+-'. ;;;###autoload (defun notmuch-search (query &optional oldest-first) "Run \"notmuch search\" with the given query string and display results." - (interactive "sNotmuch search: ") + (interactive (list (completing-read "Notmuch-search: " + 'notmuch-search-compute-completion-function))) (let ((buffer (get-buffer-create (concat "*notmuch-search-" query "*")))) (switch-to-buffer buffer) (notmuch-search-mode) @@ -1494,3 +1495,46 @@ Currently available key bindings: (goto-line n)))) (provide 'notmuch) + +(defun notmuch-search-compute-completion-function (string predicate flag) + "Smart completion for notmuch. " + (let ((to-complete) + (prefix) + (completion-list)) + ;; First case: complete a header + (cond ((string-match "\\(.*\\<\\)\\(\\w+\\):\\(\".*?\\|\\w*?\\)$" string) + (setq header (match-string 2 string)) + (setq prefix (concat (match-string 1 string) (match-string 2 string) ":")) + (setq to-complete (match-string 3 string)) + (setq completion-list + (split-string + (with-output-to-string + (with-current-buffer standard-output + ;; XXX: We should add to the query string the what's already + ;; in the minibuffer; this requires a more complex parser + ;; to handle incomplete parenthesis, etc. + (apply 'call-process notmuch-command nil t nil + "search-headers" header "dummy" + ;(list notmuch-search-query-string) + ; XXX: notmuch-search-query-string is not accessible + ; from the minibuffer + (list "tag:inbox") + ))) + "\n+" t)) + ) + ;; Second case: complete a prefix + ((string-match "\\(.*\\<\\)\\(\\w*\\)$" string) + (setq to-complete (match-string 2 string)) + (setq completion-list (list "from:" "to:" "subject:\"" "attachment:\"" + "tag:" "id:" "and " "or ")) + (setq prefix (match-string 1 string)) + ) + ;; Nothing to complete. + (t (setq to-complete "") + (setq completion-list nil) + (setq prefix string))) + (case flag + (nil (concat prefix (try-completion to-complete completion-list))) + ('t (mapcar (lambda (string) (concat prefix string)) + (all-completions to-complete completion-list))) + ('lambda (test-completion to-complete completion-list)))))