merge changes from upstream
authorJameson Graef Rollins <jrollins@finestructure.net>
Sat, 5 Dec 2009 06:19:53 +0000 (01:19 -0500)
committerJameson Graef Rollins <jrollins@finestructure.net>
Sat, 5 Dec 2009 06:20:11 +0000 (01:20 -0500)
26 files changed:
.gitignore
Makefile
Makefile.local
TODO
compat/Makefile [new file with mode: 0644]
compat/Makefile.local [new file with mode: 0644]
compat/compat.h [new file with mode: 0644]
compat/getdelim.c [new file with mode: 0644]
compat/getline.c [new file with mode: 0644]
config/README [new file with mode: 0644]
config/have_getline.c [new file with mode: 0644]
configure
lib/index.cc
lib/notmuch-private.h
lib/xutil.c
notmuch-client.h
notmuch-config.c
notmuch-new.c
notmuch-reply.c
notmuch-setup.c
notmuch-show.c
notmuch-tag.c
notmuch.1
notmuch.c
notmuch.el
vim/plugin/notmuch.vim

index 8794354e547115484fd0fd2d99ae2496d88c9cda..efa98fbbe03db365f815fa9c1107f792af5172b0 100644 (file)
@@ -1,3 +1,4 @@
+Makefile.config
 TAGS
 tags
 *cscope*
index 2cd1b1bae4af648b07b51a110e08f623868fa66e..021fdb82a06d60dff275e1d52ad324525b78321c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,5 @@
-# Default FLAGS, (can be overridden by user such as "make CFLAGS=-O2")
-WARN_FLAGS=-Wall -Wextra -Wmissing-declarations -Wwrite-strings -Wswitch-enum
-CFLAGS=-O2
+WARN_CXXFLAGS=-Wall -Wextra -Wwrite-strings -Wswitch-enum
+WARN_CFLAGS=$(WARN_CXXFLAGS) -Wmissing-declarations
 
 # Additional programs that are used during the compilation process.
 EMACS ?= emacs
@@ -8,66 +7,69 @@ EMACS ?= emacs
 # arguments to gzip.
 gzip = gzip
 
-# Additional flags that we will append to whatever the user set.
-# These aren't intended for the user to manipulate.
-extra_cflags := $(shell pkg-config --cflags glib-2.0 gmime-2.4 talloc)
-extra_cxxflags := $(shell xapian-config --cxxflags)
-
-emacs_lispdir := $(shell pkg-config emacs --variable sitepkglispdir)
-# Hard-code if this system doesn't have an emacs.pc file
-ifeq ($(emacs_lispdir),)
-       emacs_lispdir = $(prefix)/share/emacs/site-lisp
-endif
+bash_completion_dir = /etc/bash_completion.d
 
 all_deps = Makefile Makefile.local Makefile.config \
                   lib/Makefile lib/Makefile.local
 
+extra_cflags :=
+extra_cxxflags :=
+
 # Now smash together user's values with our extra values
-override CFLAGS += $(WARN_FLAGS) $(extra_cflags)
-override CXXFLAGS += $(WARN_FLAGS) $(extra_cflags) $(extra_cxxflags)
+FINAL_CFLAGS = $(CFLAGS) $(WARN_CFLAGS) $(CONFIGURE_CFLAGS) $(extra_cflags)
+FINAL_CXXFLAGS = $(CXXFLAGS) $(WARN_CXXFLAGS) $(CONFIGURE_CXXFLAGS) $(extra_cflags) $(extra_cxxflags)
+FINAL_LDFLAGS = $(LDFLAGS) $(CONFIGURE_LDFLAGS)
+
+all: notmuch notmuch.1.gz
+
+# Before including any other Makefile fragments, get settings from the
+# output of configure
+Makefile.config: configure
+       @echo ""
+       @echo "Note: Calling ./configure with no command-line arguments. This is often fine,"
+       @echo "      but if you want to specify any arguments (such as an alternate prefix"
+       @echo "      into which to install), call ./configure explicitly and then make again."
+       @echo "      See \"./configure --help\" for more details."
+       @echo ""
+       ./configure
 
-override LDFLAGS += \
-       $(shell pkg-config --libs glib-2.0 gmime-2.4 talloc) \
-       $(shell xapian-config --libs)
+include Makefile.config
 
-# Include our local Makefile.local first so that its first target is default
-include Makefile.local
 include lib/Makefile.local
-
-# And get user settings from the output of configure
-include Makefile.config
+include compat/Makefile.local
+include Makefile.local
 
 # The user has not set any verbosity, default to quiet mode and inform the
 # user how to enable verbose compiles.
 ifeq ($(V),)
 quiet_DOC := "Use \"$(MAKE) V=1\" to see the verbose compile lines.\n"
-quiet = @echo $(quiet_DOC)$(eval quiet_DOC:=)"  $1     $@"; $($1)
+quiet = @printf $(quiet_DOC)$(eval quiet_DOC:=)"  $1 $2        $@\n"; $($1)
 endif
 # The user has explicitly enabled quiet compilation.
 ifeq ($(V),0)
-quiet = @echo "  $1    $@"; $($1)
+quiet = @printf "  $1  $@\n"; $($1)
 endif
 # Otherwise, print the full command line.
 quiet ?= $($1)
 
 %.o: %.cc $(all_deps)
-       $(call quiet,CXX) -c $(CXXFLAGS) $< -o $@
+       $(call quiet,CXX,$(CXXFLAGS)) -c $(FINAL_CXXFLAGS) $< -o $@
 
 %.o: %.c $(all_deps)
-       $(call quiet,CC) -c $(CFLAGS) $< -o $@
+       $(call quiet,CC,$(CFLAGS)) -c $(FINAL_CFLAGS) $< -o $@
 
 %.elc: %.el
        $(call quiet,EMACS) -batch -f batch-byte-compile $<
 
 .deps/%.d: %.c $(all_deps)
        @set -e; rm -f $@; mkdir -p $$(dirname $@) ; \
-       $(CC) -M $(CPPFLAGS) $(CFLAGS) $< > $@.$$$$; \
+       $(CC) -M $(CPPFLAGS) $(FINAL_CFLAGS) $< > $@.$$$$ 2>/dev/null ; \
        sed 's,'$$(basename $*)'\.o[ :]*,$*.o $@ : ,g' < $@.$$$$ > $@; \
        rm -f $@.$$$$
 
 .deps/%.d: %.cc $(all_deps)
        @set -e; rm -f $@; mkdir -p $$(dirname $@) ; \
-       $(CXX) -M $(CPPFLAGS) $(CXXFLAGS) $< > $@.$$$$; \
+       $(CXX) -M $(CPPFLAGS) $(FINAL_CXXFLAGS) $< > $@.$$$$ 2>/dev/null ; \
        sed 's,'$$(basename $*)'\.o[ :]*,$*.o $@ : ,g' < $@.$$$$ > $@; \
        rm -f $@.$$$$
 
index b8186277e87933c2bb8f1e03705b4fc4a9818d21..933ff4c7de7f8624ce0169e535a1be0d9b2a1579 100644 (file)
@@ -1,8 +1,7 @@
-all: notmuch notmuch.1.gz
-
 emacs: notmuch.elc
 
 notmuch_client_srcs =          \
+       $(notmuch_compat_srcs)  \
        debugger.c              \
        gmime-filter-reply.c    \
        notmuch.c               \
@@ -23,21 +22,18 @@ notmuch_client_srcs =               \
 
 notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
 notmuch: $(notmuch_client_modules) lib/notmuch.a
-       $(call quiet,CXX) $^ $(LDFLAGS) -o $@
+       $(call quiet,CXX,$(LDFLAGS)) $^ $(FINAL_LDFLAGS) -o $@
 
 notmuch.1.gz: notmuch.1
        $(call quiet,gzip) --stdout $^ > $@
 
 install: all notmuch.1.gz
-       for d in $(DESTDIR)$(prefix)/bin/ $(DESTDIR)$(prefix)/share/man/man1 \
-               $(DESTDIR)$(bash_completion_dir) ; \
+       for d in $(DESTDIR)$(prefix)/bin/ $(DESTDIR)$(prefix)/share/man/man1 ; \
        do \
                install -d $$d ; \
        done ;
        install notmuch $(DESTDIR)$(prefix)/bin/
        install -m0644 notmuch.1.gz $(DESTDIR)$(prefix)/share/man/man1/
-       install -m0644 contrib/notmuch-completion.bash \
-               $(DESTDIR)$(bash_completion_dir)/notmuch
 
 install-emacs: install emacs
        for d in $(DESTDIR)/$(emacs_lispdir) ; \
@@ -47,5 +43,10 @@ install-emacs: install emacs
        install -m0644 notmuch.el $(DESTDIR)$(emacs_lispdir)
        install -m0644 notmuch.elc $(DESTDIR)$(emacs_lispdir)
 
+install-bash:
+       install -d $(DESTDIR)$(bash_completion_dir)
+       install -m0644 contrib/notmuch-completion.bash \
+               $(DESTDIR)$(bash_completion_dir)/notmuch
+
 SRCS  := $(SRCS) $(notmuch_client_srcs)
 CLEAN := $(CLEAN) notmuch $(notmuch_client_modules) notmuch.elc notmuch.1.gz
diff --git a/TODO b/TODO
index 1b8fb42ab7297e892b1e0c07fa19fbe50e607a7e..d6c303ee16f07831f84fadaba1268d724e734274 100644 (file)
--- a/TODO
+++ b/TODO
@@ -8,9 +8,6 @@ Fix the things that are causing the most pain to new users
 
 Emacs interface (notmuch.el)
 ----------------------------
-Make the keybindings help ('?') display the summary of each command's
-documentation, not the function name.
-
 Add a global keybinding table for notmuch, and then view-specific
 tables that add to it.
 
@@ -18,8 +15,6 @@ Add a command to archive all threads in a search view.
        
 Add a '|' binding from the search view.
 
-Add a binding to run a search from notmuch-show-mode.
-
 When a thread has been entirely read, start out by closing all
 messages except those that matched the search terms.
 
@@ -41,10 +36,6 @@ Portability
 -----------
 Fix configure script to test each compiler warning we want to use.
 
-Implement strndup locally (or call talloc_strndup instead).
-
-Implement getline locally, (look at gnulib).
-
 Completion
 ----------
 Fix bash completion to complete multiple search options (both --first
@@ -53,6 +44,11 @@ and *then* --max-threads), and also complete value for --sort=
 
 notmuch command-line tool
 -------------------------
+Fix "notmuch show" so that the UI doesn't fail to show a thread that
+is visible in a search buffer, but happens to no longer match the
+current search. (Perhaps add a --matching=<secondary-search-terms>
+option (or similar) to "notmuch show".)
+
 Teach "notmuch search" to return many different kinds of results. Some
 ideas:
 
