]> git.notmuchmail.org Git - notmuch/blobdiff - notmuch.c
libify: Move library sources down into lib directory.
[notmuch] / notmuch.c
index f47e4fcbb65d6ddf805ecbedb07c4fa61a097861..923a7cee53eb366cd91ee822ffb8c7678e157621 100644 (file)
--- a/notmuch.c
+++ b/notmuch.c
@@ -23,6 +23,8 @@
 #endif
 #include <stdio.h>
 
 #endif
 #include <stdio.h>
 
+#include <gmime/gmime.h>
+
 #include "notmuch.h"
 
 /* This is separate from notmuch-private.h because we're trying to
 #include "notmuch.h"
 
 /* This is separate from notmuch-private.h because we're trying to
@@ -60,7 +62,7 @@
 
 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
 
 
 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
 
-typedef int (*command_function_t) (int argc, char *argv[]);
+typedef int (*command_function_t) (void *ctx, int argc, char *argv[]);
 
 typedef struct command {
     const char *name;
 
 typedef struct command {
     const char *name;
@@ -471,7 +473,7 @@ count_files (const char *path, int *count)
 }
 
 static int
 }
 
 static int
-setup_command (unused (int argc), unused (char *argv[]))
+setup_command (unused (void *ctx), unused (int argc), unused (char *argv[]))
 {
     notmuch_database_t *notmuch = NULL;
     char *default_path, *mail_directory = NULL;
 {
     notmuch_database_t *notmuch = NULL;
     char *default_path, *mail_directory = NULL;
@@ -531,7 +533,7 @@ setup_command (unused (int argc), unused (char *argv[]))
        free (default_path);
     }
 
        free (default_path);
     }
 
-    /* Coerce th directory into an absolute directory name. */
+    /* Coerce the directory into an absolute directory name. */
     if (*mail_directory != '/') {
        char *cwd, *absolute_mail_directory;
 
     if (*mail_directory != '/') {
        char *cwd, *absolute_mail_directory;
 
@@ -551,8 +553,6 @@ setup_command (unused (int argc), unused (char *argv[]))
        free (cwd);
        free (mail_directory);
        mail_directory = absolute_mail_directory;
        free (cwd);
        free (mail_directory);
        mail_directory = absolute_mail_directory;
-
-       printf ("Abs: %s\n", mail_directory);
     }
 
     notmuch = notmuch_database_create (mail_directory);
     }
 
     notmuch = notmuch_database_create (mail_directory);
@@ -629,7 +629,7 @@ tag_inbox_and_unread (notmuch_message_t *message)
 }
 
 static int
 }
 
 static int
-new_command (unused (int argc), unused (char *argv[]))
+new_command (unused (void *ctx), unused (int argc), unused (char *argv[]))
 {
     notmuch_database_t *notmuch;
     const char *mail_directory;
 {
     notmuch_database_t *notmuch;
     const char *mail_directory;
@@ -733,16 +733,93 @@ query_string_from_args (void *ctx, int argc, char *argv[])
     return query_string;
 }
 
     return query_string;
 }
 
+/* Format a nice representation of 'time' relative to the current time.
+ *
+ * Examples include:
+ *
+ *     5 mins. ago     (For times less than 60 minutes ago)
+ *     Today 12:30     (For times >60 minutes but still today)
+ *     Yest. 12:30
+ *     Mon.  12:30     (Before yesterday but fewer than 7 days ago)
+ *     October 12      (Between 7 and 180 days ago (about 6 months))
+ *     2008-06-30      (More than 180 days ago)
+ */
+#define MINUTE (60)
+#define HOUR (60 * MINUTE)
+#define DAY (24 * HOUR)
+#define RELATIVE_DATE_MAX 20
+static const char *
+_format_relative_date (void *ctx, time_t then)
+{
+    struct tm tm_now, tm_then;
+    time_t now = time(NULL);
+    time_t delta;
+    char *result;
+
+    localtime_r (&now, &tm_now);
+    localtime_r (&then, &tm_then);
+
+    result = talloc_zero_size (ctx, RELATIVE_DATE_MAX);
+    if (result == NULL)
+       return "when?";
+
+    if (then > now)
+       return "the future";
+
+    delta = now - then;
+
+    if (delta > 180 * DAY) {
+       strftime (result, RELATIVE_DATE_MAX,
+                 "%F", &tm_then); /* 2008-06-30 */
+       return result;
+    }
+
+    if (delta < 3600) {
+       snprintf (result, RELATIVE_DATE_MAX,
+                 "%d mins. ago", (int) (delta / 60));
+       return result;
+    }
+
+    if (delta <= 7 * DAY) {
+       if (tm_then.tm_wday == tm_now.tm_wday &&
+           delta < DAY)
+       {
+           strftime (result, RELATIVE_DATE_MAX,
+                     "Today %R", &tm_then); /* Today 12:30 */
+           return result;
+       } else if ((tm_now.tm_wday + 7 - tm_then.tm_wday) % 7 == 1) {
+           strftime (result, RELATIVE_DATE_MAX,
+                     "Yest. %R", &tm_then); /* Yest. 12:30 */
+           return result;
+       } else {
+           if (tm_then.tm_wday != tm_now.tm_wday) {
+               strftime (result, RELATIVE_DATE_MAX,
+                         "%a. %R", &tm_then); /* Mon. 12:30 */
+               return result;
+           }
+       }
+    }
+
+    strftime (result, RELATIVE_DATE_MAX,
+             "%B %d", &tm_then); /* October 12 */
+    return result;
+}
+#undef MINUTE
+#undef HOUR
+#undef DAY
+
 static int
 static int
-search_command (int argc, char *argv[])
+search_command (void *ctx, int argc, char *argv[])
 {
 {
-    void *local = talloc_new (NULL);
+    void *local = talloc_new (ctx);
     notmuch_database_t *notmuch = NULL;
     notmuch_query_t *query;
     notmuch_database_t *notmuch = NULL;
     notmuch_query_t *query;
-    notmuch_thread_results_t *results;
+    notmuch_threads_t *threads;
     notmuch_thread_t *thread;
     notmuch_tags_t *tags;
     char *query_str;
     notmuch_thread_t *thread;
     notmuch_tags_t *tags;
     char *query_str;
+    const char *relative_date;
+    time_t date;
     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
 
     notmuch = notmuch_database_open (NULL);
     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
 
     notmuch = notmuch_database_open (NULL);
@@ -760,16 +837,20 @@ search_command (int argc, char *argv[])
        goto DONE;
     }
 
        goto DONE;
     }
 
-    for (results = notmuch_query_search_threads (query);
-        notmuch_thread_results_has_more (results);
-        notmuch_thread_results_advance (results))
+    for (threads = notmuch_query_search_threads (query);
+        notmuch_threads_has_more (threads);
+        notmuch_threads_advance (threads))
     {
        int first = 1;
 
     {
        int first = 1;
 
-       thread = notmuch_thread_results_get (results);
+       thread = notmuch_threads_get (threads);
+
+       date = notmuch_thread_get_oldest_date (thread);
+       relative_date = _format_relative_date (local, date);
 
 
-       printf ("%s %s",
+       printf ("thread:%s %12s %s",
                notmuch_thread_get_thread_id (thread),
                notmuch_thread_get_thread_id (thread),
+               relative_date,
                notmuch_thread_get_subject (thread));
 
        printf (" (");
                notmuch_thread_get_subject (thread));
 
        printf (" (");
@@ -797,25 +878,206 @@ search_command (int argc, char *argv[])
     return ret;
 }
 
     return ret;
 }
 