@@ -72,6 +68,10 @@ Give "notmuch restore" some progress indicator. Until we get the
 Xapian bugs fixed that are making this operation slow, we really need
 to let the user know that things are still moving.
 
+Fix "notmuch restore" to operate in a single pass much like "notmuch
+dump" does, rather than doing N searches into the database, each
+matching 1/N messages.
+
 Add a "-f <filename>" option to select an alternate configuration
 file.
 
@@ -80,10 +80,6 @@ relative to the database path. (Otherwise, moving the database to a
 new directory will result in notmuch creating new timestamp documents
 and leaving stale ones behind.)
 
-Ensure that "notmuch new" is sane if its first, giant indexing session
-gets interrupted, (that is, ensure that any results indexed so far are
-flushed).
-
 Fix notmuch.c to use a DIR prefix for directory timestamps, (the idea
 being that it can then add other non-directory timestamps such as for
 noting how far back in the past mail has been indexed, and whether it
@@ -104,10 +100,12 @@ indexing.
 
 notmuch library
 ---------------
+Index content from citations, please.
+
 Provide a sane syntax for date ranges. First, we don't want to require
 both endpoints to be specified. For example it would be nice to be
 able to say things like "since:2009-01-1" or "until:2009-01-1" and
-have the other enpoint be implicit. Second we'de like to support
+have the other endpoint be implicit. Second we'd like to support
 relative specifications of time such as "since:'2 months ago'". To do
 any of this we're probably going to need to break down an write our
 own parser for the query string rather than using Xapian's QueryParser
@@ -132,11 +130,6 @@ Add support for configuring "virtual tags" which are a tuple of
 (tag-name, search-specification). The database is responsible for
 ensuring that the virtual tag is always consistent.
 
-Think about optimizing chunked searches (max-threads > 0) to avoid
-repeating work. That would be saving state from the previous chunk and
-reusing it if the next search is the next chunk with the same search
-string.
-
 General
 -------
 Audit everything for dealing with out-of-memory (and drop xutil.c).
diff --git a/compat/Makefile b/compat/Makefile
new file mode 100644 (file)
index 0000000..9a29ffc
--- /dev/null
@@ -0,0 +1,5 @@
+all:
+       $(MAKE) -C .. all
+
+clean:
+       $(MAKE) -C .. clean
diff --git a/compat/Makefile.local b/compat/Makefile.local
new file mode 100644 (file)
index 0000000..ccc59ae
--- /dev/null
@@ -0,0 +1,8 @@
+dir=compat
+extra_cflags += -I$(dir)
+
+notmuch_compat_srcs =
+
+ifneq ($(HAVE_GETLINE),1)
+notmuch_compat_srcs += $(dir)/getline.c $(dir)/getdelim.c
+endif
diff --git a/compat/compat.h b/compat/compat.h
new file mode 100644 (file)
index 0000000..d639e0f
--- /dev/null
@@ -0,0 +1,41 @@
+/* notmuch - Not much of an email library, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+/* This header file defines functions that will only be conditionally
+ * compiled for compatibility on systems that don't provide their own
+ * implementations of the functions.
+ */
+
+#ifndef NOTMUCH_COMPAT_H
+#define NOTMUCH_COMPAT_H
+
+#if !HAVE_GETLINE
+#include <stdio.h>
+#include <unistd.h>
+
+ssize_t
+getline (char **lineptr, size_t *n, FILE *stream);
+
+ssize_t
+getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp);
+
+#endif /* !HAVE_GETLINE */
+
+#endif /* NOTMUCH_COMPAT_H */
diff --git a/compat/getdelim.c b/compat/getdelim.c
new file mode 100644 (file)
index 0000000..407f3d0
--- /dev/null
@@ -0,0 +1,133 @@
+/* getdelim.c --- Implementation of replacement getdelim function.
+   Copyright (C) 1994, 1996, 1997, 1998, 2001, 2003, 2005, 2006, 2007,
+   2008, 2009 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 3, or (at
+   your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA.  */
+
+/* Ported from glibc by Simon Josefsson. */
+
+#include "compat.h"
+
+#include <stdio.h>
+
+#include <limits.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
+#endif
+
+#if USE_UNLOCKED_IO
+# include "unlocked-io.h"
+# define getc_maybe_unlocked(fp)       getc(fp)
+#elif !HAVE_FLOCKFILE || !HAVE_FUNLOCKFILE || !HAVE_DECL_GETC_UNLOCKED
+# undef flockfile
+# undef funlockfile
+# define flockfile(x) ((void) 0)
+# define funlockfile(x) ((void) 0)
+# define getc_maybe_unlocked(fp)       getc(fp)
+#else
+# define getc_maybe_unlocked(fp)       getc_unlocked(fp)
+#endif
+
+/* Read up to (and including) a DELIMITER from FP into *LINEPTR (and
+   NUL-terminate it).  *LINEPTR is a pointer returned from malloc (or
+   NULL), pointing to *N characters of space.  It is realloc'ed as
+   necessary.  Returns the number of characters read (not including
+   the null terminator), or -1 on error or EOF.  */
+
+ssize_t
+getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp)
+{
+  ssize_t result = -1;
+  size_t cur_len = 0;
+
+  if (lineptr == NULL || n == NULL || fp == NULL)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  flockfile (fp);
+
+  if (*lineptr == NULL || *n == 0)
+    {
+      char *new_lineptr;
+      *n = 120;
+      new_lineptr = (char *) realloc (*lineptr, *n);
+      if (new_lineptr == NULL)
+       {
+         result = -1;
+         goto unlock_return;
+       }
+      *lineptr = new_lineptr;
+    }
+
+  for (;;)
+    {
+      int i;
+
+      i = getc_maybe_unlocked (fp);
+      if (i == EOF)
+       {
+         result = -1;
+         break;
+       }
+
+      /* Make enough space for len+1 (for final NUL) bytes.  */
+      if (cur_len + 1 >= *n)
+       {
+         size_t needed_max =
+           SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX;
+         size_t needed = 2 * *n + 1;   /* Be generous. */
+         char *new_lineptr;
+
+         if (needed_max < needed)
+           needed = needed_max;
+         if (cur_len + 1 >= needed)
+           {
+             result = -1;
+             errno = EOVERFLOW;
+             goto unlock_return;
+           }
+
+         new_lineptr = (char *) realloc (*lineptr, needed);
+         if (new_lineptr == NULL)
+           {
+             result = -1;
+             goto unlock_return;
+           }
+
+         *lineptr = new_lineptr;
+         *n = needed;
+       }
+
+      (*lineptr)[cur_len] = i;
+      cur_len++;
+
+      if (i == delimiter)
+       break;
+    }
+  (*lineptr)[cur_len] = '\0';
+  result = cur_len ? (ssize_t) cur_len : result;
+
+ unlock_return:
+  funlockfile (fp); /* doesn't set errno */
+
+  return result;
+}
diff --git a/compat/getline.c b/compat/getline.c
new file mode 100644 (file)
index 0000000..222e0f6
--- /dev/null
@@ -0,0 +1,29 @@
+/* getline.c --- Implementation of replacement getline function.
+   Copyright (C) 2005, 2006, 2007 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 3, or (at
+   your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA.  */
+
+/* Written by Simon Josefsson. */
+
+#include "compat.h"
+
+#include <stdio.h>
+
+ssize_t
+getline (char **lineptr, size_t *n, FILE *stream)
+{
+  return getdelim (lineptr, n, '\n', stream);
+}
diff --git a/config/README b/config/README
new file mode 100644 (file)
index 0000000..eabfe28
--- /dev/null
@@ -0,0 +1,5 @@
+notmuch/config
+
+This directory consists of small programs used by the notmuch
+configure script to test for the availability of certain system
+features, (library functions, etc.).
diff --git a/config/have_getline.c b/config/have_getline.c
new file mode 100644 (file)
index 0000000..a8bcd17
--- /dev/null
@@ -0,0 +1,13 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <sys/types.h>
+
+int main()
+{
+    ssize_t count = 0;
+    size_t n = 0;
+    char **lineptr = NULL;
+    FILE *stream = NULL;
+
+    count = getline(lineptr, &n, stream);
+}
index 1010799dbe1fe385712933c826d7225494762f16..fa8e142bb786e0450992abe823c6a9ee26bffb9e 100755 (executable)
--- a/configure
+++ b/configure
@@ -1,12 +1,70 @@
 #! /bin/sh
 
-# defaults
+# Set several defaults (optionally specified by the user in
+# environemnt variables)
+CC=${CC:-gcc}
+CXX=${CXX:-g++}
+CFLAGS=${CFLAGS:--O2}
+CXXFLAGS=${CXXFLAGS:-\$(CFLAGS)}
+
+# Set the defaults for values the user can specify with command-line
+# options.
 PREFIX=/usr/local
 
-# option parsing
+usage ()
+{
+    cat <<EOF
+Usage: ./configure [options]...
+
+This script configures notmuch to build on your system.
+
+It verifies that dependencies are available, determines flags needed
+to compile and link against various required libraries, and identifies
+whether various system functions can be used or if locally-provided
+replacements will be built instead.
+
+Finally, it allows you to control various aspects of the build and
+installation process.
+
+First, some common variables can specified via environment variables:
+
+       CC              The C compiler to use
+       CFLAGS          Flags to pass to the C compiler
+       CXX             The C++ compiler to use
+       CXXFLAGS        Flags to pass to the C compiler
+       LDFLAGS         Flags to pass when linking
+
+Each of these values can further be controlled by specifying them
+later on the "make" command line.
+
+Additionally, various options can be specified on the configure
+command line.
+
+       --prefix=PREFIX Install files in PREFIX [$PREFIX]
+
+By default, "make install" will install the resulting program to
+$PREFIX/bin, documentation to $PREFIX/share, etc. You can
+specify an installation prefix other than $PREFIX using
+--prefix, for instance:
+
+       ./configure --prefix=\$HOME
+
+EOF
+}
+
+# Parse command-line options
 for option; do
-    if [ "${option%=*}" = '--prefix' ] ; then
+    if [ "${option}" = '--help' ] ; then
+       usage
+       exit 0
+    elif [ "${option%%=*}" = '--prefix' ] ; then
        PREFIX="${option#*=}"
+    else
+       echo "Unrecognized option: ${option}."
+       echo "See:"
+       echo "  $0 --help"
+       echo ""
+       exit 1
     fi
 done
 
@@ -17,14 +75,16 @@ We hope that the process of building and installing notmuch is quick
 and smooth so that you can soon be reading and processing your email
 more efficiently than ever.
 