+static const char *
+_get_tags_as_string (void *ctx, notmuch_message_t *message)
+{
+    notmuch_tags_t *tags;
+    int first = 1;
+    const char *tag;
+    char *result;
+
+    result = talloc_strdup (ctx, "");
+    if (result == NULL)
+       return NULL;
+
+    for (tags = notmuch_message_get_tags (message);
+        notmuch_tags_has_more (tags);
+        notmuch_tags_advance (tags))
+    {
+       tag = notmuch_tags_get (tags);
+
+       result = talloc_asprintf_append (result, "%s%s",
+                                        first ? "" : " ", tag);
+       first = 0;
+    }
+
+    return result;
+}
+
+/* Get a nice, single-line summary of message. */
+static const char *
+_get_one_line_summary (void *ctx, notmuch_message_t *message)
+{
+    const char *from;
+    time_t date;
+    const char *relative_date;
+    const char *tags;
+
+    from = notmuch_message_get_header (message, "from");
+
+    date = notmuch_message_get_date (message);
+    relative_date = _format_relative_date (ctx, date);
+
+    tags = _get_tags_as_string (ctx, message);
+
+    return talloc_asprintf (ctx, "%s (%s) (%s)",
+                           from, relative_date, tags);
+}
+
+static void
+show_message_part (GMimeObject *part, int *part_count)
+{
+    GMimeStream *stream;
+    GMimeDataWrapper *wrapper;
+    GMimeContentDisposition *disposition;
+    GMimeContentType *content_type;
+
+    *part_count = *part_count + 1;
+
+    if (GMIME_IS_MULTIPART (part)) {
+       GMimeMultipart *multipart = GMIME_MULTIPART (part);
+       int i;
+
+       for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
+           if (GMIME_IS_MULTIPART_SIGNED (multipart)) {
+               /* Don't index the signature. */
+               if (i == 1)
+                   continue;
+               if (i > 1)
+                   fprintf (stderr, "Warning: Unexpected extra parts of mutlipart/signed. Continuing.\n");
+           }
+           show_message_part (g_mime_multipart_get_part (multipart, i),
+                              part_count);
+       }
+       return;
+    }
+
+    if (GMIME_IS_MESSAGE_PART (part)) {
+       GMimeMessage *mime_message;
+
+       mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
+
+       show_message_part (g_mime_message_get_mime_part (mime_message),
+                          part_count);
+
+       return;
+    }
+
+    if (! (GMIME_IS_PART (part))) {
+       fprintf (stderr, "Warning: Not displaying unknown mime part: %s.\n",
+                g_type_name (G_OBJECT_TYPE (part)));
+       return;
+    }
+
+    disposition = g_mime_object_get_content_disposition (part);
+    if (disposition &&
+       strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
+    {
+       const char *filename = g_mime_part_get_filename (GMIME_PART (part));
+       content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
+
+       printf ("\fattachment{ ID: %d, Content-type: %s\n",
+               *part_count,
+               g_mime_content_type_to_string (content_type));
+       printf ("Attachment: %s (%s)\n", filename,
+               g_mime_content_type_to_string (content_type));
+       printf ("\fattachment}\n");
+
+       return;
+    }
+
+    content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
+
+    printf ("\fpart{ ID: %d, Content-type: %s\n",
+           *part_count,
+           g_mime_content_type_to_string (content_type));
+
+    if (g_mime_content_type_is_type (content_type, "text", "*") &&
+       !g_mime_content_type_is_type (content_type, "text", "html"))
+    {
+       stream = g_mime_stream_file_new (stdout);
+       g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE);
+
+       wrapper = g_mime_part_get_content_object (GMIME_PART (part));
+       if (wrapper)
+           g_mime_data_wrapper_write_to_stream (wrapper, stream);
+
+       g_object_unref (stream);
+    }
+    else
+    {
+       printf ("Non-text part: %s\n",
+               g_mime_content_type_to_string (content_type));
+    }
+
+    printf ("\fpart}\n");
+}
+
+static notmuch_status_t
+show_message_body (const char *filename)
+{
+    GMimeStream *stream = NULL;
+    GMimeParser *parser = NULL;
+    GMimeMessage *mime_message = NULL;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    static int initialized = 0;
+    FILE *file = NULL;
+    int part_count = 0;
+
+    if (! initialized) {
+       g_mime_init (0);
+       initialized = 1;
+    }
+
+    file = fopen (filename, "r");
+    if (! file) {
+       fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    stream = g_mime_stream_file_new (file);
+    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE);
+
+    parser = g_mime_parser_new_with_stream (stream);
+
+    mime_message = g_mime_parser_construct_message (parser);
+
+    show_message_part (g_mime_message_get_mime_part (mime_message),
+                      &part_count);
+
+  DONE:
+    if (mime_message)
+       g_object_unref (mime_message);
+
+    if (parser)
+       g_object_unref (parser);
+
+    if (stream)
+       g_object_unref (stream);
+
+    if (file)
+       fclose (file);
+
+    return ret;
+}
+
 static int
 static int