-If anything goes wrong in this process, please do as much as you can
-to figure out what could be different on your machine compared to
-those of the notmuch developers. Then, please email those details to
-the Notmuch list (notmuch@notmuchmail.org) so that we can hopefully make
-future versions of notmuch easier for you to use.
+If anything goes wrong in the configure process, you can override any
+decisions it makes by manually editing the Makefile.config file that
+it creates. Also please do as much as you can to figure out what could
+be different on your machine compared to those of the notmuch
+developers. Then, please email those details to the Notmuch list
+(notmuch@notmuchmail.org) so that we can hopefully make future
+versions of notmuch easier for you to use.
 
-We'll now investigate your system to find verify that various software
-components that notmuch relies on are available.
+We'll now investigate your system to verify that all required
+dependencies are available:
 
 EOF
 
@@ -36,48 +96,57 @@ else
     have_pkg_config=0
 fi
 
+printf "Checking for Xapian development files... "
 if xapian-config --version > /dev/null 2>&1; then
-    echo "Checking for Xapian development files... Yes."
+    printf "Yes.\n"
     have_xapian=1
+    xapian_cxxflags=$(xapian-config --cxxflags)
+    xapian_ldflags=$(xapian-config --libs)
 else
-    echo "Checking for Xapian development files... No."
+    printf "No.\n"
     have_xapian=0
     errors=$((errors + 1))
 fi
 
+printf "Checking for GMime 2.4 development files... "
 if pkg-config --modversion gmime-2.4 > /dev/null 2>&1; then
-    echo "Checking for GMime 2.4 development files... Yes."
+    printf "Yes.\n"
     have_gmime=1
+    gmime_cflags=$(pkg-config --cflags gmime-2.4)
+    gmime_ldflags=$(pkg-config --libs gmime-2.4)
 else
-    echo "Checking for GMime 2.4 development files... No."
+    printf "No.\n"
     have_gmime=0
     errors=$((errors + 1))
 fi
 
+printf "Checking for talloc development files... "
 if pkg-config --modversion talloc > /dev/null 2>&1; then
-    echo "Checking for talloc development files... Yes."
+    printf "Yes.\n"
     have_talloc=1
+    talloc_cflags=$(pkg-config --cflags talloc)
+    talloc_ldflags=$(pkg-config --libs talloc)
 else
-    echo "Checking for talloc development files... No."
+    printf "No.\n"
     have_talloc=0
+    talloc_cflags=
     errors=$((errors + 1))
 fi
 
-if printf 'int main(){return 0;}' | gcc -x c -lz -o /dev/null - > /dev/null 2>&1; then
-    echo "Checking for zlib development files... Yes."
-    have_zlib=1
+printf "Checking for valgrind development files... "
+if pkg-config --modversion valgrind > /dev/null 2>&1; then
+    printf "Yes.\n"
+    have_valgrind=1
+    valgrind_cflags=$(pkg-config --cflags valgrind)
 else
-    echo "Checking for zlib development files... No."
-    have_zlib=0
-    errors=$((errors + 1))
+    printf "No (but that's fine).\n"
+    have_valgrind=0
 fi
 
-if pkg-config --modversion valgrind > /dev/null 2>&1; then
-    echo "Checking for valgrind development files... Yes."
-    have_valgrind=-DHAVE_VALGRIND
+if pkg-config --modversion emacs > /dev/null 2>&1; then
+    emacs_lispdir=$(pkg-config emacs --variable sitepkglispdir)
 else
-    echo "Checking for valgrind development files... No."
-    have_valgrind=
+    emacs_lispdir='$(prefix)/share/emacs/site-lisp'
 fi
 
 if [ $errors -gt 0 ]; then
@@ -100,34 +169,39 @@ EOF
        echo "  The talloc library (including development files such as headers)"
        echo "  http://talloc.samba.org/"
     fi
-    if [ $have_zlib -eq 0 ]; then
-       echo "  The zlib library (including development files such as headers)"
-    fi
     cat <<EOF
 
-On a modern, package-based operating system such as Debian, you can
-install all of the dependencies with the following simple command
-line:
+With any luck, you're using a modern, package-based operating system
+that has all of these packages available in the distribution. In that
+case a simple command will install everything you need. For example:
 
-       sudo apt-get install libxapian-dev libgmime-2.4-dev libtalloc-dev libz-dev
+On Debian and similar systems:
 
-On other systems, a similar command can be used, but the details of the 
-package names may be different, (such as "devel" in place of "dev").
+       sudo apt-get install libxapian-dev libgmime-2.4-dev libtalloc-dev
+
+Or on Fedora and similar systems:
+
+       sudo yum install xapian-core-devel gmime-devel libtalloc-devel
+
+On other systems, similar commands can be used, but the details of the
+package names may be different.
 
 EOF
     if [ $have_pkg_config -eq 0 ]; then
 cat <<EOF
-Note: the pkg-config program is not available. Both this configure
-script and the Makefile of notmuch use pkg-config to find the
-compilation flags required to link against the various libraries
-needed by notmuch. It's possible you simply need to install pkg-config
-with a command such as:
+Note: the pkg-config program is not available. This configure script
+uses pkg-config to find the compilation flags required to link against
+the various libraries needed by notmuch. It's possible you simply need
+to install pkg-config with a command such as:
 
        sudo apt-get install pkg-config
+Or:
+       sudo yum install pkgconfig
 
 But if pkg-config is not available for your system, then you will need
-to manually edit the notmuch Makefile to set NOTMUCH_CFLAGS and
-NOTMUCH_LDFLAGS to the correct values without calling pkg-config.
+to modify the configure script to manually set the cflags and ldflags
+variables to the correct values to link against each library in each
+case that pkg-config could not be used to determine those values.
 
 EOF
     fi
@@ -140,6 +214,17 @@ EOF
     exit 1
 fi
 
+printf "Checking for getline... "
+if ${CC} -o config/have_getline config/have_getline.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    have_getline=1
+else
+    printf "No (will use our own instead).\n"
+    have_getline=0
+fi
+rm -f config/have_getline
+
 cat <<EOF
 
 All required packages were found. You may now run the following
@@ -152,7 +237,59 @@ EOF
 
 # construct the Makefile.config
 cat > Makefile.config <<EOF
-prefix = $PREFIX
-bash_completion_dir = /etc/bash_completion.d
-CFLAGS += ${have_valgrind}
+# This Makefile.config was automatically generated by the ./configure
+# script of notmuch. If the configure script identified anything
+# incorrectly, then you can edit this file to try to correct things,
+# but be warned that if configure is run again it will destroy your
+# changes, (and this could happen by simply calling "make" if the
+# configure script is updated).
+
+# The C compiler to use
+CC = ${CC}
+
+# The C++ compiler to use
+CXX = ${CXX}
+
+# Default FLAGS for C compiler (can be overridden by user such as "make CFLAGS=-g")
+CFLAGS = ${CFLAGS}
+
+# Default FLAGS for C++ compiler (can be overridden by user such as "make CXXFLAGS=-g")
+CXXFLAGS = ${CXXFLAGS}
+
+# The prefix to which notmuch should be installed
+prefix = ${PREFIX}
+
+# The directory to which emacs lisp files should be installed
+emacs_lispdir=${emacs_lispdir}
+
+# Whether the getline function is available (if not, then notmuch will
+# build its own version)
+HAVE_GETLINE = ${have_getline}
+
+# Flags needed to compile and link against Xapian
+XAPIAN_CXXFLAGS = ${xapian_cxxflags}
+XAPIAN_LDFLAGS = ${xapian_ldflags}
+
+# Flags needed to compile and link against GMime-2.4
+GMIME_CFLAGS = ${gmime_cflags}
+GMIME_LDFLAGS = ${gmime_ldflags}
+
+# Flags needed to compile and link against talloc
+TALLOC_CFLAGS = ${talloc_cflags}
+TALLOC_LDFLAGS = ${talloc_ldflags}
+
+# Whether valgrind header files are available
+HAVE_VALGRIND = ${have_valgrind}
+
+# And if so, flags needed at compile time for valgrind macros
+VALGRIND_CFLAGS = ${valgrind_cflags}
+
+# Combined flags for compiling and linking against all of the above
+CONFIGURE_CFLAGS = -DHAVE_GETLINE=\$(HAVE_GETLINE) \$(GMIME_CFLAGS)      \\
+                  \$(TALLOC_CFLAGS) -DHAVE_VALGRIND=\$(HAVE_VALGRIND)   \\
+                  \$(VALGRIND_CFLAGS)
+CONFIGURE_CXXFLAGS = -DHAVE_GETLINE=\$(HAVE_GETLINE) \$(GMIME_CFLAGS)    \\
+                    \$(TALLOC_CFLAGS) -DHAVE_VALGRIND=\$(HAVE_VALGRIND) \\
+                    \$(VALGRIND_CFLAGS) \$(XAPIAN_CXXFLAGS)
+CONFIGURE_LDFLAGS =  \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(XAPIAN_LDFLAGS)
 EOF
index 80df64bfb73a3edbf2ff9819e99c473c3d41f81b..125fa6c94f2b6612f6aac3e36dfe518753eaddaf 100644 (file)
@@ -31,7 +31,7 @@ _index_address_mailbox (notmuch_message_t *message,
 {
     InternetAddressMailbox *mailbox = INTERNET_ADDRESS_MAILBOX (address);
     const char *name, *addr;
-    int own_name = 0;
+    void *local = talloc_new (NULL);
 
     name = internet_address_get_name (address);
     addr = internet_address_mailbox_get_addr (mailbox);
@@ -42,16 +42,16 @@ _index_address_mailbox (notmuch_message_t *message,
        const char *at;
 
        at = strchr (addr, '@');
-       if (at) {
-           name = strndup (addr, at - addr);
-           own_name = 1;
-       }
+       if (at)
+           name = talloc_strndup (local, addr, at - addr);
     }
 
     if (name)
        _notmuch_message_gen_terms (message, prefix_name, name);
     if (addr)
        _notmuch_message_gen_terms (message, prefix_name, addr);
+
+    talloc_free (local);
 }
 
 static void
index d3f9a4c4ba4727c0860804ac30a0ddfd883565b6..0c340a769fc005d9b98c1b46a36c77492dcc6c72 100644 (file)
@@ -26,6 +26,8 @@
 #endif
 #include <stdio.h>
 
+#include "compat.h"
+
 #include "notmuch.h"
 
 NOTMUCH_BEGIN_DECLS
@@ -330,18 +332,6 @@ void
 _notmuch_message_add_reply (notmuch_message_t *message,
                            notmuch_message_node_t *reply);
 
-/* date.c */
-
-/* Parse an RFC 8222 date string to a time_t value.
- *
- * The tz_offset argument can be used to also obtain the time-zone
- * offset, (but can be NULL if the call is not interested in that).
- *
- * Returns 0 on error.
- */
-time_t
-notmuch_parse_date (const char *str, int *tz_offset);
-
 /* sha1.c */
 
 char *
index 6fa5eb0ddb49dfb12dcd0cf039d4165cd8f6f7cb..268225b8956690137ae471c82e4ef39de71c4a96 100644 (file)
@@ -18,7 +18,6 @@
  * Author: Carl Worth <cworth@cworth.org>
  */
 
-#define _GNU_SOURCE /* For strndup */
 #include "notmuch-private.h"
 
 #include <stdio.h>
@@ -84,11 +83,16 @@ xstrndup (const char *s, size_t n)
 {
     char *ret;
 
-    ret = strndup (s, n);
+    if (strlen (s) <= n)
+       n = strlen (s);
+
+    ret = malloc (n + 1);
     if (ret == NULL) {
        fprintf (stderr, "Out of memory.\n");
        exit (1);
     }
+    memcpy (ret, s, n);
+    ret[n] = '\0';
 
     return ret;
 }
index 2888a6c89679fc3574b8dac103ad8013d00b6359..50a30fed5d786fba3bbf7a6cfd26a13bb08ba5b3 100644 (file)
 #ifndef NOTMUCH_CLIENT_H
 #define NOTMUCH_CLIENT_H
 
-
 #ifndef _GNU_SOURCE
 #define _GNU_SOURCE /* for getline */
 #endif
 #include <stdio.h>
 
+#include "compat.h"
+
 #include <gmime/gmime.h>
 
 #include "notmuch.h"
index fc65d6b00cb1112ea59718798a3cdd2dc31be5d9..95430db104fce8583ac089f9a3e83f52c08901b0 100644 (file)
@@ -317,9 +317,11 @@ notmuch_config_save (notmuch_config_t *config)
        fprintf (stderr, "Error saving configuration to %s: %s\n",
                 config->filename, error->message);
        g_error_free (error);
+       g_free (data);
        return 1;
     }
 