-show_command (unused (int argc), unused (char *argv[]))
+show_command (void *ctx, unused (int argc), unused (char *argv[]))
 {
 {
-    void *local = talloc_new (NULL);
+    void *local = talloc_new (ctx);
     char *query_string;
     notmuch_database_t *notmuch = NULL;
     notmuch_query_t *query = NULL;
     char *query_string;
     notmuch_database_t *notmuch = NULL;
     notmuch_query_t *query = NULL;
-    notmuch_message_results_t *messages;
+    notmuch_messages_t *messages;
     notmuch_message_t *message;
     notmuch_message_t *message;
-    const char *filename;
-    FILE *file;
     int ret = 0;
     int ret = 0;
-    int c;
 
 
-    if (argc != 1) {
-       fprintf (stderr, "Error: \"notmuch show\" requires exactly one thread-ID argument.\n");
-       ret = 1;
-       goto DONE;
-    }
+    const char *headers[] = {
+       "From", "To", "Cc", "Bcc", "Date"
+    };
+    const char *name, *value;
+    unsigned int i;
 
     notmuch = notmuch_database_open (NULL);
     if (notmuch == NULL) {
 
     notmuch = notmuch_database_open (NULL);
     if (notmuch == NULL) {
@@ -823,7 +1085,7 @@ show_command (unused (int argc), unused (char *argv[]))
        goto DONE;
     }
 
        goto DONE;
     }
 
-    query_string = talloc_asprintf (local, "thread:%s", argv[0]);
+    query_string = query_string_from_args (local, argc, argv);
     if (query_string == NULL) {
        fprintf (stderr, "Out of memory\n");
        ret = 1;
     if (query_string == NULL) {
        fprintf (stderr, "Out of memory\n");
        ret = 1;
@@ -838,35 +1100,36 @@ show_command (unused (int argc), unused (char *argv[]))
     }
 
     for (messages = notmuch_query_search_messages (query);
     }
 
     for (messages = notmuch_query_search_messages (query);
-        notmuch_message_results_has_more (messages);
-        notmuch_message_results_advance (messages))
+        notmuch_messages_has_more (messages);
+        notmuch_messages_advance (messages))
     {
     {
-       message = notmuch_message_results_get (messages);
-
-       printf ("%%message{\n");
+       message = notmuch_messages_get (messages);
 
 
-       printf ("%%header{\n");
+       printf ("\fmessage{ id:%s filename:%s\n",
+               notmuch_message_get_message_id (message),
+               notmuch_message_get_filename (message));
 
 
-       printf ("%s", notmuch_message_get_all_headers (message));
+       printf ("\fheader{\n");
 
 
-       printf ("%%header}\n");
+       printf ("%s\n", _get_one_line_summary (local, message));
 
 
-       filename = notmuch_message_get_filename (message);
+       printf ("%s\n", notmuch_message_get_header (message, "subject"));
 
 
-       file = fopen (filename, "r");
-       if (file) {
-           size_t header_size = notmuch_message_get_header_size (message);
-           fseek (file, header_size + 1, SEEK_SET);
-           while (1) {
-               c = fgetc (file);
-               if (c == EOF)
-                   break;
-               putchar (c);
-           }
+       for (i = 0; i < ARRAY_SIZE (headers); i++) {
+           name = headers[i];
+           value = notmuch_message_get_header (message, name);
+           if (value)
+               printf ("%s: %s\n", name, value);
        }
        }
-       fclose (file);
 
 
-       printf ("%%message}\n");
+       printf ("\fheader}\n");
+       printf ("\fbody{\n");
+
+       show_message_body (notmuch_message_get_filename (message));
+
+       printf ("\fbody}\n");
+
+       printf ("\fmessage}\n");
 
        notmuch_message_destroy (message);
     }
 
        notmuch_message_destroy (message);
     }
@@ -885,7 +1148,7 @@ show_command (unused (int argc), unused (char *argv[]))
 }
 
 static int
 }
 
 static int
-tag_command (unused (int argc), unused (char *argv[]))
+tag_command (void *ctx, unused (int argc), unused (char *argv[]))
 {
     void *local;
     int *add_tags, *remove_tags;
 {
     void *local;
     int *add_tags, *remove_tags;
@@ -894,12 +1157,12 @@ tag_command (unused (int argc), unused (char *argv[]))
     char *query_string;
     notmuch_database_t *notmuch = NULL;
     notmuch_query_t *query;
     char *query_string;
     notmuch_database_t *notmuch = NULL;
     notmuch_query_t *query;
-    notmuch_message_results_t *results;
+    notmuch_messages_t *messages;
     notmuch_message_t *message;
     int ret = 0;
     int i;
 
     notmuch_message_t *message;
     int ret = 0;
     int i;
 
-    local = talloc_new (NULL);
+    local = talloc_new (ctx);
     if (local == NULL) {
        ret = 1;
        goto DONE;
     if (local == NULL) {
        ret = 1;
        goto DONE;
@@ -958,11 +1221,11 @@ tag_command (unused (int argc), unused (char *argv[]))
        goto DONE;
     }
 
        goto DONE;
     }
 
-    for (results = notmuch_query_search_messages (query);
-        notmuch_message_results_has_more (results);
-        notmuch_message_results_advance (results))
+    for (messages = notmuch_query_search_messages (query);
+        notmuch_messages_has_more (messages);
+        notmuch_messages_advance (messages))
     {
     {
-       message = notmuch_message_results_get (results);
+       message = notmuch_messages_get (messages);
 
        notmuch_message_freeze (message);
 
 
        notmuch_message_freeze (message);
 
@@ -990,12 +1253,12 @@ tag_command (unused (int argc), unused (char *argv[]))
 }
 
 static int
 }
 
 static int
-dump_command (int argc, char *argv[])
+dump_command (unused (void *ctx), int argc, char *argv[])
 {
     FILE *output = NULL;
     notmuch_database_t *notmuch = NULL;
     notmuch_query_t *query;
 {
     FILE *output = NULL;
     notmuch_database_t *notmuch = NULL;
     notmuch_query_t *query;
-    notmuch_message_results_t *results;
+    notmuch_messages_t *messages;
     notmuch_message_t *message;
     notmuch_tags_t *tags;
     int ret = 0;
     notmuch_message_t *message;
     notmuch_tags_t *tags;
     int ret = 0;
@@ -1027,12 +1290,12 @@ dump_command (int argc, char *argv[])
 
     notmuch_query_set_sort (query, NOTMUCH_SORT_MESSAGE_ID);
 
 
     notmuch_query_set_sort (query, NOTMUCH_SORT_MESSAGE_ID);
 
-    for (results = notmuch_query_search_messages (query);
-        notmuch_message_results_has_more (results);
-        notmuch_message_results_advance (results))
+    for (messages = notmuch_query_search_messages (query);
+        notmuch_messages_has_more (messages);
+        notmuch_messages_advance (messages))
     {
        int first = 1;
     {
        int first = 1;
-       message = notmuch_message_results_get (results);
+       message = notmuch_messages_get (messages);
 
        fprintf (output,
                 "%s (", notmuch_message_get_message_id (message));
 
        fprintf (output,
                 "%s (", notmuch_message_get_message_id (message));
@@ -1066,7 +1329,7 @@ dump_command (int argc, char *argv[])
 }
 
 static int
 }
 
 static int
-restore_command (int argc, char *argv[])
+restore_command (unused (void *ctx), int argc, char *argv[])
 {
     FILE *input = NULL;
     notmuch_database_t *notmuch = NULL;
 {
     FILE *input = NULL;
     notmuch_database_t *notmuch = NULL;
@@ -1175,7 +1438,7 @@ restore_command (int argc, char *argv[])
 }
 
 static int
 }
 
 static int
-help_command (int argc, char *argv[]);
+help_command (void *ctx, int argc, char *argv[]);
 
 command_t commands[] = {
     { "setup", setup_command,
 
 command_t commands[] = {
     { "setup", setup_command,
@@ -1193,7 +1456,7 @@ command_t commands[] = {
       "\t\t\"inbox\" and \"unread\".\n"
       "\n"
       "\t\tNote: \"notmuch new\" will skip any read-only directories,\n"
       "\t\t\"inbox\" and \"unread\".\n"
       "\n"
       "\t\tNote: \"notmuch new\" will skip any read-only directories,\n"
-      "\t\tso you can use that to mark tdirectories that will not\n"
+      "\t\tso you can use that to mark directories that will not\n"
       "\t\treceive any new mail (and make \"notmuch new\" faster)." },
     { "search", search_command,
       "<search-term> [...]\n\n"
       "\t\treceive any new mail (and make \"notmuch new\" faster)." },
     { "search", search_command,
       "<search-term> [...]\n\n"
@@ -1201,11 +1464,24 @@ command_t commands[] = {
       "\t\tNote that the individual mail messages will be matched\n"
       "\t\tagainst the search terms, but the results will be the\n"
       "\t\tthreads containing the matched messages.\n\n"
       "\t\tNote that the individual mail messages will be matched\n"
       "\t\tagainst the search terms, but the results will be the\n"
       "\t\tthreads containing the matched messages.\n\n"
-      "\t\tCurrently, the supported search terms are as follows, (where\n"
-      "\t\t<brackets> indicate user-supplied values):\n\n"
+      "\t\tCurrently, in addition to free text (and quoted phrases)\n"
+      "\t\twhich match terms appearing anywhere within an email,\n"
+      "\t\tthe following prefixes can be used to search specific\n"
+      "\t\tportions of an email, (where <brackets> indicate user-\n"
+      "\t\tsupplied values):\n\n"
+      "\t\t\tfrom:<name-or-address>\n"
+      "\t\t\tto:<name-or-address>\n"
+      "\t\t\tsubject:<word-or-quoted-phrase>\n"
       "\t\t\ttag:<tag>\n"
       "\t\t\tid:<message-id>\n"
       "\t\t\tthread:<thread-id>\n\n"
       "\t\t\ttag:<tag>\n"
       "\t\t\tid:<message-id>\n"
       "\t\t\tthread:<thread-id>\n\n"
+      "\t\tThe from: prefix is used to match the name or address of\n"
+      "\t\tthe sender of an email message.\n\n"
+      "\t\tThe to: prefix is used to match the names or addresses of\n"
+      "\t\tany recipient of an email message, (whether To, Cc, or Bcc).\n\n"
+      "\t\tAny term prefixed with subject: will match only text from\n"
+      "\t\tthe subject of an email. Quoted phrases are supported when\n"
+      "\t\tsearching with: subject:\"this is a phrase\".\n\n"
       "\t\tValid tag values include \"inbox\" and \"unread\" by default\n"
       "\t\tfor new messages added by \"notmuch new\" as well as any other\n"
       "\t\ttag values added manually with \"notmuch tag\".\n\n"
       "\t\tValid tag values include \"inbox\" and \"unread\" by default\n"
       "\t\tfor new messages added by \"notmuch new\" as well as any other\n"
       "\t\ttag values added manually with \"notmuch tag\".\n\n"
@@ -1224,11 +1500,22 @@ command_t commands[] = {
       "\t\tinterpretation by the shell, (such as by putting quotation\n"
       "\t\tmarks around any parenthesized expression)." },
     { "show", show_command,
       "\t\tinterpretation by the shell, (such as by putting quotation\n"
       "\t\tmarks around any parenthesized expression)." },
     { "show", show_command,
-      "<thread-id>\n\n"
-      "\t\tShow the thread with the given thread ID (see 'search').",
-      "\t\tThread ID values are given as the first column in the\n"
-      "\t\toutput of the \"notmuch search\" command. These are the\n"
-      "\t\trandom-looking strings of 32 characters." },
+      "<search-terms> [...]\n\n"
+      "\t\tShows all messages matching the search terms.",
+      "\t\tSee the documentation of \"notmuch search\" for details\n"
+      "\t\tof the supported syntax of search terms.\n\n"
+      "\t\tA common use of \"notmuch show\" is to display a single\n"
+      "\t\tthread of email messages. For this, use a search term of\n"
+      "\t\t\"thread:<thread-id>\" as can be seen in the first column\n"
+      "\t\tof output from the \"notmuch search\" command.\n\n"
+      "\t\tAll messages will be displayed in date order. The output\n"
+      "\t\tformat is plain-text, with all text-content MIME parts\n"
+      "\t\tdecoded. Various components in the output, ('message',\n"
+      "\t\t'header', 'body', 'attachment', and MIME 'part') will be\n"
+      "\t\tdelimited by easily-parsed markers. Each marker consists\n"
+      "\t\tof a Control-L character (ASCII decimal 12), the name of\n"
+      "\t\tthe marker, and then either an opening or closing brace,\n"
+      "\t\t'{' or '}' to either open or close the component."},
     { "tag", tag_command,
       "+<tag>|-<tag> [...] [--] <search-term> [...]\n\n"
       "\t\tAdd/remove tags for all messages matching the search terms.",
     { "tag", tag_command,
       "+<tag>|-<tag> [...] [--] <search-term> [...]\n\n"
       "\t\tAdd/remove tags for all messages matching the search terms.",
@@ -1292,7 +1579,7 @@ usage (void)
 }
 
 static int
 }
 
 static int
-help_command (int argc, char *argv[])
+help_command (unused (void *ctx), int argc, char *argv[])
 {
     command_t *command;
     unsigned int i;
 {
     command_t *command;
     unsigned int i;
@@ -1323,23 +1610,26 @@ help_command (int argc, char *argv[])
 int
 main (int argc, char *argv[])
 {
 int
 main (int argc, char *argv[])
 {
+    void *local = talloc_new (NULL);
     command_t *command;
     unsigned int i;
 
     if (argc == 1)
     command_t *command;
     unsigned int i;
 
     if (argc == 1)
-       return setup_command (0, NULL);
+       return setup_command (local, 0, NULL);
 
     for (i = 0; i < ARRAY_SIZE (commands); i++) {
        command = &commands[i];
 
        if (strcmp (argv[1], command->name) == 0)
 
     for (i = 0; i < ARRAY_SIZE (commands); i++) {
        command = &commands[i];
 
        if (strcmp (argv[1], command->name) == 0)
-           return (command->function) (argc - 2, &argv[2]);
+           return (command->function) (local, argc - 2, &argv[2]);
     }
 
     /* Don't complain about "help" being an unknown command when we're
        about to provide exactly what's wanted anyway. */
     }
 
     /* Don't complain about "help" being an unknown command when we're
        about to provide exactly what's wanted anyway. */
-    fprintf (stderr, "Error: Unknown command '%s'\n\n", argv[1]);
-    usage ();
+    fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",
+            argv[1]);
+
+    talloc_free (local);
 
     return 1;
 }
 
     return 1;
 }