+    g_free (data);
     return 0;
 }
 
index f58a384b52b1cf48cbb7067b2e5f8743e9b0aeae..9d206167e997382d623934ce4dd1797751b2e950 100644 (file)
@@ -35,8 +35,10 @@ static volatile sig_atomic_t interrupted;
 static void
 handle_sigint (unused (int sig))
 {
+    ssize_t ignored;
     static char msg[] = "Stopping...         \n";
-    write(2, msg, sizeof(msg)-1);
+
+    ignored = write(2, msg, sizeof(msg)-1);
     interrupted = 1;
 }
 
index 9ca1236b9c1791aec6d0ba88ba250e9773cf0689..0cda72dcf05488be170faca9bd9a6e63f6c3e054 100644 (file)
@@ -39,11 +39,17 @@ reply_part_content (GMimeObject *part)
 {
     GMimeStream *stream_stdout = NULL, *stream_filter = NULL;
     GMimeDataWrapper *wrapper;
+    const char *charset;
 
+    charset = g_mime_object_get_content_type_parameter (part, "charset");
     stream_stdout = g_mime_stream_file_new (stdout);
     if (stream_stdout) {
        g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
        stream_filter = g_mime_stream_filter_new(stream_stdout);
+        if (charset) {
+          g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
+                                   g_mime_filter_charset_new(charset, "UTF-8"));
+        }
     }
     g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
                             g_mime_filter_reply_new(TRUE));
index 5ec176d372a74e9b65c31846dc33fbe7ed95d22c..622bbaa68343c7633ade295d794e86e66c64d137 100644 (file)
@@ -100,12 +100,15 @@ notmuch_setup_command (unused (void *ctx),
     unsigned int i;
     int is_new;
 
-#define prompt(format, ...)                            \
-    do {                                               \
-       printf (format, ##__VA_ARGS__);                 \
-       fflush (stdout);                                \
-       getline (&response, &response_size, stdin);     \
-       chomp_newline (response);                       \
+#define prompt(format, ...)                                    \
+    do {                                                       \
+       printf (format, ##__VA_ARGS__);                         \
+       fflush (stdout);                                        \
+       if (getline (&response, &response_size, stdin) < 0) {   \
+           printf ("Exiting.\n");                              \
+           exit (1);                                           \
+       }                                                       \
+       chomp_newline (response);                               \
     } while (0)
 
     config = notmuch_config_open (ctx, NULL, &is_new);
index 13c91e47f17cee399d881af125ad2907916c5d56..376aacd7bcbe03be72d3562529f74d05d8efa1a3 100644 (file)
@@ -184,9 +184,12 @@ show_message (void *ctx, notmuch_message_t *message, int indent)
 
 
 static void
-show_messages (void *ctx, notmuch_messages_t *messages, int indent)
+show_messages (void *ctx, notmuch_messages_t *messages, int indent,
+              notmuch_bool_t entire_thread)
 {
     notmuch_message_t *message;
+    notmuch_bool_t match;
+    int next_indent;
 
     for (;
         notmuch_messages_has_more (messages);
@@ -194,9 +197,17 @@ show_messages (void *ctx, notmuch_messages_t *messages, int indent)
     {
        message = notmuch_messages_get (messages);
 
-       show_message (ctx, message, indent);
+       match = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);
 
-       show_messages (ctx, notmuch_message_get_replies (message), indent + 1);
+       next_indent = indent;
+
+       if (match || entire_thread) {
+           show_message (ctx, message, indent);
+           next_indent = indent + 1;
+       }
+
+       show_messages (ctx, notmuch_message_get_replies (message),
+                      next_indent, entire_thread);
 
        notmuch_message_destroy (message);
     }
@@ -212,6 +223,24 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
     notmuch_thread_t *thread;
     notmuch_messages_t *messages;
     char *query_string;
+    int entire_thread = 0;
+    int i;
+
+    for (i = 0; i < argc && argv[i][0] == '-'; i++) {
+       if (strcmp (argv[i], "--") == 0) {
+           i++;
+           break;
+       }
+        if (strcmp(argv[i], "--entire-thread") == 0) {
+           entire_thread = 1;
+       } else {
+           fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
+           return 1;
+       }
+    }
+
+    argc -= i;
+    argv += i;
 
     config = notmuch_config_open (ctx, NULL, NULL);
     if (config == NULL)
@@ -251,7 +280,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
            INTERNAL_ERROR ("Thread %s has no toplevel messages.\n",
                            notmuch_thread_get_thread_id (thread));
 
-       show_messages (ctx, messages, 0);
+       show_messages (ctx, messages, 0, entire_thread);
 
        notmuch_thread_destroy (thread);
     }
index 07cb8c5f21b53df36452b3fc14415b63f2f0b075..00588a11608bc0b5a8ead8d7f7e4a2bb804d403b 100644 (file)
@@ -25,8 +25,10 @@ static volatile sig_atomic_t interrupted;
 static void
 handle_sigint (unused (int sig))
 {
+    ssize_t ignored;
+
     static char msg[] = "Stopping...         \n";
-    write(2, msg, sizeof(msg)-1);
+    ignored = write(2, msg, sizeof(msg)-1);
     interrupted = 1;
 }
 
index 04bd0cf184cbaf4689dcdecb919fc9a02a46b0ac..369ecba169e49862f5e779e01698d4b1df8b2a12 100644 (file)
--- a/notmuch.1
+++ b/notmuch.1
@@ -169,6 +169,8 @@ when sorting by
 .B newest\-first
 the threads will be sorted by the newest message in each thread.
 
+.RE
+.RS 4
 By default, results will be displayed in reverse chronological order,
 (that is, the newest results will be displayed first).
 
@@ -177,7 +179,7 @@ See the
 section below for details of the supported syntax for <search-terms>.
 .RE
 .TP
-.BR show " <search-term>..."
+.BR show " [options...] <search-term>..."
 
 Shows all messages matching the search terms.
 
@@ -187,6 +189,19 @@ message in date order). The output is not indented by default, but
 depth tags are printed so that proper indentation can be performed by
 a post-processor (such as the emacs interface to notmuch).
 
+Supported options for
+.B show
+include
+.RS 4
+.TP 4
+.B \-\-entire\-thread
+
+By default only those messages that match the search terms will be
+displayed. With this option, all messages in the same thread as any
+matched message will be displayed.
+.RE
+
+.RS 4
 The  output format  is plain-text,  with all  text-content  MIME parts
 decoded. Various components in the output,
 .RB ( message ", " header ", " body ", " attachment ", and MIME " part ),
@@ -207,6 +222,7 @@ See the
 .B "SEARCH SYNTAX"
 section below for details of the supported syntax for <search-terms>.
 .RE
+.RE
 
 The
 .B reply
index d9846ce791c1d6e5d4ae8bec8c9a51b928662f50..2ac8a592bb264e23259a8d1601b0aad7451eb088 100644 (file)
--- a/notmuch.c
+++ b/notmuch.c
@@ -177,6 +177,15 @@ command_t commands[] = {
       "\t\t(all replies to a particular message appear immediately\n"
       "\t\tafter that message in date order).\n"
       "\n"
+      "\t\tSupported options for show include:\n"
+      "\n"
+      "\t\t--entire-thread\n"
+      "\n"
+      "\t\t\tBy default only those messages that match the\n"
+      "\t\t\tsearch terms will be displayed. With this option,\n"
+      "\t\t\tall messages in the same thread as any matched\n"
+      "\t\t\tmessage will be displayed.\n"
+      "\n"
       "\t\tThe output format is plain-text, with all text-content\n"
       "\t\tMIME parts decoded. Various components in the output,\n"
       "\t\t('message', 'header', 'body', 'attachment', and MIME 'part')\n"
index 65473ba70911fd0ab5bdc6e0f2e05b71dd72b2a9..c504f46de22aa89a3a3c68faf75db530f7b7205d 100644 (file)
 
 (defvar notmuch-show-mode-map
   (let ((map (make-sparse-keymap)))
-    ; I don't actually want all of these toggle commands occupying
-    ; keybindings. They steal valuable key-binding space, are hard
-    ; to remember, and act globally rather than locally.
-    ;
-    ; Will be much preferable to switch to direct manipulation for
-    ; toggling visibility of these components. Probably using
-    ; overlays-at to query and manipulate the current overlay.
-    (define-key map "a" 'notmuch-show-archive-thread)
-    (define-key map "A" 'notmuch-show-mark-read-then-archive-thread)
-    (define-key map "f" 'notmuch-show-forward-current)
-    (define-key map "m" 'message-mail)
-    (define-key map "n" 'notmuch-show-next-message)
-    (define-key map "N" 'notmuch-show-mark-read-then-next-open-message)
-    (define-key map "p" 'notmuch-show-previous-message)
-    (define-key map (kbd "C-n") 'notmuch-show-next-line)
-    (define-key map (kbd "C-p") 'notmuch-show-previous-line)
+    (define-key map "?" 'notmuch-help)
     (define-key map "q" 'kill-this-buffer)
-    (define-key map "r" 'notmuch-show-reply)
+    (define-key map (kbd "C-p") 'notmuch-show-previous-line)
+    (define-key map (kbd "C-n") 'notmuch-show-next-line)
+    (define-key map (kbd "M-TAB") 'notmuch-show-previous-button)
+    (define-key map (kbd "TAB") 'notmuch-show-next-button)
     (define-key map "s" 'notmuch-search)
-    (define-key map "v" 'notmuch-show-view-all-mime-parts)
-    (define-key map "V" 'notmuch-show-view-raw-message)
+    (define-key map "m" 'message-mail)
+    (define-key map "f" 'notmuch-show-forward-current)
+    (define-key map "r" 'notmuch-show-reply)
+    (define-key map "|" 'notmuch-show-pipe-message)
     (define-key map "w" 'notmuch-show-save-attachments)
-    (define-key map "x" 'kill-this-buffer)
-    (define-key map "+" 'notmuch-show-add-tag)
+    (define-key map "V" 'notmuch-show-view-raw-message)
+    (define-key map "v" 'notmuch-show-view-all-mime-parts)
     (define-key map "-" 'notmuch-show-remove-tag)
+    (define-key map "+" 'notmuch-show-add-tag)
+    (define-key map "X" 'notmuch-show-mark-read-then-archive-then-exit)
+    (define-key map "x" 'notmuch-show-archive-thread-then-exit)
+    (define-key map "A" 'notmuch-show-mark-read-then-archive-thread)
+    (define-key map "a" 'notmuch-show-archive-thread)
+    (define-key map "p" 'notmuch-show-previous-message)
+    (define-key map "N" 'notmuch-show-mark-read-then-next-open-message)
+    (define-key map "n" 'notmuch-show-next-message)
     (define-key map (kbd "DEL") 'notmuch-show-rewind)
     (define-key map " " 'notmuch-show-advance-marking-read-and-archiving)
-    (define-key map "|" 'notmuch-show-pipe-message)
-    (define-key map "?" 'describe-mode)
-    (define-key map (kbd "TAB") 'notmuch-show-next-button)
-    (define-key map (kbd "M-TAB") 'notmuch-show-previous-button)
     map)
   "Keymap for \"notmuch show\" buffers.")
 (fset 'notmuch-show-mode-map notmuch-show-mode-map)
@@ -117,7 +111,7 @@ pattern can still test against the entire line).")
 (defvar notmuch-show-marker-regexp "\f\\(message\\|header\\|body\\|attachment\\|part\\)[{}].*$")
 
 (defvar notmuch-show-id-regexp "\\(id:[^ ]*\\)")
-(defvar notmuch-show-depth-regexp " depth:\\([0-9]*\\) ")
+(defvar notmuch-show-depth-match-regexp " depth:\\([0-9]*\\).*match:\\([01]\\) ")
 (defvar notmuch-show-filename-regexp "filename:\\(.*\\)$")
 (defvar notmuch-show-tags-regexp "(\\([^)]*\\))$")
 
@@ -165,7 +159,7 @@ Unlike builtin `next-line' this version accepts no arguments."
 
 By advancing forward until reaching a visible character.
 
-Unlike builtin `next-line' this version accepts no arguments."
+Unlike builtin `previous-line' this version accepts no arguments."
   (interactive)
   (set 'this-command 'previous-line)
   (call-interactively 'previous-line)
@@ -252,7 +246,7 @@ Unlike builtin `next-line' this version accepts no arguments."
          (notmuch-search-show-thread)))))
 
 (defun notmuch-show-mark-read-then-archive-thread ()
-  "Remove \"unread\" tag from each message, then archive and show next thread.
+  "Remove unread tags from thread, then archive and show next thread.
 
 Archive each message currently shown by removing the \"unread\"
 and \"inbox\" tag from each. Then kill this buffer and show the
@@ -267,7 +261,7 @@ buffer."
   (notmuch-show-archive-thread-maybe-mark-read t))
 
 (defun notmuch-show-archive-thread ()
-  "Archive each message in thread, and show next thread from search.
+  "Archive each message in thread, then show next thread from search.
 
 Archive each message currently shown by removing the \"inbox\"
 tag from each. Then kill this buffer and show the next thread
@@ -280,6 +274,18 @@ buffer."
   (interactive)
   (notmuch-show-archive-thread-maybe-mark-read nil))
 
+(defun notmuch-show-archive-thread-then-exit ()
+  "Archive each message in thread, then exit back to search results."
+  (interactive)
+  (notmuch-show-archive-thread)
+  (kill-this-buffer))
+
+(defun notmuch-show-mark-read-then-archive-then-exit ()
+  "Remove unread tags from thread, then archive and exit to search results."
+  (interactive)
+  (notmuch-show-mark-read-then-archive-thread)
+  (kill-this-buffer))
+
 (defun notmuch-show-view-raw-message ()
   "View the raw email of the current message."
   (interactive)
@@ -296,7 +302,7 @@ buffer."
         (kill-buffer buf)))))
 
 (defun notmuch-show-view-all-mime-parts ()
-  "Use external viewers (according to mailcap) to view all MIME-encoded parts."
+  "Use external viewers to view all attachments from the current message."
   (interactive)
   (with-current-notmuch-show-message
    (mm-display-parts (mm-dissect-buffer))))
@@ -334,7 +340,7 @@ buffer."
    mm-handle))
 
 (defun notmuch-show-save-attachments ()
-  "Save the attachments to a message"
+  "Save all attachments from the current message."
   (interactive)
   (with-current-notmuch-show-message
    (let ((mm-handle (mm-dissect-buffer)))
@@ -360,7 +366,7 @@ buffer."
     (notmuch-reply message-id)))
 
 (defun notmuch-show-forward-current ()
-  "Forward the current message."
+  "Forward the current message."
   (interactive)
   (with-current-notmuch-show-message
    (message-forward)))
@@ -385,7 +391,7 @@ point either forward or backward to the next visible character
 when a command ends with point on an invisible character).
 
 Emits an error if point is not within a valid message, (that is
-not pattern of `notmuch-show-message-begin-regexp' could be found
+no pattern of `notmuch-show-message-begin-regexp' could be found
 by searching backward)."
   (beginning-of-line)
   (if (not (looking-at notmuch-show-message-begin-regexp))
@@ -402,22 +408,35 @@ by searching backward)."
       (not (re-search-forward notmuch-show-message-begin-regexp nil t)))))
 
 (defun notmuch-show-message-unread-p ()
-  "Preficate testing whether current message is unread."
+  "Predicate testing whether current message is unread."
   (member "unread" (notmuch-show-get-tags)))
 
+(defun notmuch-show-message-open-p ()
+  "Predicate testing whether current message is open (body is visible)."
+  (let ((btn (previous-button (point) t)))
+    (while (not (button-has-type-p btn 'notmuch-button-body-toggle-type))
+      (setq btn (previous-button (button-start btn))))
+    (not (invisible-p (button-get btn 'invisibility-spec)))))
+
 (defun notmuch-show-next-message ()
   "Advance to the beginning of the next message in the buffer.
 
 Moves to the last visible character of the current message if
-already on the last message in the buffer."
+already on the last message in the buffer.
+
+Returns nil if already on the last message in the buffer."
   (interactive)
   (notmuch-show-move-to-current-message-summary-line)
   (if (re-search-forward notmuch-show-message-begin-regexp nil t)
-      (notmuch-show-move-to-current-message-summary-line)
+      (progn
+       (notmuch-show-move-to-current-message-summary-line)
+       (recenter 0)
+       t)
     (goto-char (- (point-max) 1))
     (while (point-invisible-p)
-      (backward-char)))
-  (recenter 0))
+      (backward-char))
+    (recenter 0)
+    nil))
 
 (defun notmuch-show-find-next-message ()
   "Returns the position of the next message in the buffer.
@@ -445,14 +464,9 @@ there are no more unread messages past the current point."
       (notmuch-show-next-message)))
 
 (defun notmuch-show-next-open-message ()
-  "Advance to the next message which is not hidden.
-
-If read messages are currently hidden, advance to the next unread
-message. Otherwise, advance to the next message."
-  (if (or (memq 'notmuch-show-body-read buffer-invisibility-spec)
-         (assq 'notmuch-show-body-read buffer-invisibility-spec))
-      (notmuch-show-next-unread-message)
-    (notmuch-show-next-message)))
+  "Advance to the next open message (that is, body is not invisible)."
+  (while (and (notmuch-show-next-message)
+             (not (notmuch-show-message-open-p)))))
 
 (defun notmuch-show-previous-message ()
   "Backup to the beginning of the previous message in the buffer.
@@ -486,13 +500,13 @@ it."
       (point))))
 
 (defun notmuch-show-mark-read-then-next-open-message ()
-  "Remove unread tag from current message, then advance to next unread message."
+  "Remove unread tag from this message, then advance to next open message."
   (interactive)
   (notmuch-show-remove-tag "unread")
   (notmuch-show-next-open-message))
 
 (defun notmuch-show-rewind ()
-  "Do reverse scrolling compared to `notmuch-show-advance-marking-read-and-archiving'
+  "Backup through the thread, (reverse scrolling compared to \\[notmuch-show-advance-marking-read-and-archiving]).
 
 Specifically, if the beginning of the previous email is fewer
 than `window-height' lines from the current point, move to it
@@ -514,7 +528,7 @@ any effects from previous calls to
       (notmuch-show-previous-message))))
 
 (defun notmuch-show-advance-marking-read-and-archiving ()
-  "Advance through buffer, marking read and archiving.
+  "Advance through thread, marking read and archiving.
 
 This command is intended to be one of the simplest ways to
 process a thread of email. It does the following:
@@ -552,7 +566,7 @@ which this thread was originally shown."
   (goto-char (button-start (previous-button (point)))))
 
 (defun notmuch-toggle-invisible-action (cite-button)
-  (let ((invis-spec (button-get button 'invisibility-spec)))
+  (let ((invis-spec (button-get cite-button 'invisibility-spec)))
         (if (invisible-p invis-spec)
             (remove-from-invisibility-spec invis-spec)
           (add-to-invisibility-spec invis-spec)
@@ -560,14 +574,19 @@ which this thread was originally shown."
   (force-window-update)
   (redisplay t))
 
-(define-button-type 'notmuch-button-invisibility-toggle-type 'action 'notmuch-toggle-invisible-action 'follow-link t)
+(define-button-type 'notmuch-button-invisibility-toggle-type
+  'action 'notmuch-toggle-invisible-action
+  'follow-link t
+  'face "default")
 (define-button-type 'notmuch-button-citation-toggle-type 'help-echo "mouse-1, RET: Show citation"
   :supertype 'notmuch-button-invisibility-toggle-type)
 (define-button-type 'notmuch-button-signature-toggle-type 'help-echo "mouse-1, RET: Show signature"
   :supertype 'notmuch-button-invisibility-toggle-type)
 (define-button-type 'notmuch-button-headers-toggle-type 'help-echo "mouse-1, RET: Show headers"
   :supertype 'notmuch-button-invisibility-toggle-type)
-(define-button-type 'notmuch-button-body-toggle-type 'help-echo "mouse-1, RET: Show message"
+(define-button-type 'notmuch-button-body-toggle-type
+  'help-echo "mouse-1, RET: Show message"
+  'face 'notmuch-message-summary-face
   :supertype 'notmuch-button-invisibility-toggle-type)
 
 (defun notmuch-show-markup-citations-region (beg end depth)
@@ -665,7 +684,20 @@ which this thread was originally shown."
               (notmuch-show-markup-part
                beg end depth mime-message))))))
 
-(defun notmuch-show-markup-body (depth btn)
+(defun notmuch-show-markup-body (depth match btn)
+  "Markup a message body, (indenting, buttonizing citations,
+etc.), and conditionally hiding the body itself if the message
+has been read and does not match the current search.
+
+DEPTH specifies the depth at which this message appears in the
+tree of the current thread, (the top-level messages have depth 0
+and each reply increases depth by 1). MATCH indicates whether
+this message is regarded as matching the current search. BTN is
+the button which is used to toggle the visibility of this
+message.
+
+When this function is called, point must be within the message, but
+before the delimiter marking the beginning of the body."
   (re-search-forward notmuch-show-body-begin-regexp)
   (forward-line)
   (let ((beg (point-marker)))
@@ -676,86 +708,95 @@ which this thread was originally shown."
         (overlay-put (make-overlay beg end)
                      'invisible invis-spec)
         (button-put btn 'invisibility-spec invis-spec)
-        (if (not (notmuch-show-message-unread-p))
+        (if (not (or (notmuch-show-message-unread-p) match))
             (add-to-invisibility-spec invis-spec)))
       (set-marker beg nil)
       (set-marker end nil)
       )))
+
 (defun notmuch-fontify-headers ()
-  (progn
-    (if (looking-at "[Tt]o:")
-       (progn
-         (overlay-put (make-overlay (point) (re-search-forward ":"))
-                       'face 'message-header-name)
-          (overlay-put (make-overlay (point) (re-search-forward ".*$"))
-                       'face 'message-header-to))
+  (while (looking-at "[[:space:]]")
+    (forward-char))
+  (if (looking-at "[Tt]o:")
+      (progn
+       (overlay-put (make-overlay (point) (re-search-forward ":"))
+                    'face 'message-header-name)
+       (overlay-put (make-overlay (point) (re-search-forward ".*$"))
+                    'face 'message-header-to))
     (if (looking-at "[B]?[Cc][Cc]:")
        (progn
          (overlay-put (make-overlay (point) (re-search-forward ":"))
-                       'face 'message-header-name)
-          (overlay-put (make-overlay (point) (re-search-forward ".*$"))
-                       'face 'message-header-cc))
-    (if (looking-at "[Ss]ubject:")
-       (progn
-         (overlay-put (make-overlay (point) (re-search-forward ":"))
-                       'face 'message-header-name)
-          (overlay-put (make-overlay (point) (re-search-forward ".*$"))
-                       'face 'message-header-subject))
-    (if (looking-at "[Ff]rom:")
-       (progn
-         (overlay-put (make-overlay (point) (re-search-forward ":"))
-                       'face 'message-header-name)
-          (overlay-put (make-overlay (point) (re-search-forward ".*$"))
-                       'face 'message-header-other))))))))
-
-(defun notmuch-show-markup-header (depth)
+                      'face 'message-header-name)
+         (overlay-put (make-overlay (point) (re-search-forward ".*$"))
+                      'face 'message-header-cc))
+      (if (looking-at "[Ss]ubject:")
+         (progn
+           (overlay-put (make-overlay (point) (re-search-forward ":"))
+                        'face 'message-header-name)
+           (overlay-put (make-overlay (point) (re-search-forward ".*$"))
+                        'face 'message-header-subject))
+       (if (looking-at "[Ff]rom:")
+           (progn
+             (overlay-put (make-overlay (point) (re-search-forward ":"))
+                          'face 'message-header-name)
+             (overlay-put (make-overlay (point) (re-search-forward ".*$"))
+                          'face 'message-header-other)))))))
+
+(defun notmuch-show-markup-header (message-begin depth)
+  "Buttonize and decorate faces in a message header.
+
+MESSAGE-BEGIN is the position of the absolute first character in
+the message (including all delimiters that will end up being
+invisible etc.). This is to allow a button to reliably extend to
+the beginning of the message even if point is positioned at an
+invisible character (such as the beginning of the buffer).
+
+DEPTH specifies the depth at which this message appears in the
+tree of the current thread, (the top-level messages have depth 0
+and each reply increases depth by 1)."
   (re-search-forward notmuch-show-header-begin-regexp)
   (forward-line)
   (let ((beg (point-marker))
+       (summary-end (copy-marker (line-beginning-position 2)))
+       (subject-end (copy-marker (line-end-position 2)))
+       (invis-spec (make-symbol "notmuch-show-header"))
         (btn nil))
-    (end-of-line)
-    ; Inverse video for subject
-    (overlay-put (make-overlay beg (point)) 'face '(:inverse-video t))
-    (setq btn (make-button beg (point) :type 'notmuch-button-body-toggle-type))
-    (forward-line 1)
-    (end-of-line)
-    (let ((beg-hidden (point-marker)))
-      (re-search-forward notmuch-show-header-end-regexp)
-      (beginning-of-line)
-      (let ((end (point-marker)))
-        (goto-char beg)
-        (forward-line)
-        (while (looking-at "[A-Za-z][-A-Za-z0-9]*:")
-          (beginning-of-line)
-         (notmuch-fontify-headers)
-          (forward-line)
-          )
-       (indent-rigidly beg end depth)
-        (let ((invis-spec (make-symbol "notmuch-show-header")))
-          (add-to-invisibility-spec (cons invis-spec t))
-          (overlay-put (make-overlay beg-hidden end)
-                       'invisible invis-spec)
-          (goto-char beg)
-          (forward-line)
-          (make-button (line-beginning-position) (line-end-position)
-                        'invisibility-spec (cons invis-spec t)
-                        :type 'notmuch-button-headers-toggle-type))
-        (goto-char end)
-        (insert "\n")
-       (set-marker beg nil)
-       (set-marker beg-hidden nil)
-       (set-marker end nil)
-       ))
-    btn))
+    (re-search-forward notmuch-show-header-end-regexp)
+    (beginning-of-line)
+    (let ((end (point-marker)))
+      (indent-rigidly beg end depth)
+      (goto-char beg)
+      (setq btn (make-button message-begin summary-end :type 'notmuch-button-body-toggle-type))
+      (forward-line)
+      (add-to-invisibility-spec invis-spec)
+      (overlay-put (make-overlay subject-end end)
+                  'invisible invis-spec)
+      (make-button (line-beginning-position) subject-end
+                  'invisibility-spec invis-spec
+                  :type 'notmuch-button-headers-toggle-type)
+      (while (looking-at "[[:space:]]*[A-Za-z][-A-Za-z0-9]*:")
+       (beginning-of-line)
+       (notmuch-fontify-headers)
+       (forward-line)
+       )
+      (goto-char end)
+      (insert "\n")
+      (set-marker beg nil)
+      (set-marker summary-end nil)
+      (set-marker subject-end nil)
+      (set-marker end nil)
+      )
+  btn))
 
 (defun notmuch-show-markup-message ()
   (if (re-search-forward notmuch-show-message-begin-regexp nil t)
-      (progn
-       (re-search-forward notmuch-show-depth-regexp)
+      (let ((message-begin (match-beginning 0)))
+       (re-search-forward notmuch-show-depth-match-regexp)
        (let ((depth (string-to-number (buffer-substring (match-beginning 1) (match-end 1))))
+             (match (string= "1" (buffer-substring (match-beginning 2) (match-end 2))))
               (btn nil))
-         (setq btn (notmuch-show-markup-header depth))
-         (notmuch-show-markup-body depth btn)))
+         (setq btn (notmuch-show-markup-header message-begin depth))
+         (notmuch-show-markup-body depth match btn)))
     (goto-char (point-max))))
 
 (defun notmuch-show-hide-markers ()
@@ -775,6 +816,72 @@ which this thread was originally shown."
       (notmuch-show-markup-message)))
   (notmuch-show-hide-markers))
 
+(defun notmuch-documentation-first-line (symbol)
+  "Return the first line of the documentation string for SYMBOL."
+  (let ((doc (documentation symbol)))
+    (if doc
+       (with-temp-buffer
+         (insert (documentation symbol t))
+         (goto-char (point-min))
+         (let ((beg (point)))
+           (end-of-line)
+           (buffer-substring beg (point))))
+      "")))
+
+(defun notmuch-prefix-key-description (key)
+  "Given a prefix key code, return a human-readable string representation.
+
+This is basically just `format-kbd-macro' but we also convert ESC to M-."
+  (let ((desc (format-kbd-macro (vector key))))
+    (if (string= desc "ESC")
+       "M-"
+      (concat desc " "))))
+
+; I would think that emacs would have code handy for walking a keymap
+; and generating strings for each key, and I would prefer to just call
+; that. But I couldn't find any (could be all implemented in C I
+; suppose), so I wrote my own here.
+(defun notmuch-substitute-one-command-key-with-prefix (prefix binding)
+  "For a key binding, return a string showing a human-readable
+representation of the prefixed key as well as the first line of
+documentation from the bound function.
+
+For a mouse binding, return nil."
+  (let ((key (car binding))
+       (action (cdr binding)))
+    (if (mouse-event-p key)
+       nil
+      (if (keymapp action)
+         (let ((substitute (apply-partially 'notmuch-substitute-one-command-key-with-prefix (notmuch-prefix-key-description key))))
+           (mapconcat substitute (cdr action) "\n"))
+       (concat prefix (format-kbd-macro (vector key))
+               "\t"
+               (notmuch-documentation-first-line action))))))
+
+(defalias 'notmuch-substitute-one-command-key
+  (apply-partially 'notmuch-substitute-one-command-key-with-prefix nil))
+
+(defun notmuch-substitute-command-keys (doc)
+  "Like `substitute-command-keys' but with documentation, not function names."
+  (let ((beg 0))
+    (while (string-match "\\\\{\\([^}[:space:]]*\\)}" doc beg)
+      (let ((map (substring doc (match-beginning 1) (match-end 1))))
+       (setq doc (replace-match (mapconcat 'notmuch-substitute-one-command-key
+                                           (cdr (symbol-value (intern map))) "\n") 1 1 doc)))
+      (setq beg (match-end 0)))
+    doc))
+
+(defun notmuch-help ()
+  "Display help for the current notmuch mode."
+  (interactive)
+  (let* ((mode major-mode)
+        (doc (substitute-command-keys (notmuch-substitute-command-keys (documentation mode t)))))
+    (with-current-buffer (generate-new-buffer "*notmuch-help*")
+      (insert doc)
+      (goto-char (point-min))
+      (set-buffer-modified-p nil)
+      (view-buffer (current-buffer) 'kill-buffer-if-not-modified))))
+
 ;;;###autoload
 (defun notmuch-show-mode ()
   "Major mode for viewing a thread with notmuch.
@@ -783,22 +890,28 @@ This buffer contains the results of the \"notmuch show\" command
 for displaying a single thread of email from your email archives.
 
 By default, various components of email messages, (citations,
-signatures, already-read messages), are invisible to help you
-focus on the most important things, (new text from unread
-messages). See the various commands below for toggling the
-visibility of hidden components.
-
-The `notmuch-show-next-message' and
-`notmuch-show-previous-message' commands, (bound to 'n' and 'p by
-default), allow you to navigate to the next and previous
-messages. Each time you navigate away from a message with
-`notmuch-show-next-message' the current message will have its
-\"unread\" tag removed.
-
-You can add or remove tags from the current message with '+' and
-'-'.  You can also archive all messages in the current
-view, (remove the \"inbox\" tag from each), with
-`notmuch-show-archive-thread' (bound to 'a' by default).
+signatures, already-read messages), are hidden. You can make
+these parts visible by clicking with the mouse button or by
+pressing RET after positioning the cursor on a hidden part, (for
+which \\[notmuch-show-next-button] and \\[notmuch-show-previous-button] are helpful).
+
+Reading the thread sequentially is well-supported by pressing
+\\[notmuch-show-advance-marking-read-and-archiving]. This will scroll the current message (if necessary),
+advance to the next message, or advance to the next thread (if
+already on the last message of a thread). As each message is
+scrolled away its \"unread\" tag will be removed, and as each
+thread is scrolled away the \"inbox\" tag will be removed from
+each message in the thread.
+
+Other commands are available to read or manipulate the thread more
+selectively, (such as '\\[notmuch-show-next-message]' and '\\[notmuch-show-previous-message]' to advance to messages without
+removing any tags, and '\\[notmuch-show-archive-thread]' to archive an entire thread without
+scrolling through with \\[notmuch-show-advance-marking-read-and-archiving]).
+
+You can add or remove arbitary tags from the current message with
+'\\[notmuch-show-add-tag]' or '\\[notmuch-show-remove-tag]'.
+
+All currently available key bindings:
 
 \\{notmuch-show-mode-map}"
   (interactive)
@@ -843,7 +956,8 @@ The optional PARENT-BUFFER is the notmuch-search buffer from
 which this notmuch-show command was executed, (so that the next
 thread from that buffer can be show when done with this one)."
   (interactive "sNotmuch show: ")
-  (let ((buffer (get-buffer-create (concat "*notmuch-show-" thread-id "*"))))
+  (let ((query notmuch-search-query-string)
+       (buffer (get-buffer-create (concat "*notmuch-show-" thread-id "*"))))
     (switch-to-buffer buffer)
     (notmuch-show-mode)
     (set (make-local-variable 'notmuch-show-parent-buffer) parent-buffer)
@@ -855,30 +969,13 @@ thread from that buffer can be show when done with this one)."
       (erase-buffer)
       (goto-char (point-min))
       (save-excursion
-       (call-process notmuch-command nil t nil "show" thread-id)
+       (call-process notmuch-command nil t nil "show" "--entire-thread" thread-id "and (" query ")")
        (notmuch-show-markup-messages)
        )
       (run-hooks 'notmuch-show-hook)
-      ; Move straight to the first unread message
-      (if (not (notmuch-show-message-unread-p))
-         (progn
-           (notmuch-show-next-unread-message)
-           ; But if there are no unread messages, go back to the
-           ; beginning of the buffer, and open up the bodies of all
-           ; read message.
-           (if (not (notmuch-show-message-unread-p))
-               (progn
-                 (goto-char (point-min))
-                  (let ((btn (forward-button 1)))
-                    (while btn
-                      (if (button-has-type-p btn 'notmuch-button-body-toggle-type)
-                          (push-button))
-                      (condition-case err
-                          (setq btn (forward-button 1))
-                        (error (setq btn nil)))
-                    ))
-                  (beginning-of-buffer)
-                  ))))
+      ; Move straight to the first open message
+      (if (not (notmuch-show-message-open-p))
+         (notmuch-show-next-open-message))
       )))
 
 (defvar notmuch-search-authors-width 40
@@ -886,30 +983,29 @@ thread from that buffer can be show when done with this one)."
 
 (defvar notmuch-search-mode-map
   (let ((map (make-sparse-keymap)))
-    (define-key map "a" 'notmuch-search-archive-thread)
-    (define-key map "b" 'notmuch-search-scroll-down)
-    (define-key map "f" 'notmuch-search-filter)
-    (define-key map "m" 'message-mail)
-    (define-key map "n" 'next-line)
-    (define-key map "o" 'notmuch-search-toggle-order)
-    (define-key map "p" 'previous-line)
+    (define-key map "?" 'notmuch-help)
     (define-key map "q" 'kill-this-buffer)
+    (define-key map "x" 'kill-this-buffer)
+    (define-key map (kbd "<DEL>") 'notmuch-search-scroll-down)
+    (define-key map "b" 'notmuch-search-scroll-down)
+    (define-key map " " 'notmuch-search-scroll-up)
+    (define-key map "<" 'notmuch-search-first-thread)
+    (define-key map ">" 'notmuch-search-last-thread)
+    (define-key map "p" 'notmuch-search-previous-thread)
+    (define-key map "n" 'notmuch-search-next-thread)
     (define-key map "r" 'notmuch-search-reply-to-thread)
+    (define-key map "m" 'message-mail)
     (define-key map "s" 'notmuch-search)
+    (define-key map "o" 'notmuch-search-toggle-order)
+    (define-key map "=" 'notmuch-search-refresh-view)
     (define-key map "t" 'notmuch-search-filter-by-tag)
-    (define-key map "x" 'kill-this-buffer)
-    (define-key map (kbd "RET") 'notmuch-search-show-thread)
+    (define-key map "f" 'notmuch-search-filter)
     (define-key map [mouse-1] 'notmuch-search-show-thread)
-    (define-key map "+" 'notmuch-search-add-tag)
-    (define-key map "-" 'notmuch-search-remove-tag)
     (define-key map "*" 'notmuch-search-operate-all)
-    (define-key map "<" 'beginning-of-buffer)
-    (define-key map ">" 'notmuch-search-goto-last-thread)
-    (define-key map "=" 'notmuch-search-refresh-view)
-    (define-key map "\M->" 'notmuch-search-goto-last-thread)
-    (define-key map " " 'notmuch-search-scroll-up)
-    (define-key map (kbd "<DEL>") 'notmuch-search-scroll-down)
-    (define-key map "?" 'describe-mode)
+    (define-key map "a" 'notmuch-search-archive-thread)
+    (define-key map "-" 'notmuch-search-remove-tag)
+    (define-key map "+" 'notmuch-search-add-tag)
+    (define-key map (kbd "RET") 'notmuch-search-show-thread)
     map)
   "Keymap for \"notmuch search\" buffers.")
 (fset 'notmuch-search-mode-map notmuch-search-mode-map)
@@ -918,16 +1014,17 @@ thread from that buffer can be show when done with this one)."
 (defvar notmuch-search-oldest-first t
   "Show the oldest mail first in the search-mode")
 
+(defvar notmuch-search-disjunctive-regexp      "\\<[oO][rR]\\>")
 
 (defun notmuch-search-scroll-up ()
-  "Scroll up, moving point to last message in thread if at end."
+  "Move forward through search results by one window's worth."
   (interactive)
   (condition-case nil
       (scroll-up nil)
-    ((end-of-buffer) (notmuch-search-goto-last-thread))))
+    ((end-of-buffer) (notmuch-search-last-thread))))
 
 (defun notmuch-search-scroll-down ()
-  "Scroll down, moving point to first message in thread if at beginning."
+  "Move backward through the search results by one window's worth."
   (interactive)
   ; I don't know why scroll-down doesn't signal beginning-of-buffer
   ; the way that scroll-up signals end-of-buffer, but c'est la vie.
@@ -937,16 +1034,37 @@ thread from that buffer can be show when done with this one)."
   ; directly to that position. (We have to count lines since the
   ; window-start position is not the same as point-min due to the
   ; invisible thread-ID characters on the first line.
-  (if (equal (count-lines (point-min) (window-start)) 1)
-      (goto-char (window-start))
+  (if (equal (count-lines (point-min) (window-start)) 0)
+      (goto-char (point-min))
     (scroll-down nil)))
 
-(defun notmuch-search-goto-last-thread ()
-  "Move point to the last thread in the buffer."
+(defun notmuch-search-next-thread ()
+  "Select the next thread in the search results."
+  (interactive)
+  (forward-line 1))
+
+(defun notmuch-search-previous-thread ()
+  "Select the previous thread in the search results."
   (interactive)
-  (goto-char (point-max))
   (forward-line -1))
 
+(defun notmuch-search-last-thread ()
+  "Select the last thread in the search results."
+  (interactive)
+  (goto-char (point-max))
+  (forward-line -2))
+
+(defun notmuch-search-first-thread ()
+  "Select the first thread in the search results."
+  (interactive)
+  (goto-char (point-min)))
+
+(defface notmuch-message-summary-face
+ '((((class color) (background light)) (:background "#f0f0f0"))
+   (((class color) (background dark)) (:background "#303030")))
+ "Face for the single-line message summary in notmuch-show-mode."
+ :group 'notmuch)
+
 (defface notmuch-tag-face
   '((((class color)
       (background dark))
@@ -966,22 +1084,27 @@ thread from that buffer can be show when done with this one)."
 
 ;;;###autoload
 (defun notmuch-search-mode ()
-  "Major mode for searching mail with notmuch.
+  "Major mode displaying results of a notmuch search.
 
 This buffer contains the results of a \"notmuch search\" of your
 email archives. Each line in the buffer represents a single
-thread giving a relative date for the thread and a subject.
+thread giving a summary of the thread (a relative date, the
+number of matched messages and total messages in the thread,
+participants in the thread, a representative subject line, and
+any tags).
 
-Pressing RET on any line displays that thread. The '+' and '-'
-keys can be used to add or remove tags from a thread. The 'a' key
-is a convenience key for archiving a thread (removing the
-\"inbox\" tag).
+Pressing \\[notmuch-search-show-thread] on any line displays that thread. The '\\[notmuch-search-add-tag]' and '\\[notmuch-search-remove-tag]'
+keys can be used to add or remove tags from a thread. The '\\[notmuch-search-archive-thread]' key
+is a convenience for archiving a thread (removing the \"inbox\"
+tag). The '\\[notmuch-search-operate-all]' key can be used to add or remove a tag from all
+threads in the current buffer.
 
-Other useful commands are `notmuch-search-filter' for filtering
-the current search based on an additional query string,
-`notmuch-search-filter-by-tag' for filtering to include only
-messages with a given tag, and `notmuch-search' to execute a new,
-global search.
+Other useful commands are '\\[notmuch-search-filter]' for filtering the current search
+based on an additional query string, '\\[notmuch-search-filter-by-tag]' for filtering to include
+only messages with a given tag, and '\\[notmuch-search]' to execute a new, global
+search.
+
+Complete list of currently available key bindings:
 
 \\{notmuch-search-mode-map}"
   (interactive)
@@ -998,12 +1121,11 @@ global search.
   (if (not notmuch-tag-face-alist)
       (add-to-list 'notmuch-search-font-lock-keywords (list
                "(\\([^)]*\\))$" '(1  'notmuch-tag-face)))
-    (progn
-  (setq notmuch-search-tags (mapcar 'car notmuch-tag-face-alist))
-  (loop for notmuch-search-tag  in notmuch-search-tags
-    do (add-to-list 'notmuch-search-font-lock-keywords (list
-                               (concat "([^)]*\\(" notmuch-search-tag "\\)[^)]*)$")
-                   `(1  ,(cdr (assoc notmuch-search-tag notmuch-tag-face-alist))))))))
+    (let ((notmuch-search-tags (mapcar 'car notmuch-tag-face-alist)))
+      (loop for notmuch-search-tag  in notmuch-search-tags
+           do (add-to-list 'notmuch-search-font-lock-keywords (list
+                       (concat "([^)]*\\(" notmuch-search-tag "\\)[^)]*)$")
+                       `(1  ,(cdr (assoc notmuch-search-tag notmuch-tag-face-alist))))))))
   (set (make-local-variable 'font-lock-defaults)
          '(notmuch-search-font-lock-keywords t)))
 
@@ -1012,6 +1134,7 @@ global search.
   (get-text-property (point) 'notmuch-search-thread-id))
 
 (defun notmuch-search-show-thread ()
+  "Display the currently selected thread."
   (interactive)
   (let ((thread-id (notmuch-search-find-thread-id)))
     (if (> (length thread-id) 0)
@@ -1064,25 +1187,29 @@ and will also appear in a buffer named \"*Notmuch errors*\"."
        (split-string (buffer-substring beg end))))))
 
 (defun notmuch-search-add-tag (tag)
-  "Add a tag to messages in the current thread matching the
-active query."
+  "Add a tag to the currently selected thread.
+
+The tag is added to messages in the currently selected thread
+which match the current search terms."
   (interactive
    (list (notmuch-select-tag-with-completion "Tag to add: ")))
   (notmuch-call-notmuch-process "tag" (concat "+" tag) (notmuch-search-find-thread-id) " and " notmuch-search-query-string)
   (notmuch-search-set-tags (delete-dups (sort (cons tag (notmuch-search-get-tags)) 'string<))))
 
 (defun notmuch-search-remove-tag (tag)
-  "Remove a tag from messages in the current thread matching the
-active query."
+  "Remove a tag from the currently selected thread.
+
+The tag is removed from messages in the currently selected thread
+which match the current search terms."
   (interactive
    (list (notmuch-select-tag-with-completion "Tag to remove: " (notmuch-search-find-thread-id))))
   (notmuch-call-notmuch-process "tag" (concat "-" tag) (notmuch-search-find-thread-id) " and " notmuch-search-query-string)
   (notmuch-search-set-tags (delete tag (notmuch-search-get-tags))))
 
 (defun notmuch-search-archive-thread ()
-  "Archive the current thread (remove its \"inbox\" tag).
+  "Archive the currently selected thread (remove its \"inbox\" tag).
 
-This function advances point to the next line when finished."
+This function advances the next thread when finished."
   (interactive)
   (notmuch-search-remove-tag "inbox")
   (forward-line))
@@ -1136,12 +1263,12 @@ This function advances point to the next line when finished."
       (delete-process proc))))
 
 (defun notmuch-search-operate-all (action)
-  "Operate on all messages matching the current query.  Any
-number of whitespace separated actions can be given.  Each action
-must have one of the two forms
+  "Add/remove tags from all matching messages.
 
-  +tagname              Add the tag `tagname'
-  -tagname              Remove the tag `tagname'
+Tis command adds or removes tags from all messages matching the
+current search terms. When called interactively, this command
+will prompt for tags to be added or removed. Tags prefixed with
+'+' will be added and tags prefixed with '-' will be removed.
 
 Each character of the tag name may consist of alphanumeric
 characters as well as `_.+-'.
@@ -1227,7 +1354,8 @@ search."
 Runs a new search matching only messages that match both the
 current search results AND the additional query string provided."
   (interactive "sFilter search: ")
-  (notmuch-search (concat notmuch-search-query-string " and " query) notmuch-search-oldest-first))
+  (let ((grouped-query (if (string-match-p notmuch-search-disjunctive-regexp query) (concat "( " query " )") query)))
+    (notmuch-search (concat notmuch-search-query-string " and " grouped-query) notmuch-search-oldest-first)))
 
 (defun notmuch-search-filter-by-tag (tag)
   "Filter the current search results based on a single tag.
@@ -1249,16 +1377,17 @@ current search results AND that are tagged with the given tag."
 
 (defvar notmuch-folder-mode-map
   (let ((map (make-sparse-keymap)))
-    (define-key map "n" 'next-line)
-    (define-key map "p" 'previous-line)
+    (define-key map "?" 'notmuch-help)
     (define-key map "x" 'kill-this-buffer)
     (define-key map "q" 'kill-this-buffer)
-    (define-key map "s" 'notmuch-search)
-    (define-key map (kbd "RET") 'notmuch-folder-show-search)
-    (define-key map "<" 'beginning-of-buffer)
+    (define-key map ">" 'notmuch-folder-last)
+    (define-key map "<" 'notmuch-folder-first)
     (define-key map "=" 'notmuch-folder)
-    (define-key map "?" 'describe-mode)
+    (define-key map "s" 'notmuch-search)
     (define-key map [mouse-1] 'notmuch-folder-show-search)
+    (define-key map (kbd "RET") 'notmuch-folder-show-search)
+    (define-key map "p" 'notmuch-folder-previous)
+    (define-key map "n" 'notmuch-folder-next)
     map)
   "Keymap for \"notmuch folder\" buffers.")
 
@@ -1272,12 +1401,26 @@ current search results AND that are tagged with the given tag."
 (defun notmuch-folder-mode ()
   "Major mode for showing notmuch 'folders'.
 
-This buffer contains a list of messages counts returned by a
-customizable set of searches of your email archives. Each line
-in the buffer shows the search terms and the resulting message count.
+This buffer contains a list of message counts returned by a
+customizable set of searches of your email archives. Each line in
+the buffer shows the name of a saved search and the resulting
+message count.
 
 Pressing RET on any line opens a search window containing the
-results for the search terms in that line.
+results for the saved search on that line.
+
+Here is an example of how the search list could be
+customized, (the following text would be placed in your ~/.emacs
+file):
+
+(setq notmuch-folders '((\"inbox\" . \"tag:inbox\")
+                        (\"unread\" . \"tag:inbox AND tag:unread\")
+                        (\"notmuch\" . \"tag:inbox AND to:notmuchmail.org\")))
+
+Of course, you can have any number of folders, each configured
+with any supported search terms (see \"notmuch help search-terms\").
+
+Currently available key bindings:
 
 \\{notmuch-folder-mode-map}"
   (interactive)
@@ -1289,6 +1432,29 @@ results for the search terms in that line.
        mode-name "notmuch-folder")
   (setq buffer-read-only t))
 
+(defun notmuch-folder-next ()
+  "Select the next folder in the list."
+  (interactive)
+  (forward-line 1)
+  (if (eobp)
+      (forward-line -1)))
+
+(defun notmuch-folder-previous ()
+  "Select the previous folder in the list."
+  (interactive)
+  (forward-line -1))
+
+(defun notmuch-folder-first ()
+  "Select the first folder in the list."
+  (interactive)
+  (goto-char (point-min)))
+
+(defun notmuch-folder-last ()
+  "Select the last folder in the list."
+  (interactive)
+  (goto-char (point-max))
+  (forward-line -1))
+
 (defun notmuch-folder-add (folders)
   (if folders
       (let ((name (car (car folders)))
index b415f500a167619151afc3d623542a8aabb7efaa..a226f2034d594f781048211265fcdf13582b49d6 100644 (file)
@@ -275,6 +275,7 @@ function! s:NM_search_show_thread(everything)
                 call add(words, ')')
         endif
         call <SID>NM_cmd_show(words)
+        let b:nm_show_everything = a:everything
 endfunction
 
 function! s:NM_search_prompt()
@@ -408,7 +409,7 @@ endfunction
 
 function! s:NM_cmd_show(words)
         let prev_bufnr = bufnr('%')
-        let data = s:NM_run(['show'] + a:words)
+        let data = s:NM_run(['show', '--entire-thread'] + a:words)
         let lines = split(data, "\n")
 
         let info = s:NM_cmd_show_parse(lines)
@@ -430,6 +431,7 @@ function! s:NM_cmd_show(words)
 endfunction
 
 function! s:NM_show_previous(can_change_thread, find_matching)
+        let everything = exists('b:nm_show_everything') ? b:nm_show_everything : 0
         let info = b:nm_raw_info
         let lnum = line('.')
         for msg in reverse(copy(info['msgs']))
@@ -450,7 +452,7 @@ function! s:NM_show_previous(can_change_thread, find_matching)
         call <SID>NM_kill_this_buffer()
         if line('.') > 1
                 norm k
-                call <SID>NM_search_show_thread()
+                call <SID>NM_search_show_thread(everything)
                 norm G
                 call <SID>NM_show_previous(0, a:find_matching)
         else
@@ -479,10 +481,11 @@ function! s:NM_show_next(can_change_thread, find_matching)
 endfunction
 
 function! s:NM_show_next_thread()
+        let everything = exists('b:nm_show_everything') ? b:nm_show_everything : 0
         call <SID>NM_kill_this_buffer()
         if line('.') != line('$')
                 norm j
-                call <SID>NM_search_show_thread()
+                call <SID>NM_search_show_thread(everything)
         else
                 echo 'No more messages.'
         endif