]> git.notmuchmail.org Git - notmuch/commitdiff
Merge tag 'debian/0.17-3' into wheezy-backports
authorDavid Bremner <david@tethera.net>
Thu, 30 Jan 2014 00:20:43 +0000 (20:20 -0400)
committerDavid Bremner <david@tethera.net>
Thu, 30 Jan 2014 00:20:43 +0000 (20:20 -0400)
uploaded to Debian unstable

Conflicts:
debian/changelog

121 files changed:
Makefile.local
NEWS
bindings/python/notmuch/version.py
compat/Makefile.local
compat/check_asctime.c [new file with mode: 0644]
compat/check_getpwuid.c [new file with mode: 0644]
compat/compat.h
compat/have_strsep.c [new file with mode: 0644]
compat/have_timegm.c [new file with mode: 0644]
compat/strsep.c [new file with mode: 0644]
compat/timegm.c [new file with mode: 0644]
completion/notmuch-completion.bash
configure
contrib/notmuch-mutt/README
contrib/notmuch-mutt/notmuch-mutt
contrib/notmuch-pick/README [deleted file]
contrib/notmuch-pick/TODO [deleted file]
contrib/notmuch-pick/notmuch-pick.el [deleted file]
contrib/notmuch-pick/run-tests.sh [deleted file]
contrib/notmuch-pick/test/emacs-pick [deleted file]
contrib/notmuch-pick/test/emacs-pick-sync [deleted file]
contrib/notmuch-pick/test/pick.expected-output/notmuch-pick-show-window [deleted file]
contrib/notmuch-pick/test/pick.expected-output/notmuch-pick-single-thread [deleted file]
contrib/notmuch-pick/test/pick.expected-output/notmuch-pick-tag-inbox [deleted file]
debian/NEWS [new file with mode: 0644]
debian/NEWS.Debian [deleted file]
debian/changelog
debian/control
debian/libnotmuch3.symbols
debian/notmuch-emacs.README.Debian
debian/notmuch-emacs.emacsen-compat [new file with mode: 0644]
debian/notmuch-emacs.emacsen-install
debian/notmuch-emacs.emacsen-remove
debian/notmuch-emacs.postinst [new file with mode: 0644]
debian/notmuch-emacs.prerm [new file with mode: 0644]
debian/notmuch-vim.dirs
debian/notmuch-vim.install
debian/source/options [new file with mode: 0644]
devel/TODO
devel/release-checks.sh
devel/schemata
emacs/Makefile.local
emacs/notmuch-hello.el
emacs/notmuch-lib.el
emacs/notmuch-mua.el
emacs/notmuch-show.el
emacs/notmuch-tag.el
emacs/notmuch-tree.el [new file with mode: 0644]
emacs/notmuch.el
gmime-filter-headers.c [deleted file]
gmime-filter-headers.h [deleted file]
lib/Makefile.local
lib/database.cc
lib/index.cc
lib/libsha1.c
lib/message-file.c
lib/message.cc
lib/notmuch-private.h
lib/notmuch.h
lib/query.cc
man/Makefile.local
man/man1/notmuch-compact.1 [new file with mode: 0644]
man/man1/notmuch-config.1
man/man1/notmuch-count.1
man/man1/notmuch-dump.1
man/man1/notmuch-insert.1
man/man1/notmuch-new.1
man/man1/notmuch-reply.1
man/man1/notmuch-restore.1
man/man1/notmuch-search.1
man/man1/notmuch-show.1
man/man1/notmuch-tag.1
man/man1/notmuch.1
man/man5/notmuch-hooks.5
man/man7/notmuch-search-terms.7
notmuch-client.h
notmuch-compact.c [new file with mode: 0644]
notmuch-config.c
notmuch-count.c
notmuch-new.c
notmuch-reply.c
notmuch-search.c
notmuch-show.c
notmuch.c
parse-time-string/parse-time-string.c
performance-test/Makefile.local
test/Makefile.local
test/aggregate-results.sh
test/compact [new file with mode: 0755]
test/count
test/crypto
test/emacs
test/emacs-show
test/emacs-tree [new file with mode: 0755]
test/encoding
test/excludes
test/insert
test/json
test/maildir-sync
test/missing-headers
test/multipart
test/new
test/notmuch-test
test/raw
test/reply
test/reply-to-sender
test/search-output
test/sexp
test/test-lib.el
test/test-lib.sh
test/tree.expected-output/notmuch-tree-show-window [new file with mode: 0644]
test/tree.expected-output/notmuch-tree-single-thread [new file with mode: 0644]
test/tree.expected-output/notmuch-tree-tag-inbox [new file with mode: 0644]
test/tree.expected-output/notmuch-tree-tag-inbox-tagged [new file with mode: 0644]
test/tree.expected-output/notmuch-tree-tag-inbox-thread-tagged [new file with mode: 0644]
util/endian-util.h [new file with mode: 0644]
version
vim/Makefile
vim/notmuch.txt [new file with mode: 0644]
vim/notmuch.vim
vim/notmuch.yaml

index 84043fe63c2cfd3f103a7470f0e6f3dc175d601b..72524eb361e4a05804c39ce3a237756f3952609b 100644 (file)
@@ -39,8 +39,8 @@ GPG_FILE=$(SHA1_FILE).asc
 PV_FILE=bindings/python/notmuch/version.py
 
 # Smash together user's values with our extra values
-FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CPPFLAGS) $(CFLAGS) $(WARN_CFLAGS) $(CONFIGURE_CFLAGS) $(extra_cflags)
-FINAL_CXXFLAGS = $(CPPFLAGS) $(CXXFLAGS) $(WARN_CXXFLAGS) $(CONFIGURE_CXXFLAGS) $(extra_cflags) $(extra_cxxflags)
+FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CPPFLAGS) $(CFLAGS) $(WARN_CFLAGS) $(extra_cflags) $(CONFIGURE_CFLAGS)
+FINAL_CXXFLAGS = $(CPPFLAGS) $(CXXFLAGS) $(WARN_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) $(CONFIGURE_CXXFLAGS)
 FINAL_NOTMUCH_LDFLAGS = $(LDFLAGS) -Lutil -lutil -Llib -lnotmuch $(AS_NEEDED_LDFLAGS) $(GMIME_LDFLAGS) $(TALLOC_LDFLAGS)
 FINAL_NOTMUCH_LINKER = CC
 ifneq ($(LINKER_RESOLVES_LIBRARY_DEPENDENCIES),1)
@@ -255,9 +255,9 @@ notmuch_client_srcs =               \
        command-line-arguments.c\
        debugger.c              \
        gmime-filter-reply.c    \
-       gmime-filter-headers.c  \
        hooks.c                 \
        notmuch.c               \
+       notmuch-compact.c       \
        notmuch-config.c        \
        notmuch-count.c         \
        notmuch-dump.c          \
diff --git a/NEWS b/NEWS
index 722a35dcd17de2293e2b1efe449f257fcd45f81e..28788d8dc85bd3b426effaf0d5d5828c3f7f92f1 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,178 @@
+Notmuch 0.17 (2013-12-30)
+=========================
+
+Incompatible change in SHA1 computation
+---------------------------------------
+
+Previously on big endian architectures like sparc and powerpc the
+computation of SHA1 hashes was incorrect. This meant that messages
+with overlong or missing message-ids were given different computed
+message-ids than on more common little endian architectures like i386
+and amd64.  If you use notmuch on a big endian architecture, you are
+strongly advised to make a backup of your tags using `notmuch dump`
+before this upgrade. You can locate the affected files using something
+like:
+
+    notmuch dump | \
+      awk '/^notmuch-sha1-[0-9a-f]{40} / \
+        {system("notmuch search --exclude=false --output=files id:" $1)}'
+
+Command-Line Interface
+----------------------
+
+New options to better support handling duplicate messages
+
+  If more than one message file is associated with a message-id,
+  `notmuch search --output=files` will print all of them. A new
+  `--duplicate=N` option can be used to specify which duplicate to
+  print for each message.
+
+  `notmuch count` now supports `--output=files` option to output the
+  number of files associated with matching messages. This may be
+  bigger than the number of matching messages due to duplicates
+  (i.e. multiple files having the same message-id).
+
+Improved `notmuch new` performance for unchanged folders
+
+  `notmuch new` now skips over unchanged folders more efficiently,
+  which can substantially improve the performance of checking for new
+  mail in some situations (like NFS-mounted Maildirs).
+
+`notmuch reply --format=text` RFC 2047-encodes headers
+
+  Previously, this used a mix of standard MIME encoding for the reply
+  body and UTF-8 for the headers.  Now, the text format reply template
+  RFC 2047-encodes the headers, making the output a valid RFC 2822
+  message.  The JSON/sexp format is unchanged.
+
+`notmuch compact` command
+
+  The new `compact` command exposes Xapian's compaction
+  functionality through a more convenient interface than
+  `xapian-compact`. `notmuch compact` will compact the database to a
+  temporary location, optionally backup the original database, and
+  move the compacted database into place.
+
+Emacs Interface
+---------------
+
+`notmuch-tree` (formerly `notmuch-pick`) has been added to mainline
+
+  `notmuch-tree` is a threaded message view for the emacs
+  interface. Each message is one line in the results and the thread
+  structure is shown using UTF-8 box drawing characters (similar to
+  Mutt's threaded view). It comes between search and show in terms of
+  amount of output and can be useful for viewing both single threads
+  and multiple threads.
+
+  Using `notmuch-tree`
+
+  The main key entries to notmuch tree are
+
+  'z' enter a query to view using notmuch tree (works in hello,
+      search, show and tree mode itself)
+
+  'Z' view the current query in tree notmuch tree (works from search
+      and show)
+
+  Once in tree mode, keybindings are mostly in line with the rest of
+  notmuch and are all viewable with '?' as usual.
+
+  Customising `notmuch-tree`
+
+  `notmuch-tree` has several customisation variables. The most
+  significant is the first notmuch-tree-show-out which determines the
+  behaviour when selecting a message (with RET) in tree view. By
+  default tree view uses a split window showing the single message in
+  the bottom pane. However, if this option is set then it views the
+  whole thread in the complete window jumping to the selected message
+  in the thread. In either case command-prefix selects the other option.
+
+Tagging threads in search is now race-free
+
+  Previously, adding or removing a tag from a thread in a search
+  buffer would affect messages that had arrived after the search was
+  performed, resulting in, for example, archiving messages that were
+  never seen.  Tagging now affects only the messages that were in the
+  thread when the search was performed.
+
+`notmuch-hello` refreshes when switching to the buffer
+
+  The hello buffer now refreshes whenever you switch to the buffer,
+  regardless of how you get there.  You can disable automatic
+  refreshing by customizing `notmuch-hello-auto-refresh`.
+
+Specific mini-buffer prompts for tagging operations
+
+  When entering tags to add or remove, the mini-buffer prompt now
+  indicates what operation will be performed (e.g., "Tag thread", "Tag
+  message", etc).
+
+Built-in help improvements
+
+  Documentation for many commands has been improved, as displayed by
+  `notmuch-help` (usually bound to "?").  The bindings listed by
+  `notmuch-help` also now include descriptions of prefixed commands.
+
+Quote replies as they are displayed in show view
+
+  We now render the parts for reply quoting the same way they are
+  rendered for show. At this time, the notable change is that replies
+  to text/calendar are now pretty instead of raw vcalendar.
+
+Fixed inconsistent use of configured search order
+
+  All ways of interactively invoking search now honor the value of
+  `notmuch-search-oldest-first`.
+
+Common keymap for notmuch-wide bindings
+
+  Several key bindings have been moved from mode-specific keymaps to
+  the single `notmuch-common-keymap`, which is inherited by each
+  notmuch mode.  If you've customized your key bindings, you may want
+  to move some of them to the common keymap.
+
+The `notmuch-tag` function now requires a list of tag changes
+
+  For users who have scripted the Emacs interface: the `notmuch-tag`
+  API has changed.  Previously, it accepted either a list of tag
+  changes or a space-separated string of tag changes.  The latter is
+  no longer supported and the function now returns nothing.
+
+Fixed `notmuch-reply` putting reply in primary selection
+
+  On emacs 24 notmuch-reply used to put the cited text into the
+  primary selection (which could lead to inadvertently pasting this
+  cited text elsewhere). Now the primary-selection is not changed.
+
+Fixed `notmuch-show` invisible part handling
+
+  In some obscure cases part buttons and invisibility had strange
+  interactions: in particular, the default action for some parts gave
+  the wrong action. This has been fixed.
+
+Fixed `notmuch-show` attachment viewers and stderr
+
+  In emacs 24.3+ viewing an attachment could cause spurious text to
+  appear in the show buffer (any stderr or stdout the viewer
+  produced). By default this output is now discarded. For debugging,
+  setting `notmuch-show-attachment-debug` causes notmuch to keep the
+  viewer's stderr and stdout in a separate buffer.
+
+Fixed `notmuch-mua-reply` point placement when signature involved
+
+  By restricting cursor movement to body section for cursor placement
+  after signature is inserted, the cursor cannot "leak" to header
+  section anymore. Now inserted citation content will definitely go to
+  the body part of the message.
+
+Vim Interface
+-------------
+
+  It is now possible to compose new messages in the Vim interface, as
+  opposed reply to existing messages.  There is also support for
+  going straight to a search (bypassing the folders view).
+
 Notmuch 0.16 (2013-08-03)
 =========================
 
index 8f08f62f510da415afb40785d14021ffc98840ce..fa3f93b817986559334d0aaff46ea0fa51e79b24 100644 (file)
@@ -1,2 +1,2 @@
 # this file should be kept in sync with ../../../version
-__VERSION__ = '0.16'
+__VERSION__ = '0.17'
index 13f16cd3fa30f29041f4d9e6aaab1cc19aee33d9..b0d5417f21780f01a59f11bd65d9784b89831b6d 100644 (file)
@@ -13,4 +13,12 @@ ifneq ($(HAVE_STRCASESTR),1)
 notmuch_compat_srcs += $(dir)/strcasestr.c
 endif
 
+ifneq ($(HAVE_STRSEP),1)
+notmuch_compat_srcs += $(dir)/strsep.c
+endif
+
+ifneq ($(HAVE_TIMEGM),1)
+notmuch_compat_srcs += $(dir)/timegm.c
+endif
+
 SRCS := $(SRCS) $(notmuch_compat_srcs)
diff --git a/compat/check_asctime.c b/compat/check_asctime.c
new file mode 100644 (file)
index 0000000..b0e56f0
--- /dev/null
@@ -0,0 +1,11 @@
+#include <time.h>
+#include <stdio.h>
+
+int main()
+{
+    struct tm tm;
+
+    (void) asctime_r (&tm, NULL);
+
+    return (0);
+}
diff --git a/compat/check_getpwuid.c b/compat/check_getpwuid.c
new file mode 100644 (file)
index 0000000..c435eb8
--- /dev/null
@@ -0,0 +1,11 @@
+#include <stdio.h>
+#include <pwd.h>
+
+int main()
+{
+    struct passwd passwd, *ignored;
+
+    (void) getpwuid_r (0, &passwd, NULL, 0, &ignored);
+
+    return (0);
+}
index b2e27368cf0690701c81b692fc0f9dce4c7216f4..5a402d5c7693e7e6066382ce4cfe1983664ef7b0 100644 (file)
 extern "C" {
 #endif
 
+#if !STD_GETPWUID
+#define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+#if !STD_ASCTIME
+#define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+
 #if !HAVE_GETLINE
 #include <stdio.h>
 #include <unistd.h>
@@ -46,6 +53,15 @@ getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp);
 char* strcasestr(const char *haystack, const char *needle);
 #endif /* !HAVE_STRCASESTR */
 
+#if !HAVE_STRSEP
+char *strsep(char **stringp, const char *delim);
+#endif /* !HAVE_STRSEP */
+
+#if !HAVE_TIMEGM
+#include <time.h>
+time_t timegm (struct tm *tm);
+#endif /* !HAVE_TIMEGM */
+
 /* Silence gcc warnings about unused results.  These warnings exist
  * for a reason; any use of this needs to be justified. */
 #ifdef __GNUC__
diff --git a/compat/have_strsep.c b/compat/have_strsep.c
new file mode 100644 (file)
index 0000000..2abab81
--- /dev/null
@@ -0,0 +1,11 @@
+#define _GNU_SOURCE
+#include <string.h>
+
+int main()
+{
+    char *found;
+    char **stringp;
+    const char *delim;
+
+    found = strsep(stringp, delim);
+}
diff --git a/compat/have_timegm.c b/compat/have_timegm.c
new file mode 100644 (file)
index 0000000..b62b793
--- /dev/null
@@ -0,0 +1,7 @@
+#include <time.h>
+#include "compat.h"
+
+int main()
+{
+    return (int) timegm((struct tm *)0);
+}
diff --git a/compat/strsep.c b/compat/strsep.c
new file mode 100644 (file)
index 0000000..78ab9e7
--- /dev/null
@@ -0,0 +1,65 @@
+/* Copyright (C) 1992, 93, 96, 97, 98, 99, 2004 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307 USA.  */
+
+#include <string.h>
+
+/* Taken from glibc 2.6.1 */
+
+char *strsep (char **stringp, const char *delim)
+{
+  char *begin, *end;
+
+  begin = *stringp;
+  if (begin == NULL)
+    return NULL;
+
+  /* A frequent case is when the delimiter string contains only one
+     character.  Here we don't need to call the expensive `strpbrk'
+     function and instead work using `strchr'.  */
+  if (delim[0] == '\0' || delim[1] == '\0')
+    {
+      char ch = delim[0];
+
+      if (ch == '\0')
+       end = NULL;
+      else
+       {
+         if (*begin == ch)
+           end = begin;
+         else if (*begin == '\0')
+           end = NULL;
+         else
+           end = strchr (begin + 1, ch);
+       }
+    }
+  else
+    /* Find the end of the token.  */
+    end = strpbrk (begin, delim);
+
+  if (end)
+    {
+      /* Terminate the token and set *STRINGP past NUL character.  */
+      *end++ = '\0';
+      *stringp = end;
+    }
+  else
+    /* No more delimiters; this is the last token.  */
+    *stringp = NULL;
+
+  return begin;
+}
diff --git a/compat/timegm.c b/compat/timegm.c
new file mode 100644 (file)
index 0000000..3560c37
--- /dev/null
@@ -0,0 +1,56 @@
+/* timegm.c --- Implementation of replacement timegm function.
+
+   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.  */
+
+/* Copyright 2013 Blake Jones. */
+
+#include <time.h>
+#include "compat.h"
+
+static int
+leapyear (int year)
+{
+    return ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0));
+}
+
+/*
+ * This is a simple implementation of timegm() which does what is needed
+ * by create_output() -- just turns the "struct tm" into a GMT time_t.
+ * It does not normalize any of the fields of the "struct tm", nor does
+ * it set tm_wday or tm_yday.
+ */
+time_t
+timegm (struct tm *tm)
+{
+    int        monthlen[2][12] = {
+       { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+       { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+    };
+    int        year, month, days;
+
+    days = 365 * (tm->tm_year - 70);
+    for (year = 70; year < tm->tm_year; year++) {
+       if (leapyear(1900 + year)) {
+           days++;
+       }
+    }
+    for (month = 0; month < tm->tm_mon; month++) {
+       days += monthlen[leapyear(1900 + year)][month];
+    }
+    days += tm->tm_mday - 1;
+
+    return ((((days * 24) + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec);
+}
index 7bd7745fdfd2214d8bd10cde97b1feb214fb6639..04324bbb3968222d84af224089a69c59e08a407b 100644 (file)
@@ -59,6 +59,29 @@ _notmuch_search_terms()
     __ltrim_colon_completions "${cur}"
 }
 
+_notmuch_compact()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --backup)
+           _filedir
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       -*)
+           local options="--backup= --quiet"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           ;;
+    esac
+}
+
 _notmuch_config()
 {
     local cur prev words cword split
@@ -89,19 +112,23 @@ _notmuch_count()
     $split &&
     case "${prev}" in
        --output)
-           COMPREPLY=( $( compgen -W "messages threads" -- "${cur}" ) )
+           COMPREPLY=( $( compgen -W "messages threads files" -- "${cur}" ) )
            return
            ;;
        --exclude)
            COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) )
            return
            ;;
+       --input)
+           _filedir
+           return
+           ;;
     esac
 
     ! $split &&
     case "${cur}" in
        -*)
-           local options="--output= --exclude="
+           local options="--output= --exclude= --batch --input="
            compopt -o nospace
            COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
            ;;
@@ -141,6 +168,39 @@ _notmuch_dump()
     esac
 }
 
+_notmuch_insert()
+{
+    local cur prev words cword split
+    # handle tags with colons and equal signs
+    _init_completion -s -n := || return
+
+    $split &&
+    case "${prev}" in
+       --folder)
+           _filedir
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       --*)
+           local options="--create-folder --folder="
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           return
+           ;;
+       +*)
+           COMPREPLY=( $(compgen -P "+" -W "`notmuch search --output=tags \*`" -- ${cur##+}) )
+           ;;
+       -*)
+           COMPREPLY=( $(compgen -P "-" -W "`notmuch search --output=tags \*`" -- ${cur##-}) )
+           ;;
+    esac
+    # handle tags with colons
+    __ltrim_colon_completions "${cur}"
+}
+
 _notmuch_new()
 {
     local cur prev words cword split
@@ -231,7 +291,7 @@ _notmuch_search()
            return
            ;;
        --exclude)
-           COMPREPLY=( $( compgen -W "true false flag" -- "${cur}" ) )
+           COMPREPLY=( $( compgen -W "true false flag all" -- "${cur}" ) )
            return
            ;;
     esac
@@ -239,7 +299,7 @@ _notmuch_search()
     ! $split &&
     case "${cur}" in
        -*)
-           local options="--format= --output= --sort= --offset= --limit= --exclude="
+           local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate="
            compopt -o nospace
            COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
            ;;
@@ -273,7 +333,7 @@ _notmuch_show()
     ! $split &&
     case "${cur}" in
        -*)
-           local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt"
+           local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt --include-html"
            compopt -o nospace
            COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
            ;;
@@ -287,9 +347,24 @@ _notmuch_tag()
 {
     local cur prev words cword split
     # handle tags with colons and equal signs
-    _init_completion -n := || return
+    _init_completion -s -n := || return
+
+    $split &&
+    case "${prev}" in
+       --input)
+           _filedir
+           return
+           ;;
+    esac
 
+    ! $split &&
     case "${cur}" in
+       --*)
+           local options="--batch --input= --remove-all"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           return
+           ;;
        +*)
            COMPREPLY=( $(compgen -P "+" -W "`notmuch search --output=tags \*`" -- ${cur##+}) )
            ;;
@@ -307,7 +382,7 @@ _notmuch_tag()
 
 _notmuch()
 {
-    local _notmuch_commands="config count dump help new reply restore search setup show tag"
+    local _notmuch_commands="compact config count dump help insert new reply restore search setup show tag"
     local arg cur prev words cword split
     _init_completion || return
 
index 3ba1ec3497419409045a7afc4b4cb4c37f0677e1..13b60620fe3cebdd0cc00cee4a4bc088260ef720 100755 (executable)
--- a/configure
+++ b/configure
@@ -277,7 +277,8 @@ printf "Checking for Xapian development files... "
 have_xapian=0
 for xapian_config in ${XAPIAN_CONFIG}; do
     if ${xapian_config} --version > /dev/null 2>&1; then
-       printf "Yes (%s).\n" $(${xapian_config} --version | sed -e 's/.* //')
+       xapian_version=$(${xapian_config} --version | sed -e 's/.* //')
+       printf "Yes (%s).\n" ${xapian_version}
        have_xapian=1
        xapian_cxxflags=$(${xapian_config} --cxxflags)
        xapian_ldflags=$(${xapian_config} --libs)
@@ -289,6 +290,21 @@ if [ ${have_xapian} = "0" ]; then
     errors=$((errors + 1))
 fi
 
+# Compaction is only supported on Xapian > 1.2.6
+have_xapian_compact=0
+if [ ${have_xapian} = "1" ]; then
+    printf "Checking for Xapian compaction support... "
+    case "${xapian_version}" in
+        0.*|1.[01].*|1.2.[0-5])
+            printf "No (only available with Xapian > 1.2.6).\n" ;;
+        [1-9]*.[0-9]*.[0-9]*)
+            have_xapian_compact=1
+            printf "Yes.\n" ;;
+        *)
+            printf "Unknown version.\n" ;;
+    esac
+fi
+
 printf "Checking for GMime development files... "
 have_gmime=0
 IFS=';'
@@ -425,6 +441,19 @@ else
 EOF
 fi
 
+printf "Checking byte order... "
+cat> _byteorder.c <<EOF
+#include <stdio.h>
+#include <stdint.h>
+uint32_t test = 0x34333231;
+int main() { printf("%.4s\n", (const char*)&test); return 0; }
+EOF
+${CC} ${CFLAGS} _byteorder.c -o _byteorder > /dev/null 2>&1
+util_byte_order=$(./_byteorder)
+echo $util_byte_order
+
+rm -f _byteorder _byteorder.c
+
 if [ $errors -gt 0 ]; then
     cat <<EOF
 
@@ -519,6 +548,50 @@ else
 fi
 rm -f compat/have_strcasestr
 
+printf "Checking for strsep... "
+if ${CC} -o compat/have_strsep "$srcdir"/compat/have_strsep.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    have_strsep="1"
+else
+    printf "No (will use our own instead).\n"
+    have_strsep="0"
+fi
+rm -f compat/have_strsep
+
+printf "Checking for timegm... "
+if ${CC} -o compat/have_timegm "$srcdir"/compat/have_timegm.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    have_timegm="1"
+else
+    printf "No (will use our own instead).\n"
+    have_timegm="0"
+fi
+rm -f compat/have_timegm
+
+printf "Checking for standard version of getpwuid_r... "
+if ${CC} -o compat/check_getpwuid "$srcdir"/compat/check_getpwuid.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    std_getpwuid=1
+else
+    printf "No (will define _POSIX_PTHREAD_SEMANTICS to get it).\n"
+    std_getpwuid=0
+fi
+rm -f compat/check_getpwuid
+
+printf "Checking for standard version of asctime_r... "
+if ${CC} -o compat/check_asctime "$srcdir"/compat/check_asctime.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    std_asctime=1
+else
+    printf "No (will define _POSIX_PTHREAD_SEMANTICS to get it).\n"
+    std_asctime=0
+fi
+rm -f compat/check_asctime
+
 printf "int main(void){return 0;}\n" > minimal.c
 
 printf "Checking for rpath support... "
@@ -642,6 +715,9 @@ prefix = ${PREFIX}
 # LIBDIR_IN_LDCONFIG value below is still set correctly.
 libdir = ${LIBDIR:=\$(prefix)/lib}
 
+# byte order within a 32 bit word. 1234 = little, 4321 = big, 0 = guess
+UTIL_BYTE_ORDER = ${util_byte_order}
+
 # Whether libdir is in a path configured into ldconfig
 LIBDIR_IN_LDCONFIG = ${libdir_in_ldconfig}
 
@@ -681,6 +757,23 @@ HAVE_GETLINE = ${have_getline}
 # build its own version)
 HAVE_STRCASESTR = ${have_strcasestr}
 
+# Whether the strsep function is available (if not, then notmuch will
+# build its own version)
+HAVE_STRSEP = ${have_strsep}
+
+# Whether the Xapian version in use supports compaction
+HAVE_XAPIAN_COMPACT = ${have_xapian_compact}
+
+# Whether the getpwuid_r function is standards-compliant
+# (if not, then notmuch will #define _POSIX_PTHREAD_SEMANTICS
+# to enable the standards-compliant version -- needed for Solaris)
+STD_GETPWUID = ${std_getpwuid}
+
+# Whether the asctime_r function is standards-compliant
+# (if not, then notmuch will #define _POSIX_PTHREAD_SEMANTICS
+# to enable the standards-compliant version -- needed for Solaris)
+STD_ASCTIME = ${std_asctime}
+
 # Supported platforms (so far) are: LINUX, MACOSX, SOLARIS, FREEBSD, OPENBSD
 PLATFORM = ${platform}
 
@@ -725,10 +818,23 @@ WITH_ZSH = ${WITH_ZSH}
 # 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) -DHAVE_STRCASESTR=\$(HAVE_STRCASESTR)
+                  \$(VALGRIND_CFLAGS)                                   \\
+                  -DHAVE_STRCASESTR=\$(HAVE_STRCASESTR)                 \\
+                  -DHAVE_STRSEP=\$(HAVE_STRSEP)                         \\
+                  -DSTD_GETPWUID=\$(STD_GETPWUID)                       \\
+                  -DSTD_ASCTIME=\$(STD_ASCTIME)                         \\
+                  -DHAVE_XAPIAN_COMPACT=\$(HAVE_XAPIAN_COMPACT)         \\
+                  -DUTIL_BYTE_ORDER=\$(UTIL_BYTE_ORDER)
+
 CONFIGURE_CXXFLAGS = -DHAVE_GETLINE=\$(HAVE_GETLINE) \$(GMIME_CFLAGS)    \\
                     \$(TALLOC_CFLAGS) -DHAVE_VALGRIND=\$(HAVE_VALGRIND) \\
                     \$(VALGRIND_CFLAGS) \$(XAPIAN_CXXFLAGS)             \\
-                     -DHAVE_STRCASESTR=\$(HAVE_STRCASESTR)
+                    -DHAVE_STRCASESTR=\$(HAVE_STRCASESTR)               \\
+                    -DHAVE_STRSEP=\$(HAVE_STRSEP)                       \\
+                    -DSTD_GETPWUID=\$(STD_GETPWUID)                     \\
+                    -DSTD_ASCTIME=\$(STD_ASCTIME)                       \\
+                    -DHAVE_XAPIAN_COMPACT=\$(HAVE_XAPIAN_COMPACT)       \\
+                    -DUTIL_BYTE_ORDER=\$(UTIL_BYTE_ORDER)
+
 CONFIGURE_LDFLAGS =  \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(XAPIAN_LDFLAGS)
 EOF
index e00035c62ecea05842d4f6bbe0a4ca9331c9f3c3..382ac911fd188c4b52e5f33cf98950a38887954b 100644 (file)
@@ -41,11 +41,6 @@ To *run* notmuch-mutt you will need Perl with the following libraries:
   (Debian package: libstring-shellquote-perl)
 - Term::ReadLine <http://search.cpan.org/~hayashi/Term-ReadLine-Gnu/>
   (Debian package: libterm-readline-gnu-perl)
-- File::Which <http://search.cpan.org/dist/File-Which/>
-  (Debian package: libfile-which-perl)
-
-The --remove-dups option will use fdupes <https://code.google.com/p/fdupes/>
-if it is installed.  Version fdupes-1.50-PR2 or higher is required.
 
 To *build* notmuch-mutt documentation you will need:
 
index 00c5ef82d50d67d0fcac7c43794dcf6958ac3cb1..4969e4be64c3f29140e25b97ee137947058cd8ca 100755 (executable)
@@ -18,8 +18,6 @@ use Mail::Box::Maildir;
 use Pod::Usage;
 use String::ShellQuote;
 use Term::ReadLine;
-use Digest::SHA;
-use File::Which;
 
 
 my $xdg_cache_dir = "$ENV{HOME}/.cache";
@@ -36,65 +34,22 @@ sub empty_maildir($) {
     $folder->close();
 }
 
-# Match files by size and SHA-256; then delete duplicates
-sub builtin_remove_dups($) {
-    my ($maildir) = @_;
-    my (%size_to_files, %sha_to_files);
-
-    # Group files by matching sizes
-    foreach my $file (glob("$maildir/cur/*")) {
-        my $size = -s $file;
-        push(@{$size_to_files{$size}}, $file) if $size;
-    }
-
-    foreach my $same_size_files (values %size_to_files) {
-        # Don't run sha unless there is another file of the same size
-        next if scalar(@$same_size_files) < 2;
-        %sha_to_files = ();
-
-        # Group files with matching sizes by SHA-256
-        foreach my $file (@$same_size_files) {
-            open(my $fh, '<', $file) or next;
-            binmode($fh);
-            my $sha256hash = Digest::SHA->new(256)->addfile($fh)->hexdigest;
-            close($fh);
-
-            push(@{$sha_to_files{$sha256hash}}, $file);
-        }
-
-        # Remove duplicates
-        foreach my $same_sha_files (values %sha_to_files) {
-            next if scalar(@$same_sha_files) < 2;
-            unlink(@{$same_sha_files}[1..$#$same_sha_files]);
-        }
-    }
-}
-
-# Use either fdupes or the built-in scanner to detect and remove duplicate
-# search results in the maildir
-sub remove_duplicates($) {
-    my ($maildir) = @_;
-
-    my $fdupes = which("fdupes");
-    if ($fdupes) {
-      system("$fdupes --hardlinks --symlinks --delete --noprompt"
-             . " --quiet $maildir/cur/ > /dev/null");
-    } else {
-        builtin_remove_dups($maildir);
-    }
-}
-
 # search($maildir, $remove_dups, $query)
 # search mails according to $query with notmuch; store results in $maildir
 sub search($$$) {
     my ($maildir, $remove_dups, $query) = @_;
+    my $dup_option = "";
+
     $query = shell_quote($query);
 
+    if ($remove_dups) {
+      $dup_option = "--duplicate=1";
+    }
+
     empty_maildir($maildir);
-    system("notmuch search --output=files $query"
+    system("notmuch search --output=files $dup_option $query"
           . " | sed -e 's: :\\\\ :g'"
           . " | xargs --no-run-if-empty ln -s -t $maildir/cur/");
-    remove_duplicates($maildir) if ($remove_dups);
 }
 
 sub prompt($$) {
@@ -158,9 +113,7 @@ sub tag_action(@) {
     my $mid = get_message_id();
     defined $mid or die "notmuch-mutt: cannot find Message-Id, abort.\n";
 
-    system("notmuch tag "
-          . shell_quote(join(' ', @_))
-          . " id:$mid");
+    system("notmuch", "tag", @_, "--", "id:$mid");
 }
 
 sub die_usage() {
@@ -252,7 +205,10 @@ Instead of using command line search terms, prompt the user for them (only for
 
 =item --remove-dups
 
-Remove duplicates from search results.
+Remove emails with duplicate message-ids from search results.  (Passes
+--duplicate=1 to notmuch search command.)  Note this can hide search
+results if an email accidentally or maliciously uses the same message-id
+as a different email.
 
 =item -h
 
diff --git a/contrib/notmuch-pick/README b/contrib/notmuch-pick/README
deleted file mode 100644 (file)
index 4200824..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-NOTMUCH PICK
-
-Notmuch pick is an experimental threaded message view for the emacs
-interface. Each message is one line in the results and the thread
-structure is shown using UTF-8 box drawing characters (similar to
-Mutt's threaded view). It comes between search and show in terms of
-amount of output and can be useful for viewing both single threads and
-multiple threads.
-
-INSTALL
-
-Just copy the notmuch-pick.el file somewhere into emacs's load-path.
-
-Then after the "(require 'notmuch)" line in your .emacs file add
-the line "(require 'notmuch-pick nil t)". This will load notmuch-pick on
-your next emacs start.
-
-TEST
-
-Just execute run-tests.sh and it should all work (it does require that
-notmuch has already been built).
-
-USING PICK
-
-The main key entries to notmuch pick are
-
-'z' enter a query to view using notmuch pick (works in hello, search,
-    show and pick itself).
-'Z' view the current query in pick (works from search and show)
-'M-RET' view the selected thread in pick (works in search mode)
-
-Once in pick mode, keybindings are mostly in line with the rest of
-notmuch and are all viewable with '?' as usual.
-
-CUSTOMISING PICK
-
-Pick has several customisation variables. The most significant is the
-first notmuch-pick-show-out which determines the behaviour when
-selecting a message (with RET) in the pick view. By default pick uses
-a split window showing the single message in the bottom pane. However,
-if this option is set then it views the whole thread in the complete
-window jumping to the selected message in the thread. In either case
-M-RET selects the other option.
diff --git a/contrib/notmuch-pick/TODO b/contrib/notmuch-pick/TODO
deleted file mode 100644 (file)
index 8474e30..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-TODO FOR NOTMUCH-PICK
-
-(These are the things I can think of: to be added to as problems get
-reported or found!)
-
-Things that need fixing before acceptance to mainline
-
-- Review lisp to make idiomatic.
-- Unify functions with search or show where appropriate.
-- Work out a fall-back if the font does not contain box graphic characters.
-- Add extra functionality?
-
-- Remove debugging information.
-
-- Add tests (I have some but I am not sure how to add them if pick is
-  in contrib).
-
-Bugs:
-
-- The display flickers while pick is running. I have no idea why.
-
-Other todo items
-
-- c i, c f for stashing ids etc.
-
-- Perhaps the author should be "To: ???" if the message is from the user.
-
-- Is there some nice way to do use the expand citation buttons of
-  notmuch-show when using the split-pane mode?
diff --git a/contrib/notmuch-pick/notmuch-pick.el b/contrib/notmuch-pick/notmuch-pick.el
deleted file mode 100644 (file)
index 7f5f729..0000000
+++ /dev/null
@@ -1,881 +0,0 @@
-;; notmuch-pick.el --- displaying notmuch forests.
-;;
-;; Copyright © Carl Worth
-;; Copyright © David Edmondson
-;; Copyright © Mark Walters
-;;
-;; This file is part of Notmuch.
-;;
-;; Notmuch 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.
-;;
-;; Notmuch 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 Notmuch.  If not, see <http://www.gnu.org/licenses/>.
-;;
-;; Authors: David Edmondson <dme@dme.org>
-;;          Mark Walters <markwalters1009@gmail.com>
-
-(require 'mail-parse)
-
-(require 'notmuch-lib)
-(require 'notmuch-query)
-(require 'notmuch-show)
-(require 'notmuch) ;; XXX ATM, as notmuch-search-mode-map is defined here
-
-(eval-when-compile (require 'cl))
-
-(declare-function notmuch-call-notmuch-process "notmuch" (&rest args))
-(declare-function notmuch-show "notmuch-show" (&rest args))
-(declare-function notmuch-tag "notmuch" (query &rest tags))
-(declare-function notmuch-show-strip-re "notmuch-show" (subject))
-(declare-function notmuch-show-spaces-n "notmuch-show" (n))
-(declare-function notmuch-read-query "notmuch" (prompt))
-(declare-function notmuch-read-tag-changes "notmuch" (&optional initial-input &rest search-terms))
-(declare-function notmuch-update-tags "notmuch" (current-tags tag-changes))
-(declare-function notmuch-hello-trim "notmuch-hello" (search))
-(declare-function notmuch-search-find-thread-id "notmuch" ())
-(declare-function notmuch-search-find-subject "notmuch" ())
-
-;; the following variable is defined in notmuch.el
-(defvar notmuch-search-query-string)
-
-(defgroup notmuch-pick nil
-  "Showing message and thread structure."
-  :group 'notmuch)
-
-;; This is ugly. We can't run setup-show-out until it has been defined
-;; which needs the keymap to be defined. So we defer setting up to
-;; notmuch-pick-init.
-(defcustom notmuch-pick-show-out nil
-  "View selected messages in new window rather than split-pane."
-  :type 'boolean
-  :group 'notmuch-pick
-  :set (lambda (symbol value)
-        (set-default symbol value)
-        (when (fboundp 'notmuch-pick-setup-show-out)
-          (notmuch-pick-setup-show-out))))
-
-(defcustom notmuch-pick-result-format
-  `(("date" . "%12s  ")
-    ("authors" . "%-20s")
-    ("subject" . " %-54s ")
-    ("tags" . "(%s)"))
-  "Result formatting for Pick. Supported fields are: date,
-        authors, subject, tags Note: subject includes the tree
-        structure graphics, and the author string should not
-        contain whitespace (put it in the neighbouring fields
-        instead).  For example:
-        (setq notmuch-pick-result-format \(\(\"authors\" . \"%-40s\"\)
-                                             \(\"subject\" . \"%s\"\)\)\)"
-  :type '(alist :key-type (string) :value-type (string))
-  :group 'notmuch-pick)
-
-(defcustom notmuch-pick-asynchronous-parser t
-  "Use the asynchronous parser."
-  :type 'boolean
-  :group 'notmuch-pick)
-
-;; Faces for messages that match the query.
-(defface notmuch-pick-match-date-face
-  '((t :inherit default))
-  "Face used in pick mode for the date in messages matching the query."
-  :group 'notmuch-pick
-  :group 'notmuch-faces)
-
-(defface notmuch-pick-match-author-face
-  '((((class color)
-      (background dark))
-     (:foreground "OliveDrab1"))
-    (((class color)
-      (background light))
-     (:foreground "dark blue"))
-    (t
-     (:bold t)))
-  "Face used in pick mode for the date in messages matching the query."
-  :group 'notmuch-pick
-  :group 'notmuch-faces)
-
-(defface notmuch-pick-match-subject-face
-  '((t :inherit default))
-  "Face used in pick mode for the subject in messages matching the query."
-  :group 'notmuch-pick
-  :group 'notmuch-faces)
-
-(defface notmuch-pick-match-tag-face
-  '((((class color)
-      (background dark))
-     (:foreground "OliveDrab1"))
-    (((class color)
-      (background light))
-     (:foreground "navy blue" :bold t))
-    (t
-     (:bold t)))
-  "Face used in pick mode for tags in messages matching the query."
-  :group 'notmuch-pick
-  :group 'notmuch-faces)
-
-;; Faces for messages that do not match the query.
-(defface notmuch-pick-no-match-date-face
-  '((t (:foreground "gray")))
-  "Face used in pick mode for non-matching dates."
-  :group 'notmuch-pick
-  :group 'notmuch-faces)
-
-(defface notmuch-pick-no-match-subject-face
-  '((t (:foreground "gray")))
-  "Face used in pick mode for non-matching subjects."
-  :group 'notmuch-pick
-  :group 'notmuch-faces)
-
-(defface notmuch-pick-no-match-author-face
-  '((t (:foreground "gray")))
-  "Face used in pick mode for the date in messages matching the query."
-  :group 'notmuch-pick
-  :group 'notmuch-faces)
-
-(defface notmuch-pick-no-match-tag-face
-  '((t (:foreground "gray")))
-  "Face used in pick mode face for non-matching tags."
-  :group 'notmuch-pick
-  :group 'notmuch-faces)
-
-(defvar notmuch-pick-previous-subject
-  "The subject of the most recent result shown during the async display")
-(make-variable-buffer-local 'notmuch-pick-previous-subject)
-
-(defvar notmuch-pick-basic-query nil
-  "A buffer local copy of argument query to the function notmuch-pick")
-(make-variable-buffer-local 'notmuch-pick-basic-query)
-
-(defvar notmuch-pick-query-context nil
-  "A buffer local copy of argument query-context to the function notmuch-pick")
-(make-variable-buffer-local 'notmuch-pick-query-context)
-
-(defvar notmuch-pick-target-msg nil
-  "A buffer local copy of argument target to the function notmuch-pick")
-(make-variable-buffer-local 'notmuch-pick-target-msg)
-
-(defvar notmuch-pick-open-target nil
-  "A buffer local copy of argument open-target to the function notmuch-pick")
-(make-variable-buffer-local 'notmuch-pick-open-target)
-
-(defvar notmuch-pick-buffer-name nil
-  "A buffer local copy of argument buffer-name to the function notmuch-pick")
-(make-variable-buffer-local 'notmuch-pick-buffer-name)
-
-(defvar notmuch-pick-message-window nil
-  "The window of the message pane.
-
-It is set in both the pick buffer and the child show buffer. It
-is used to try and close the message pane when quitting pick or
-the child show buffer.")
-(make-variable-buffer-local 'notmuch-pick-message-window)
-(put 'notmuch-pick-message-window 'permanent-local t)
-
-(defvar notmuch-pick-message-buffer nil
-  "The buffer name of the show buffer in the message pane.
-
-This is used to try and make sure we don't close the message pane
-if the user has loaded a different buffer in that window.")
-(make-variable-buffer-local 'notmuch-pick-message-buffer)
-(put 'notmuch-pick-message-buffer 'permanent-local t)
-
-(defvar notmuch-pick-mode-map
-  (let ((map (make-sparse-keymap)))
-    (define-key map [mouse-1] 'notmuch-pick-show-message)
-    (define-key map "q" 'notmuch-pick-quit)
-    (define-key map "x" 'notmuch-pick-quit)
-    (define-key map "?" 'notmuch-help)
-    (define-key map "a" 'notmuch-pick-archive-message-then-next)
-    (define-key map "=" 'notmuch-pick-refresh-view)
-    (define-key map "s" 'notmuch-pick-to-search)
-    (define-key map "z" 'notmuch-pick-to-pick)
-    (define-key map "m" 'notmuch-pick-new-mail)
-    (define-key map "f" 'notmuch-pick-forward-message)
-    (define-key map "r" 'notmuch-pick-reply-sender)
-    (define-key map "R" 'notmuch-pick-reply)
-    (define-key map "n" 'notmuch-pick-next-matching-message)
-    (define-key map "p" 'notmuch-pick-prev-matching-message)
-    (define-key map "N" 'notmuch-pick-next-message)
-    (define-key map "P" 'notmuch-pick-prev-message)
-    (define-key map "|" 'notmuch-pick-pipe-message)
-    (define-key map "-" 'notmuch-pick-remove-tag)
-    (define-key map "+" 'notmuch-pick-add-tag)
-    (define-key map " " 'notmuch-pick-scroll-or-next)
-    (define-key map "b" 'notmuch-pick-scroll-message-window-back)
-    map))
-(fset 'notmuch-pick-mode-map notmuch-pick-mode-map)
-
-(defun notmuch-pick-setup-show-out ()
-  "Set up the keymap for showing a thread
-
-This uses the value of the defcustom notmuch-pick-show-out to
-decide whether to show a message in the message pane or in the
-whole window."
-  (let ((map notmuch-pick-mode-map))
-    (if notmuch-pick-show-out
-       (progn
-         (define-key map (kbd "M-RET") 'notmuch-pick-show-message)
-         (define-key map (kbd "RET") 'notmuch-pick-show-message-out))
-      (progn
-       (define-key map (kbd "RET") 'notmuch-pick-show-message)
-       (define-key map (kbd "M-RET") 'notmuch-pick-show-message-out)))))
-
-(defun notmuch-pick-get-message-properties ()
-  "Return the properties of the current message as a plist.
-
-Some useful entries are:
-:headers - Property list containing the headers :Date, :Subject, :From, etc.
-:tags - Tags for this message"
-  (save-excursion
-    (beginning-of-line)
-    (get-text-property (point) :notmuch-message-properties)))
-
-(defun notmuch-pick-set-message-properties (props)
-  (save-excursion
-    (beginning-of-line)
-    (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))
-
-(defun notmuch-pick-set-prop (prop val &optional props)
-  (let ((inhibit-read-only t)
-       (props (or props
-                  (notmuch-pick-get-message-properties))))
-    (plist-put props prop val)
-    (notmuch-pick-set-message-properties props)))
-
-(defun notmuch-pick-get-prop (prop &optional props)
-  (let ((props (or props
-                  (notmuch-pick-get-message-properties))))
-    (plist-get props prop)))
-
-(defun notmuch-pick-set-tags (tags)
-  "Set the tags of the current message."
-  (notmuch-pick-set-prop :tags tags))
-
-(defun notmuch-pick-get-tags ()
-  "Return the tags of the current message."
-  (notmuch-pick-get-prop :tags))
-
-(defun notmuch-pick-get-message-id ()
-  "Return the message id of the current message."
-  (let ((id (notmuch-pick-get-prop :id)))
-    (if id
-       (notmuch-id-to-query id)
-      nil)))
-
-(defun notmuch-pick-get-match ()
-  "Return whether the current message is a match."
-  (interactive)
-  (notmuch-pick-get-prop :match))
-
-(defun notmuch-pick-refresh-result ()
-  "Redisplay the current message line.
-
-This redisplays the current line based on the messages
-properties (as they are now). This is used when tags are
-updated."
-  (let ((init-point (point))
-       (end (line-end-position))
-       (msg (notmuch-pick-get-message-properties))
-       (inhibit-read-only t))
-    (beginning-of-line)
-    ;; This is a little tricky: we override
-    ;; notmuch-pick-previous-subject to get the decision between
-    ;; ... and a subject right and it stops notmuch-pick-insert-msg
-    ;; from overwriting the buffer local copy of
-    ;; notmuch-pick-previous-subject if this is called while the
-    ;; buffer is displaying.
-    (let ((notmuch-pick-previous-subject (notmuch-pick-get-prop :previous-subject)))
-      (delete-region (point) (1+ (line-end-position)))
-      (notmuch-pick-insert-msg msg))
-    (let ((new-end (line-end-position)))
-      (goto-char (if (= init-point end)
-                    new-end
-                  (min init-point (- new-end 1)))))))
-
-(defun notmuch-pick-tag-update-display (&optional tag-changes)
-  "Update display for TAG-CHANGES to current message.
-
-Does NOT change the database."
-  (let* ((current-tags (notmuch-pick-get-tags))
-        (new-tags (notmuch-update-tags current-tags tag-changes)))
-    (unless (equal current-tags new-tags)
-      (notmuch-pick-set-tags new-tags)
-      (notmuch-pick-refresh-result))))
-
-(defun notmuch-pick-tag (&optional tag-changes)
-  "Change tags for the current message"
-  (interactive)
-  (setq tag-changes (notmuch-tag (notmuch-pick-get-message-id) tag-changes))
-  (notmuch-pick-tag-update-display tag-changes))
-
-(defun notmuch-pick-add-tag ()
-  "Same as `notmuch-pick-tag' but sets initial input to '+'."
-  (interactive)
-  (notmuch-pick-tag "+"))
-
-(defun notmuch-pick-remove-tag ()
-  "Same as `notmuch-pick-tag' but sets initial input to '-'."
-  (interactive)
-  (notmuch-pick-tag "-"))
-
-;; The next two functions close the message window before searching or
-;; picking but they do so after the user has entered the query (in
-;; case the user was basing the query on something in the message
-;; window).
-
-(defun notmuch-pick-to-search ()
-  "Run \"notmuch search\" with the given `query' and display results."
-  (interactive)
-  (let ((query (notmuch-read-query "Notmuch search: ")))
-    (notmuch-pick-close-message-window)
-    (notmuch-search query)))
-
-(defun notmuch-pick-to-pick ()
-  "Run a query and display results in experimental notmuch-pick mode"
-  (interactive)
-  (let ((query (notmuch-read-query "Notmuch pick: ")))
-    (notmuch-pick-close-message-window)
-    (notmuch-pick query)))
-
-;; This function should be in notmuch-hello.el but we are trying to
-;; minimise impact on the rest of the codebase.
-(defun notmuch-pick-from-hello (&optional search)
-  "Run a query and display results in experimental notmuch-pick mode"
-  (interactive)
-  (unless (null search)
-    (setq search (notmuch-hello-trim search))
-    (let ((history-delete-duplicates t))
-      (add-to-history 'notmuch-search-history search)))
-  (notmuch-pick search))
-
-;; This function should be in notmuch-show.el but be we trying to
-;; minimise impact on the rest of the codebase.
-(defun notmuch-pick-from-show-current-query ()
-  "Call notmuch pick with the current query"
-  (interactive)
-  (notmuch-pick notmuch-show-thread-id
-               notmuch-show-query-context
-               (notmuch-show-get-message-id)))
-
-;; This function should be in notmuch.el but be we trying to minimise
-;; impact on the rest of the codebase.
-(defun notmuch-pick-from-search-current-query ()
-  "Call notmuch pick with the current query"
-  (interactive)
-  (notmuch-pick notmuch-search-query-string))
-
-;; This function should be in notmuch.el but be we trying to minimise
-;; impact on the rest of the codebase.
-(defun notmuch-pick-from-search-thread ()
-  "Show the selected thread with notmuch-pick"
-  (interactive)
-  (notmuch-pick (notmuch-search-find-thread-id)
-                notmuch-search-query-string
-               nil
-                (notmuch-prettify-subject (notmuch-search-find-subject))
-               t))
-
-(defun notmuch-pick-message-window-kill-hook ()
-  "Close the message pane when exiting the show buffer."
-  (let ((buffer (current-buffer)))
-    (when (and (window-live-p notmuch-pick-message-window)
-              (eq (window-buffer notmuch-pick-message-window) buffer))
-      ;; We do not want an error if this is the sole window in the
-      ;; frame and I do not know how to test for that in emacs pre
-      ;; 24. Hence we just ignore-errors.
-      (ignore-errors
-       (delete-window notmuch-pick-message-window)))))
-
-(defun notmuch-pick-show-message ()
-  "Show the current message (in split-pane)."
-  (interactive)
-  (let ((id (notmuch-pick-get-message-id))
-       (inhibit-read-only t)
-       buffer)
-    (when id
-      ;; We close and reopen the window to kill off un-needed buffers
-      ;; this might cause flickering but seems ok.
-      (notmuch-pick-close-message-window)
-      (setq notmuch-pick-message-window
-           (split-window-vertically (/ (window-height) 4)))
-      (with-selected-window notmuch-pick-message-window
-       ;; Since we are only displaying one message do not indent.
-       (let ((notmuch-show-indent-messages-width 0)
-             (notmuch-show-only-matching-messages t))
-         (setq buffer (notmuch-show id nil nil nil))))
-      ;; We need the `let' as notmuch-pick-message-window is buffer local.
-      (let ((window notmuch-pick-message-window))
-       (with-current-buffer buffer
-         (setq notmuch-pick-message-window window)
-         (add-hook 'kill-buffer-hook 'notmuch-pick-message-window-kill-hook)))
-      (when notmuch-show-mark-read-tags
-       (notmuch-pick-tag-update-display notmuch-show-mark-read-tags))
-      (setq notmuch-pick-message-buffer buffer))))
-
-(defun notmuch-pick-show-message-out ()
-  "Show the current message (in whole window)."
-  (interactive)
-  (let ((id (notmuch-pick-get-message-id))
-       (inhibit-read-only t)
-       buffer)
-    (when id
-      ;; We close the window to kill off un-needed buffers.
-      (notmuch-pick-close-message-window)
-      (notmuch-show id nil nil nil))))
-
-(defun notmuch-pick-scroll-message-window ()
-  "Scroll the message window (if it exists)"
-  (interactive)
-  (when (window-live-p notmuch-pick-message-window)
-    (with-selected-window notmuch-pick-message-window
-      (if (pos-visible-in-window-p (point-max))
-         t
-       (scroll-up)))))
-
-(defun notmuch-pick-scroll-message-window-back ()
-  "Scroll the message window back(if it exists)"
-  (interactive)
-  (when (window-live-p notmuch-pick-message-window)
-    (with-selected-window notmuch-pick-message-window
-      (if (pos-visible-in-window-p (point-min))
-         t
-       (scroll-down)))))
-
-(defun notmuch-pick-scroll-or-next ()
-  "Scroll the message window. If it at end go to next message."
-  (interactive)
-  (when (notmuch-pick-scroll-message-window)
-    (notmuch-pick-next-matching-message)))
-
-(defun notmuch-pick-quit ()
-  "Close the split view or exit pick."
-  (interactive)
-  (unless (notmuch-pick-close-message-window)
-    (kill-buffer (current-buffer))))
-
-(defun notmuch-pick-close-message-window ()
-  "Close the message-window. Return t if close succeeds."
-  (interactive)
-  (when (and (window-live-p notmuch-pick-message-window)
-            (eq (window-buffer notmuch-pick-message-window) notmuch-pick-message-buffer))
-    (delete-window notmuch-pick-message-window)
-    (unless (get-buffer-window-list notmuch-pick-message-buffer)
-      (kill-buffer notmuch-pick-message-buffer))
-    t))
-
-(defun notmuch-pick-archive-message (&optional unarchive)
-  "Archive the current message.
-
-Archive the current message by applying the tag changes in
-`notmuch-archive-tags' to it. If a prefix argument is given, the
-message will be \"unarchived\", i.e. the tag changes in
-`notmuch-archive-tags' will be reversed."
-  (interactive "P")
-  (when notmuch-archive-tags
-    (apply 'notmuch-pick-tag
-          (notmuch-tag-change-list notmuch-archive-tags unarchive))))
-
-(defun notmuch-pick-archive-message-then-next (&optional unarchive)
-  "Archive the current message and move to next matching message."
-  (interactive "P")
-  (notmuch-pick-archive-message unarchive)
-  (notmuch-pick-next-matching-message))
-
-(defun notmuch-pick-next-message ()
-  "Move to next message."
-  (interactive)
-  (forward-line)
-  (when (window-live-p notmuch-pick-message-window)
-    (notmuch-pick-show-message)))
-
-(defun notmuch-pick-prev-message ()
-  "Move to previous message."
-  (interactive)
-  (forward-line -1)
-  (when (window-live-p notmuch-pick-message-window)
-    (notmuch-pick-show-message)))
-
-(defun notmuch-pick-prev-matching-message ()
-  "Move to previous matching message."
-  (interactive)
-  (forward-line -1)
-  (while (and (not (bobp)) (not (notmuch-pick-get-match)))
-    (forward-line -1))
-  (when (window-live-p notmuch-pick-message-window)
-    (notmuch-pick-show-message)))
-
-(defun notmuch-pick-next-matching-message ()
-  "Move to next matching message."
-  (interactive)
-  (forward-line)
-  (while (and (not (eobp)) (not (notmuch-pick-get-match)))
-    (forward-line))
-  (when (window-live-p notmuch-pick-message-window)
-    (notmuch-pick-show-message)))
-
-(defun notmuch-pick-refresh-view ()
-  "Refresh view."
-  (interactive)
-  (let ((inhibit-read-only t)
-       (basic-query notmuch-pick-basic-query)
-       (query-context notmuch-pick-query-context)
-       (target (notmuch-pick-get-message-id))
-       (buffer-name notmuch-pick-buffer-name))
-    (erase-buffer)
-    (notmuch-pick-worker basic-query
-                        query-context
-                        target
-                        (get-buffer buffer-name))))
-
-(defmacro with-current-notmuch-pick-message (&rest body)
-  "Evaluate body with current buffer set to the text of current message"
-  `(save-excursion
-     (let ((id (notmuch-pick-get-message-id)))
-       (let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*"))))
-         (with-current-buffer buf
-           (call-process notmuch-command nil t nil "show" "--format=raw" id)
-           ,@body)
-        (kill-buffer buf)))))
-
-(defun notmuch-pick-new-mail (&optional prompt-for-sender)
-  "Compose new mail."
-  (interactive "P")
-  (notmuch-pick-close-message-window)
-  (notmuch-mua-new-mail prompt-for-sender ))
-
-(defun notmuch-pick-forward-message (&optional prompt-for-sender)
-  "Forward the current message."
-  (interactive "P")
-  (notmuch-pick-close-message-window)
-  (with-current-notmuch-pick-message
-   (notmuch-mua-new-forward-message prompt-for-sender)))
-
-(defun notmuch-pick-reply (&optional prompt-for-sender)
-  "Reply to the sender and all recipients of the current message."
-  (interactive "P")
-  (notmuch-pick-close-message-window)
-  (notmuch-mua-new-reply (notmuch-pick-get-message-id) prompt-for-sender t))
-
-(defun notmuch-pick-reply-sender (&optional prompt-for-sender)
-  "Reply to the sender of the current message."
-  (interactive "P")
-  (notmuch-pick-close-message-window)
-  (notmuch-mua-new-reply (notmuch-pick-get-message-id) prompt-for-sender nil))
-
-;; Shamelessly stolen from notmuch-show.el: maybe should be unified.
-(defun notmuch-pick-pipe-message (command)
-  "Pipe the contents of the current message to the given command.
-
-The given command will be executed with the raw contents of the
-current email message as stdin. Anything printed by the command
-to stdout or stderr will appear in the *notmuch-pipe* buffer.
-
-When invoked with a prefix argument, the command will receive all
-open messages in the current thread (formatted as an mbox) rather
-than only the current message."
-  (interactive "sPipe message to command: ")
-  (let ((shell-command
-        (concat notmuch-command " show --format=raw "
-                (shell-quote-argument (notmuch-pick-get-message-id)) " | " command))
-        (buf (get-buffer-create (concat "*notmuch-pipe*"))))
-    (with-current-buffer buf
-      (setq buffer-read-only nil)
-      (erase-buffer)
-      (let ((exit-code (call-process-shell-command shell-command nil buf)))
-       (goto-char (point-max))
-       (set-buffer-modified-p nil)
-       (setq buffer-read-only t)
-       (unless (zerop exit-code)
-         (switch-to-buffer-other-window buf)
-         (message (format "Command '%s' exited abnormally with code %d"
-                          shell-command exit-code)))))))
-
-(defun notmuch-pick-clean-address (address)
-  "Try to clean a single email ADDRESS for display. Return
-AUTHOR_NAME if present, otherwise return AUTHOR_EMAIL. Return
-unchanged ADDRESS if parsing fails."
-  (let* ((clean-address (notmuch-clean-address address))
-        (p-address (car clean-address))
-        (p-name (cdr clean-address)))
-
-    ;; If we have a name return that otherwise return the address.
-    (or p-name p-address)))
-
-(defun notmuch-pick-insert-field (field format-string msg)
-  (let* ((headers (plist-get msg :headers))
-       (match (plist-get msg :match)))
-    (cond
-     ((string-equal field "date")
-      (let ((face (if match
-                     'notmuch-pick-match-date-face
-                   'notmuch-pick-no-match-date-face)))
-       (insert (propertize (format format-string (plist-get msg :date_relative))
-                           'face face))))
-
-     ((string-equal field "subject")
-      (let ((tree-status (plist-get msg :tree-status))
-           (bare-subject (notmuch-show-strip-re (plist-get headers :Subject)))
-           (face (if match
-                     'notmuch-pick-match-subject-face
-                   'notmuch-pick-no-match-subject-face)))
-       (insert (propertize (format format-string
-                                   (concat
-                                    (mapconcat #'identity (reverse tree-status) "")
-                                    (if (string= notmuch-pick-previous-subject bare-subject)
-                                        " ..."
-                                      bare-subject)))
-                           'face face))
-       (setq notmuch-pick-previous-subject bare-subject)))
-
-     ((string-equal field "authors")
-      (let ((author (notmuch-pick-clean-address (plist-get headers :From)))
-           (len (length (format format-string "")))
-           (face (if match
-                     'notmuch-pick-match-author-face
-                   'notmuch-pick-no-match-author-face)))
-       (when (> (length author) len)
-         (setq author (substring author 0 len)))
-       (insert (propertize (format format-string author)
-                           'face face))))
-
-     ((string-equal field "tags")
-      (let ((tags (plist-get msg :tags))
-           (face (if match
-                         'notmuch-pick-match-tag-face
-                       'notmuch-pick-no-match-tag-face)))
-       (when tags
-         (insert (propertize (format format-string
-                                     (mapconcat #'identity tags ", "))
-                             'face face))))))))
-
-(defun notmuch-pick-insert-msg (msg)
-  "Insert the message MSG according to notmuch-pick-result-format"
-  ;; We need to save the previous subject as it will get overwritten
-  ;; by the insert-field calls.
-  (let ((previous-subject notmuch-pick-previous-subject))
-    (dolist (spec notmuch-pick-result-format)
-      (notmuch-pick-insert-field (car spec) (cdr spec) msg))
-    (notmuch-pick-set-message-properties msg)
-    (notmuch-pick-set-prop :previous-subject previous-subject)
-    (insert "\n")))
-
-(defun notmuch-pick-goto-and-insert-msg (msg)
-  "Insert msg at the end of the buffer. Move point to msg if it is the target"
-  (save-excursion
-    (goto-char (point-max))
-    (notmuch-pick-insert-msg msg))
-  (let ((msg-id (notmuch-id-to-query (plist-get msg :id)))
-       (target notmuch-pick-target-msg))
-    (when (or (and (not target) (plist-get msg :match))
-             (string= msg-id target))
-      (setq notmuch-pick-target-msg "found")
-      (goto-char (point-max))
-      (forward-line -1)
-      (when notmuch-pick-open-target
-       (notmuch-pick-show-message)))))
-
-(defun notmuch-pick-insert-tree (tree depth tree-status first last)
-  "Insert the message tree TREE at depth DEPTH in the current thread.
-
-A message tree is another name for a single sub-thread: i.e., a
-message together with all its descendents."
-  (let ((msg (car tree))
-       (replies (cadr tree)))
-
-      (cond
-       ((and (< 0 depth) (not last))
-       (push "├" tree-status))
-       ((and (< 0 depth) last)
-       (push "╰" tree-status))
-       ((and (eq 0 depth) first last)
-;;       (push "─" tree-status)) choice between this and next line is matter of taste.
-       (push " " tree-status))
-       ((and (eq 0 depth) first (not last))
-         (push "┬" tree-status))
-       ((and (eq 0 depth) (not first) last)
-       (push "╰" tree-status))
-       ((and (eq 0 depth) (not first) (not last))
-       (push "├" tree-status)))
-
-      (push (concat (if replies "┬" "─") "►") tree-status)
-      (notmuch-pick-goto-and-insert-msg (plist-put msg :tree-status tree-status))
-      (pop tree-status)
-      (pop tree-status)
-
-      (if last
-         (push " " tree-status)
-       (push "│" tree-status))
-
-    (notmuch-pick-insert-thread replies (1+ depth) tree-status)))
-
-(defun notmuch-pick-insert-thread (thread depth tree-status)
-  "Insert the collection of sibling sub-threads THREAD at depth DEPTH in the current forest."
-  (let ((n (length thread)))
-    (loop for tree in thread
-         for count from 1 to n
-
-         do (notmuch-pick-insert-tree tree depth tree-status (eq count 1) (eq count n)))))
-
-(defun notmuch-pick-insert-forest-thread (forest-thread)
-  "Insert a single complete thread."
-  (let (tree-status)
-    ;; Reset at the start of each main thread.
-    (setq notmuch-pick-previous-subject nil)
-    (notmuch-pick-insert-thread forest-thread 0 tree-status)))
-
-(defun notmuch-pick-insert-forest (forest)
-  "Insert a forest of threads.
-
-This function inserts a collection of several complete threads as
-passed to it by notmuch-pick-process-filter."
-  (mapc 'notmuch-pick-insert-forest-thread forest))
-
-(defun notmuch-pick-mode ()
-  "Major mode displaying messages (as opposed to threads) of of a notmuch search.
-
-This buffer contains the results of a \"notmuch pick\" of your
-email archives. Each line in the buffer represents a single
-message giving the relative date, the author, subject, and any
-tags.
-
-Pressing \\[notmuch-pick-show-message] on any line displays that message.
-
-Complete list of currently available key bindings:
-
-\\{notmuch-pick-mode-map}"
-
-  (interactive)
-  (kill-all-local-variables)
-  (use-local-map notmuch-pick-mode-map)
-  (setq major-mode 'notmuch-pick-mode
-       mode-name "notmuch-pick")
-  (hl-line-mode 1)
-  (setq buffer-read-only t
-       truncate-lines t))
-
-(defun notmuch-pick-process-sentinel (proc msg)
-  "Add a message to let user know when \"notmuch pick\" exits"
-  (let ((buffer (process-buffer proc))
-       (status (process-status proc))
-       (exit-status (process-exit-status proc))
-       (never-found-target-thread nil))
-    (when (memq status '(exit signal))
-        (kill-buffer (process-get proc 'parse-buf))
-       (if (buffer-live-p buffer)
-           (with-current-buffer buffer
-             (save-excursion
-               (let ((inhibit-read-only t)
-                     (atbob (bobp)))
-                 (goto-char (point-max))
-                 (if (eq status 'signal)
-                     (insert "Incomplete search results (pick process was killed).\n"))
-                 (when (eq status 'exit)
-                   (insert "End of search results.")
-                   (unless (= exit-status 0)
-                     (insert (format " (process returned %d)" exit-status)))
-                   (insert "\n")))))))))
-
-(defun notmuch-pick-process-filter (proc string)
-  "Process and filter the output of \"notmuch show\" (for pick)"
-  (let ((results-buf (process-buffer proc))
-        (parse-buf (process-get proc 'parse-buf))
-        (inhibit-read-only t)
-        done)
-    (if (not (buffer-live-p results-buf))
-        (delete-process proc)
-      (with-current-buffer parse-buf
-        ;; Insert new data
-        (save-excursion
-          (goto-char (point-max))
-          (insert string))
-       (notmuch-sexp-parse-partial-list 'notmuch-pick-insert-forest-thread
-                                        results-buf)))))
-
-(defun notmuch-pick-worker (basic-query &optional query-context target buffer open-target)
-  (interactive)
-  (notmuch-pick-mode)
-  (setq notmuch-pick-basic-query basic-query)
-  (setq notmuch-pick-query-context query-context)
-  (setq notmuch-pick-buffer-name (buffer-name buffer))
-  (setq notmuch-pick-target-msg target)
-  (setq notmuch-pick-open-target open-target)
-
-  (erase-buffer)
-  (goto-char (point-min))
-  (let* ((search-args (concat basic-query
-                      (if query-context (concat " and (" query-context ")"))
-                      ))
-        (message-arg "--entire-thread"))
-    (if (equal (car (process-lines notmuch-command "count" search-args)) "0")
-       (setq search-args basic-query))
-    (if notmuch-pick-asynchronous-parser
-       (let ((proc (notmuch-start-notmuch
-                    "notmuch-pick" buffer #'notmuch-pick-process-sentinel
-                    "show" "--body=false" "--format=sexp"
-                    message-arg search-args))
-             ;; Use a scratch buffer to accumulate partial output.
-              ;; This buffer will be killed by the sentinel, which
-              ;; should be called no matter how the process dies.
-              (parse-buf (generate-new-buffer " *notmuch pick parse*")))
-          (process-put proc 'parse-buf parse-buf)
-         (set-process-filter proc 'notmuch-pick-process-filter)
-         (set-process-query-on-exit-flag proc nil))
-      (progn
-       (notmuch-pick-insert-forest
-        (notmuch-query-get-threads
-         (list "--body=false" message-arg search-args)))
-       (save-excursion
-         (goto-char (point-max))
-         (insert "End of search results.\n"))))))
-
-
-(defun notmuch-pick (&optional query query-context target buffer-name open-target)
-  "Run notmuch pick with the given `query' and display the results.
-
-The arguments are:
-  QUERY: the main query. This can be any query but in many cases will be
-      a single thread. If nil this is read interactively from the minibuffer.
-  QUERY-CONTEXT: is an additional term for the query. The query used
-      is QUERY and QUERY-CONTEXT unless that does not match any messages
-      in which case we fall back to just QUERY.
-  TARGET: A message ID (with the id: prefix) that will be made
-      current if it appears in the pick results.
-  BUFFER-NAME: the name of the buffer to show the pick tree. If
-      it is nil \"*notmuch-pick\" followed by QUERY is used.
-  OPEN-TARGET: If TRUE open the target message in the message pane."
-  (interactive "sNotmuch pick: ")
-  (if (null query)
-      (setq query (notmuch-read-query "Notmuch pick: ")))
-  (let ((buffer (get-buffer-create (generate-new-buffer-name
-                                   (or buffer-name
-                                       (concat "*notmuch-pick-" query "*")))))
-       (inhibit-read-only t))
-
-    (switch-to-buffer buffer)
-    ;; Don't track undo information for this buffer
-    (set 'buffer-undo-list t)
-
-    (notmuch-pick-worker query query-context target buffer open-target)
-
-    (setq truncate-lines t)))
-
-
-;; Set up key bindings from the rest of notmuch.
-(define-key 'notmuch-search-mode-map "z" 'notmuch-pick)
-(define-key 'notmuch-search-mode-map "Z" 'notmuch-pick-from-search-current-query)
-(define-key 'notmuch-search-mode-map (kbd "M-RET") 'notmuch-pick-from-search-thread)
-(define-key 'notmuch-hello-mode-map "z" 'notmuch-pick-from-hello)
-(define-key 'notmuch-show-mode-map "z" 'notmuch-pick)
-(define-key 'notmuch-show-mode-map "Z" 'notmuch-pick-from-show-current-query)
-(notmuch-pick-setup-show-out)
-(message "Initialised notmuch-pick")
-
-(provide 'notmuch-pick)
diff --git a/contrib/notmuch-pick/run-tests.sh b/contrib/notmuch-pick/run-tests.sh
deleted file mode 100755 (executable)
index 7ddc9cc..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env bash
-
-set -eu
-
-fail() {
-    echo ERROR $1
-    exit 1
-}
-
-TESTS="emacs-pick emacs-pick-sync"
-TESTFILES="$TESTS pick.expected-output"
-
-export PICK_DIR="`cd \`dirname "$0"\` && pwd`"
-PICK_TEST_DIR="$PICK_DIR/test"
-
-
-for f in $TESTFILES
-do
-    test -f "$PICK_TEST_DIR/$f" || test -d "$PICK_TEST_DIR/$f" || fail "$PICK_TEST_DIR/$f does not exist"
-done
-
-cd "$PICK_DIR/../../test"
-
-test -x ../notmuch || fail "`cd .. && pwd`/notmuch has not been built"
-
-for f in $TESTFILES
-do
-    if test -f "$f"
-    then
-       fail "$f exists"
-    fi
-done
-
-trap "rm -f $TESTFILES" 0
-
-for f in $TESTFILES
-do
-    ln -s "$PICK_TEST_DIR/$f" .
-done
-
-#don't exec -- traps would not run.
-for f in $TESTS
-do
-    echo $f
-    ./$f
-done
diff --git a/contrib/notmuch-pick/test/emacs-pick b/contrib/notmuch-pick/test/emacs-pick
deleted file mode 100755 (executable)
index eed5f02..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/usr/bin/env bash
-
-test_description="emacs pick interface"
-. test-lib.sh
-
-EXPECTED=$TEST_DIRECTORY/pick.expected-output
-
-add_email_corpus
-test_begin_subtest "Do we have emacs"
-test_emacs '(insert "hello\n")
-           (test-output)'
-cat <<EOF >EXPECTED
-hello
-EOF
-test_expect_equal_file OUTPUT EXPECTED
-
-test_begin_subtest "Basic notmuch-pick view in emacs"
-test_emacs '(add-to-list (quote load-path) "'$PICK_DIR'")
-           (require (quote notmuch-pick))
-           (notmuch-pick "tag:inbox")
-           (notmuch-test-wait)
-           (test-output)
-           (delete-other-windows)'
-test_expect_equal_file OUTPUT $EXPECTED/notmuch-pick-tag-inbox
-
-test_begin_subtest "Navigation of notmuch-hello to search results"
-test_emacs '(notmuch-hello)
-           (goto-char (point-min))
-           (re-search-forward "inbox")
-           (widget-button-press (1- (point)))
-           (notmuch-test-wait)
-           (notmuch-pick-from-search-current-query)
-           (notmuch-test-wait)
-           (test-output)
-           (delete-other-windows)'
-test_expect_equal_file OUTPUT $EXPECTED/notmuch-pick-tag-inbox
-
-test_begin_subtest "Pick of a single thread (from search)"
-test_emacs '(notmuch-hello)
-           (goto-char (point-min))
-           (re-search-forward "inbox")
-           (widget-button-press (1- (point)))
-           (notmuch-test-wait)
-           (notmuch-pick-from-search-thread)
-           (notmuch-test-wait)
-           (test-output)
-           (delete-other-windows)'
-test_expect_equal_file OUTPUT $EXPECTED/notmuch-pick-single-thread
-
-test_begin_subtest "Pick of a single thread (from show)"
-test_emacs '(notmuch-hello)
-           (goto-char (point-min))
-           (re-search-forward "inbox")
-           (widget-button-press (1- (point)))
-           (notmuch-test-wait)
-           (notmuch-search-show-thread)
-           (notmuch-pick-from-show-current-query)
-           (notmuch-test-wait)
-           (test-output)
-           (delete-other-windows)'
-test_expect_equal_file OUTPUT $EXPECTED/notmuch-pick-single-thread
-
-test_begin_subtest "Message window of pick"
-test_emacs '(notmuch-hello)
-           (goto-char (point-min))
-           (re-search-forward "inbox")
-           (widget-button-press (1- (point)))
-           (notmuch-test-wait)
-           (notmuch-search-next-thread)
-           (notmuch-pick-from-search-thread)
-           (notmuch-test-wait)
-           (select-window notmuch-pick-message-window)
-           (test-output)
-           (delete-other-windows)'
-cp OUTPUT /tmp/mjwout
-test_expect_equal_file OUTPUT $EXPECTED/notmuch-pick-show-window
-
-test_done
diff --git a/contrib/notmuch-pick/test/emacs-pick-sync b/contrib/notmuch-pick/test/emacs-pick-sync
deleted file mode 100755 (executable)
index a7da0ff..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env bash
-
-test_description="emacs pick interface (sync parser)"
-. test-lib.sh
-
-EXPECTED=$TEST_DIRECTORY/pick.expected-output
-
-add_email_corpus
-test_begin_subtest "Do we have emacs"
-test_emacs '(insert "hello\n")
-           (test-output)'
-cat <<EOF >EXPECTED
-hello
-EOF
-test_expect_equal_file OUTPUT EXPECTED
-
-test_begin_subtest "Basic notmuch-pick view in emacs"
-test_emacs '(add-to-list (quote load-path) "'$PICK_DIR'")
-           (require (quote notmuch-pick))
-           (setq notmuch-pick-asynchronous-parser nil)
-           (notmuch-pick "tag:inbox")
-           (notmuch-test-wait)
-           (test-output)
-           (delete-other-windows)'
-test_expect_equal_file OUTPUT $EXPECTED/notmuch-pick-tag-inbox
-
-test_begin_subtest "Navigation of notmuch-hello to search results"
-test_emacs '(notmuch-hello)
-           (goto-char (point-min))
-           (re-search-forward "inbox")
-           (widget-button-press (1- (point)))
-           (notmuch-test-wait)
-           (notmuch-pick-from-search-current-query)
-           (notmuch-test-wait)
-           (test-output)
-           (delete-other-windows)'
-test_expect_equal_file OUTPUT $EXPECTED/notmuch-pick-tag-inbox
-
-test_begin_subtest "Pick of a single thread (from search)"
-test_emacs '(notmuch-hello)
-           (goto-char (point-min))
-           (re-search-forward "inbox")
-           (widget-button-press (1- (point)))
-           (notmuch-test-wait)
-           (notmuch-pick-from-search-thread)
-           (notmuch-test-wait)
-           (test-output)
-           (delete-other-windows)'
-test_expect_equal_file OUTPUT $EXPECTED/notmuch-pick-single-thread
-
-test_begin_subtest "Pick of a single thread (from show)"
-test_emacs '(notmuch-hello)
-           (goto-char (point-min))
-           (re-search-forward "inbox")
-           (widget-button-press (1- (point)))
-           (notmuch-test-wait)
-           (notmuch-search-show-thread)
-           (notmuch-pick-from-show-current-query)
-           (notmuch-test-wait)
-           (test-output)
-           (delete-other-windows)'
-test_expect_equal_file OUTPUT $EXPECTED/notmuch-pick-single-thread
-
-test_done
diff --git a/contrib/notmuch-pick/test/pick.expected-output/notmuch-pick-show-window b/contrib/notmuch-pick/test/pick.expected-output/notmuch-pick-show-window
deleted file mode 100644 (file)
index e16792b..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
-Subject: [notmuch] Working with Maildir storage?
-To: notmuch@notmuchmail.org
-Date: Tue, 17 Nov 2009 14:00:54 -0500
-
-[ multipart/mixed ]
-[ multipart/signed ]
-[ text/plain ]
-I saw the LWN article and decided to take a look at notmuch.  I'm
-currently using mutt and mairix to index and read a collection of
-Maildir mail folders (around 40,000 messages total).
-
-notmuch indexed the messages without complaint, but my attempt at
-searching bombed out. Running, for example:
-
-  notmuch search storage
-
-Resulted in 4604 lines of errors along the lines of:
-
-  Error opening
-  /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
-  Too many open files
-
-I'm curious if this is expected behavior (i.e., notmuch does not work
-with Maildir) or if something else is going on.
-
-Cheers,
-
-[ 4-line signature. Click/Enter to show. ]
--- 
-Lars Kellogg-Stedman <lars@seas.harvard.edu>
-Senior Technologist, Computing and Information Technology
-Harvard University School of Engineering and Applied Sciences
-[ application/pgp-signature ]
-[ text/plain ]
-[ 4-line signature. Click/Enter to show. ]
-_______________________________________________
-notmuch mailing list
-notmuch@notmuchmail.org
-http://notmuchmail.org/mailman/listinfo/notmuch
diff --git a/contrib/notmuch-pick/test/pick.expected-output/notmuch-pick-single-thread b/contrib/notmuch-pick/test/pick.expected-output/notmuch-pick-single-thread
deleted file mode 100644 (file)
index c9e5ef8..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox)
-  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox, unread)
-  2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox, unread)
-  2009-11-17  Keith Packard          ╰┬► ...                                              (inbox, unread)
-  2009-11-18  Carl Worth              ╰─► ...                                             (inbox, unread)
-End of search results.
diff --git a/contrib/notmuch-pick/test/pick.expected-output/notmuch-pick-tag-inbox b/contrib/notmuch-pick/test/pick.expected-output/notmuch-pick-tag-inbox
deleted file mode 100644 (file)
index 484141e..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-  2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox, unread)
-  2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox, unread)
-  2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox, unread)
-  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search        instead of going into an infinite loop (attachment, inbox, unread)
-  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (inbox, unread)
-  2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox, unread)
-  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
-  2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox, signed, unread)
-  2009-11-18  Keith Packard         ├─► ...                                               (inbox, unread)
-  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
-  2009-11-17  Israel Herraiz        ┬►[notmuch] New to the list                           (inbox, unread)
-  2009-11-18  Keith Packard         ├─► ...                                               (inbox, unread)
-  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
-  2009-11-17  Jan Janak             ┬►[notmuch] What a great idea!                        (inbox, unread)
-  2009-11-17  Jan Janak             ├─► ...                                               (inbox, unread)
-  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
-  2009-11-17  Jan Janak             ┬►[notmuch] [PATCH] Older versions of install do not support -C. (inbox, unread)
-  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
-  2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox, unread)
-  2009-11-18  Keith Packard         ╰┬► ...                                               (inbox, unread)
-  2009-11-18  Carl Worth             ╰─► ...                                              (inbox, unread)
-  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove    inbox (and unread) tags (inbox, unread)
-  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox, unread)
-  2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox, signed, unread)
-  2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox, signed, unread)
-  2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox, signed, unread)
-  2009-11-17  Mikhail Gusarov       │ ├─► ...                                             (inbox, unread)
-  2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox, unread)
-  2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox, signed, unread)
-  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
-  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox, unread)
-  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox, unread)
-  2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox, unread)
-  2009-11-17  Keith Packard          ╰┬► ...                                              (inbox, unread)
-  2009-11-18  Carl Worth              ╰─► ...                                             (inbox, unread)
-  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that    highlights URLs and uses word-wrap (inbox, unread)
-  2009-11-18  Alexander Botero-Low  ╰─►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox, unread)
-  2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox, unread)
-  2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox, unread)
-  2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox, unread)
-  2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox, unread)
-  2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox, unread)
-  2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox, unread)
-  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool       subdirectories into tags (inbox, unread)
-  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before  statting (inbox, unread)
-  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox, unread)
-  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++     libs. (inbox, unread)
-  2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment, inbox, signed, unread)
-  2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment, inbox, signed, unread)
-  2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox, unread)
-  2009-11-17  Alex Botero-Lowry     ┬►[notmuch] preliminary FreeBSD support               (attachment, inbox, unread)
-  2009-11-17  Carl Worth            ╰─► ...                                               (inbox, unread)
-End of search results.
diff --git a/debian/NEWS b/debian/NEWS
new file mode 100644 (file)
index 0000000..8049a93
--- /dev/null
@@ -0,0 +1,44 @@
+notmuch (0.17-1) unstable; urgency=low
+
+  * Previously on big endian architectures like sparc and powerpc the
+    computation of SHA1 hashes was incorrect. This meant that messages
+    with overlong or missing message-ids were given different computed
+    message-ids than on more common little endian architectures like
+    i386 and amd64.  If you use notmuch on a big endian architecture,
+    you are strongly advised to make a backup of your tags using
+    `notmuch dump` before this upgrade.  You can locate the affected
+    files using something like:
+
+    notmuch dump | \
+      awk '/^notmuch-sha1-[0-9a-f]{40} / \
+        {system("notmuch search --exclude=false --output=files id:" $1)}'
+
+ -- David Bremner <bremner@debian.org>  Mon, 30 Dec 2013 20:31:16 -0400
+
+notmuch (0.16-1) unstable; urgency=low
+
+  * The vim interface has been rewritten from scratch. In particular
+    it requires a version of vim with ruby support.
+
+ -- David Bremner <bremner@debian.org>  Sat, 16 Feb 2013 08:12:02 -0400
+
+notmuch (0.14-1) unstable; urgency=low
+
+  There is an incompatible change in option syntax for dump and restore
+  in this release. Please update your scripts.
+
+  From upstream NEWS:
+
+  The deprecated positional output file argument to notmuch dump has
+  been replaced with an --output option. The input file positional
+  argument for restore has been replaced with an --input option for
+  consistency with dump.
+
+ -- David Bremner <bremner@debian.org>  Sun, 05 Aug 2012 11:52:49 -0300
+
+notmuch (0.6~238) unstable; urgency=low
+
+  The emacs user interface to notmuch is now contained in a separate
+  package called notmuch-emacs.
+
+ -- David Bremner <bremner@debian.org>  Mon, 20 Jun 2011 23:57:55 -0300
diff --git a/debian/NEWS.Debian b/debian/NEWS.Debian
deleted file mode 100644 (file)
index e57b4d4..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-notmuch (0.16-1) unstable; urgency=low
-
-  * The vim interface has been rewritten from scratch. In particular
-    it requires a version of vim with ruby support.
-
- -- David Bremner <bremner@debian.org>  Sat, 16 Feb 2013 08:12:02 -0400
-
-notmuch (0.14-1) unstable; urgency=low
-
-  There is an incompatible change in option syntax for dump and restore
-  in this release. Please update your scripts.
-
-  From upstream NEWS:
-
-  The deprecated positional output file argument to notmuch dump has
-  been replaced with an --output option. The input file positional
-  argument for restore has been replaced with an --input option for
-  consistency with dump.
-
- -- David Bremner <bremner@debian.org>  Sun, 05 Aug 2012 11:52:49 -0300
-
-notmuch (0.6~238) unstable; urgency=low
-
-  The emacs user interface to notmuch is now contained in a separate
-  package called notmuch-emacs.
-
- -- David Bremner <bremner@debian.org>  Mon, 20 Jun 2011 23:57:55 -0300
index ed00499932e9d6b2b0a13847fd1706b1f7a27a2e..6edafe0689dcac9a511e1656fbeef980b9f707e3 100644 (file)
@@ -1,9 +1,58 @@
+notmuch (0.17-3) unstable; urgency=medium
+
+  * update notmuch-emacs for debian emacs policy 2.0.6
+  * Update emacs test suite for Hurd compatibility
+
+ -- David Bremner <bremner@debian.org>  Sun, 12 Jan 2014 17:07:16 -0400
+
+notmuch (0.17-2) unstable; urgency=medium
+
+  * Bug fix: "package should warn in a NEWS.Debian file about possible
+    pre-upgrade action", thanks to Jonas Smedegaard (Closes: #733853).
+
+ -- David Bremner <bremner@debian.org>  Wed, 01 Jan 2014 07:44:25 -0400
+
+notmuch (0.17-1) unstable; urgency=low
+
+  * New upstream feature release. See /usr/share/doc/notmuch/NEWS.gz
+    for details.  Highlights include:
+    - notmuch compact command (Closes: #720543).
+    - emacs "tree" view
+  * Remove madduck from uploaders (Closes: #719100).
+
+ -- David Bremner <bremner@debian.org>  Mon, 30 Dec 2013 20:28:20 -0400
+
+notmuch (0.17~rc4-1) experimental; urgency=low
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Sat, 28 Dec 2013 18:30:06 -0400
+
+notmuch (0.17~rc3-1) experimental; urgency=low
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Sat, 07 Dec 2013 17:05:11 +0800
+
+notmuch (0.17~rc2-1) experimental; urgency=low
+
+  * New upstream release candidate
+  * Remove gdb as build-dep on s390x. This implicitly disables failing
+    atomicity test.  For more information, see #728705
+
+ -- David Bremner <bremner@debian.org>  Sun, 24 Nov 2013 19:34:28 -0400
+
+notmuch (0.17~rc1-1) experimental; urgency=low
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Wed, 20 Nov 2013 19:27:48 -0400
+
 notmuch (0.16-1~bpo70+1) wheezy-backports; urgency=low
 
   * Rebuild for wheezy-backports.
 
  -- David Bremner <bremner@debian.org>  Sun, 01 Sep 2013 19:04:32 -0300
-
 notmuch (0.16-1) unstable; urgency=low
 
   * New upstream feature release
index 74a51614f4b719339b7f81405f1b5390619da663..a887263cfbd7d4c5053882f53b7fff6fd0e4f2c5 100644 (file)
@@ -18,7 +18,7 @@ Build-Depends:
  ruby, ruby-dev (>>1:1.9.3~),
  emacs23-nox | emacs23 (>=23~) | emacs23-lucid (>=23~) |
  emacs24-nox | emacs24 (>=24~) | emacs24-lucid (>=24~),
- gdb,
+ gdb [!s390x !ia64],
  dtach (>= 0.8)
 Standards-Version: 3.9.4
 Homepage: http://notmuchmail.org/
@@ -108,6 +108,7 @@ Architecture: all
 Section: mail
 Breaks: notmuch (<<0.6~254~)
 Replaces: notmuch (<<0.6~254~)
+Conflicts: emacsen-common (<< 2.0.0)
 Depends: ${misc:Depends}, notmuch (>= ${source:Version}),
  emacs23 (>= 23~) | emacs23-nox (>=23~) | emacs23-lucid (>=23~) |
  emacs24 (>= 24~) | emacs24-nox (>=24~) | emacs24-lucid (>=24~)
@@ -142,9 +143,8 @@ Depends:
  notmuch (>= 0.4),
  libmail-box-perl, libmailtools-perl,
  libstring-shellquote-perl, libterm-readline-gnu-perl,
- libfile-which-perl,
  ${misc:Depends}
-Recommends: mutt, fdupes
+Recommends: mutt
 Enhances: notmuch, mutt
 Description: thread-based email index, search and tagging (Mutt interface)
  notmuch-mutt provides integration among the Mutt mail user agent and
index 17946db33d710373619f63de143bf605b34fcfc2..ef6ae9db9aa1fcde2f83fd2c13e9da85d7ce27a0 100644 (file)
@@ -2,6 +2,7 @@ libnotmuch.so.3 libnotmuch3 #MINVER#
  notmuch_database_add_message@Base 0.3
  notmuch_database_begin_atomic@Base 0.9~rc1
  notmuch_database_close@Base 0.13~rc1
+ notmuch_database_compact@Base 0.17~rc1
  notmuch_database_create@Base 0.3
  notmuch_database_destroy@Base 0.13~rc1
  notmuch_database_end_atomic@Base 0.9~rc1
index 713523b4115cda2007b2be6a3173f96d0f9d61e5..33f2d99314ed9fb0228b8c7b672ff01adcbff375 100644 (file)
@@ -1,8 +1,4 @@
-
-* This package currently works only with emacs 23.  Users of 
-  pre-release snapshots of emacs 24 can expect problems.
-
 * For help in getting started with notmuch and the emacs interface,
   see /usr/share/doc/notmuch/README.
 
- -- David Bremner <bremner@debian.org>, Fri,  1 Jul 2011 11:41:26 -0300
+ -- David Bremner <bremner@debian.org>, Wed, 27 Nov 2013 08:17:11 -0400
diff --git a/debian/notmuch-emacs.emacsen-compat b/debian/notmuch-emacs.emacsen-compat
new file mode 100644 (file)
index 0000000..573541a
--- /dev/null
@@ -0,0 +1 @@
+0
index 8fd302763b74e74ad591ec773da842bb6c6bb99a..dfd8fda94ded28f9080980d1193a4c9c57162564 100755 (executable)
@@ -8,8 +8,6 @@
 FLAVOR=$1
 PACKAGE=notmuch
 
-if [ ${FLAVOR} = emacs ]; then exit 0; fi
-
 # We know that the notmuch emacs code doesn't work with emacs before emacs23
 if [ ${FLAVOR} = emacs21 ]; then exit 0; fi
 if [ ${FLAVOR} = emacs22 ]; then exit 0; fi
index 184c2b602ab3807561c9f0f1c44eb6c20922085b..3b433ae2bebe0eb13277caf0139a43a1ee213cf3 100755 (executable)
@@ -4,7 +4,5 @@
 FLAVOR=$1
 PACKAGE=notmuch
 
-if [ ${FLAVOR} != emacs ]; then
-    echo remove/${PACKAGE}: purging byte-compiled files for ${FLAVOR}
-    rm -rf /usr/share/${FLAVOR}/site-lisp/${PACKAGE}
-fi
+echo remove/${PACKAGE}: purging byte-compiled files for ${FLAVOR}
+rm -rf /usr/share/${FLAVOR}/site-lisp/${PACKAGE}
diff --git a/debian/notmuch-emacs.postinst b/debian/notmuch-emacs.postinst
new file mode 100644 (file)
index 0000000..48ecf23
--- /dev/null
@@ -0,0 +1,4 @@
+dir="/var/lib/emacsen-common/state/package/installed"
+mkdir -p 0755 ${dir}
+touch ${dir}/notmuch-emacs
+#DEBHELPER#
diff --git a/debian/notmuch-emacs.prerm b/debian/notmuch-emacs.prerm
new file mode 100644 (file)
index 0000000..5e2758d
--- /dev/null
@@ -0,0 +1,3 @@
+#DEBHELPER#
+dir="/var/lib/emacsen-common/state/package/installed"
+rm -f ${dir}/notmuch-emacs
index c978ac117b4a38e936d26ec9ba901323cd749ffd..c6373e421f50975e1e44567c1389434c8740dfab 100644 (file)
@@ -1,3 +1,4 @@
 usr/share/vim/registry
 usr/share/vim/addons/plugin
+usr/share/vim/addons/doc
 usr/share/vim/addons/syntax
index d444fef764ef2b0422185ad452c0157e2b730a24..a1af708d78b2a1b051c016177920f7424952ae3f 100644 (file)
@@ -1,3 +1,4 @@
 vim/notmuch.vim usr/share/vim/addons/plugin
+vim/notmuch.txt usr/share/vim/addons/doc
 vim/syntax/notmuch-*.vim usr/share/vim/addons/syntax
 vim/notmuch.yaml usr/share/vim/registry
diff --git a/debian/source/options b/debian/source/options
new file mode 100644 (file)
index 0000000..7423a2d
--- /dev/null
@@ -0,0 +1 @@
+single-debian-patch
index 844555ed464fa2bef139e1752ff72e98fc1decb0..1cf4089f1d93b7a894dc1ada6aba1da12a1fedaf 100644 (file)
@@ -14,11 +14,6 @@ sender's name containing ';' which causes emacs to drop a search
 result.) This may require removing the outer array from the current
 "notmuch search --format=json" results.
 
-Fix '*' to work by simply calling '+' or '-' on a region consisting of
-the entire buffer, (this would avoid one race condition---while still
-leaving other race conditions---but could also potentially make '*' a
-very expensive operation).
-
 Add a global keybinding table for notmuch, and then view-specific
 tables that add to it.
        
@@ -114,6 +109,12 @@ are multiple ideas that might make sense. Some consensus needs to be
 reached on this issue, and then both reply formats should be updated
 to be consistent.
 
+Return docid-based queries in thread search results, instead of
+message-id-based queries.  These are much more compact and much faster
+to later query.  This will require support from the library, both for
+retrieving docids (probably as an opaque "get a query that identifies
+this message") and for docid queries.
+
 notmuch library
 ---------------
 Add support for custom flag<->tag mappings. In the notmuch
index 4eff1a7fd9a78f6a9a0e29d3e5773d80a8c99f1d..8938905e0462e079b6edc520cca2f2f468b32d27 100755 (executable)
@@ -13,6 +13,8 @@ fi
 set -o posix
 set -o pipefail # bash feature
 
+readonly DEFAULT_IFS="$IFS" # Note: In this particular case quotes are needed.
+
 # Avoid locale-specific differences in output of executed commands
 LANG=C LC_ALL=C; export LANG LC_ALL
 
@@ -20,8 +22,10 @@ readonly PV_FILE='bindings/python/notmuch/version.py'
 
 # Using array here turned out to be unnecessarily complicated
 emsgs=''
+emsg_count=0
 append_emsg ()
 {
+       emsg_count=$((emsg_count + 1))
        emsgs="${emsgs:+$emsgs\n}  $1"
 }
 
@@ -73,6 +77,37 @@ case $VERSION in
        *)      verfail "'$VERSION' is a single number" ;;
 esac
 
+echo -n "Checking that LIBNOTMUCH version macros & variables match ... "
+# lib/notmuch.h
+LIBNOTMUCH_MAJOR_VERSION=broken
+LIBNOTMUCH_MINOR_VERSION=broken
+LIBNOTMUCH_MICRO_VERSION=broken
+# lib/Makefile.local
+LIBNOTMUCH_VERSION_MAJOR=borken
+LIBNOTMUCH_VERSION_MINOR=borken
+LIBNOTMUCH_VERSION_RELEASE=borken
+
+eval `awk 'NF == 3 && $1 == "#define" && $2 ~ /^LIBNOTMUCH_[A-Z]+_VERSION$/ \
+       && $3 ~ /^[0-9]+$/ { print $2 "=" $3 }' lib/notmuch.h`
+
+eval `awk 'NF == 3 && $1 ~ /^LIBNOTMUCH_VERSION_[A-Z]+$/ && $2 == "=" \
+       && $3 ~ /^[0-9]+$/ { print $1 "=" $3 }' lib/Makefile.local`
+
+
+check_version_component ()
+{
+       eval local v1=\$LIBNOTMUCH_$1_VERSION
+       eval local v2=\$LIBNOTMUCH_VERSION_$2
+       if [ $v1 != $v2 ]
+       then    append_emsg "LIBNOTMUCH_$1_VERSION ($v1) does not equal LIBNOTMUCH_VERSION_$2 ($v2)"
+       fi
+}
+
+old_emsg_count=$emsg_count
+check_version_component MAJOR MAJOR
+check_version_component MINOR MINOR
+check_version_component MICRO RELEASE
+[ $old_emsg_count = $emsg_count ] && echo Yes. || echo No.
 
 echo -n "Checking that this is Debian package for notmuch... "
 read deb_notmuch deb_version rest < debian/changelog
index 2405756e43a724ca3f2d2fc576710afc34e8046f..41dc4a60fff36608e25425c7f113c6f2a1b667b0 100644 (file)
@@ -14,7 +14,17 @@ are interleaved. Keys are printed as keywords (symbols preceded by a
 colon), e.g. (:id "123" :time 54321 :from "foobar"). Null is printed as
 nil, true as t and false as nil.
 
-This is version 1 of the structured output format.
+This is version 2 of the structured output format.
+
+Version history
+---------------
+
+v1
+- First versioned schema release.
+- Added part.content-length and part.content-transfer-encoding fields.
+
+v2
+- Added the thread_summary.query field.
 
 Common non-terminals
 --------------------
@@ -122,21 +132,21 @@ notmuch search schema
 ---------------------
 
 # --output=summary
-summary = [thread*]
+search_summary = [thread_summary*]
 
 # --output=threads
-threads = [threadid*]
+search_threads = [threadid*]
 
 # --output=messages
-messages = [messageid*]
+search_messages = [messageid*]
 
 # --output=files
-files = [string*]
+search_files = [string*]
 
 # --output=tags
-tags = [string*]
+search_tags = [string*]
 
-thread = {
+thread_summary = {
     thread:         threadid,
     timestamp:      unix_time,
     date_relative:  string,   # user-friendly timestamp
@@ -145,7 +155,15 @@ thread = {
     authors:        string,   # comma-separated names with | between
                               # matched and unmatched
     subject:        string,
-    tags:           [string*]
+    tags:           [string*],
+
+    # Two stable query strings identifying exactly the matched and
+    # unmatched messages currently in this thread.  The messages
+    # matched by these queries will not change even if more messages
+    # arrive in the thread.  If there are no matched or unmatched
+    # messages, the corresponding query will be null (there is no
+    # query that matches nothing).  (Added in schema version 2.)
+    query:          [string|null, string|null],
 }
 
 notmuch reply schema
index a910affadbdc02de2152037fbba2ed060ce489ca..92467a318e75fd5669f46f4e16fd7a965ac5a50f 100644 (file)
@@ -7,6 +7,7 @@ emacs_sources := \
        $(dir)/notmuch.el \
        $(dir)/notmuch-query.el \
        $(dir)/notmuch-show.el \
+       $(dir)/notmuch-tree.el \
        $(dir)/notmuch-wash.el \
        $(dir)/notmuch-hello.el \
        $(dir)/notmuch-mua.el \
index 9db8c9931a8fee0a25e105d00d5345def45ec47f..55c416ac5e9f328f9523cb44c928c2248f2a13b1 100644 (file)
@@ -232,6 +232,11 @@ supported for \"Customized queries section\" items."
            notmuch-hello-query-section
            (function :tag "Custom section"))))
 
+(defcustom notmuch-hello-auto-refresh t
+  "Automatically refresh when returning to the notmuch-hello buffer."
+  :group 'notmuch-hello
+  :type 'boolean)
+
 (defvar notmuch-hello-hidden-sections nil
   "List of sections titles whose contents are hidden")
 
@@ -258,13 +263,11 @@ afterwards.")
     search))
 
 (defun notmuch-hello-search (&optional search)
-  (interactive)
   (unless (null search)
     (setq search (notmuch-hello-trim search))
     (let ((history-delete-duplicates t))
       (add-to-history 'notmuch-search-history search)))
-  (notmuch-search search notmuch-search-oldest-first nil nil
-                 #'notmuch-hello-search-continuation))
+  (notmuch-search search notmuch-search-oldest-first))
 
 (defun notmuch-hello-add-saved-search (widget)
   (interactive)
@@ -322,8 +325,7 @@ diagonal."
 (defun notmuch-hello-widget-search (widget &rest ignore)
   (notmuch-search (widget-get widget
                              :notmuch-search-terms)
-                 notmuch-search-oldest-first
-                 nil nil #'notmuch-hello-search-continuation))
+                 notmuch-search-oldest-first))
 
 (defun notmuch-saved-search-count (search)
   (car (process-lines notmuch-command "count" search)))
@@ -476,34 +478,54 @@ Such a list can be computed with `notmuch-hello-query-counts'."
 
 (defimage notmuch-hello-logo ((:type png :file "notmuch-logo.png")))
 
-(defun notmuch-hello-search-continuation()
-  (notmuch-hello-update t))
-
 (defun notmuch-hello-update (&optional no-display)
   "Update the current notmuch view."
   ;; Lazy - rebuild everything.
-  (interactive)
   (notmuch-hello no-display))
 
-(defun notmuch-hello-poll-and-update ()
-  "Invoke `notmuch-poll' to import mail, then refresh the current view."
-  (interactive)
-  (notmuch-poll)
-  (notmuch-hello-update))
+(defun notmuch-hello-window-configuration-change ()
+  "Hook function to update the hello buffer when it is switched to."
+  (let ((hello-buf (get-buffer "*notmuch-hello*"))
+       (do-refresh nil))
+    ;; Consider all windows in the currently selected frame, since
+    ;; that's where the configuration change happened.  This also
+    ;; refreshes our snapshot of all windows, so we have to do this
+    ;; even if we know we won't refresh (e.g., hello-buf is null).
+    (dolist (window (window-list))
+      (let ((last-buf (window-parameter window 'notmuch-hello-last-buffer))
+           (cur-buf (window-buffer window)))
+       (when (not (eq last-buf cur-buf))
+         ;; This window changed or is new.  Update recorded buffer
+         ;; for next time.
+         (set-window-parameter window 'notmuch-hello-last-buffer cur-buf)
+         (when (and (eq cur-buf hello-buf) last-buf)
+           ;; The user just switched to hello in this window (hello
+           ;; is currently visible, was not visible on the last
+           ;; configuration change, and this is not a new window)
+           (setq do-refresh t)))))
+    (when (and do-refresh notmuch-hello-auto-refresh)
+      ;; Refresh hello as soon as we get back to redisplay.  On Emacs
+      ;; 24, we can't do it right here because something in this
+      ;; hook's call stack overrides hello's point placement.
+      (run-at-time nil nil #'notmuch-hello t))
+    (when (null hello-buf)
+      ;; Clean up hook
+      (remove-hook 'window-configuration-change-hook
+                  #'notmuch-hello-window-configuration-change))))
 
 
 (defvar notmuch-hello-mode-map
-  (let ((map (make-sparse-keymap)))
-    (set-keymap-parent map widget-keymap)
+  (let ((map (if (fboundp 'make-composed-keymap)
+                ;; Inherit both widget-keymap and notmuch-common-keymap
+                (make-composed-keymap widget-keymap)
+              ;; Before Emacs 24, keymaps didn't support multiple
+              ;; inheritance,, so just copy the widget keymap since
+              ;; it's unlikely to change.
+              (copy-keymap widget-keymap))))
+    (set-keymap-parent map notmuch-common-keymap)
     (define-key map "v" (lambda () "Display the notmuch version" (interactive)
                          (message "notmuch version %s" (notmuch-version))))
-    (define-key map "?" 'notmuch-help)
-    (define-key map "q" 'notmuch-kill-this-buffer)
-    (define-key map "=" 'notmuch-hello-update)
-    (define-key map "G" 'notmuch-hello-poll-and-update)
     (define-key map (kbd "<C-tab>") 'widget-backward)
-    (define-key map "m" 'notmuch-mua-new-mail)
-    (define-key map "s" 'notmuch-hello-search)
     map)
   "Keymap for \"notmuch hello\" buffers.")
 (fset 'notmuch-hello-mode-map notmuch-hello-mode-map)
@@ -516,6 +538,7 @@ Complete list of currently available key bindings:
 \\{notmuch-hello-mode-map}"
  (interactive)
  (kill-all-local-variables)
+ (setq notmuch-buffer-refresh-function #'notmuch-hello-update)
  (use-local-map notmuch-hello-mode-map)
  (setq major-mode 'notmuch-hello-mode
        mode-name "notmuch-hello")
@@ -765,9 +788,17 @@ following:
   "Run notmuch and display saved searches, known tags, etc."
   (interactive)
 
-  (if no-display
-      (set-buffer "*notmuch-hello*")
-    (switch-to-buffer "*notmuch-hello*"))
+  ;; This may cause a window configuration change, so if the
+  ;; auto-refresh hook is already installed, avoid recursive refresh.
+  (let ((notmuch-hello-auto-refresh nil))
+    (if no-display
+       (set-buffer "*notmuch-hello*")
+      (switch-to-buffer "*notmuch-hello*")))
+
+  ;; Install auto-refresh hook
+  (when notmuch-hello-auto-refresh
+    (add-hook 'window-configuration-change-hook
+             #'notmuch-hello-window-configuration-change))
 
   (let ((target-line (line-number-at-pos))
        (target-column (current-column))
index 4796f177c9bc342a05ee34d9c19fa3aa4039db55..49fe64457b3a03e34241fb6741ebf82f19d9c7cd 100644 (file)
@@ -76,6 +76,32 @@ search."
   :type 'boolean
   :group 'notmuch-search)
 
+(defcustom notmuch-poll-script nil
+  "An external script to incorporate new mail into the notmuch database.
+
+This variable controls the action invoked by
+`notmuch-poll-and-refresh-this-buffer' (bound by default to 'G')
+to incorporate new mail into the notmuch database.
+
+If set to nil (the default), new mail is processed by invoking
+\"notmuch new\". Otherwise, this should be set to a string that
+gives the name of an external script that processes new mail. If
+set to the empty string, no command will be run.
+
+The external script could do any of the following depending on
+the user's needs:
+
+1. Invoke a program to transfer mail to the local mail store
+2. Invoke \"notmuch new\" to incorporate the new mail
+3. Invoke one or more \"notmuch tag\" commands to classify the mail
+
+Note that the recommended way of achieving the same is using
+\"notmuch new\" hooks."
+  :type '(choice (const :tag "notmuch new" nil)
+                (const :tag "Disabled" "")
+                (string :tag "Custom script"))
+  :group 'notmuch-external)
+
 ;;
 
 (defvar notmuch-search-history nil
@@ -101,6 +127,18 @@ For example, if you wanted to remove an \"inbox\" tag and add an
   :group 'notmuch-search
   :group 'notmuch-show)
 
+(defvar notmuch-common-keymap
+  (let ((map (make-sparse-keymap)))
+    (define-key map "?" 'notmuch-help)
+    (define-key map "q" 'notmuch-kill-this-buffer)
+    (define-key map "s" 'notmuch-search)
+    (define-key map "z" 'notmuch-tree)
+    (define-key map "m" 'notmuch-mua-new-mail)
+    (define-key map "=" 'notmuch-refresh-this-buffer)
+    (define-key map "G" 'notmuch-poll-and-refresh-this-buffer)
+    map)
+  "Keymap shared by all notmuch modes.")
+
 ;; By default clicking on a button does not select the window
 ;; containing the button (as opposed to clicking on a widget which
 ;; does). This means that the button action is then executed in the
@@ -161,11 +199,163 @@ Otherwise the output will be returned"
   "Return the user.other_email value (as a list) from the notmuch configuration."
   (split-string (notmuch-config-get "user.other_email") "\n"))
 
+(defun notmuch-poll ()
+  "Run \"notmuch new\" or an external script to import mail.
+
+Invokes `notmuch-poll-script', \"notmuch new\", or does nothing
+depending on the value of `notmuch-poll-script'."
+  (interactive)
+  (if (stringp notmuch-poll-script)
+      (unless (string= notmuch-poll-script "")
+       (call-process notmuch-poll-script nil nil))
+    (call-process notmuch-command nil nil nil "new")))
+
 (defun notmuch-kill-this-buffer ()
   "Kill the current buffer."
   (interactive)
   (kill-buffer (current-buffer)))
 
+(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 " "))))
+
+
+(defun notmuch-describe-key (actual-key binding prefix ua-keys tail)
+  "Prepend cons cells describing prefix-arg ACTUAL-KEY and ACTUAL-KEY to TAIL
+
+It does not prepend if ACTUAL-KEY is already listed in TAIL."
+  (let ((key-string (concat prefix (format-kbd-macro actual-key))))
+    ;; We don't include documentation if the key-binding is
+    ;; over-ridden. Note, over-riding a binding automatically hides the
+    ;; prefixed version too.
+    (unless (assoc key-string tail)
+      (when (and ua-keys (symbolp binding)
+                (get binding 'notmuch-prefix-doc))
+       ;; Documentation for prefixed command
+       (let ((ua-desc (key-description ua-keys)))
+         (push (cons (concat ua-desc " " prefix (format-kbd-macro actual-key))
+                     (get binding 'notmuch-prefix-doc))
+               tail)))
+      ;; Documentation for command
+      (push (cons key-string
+                 (or (and (symbolp binding) (get binding 'notmuch-doc))
+                     (notmuch-documentation-first-line binding)))
+           tail)))
+    tail)
+
+(defun notmuch-describe-remaps (remap-keymap ua-keys base-keymap prefix tail)
+  ;; Remappings are represented as a binding whose first "event" is
+  ;; 'remap.  Hence, if the keymap has any remappings, it will have a
+  ;; binding whose "key" is 'remap, and whose "binding" is itself a
+  ;; keymap that maps not from keys to commands, but from old (remapped)
+  ;; functions to the commands to use in their stead.
+  (map-keymap
+   (lambda (command binding)
+     (mapc
+      (lambda (actual-key)
+       (setq tail (notmuch-describe-key actual-key binding prefix ua-keys tail)))
+      (where-is-internal command base-keymap)))
+   remap-keymap)
+  tail)
+
+(defun notmuch-describe-keymap (keymap ua-keys base-keymap &optional prefix tail)
+  "Return a list of cons cells, each describing one binding in KEYMAP.
+
+Each cons cell consists of a string giving a human-readable
+description of the key, and a one-line description of the bound
+function.  See `notmuch-help' for an overview of how this
+documentation is extracted.
+
+UA-KEYS should be a key sequence bound to `universal-argument'.
+It will be used to describe bindings of commands that support a
+prefix argument.  PREFIX and TAIL are used internally."
+  (map-keymap
+   (lambda (key binding)
+     (cond ((mouse-event-p key) nil)
+          ((keymapp binding)
+           (setq tail
+                 (if (eq key 'remap)
+                     (notmuch-describe-remaps
+                      binding ua-keys base-keymap prefix tail)
+                   (notmuch-describe-keymap
+                    binding ua-keys base-keymap (notmuch-prefix-key-description key) tail))))
+          (binding
+           (setq tail (notmuch-describe-key (vector key) binding prefix ua-keys tail)))))
+   keymap)
+  tail)
+
+(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 ((desc
+            (save-match-data
+              (let* ((keymap-name (substring doc (match-beginning 1) (match-end 1)))
+                     (keymap (symbol-value (intern keymap-name)))
+                     (ua-keys (where-is-internal 'universal-argument keymap t))
+                     (desc-alist (notmuch-describe-keymap keymap ua-keys keymap))
+                     (desc-list (mapcar (lambda (arg) (concat (car arg) "\t" (cdr arg))) desc-alist)))
+                (mapconcat #'identity desc-list "\n")))))
+       (setq doc (replace-match desc 1 1 doc)))
+      (setq beg (match-end 0)))
+    doc))
+
+(defun notmuch-help ()
+  "Display help for the current notmuch mode.
+
+This is similar to `describe-function' for the current major
+mode, but bindings tables are shown with documentation strings
+rather than command names.  By default, this uses the first line
+of each command's documentation string.  A command can override
+this by setting the 'notmuch-doc property of its command symbol.
+A command that supports a prefix argument can explicitly document
+its prefixed behavior by setting the 'notmuch-prefix-doc property
+of its command symbol."
+  (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))))
+
+(defvar notmuch-buffer-refresh-function nil
+  "Function to call to refresh the current buffer.")
+(make-variable-buffer-local 'notmuch-buffer-refresh-function)
+
+(defun notmuch-refresh-this-buffer ()
+  "Refresh the current buffer."
+  (interactive)
+  (when notmuch-buffer-refresh-function
+    (if (commandp notmuch-buffer-refresh-function)
+       ;; Pass prefix argument, etc.
+       (call-interactively notmuch-buffer-refresh-function)
+      (funcall notmuch-buffer-refresh-function))))
+
+(defun notmuch-poll-and-refresh-this-buffer ()
+  "Invoke `notmuch-poll' to import mail, then refresh the current buffer."
+  (interactive)
+  (notmuch-poll)
+  (notmuch-refresh-this-buffer))
+
 (defun notmuch-prettify-subject (subject)
   ;; This function is used by `notmuch-search-process-filter' which
   ;; requires that we not disrupt its' matching state.
@@ -175,6 +365,12 @@ Otherwise the output will be returned"
        "[No Subject]"
       subject)))
 
+(defun notmuch-sanitize (str)
+  "Sanitize control character in STR.
+
+This includes newlines, tabs, and other funny characters."
+  (replace-regexp-in-string "[[:cntrl:]\x7f\u2028\u2029]+" " " str))
+
 (defun notmuch-escape-boolean-term (term)
   "Escape a boolean term for use in a query.
 
@@ -193,6 +389,14 @@ user-friendly queries."
   "Return a query that matches the message with id ID."
   (concat "id:" (notmuch-escape-boolean-term id)))
 
+(defun notmuch-hex-encode (str)
+  "Hex-encode STR (e.g., as used by batch tagging).
+
+This replaces spaces, percents, and double quotes in STR with
+%NN where NN is the hexadecimal value of the character."
+  (replace-regexp-in-string
+   "[ %\"]" (lambda (match) (format "%%%02x" (aref match 0))) str))
+
 ;;
 
 (defun notmuch-common-do-stash (text)
@@ -466,17 +670,55 @@ You may need to restart Emacs or upgrade your notmuch package."))
        ;; `notmuch-logged-error' does not return.
        ))))
 
+(defun notmuch-call-notmuch--helper (destination args)
+  "Helper for synchronous notmuch invocation commands.
+
+This wraps `call-process'.  DESTINATION has the same meaning as
+for `call-process'.  ARGS is as described for
+`notmuch-call-notmuch-process'."
+
+  (let (stdin-string)
+    (while (keywordp (car args))
+      (case (car args)
+       (:stdin-string (setq stdin-string (cadr args)
+                            args (cddr args)))
+       (otherwise
+        (error "Unknown keyword argument: %s" (car args)))))
+    (if (null stdin-string)
+       (apply #'call-process notmuch-command nil destination nil args)
+      (insert stdin-string)
+      (apply #'call-process-region (point-min) (point-max)
+            notmuch-command t destination nil args))))
+
+(defun notmuch-call-notmuch-process (&rest args)
+  "Synchronously invoke `notmuch-command' with ARGS.
+
+The caller may provide keyword arguments before ARGS.  Currently
+supported keyword arguments are:
+
+  :stdin-string STRING - Write STRING to stdin
+
+If notmuch exits with a non-zero status, output from the process
+will appear in a buffer named \"*Notmuch errors*\" and an error
+will be signaled."
+  (with-temp-buffer
+    (let ((status (notmuch-call-notmuch--helper t args)))
+      (notmuch-check-exit-status status (cons notmuch-command args)
+                                (buffer-string)))))
+
 (defun notmuch-call-notmuch-sexp (&rest args)
   "Invoke `notmuch-command' with ARGS and return the parsed S-exp output.
 
-If notmuch exits with a non-zero status, this will pop up a
-buffer containing notmuch's output and signal an error."
+This is equivalent to `notmuch-call-notmuch-process', but parses
+notmuch's output as an S-expression and returns the parsed value.
+Like `notmuch-call-notmuch-process', if notmuch exits with a
+non-zero status, this will report its output and signal an
+error."
 
   (with-temp-buffer
     (let ((err-file (make-temp-file "nmerr")))
       (unwind-protect
-         (let ((status (apply #'call-process
-                              notmuch-command nil (list t err-file) nil args)))
+         (let ((status (notmuch-call-notmuch--helper (list t err-file) args)))
            (notmuch-check-exit-status status (cons notmuch-command args)
                                       (buffer-string) err-file)
            (goto-char (point-min))
@@ -558,7 +800,6 @@ status."
 (defvar notmuch-show-process-crypto nil)
 (make-variable-buffer-local 'notmuch-show-process-crypto)
 
-
 (provide 'notmuch-lib)
 
 ;; Local Variables:
index 2baae5f111f5a04c5d48c6de1ccb43bf47b7fb2d..00cd9808c1141f8e6ae470eaa63f2482652bfa5d 100644 (file)
@@ -28,6 +28,8 @@
 
 (eval-when-compile (require 'cl))
 
+(declare-function notmuch-show-insert-bodypart "notmuch-show" (msg part depth &optional hide))
+
 ;;
 
 (defcustom notmuch-mua-send-hook '(notmuch-mua-message-send-hook)
@@ -128,12 +130,15 @@ list."
          collect part))
 
 (defun notmuch-mua-insert-quotable-part (message part)
-  (save-restriction
-    (narrow-to-region (point) (point))
-    (notmuch-mm-display-part-inline message part (plist-get part :id)
-                                   (plist-get part :content-type)
-                                   notmuch-show-process-crypto)
-    (goto-char (point-max))))
+  ;; We don't want text properties leaking from the show renderer into
+  ;; the reply so we use a temp buffer. Also we don't want hooks, such
+  ;; as notmuch-wash-*, to be run on the quotable part so we set
+  ;; notmuch-show-insert-text/plain-hook to nil.
+  (insert (with-temp-buffer
+           (let ((notmuch-show-insert-text/plain-hook nil))
+             ;; Show the part but do not add buttons.
+             (notmuch-show-insert-bodypart message part 0 'no-buttons))
+           (buffer-substring-no-properties (point-min) (point-max)))))
 
 ;; There is a bug in emacs 23's message.el that results in a newline
 ;; not being inserted after the References header, so the next header
@@ -191,11 +196,16 @@ list."
                            nil (notmuch-mua-get-switch-function))))
 
       ;; Insert the message body - but put it in front of the signature
-      ;; if one is present
-      (goto-char (point-max))
-      (if (re-search-backward message-signature-separator nil t)
-         (forward-line -1)
-       (goto-char (point-max)))
+      ;; if one is present, and after any other content
+      ;; message*setup-hooks may have added to the message body already.
+      (save-restriction
+       (message-goto-body)
+       (narrow-to-region (point) (point-max))
+       (goto-char (point-max))
+       (if (re-search-backward message-signature-separator nil t)
+           (if message-signature-insert-empty-line
+               (forward-line -1))
+         (goto-char (point-max))))
 
       (let ((from (plist-get original-headers :From))
            (date (plist-get original-headers :Date))
@@ -303,8 +313,9 @@ the From: header is already filled in by notmuch."
       (ido-completing-read "Send mail From: " notmuch-identities
                           nil nil nil 'notmuch-mua-sender-history (car notmuch-identities)))))
 
+(put 'notmuch-mua-new-mail 'notmuch-prefix-doc "... and prompt for sender")
 (defun notmuch-mua-new-mail (&optional prompt-for-sender)
-  "Invoke the notmuch mail composition window.
+  "Compose new mail.
 
 If PROMPT-FOR-SENDER is non-nil, the user will be prompted for
 the From: address first."
@@ -317,9 +328,10 @@ the From: address first."
 (defun notmuch-mua-new-forward-message (&optional prompt-for-sender)
   "Invoke the notmuch message forwarding window.
 
+The current buffer must contain an RFC2822 message to forward.
+
 If PROMPT-FOR-SENDER is non-nil, the user will be prompted for
 the From: address first."
-  (interactive "P")
   (if (or prompt-for-sender notmuch-always-prompt-for-sender)
       (let* ((sender (notmuch-mua-prompt-for-sender))
             (address-components (mail-extract-address-components sender))
@@ -329,12 +341,30 @@ the From: address first."
     (notmuch-mua-forward-message)))
 
 (defun notmuch-mua-new-reply (query-string &optional prompt-for-sender reply-all)
-  "Invoke the notmuch reply window."
-  (interactive "P")
+  "Compose a reply to the message identified by QUERY-STRING.
+
+If PROMPT-FOR-SENDER is non-nil, the user will be prompted for
+the From: address first.  If REPLY-ALL is non-nil, the message
+will be addressed to all recipients of the source message."
+
+;; In current emacs (24.3) select-active-regions is set to t by
+;; default. The reply insertion code sets the region to the quoted
+;; message to make it easy to delete (kill-region or C-w). These two
+;; things combine to put the quoted message in the primary selection.
+;;
+;; This is not what the user wanted and is a privacy risk (accidental
+;; pasting of the quoted message). We can avoid some of the problems
+;; by let-binding select-active-regions to nil. This fixes if the
+;; primary selection was previously in a non-emacs window but not if
+;; it was in an emacs window. To avoid the problem in the latter case
+;; we deactivate mark.
+
   (let ((sender
         (when prompt-for-sender
-          (notmuch-mua-prompt-for-sender))))
-    (notmuch-mua-reply query-string sender reply-all)))
+          (notmuch-mua-prompt-for-sender)))
+       (select-active-regions nil))
+    (notmuch-mua-reply query-string sender reply-all)
+    (deactivate-mark)))
 
 (defun notmuch-mua-send-and-exit (&optional arg)
   (interactive "P")
index 82b70bafcd2bc35d4c67d1d827a602f812c4619c..784644cd18f43e85dcbb009b883e9f2f0d59d233 100644 (file)
 (declare-function notmuch-search-next-thread "notmuch" nil)
 (declare-function notmuch-search-previous-thread "notmuch" nil)
 (declare-function notmuch-search-show-thread "notmuch" nil)
+(declare-function notmuch-foreach-mime-part "notmuch" (function mm-handle))
+(declare-function notmuch-count-attachments "notmuch" (mm-handle))
+(declare-function notmuch-save-attachments "notmuch" (mm-handle &optional queryp))
+(declare-function notmuch-tree "notmuch-tree"
+                 (&optional query query-context target buffer-name open-target))
 
 (defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date")
   "Headers that should be shown in a message, in this order.
@@ -154,6 +159,15 @@ indentation."
 (make-variable-buffer-local 'notmuch-show-indent-content)
 (put 'notmuch-show-indent-content 'permanent-local t)
 
+(defvar notmuch-show-attachment-debug nil
+  "If t log stdout and stderr from attachment handlers
+
+When set to nil (the default) stdout and stderr from attachment
+handlers is discarded. When set to t the stdout and stderr from
+each attachment handler is logged in buffers with names beginning
+\" *notmuch-part*\". This option requires emacs version at least
+24.3 to work.")
+
 (defcustom notmuch-show-stash-mlarchive-link-alist
   '(("Gmane" . "http://mid.gmane.org/")
     ("MARC" . "http://marc.info/?i=")
@@ -237,42 +251,6 @@ For example, if you wanted to remove an \"unread\" tag and add a
                                 )))
      (mm-display-parts (mm-dissect-buffer)))))
 
-(defun notmuch-foreach-mime-part (function mm-handle)
-  (cond ((stringp (car mm-handle))
-         (dolist (part (cdr mm-handle))
-           (notmuch-foreach-mime-part function part)))
-        ((bufferp (car mm-handle))
-         (funcall function mm-handle))
-        (t (dolist (part mm-handle)
-             (notmuch-foreach-mime-part function part)))))
-
-(defun notmuch-count-attachments (mm-handle)
-  (let ((count 0))
-    (notmuch-foreach-mime-part
-     (lambda (p)
-       (let ((disposition (mm-handle-disposition p)))
-         (and (listp disposition)
-              (or (equal (car disposition) "attachment")
-                  (and (equal (car disposition) "inline")
-                       (assq 'filename disposition)))
-              (incf count))))
-     mm-handle)
-    count))
-
-(defun notmuch-save-attachments (mm-handle &optional queryp)
-  (notmuch-foreach-mime-part
-   (lambda (p)
-     (let ((disposition (mm-handle-disposition p)))
-       (and (listp disposition)
-            (or (equal (car disposition) "attachment")
-                (and (equal (car disposition) "inline")
-                     (assq 'filename disposition)))
-            (or (not queryp)
-                (y-or-n-p
-                 (concat "Save '" (cdr (assq 'filename disposition)) "' ")))
-            (mm-save-part p))))
-   mm-handle))
-
 (defun notmuch-show-save-attachments ()
   "Save all attachments from the current message."
   (interactive)
@@ -440,7 +418,8 @@ unchanged ADDRESS if parsing fails."
 message at DEPTH in the current thread."
   (let ((start (point)))
     (insert (notmuch-show-spaces-n (* notmuch-show-indent-messages-width depth))
-           (notmuch-show-clean-address (plist-get headers :From))
+           (notmuch-sanitize
+            (notmuch-show-clean-address (plist-get headers :From)))
            " ("
            date
            ") ("
@@ -450,7 +429,7 @@ message at DEPTH in the current thread."
 
 (defun notmuch-show-insert-header (header header-value)
   "Insert a single header."
-  (insert header ": " header-value "\n"))
+  (insert header ": " (notmuch-sanitize header-value) "\n"))
 
 (defun notmuch-show-insert-headers (headers)
   "Insert the headers of the current message."
@@ -503,7 +482,7 @@ message at DEPTH in the current thread."
             (new-start (button-start button))
             (button-label (button-get button :base-label))
             (old-point (point))
-            (properties (text-properties-at (point)))
+            (properties (text-properties-at (button-start button)))
             (inhibit-read-only t))
        ;; Toggle the button itself.
        (button-put button :notmuch-part-hidden (not show))
@@ -621,6 +600,10 @@ message at DEPTH in the current thread."
 
     ;; Render the primary part.
     (notmuch-show-insert-bodypart msg (car inner-parts) depth)
+    ;; Add hidden buttons for the rest
+    (mapc (lambda (inner-part)
+           (notmuch-show-insert-bodypart msg inner-part depth t))
+         (cdr inner-parts))
 
     (when notmuch-show-indent-multipart
       (indent-rigidly start (point) 1)))
@@ -877,7 +860,11 @@ message at DEPTH in the current thread."
 (defun notmuch-show-insert-bodypart (msg part depth &optional hide)
   "Insert the body part PART at depth DEPTH in the current thread.
 
-If HIDE is non-nil then initially hide this part."
+HIDE determines whether to show or hide the part and the button
+as follows: If HIDE is nil, show the part and the button. If HIDE
+is t, hide the part initially and show the button. If HIDE is
+'no-buttons, show the part but do not add any buttons (this is
+useful for quoting in replies)."
 
   (let* ((content-type (downcase (plist-get part :content-type)))
         (mime-type (or (and (string= content-type "application/octet-stream")
@@ -887,15 +874,19 @@ If HIDE is non-nil then initially hide this part."
                        content-type))
         (nth (plist-get part :id))
         (beg (point))
-        ;; We omit the part button for the first (or only) part if this is text/plain.
-        (button (unless (and (string= mime-type "text/plain") (<= nth 1))
+        ;; Hide the part initially if HIDE is t.
+        (show-part (not (equal hide t)))
+        ;; We omit the part button for the first (or only) part if
+        ;; this is text/plain, or HIDE is 'no-buttons.
+        (button (unless (or (equal hide 'no-buttons)
+                            (and (string= mime-type "text/plain") (<= nth 1)))
                   (notmuch-show-insert-part-header nth mime-type content-type (plist-get part :filename))))
         (content-beg (point)))
 
     ;; Store the computed mime-type for later use (e.g. by attachment handlers).
     (plist-put part :computed-type mime-type)
 
-    (if (not hide)
+    (if show-part
         (notmuch-show-insert-bodypart-internal msg part mime-type nth depth button)
       (button-put button :notmuch-lazy-part
                   (list msg part mime-type nth depth button)))
@@ -908,7 +899,7 @@ If HIDE is non-nil then initially hide this part."
       (insert "\n"))
     ;; We do not create the overlay for hidden (lazy) parts until
     ;; they are inserted.
-    (if (not hide)
+    (if show-part
        (notmuch-show-create-part-overlays button content-beg (point))
       (save-excursion
        (notmuch-show-toggle-part-invisibility button)))
@@ -1101,15 +1092,17 @@ buttons for a corresponding notmuch search."
        (make-text-button (first link) (second link)
                          :type 'notmuch-button-type
                          'action `(lambda (arg)
-                                    (notmuch-show ,(third link)))
+                                    (notmuch-show ,(third link) current-prefix-arg))
                          'follow-link t
                          'help-echo "Mouse-1, RET: search for this message"
                          'face goto-address-mail-face)))))
 
 ;;;###autoload
-(defun notmuch-show (thread-id &optional parent-buffer query-context buffer-name)
+(defun notmuch-show (thread-id &optional elide-toggle parent-buffer query-context buffer-name)
   "Run \"notmuch show\" with the given thread ID and display results.
 
+ELIDE-TOGGLE, if non-nil, inverts the default elide behavior.
+
 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
@@ -1123,7 +1116,7 @@ The optional BUFFER-NAME provides the name of the buffer in
 which the message thread is shown. If it is nil (which occurs
 when the command is called interactively) the argument to the
 function is used."
-  (interactive "sNotmuch show: ")
+  (interactive "sNotmuch show: \nP")
   (let ((buffer-name (generate-new-buffer-name
                      (or buffer-name
                          (concat "*notmuch-" thread-id "*")))))
@@ -1133,9 +1126,9 @@ function is used."
     (setq notmuch-show-process-crypto notmuch-crypto-process-mime)
     ;; Set the default value for
     ;; `notmuch-show-elide-non-matching-messages' in this buffer. If
-    ;; there is a prefix argument, invert the default.
+    ;; elide-toggle is set, invert the default.
     (setq notmuch-show-elide-non-matching-messages notmuch-show-only-matching-messages)
-    (if current-prefix-arg
+    (if elide-toggle
        (setq notmuch-show-elide-non-matching-messages (not notmuch-show-elide-non-matching-messages)))
 
     (setq notmuch-show-thread-id thread-id
@@ -1175,7 +1168,7 @@ function is used."
       (jit-lock-register #'notmuch-show-buttonise-links)
 
       ;; Set the header line to the subject of the first message.
-      (setq header-line-format (notmuch-show-strip-re (notmuch-show-get-subject)))
+      (setq header-line-format (notmuch-sanitize (notmuch-show-strip-re (notmuch-show-get-subject))))
 
       (run-hooks 'notmuch-show-hook))))
 
@@ -1264,14 +1257,12 @@ reset based on the original query."
 
 (defvar notmuch-show-mode-map
       (let ((map (make-sparse-keymap)))
-       (define-key map "?" 'notmuch-help)
-       (define-key map "q" 'notmuch-kill-this-buffer)
+       (set-keymap-parent map notmuch-common-keymap)
+       (define-key map "Z" 'notmuch-tree-from-show-current-query)
        (define-key map (kbd "<C-tab>") 'widget-backward)
        (define-key map (kbd "M-TAB") 'notmuch-show-previous-button)
        (define-key map (kbd "<backtab>") 'notmuch-show-previous-button)
        (define-key map (kbd "TAB") 'notmuch-show-next-button)
-       (define-key map "s" 'notmuch-search)
-       (define-key map "m" 'notmuch-mua-new-mail)
        (define-key map "f" 'notmuch-show-forward-message)
        (define-key map "r" 'notmuch-show-reply-sender)
        (define-key map "R" 'notmuch-show-reply)
@@ -1279,7 +1270,6 @@ reset based on the original query."
        (define-key map "w" 'notmuch-show-save-attachments)
        (define-key map "V" 'notmuch-show-view-raw-message)
        (define-key map "c" 'notmuch-show-stash-map)
-       (define-key map "=" 'notmuch-show-refresh-view)
        (define-key map "h" 'notmuch-show-toggle-visibility-headers)
        (define-key map "*" 'notmuch-show-tag-all)
        (define-key map "-" 'notmuch-show-remove-tag)
@@ -1338,12 +1328,20 @@ All currently available key bindings:
 \\{notmuch-show-mode-map}"
   (interactive)
   (kill-all-local-variables)
+  (setq notmuch-buffer-refresh-function #'notmuch-show-refresh-view)
   (use-local-map notmuch-show-mode-map)
   (setq major-mode 'notmuch-show-mode
        mode-name "notmuch-show")
   (setq buffer-read-only t
        truncate-lines t))
 
+(defun notmuch-tree-from-show-current-query ()
+  "Call notmuch tree with the current query"
+  (interactive)
+  (notmuch-tree notmuch-show-thread-id
+               notmuch-show-query-context
+               (notmuch-show-get-message-id)))
+
 (defun notmuch-show-move-to-message-top ()
   (goto-char (notmuch-show-message-top)))
 
@@ -1512,6 +1510,8 @@ current thread."
   "Are the headers of the current message visible?"
   (notmuch-show-get-prop :headers-visible))
 
+(put 'notmuch-show-mark-read 'notmuch-prefix-doc
+     "Mark the current message as unread.")
 (defun notmuch-show-mark-read (&optional unread)
   "Mark the current message as read.
 
@@ -1599,7 +1599,7 @@ shown."
       (notmuch-show-archive-thread-then-next)))
 
 (defun notmuch-show-rewind ()
-  "Backup through the thread, (reverse scrolling compared to \\[notmuch-show-advance-and-archive]).
+  "Backup through the thread (reverse scrolling compared to \\[notmuch-show-advance-and-archive]).
 
 Specifically, if the beginning of the previous email is fewer
 than `window-height' lines from the current point, move to it
@@ -1633,16 +1633,20 @@ any effects from previous calls to
       ;; Move to the previous message.
       (notmuch-show-previous-message)))))
 
+(put 'notmuch-show-reply 'notmuch-prefix-doc "... and prompt for sender")
 (defun notmuch-show-reply (&optional prompt-for-sender)
   "Reply to the sender and all recipients of the current message."
   (interactive "P")
   (notmuch-mua-new-reply (notmuch-show-get-message-id) prompt-for-sender t))
 
+(put 'notmuch-show-reply-sender 'notmuch-prefix-doc "... and prompt for sender")
 (defun notmuch-show-reply-sender (&optional prompt-for-sender)
   "Reply to the sender of the current message."
   (interactive "P")
   (notmuch-mua-new-reply (notmuch-show-get-message-id) prompt-for-sender nil))
 
+(put 'notmuch-show-forward-message 'notmuch-prefix-doc
+     "... and prompt for sender")
 (defun notmuch-show-forward-message (&optional prompt-for-sender)
   "Forward the current message."
   (interactive "P")
@@ -1746,16 +1750,21 @@ to show, nil otherwise."
     (set-buffer-modified-p nil)
     (view-buffer buf 'kill-buffer-if-not-modified)))
 
+(put 'notmuch-show-pipe-message 'notmuch-doc
+     "Pipe the contents of the current message to a command.")
+(put 'notmuch-show-pipe-message 'notmuch-prefix-doc
+     "Pipe the thread as an mbox to a command.")
 (defun notmuch-show-pipe-message (entire-thread command)
-  "Pipe the contents of the current message (or thread) to the given command.
+  "Pipe the contents of the current message (or thread) to COMMAND.
 
-The given command will be executed with the raw contents of the
-current email message as stdin. Anything printed by the command
-to stdout or stderr will appear in the *notmuch-pipe* buffer.
+COMMAND will be executed with the raw contents of the current
+email message as stdin. Anything printed by the command to stdout
+or stderr will appear in the *notmuch-pipe* buffer.
 
-When invoked with a prefix argument, the command will receive all
-open messages in the current thread (formatted as an mbox) rather
-than only the current message."
+If ENTIRE-THREAD is non-nil (or when invoked with a prefix
+argument), COMMAND will receive all open messages in the current
+thread (formatted as an mbox) rather than only the current
+message."
   (interactive (let ((query-string (if current-prefix-arg
                                       "Pipe all open messages to command: "
                                     "Pipe message to command: ")))
@@ -1793,23 +1802,28 @@ TAG-CHANGES is a list of tag operations for `notmuch-tag'."
       (notmuch-tag (notmuch-show-get-message-id) tag-changes)
       (notmuch-show-set-tags new-tags))))
 
-(defun notmuch-show-tag (&optional tag-changes)
+(defun notmuch-show-tag (tag-changes)
   "Change tags for the current message.
 
 See `notmuch-tag' for information on the format of TAG-CHANGES."
-  (interactive)
-  (let* ((tag-changes (notmuch-tag (notmuch-show-get-message-id) tag-changes))
-        (current-tags (notmuch-show-get-tags))
+  (interactive (list (notmuch-read-tag-changes (notmuch-show-get-tags)
+                                              "Tag message")))
+  (notmuch-tag (notmuch-show-get-message-id) tag-changes)
+  (let* ((current-tags (notmuch-show-get-tags))
         (new-tags (notmuch-update-tags current-tags tag-changes)))
     (unless (equal current-tags new-tags)
       (notmuch-show-set-tags new-tags))))
 
-(defun notmuch-show-tag-all (&optional tag-changes)
+(defun notmuch-show-tag-all (tag-changes)
   "Change tags for all messages in the current show buffer.
 
 See `notmuch-tag' for information on the format of TAG-CHANGES."
-  (interactive)
-  (setq tag-changes (notmuch-tag (notmuch-show-get-messages-ids-search) tag-changes))
+  (interactive
+   (list (let (tags)
+          (notmuch-show-mapc
+           (lambda () (setq tags (append (notmuch-show-get-tags) tags))))
+          (notmuch-read-tag-changes tags "Tag thread"))))
+  (notmuch-tag (notmuch-show-get-messages-ids-search) tag-changes)
   (notmuch-show-mapc
    (lambda ()
      (let* ((current-tags (notmuch-show-get-tags))
@@ -1817,15 +1831,21 @@ See `notmuch-tag' for information on the format of TAG-CHANGES."
        (unless (equal current-tags new-tags)
         (notmuch-show-set-tags new-tags))))))
 
-(defun notmuch-show-add-tag ()
-  "Same as `notmuch-show-tag' but sets initial input to '+'."
-  (interactive)
-  (notmuch-show-tag "+"))
+(defun notmuch-show-add-tag (tag-changes)
+  "Change tags for the current message (defaulting to add).
 
-(defun notmuch-show-remove-tag ()
-  "Same as `notmuch-show-tag' but sets initial input to '-'."
-  (interactive)
-  (notmuch-show-tag "-"))
+Same as `notmuch-show-tag' but sets initial input to '+'."
+  (interactive
+   (list (notmuch-read-tag-changes (notmuch-show-get-tags) "Tag message" "+")))
+  (notmuch-show-tag tag-changes))
+
+(defun notmuch-show-remove-tag (tag-changes)
+  "Change tags for the current message (defaulting to remove).
+
+Same as `notmuch-show-tag' but sets initial input to '-'."
+  (interactive
+   (list (notmuch-read-tag-changes (notmuch-show-get-tags) "Tag message" "-")))
+  (notmuch-show-tag tag-changes))
 
 (defun notmuch-show-toggle-visibility-headers ()
   "Toggle the visibility of the current message headers."
@@ -1845,8 +1865,11 @@ See `notmuch-tag' for information on the format of TAG-CHANGES."
      (not (plist-get props :message-visible))))
   (force-window-update))
 
+(put 'notmuch-show-open-or-close-all 'notmuch-doc "Show all messages.")
+(put 'notmuch-show-open-or-close-all 'notmuch-prefix-doc "Hide all messages.")
 (defun notmuch-show-open-or-close-all ()
   "Set the visibility all of the messages in the current thread.
+
 By default make all of the messages visible. With a prefix
 argument, hide all of the messages."
   (interactive)
@@ -1895,6 +1918,8 @@ search results instead."
   (interactive)
   (notmuch-show-next-thread t t))
 
+(put 'notmuch-show-archive-thread 'notmuch-prefix-doc
+     "Un-archive each message in thread.")
 (defun notmuch-show-archive-thread (&optional unarchive)
   "Archive each message in thread.
 
@@ -1924,6 +1949,8 @@ buffer."
   (notmuch-show-archive-thread)
   (notmuch-show-next-thread))
 
+(put 'notmuch-show-archive-message 'notmuch-prefix-doc
+     "Un-archive the current message.")
 (defun notmuch-show-archive-message (&optional unarchive)
   "Archive the current message.
 
@@ -1975,6 +2002,8 @@ thread from search."
   (interactive)
   (notmuch-common-do-stash (notmuch-show-get-from)))
 
+(put 'notmuch-show-stash-message-id 'notmuch-prefix-doc
+     "Copy thread: query matching current thread to kill-ring.")
 (defun notmuch-show-stash-message-id (&optional stash-thread-id)
   "Copy id: query matching the current message to kill-ring.
 
@@ -2069,8 +2098,16 @@ caller is responsible for killing this buffer as appropriate."
 This ensures that the temporary buffer created for the mm-handle
 is destroyed when FN returns."
   (let ((handle (notmuch-show-current-part-handle)))
+    ;; emacs 24.3+ puts stdout/stderr into the calling buffer so we
+    ;; call it from a temp-buffer, unless
+    ;; notmuch-show-attachment-debug is non-nil in which case we put
+    ;; it in " *notmuch-part*".
     (unwind-protect
-       (funcall fn handle)
+       (if notmuch-show-attachment-debug
+           (with-current-buffer (generate-new-buffer " *notmuch-part*")
+             (funcall fn handle))
+         (with-temp-buffer
+           (funcall fn handle)))
       (kill-buffer (mm-handle-buffer handle)))))
 
 (defun notmuch-show-part-button-default (&optional button)
index 064cfa8d81154535e3055c1265927e435fa2cae9..b60f46c74d33ee5d70ec0feca703efb39fc60ecb 100644 (file)
@@ -30,7 +30,8 @@
 
 (defcustom notmuch-tag-formats
   '(("unread" (propertize tag 'face '(:foreground "red")))
-    ("flagged" (notmuch-tag-format-image-data tag (notmuch-tag-star-icon))))
+    ("flagged" (propertize tag 'face '(:foreground "blue"))
+     (notmuch-tag-format-image-data tag (notmuch-tag-star-icon))))
   "Custom formats for individual tags.
 
 This gives a list that maps from tag names to lists of formatting
@@ -188,7 +189,10 @@ the messages that were tagged"
   "Variable to store minibuffer history for
 `notmuch-read-tag-changes' function.")
 
-(defun notmuch-tag-completions (&optional search-terms)
+(defun notmuch-tag-completions (&rest search-terms)
+  "Return a list of tags for messages matching SEARCH-TERMS.
+
+Returns all tags if no search terms are given."
   (if (null search-terms)
       (setq search-terms (list "*")))
   (split-string
@@ -199,17 +203,24 @@ the messages that were tagged"
    "\n+" t))
 
 (defun notmuch-select-tag-with-completion (prompt &rest search-terms)
-  (let ((tag-list (notmuch-tag-completions search-terms)))
+  (let ((tag-list (apply #'notmuch-tag-completions search-terms)))
     (completing-read prompt tag-list nil nil nil 'notmuch-select-tag-history)))
 
-(defun notmuch-read-tag-changes (&optional initial-input &rest search-terms)
+(defun notmuch-read-tag-changes (current-tags &optional prompt initial-input)
+  "Prompt for tag changes in the minibuffer.
+
+CURRENT-TAGS is a list of tags that are present on the message or
+messages to be changed.  These are offered as tag removal
+completions.  CURRENT-TAGS may contain duplicates.  PROMPT, if
+non-nil, is the query string to present in the minibuffer.  It
+defaults to \"Tags\".  INITIAL-INPUT, if non-nil, will be the
+initial input in the minibuffer."
+
   (let* ((all-tag-list (notmuch-tag-completions))
         (add-tag-list (mapcar (apply-partially 'concat "+") all-tag-list))
-        (remove-tag-list (mapcar (apply-partially 'concat "-")
-                                 (if (null search-terms)
-                                     all-tag-list
-                                   (notmuch-tag-completions search-terms))))
+        (remove-tag-list (mapcar (apply-partially 'concat "-") current-tags))
         (tag-list (append add-tag-list remove-tag-list))
+        (prompt (concat (or prompt "Tags") " (+add -drop): "))
         (crm-separator " ")
         ;; By default, space is bound to "complete word" function.
         ;; Re-bind it to insert a space instead.  Note that <tab>
@@ -219,8 +230,16 @@ the messages that were tagged"
            (set-keymap-parent map crm-local-completion-map)
            (define-key map " " 'self-insert-command)
            map)))
-    (delete "" (completing-read-multiple "Tags (+add -drop): "
-               tag-list nil nil initial-input
+    (delete "" (completing-read-multiple
+               prompt
+               ;; Append the separator to each completion so when the
+               ;; user completes a tag they can immediately begin
+               ;; entering another.  `completing-read-multiple'
+               ;; ultimately splits the input on crm-separator, so we
+               ;; don't need to strip this back off (we just need to
+               ;; delete "empty" entries caused by trailing spaces).
+               (mapcar (lambda (tag-op) (concat tag-op crm-separator)) tag-list)
+               nil nil initial-input
                'notmuch-read-tag-changes-history))))
 
 (defun notmuch-update-tags (tags tag-changes)
@@ -242,37 +261,38 @@ from TAGS if present."
           (error "Changed tag must be of the form `+this_tag' or `-that_tag'")))))
     (sort result-tags 'string<)))
 
-(defun notmuch-tag (query &optional tag-changes)
+(defconst notmuch-tag-argument-limit 1000
+  "Use batch tagging if the tagging query is longer than this.
+
+This limits the length of arguments passed to the notmuch CLI to
+avoid system argument length limits and performance problems.")
+
+(defun notmuch-tag (query tag-changes)
   "Add/remove tags in TAG-CHANGES to messages matching QUERY.
 
 QUERY should be a string containing the search-terms.
-TAG-CHANGES can take multiple forms.  If TAG-CHANGES is a list of
-strings of the form \"+tag\" or \"-tag\" then those are the tag
-changes applied.  If TAG-CHANGES is a string then it is
-interpreted as a single tag change.  If TAG-CHANGES is the string
-\"-\" or \"+\", or null, then the user is prompted to enter the
-tag changes.
+TAG-CHANGES is a list of strings of the form \"+tag\" or
+\"-tag\" to add or remove tags, respectively.
 
 Note: Other code should always use this function alter tags of
 messages instead of running (notmuch-call-notmuch-process \"tag\" ..)
 directly, so that hooks specified in notmuch-before-tag-hook and
 notmuch-after-tag-hook will be run."
   ;; Perform some validation
-  (if (string-or-null-p tag-changes)
-      (if (or (string= tag-changes "-") (string= tag-changes "+") (null tag-changes))
-         (setq tag-changes (notmuch-read-tag-changes tag-changes query))
-       (setq tag-changes (list tag-changes))))
   (mapc (lambda (tag-change)
          (unless (string-match-p "^[-+]\\S-+$" tag-change)
            (error "Tag must be of the form `+this_tag' or `-that_tag'")))
        tag-changes)
   (unless (null tag-changes)
     (run-hooks 'notmuch-before-tag-hook)
-    (apply 'notmuch-call-notmuch-process "tag"
-          (append tag-changes (list "--" query)))
-    (run-hooks 'notmuch-after-tag-hook))
-  ;; in all cases we return tag-changes as a list
-  tag-changes)
+    (if (<= (length query) notmuch-tag-argument-limit)
+       (apply 'notmuch-call-notmuch-process "tag"
+              (append tag-changes (list "--" query)))
+      ;; Use batch tag mode to avoid argument length limitations
+      (let ((batch-op (concat (mapconcat #'notmuch-hex-encode tag-changes " ")
+                             " -- " query)))
+       (notmuch-call-notmuch-process :stdin-string batch-op "tag" "--batch")))
+    (run-hooks 'notmuch-after-tag-hook)))
 
 (defun notmuch-tag-change-list (tags &optional reverse)
   "Convert TAGS into a list of tag changes.
diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el
new file mode 100644 (file)
index 0000000..8d59e65
--- /dev/null
@@ -0,0 +1,914 @@
+;; notmuch-tree.el --- displaying notmuch forests.
+;;
+;; Copyright © Carl Worth
+;; Copyright © David Edmondson
+;; Copyright © Mark Walters
+;;
+;; This file is part of Notmuch.
+;;
+;; Notmuch 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.
+;;
+;; Notmuch 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 Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;;
+;; Authors: David Edmondson <dme@dme.org>
+;;          Mark Walters <markwalters1009@gmail.com>
+
+(require 'mail-parse)
+
+(require 'notmuch-lib)
+(require 'notmuch-query)
+(require 'notmuch-show)
+(require 'notmuch-tag)
+(require 'notmuch-parser)
+
+(eval-when-compile (require 'cl))
+(declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line))
+(declare-function notmuch-call-notmuch-process "notmuch" (&rest args))
+(declare-function notmuch-read-query "notmuch" (prompt))
+(declare-function notmuch-search-find-thread-id "notmuch" (&optional bare))
+(declare-function notmuch-search-find-subject "notmuch" ())
+
+;; the following variable is defined in notmuch.el
+(defvar notmuch-search-query-string)
+
+(defgroup notmuch-tree nil
+  "Showing message and thread structure."
+  :group 'notmuch)
+
+(defcustom notmuch-tree-show-out nil
+  "View selected messages in new window rather than split-pane."
+  :type 'boolean
+  :group 'notmuch-tree)
+
+(defcustom notmuch-tree-result-format
+  `(("date" . "%12s  ")
+    ("authors" . "%-20s")
+    ((("tree" . "%s")("subject" . "%s")) ." %-54s ")
+    ("tags" . "(%s)"))
+  "Result formatting for Tree view. Supported fields are: date,
+        authors, subject, tree, tags.  Tree means the thread tree
+        box graphics. The field may also be a list in which case
+        the formatting rules are applied recursively and then the
+        output of all the fields in the list is inserted
+        according to format-string.
+
+Note the author string should not contain
+        whitespace (put it in the neighbouring fields instead).
+        For example:
+        (setq notmuch-tree-result-format \(\(\"authors\" . \"%-40s\"\)
+                                             \(\"subject\" . \"%s\"\)\)\)"
+  :type '(alist :key-type (string) :value-type (string))
+  :group 'notmuch-tree)
+
+;; Faces for messages that match the query.
+(defface notmuch-tree-match-date-face
+  '((t :inherit default))
+  "Face used in tree mode for the date in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-match-author-face
+  '((((class color)
+      (background dark))
+     (:foreground "OliveDrab1"))
+    (((class color)
+      (background light))
+     (:foreground "dark blue"))
+    (t
+     (:bold t)))
+  "Face used in tree mode for the date in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-match-subject-face
+  '((t :inherit default))
+  "Face used in tree mode for the subject in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-match-tree-face
+  '((t :inherit default))
+  "Face used in tree mode for the thread tree block graphics in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-match-tag-face
+  '((((class color)
+      (background dark))
+     (:foreground "OliveDrab1"))
+    (((class color)
+      (background light))
+     (:foreground "navy blue" :bold t))
+    (t
+     (:bold t)))
+  "Face used in tree mode for tags in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+;; Faces for messages that do not match the query.
+(defface notmuch-tree-no-match-date-face
+  '((t (:foreground "gray")))
+  "Face used in tree mode for non-matching dates."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-no-match-subject-face
+  '((t (:foreground "gray")))
+  "Face used in tree mode for non-matching subjects."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-no-match-tree-face
+  '((t (:foreground "gray")))
+  "Face used in tree mode for the thread tree block graphics in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-no-match-author-face
+  '((t (:foreground "gray")))
+  "Face used in tree mode for the date in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-no-match-tag-face
+  '((t (:foreground "gray")))
+  "Face used in tree mode face for non-matching tags."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defvar notmuch-tree-previous-subject
+  "The subject of the most recent result shown during the async display")
+(make-variable-buffer-local 'notmuch-tree-previous-subject)
+
+(defvar notmuch-tree-basic-query nil
+  "A buffer local copy of argument query to the function notmuch-tree")
+(make-variable-buffer-local 'notmuch-tree-basic-query)
+
+(defvar notmuch-tree-query-context nil
+  "A buffer local copy of argument query-context to the function notmuch-tree")
+(make-variable-buffer-local 'notmuch-tree-query-context)
+
+(defvar notmuch-tree-target-msg nil
+  "A buffer local copy of argument target to the function notmuch-tree")
+(make-variable-buffer-local 'notmuch-tree-target-msg)
+
+(defvar notmuch-tree-open-target nil
+  "A buffer local copy of argument open-target to the function notmuch-tree")
+(make-variable-buffer-local 'notmuch-tree-open-target)
+
+(defvar notmuch-tree-message-window nil
+  "The window of the message pane.
+
+It is set in both the tree buffer and the child show buffer. It
+is used to try and close the message pane when quitting tree view
+or the child show buffer.")
+(make-variable-buffer-local 'notmuch-tree-message-window)
+(put 'notmuch-tree-message-window 'permanent-local t)
+
+(defvar notmuch-tree-message-buffer nil
+  "The buffer name of the show buffer in the message pane.
+
+This is used to try and make sure we don't close the message pane
+if the user has loaded a different buffer in that window.")
+(make-variable-buffer-local 'notmuch-tree-message-buffer)
+(put 'notmuch-tree-message-buffer 'permanent-local t)
+
+(defun notmuch-tree-to-message-pane (func)
+  "Execute FUNC in message pane.
+
+This function returns a function (so can be used as a keybinding)
+which executes function FUNC in the message pane if it is
+open (if the message pane is closed it does nothing)."
+  `(lambda ()
+      ,(concat "(In message pane) " (documentation func t))
+     (interactive)
+     (when (window-live-p notmuch-tree-message-window)
+       (with-selected-window notmuch-tree-message-window
+        (call-interactively #',func)))))
+
+(defun notmuch-tree-button-activate (&optional button)
+  "Activate BUTTON or button at point
+
+This function does not give an error if there is no button."
+  (interactive)
+  (let ((button (or button (button-at (point)))))
+    (when button (button-activate button))))
+
+(defun notmuch-tree-close-message-pane-and (func)
+  "Close message pane and execute FUNC.
+
+This function returns a function (so can be used as a keybinding)
+which closes the message pane if open and then executes function
+FUNC."
+  `(lambda ()
+      ,(concat "(Close message pane and) " (documentation func t))
+     (interactive)
+     (notmuch-tree-close-message-window)
+     (call-interactively #',func)))
+
+(defvar notmuch-tree-mode-map
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map notmuch-common-keymap)
+    ;; The following override the global keymap.
+    ;; Override because we want to close message pane first.
+    (define-key map [remap notmuch-help] (notmuch-tree-close-message-pane-and #'notmuch-help))
+    ;; Override because we first close message pane and then close tree buffer.
+    (define-key map [remap notmuch-kill-this-buffer] 'notmuch-tree-quit)
+    ;; Override because we close message pane after the search query is entered.
+    (define-key map [remap notmuch-search] 'notmuch-tree-to-search)
+    ;; Override because we want to close message pane first.
+    (define-key map [remap notmuch-mua-new-mail] (notmuch-tree-close-message-pane-and #'notmuch-mua-new-mail))
+
+    ;; these use notmuch-show functions directly
+    (define-key map "|" 'notmuch-show-pipe-message)
+    (define-key map "w" 'notmuch-show-save-attachments)
+    (define-key map "v" 'notmuch-show-view-all-mime-parts)
+    (define-key map "c" 'notmuch-show-stash-map)
+
+    ;; these apply to the message pane
+    (define-key map (kbd "M-TAB") (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
+    (define-key map (kbd "<backtab>")  (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
+    (define-key map (kbd "TAB") (notmuch-tree-to-message-pane #'notmuch-show-next-button))
+    (define-key map "e" (notmuch-tree-to-message-pane #'notmuch-tree-button-activate))
+
+    ;; bindings from show (or elsewhere) but we close the message pane first.
+    (define-key map "f" (notmuch-tree-close-message-pane-and #'notmuch-show-forward-message))
+    (define-key map "r" (notmuch-tree-close-message-pane-and #'notmuch-show-reply-sender))
+    (define-key map "R" (notmuch-tree-close-message-pane-and #'notmuch-show-reply))
+    (define-key map "V" (notmuch-tree-close-message-pane-and #'notmuch-show-view-raw-message))
+
+    ;; The main tree view bindings
+    (define-key map (kbd "RET") 'notmuch-tree-show-message)
+    (define-key map [mouse-1] 'notmuch-tree-show-message)
+    (define-key map "x" 'notmuch-tree-quit)
+    (define-key map "A" 'notmuch-tree-archive-thread)
+    (define-key map "a" 'notmuch-tree-archive-message-then-next)
+    (define-key map "=" 'notmuch-tree-refresh-view)
+    (define-key map "z" 'notmuch-tree-to-tree)
+    (define-key map "n" 'notmuch-tree-next-matching-message)
+    (define-key map "p" 'notmuch-tree-prev-matching-message)
+    (define-key map "N" 'notmuch-tree-next-message)
+    (define-key map "P" 'notmuch-tree-prev-message)
+    (define-key map (kbd "M-p") 'notmuch-tree-prev-thread)
+    (define-key map (kbd "M-n") 'notmuch-tree-next-thread)
+    (define-key map "-" 'notmuch-tree-remove-tag)
+    (define-key map "+" 'notmuch-tree-add-tag)
+    (define-key map "*" 'notmuch-tree-tag-thread)
+    (define-key map " " 'notmuch-tree-scroll-or-next)
+    (define-key map "b" 'notmuch-tree-scroll-message-window-back)
+    map))
+(fset 'notmuch-tree-mode-map notmuch-tree-mode-map)
+
+(defun notmuch-tree-get-message-properties ()
+  "Return the properties of the current message as a plist.
+
+Some useful entries are:
+:headers - Property list containing the headers :Date, :Subject, :From, etc.
+:tags - Tags for this message"
+  (save-excursion
+    (beginning-of-line)
+    (get-text-property (point) :notmuch-message-properties)))
+
+;; XXX This should really be a lib function but we are trying to
+;; reduce impact on the code base.
+(defun notmuch-show-get-prop (prop &optional props)
+  "This is a tree view overridden version of notmuch-show-get-prop
+
+It gets property PROP from PROPS or, if PROPS is nil, the current
+message in either tree or show. This means that several functions
+in notmuch-show now work unchanged in tree as they just need the
+correct message properties."
+  (let ((props (or props
+                  (cond ((eq major-mode 'notmuch-show-mode)
+                         (notmuch-show-get-message-properties))
+                        ((eq major-mode 'notmuch-tree-mode)
+                         (notmuch-tree-get-message-properties))))))
+    (plist-get props prop)))
+
+(defun notmuch-tree-set-message-properties (props)
+  (save-excursion
+    (beginning-of-line)
+    (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))
+
+(defun notmuch-tree-set-prop (prop val &optional props)
+  (let ((inhibit-read-only t)
+       (props (or props
+                  (notmuch-tree-get-message-properties))))
+    (plist-put props prop val)
+    (notmuch-tree-set-message-properties props)))
+
+(defun notmuch-tree-get-prop (prop &optional props)
+  (let ((props (or props
+                  (notmuch-tree-get-message-properties))))
+    (plist-get props prop)))
+
+(defun notmuch-tree-set-tags (tags)
+  "Set the tags of the current message."
+  (notmuch-tree-set-prop :tags tags))
+
+(defun notmuch-tree-get-tags ()
+  "Return the tags of the current message."
+  (notmuch-tree-get-prop :tags))
+
+(defun notmuch-tree-get-message-id ()
+  "Return the message id of the current message."
+  (let ((id (notmuch-tree-get-prop :id)))
+    (if id
+       (notmuch-id-to-query id)
+      nil)))
+
+(defun notmuch-tree-get-match ()
+  "Return whether the current message is a match."
+  (interactive)
+  (notmuch-tree-get-prop :match))
+
+(defun notmuch-tree-refresh-result ()
+  "Redisplay the current message line.
+
+This redisplays the current line based on the messages
+properties (as they are now). This is used when tags are
+updated."
+  (let ((init-point (point))
+       (end (line-end-position))
+       (msg (notmuch-tree-get-message-properties))
+       (inhibit-read-only t))
+    (beginning-of-line)
+    ;; This is a little tricky: we override
+    ;; notmuch-tree-previous-subject to get the decision between
+    ;; ... and a subject right and it stops notmuch-tree-insert-msg
+    ;; from overwriting the buffer local copy of
+    ;; notmuch-tree-previous-subject if this is called while the
+    ;; buffer is displaying.
+    (let ((notmuch-tree-previous-subject (notmuch-tree-get-prop :previous-subject)))
+      (delete-region (point) (1+ (line-end-position)))
+      (notmuch-tree-insert-msg msg))
+    (let ((new-end (line-end-position)))
+      (goto-char (if (= init-point end)
+                    new-end
+                  (min init-point (- new-end 1)))))))
+
+(defun notmuch-tree-tag-update-display (&optional tag-changes)
+  "Update display for TAG-CHANGES to current message.
+
+Does NOT change the database."
+  (let* ((current-tags (notmuch-tree-get-tags))
+        (new-tags (notmuch-update-tags current-tags tag-changes)))
+    (unless (equal current-tags new-tags)
+      (notmuch-tree-set-tags new-tags)
+      (notmuch-tree-refresh-result))))
+
+(defun notmuch-tree-tag (tag-changes)
+  "Change tags for the current message"
+  (interactive
+   (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message")))
+  (notmuch-tag (notmuch-tree-get-message-id) tag-changes)
+  (notmuch-tree-tag-update-display tag-changes))
+
+(defun notmuch-tree-add-tag (tag-changes)
+  "Same as `notmuch-tree-tag' but sets initial input to '+'."
+  (interactive
+   (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message" "+")))
+  (notmuch-tree-tag tag-changes))
+
+(defun notmuch-tree-remove-tag (tag-changes)
+  "Same as `notmuch-tree-tag' but sets initial input to '-'."
+  (interactive
+   (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message" "-")))
+  (notmuch-tree-tag tag-changes))
+
+;; The next two functions close the message window before calling
+;; notmuch-search or notmuch-tree but they do so after the user has
+;; entered the query (in case the user was basing the query on
+;; something in the message window).
+
+(defun notmuch-tree-to-search ()
+  "Run \"notmuch search\" with the given `query' and display results."
+  (interactive)
+  (let ((query (notmuch-read-query "Notmuch search: ")))
+    (notmuch-tree-close-message-window)
+    (notmuch-search query)))
+
+(defun notmuch-tree-to-tree ()
+  "Run a query and display results in Tree view"
+  (interactive)
+  (let ((query (notmuch-read-query "Notmuch tree view search: ")))
+    (notmuch-tree-close-message-window)
+    (notmuch-tree query)))
+
+(defun notmuch-tree-message-window-kill-hook ()
+  "Close the message pane when exiting the show buffer."
+  (let ((buffer (current-buffer)))
+    (when (and (window-live-p notmuch-tree-message-window)
+              (eq (window-buffer notmuch-tree-message-window) buffer))
+      ;; We do not want an error if this is the sole window in the
+      ;; frame and I do not know how to test for that in emacs pre
+      ;; 24. Hence we just ignore-errors.
+      (ignore-errors
+       (delete-window notmuch-tree-message-window)))))
+
+(defun notmuch-tree-show-message-in ()
+  "Show the current message (in split-pane)."
+  (interactive)
+  (let ((id (notmuch-tree-get-message-id))
+       (inhibit-read-only t)
+       buffer)
+    (when id
+      ;; We close and reopen the window to kill off un-needed buffers
+      ;; this might cause flickering but seems ok.
+      (notmuch-tree-close-message-window)
+      (setq notmuch-tree-message-window
+           (split-window-vertically (/ (window-height) 4)))
+      (with-selected-window notmuch-tree-message-window
+       ;; Since we are only displaying one message do not indent.
+       (let ((notmuch-show-indent-messages-width 0)
+             (notmuch-show-only-matching-messages t))
+         (setq buffer (notmuch-show id))))
+      ;; We need the `let' as notmuch-tree-message-window is buffer local.
+      (let ((window notmuch-tree-message-window))
+       (with-current-buffer buffer
+         (setq notmuch-tree-message-window window)
+         (add-hook 'kill-buffer-hook 'notmuch-tree-message-window-kill-hook)))
+      (when notmuch-show-mark-read-tags
+       (notmuch-tree-tag-update-display notmuch-show-mark-read-tags))
+      (setq notmuch-tree-message-buffer buffer))))
+
+(defun notmuch-tree-show-message-out ()
+  "Show the current message (in whole window)."
+  (interactive)
+  (let ((id (notmuch-tree-get-message-id))
+       (inhibit-read-only t)
+       buffer)
+    (when id
+      ;; We close the window to kill off un-needed buffers.
+      (notmuch-tree-close-message-window)
+      (notmuch-show id))))
+
+(defun notmuch-tree-show-message (arg)
+  "Show the current message.
+
+Shows in split pane or whole window according to value of
+`notmuch-tree-show-out'. A prefix argument reverses the choice."
+  (interactive "P")
+  (if (or (and notmuch-tree-show-out  (not arg))
+         (and (not notmuch-tree-show-out) arg))
+      (notmuch-tree-show-message-out)
+    (notmuch-tree-show-message-in)))
+
+(defun notmuch-tree-scroll-message-window ()
+  "Scroll the message window (if it exists)"
+  (interactive)
+  (when (window-live-p notmuch-tree-message-window)
+    (with-selected-window notmuch-tree-message-window
+      (if (pos-visible-in-window-p (point-max))
+         t
+       (scroll-up)))))
+
+(defun notmuch-tree-scroll-message-window-back ()
+  "Scroll the message window back(if it exists)"
+  (interactive)
+  (when (window-live-p notmuch-tree-message-window)
+    (with-selected-window notmuch-tree-message-window
+      (if (pos-visible-in-window-p (point-min))
+         t
+       (scroll-down)))))
+
+(defun notmuch-tree-scroll-or-next ()
+  "Scroll the message window. If it at end go to next message."
+  (interactive)
+  (when (notmuch-tree-scroll-message-window)
+    (notmuch-tree-next-matching-message)))
+
+(defun notmuch-tree-quit ()
+  "Close the split view or exit tree."
+  (interactive)
+  (unless (notmuch-tree-close-message-window)
+    (kill-buffer (current-buffer))))
+
+(defun notmuch-tree-close-message-window ()
+  "Close the message-window. Return t if close succeeds."
+  (interactive)
+  (when (and (window-live-p notmuch-tree-message-window)
+            (eq (window-buffer notmuch-tree-message-window) notmuch-tree-message-buffer))
+    (delete-window notmuch-tree-message-window)
+    (unless (get-buffer-window-list notmuch-tree-message-buffer)
+      (kill-buffer notmuch-tree-message-buffer))
+    t))
+
+(defun notmuch-tree-archive-message (&optional unarchive)
+  "Archive the current message.
+
+Archive the current message by applying the tag changes in
+`notmuch-archive-tags' to it. If a prefix argument is given, the
+message will be \"unarchived\", i.e. the tag changes in
+`notmuch-archive-tags' will be reversed."
+  (interactive "P")
+  (when notmuch-archive-tags
+    (notmuch-tree-tag (notmuch-tag-change-list notmuch-archive-tags unarchive))))
+
+(defun notmuch-tree-archive-message-then-next (&optional unarchive)
+  "Archive the current message and move to next matching message."
+  (interactive "P")
+  (notmuch-tree-archive-message unarchive)
+  (notmuch-tree-next-matching-message))
+
+(defun notmuch-tree-next-message ()
+  "Move to next message."
+  (interactive)
+  (forward-line)
+  (when (window-live-p notmuch-tree-message-window)
+    (notmuch-tree-show-message-in)))
+
+(defun notmuch-tree-prev-message ()
+  "Move to previous message."
+  (interactive)
+  (forward-line -1)
+  (when (window-live-p notmuch-tree-message-window)
+    (notmuch-tree-show-message-in)))
+
+(defun notmuch-tree-prev-matching-message ()
+  "Move to previous matching message."
+  (interactive)
+  (forward-line -1)
+  (while (and (not (bobp)) (not (notmuch-tree-get-match)))
+    (forward-line -1))
+  (when (window-live-p notmuch-tree-message-window)
+    (notmuch-tree-show-message-in)))
+
+(defun notmuch-tree-next-matching-message ()
+  "Move to next matching message."
+  (interactive)
+  (forward-line)
+  (while (and (not (eobp)) (not (notmuch-tree-get-match)))
+    (forward-line))
+  (when (window-live-p notmuch-tree-message-window)
+    (notmuch-tree-show-message-in)))
+
+(defun notmuch-tree-refresh-view ()
+  "Refresh view."
+  (interactive)
+  (let ((inhibit-read-only t)
+       (basic-query notmuch-tree-basic-query)
+       (query-context notmuch-tree-query-context)
+       (target (notmuch-tree-get-message-id)))
+    (erase-buffer)
+    (notmuch-tree-worker basic-query
+                        query-context
+                        target)))
+
+(defun notmuch-tree-thread-top ()
+  (when (notmuch-tree-get-message-properties)
+    (while (not (or (notmuch-tree-get-prop :first) (eobp)))
+      (forward-line -1))))
+
+(defun notmuch-tree-prev-thread ()
+  (interactive)
+  (forward-line -1)
+  (notmuch-tree-thread-top))
+
+(defun notmuch-tree-next-thread ()
+  (interactive)
+  (forward-line 1)
+  (while (not (or (notmuch-tree-get-prop :first) (eobp)))
+    (forward-line 1)))
+
+(defun notmuch-tree-thread-mapcar (function)
+  "Iterate through all messages in the current thread
+ and call FUNCTION for side effects."
+  (save-excursion
+    (notmuch-tree-thread-top)
+    (loop collect (funcall function)
+         do (forward-line)
+         while (and (notmuch-tree-get-message-properties)
+                    (not (notmuch-tree-get-prop :first))))))
+
+(defun notmuch-tree-get-messages-ids-thread-search ()
+  "Return a search string for all message ids of messages in the current thread."
+  (mapconcat 'identity
+            (notmuch-tree-thread-mapcar 'notmuch-tree-get-message-id)
+            " or "))
+
+(defun notmuch-tree-tag-thread (tag-changes)
+  "Tag all messages in the current thread"
+  (interactive
+   (let ((tags (apply #'append (notmuch-tree-thread-mapcar
+                               (lambda () (notmuch-tree-get-tags))))))
+     (list (notmuch-read-tag-changes tags "Tag thread"))))
+  (when (notmuch-tree-get-message-properties)
+    (notmuch-tag (notmuch-tree-get-messages-ids-thread-search) tag-changes)
+    (notmuch-tree-thread-mapcar
+     (lambda () (notmuch-tree-tag-update-display tag-changes)))))
+
+(defun notmuch-tree-archive-thread (&optional unarchive)
+  "Archive each message in thread.
+
+Archive each message currently shown by applying the tag changes
+in `notmuch-archive-tags' to each. If a prefix argument is given,
+the messages will be \"unarchived\", i.e. the tag changes in
+`notmuch-archive-tags' will be reversed.
+
+Note: This command is safe from any race condition of new messages
+being delivered to the same thread. It does not archive the
+entire thread, but only the messages shown in the current
+buffer."
+  (interactive "P")
+  (when notmuch-archive-tags
+    (notmuch-tree-tag-thread
+     (notmuch-tag-change-list notmuch-archive-tags unarchive))))
+
+;; Functions below here display the tree buffer itself.
+
+(defun notmuch-tree-clean-address (address)
+  "Try to clean a single email ADDRESS for display. Return
+AUTHOR_NAME if present, otherwise return AUTHOR_EMAIL. Return
+unchanged ADDRESS if parsing fails."
+  (let* ((clean-address (notmuch-clean-address address))
+        (p-address (car clean-address))
+        (p-name (cdr clean-address)))
+
+    ;; If we have a name return that otherwise return the address.
+    (or p-name p-address)))
+
+(defun notmuch-tree-format-field (field format-string msg)
+  "Format a FIELD of MSG according to FORMAT-STRING and return string"
+  (let* ((headers (plist-get msg :headers))
+        (match (plist-get msg :match)))
+    (cond
+     ((listp field)
+      (format format-string (notmuch-tree-format-field-list field msg)))
+
+     ((string-equal field "date")
+      (let ((face (if match
+                     'notmuch-tree-match-date-face
+                   'notmuch-tree-no-match-date-face)))
+       (propertize (format format-string (plist-get msg :date_relative)) 'face face)))
+
+     ((string-equal field "tree")
+      (let ((tree-status (plist-get msg :tree-status))
+           (face (if match
+                     'notmuch-tree-match-tree-face
+                   'notmuch-tree-no-match-tree-face)))
+
+       (propertize (format format-string
+                           (mapconcat #'identity (reverse tree-status) ""))
+                   'face face)))
+
+     ((string-equal field "subject")
+      (let ((bare-subject (notmuch-show-strip-re (plist-get headers :Subject)))
+           (previous-subject notmuch-tree-previous-subject)
+           (face (if match
+                     'notmuch-tree-match-subject-face
+                   'notmuch-tree-no-match-subject-face)))
+
+       (setq notmuch-tree-previous-subject bare-subject)
+       (propertize (format format-string
+                           (if (string= previous-subject bare-subject)
+                               " ..."
+                             bare-subject))
+                   'face face)))
+
+     ((string-equal field "authors")
+      (let ((author (notmuch-tree-clean-address (plist-get headers :From)))
+           (len (length (format format-string "")))
+           (face (if match
+                     'notmuch-tree-match-author-face
+                   'notmuch-tree-no-match-author-face)))
+       (when (> (length author) len)
+         (setq author (substring author 0 len)))
+       (propertize (format format-string author) 'face face)))
+
+     ((string-equal field "tags")
+      (let ((tags (plist-get msg :tags))
+           (face (if match
+                     'notmuch-tree-match-tag-face
+                   'notmuch-tree-no-match-tag-face)))
+       (propertize (format format-string
+                           (mapconcat #'identity tags ", "))
+                   'face face))))))
+
+
+(defun notmuch-tree-format-field-list (field-list msg)
+  "Format fields of MSG according to FIELD-LIST and return string"
+  (let (result-string)
+    (dolist (spec field-list result-string)
+      (let ((field-string (notmuch-tree-format-field (car spec) (cdr spec) msg)))
+       (setq result-string (concat result-string field-string))))))
+
+(defun notmuch-tree-insert-msg (msg)
+  "Insert the message MSG according to notmuch-tree-result-format"
+  ;; We need to save the previous subject as it will get overwritten
+  ;; by the insert-field calls.
+  (let ((previous-subject notmuch-tree-previous-subject))
+    (insert (notmuch-tree-format-field-list notmuch-tree-result-format msg))
+    (notmuch-tree-set-message-properties msg)
+    (notmuch-tree-set-prop :previous-subject previous-subject)
+    (insert "\n")))
+
+(defun notmuch-tree-goto-and-insert-msg (msg)
+  "Insert msg at the end of the buffer. Move point to msg if it is the target"
+  (save-excursion
+    (goto-char (point-max))
+    (notmuch-tree-insert-msg msg))
+  (let ((msg-id (notmuch-id-to-query (plist-get msg :id)))
+       (target notmuch-tree-target-msg))
+    (when (or (and (not target) (plist-get msg :match))
+             (string= msg-id target))
+      (setq notmuch-tree-target-msg "found")
+      (goto-char (point-max))
+      (forward-line -1)
+      (when notmuch-tree-open-target
+       (notmuch-tree-show-message-in)))))
+
+(defun notmuch-tree-insert-tree (tree depth tree-status first last)
+  "Insert the message tree TREE at depth DEPTH in the current thread.
+
+A message tree is another name for a single sub-thread: i.e., a
+message together with all its descendents."
+  (let ((msg (car tree))
+       (replies (cadr tree)))
+
+      (cond
+       ((and (< 0 depth) (not last))
+       (push "├" tree-status))
+       ((and (< 0 depth) last)
+       (push "╰" tree-status))
+       ((and (eq 0 depth) first last)
+;;       (push "─" tree-status)) choice between this and next line is matter of taste.
+       (push " " tree-status))
+       ((and (eq 0 depth) first (not last))
+         (push "┬" tree-status))
+       ((and (eq 0 depth) (not first) last)
+       (push "╰" tree-status))
+       ((and (eq 0 depth) (not first) (not last))
+       (push "├" tree-status)))
+
+      (push (concat (if replies "┬" "─") "►") tree-status)
+      (plist-put msg :first (and first (eq 0 depth)))
+      (notmuch-tree-goto-and-insert-msg (plist-put msg :tree-status tree-status))
+      (pop tree-status)
+      (pop tree-status)
+
+      (if last
+         (push " " tree-status)
+       (push "│" tree-status))
+
+    (notmuch-tree-insert-thread replies (1+ depth) tree-status)))
+
+(defun notmuch-tree-insert-thread (thread depth tree-status)
+  "Insert the collection of sibling sub-threads THREAD at depth DEPTH in the current forest."
+  (let ((n (length thread)))
+    (loop for tree in thread
+         for count from 1 to n
+
+         do (notmuch-tree-insert-tree tree depth tree-status (eq count 1) (eq count n)))))
+
+(defun notmuch-tree-insert-forest-thread (forest-thread)
+  "Insert a single complete thread."
+  (let (tree-status)
+    ;; Reset at the start of each main thread.
+    (setq notmuch-tree-previous-subject nil)
+    (notmuch-tree-insert-thread forest-thread 0 tree-status)))
+
+(defun notmuch-tree-insert-forest (forest)
+  "Insert a forest of threads.
+
+This function inserts a collection of several complete threads as
+passed to it by notmuch-tree-process-filter."
+  (mapc 'notmuch-tree-insert-forest-thread forest))
+
+(defun notmuch-tree-mode ()
+  "Major mode displaying messages (as opposed to threads) of of a notmuch search.
+
+This buffer contains the results of a \"notmuch tree\" of your
+email archives. Each line in the buffer represents a single
+message giving the relative date, the author, subject, and any
+tags.
+
+Pressing \\[notmuch-tree-show-message] on any line displays that message.
+
+Complete list of currently available key bindings:
+
+\\{notmuch-tree-mode-map}"
+
+  (interactive)
+  (kill-all-local-variables)
+  (setq notmuch-buffer-refresh-function #'notmuch-tree-refresh-view)
+  (use-local-map notmuch-tree-mode-map)
+  (setq major-mode 'notmuch-tree-mode
+       mode-name "notmuch-tree")
+  (hl-line-mode 1)
+  (setq buffer-read-only t
+       truncate-lines t))
+
+(defun notmuch-tree-process-sentinel (proc msg)
+  "Add a message to let user know when \"notmuch tree\" exits"
+  (let ((buffer (process-buffer proc))
+       (status (process-status proc))
+       (exit-status (process-exit-status proc))
+       (never-found-target-thread nil))
+    (when (memq status '(exit signal))
+        (kill-buffer (process-get proc 'parse-buf))
+       (if (buffer-live-p buffer)
+           (with-current-buffer buffer
+             (save-excursion
+               (let ((inhibit-read-only t)
+                     (atbob (bobp)))
+                 (goto-char (point-max))
+                 (if (eq status 'signal)
+                     (insert "Incomplete search results (tree view process was killed).\n"))
+                 (when (eq status 'exit)
+                   (insert "End of search results.")
+                   (unless (= exit-status 0)
+                     (insert (format " (process returned %d)" exit-status)))
+                   (insert "\n")))))))))
+
+(defun notmuch-tree-process-filter (proc string)
+  "Process and filter the output of \"notmuch show\" for tree view"
+  (let ((results-buf (process-buffer proc))
+        (parse-buf (process-get proc 'parse-buf))
+        (inhibit-read-only t)
+        done)
+    (if (not (buffer-live-p results-buf))
+        (delete-process proc)
+      (with-current-buffer parse-buf
+        ;; Insert new data
+        (save-excursion
+          (goto-char (point-max))
+          (insert string))
+       (notmuch-sexp-parse-partial-list 'notmuch-tree-insert-forest-thread
+                                        results-buf)))))
+
+(defun notmuch-tree-worker (basic-query &optional query-context target open-target)
+  "Insert the tree view of the search in the current buffer.
+
+This is is a helper function for notmuch-tree. The arguments are
+the same as for the function notmuch-tree."
+  (interactive)
+  (notmuch-tree-mode)
+  (setq notmuch-tree-basic-query basic-query)
+  (setq notmuch-tree-query-context query-context)
+  (setq notmuch-tree-target-msg target)
+  (setq notmuch-tree-open-target open-target)
+
+  (erase-buffer)
+  (goto-char (point-min))
+  (let* ((search-args (concat basic-query
+                      (if query-context (concat " and (" query-context ")"))
+                      ))
+        (message-arg "--entire-thread"))
+    (if (equal (car (process-lines notmuch-command "count" search-args)) "0")
+       (setq search-args basic-query))
+    (let ((proc (notmuch-start-notmuch
+                "notmuch-tree" (current-buffer) #'notmuch-tree-process-sentinel
+                "show" "--body=false" "--format=sexp"
+                message-arg search-args))
+         ;; Use a scratch buffer to accumulate partial output.
+         ;; This buffer will be killed by the sentinel, which
+         ;; should be called no matter how the process dies.
+         (parse-buf (generate-new-buffer " *notmuch tree parse*")))
+      (process-put proc 'parse-buf parse-buf)
+      (set-process-filter proc 'notmuch-tree-process-filter)
+      (set-process-query-on-exit-flag proc nil))))
+
+(defun notmuch-tree (&optional query query-context target buffer-name open-target)
+  "Display threads matching QUERY in Tree View.
+
+The arguments are:
+  QUERY: the main query. This can be any query but in many cases will be
+      a single thread. If nil this is read interactively from the minibuffer.
+  QUERY-CONTEXT: is an additional term for the query. The query used
+      is QUERY and QUERY-CONTEXT unless that does not match any messages
+      in which case we fall back to just QUERY.
+  TARGET: A message ID (with the id: prefix) that will be made
+      current if it appears in the tree view results.
+  BUFFER-NAME: the name of the buffer to display the tree view. If
+      it is nil \"*notmuch-tree\" followed by QUERY is used.
+  OPEN-TARGET: If TRUE open the target message in the message pane."
+  (interactive)
+  (if (null query)
+      (setq query (notmuch-read-query "Notmuch tree view search: ")))
+  (let ((buffer (get-buffer-create (generate-new-buffer-name
+                                   (or buffer-name
+                                       (concat "*notmuch-tree-" query "*")))))
+       (inhibit-read-only t))
+
+    (switch-to-buffer buffer))
+  ;; Don't track undo information for this buffer
+  (set 'buffer-undo-list t)
+
+  (notmuch-tree-worker query query-context target open-target)
+
+  (setq truncate-lines t))
+
+
+;;
+
+(provide 'notmuch-tree)
index f3ce840028ec8b62bf437a9ec3f63f42ebb17977..c9bc2f22c7d43a21598e2c189ef6fe3f0f738f5a 100644 (file)
@@ -54,6 +54,7 @@
 (require 'notmuch-lib)
 (require 'notmuch-tag)
 (require 'notmuch-show)
+(require 'notmuch-tree)
 (require 'notmuch-mua)
 (require 'notmuch-hello)
 (require 'notmuch-maildir-fcc)
@@ -119,83 +120,6 @@ To enter a line break in customize, press \\[quoted-insert] C-j."
             (mm-save-part p))))
    mm-handle))
 
-(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)))
-               (as-list))
-           (map-keymap (lambda (a b)
-                         (push (cons a b) as-list))
-                       action)
-           (mapconcat substitute as-list "\n"))
-       (concat prefix (format-kbd-macro (vector key))
-               "\t"
-               (notmuch-documentation-first-line action))))))
-
-(defun notmuch-substitute-command-keys-one (key)
-  ;; A `keymap' key indicates inheritance from a parent keymap - the
-  ;; inherited mappings follow, so there is nothing to print for
-  ;; `keymap' itself.
-  (when (not (eq key 'keymap))
-    (notmuch-substitute-one-command-key-with-prefix nil key)))
-
-(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* ((keymap-name (substring doc (match-beginning 1) (match-end 1)))
-            (keymap (symbol-value (intern keymap-name))))
-       (setq doc (replace-match
-                  (mapconcat #'notmuch-substitute-command-keys-one
-                             (cdr keymap) "\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))))
-
 (require 'hl-line)
 
 (defun notmuch-hl-line-mode ()
@@ -212,9 +136,8 @@ For a mouse binding, return nil."
 
 (defvar notmuch-search-mode-map
   (let ((map (make-sparse-keymap)))
-    (define-key map "?" 'notmuch-help)
-    (define-key map "q" 'notmuch-search-quit)
-    (define-key map "x" 'notmuch-search-quit)
+    (set-keymap-parent map notmuch-common-keymap)
+    (define-key map "x" 'notmuch-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)
@@ -224,12 +147,8 @@ For a mouse binding, return nil."
     (define-key map "n" 'notmuch-search-next-thread)
     (define-key map "r" 'notmuch-search-reply-to-thread-sender)
     (define-key map "R" 'notmuch-search-reply-to-thread)
-    (define-key map "m" 'notmuch-mua-new-mail)
-    (define-key map "s" 'notmuch-search)
     (define-key map "o" 'notmuch-search-toggle-order)
     (define-key map "c" 'notmuch-search-stash-map)
-    (define-key map "=" 'notmuch-search-refresh-view)
-    (define-key map "G" 'notmuch-search-poll-and-refresh-view)
     (define-key map "t" 'notmuch-search-filter-by-tag)
     (define-key map "f" 'notmuch-search-filter)
     (define-key map [mouse-1] 'notmuch-search-show-thread)
@@ -238,6 +157,7 @@ For a mouse binding, return nil."
     (define-key map "-" 'notmuch-search-remove-tag)
     (define-key map "+" 'notmuch-search-add-tag)
     (define-key map (kbd "RET") 'notmuch-search-show-thread)
+    (define-key map "Z" 'notmuch-tree-from-search-current-query)
     map)
   "Keymap for \"notmuch search\" buffers.")
 (fset 'notmuch-search-mode-map notmuch-search-mode-map)
@@ -257,18 +177,9 @@ For a mouse binding, return nil."
 (defvar notmuch-search-query-string)
 (defvar notmuch-search-target-thread)
 (defvar notmuch-search-target-line)
-(defvar notmuch-search-continuation)
 
 (defvar notmuch-search-disjunctive-regexp      "\\<[oO][rR]\\>")
 
-(defun notmuch-search-quit ()
-  "Exit the search buffer, calling any defined continuation function."
-  (interactive)
-  (let ((continuation notmuch-search-continuation))
-    (notmuch-kill-this-buffer)
-    (when continuation
-      (funcall continuation))))
-
 (defun notmuch-search-scroll-up ()
   "Move forward through search results by one window's worth."
   (interactive)
@@ -412,7 +323,7 @@ Complete list of currently available key bindings:
   (make-local-variable 'notmuch-search-oldest-first)
   (make-local-variable 'notmuch-search-target-thread)
   (make-local-variable 'notmuch-search-target-line)
-  (set (make-local-variable 'notmuch-search-continuation) nil)
+  (setq notmuch-buffer-refresh-function #'notmuch-search-refresh-view)
   (set (make-local-variable 'scroll-preserve-screen-position) t)
   (add-to-invisibility-spec (cons 'ellipsis t))
   (use-local-map notmuch-search-mode-map)
@@ -489,14 +400,25 @@ If BARE is set then do not prefix with \"thread:\""
   (let ((thread (plist-get (notmuch-search-get-result) :thread)))
     (when thread (concat (unless bare "thread:") thread))))
 
-(defun notmuch-search-find-thread-id-region (beg end)
-  "Return a list of threads for the current region"
-  (mapcar (lambda (thread) (concat "thread:" thread))
-         (notmuch-search-properties-in-region :thread beg end)))
+(defun notmuch-search-find-stable-query ()
+  "Return the stable queries for the current thread.
 
-(defun notmuch-search-find-thread-id-region-search (beg end)
-  "Return a search string for threads for the current region"
-  (mapconcat 'identity (notmuch-search-find-thread-id-region beg end) " or "))
+This returns a list (MATCHED-QUERY UNMATCHED-QUERY) for the
+matched and unmatched messages in the current thread."
+  (plist-get (notmuch-search-get-result) :query))
+
+(defun notmuch-search-find-stable-query-region (beg end &optional only-matched)
+  "Return the stable query for the current region.
+
+If ONLY-MATCHED is non-nil, include only matched messages.  If it
+is nil, include both matched and unmatched messages."
+  (let ((query-list nil) (all (not only-matched)))
+    (dolist (queries (notmuch-search-properties-in-region :query beg end))
+      (when (first queries)
+       (push (first queries) query-list))
+      (when (and all (second queries))
+       (push (second queries) query-list)))
+    (concat "(" (mapconcat 'identity query-list ") or (") ")")))
 
 (defun notmuch-search-find-authors ()
   "Return the authors for the current thread"
@@ -514,19 +436,34 @@ If BARE is set then do not prefix with \"thread:\""
   "Return a list of authors for the current region"
   (notmuch-search-properties-in-region :subject beg end))
 
-(defun notmuch-search-show-thread ()
+(defun notmuch-search-show-thread (&optional elide-toggle)
   "Display the currently selected thread."
-  (interactive)
+  (interactive "P")
   (let ((thread-id (notmuch-search-find-thread-id))
        (subject (notmuch-search-find-subject)))
     (if (> (length thread-id) 0)
        (notmuch-show thread-id
+                     elide-toggle
                      (current-buffer)
                      notmuch-search-query-string
                      ;; Name the buffer based on the subject.
                      (concat "*" (truncate-string-to-width subject 30 nil nil t) "*"))
       (message "End of search results."))))
 
+(defun notmuch-tree-from-search-current-query ()
+  "Call notmuch tree with the current query"
+  (interactive)
+  (notmuch-tree notmuch-search-query-string))
+
+(defun notmuch-tree-from-search-thread ()
+  "Show the selected thread with notmuch-tree"
+  (interactive)
+  (notmuch-tree (notmuch-search-find-thread-id)
+                notmuch-search-query-string
+               nil
+                (notmuch-prettify-subject (notmuch-search-find-subject))
+               t))
+
 (defun notmuch-search-reply-to-thread (&optional prompt-for-sender)
   "Begin composing a reply-all to the entire current thread in a new buffer."
   (interactive "P")
@@ -539,17 +476,6 @@ If BARE is set then do not prefix with \"thread:\""
   (let ((message-id (notmuch-search-find-thread-id)))
     (notmuch-mua-new-reply message-id prompt-for-sender nil)))
 
-(defun notmuch-call-notmuch-process (&rest args)
-  "Synchronously invoke \"notmuch\" with the given list of arguments.
-
-If notmuch exits with a non-zero status, output from the process
-will appear in a buffer named \"*Notmuch errors*\" and an error
-will be signaled."
-  (with-temp-buffer
-    (let ((status (apply #'call-process notmuch-command nil t nil args)))
-      (notmuch-check-exit-status status (cons notmuch-command args)
-                                (buffer-string)))))
-
 (defun notmuch-search-set-tags (tags &optional pos)
   (let ((new-result (plist-put (notmuch-search-get-result pos) :tags tags)))
     (notmuch-search-update-result new-result pos)))
@@ -564,37 +490,65 @@ will be signaled."
        (setq output (append output (notmuch-search-get-tags pos)))))
     output))
 
-(defun notmuch-search-tag-region (beg end &optional tag-changes)
-  "Change tags for threads in the given region."
-  (let ((search-string (notmuch-search-find-thread-id-region-search beg end)))
-    (setq tag-changes (notmuch-tag search-string tag-changes))
+(defun notmuch-search-interactive-region ()
+  "Return the bounds of the current interactive region.
+
+This returns (BEG END), where BEG and END are the bounds of the
+region if the region is active, or both `point' otherwise."
+  (if (region-active-p)
+      (list (region-beginning) (region-end))
+    (list (point) (point))))
+
+(defun notmuch-search-interactive-tag-changes (&optional initial-input)
+  "Prompt for tag changes for the current thread or region.
+
+Returns (TAG-CHANGES REGION-BEGIN REGION-END)."
+  (let* ((region (notmuch-search-interactive-region))
+        (beg (first region)) (end (second region))
+        (prompt (if (= beg end) "Tag thread" "Tag region")))
+    (cons (notmuch-read-tag-changes
+          (notmuch-search-get-tags-region beg end) prompt initial-input)
+         region)))
+
+(defun notmuch-search-tag (tag-changes &optional beg end only-matched)
+  "Change tags for the currently selected thread or region.
+
+See `notmuch-tag' for information on the format of TAG-CHANGES.
+When called interactively, this uses the region if the region is
+active.  When called directly, BEG and END provide the region.
+If these are nil or not provided, this applies to the thread at
+point.
+
+If ONLY-MATCHED is non-nil, only tag matched messages."
+  (interactive (notmuch-search-interactive-tag-changes))
+  (unless (and beg end) (setq beg (point) end (point)))
+  (let ((search-string (notmuch-search-find-stable-query-region
+                       beg end only-matched)))
+    (notmuch-tag search-string tag-changes)
     (notmuch-search-foreach-result beg end
       (lambda (pos)
        (notmuch-search-set-tags
         (notmuch-update-tags (notmuch-search-get-tags pos) tag-changes)
         pos)))))
 
-(defun notmuch-search-tag (&optional tag-changes)
-  "Change tags for the currently selected thread or region.
+(defun notmuch-search-add-tag (tag-changes &optional beg end)
+  "Change tags for the current thread or region (defaulting to add).
 
-See `notmuch-tag' for information on the format of TAG-CHANGES."
-  (interactive)
-  (let* ((beg (if (region-active-p) (region-beginning) (point)))
-        (end (if (region-active-p) (region-end) (point))))
-    (notmuch-search-tag-region beg end tag-changes)))
+Same as `notmuch-search-tag' but sets initial input to '+'."
+  (interactive (notmuch-search-interactive-tag-changes "+"))
+  (notmuch-search-tag tag-changes beg end))
 
-(defun notmuch-search-add-tag ()
-  "Same as `notmuch-search-tag' but sets initial input to '+'."
-  (interactive)
-  (notmuch-search-tag "+"))
+(defun notmuch-search-remove-tag (tag-changes &optional beg end)
+  "Change tags for the current thread or region (defaulting to remove).
 
-(defun notmuch-search-remove-tag ()
-  "Same as `notmuch-search-tag' but sets initial input to '-'."
-  (interactive)
-  (notmuch-search-tag "-"))
+Same as `notmuch-search-tag' but sets initial input to '-'."
+  (interactive (notmuch-search-interactive-tag-changes "-"))
+  (notmuch-search-tag tag-changes beg end))
 
-(defun notmuch-search-archive-thread (&optional unarchive)
-  "Archive the currently selected thread.
+(put 'notmuch-search-archive-thread 'notmuch-prefix-doc
+     "Un-archive the currently selected thread.")
+(defun notmuch-search-archive-thread (&optional unarchive beg end)
+  "Archive the currently selected thread or region.
 
 Archive each message in the currently selected thread by applying
 the tag changes in `notmuch-archive-tags' to each (remove the
@@ -603,10 +557,10 @@ messages will be \"unarchived\" (i.e. the tag changes in
 `notmuch-archive-tags' will be reversed).
 
 This function advances the next thread when finished."
-  (interactive "P")
+  (interactive (cons current-prefix-arg (notmuch-search-interactive-region)))
   (when notmuch-archive-tags
     (notmuch-search-tag
-     (notmuch-tag-change-list notmuch-archive-tags unarchive)))
+     (notmuch-tag-change-list notmuch-archive-tags unarchive) beg end))
   (notmuch-search-next-thread))
 
 (defun notmuch-search-update-result (result &optional pos)
@@ -789,11 +743,13 @@ non-authors is found, assume that all of the authors match."
                                        (plist-get result :total)))
                        'face 'notmuch-search-count)))
    ((string-equal field "subject")
-    (insert (propertize (format format-string (plist-get result :subject))
+    (insert (propertize (format format-string
+                               (notmuch-sanitize (plist-get result :subject)))
                        'face 'notmuch-search-subject)))
 
    ((string-equal field "authors")
-    (notmuch-search-insert-authors format-string (plist-get result :authors)))
+    (notmuch-search-insert-authors
+     format-string (notmuch-sanitize (plist-get result :authors))))
 
    ((string-equal field "tags")
     (let ((tags (plist-get result :tags)))
@@ -830,12 +786,14 @@ non-authors is found, assume that all of the authors match."
        (notmuch-sexp-parse-partial-list 'notmuch-search-show-result
                                         results-buf)))))
 
-(defun notmuch-search-tag-all (&optional tag-changes)
+(defun notmuch-search-tag-all (tag-changes)
   "Add/remove tags from all messages in current search buffer.
 
 See `notmuch-tag' for information on the format of TAG-CHANGES."
-  (interactive)
-  (apply 'notmuch-tag notmuch-search-query-string tag-changes))
+  (interactive
+   (list (notmuch-read-tag-changes
+         (notmuch-search-get-tags-region (point-min) (point-max)) "Tag all")))
+  (notmuch-search-tag tag-changes (point-min) (point-max) t))
 
 (defun notmuch-search-buffer-title (query)
   "Returns the title for a buffer with notmuch search results."
@@ -896,18 +854,29 @@ PROMPT is the string to prompt with."
                              'notmuch-search-history nil nil)))))
 
 ;;;###autoload
-(defun notmuch-search (&optional query oldest-first target-thread target-line continuation)
-  "Run \"notmuch search\" with the given `query' and display results.
+(put 'notmuch-search 'notmuch-doc "Search for messages.")
+(defun notmuch-search (&optional query oldest-first target-thread target-line)
+  "Display threads matching QUERY in a notmuch-search buffer.
 
-If `query' is nil, it is read interactively from the minibuffer.
+If QUERY is nil, it is read interactively from the minibuffer.
 Other optional parameters are used as follows:
 
-  oldest-first: A Boolean controlling the sort order of returned threads
-  target-thread: A thread ID (without the thread: prefix) that will be made
+  OLDEST-FIRST: A Boolean controlling the sort order of returned threads
+  TARGET-THREAD: A thread ID (without the thread: prefix) that will be made
                  current if it appears in the search results.
-  target-line: The line number to move to if the target thread does not
-               appear in the search results."
-  (interactive)
+  TARGET-LINE: The line number to move to if the target thread does not
+               appear in the search results.
+
+When called interactively, this will prompt for a query and use
+the configured default sort order."
+  (interactive
+   (list
+    ;; Prompt for a query
+    nil
+    ;; Use the default search order (if we're doing a search from a
+    ;; search buffer, ignore any buffer-local overrides)
+    (default-value 'notmuch-search-oldest-first)))
+
   (let* ((query (or query (notmuch-read-query "Notmuch search: ")))
         (buffer (get-buffer-create (notmuch-search-buffer-title query))))
     (switch-to-buffer buffer)
@@ -918,7 +887,6 @@ Other optional parameters are used as follows:
     (set 'notmuch-search-oldest-first oldest-first)
     (set 'notmuch-search-target-thread target-thread)
     (set 'notmuch-search-target-line target-line)
-    (set 'notmuch-search-continuation continuation)
     (let ((proc (get-buffer-process (current-buffer)))
          (inhibit-read-only t))
       (if proc
@@ -929,7 +897,7 @@ Other optional parameters are used as follows:
       (save-excursion
        (let ((proc (notmuch-start-notmuch
                     "notmuch-search" buffer #'notmuch-search-process-sentinel
-                    "search" "--format=sexp" "--format-version=1"
+                    "search" "--format=sexp" "--format-version=2"
                     (if oldest-first
                         "--sort=oldest-first"
                       "--sort=newest-first")
@@ -951,60 +919,14 @@ query string as the current search. If the current thread is in
 the new search results, then point will be placed on the same
 thread. Otherwise, point will be moved to attempt to be in the
 same relative position within the new buffer."
-  (interactive)
   (let ((target-line (line-number-at-pos))
        (oldest-first notmuch-search-oldest-first)
        (target-thread (notmuch-search-find-thread-id 'bare))
-       (query notmuch-search-query-string)
-       (continuation notmuch-search-continuation))
+       (query notmuch-search-query-string))
     (notmuch-kill-this-buffer)
-    (notmuch-search query oldest-first target-thread target-line continuation)
+    (notmuch-search query oldest-first target-thread target-line)
     (goto-char (point-min))))
 
-(defcustom notmuch-poll-script nil
-  "An external script to incorporate new mail into the notmuch database.
-
-This variable controls the action invoked by
-`notmuch-search-poll-and-refresh-view' and
-`notmuch-hello-poll-and-update' (each have a default keybinding
-of 'G') to incorporate new mail into the notmuch database.
-
-If set to nil (the default), new mail is processed by invoking
-\"notmuch new\". Otherwise, this should be set to a string that
-gives the name of an external script that processes new mail. If
-set to the empty string, no command will be run.
-
-The external script could do any of the following depending on
-the user's needs:
-
-1. Invoke a program to transfer mail to the local mail store
-2. Invoke \"notmuch new\" to incorporate the new mail
-3. Invoke one or more \"notmuch tag\" commands to classify the mail
-
-Note that the recommended way of achieving the same is using
-\"notmuch new\" hooks."
-  :type '(choice (const :tag "notmuch new" nil)
-                (const :tag "Disabled" "")
-                (string :tag "Custom script"))
-  :group 'notmuch-external)
-
-(defun notmuch-poll ()
-  "Run \"notmuch new\" or an external script to import mail.
-
-Invokes `notmuch-poll-script', \"notmuch new\", or does nothing
-depending on the value of `notmuch-poll-script'."
-  (interactive)
-  (if (stringp notmuch-poll-script)
-      (unless (string= notmuch-poll-script "")
-       (call-process notmuch-poll-script nil nil))
-    (call-process notmuch-command nil nil nil "new")))
-
-(defun notmuch-search-poll-and-refresh-view ()
-  "Invoke `notmuch-poll' to import mail, then refresh the current view."
-  (interactive)
-  (notmuch-poll)
-  (notmuch-search-refresh-view))
-
 (defun notmuch-search-toggle-order ()
   "Toggle the current search order.
 
diff --git a/gmime-filter-headers.c b/gmime-filter-headers.c
deleted file mode 100644 (file)
index 7db3779..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright © 2009 Keith Packard <keithp@keithp.com>
- * Copyright © 2010 Michal Sojka <sojkam1@fel.cvut.cz>
- *
- * 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, write to the Free Software Foundation, Inc.,
- * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
- */
-
-#include "gmime-filter-headers.h"
-#include <string.h>
-#include <gmime/gmime-utils.h>
-#include <glib/gprintf.h>
-#include <stdlib.h>
-#include <xutil.h>
-
-/**
- * SECTION: gmime-filter-headers
- * @title: GMimeFilterHeaders
- * @short_description: Add/remove headers markers
- *
- * A #GMimeFilter for decoding rfc2047 encoded headers to UTF-8
- **/
-
-
-static void g_mime_filter_headers_class_init (GMimeFilterHeadersClass *klass);
-static void g_mime_filter_headers_init (GMimeFilterHeaders *filter, GMimeFilterHeadersClass *klass);
-static void g_mime_filter_headers_finalize (GObject *object);
-
-static GMimeFilter *filter_copy (GMimeFilter *filter);
-static void filter_filter (GMimeFilter *filter, char *in, size_t len, size_t prespace,
-                          char **out, size_t *outlen, size_t *outprespace);
-static void filter_complete (GMimeFilter *filter, char *in, size_t len, size_t prespace,
-                            char **out, size_t *outlen, size_t *outprespace);
-static void filter_reset (GMimeFilter *filter);
-
-
-static GMimeFilterClass *parent_class = NULL;
-
-GType
-g_mime_filter_headers_get_type (void)
-{
-       static GType type = 0;
-
-       if (!type) {
-               static const GTypeInfo info = {
-                       sizeof (GMimeFilterHeadersClass),
-                       NULL, /* base_class_init */
-                       NULL, /* base_class_finalize */
-                       (GClassInitFunc) g_mime_filter_headers_class_init,
-                       NULL, /* class_finalize */
-                       NULL, /* class_data */
-                       sizeof (GMimeFilterHeaders),
-                       0,    /* n_preallocs */
-                       (GInstanceInitFunc) g_mime_filter_headers_init,
-                       NULL    /* value_table */
-               };
-
-               type = g_type_register_static (GMIME_TYPE_FILTER, "GMimeFilterHeaders", &info, (GTypeFlags) 0);
-       }
-
-       return type;
-}
-
-
-static void
-g_mime_filter_headers_class_init (GMimeFilterHeadersClass *klass)
-{
-       GObjectClass *object_class = G_OBJECT_CLASS (klass);
-       GMimeFilterClass *filter_class = GMIME_FILTER_CLASS (klass);
-
-       parent_class = (GMimeFilterClass *) g_type_class_ref (GMIME_TYPE_FILTER);
-
-       object_class->finalize = g_mime_filter_headers_finalize;
-
-       filter_class->copy = filter_copy;
-       filter_class->filter = filter_filter;
-       filter_class->complete = filter_complete;
-       filter_class->reset = filter_reset;
-}
-
-static void
-g_mime_filter_headers_init (GMimeFilterHeaders *filter, GMimeFilterHeadersClass *klass)
-{
-       (void) klass;
-       filter->saw_nl = TRUE;
-       filter->line = NULL;
-       filter->line_size = 0;
-       filter->lineptr = NULL;
-}
-
-static void
-g_mime_filter_headers_finalize (GObject *object)
-{
-       free (GMIME_FILTER_HEADERS (object)->line);
-       G_OBJECT_CLASS (parent_class)->finalize (object);
-}
-
-
-static GMimeFilter *
-filter_copy (GMimeFilter *filter)
-{
-       (void) filter;
-       return g_mime_filter_headers_new ();
-}
-
-static void
-output_decoded_header (GMimeFilterHeaders *headers, char **outptr)
-{
-       char *colon, *name, *s, *decoded_value;
-       size_t offset;
-       gint ret;
-
-       colon = strchr (headers->line, ':');
-       if (colon == NULL)
-               return;
-
-       name = headers->line;
-       *colon = '\0';
-       s = colon + 1;
-       while (*s == ' ' || *s == '\t')
-               s++;
-       decoded_value = g_mime_utils_header_decode_text(s);
-       if (decoded_value == NULL)
-               return;
-       offset = *outptr - GMIME_FILTER (headers)->outbuf;
-       g_mime_filter_set_size (GMIME_FILTER (headers), strlen(name) + 2 +
-                              strlen(decoded_value) + 2, TRUE);
-       *outptr = GMIME_FILTER (headers)->outbuf + offset;
-       ret = g_sprintf (*outptr, "%s: %s\n", name, decoded_value);
-       if (ret > 0)
-               *outptr += ret;
-       free (decoded_value);
-}
-
-static void
-output_final_newline (GMimeFilterHeaders *headers, char **outptr)
-{
-       size_t offset;
-
-       offset = *outptr - GMIME_FILTER (headers)->outbuf;
-       g_mime_filter_set_size (GMIME_FILTER (headers), 1, TRUE);
-       *outptr = GMIME_FILTER (headers)->outbuf + offset;
-       *(*outptr)++ = '\n';
-}
-
-static void
-filter_filter (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace,
-              char **outbuf, size_t *outlen, size_t *outprespace)
-{
-       GMimeFilterHeaders *headers = (GMimeFilterHeaders *) filter;
-       register const char *inptr = inbuf;
-       const char *inend = inbuf + inlen;
-       char *lineptr, *lineend, *outptr;
-
-       (void) prespace;
-       if (headers->line == NULL) {
-               headers->line_size = 200;
-               headers->lineptr = headers->line = malloc (headers->line_size);
-       }
-       lineptr = headers->lineptr;
-       lineend = headers->line + headers->line_size - 1;
-       if (lineptr == NULL)
-               return;
-       outptr = filter->outbuf;
-       while (inptr < inend) {
-               if (*inptr == '\n') {
-                       if (headers->saw_nl)
-                               output_final_newline(headers, &outptr);
-                       headers->saw_nl = TRUE;
-                       inptr++;
-                       continue;
-               }
-
-               if (lineptr == lineend) {
-                       headers->line_size *= 2;
-                       headers->line = xrealloc (headers->line, headers->line_size);
-                       lineptr = headers->line + (headers->line_size / 2) - 1;
-                       lineend = headers->line + headers->line_size - 1;
-               }
-
-               if (headers->saw_nl && *inptr != ' ' && *inptr != '\t') {
-                       *lineptr = '\0';
-                       output_decoded_header (headers, &outptr);
-                       lineptr = headers->line;
-               }
-               if (headers->saw_nl && (*inptr == ' ' || *inptr == '\t')) {
-                       *lineptr = ' ';
-                       lineptr++;
-                       while (inptr < inend && (*inptr == ' ' || *inptr == '\t'))
-                               inptr++;
-                       headers->saw_nl = FALSE;
-                       continue;
-               }
-               headers->saw_nl = FALSE;
-
-               if (*inptr != '\r')
-                       *lineptr++ = *inptr;
-               inptr++;
-       }
-       if (headers->saw_nl) {
-               *lineptr = '\0';
-               output_decoded_header (headers, &outptr);
-               lineptr = headers->line;
-       }
-       headers->lineptr = lineptr;
-       *outlen = outptr - filter->outbuf;
-       *outprespace = filter->outpre;
-       *outbuf = filter->outbuf;
-}
-
-static void
-filter_complete (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace,
-                char **outbuf, size_t *outlen, size_t *outprespace)
-{
-       if (inbuf && inlen)
-               filter_filter (filter, inbuf, inlen, prespace, outbuf, outlen, outprespace);
-}
-
-static void
-filter_reset (GMimeFilter *filter)
-{
-       GMimeFilterHeaders *headers = (GMimeFilterHeaders *) filter;
-
-       headers->saw_nl = TRUE;
-       free(headers->line);
-       headers->line = NULL;
-       headers->line_size = 0;
-}
-
-
-/**
- * g_mime_filter_headers_new:
- * @encode: %TRUE if the filter should encode or %FALSE otherwise
- * @dots: encode/decode dots (as for SMTP)
- *
- * Creates a new #GMimeFilterHeaders filter.
- *
- * If @encode is %TRUE, then all lines will be prefixed by "> ",
- * otherwise any lines starting with "> " will have that removed
- *
- * Returns: a new #GMimeFilterHeaders filter.
- **/
-GMimeFilter *
-g_mime_filter_headers_new (void)
-{
-       GMimeFilterHeaders *new_headers;
-
-       new_headers = (GMimeFilterHeaders *) g_object_newv (GMIME_TYPE_FILTER_HEADERS, 0, NULL);
-
-       return (GMimeFilter *) new_headers;
-}
-
diff --git a/gmime-filter-headers.h b/gmime-filter-headers.h
deleted file mode 100644 (file)
index 1d1a3eb..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright © 2009 Keith Packard <keithp@keithp.com>
- * Copyright © 2010 Michal Sojka <sojkam1@fel.cvut.cz>
- *
- * 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, write to the Free Software Foundation, Inc.,
- * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
- */
-
-#ifndef _GMIME_FILTER_HEADERS_H_
-#define _GMIME_FILTER_HEADERS_H_
-
-#include <gmime/gmime-filter.h>
-
-G_BEGIN_DECLS
-
-#define GMIME_TYPE_FILTER_HEADERS            (g_mime_filter_headers_get_type ())
-#define GMIME_FILTER_HEADERS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GMIME_TYPE_FILTER_HEADERS, GMimeFilterHeaders))
-#define GMIME_FILTER_HEADERS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GMIME_TYPE_FILTER_HEADERS, GMimeFilterHeadersClass))
-#define GMIME_IS_FILTER_HEADERS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GMIME_TYPE_FILTER_HEADERS))
-#define GMIME_IS_FILTER_HEADERS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GMIME_TYPE_FILTER_HEADERS))
-#define GMIME_FILTER_HEADERS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GMIME_TYPE_FILTER_HEADERS, GMimeFilterHeadersClass))
-
-typedef struct _GMimeFilterHeaders GMimeFilterHeaders;
-typedef struct _GMimeFilterHeadersClass GMimeFilterHeadersClass;
-
-/**
- * GMimeFilterHeaders:
- * @parent_object: parent #GMimeFilter
- * @saw_nl: previous char was a \n
- * @line: temporary buffer for line unfolding
- * @line_size: size of currently allocated memory for @line
- * @lineptr: pointer to the first unused character in @line
- *
- * A filter to decode rfc2047 encoded headers
- **/
-struct _GMimeFilterHeaders {
-       GMimeFilter parent_object;
-
-       gboolean saw_nl;
-       char *line;
-       size_t line_size;
-       char *lineptr;
-};
-
-struct _GMimeFilterHeadersClass {
-       GMimeFilterClass parent_class;
-
-};
-
-
-GType g_mime_filter_headers_get_type (void);
-
-GMimeFilter *g_mime_filter_headers_new (void);
-
-G_END_DECLS
-
-
-#endif /* _GMIME_FILTER_HEADERS_H_ */
index 155ac02ff00e3f90bd4227edf393e90f0e887bdf..c56cba99d7fa4aad169abc40bc112bca35552efb 100644 (file)
@@ -11,13 +11,16 @@ LIBNOTMUCH_VERSION_MAJOR = 3
 # the time of release for any additions to the library interface,
 # (and when it is incremented, the release version of the library should
 #  be reset to 0).
-LIBNOTMUCH_VERSION_MINOR = 0
+LIBNOTMUCH_VERSION_MINOR = 1
 
 # The release version the library interface. This should be incremented at
 # the time of release if there have been no changes to the interface, (but
 # simply compatible changes to the implementation).
 LIBNOTMUCH_VERSION_RELEASE = 0
 
+# Note: Don't forget to change the VERSION macros in notmuch.h when
+# any of the above change.
+
 ifeq ($(PLATFORM),MACOSX)
 LIBRARY_SUFFIX = dylib
 # On OS X, library version numbers go before suffix.
index 5cc076587a88785a5801eafcc56df2abef997d20..f395061e3a73f91b3324cc97063e7ba2c149579f 100644 (file)
@@ -24,7 +24,9 @@
 #include <iostream>
 
 #include <sys/time.h>
+#include <sys/stat.h>
 #include <signal.h>
+#include <ftw.h>
 
 #include <glib.h> /* g_free, GPtrArray, GHashTable */
 #include <glib-object.h> /* g_type_init */
@@ -268,6 +270,8 @@ notmuch_status_to_string (notmuch_status_t status)
        return "Unbalanced number of calls to notmuch_message_freeze/thaw";
     case NOTMUCH_STATUS_UNBALANCED_ATOMIC:
        return "Unbalanced number of calls to notmuch_database_begin_atomic/end_atomic";
+    case NOTMUCH_STATUS_UNSUPPORTED_OPERATION:
+       return "Unsupported operation";
     default:
     case NOTMUCH_STATUS_LAST_STATUS:
        return "Unknown error status value";
@@ -655,7 +659,7 @@ notmuch_database_open (const char *path,
 
     /* Initialize gmime */
     if (! initialized) {
-       g_mime_init (0);
+       g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
        initialized = 1;
     }
 
@@ -800,6 +804,186 @@ notmuch_database_close (notmuch_database_t *notmuch)
     notmuch->date_range_processor = NULL;
 }
 
+#if HAVE_XAPIAN_COMPACT
+static int
+unlink_cb (const char *path,
+          unused (const struct stat *sb),
+          unused (int type),
+          unused (struct FTW *ftw))
+{
+    return remove (path);
+}
+
+static int
+rmtree (const char *path)
+{
+    return nftw (path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
+}
+
+class NotmuchCompactor : public Xapian::Compactor
+{
+    notmuch_compact_status_cb_t status_cb;
+    void *status_closure;
+
+public:
+    NotmuchCompactor(notmuch_compact_status_cb_t cb, void *closure) :
+       status_cb (cb), status_closure (closure) { }
+
+    virtual void
+    set_status (const std::string &table, const std::string &status)
+    {
+       char *msg;
+
+       if (status_cb == NULL)
+           return;
+
+       if (status.length () == 0)
+           msg = talloc_asprintf (NULL, "compacting table %s", table.c_str());
+       else
+           msg = talloc_asprintf (NULL, "     %s", status.c_str());
+
+       if (msg == NULL) {
+           return;
+       }
+
+       status_cb (msg, status_closure);
+       talloc_free (msg);
+    }
+};
+
+/* Compacts the given database, optionally saving the original database
+ * in backup_path. Additionally, a callback function can be provided to
+ * give the user feedback on the progress of the (likely long-lived)
+ * compaction process.
+ *
+ * The backup path must point to a directory on the same volume as the
+ * original database. Passing a NULL backup_path will result in the
+ * uncompacted database being deleted after compaction has finished.
+ * Note that the database write lock will be held during the
+ * compaction process to protect data integrity.
+ */
+notmuch_status_t
+notmuch_database_compact (const char *path,
+                         const char *backup_path,
+                         notmuch_compact_status_cb_t status_cb,
+                         void *closure)
+{
+    void *local;
+    char *notmuch_path, *xapian_path, *compact_xapian_path;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    notmuch_database_t *notmuch = NULL;
+    struct stat statbuf;
+    notmuch_bool_t keep_backup;
+
+    local = talloc_new (NULL);
+    if (! local)
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+    ret = notmuch_database_open (path, NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch);
+    if (ret) {
+       goto DONE;
+    }
+
+    if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
+       ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
+       ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    if (! (compact_xapian_path = talloc_asprintf (local, "%s.compact", xapian_path))) {
+       ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    if (backup_path == NULL) {
+       if (! (backup_path = talloc_asprintf (local, "%s.old", xapian_path))) {
+           ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
+           goto DONE;
+       }
+       keep_backup = FALSE;
+    }
+    else {
+       keep_backup = TRUE;
+    }
+
+    if (stat (backup_path, &statbuf) != -1) {
+       fprintf (stderr, "Path already exists: %s\n", backup_path);
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+    if (errno != ENOENT) {
+       fprintf (stderr, "Unknown error while stat()ing path: %s\n",
+                strerror (errno));
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    /* Unconditionally attempt to remove old work-in-progress database (if
+     * any). This is "protected" by database lock. If this fails due to write
+     * errors (etc), the following code will fail and provide error message.
+     */
+    (void) rmtree (compact_xapian_path);
+
+    try {
+       NotmuchCompactor compactor (status_cb, closure);
+
+       compactor.set_renumber (false);
+       compactor.add_source (xapian_path);
+       compactor.set_destdir (compact_xapian_path);
+       compactor.compact ();
+    } catch (const Xapian::Error &error) {
+       fprintf (stderr, "Error while compacting: %s\n", error.get_msg().c_str());
+       ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+       goto DONE;
+    }
+
+    if (rename (xapian_path, backup_path)) {
+       fprintf (stderr, "Error moving %s to %s: %s\n",
+                xapian_path, backup_path, strerror (errno));
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    if (rename (compact_xapian_path, xapian_path)) {
+       fprintf (stderr, "Error moving %s to %s: %s\n",
+                compact_xapian_path, xapian_path, strerror (errno));
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    if (! keep_backup) {
+       if (rmtree (backup_path)) {
+           fprintf (stderr, "Error removing old database %s: %s\n",
+                    backup_path, strerror (errno));
+           ret = NOTMUCH_STATUS_FILE_ERROR;
+           goto DONE;
+       }
+    }
+
+  DONE:
+    if (notmuch)
+       notmuch_database_destroy (notmuch);
+
+    talloc_free (local);
+
+    return ret;
+}
+#else
+notmuch_status_t
+notmuch_database_compact (unused (const char *path),
+                         unused (const char *backup_path),
+                         unused (notmuch_compact_status_cb_t status_cb),
+                         unused (void *closure))
+{
+    fprintf (stderr, "notmuch was compiled against a xapian version lacking compaction support.\n");
+    return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;
+}
+#endif
+
 void
 notmuch_database_destroy (notmuch_database_t *notmuch)
 {
@@ -1380,7 +1564,7 @@ _notmuch_database_generate_doc_id (notmuch_database_t *notmuch)
     notmuch->last_doc_id++;
 
     if (notmuch->last_doc_id == 0)
-       INTERNAL_ERROR ("Xapian document IDs are exhausted.\n");        
+       INTERNAL_ERROR ("Xapian document IDs are exhausted.\n");
 
     return notmuch->last_doc_id;
 }
index a2edd6d9b6c0795fbdb2512907265000bb54fe21..78c18cf36d10898a8ea3aebcb1c8fe84fcff3df3 100644 (file)
@@ -440,7 +440,7 @@ _notmuch_message_index_file (notmuch_message_t *message,
     static bool mbox_warning = false;
 
     if (! initialized) {
-       g_mime_init (0);
+       g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
        initialized = 1;
     }
 
index 5d16f6ab1a61363dbbcec827496842ba113338fd..aaaa4eb2fc14225aad9032b66097008d9390e07a 100644 (file)
@@ -34,7 +34,7 @@
 */
 
 #include <string.h>     /* for memcpy() etc.        */
-
+#include "endian-util.h"
 #include "libsha1.h"
 
 #if defined(__cplusplus)
@@ -49,11 +49,13 @@ extern "C"
 
 #define bswap_32(x) ((rotr32((x), 24) & 0x00ff00ff) | (rotr32((x), 8) & 0xff00ff00))
 
-#if (PLATFORM_BYTE_ORDER == IS_LITTLE_ENDIAN)
-#define bsw_32(p,n) \
-    { int _i = (n); while(_i--) ((uint32_t*)p)[_i] = bswap_32(((uint32_t*)p)[_i]); }
+#if (UTIL_BYTE_ORDER == UTIL_ORDER_LITTLE_ENDIAN)
+#  define bsw_32(p,n) \
+     { int _i = (n); while(_i--) ((uint32_t*)p)[_i] = bswap_32(((uint32_t*)p)[_i]); }
+#elif (UTIL_BYTE_ORDER == UTIL_ORDER_BIG_ENDIAN)
+#  define bsw_32(p,n)
 #else
-#define bsw_32(p,n)
+#  error "Unsupported byte order"
 #endif
 
 #define SHA1_MASK   (SHA1_BLOCK_SIZE - 1)
index 4d9af89fe44dd5923cee743572149f4cf2c22f46..a2850c278b5a10ba9a5041a6662aab4ed09e3861 100644 (file)
@@ -228,7 +228,7 @@ notmuch_message_file_get_header (notmuch_message_file_t *message,
     is_received = (strcmp(header_desired,"received") == 0);
 
     if (! initialized) {
-       g_mime_init (0);
+       g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
        initialized = 1;
     }
 
index c4261e614d4eb83b605bd2312cad727a57b9ab95..1b4637950f8e1a4475a4385db20fd6f4ccf7dd54 100644 (file)
@@ -1195,7 +1195,9 @@ _get_maildir_flag_actions (notmuch_message_t *message,
  * compute the new maildir filename.
  *
  * If the existing filename is in the directory "new", the new
- * filename will be in the directory "cur".
+ * filename will be in the directory "cur", except for the case when
+ * no flags are changed and the existing filename does not contain
+ * maildir info (starting with ",2:").
  *
  * After a sequence of ":2," in the filename, any subsequent
  * single-character flags will be added or removed according to the
@@ -1218,6 +1220,7 @@ _new_maildir_filename (void *ctx,
     char *filename_new, *dir;
     char flag_map[128];
     int flags_in_map = 0;
+    notmuch_bool_t flags_changed = FALSE;
     unsigned int i;
     char *s;
 
@@ -1258,6 +1261,7 @@ _new_maildir_filename (void *ctx,
        if (flag_map[flag] == 0) {
            flag_map[flag] = 1;
            flags_in_map++;
+           flags_changed = TRUE;
        }
     }
 
@@ -1266,9 +1270,16 @@ _new_maildir_filename (void *ctx,
        if (flag_map[flag]) {
            flag_map[flag] = 0;
            flags_in_map--;
+           flags_changed = TRUE;
        }
     }
 
+    /* Messages in new/ without maildir info can be kept in new/ if no
+     * flags have changed. */
+    dir = (char *) _filename_is_in_maildir (filename);
+    if (dir && STRNCMP_LITERAL (dir, "new/") == 0 && !*info && !flags_changed)
+       return talloc_strdup (ctx, filename);
+
     filename_new = (char *) talloc_size (ctx,
                                         info - filename +
                                         strlen (":2,") + flags_in_map + 1);
index cc55bb9d18ca78ea1135513dd2bfc6f0d85f9586..af185c7c5ba8838e82ca80931282c3b7dbe55f04 100644 (file)
@@ -162,8 +162,6 @@ typedef enum _notmuch_find_flags {
 
 typedef struct _notmuch_doc_id_set notmuch_doc_id_set_t;
 
-typedef struct _notmuch_string_list notmuch_string_list_t;
-
 /* database.cc */
 
 /* Lookup a prefix value by name.
@@ -228,17 +226,6 @@ _notmuch_directory_create (notmuch_database_t *notmuch,
 unsigned int
 _notmuch_directory_get_document_id (notmuch_directory_t *directory);
 
-/* thread.cc */
-
-notmuch_thread_t *
-_notmuch_thread_create (void *ctx,
-                       notmuch_database_t *notmuch,
-                       unsigned int seed_doc_id,
-                       notmuch_doc_id_set_t *match_set,
-                       notmuch_string_list_t *excluded_terms,
-                       notmuch_exclude_t omit_exclude,
-                       notmuch_sort_t sort);
-
 /* message.cc */
 
 notmuch_message_t *
@@ -386,7 +373,7 @@ void
 notmuch_message_file_restrict_headersv (notmuch_message_file_t *message,
                                        va_list va_headers);
 
-/* Get the value of the specified header from the message.
+/* Get the value of the specified header from the message as a UTF-8 string.
  *
  * The header name is case insensitive.
  *
@@ -476,11 +463,11 @@ typedef struct _notmuch_string_node {
     struct _notmuch_string_node *next;
 } notmuch_string_node_t;
 
-struct visible _notmuch_string_list {
+typedef struct visible _notmuch_string_list {
     int length;
     notmuch_string_node_t *head;
     notmuch_string_node_t **tail;
-};
+} notmuch_string_list_t;
 
 notmuch_string_list_t *
 _notmuch_string_list_create (const void *ctx);
@@ -509,6 +496,17 @@ notmuch_filenames_t *
 _notmuch_filenames_create (const void *ctx,
                           notmuch_string_list_t *list);
 
+/* thread.cc */
+
+notmuch_thread_t *
+_notmuch_thread_create (void *ctx,
+                       notmuch_database_t *notmuch,
+                       unsigned int seed_doc_id,
+                       notmuch_doc_id_set_t *match_set,
+                       notmuch_string_list_t *excluded_terms,
+                       notmuch_exclude_t omit_exclude,
+                       notmuch_sort_t sort);
+
 NOTMUCH_END_DECLS
 
 #ifdef __cplusplus
index 73c85a41816c6386ad97d0f347ec6d06bafc0f71..d30768d908ee72ac63121e1e2ac8df5cde2f87b1 100644 (file)
@@ -41,6 +41,34 @@ NOTMUCH_BEGIN_DECLS
 #define TRUE 1
 #endif
 
+/*
+ * The library version number.  This must agree with the soname
+ * version in Makefile.local.
+ */
+#define LIBNOTMUCH_MAJOR_VERSION       3
+#define LIBNOTMUCH_MINOR_VERSION       1
+#define LIBNOTMUCH_MICRO_VERSION       0
+
+/*
+ * Check the version of the notmuch library being compiled against.
+ *
+ * Return true if the library being compiled against is of the
+ * specified version or above. For example:
+ *
+ * #if LIBNOTMUCH_CHECK_VERSION(3, 1, 0)
+ *     (code requiring libnotmuch 3.1.0 or above)
+ * #endif
+ *
+ * LIBNOTMUCH_CHECK_VERSION has been defined since version 3.1.0; you
+ * can use #if !defined(NOTMUCH_CHECK_VERSION) to check for versions
+ * prior to that.
+ */
+#define LIBNOTMUCH_CHECK_VERSION (major, minor, micro)                 \
+    (LIBNOTMUCH_MAJOR_VERSION > (major) ||                                     \
+     (LIBNOTMUCH_MAJOR_VERSION == (major) && LIBNOTMUCH_MINOR_VERSION > (minor)) || \
+     (LIBNOTMUCH_MAJOR_VERSION == (major) && LIBNOTMUCH_MINOR_VERSION == (minor) && \
+      LIBNOTMUCH_MICRO_VERSION >= (micro)))
+
 typedef int notmuch_bool_t;
 
 /* Status codes used for the return values of most functions.
@@ -101,6 +129,7 @@ typedef enum _notmuch_status {
     NOTMUCH_STATUS_TAG_TOO_LONG,
     NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW,
     NOTMUCH_STATUS_UNBALANCED_ATOMIC,
+    NOTMUCH_STATUS_UNSUPPORTED_OPERATION,
 
     NOTMUCH_STATUS_LAST_STATUS
 } notmuch_status_t;
@@ -215,8 +244,30 @@ notmuch_database_open (const char *path,
 void
 notmuch_database_close (notmuch_database_t *database);
 
+/* A callback invoked by notmuch_database_compact to notify the user
+ * of the progress of the compaction process.
+ */
+typedef void (*notmuch_compact_status_cb_t)(const char *message, void *closure);
+
+/* Compact a notmuch database, backing up the original database to the
+ * given path.
+ *
+ * The database will be opened with NOTMUCH_DATABASE_MODE_READ_WRITE
+ * during the compaction process to ensure no writes are made.
+ *
+ * If the optional callback function 'status_cb' is non-NULL, it will
+ * be called with diagnostic and informational messages. The argument
+ * 'closure' is passed verbatim to any callback invoked.
+ */
+notmuch_status_t
+notmuch_database_compact (const char* path,
+                         const char* backup_path,
+                         notmuch_compact_status_cb_t status_cb,
+                         void *closure);
+
 /* Destroy the notmuch database, closing it if necessary and freeing
-* all associated resources. */
+ * all associated resources.
+ */
 void
 notmuch_database_destroy (notmuch_database_t *database);
 
@@ -250,7 +301,8 @@ notmuch_database_needs_upgrade (notmuch_database_t *database);
  * provide progress indication to the user. If non-NULL it will be
  * called periodically with 'progress' as a floating-point value in
  * the range of [0.0 .. 1.0] indicating the progress made so far in
- * the upgrade process.
+ * the upgrade process.  The argument 'closure' is passed verbatim to
+ * any callback invoked.
  */
 notmuch_status_t
 notmuch_database_upgrade (notmuch_database_t *database,
@@ -746,12 +798,16 @@ notmuch_thread_get_total_messages (notmuch_thread_t *thread);
  * This iterator will not necessarily iterate over all of the messages
  * in the thread. It will only iterate over the messages in the thread
  * which are not replies to other messages in the thread.
+ *
+ * The returned list will be destroyed when the thread is destroyed.
  */
 notmuch_messages_t *
 notmuch_thread_get_toplevel_messages (notmuch_thread_t *thread);
 
 /* Get a notmuch_thread_t iterator for all messages in 'thread' in
  * oldest-first order.
+ *
+ * The returned list will be destroyed when the thread is destroyed.
  */
 notmuch_messages_t *
 notmuch_thread_get_messages (notmuch_thread_t *thread);
@@ -767,7 +823,7 @@ notmuch_thread_get_messages (notmuch_thread_t *thread);
 int
 notmuch_thread_get_matched_messages (notmuch_thread_t *thread);
 
-/* Get the authors of 'thread'
+/* Get the authors of 'thread' as a UTF-8 string.
  *
  * The returned string is a comma-separated list of the names of the
  * authors of mail messages in the query results that belong to this
@@ -781,7 +837,7 @@ notmuch_thread_get_matched_messages (notmuch_thread_t *thread);
 const char *
 notmuch_thread_get_authors (notmuch_thread_t *thread);
 
-/* Get the subject of 'thread'
+/* Get the subject of 'thread' as a UTF-8 string.
  *
  * The subject is taken from the first message (according to the query
  * order---see notmuch_query_set_sort) in the query results that
@@ -1013,10 +1069,13 @@ notmuch_message_set_flag (notmuch_message_t *message,
 time_t
 notmuch_message_get_date  (notmuch_message_t *message);
 
-/* Get the value of the specified header from 'message'.
+/* Get the value of the specified header from 'message' as a UTF-8 string.
+ *
+ * Common headers are stored in the database when the message is
+ * indexed and will be returned from the database.  Other headers will
+ * be read from the actual message file.
  *
- * The value will be read from the actual message file, not from the
- * notmuch database. The header name is case insensitive.
+ * The header name is case insensitive.
  *
  * The returned string belongs to the message so should not be
  * modified or freed by the caller (nor should it be referenced after
index 69668a451ac7570481792ab5aa4cc81a132f8581..ec60e2e45c01226a9ed326b8af22f1add8b77c8b 100644 (file)
@@ -80,7 +80,7 @@ notmuch_query_create (notmuch_database_t *notmuch,
     if (_debug_query ())
        fprintf (stderr, "Query string is:\n%s\n", query_string);
 
-    query = talloc (NULL, notmuch_query_t);
+    query = talloc (notmuch, notmuch_query_t);
     if (unlikely (query == NULL))
        return NULL;
 
index 216aaa02012dc67fd1c781ddb938ef7f2c26715a..57910b76ba6a32194740aa309d6d2a43fc9a3537 100644 (file)
@@ -8,6 +8,7 @@ MAIN_PAGE := $(dir)/man1/notmuch.1
 
 MAN1 := \
        $(MAIN_PAGE) \
+       $(dir)/man1/notmuch-compact.1 \
        $(dir)/man1/notmuch-config.1 \
        $(dir)/man1/notmuch-count.1 \
        $(dir)/man1/notmuch-dump.1 \
diff --git a/man/man1/notmuch-compact.1 b/man/man1/notmuch-compact.1
new file mode 100644 (file)
index 0000000..ea6218f
--- /dev/null
@@ -0,0 +1,62 @@
+.TH NOTMUCH-COMPACT 1 2013-12-30 "Notmuch 0.17"
+.SH NAME
+notmuch-compact \- compact the notmuch database
+.SH SYNOPSIS
+
+.B notmuch compact
+.RI "[ --quiet ]"
+.RI "[ --backup=<" directory "> ]"
+
+.SH DESCRIPTION
+
+The
+.B compact
+command can be used to compact the notmuch database. This can both reduce
+the space required by the database and improve lookup performance.
+
+The compacted database is built in a temporary directory and is later
+moved into the place of the origin database. The original uncompacted
+database is discarded, unless the
+.BR "\-\-backup=" <directory>
+option is used.
+
+Note that the database write lock will be held during the compaction
+process (which may be quite long) to protect data integrity.
+
+Supported options for
+.B compact
+include
+
+.RS 4
+.TP 4
+.BR "\-\-backup=" <directory>
+
+Save the current database to the given directory before replacing it
+with the compacted database. The backup directory must not exist and
+it must reside on the same mounted filesystem as the current database.
+
+.RE
+
+.RS 4
+.TP 4
+.BR \-\-quiet
+
+Do not report database compaction progress to stdout.
+
+.RE
+
+.RE
+.SH ENVIRONMENT
+The following environment variables can be used to control the
+behavior of notmuch.
+.TP
+.B NOTMUCH_CONFIG
+Specifies the location of the notmuch configuration file. Notmuch will
+use ${HOME}/.notmuch\-config if this variable is not set.
+.SH SEE ALSO
+
+\fBnotmuch\fR(1), \fBnotmuch-count\fR(1), \fBnotmuch-dump\fR(1),
+\fBnotmuch-hooks\fR(5), \fBnotmuch-insert\fR(1), \fBnotmuch-new\fR(1),
+\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1),
+\fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1),
+\fBnotmuch-tag\fR(1)
index 72952a12041a2ad5de115d804de90fea0b7ed8bc..00a420f92934936d39dca023b76a14d04c037a9d 100644 (file)
@@ -1,4 +1,4 @@
-.TH NOTMUCH-CONFIG 1 2013-08-03 "Notmuch 0.16"
+.TH NOTMUCH-CONFIG 1 2013-12-30 "Notmuch 0.17"
 .SH NAME
 notmuch-config \- access notmuch configuration file
 .SH SYNOPSIS
index baed25fb6e1a086021fb71fc290f56c87df40746..562dde1563190ac4629c2003e532f1c99233fa8a 100644 (file)
@@ -1,4 +1,4 @@
-.TH NOTMUCH-COUNT 1 2013-08-03 "Notmuch 0.16"
+.TH NOTMUCH-COUNT 1 2013-12-30 "Notmuch 0.17"
 .SH NAME
 notmuch-count \- count messages matching the given search terms
 .SH SYNOPSIS
@@ -23,7 +23,7 @@ Supported options for
 include
 .RS 4
 .TP 4
-.B \-\-output=(messages|threads)
+.B \-\-output=(messages|threads|files)
 
 .RS 4
 .TP 4
@@ -37,6 +37,14 @@ Output the number of matching messages. This is the default.
 
 Output the number of matching threads.
 .RE
+.RS 4
+.TP 4
+.B files
+
+Output the number of files associated with matching messages. This may
+be bigger than the number of matching messages due to duplicates
+(i.e. multiple files having the same message-id).
+.RE
 .RE
 
 .RS 4
index d758fdd7c1eb86bd5a8662a9b6fa88261dcfe69d..0c52d1b762281920a6f2e8df42342f0c7365d4ae 100644 (file)
@@ -1,4 +1,4 @@
-.TH NOTMUCH-DUMP 1 2013-08-03 "Notmuch 0.16"
+.TH NOTMUCH-DUMP 1 2013-12-30 "Notmuch 0.17"
 .SH NAME
 notmuch-dump \- creates a plain-text dump of the tags of each message
 
index 06c4c5ea8851ac83a076b1dfcf401d356b19d919..8ce8413e656d0d41d011f49c23d65a9034d980b8 100644 (file)
@@ -1,4 +1,4 @@
-.TH NOTMUCH-INSERT 1 2013-08-03 "Notmuch 0.16"
+.TH NOTMUCH-INSERT 1 2013-12-30 "Notmuch 0.17"
 .SH NAME
 notmuch-insert \- add a message to the maildir and notmuch database
 .SH SYNOPSIS
index 7da668eb04c5e6784f53bf8a281f2112ba884f5c..5725b7d874a0719a6b561c8292786fea9c5bf0c4 100644 (file)
@@ -1,4 +1,4 @@
-.TH NOTMUCH-NEW 1 2013-08-03 "Notmuch 0.16"
+.TH NOTMUCH-NEW 1 2013-12-30 "Notmuch 0.17"
 .SH NAME
 notmuch-new \- incorporate new mail into the notmuch database
 .SH SYNOPSIS
index ac76b07f88f6725eb245216acd26a37cef945653..93f906737bf4529c7463203e3769679c03f511e1 100644 (file)
@@ -1,4 +1,4 @@
-.TH NOTMUCH-REPLY 1 2013-08-03 "Notmuch 0.16"
+.TH NOTMUCH-REPLY 1 2013-12-30 "Notmuch 0.17"
 .SH NAME
 notmuch-reply \- constructs a reply template for a set of messages
 
@@ -41,7 +41,7 @@ include
 .RS
 .TP 4
 .BR default
-Includes subject and quoted message body.
+Includes subject and quoted message body as an RFC 2822 message.
 .TP
 .BR json
 Produces JSON output containing headers for a reply message and the
index fa2fc674d85df03a9e16ede2a9e7163abc4485a8..4cb02e3e874c9c4e9fb083ddba1b8eebae71c229 100644 (file)
@@ -1,4 +1,4 @@
-.TH NOTMUCH-RESTORE 1 2013-08-03 "Notmuch 0.16"
+.TH NOTMUCH-RESTORE 1 2013-12-30 "Notmuch 0.17"
 .SH NAME
 notmuch-restore \- restores the tags from the given file (see notmuch dump)
 
index 2d6ecafc0a91d80234f13dc63feed86020ad0227..55a81e79fce4ea8a0712781c480967e2bf2f43d9 100644 (file)
@@ -1,4 +1,4 @@
-.TH NOTMUCH-SEARCH 1 2013-08-03 "Notmuch 0.16"
+.TH NOTMUCH-SEARCH 1 2013-12-30 "Notmuch 0.17"
 .SH NAME
 notmuch-search \- search for messages matching the given search terms
 .SH SYNOPSIS
@@ -80,6 +80,10 @@ Output the filenames of all messages matching the search terms, either
 one per line (\-\-format=text), separated by null characters
 (\-\-format=text0), as a JSON array (\-\-format=json), or as an
 S-Expression list (\-\-format=sexp).
+
+Note that each message may have multiple filenames associated with it.
+All of them are included in the output, unless limited with the
+\-\-duplicate=N option.
 .RE
 .RS 4
 .TP 4
@@ -158,6 +162,22 @@ but the "match count" is the number of matching non-excluded messages in the
 thread, rather than the number of matching messages.
 .RE
 
+.RS 4
+.TP 4
+.BR \-\-duplicate=N
+
+Effective with
+.BR --output=files ,
+output the Nth filename associated with each message matching the
+query (N is 1-based). If N is greater than the number of files
+associated with the message, don't print anything.
+
+Note that this option is orthogonal with the
+.BR folder:
+search prefix. The prefix matches messages based on filenames. This
+option filters filenames of the matching messages.
+.RE
+
 .SH EXIT STATUS
 
 This command supports the following special exit status codes
index 7bb67ffc73fc35c616e3a52a0f6566b0e793d4d1..7eefdec692d483f26661a2c3d642c8d5efdf250b 100644 (file)
@@ -1,4 +1,4 @@
-.TH NOTMUCH-SHOW 1 2013-08-03 "Notmuch 0.16"
+.TH NOTMUCH-SHOW 1 2013-12-30 "Notmuch 0.17"
 .SH NAME
 notmuch-show \- show messages matching the given search terms
 .SH SYNOPSIS
@@ -207,6 +207,20 @@ This is useful if the caller only needs the headers as body-less
 output is much faster and substantially smaller.
 .RE
 
+.RS 4
+.TP 4
+.B \-\-include-html
+
+Include "text/html" parts as part of the output (currently only supported with
+--format=json and --format=sexp).
+By default, unless
+.B --part=N
+is used to select a specific part or
+.B --include-html
+is used to include all "text/html" parts, no part with content type "text/html"
+is included in the output.
+.RE
+
 A common use of
 .B notmuch show
 is to display a single thread of email messages. For this, use a
index 079f5c0ad2d71130422afc7f78f84c0cb9bd93ae..710fae6a8e3a4ef9561f489d0390f8523427c18f 100644 (file)
@@ -1,4 +1,4 @@
-.TH NOTMUCH-TAG 1 2013-08-03 "Notmuch 0.16"
+.TH NOTMUCH-TAG 1 2013-12-30 "Notmuch 0.17"
 .SH NAME
 notmuch-tag \- add/remove tags for all messages matching the search terms
 
index f8477ac4d43fef86e0e600c156f303998655275a..605b5146b2b8ea1e3c63f8e92f0dbc1b1e6780f9 100644 (file)
@@ -16,7 +16,7 @@
 .\" along with this program.  If not, see http://www.gnu.org/licenses/ .
 .\"
 .\" Author: Carl Worth <cworth@cworth.org>
-.TH NOTMUCH 1 2013-08-03 "Notmuch 0.16"
+.TH NOTMUCH 1 2013-12-30 "Notmuch 0.17"
 .SH NAME
 notmuch \- thread-based email index, search, and tagging
 .SH SYNOPSIS
index 5ca9fd64388bc34b77870436adb2f2cf40d153b4..11c55ddd2e827f3e01662fe1494ac3679f2c4417 100644 (file)
@@ -1,4 +1,4 @@
-.TH NOTMUCH-HOOKS 5 2013-08-03 "Notmuch 0.16"
+.TH NOTMUCH-HOOKS 5 2013-12-30 "Notmuch 0.17"
 
 .SH NAME
 notmuch-hooks \- hooks for notmuch
index 314efa89ed00d9d8e5da01b2aafdbafc693d73bb..a768b630a4d121823243ba57a79d01c8463a42b8 100644 (file)
@@ -1,4 +1,4 @@
-.TH NOTMUCH-SEARCH-TERMS 7 2013-08-03 "Notmuch 0.16"
+.TH NOTMUCH-SEARCH-TERMS 7 2013-12-30 "Notmuch 0.17"
 
 .SH NAME
 notmuch-search-terms \- syntax for notmuch queries
@@ -102,9 +102,11 @@ thread ID values can be seen in the first column of output from
 The
 .B folder:
 prefix can be used to search for email message files that are
-contained within particular directories within the mail store. Only
-the directory components below the top-level mail database path are
-available to be searched.
+contained within particular directories within the mail store. If the
+same email message has multiple message files associated with it, it's
+sufficient for a match that at least one of the files is contained
+within a matching directory. Only the directory components below the
+top-level mail database path are available to be searched.
 
 The
 .B date:
index da332f36a6a86e67e971c168ba21624a34f94cd4..278b498a246adac024778e8d403f30e4db8155ad 100644 (file)
@@ -89,6 +89,7 @@ typedef struct notmuch_show_params {
     notmuch_bool_t raw;
     int part;
     notmuch_crypto_t crypto;
+    notmuch_bool_t include_html;
 } notmuch_show_params_t;
 
 /* There's no point in continuing when we've detected that we've done
@@ -137,10 +138,16 @@ chomp_newline (char *str)
  * this.  New (required) map fields can be added without increasing
  * this.
  */
-#define NOTMUCH_FORMAT_CUR 1
+#define NOTMUCH_FORMAT_CUR 2
 /* The minimum supported structured output format version.  Requests
  * for format versions below this will return an error. */
 #define NOTMUCH_FORMAT_MIN 1
+/* The minimum non-deprecated structured output format version.
+ * Requests for format versions below this will print a stern warning.
+ * Must be between NOTMUCH_FORMAT_MIN and NOTMUCH_FORMAT_CUR,
+ * inclusive.
+ */
+#define NOTMUCH_FORMAT_MIN_ACTIVE 1
 
 /* The output format version requested by the caller on the command
  * line.  If no format version is requested, this will be set to
@@ -203,6 +210,9 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[]);
 int
 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[]);
 
+int
+notmuch_compact_command (notmuch_config_t *config, int argc, char *argv[]);
+
 const char *
 notmuch_time_relative_date (const void *ctx, time_t then);
 
@@ -220,7 +230,8 @@ show_one_part (const char *filename, int part);
 
 void
 format_part_sprinter (const void *ctx, struct sprinter *sp, mime_node_t *node,
-                     notmuch_bool_t first, notmuch_bool_t output_body);
+                     notmuch_bool_t first, notmuch_bool_t output_body,
+                     notmuch_bool_t include_html);
 
 void
 format_headers_sprinter (struct sprinter *sp, GMimeMessage *message,
diff --git a/notmuch-compact.c b/notmuch-compact.c
new file mode 100644 (file)
index 0000000..8b820c0
--- /dev/null
@@ -0,0 +1,64 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2013 Ben Gamari
+ *
+ * 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: Ben Gamari <bgamari.foss@gmail.com>
+ */
+
+#include "notmuch-client.h"
+
+static void
+status_update_cb (const char *msg, unused (void *closure))
+{
+    printf ("%s\n", msg);
+}
+
+int
+notmuch_compact_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    const char *path = notmuch_config_get_database_path (config);
+    const char *backup_path = NULL;
+    notmuch_status_t ret;
+    notmuch_bool_t quiet;
+    int opt_index;
+
+    notmuch_opt_desc_t options[] = {
+       { NOTMUCH_OPT_STRING, &backup_path, "backup", 0, 0 },
+       { NOTMUCH_OPT_BOOLEAN,  &quiet, "quiet", 'q', 0 },
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+       return 1;
+
+    if (! quiet)
+       printf ("Compacting database...\n");
+    ret = notmuch_database_compact (path, backup_path,
+                                   quiet ? NULL : status_update_cb, NULL);
+    if (ret) {
+       fprintf (stderr, "Compaction failed: %s\n", notmuch_status_to_string (ret));
+       return 1;
+    }
+
+    if (! quiet) {
+       if (backup_path)
+           printf ("The old database has been moved to %s.\n", backup_path);
+
+       printf ("Done.\n");
+    }
+
+    return 0;
+}
index befe9b5b5c8a45a084d7a3fd2738b5344fa4a7bb..6845e3c3db5d3af6f5a9d25f39d4eb8060a1e41a 100644 (file)
@@ -704,7 +704,7 @@ _item_split (char *item, char **group, char **key)
 
     *group = item;
 
-    period = index (item, '.');
+    period = strchr (item, '.');
     if (period == NULL || *(period+1) == '\0') {
        fprintf (stderr,
                 "Invalid configuration name: %s\n"
index 8772cff80aa2b3f8079181c8ca7a06fede37488b..01e4e3012b8a07c799418c1c256107bb4e1cffa4 100644 (file)
@@ -24,6 +24,7 @@
 enum {
     OUTPUT_THREADS,
     OUTPUT_MESSAGES,
+    OUTPUT_FILES,
 };
 
 /* The following is to allow future options to be added more easily */
@@ -32,6 +33,38 @@ enum {
     EXCLUDE_FALSE,
 };
 
+static unsigned int
+count_files (notmuch_query_t *query)
+{
+    notmuch_messages_t *messages;
+    notmuch_message_t *message;
+    notmuch_filenames_t *filenames;
+    unsigned int count = 0;
+
+    messages = notmuch_query_search_messages (query);
+    if (messages == NULL)
+       return 0;
+
+    for (;
+        notmuch_messages_valid (messages);
+        notmuch_messages_move_to_next (messages)) {
+       message = notmuch_messages_get (messages);
+       filenames = notmuch_message_get_filenames (message);
+
+       for (;
+            notmuch_filenames_valid (filenames);
+            notmuch_filenames_move_to_next (filenames))
+           count++;
+
+       notmuch_filenames_destroy (filenames);
+       notmuch_message_destroy (message);
+    }
+
+    notmuch_messages_destroy (messages);
+
+    return count;
+}
+
 static int
 print_count (notmuch_database_t *notmuch, const char *query_str,
             const char **exclude_tags, size_t exclude_tags_length, int output)
@@ -55,6 +88,9 @@ print_count (notmuch_database_t *notmuch, const char *query_str,
     case OUTPUT_THREADS:
        printf ("%u\n", notmuch_query_count_threads (query));
        break;
+    case OUTPUT_FILES:
+       printf ("%u\n", count_files (query));
+       break;
     }
 
     notmuch_query_destroy (query);
@@ -102,6 +138,7 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
        { NOTMUCH_OPT_KEYWORD, &output, "output", 'o',
          (notmuch_keyword_t []){ { "threads", OUTPUT_THREADS },
                                  { "messages", OUTPUT_MESSAGES },
+                                 { "files", OUTPUT_FILES },
                                  { 0, 0 } } },
        { NOTMUCH_OPT_KEYWORD, &exclude, "exclude", 'x',
          (notmuch_keyword_t []){ { "true", EXCLUDE_TRUE },
index faa33f1fc505f13f06bfe0a0d69b711136ac8597..ba05cb41e10f2d124681acc7399a9428a2de4a5c 100644 (file)
@@ -323,6 +323,35 @@ add_files (notmuch_database_t *notmuch,
     }
     db_mtime = directory ? notmuch_directory_get_mtime (directory) : 0;
 
+    /* If the directory is unchanged from our last scan and has no
+     * sub-directories, then return without scanning it at all.  In
+     * some situations, skipping the scan can substantially reduce the
+     * cost of notmuch new, especially since the huge numbers of files
+     * in Maildirs make scans expensive, but all files live in leaf
+     * directories.
+     *
+     * To check for sub-directories, we borrow a trick from find,
+     * kpathsea, and many other UNIX tools: since a directory's link
+     * count is the number of sub-directories (specifically, their
+     * '..' entries) plus 2 (the link from the parent and the link for
+     * '.').  This check is safe even on weird file systems, since
+     * file systems that can't compute this will return 0 or 1.  This
+     * is safe even on *really* weird file systems like HFS+ that
+     * mistakenly return the total number of directory entries, since
+     * that only inflates the count beyond 2.
+     */
+    if (directory && fs_mtime == db_mtime && st.st_nlink == 2) {
+       /* There's one catch: pass 1 below considers symlinks to
+        * directories to be directories, but these don't increase the
+        * file system link count.  So, only bail early if the
+        * database agrees that there are no sub-directories. */
+       db_subdirs = notmuch_directory_get_child_directories (directory);
+       if (!notmuch_filenames_valid (db_subdirs))
+           goto DONE;
+       notmuch_filenames_destroy (db_subdirs);
+       db_subdirs = NULL;
+    }
+
     /* If the database knows about this directory, then we sort based
      * on strcmp to match the database sorting. Otherwise, we can do
      * inode-based sorting for faster filesystem operation. */
index e151f78a79a406b695da6cf7feeca7917facbc2a..9d6f843652e080234a0c07a7df7012526c1a138f 100644 (file)
  */
 
 #include "notmuch-client.h"
-#include "gmime-filter-headers.h"
 #include "sprinter.h"
 
 static void
 show_reply_headers (GMimeMessage *message)
 {
-    GMimeStream *stream_stdout = NULL, *stream_filter = NULL;
+    GMimeStream *stream_stdout = NULL;
 
     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 (stream_filter) {
-               g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
-                                        g_mime_filter_headers_new());
-               g_mime_object_write_to_stream(GMIME_OBJECT(message), stream_filter);
-               g_object_unref(stream_filter);
-       }
+       /* Output RFC 2822 formatted (and RFC 2047 encoded) headers. */
+       g_mime_object_write_to_stream (GMIME_OBJECT(message), stream_stdout);
        g_object_unref(stream_stdout);
     }
 }
@@ -533,9 +527,12 @@ create_reply_message(void *ctx,
                              "In-Reply-To", in_reply_to);
 
     orig_references = notmuch_message_get_header (message, "references");
+    if (!orig_references)
+       /* Treat errors like missing References headers. */
+       orig_references = "";
     references = talloc_asprintf (ctx, "%s%s%s",
-                                 orig_references ? orig_references : "",
-                                 orig_references ? " " : "",
+                                 *orig_references ? orig_references : "",
+                                 *orig_references ? " " : "",
                                  in_reply_to);
     g_mime_object_set_header (GMIME_OBJECT (reply),
                              "References", references);
@@ -624,7 +621,7 @@ notmuch_reply_format_sprinter(void *ctx,
 
     /* Start the original */
     sp->map_key (sp, "original");
-    format_part_sprinter (ctx, sp, node, TRUE, TRUE);
+    format_part_sprinter (ctx, sp, node, TRUE, TRUE, FALSE);
 
     /* End */
     sp->end (sp);
index a96f07d53eab5fa073a539286c96f57213ebadbe..7c973b3d6666ac46eca508b1364b4a9b4eb04304 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "notmuch-client.h"
 #include "sprinter.h"
+#include "string-util.h"
 
 typedef enum {
     OUTPUT_SUMMARY,
@@ -46,6 +47,45 @@ sanitize_string (const void *ctx, const char *str)
     return out;
 }
 
+/* Return two stable query strings that identify exactly the matched
+ * and unmatched messages currently in thread.  If there are no
+ * matched or unmatched messages, the returned buffers will be
+ * NULL. */
+static int
+get_thread_query (notmuch_thread_t *thread,
+                 char **matched_out, char **unmatched_out)
+{
+    notmuch_messages_t *messages;
+    char *escaped = NULL;
+    size_t escaped_len = 0;
+
+    *matched_out = *unmatched_out = NULL;
+
+    for (messages = notmuch_thread_get_messages (thread);
+        notmuch_messages_valid (messages);
+        notmuch_messages_move_to_next (messages))
+    {
+       notmuch_message_t *message = notmuch_messages_get (messages);
+       const char *mid = notmuch_message_get_message_id (message);
+       /* Determine which query buffer to extend */
+       char **buf = notmuch_message_get_flag (
+           message, NOTMUCH_MESSAGE_FLAG_MATCH) ? matched_out : unmatched_out;
+       /* Add this message's id: query.  Since "id" is an exclusive
+        * prefix, it is implicitly 'or'd together, so we only need to
+        * join queries with a space. */
+       if (make_boolean_term (thread, "id", mid, &escaped, &escaped_len) < 0)
+           return -1;
+       if (*buf)
+           *buf = talloc_asprintf_append_buffer (*buf, " %s", escaped);
+       else
+           *buf = talloc_strdup (thread, escaped);
+       if (!*buf)
+           return -1;
+    }
+    talloc_free (escaped);
+    return 0;
+}
+
 static int
 do_search_threads (sprinter_t *format,
                   notmuch_query_t *query,
@@ -131,6 +171,25 @@ do_search_threads (sprinter_t *format,
                format->string (format, authors);
                format->map_key (format, "subject");
                format->string (format, subject);
+               if (notmuch_format_version >= 2) {
+                   char *matched_query, *unmatched_query;
+                   if (get_thread_query (thread, &matched_query,
+                                         &unmatched_query) < 0) {
+                       fprintf (stderr, "Out of memory\n");
+                       return 1;
+                   }
+                   format->map_key (format, "query");
+                   format->begin_list (format);
+                   if (matched_query)
+                       format->string (format, matched_query);
+                   else
+                       format->null (format);
+                   if (unmatched_query)
+                       format->string (format, unmatched_query);
+                   else
+                       format->null (format);
+                   format->end (format);
+               }
            }
 
            talloc_free (ctx_quote);
@@ -177,7 +236,8 @@ do_search_messages (sprinter_t *format,
                    notmuch_query_t *query,
                    output_t output,
                    int offset,
-                   int limit)
+                   int limit,
+                   int dupe)
 {
     notmuch_message_t *message;
     notmuch_messages_t *messages;
@@ -206,14 +266,17 @@ do_search_messages (sprinter_t *format,
        message = notmuch_messages_get (messages);
 
        if (output == OUTPUT_FILES) {
+           int j;
            filenames = notmuch_message_get_filenames (message);
 
-           for (;
+           for (j = 1;
                 notmuch_filenames_valid (filenames);
-                notmuch_filenames_move_to_next (filenames))
+                notmuch_filenames_move_to_next (filenames), j++)
            {
-               format->string (format, notmuch_filenames_get (filenames));
-               format->separator (format);
+               if (dupe < 0 || dupe == j) {
+                   format->string (format, notmuch_filenames_get (filenames));
+                   format->separator (format);
+               }
            }
            
            notmuch_filenames_destroy( filenames );
@@ -296,6 +359,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
     int offset = 0;
     int limit = -1; /* unlimited */
     notmuch_exclude_t exclude = NOTMUCH_EXCLUDE_TRUE;
+    int dupe = -1;
     unsigned int i;
 
     enum {
@@ -332,6 +396,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
                                   { 0, 0 } } },
        { NOTMUCH_OPT_INT, &offset, "offset", 'O', 0 },
        { NOTMUCH_OPT_INT, &limit, "limit", 'L', 0  },
+       { NOTMUCH_OPT_INT, &dupe, "duplicate", 'D', 0  },
        { 0, 0, 0, 0, 0 }
     };
 
@@ -414,7 +479,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
        break;
     case OUTPUT_MESSAGES:
     case OUTPUT_FILES:
-       ret = do_search_messages (format, query, output, offset, limit);
+       ret = do_search_messages (format, query, output, offset, limit, dupe);
        break;
     case OUTPUT_TAGS:
        ret = do_search_tags (notmuch, format, query);
index 62178f7217316a4cd2d1298e1fe2319967786c7f..c07f8871aefe4eab1313ea795d7476cb854d7e04 100644 (file)
@@ -630,7 +630,8 @@ format_omitted_part_meta_sprinter (sprinter_t *sp, GMimeObject *meta, GMimePart
 
 void
 format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
-                     notmuch_bool_t first, notmuch_bool_t output_body)
+                     notmuch_bool_t first, notmuch_bool_t output_body,
+                     notmuch_bool_t include_html)
 {
     /* Any changes to the JSON or S-Expression format should be
      * reflected in the file devel/schemata. */
@@ -645,7 +646,7 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
        if (output_body) {
            sp->map_key (sp, "body");
            sp->begin_list (sp);
-           format_part_sprinter (ctx, sp, mime_node_child (node, 0), first, TRUE);
+           format_part_sprinter (ctx, sp, mime_node_child (node, 0), first, TRUE, include_html);
            sp->end (sp);
        }
        sp->end (sp);
@@ -700,14 +701,15 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
        /* For non-HTML text parts, we include the content in the
         * JSON. Since JSON must be Unicode, we handle charset
         * decoding here and do not report a charset to the caller.
-        * For text/html parts, we do not include the content. If a
-        * caller is interested in text/html parts, it should retrieve
-        * them separately and they will not be decoded. Since this
-        * makes charset decoding the responsibility on the caller, we
+        * For text/html parts, we do not include the content unless
+        * the --include-html option has been passed. If a html part
+        * is not included, it can be requested directly. This makes
+        * charset decoding the responsibility on the caller so we
         * report the charset for text/html parts.
         */
        if (g_mime_content_type_is_type (content_type, "text", "*") &&
-           ! g_mime_content_type_is_type (content_type, "text", "html"))
+           (include_html ||
+            ! g_mime_content_type_is_type (content_type, "text", "html")))
        {
            GMimeStream *stream_memory = g_mime_stream_mem_new ();
            GByteArray *part_content;
@@ -737,7 +739,7 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
     }
 
     for (i = 0; i < node->nchildren; i++)
-       format_part_sprinter (ctx, sp, mime_node_child (node, i), i == 0, TRUE);
+       format_part_sprinter (ctx, sp, mime_node_child (node, i), i == 0, TRUE, include_html);
 
     /* Close content structures */
     for (i = 0; i < nclose; i++)
@@ -751,7 +753,7 @@ format_part_sprinter_entry (const void *ctx, sprinter_t *sp,
                            mime_node_t *node, unused (int indent),
                            const notmuch_show_params_t *params)
 {
-    format_part_sprinter (ctx, sp, node, TRUE, params->output_body);
+    format_part_sprinter (ctx, sp, node, TRUE, params->output_body, params->include_html);
 
     return NOTMUCH_STATUS_SUCCESS;
 }
@@ -1077,7 +1079,8 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
        .crypto = {
            .verify = FALSE,
            .decrypt = FALSE
-       }
+       },
+       .include_html = FALSE
     };
     int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED;
     int exclude = EXCLUDE_TRUE;
@@ -1105,6 +1108,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
        { NOTMUCH_OPT_BOOLEAN, &params.crypto.decrypt, "decrypt", 'd', 0 },
        { NOTMUCH_OPT_BOOLEAN, &params.crypto.verify, "verify", 'v', 0 },
        { NOTMUCH_OPT_BOOLEAN, &params.output_body, "body", 'b', 0 },
+       { NOTMUCH_OPT_BOOLEAN, &params.include_html, "include-html", 0, 0 },
        { 0, 0, 0, 0, 0 }
     };
 
@@ -1176,6 +1180,11 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
        }
     }
 
+    if (params.include_html &&
+        (format_sel != NOTMUCH_FORMAT_JSON && format_sel != NOTMUCH_FORMAT_SEXP)) {
+       fprintf (stderr, "Warning: --include-html only implemented for format=json and format=sexp\n");
+    }
+
     if (entire_thread == ENTIRE_THREAD_TRUE)
        params.entire_thread = TRUE;
     else
index 78d29a8194179c05a45ca0934bf955c3bd1f3662..54f46c6828cc13a72d6dec17cc264e50b4e9c231 100644 (file)
--- a/notmuch.c
+++ b/notmuch.c
@@ -60,6 +60,8 @@ static command_t commands[] = {
       "Create a plain-text dump of the tags for each message." },
     { "restore", notmuch_restore_command, FALSE,
       "Restore the tags from the given dump file (see 'dump')." },
+    { "compact", notmuch_compact_command, FALSE,
+      "Compact the notmuch database." },
     { "config", notmuch_config_command, FALSE,
       "Get or set settings in the notmuch configuration file." },
     { "help", notmuch_help_command, TRUE, /* create but don't save config */
@@ -125,7 +127,7 @@ by the notmuch CLI (it requires at least version %d).  You may need to\n\
 upgrade your notmuch front-end.\n",
                 notmuch_format_version, NOTMUCH_FORMAT_MIN);
        exit (NOTMUCH_EXIT_FORMAT_TOO_OLD);
-    } else if (notmuch_format_version != NOTMUCH_FORMAT_CUR) {
+    } else if (notmuch_format_version < NOTMUCH_FORMAT_MIN_ACTIVE) {
        /* Warn about old version requests so compatibility issues are
         * less likely when we drop support for a deprecated format
         * versions. */
@@ -264,7 +266,7 @@ main (int argc, char *argv[])
 
     local = talloc_new (NULL);
 
-    g_mime_init (0);
+    g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
 #if !GLIB_CHECK_VERSION(2, 35, 1)
     g_type_init ();
 #endif
index 584067d33d6f1cb61039f1859c5e511b2a57f95e..ccad422b3c8db96462257d3df30660c63f0ee2bb 100644 (file)
@@ -32,6 +32,7 @@
 #include <sys/time.h>
 #include <sys/types.h>
 
+#include "compat.h"
 #include "parse-time-string.h"
 
 /*
index e47219ad275f93c613de5b6d9e5d35dab36c3e57..d97e56d91a121cf2ad650bb16d61fa44926c819a 100644 (file)
@@ -39,4 +39,5 @@ $(TXZFILE):
 download-corpus:
        wget -O ${TXZFILE} ${DEFAULT_URL}
 
-CLEAN := $(CLEAN) $(dir)/tmp.* $(dir)/log.* $(dir)/corpus $(dir)/notmuch.cache.*
+CLEAN := $(CLEAN) $(dir)/tmp.* $(dir)/log.*
+DISTCLEAN := $(dir)/corpus $(dir)/notmuch.cache.*
index 2ec659560626e1a16e224d85e1b58cb318033fe9..8870ca374848add291a0ad969d038baab5448794 100644 (file)
@@ -27,7 +27,7 @@ $(dir)/random-corpus: $(random_corpus_deps)
 $(dir)/smtp-dummy: $(smtp_dummy_modules)
        $(call quiet,CC) $^ -o $@
 
-$(dir)/symbol-test: $(dir)/symbol-test.o
+$(dir)/symbol-test: $(dir)/symbol-test.o lib/$(LINKER_NAME)
        $(call quiet,CXX) $^ -o $@ -Llib -lnotmuch $(XAPIAN_LDFLAGS)
 
 $(dir)/parse-time: $(dir)/parse-time.o parse-time-string/parse-time-string.o
index 732d6ca7f53ca8bf332f1179199ec132fe6abe04..b016edb9117108523642b17862b20846f4fbc89c 100755 (executable)
@@ -1,5 +1,7 @@
 #!/usr/bin/env bash
 
+set -eu
+
 fixed=0
 success=0
 failed=0
@@ -79,3 +81,10 @@ if [ "$skipped" != "0" ]; then
     tests=$(pluralize "test" $skipped)
     echo "$skipped $tests skipped."
 fi
+
+if [ $success -gt 0 -a $fixed -eq 0 -a $failed -eq 0 -a $skipped -eq 0 ]
+then
+    exit 0
+else
+    exit 1
+fi
diff --git a/test/compact b/test/compact
new file mode 100755 (executable)
index 0000000..ac174ce
--- /dev/null
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+test_description='"notmuch compact"'
+. ./test-lib.sh
+
+add_message '[subject]=One'
+add_message '[subject]=Two'
+add_message '[subject]=Three'
+
+notmuch tag +tag1 \*
+notmuch tag +tag2 subject:Two
+notmuch tag -tag1 +tag3 subject:Three
+
+test_expect_success "Running compact" "notmuch compact --backup=${TEST_DIRECTORY}/xapian.old"
+
+test_begin_subtest "Compact preserves database"
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One (inbox tag1 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag1 tag2 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Three (inbox tag3 unread)"
+
+test_expect_success 'Restoring Backup' \
+    'rm -Rf ${MAIL_DIR}/.notmuch/xapian &&
+     mv ${TEST_DIRECTORY}/xapian.old ${MAIL_DIR}/.notmuch/xapian'
+
+test_begin_subtest "Checking restored backup"
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One (inbox tag1 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag1 tag2 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Three (inbox tag3 unread)"
+
+test_done
index 05713fdc7eb250a65b618fbd152846f93d08a134..da86c8cc70cf4a9f32669fd1b3472353f528d295 100755 (executable)
@@ -28,6 +28,16 @@ test_expect_equal \
     "$((`notmuch search '*' | wc -l`))" \
     "`notmuch count --output=threads '*'`"
 
+test_begin_subtest "files count"
+test_expect_equal \
+    "$((`notmuch search --output=files '*' | wc -l`))" \
+    "`notmuch count --output=files '*'`"
+
+test_begin_subtest "files count for a duplicate message-id"
+test_expect_equal \
+    "2" \
+    "`notmuch count --output=files id:20091117232137.GA7669@griffis1.net`"
+
 test_begin_subtest "count with no matching messages"
 test_expect_equal \
     "0" \
index aa96ec228ca082cb8f7a125064883a2f76b7ae58..477b397e419e644dbbf48d92b0a7d13b09e373e5 100755 (executable)
@@ -19,6 +19,7 @@ add_gnupg_home ()
     elif (gpg --debug-quick-random --version >/dev/null 2>&1) ; then
        echo debug-quick-random >> "$GNUPGHOME"/gpg.conf
     fi
+    echo no-emit-version >> "$GNUPGHOME"/gpg.conf
 }
 
 ##################################################
@@ -27,12 +28,8 @@ add_gnupg_home
 # get key fingerprint
 FINGERPRINT=$(gpg --no-tty --list-secret-keys --with-colons --fingerprint | grep '^fpr:' | cut -d: -f10)
 
-# for some reason this is needed for emacs_deliver_message to work,
-# although I can't figure out why
-add_email_corpus
-
 test_expect_success 'emacs delivery of signed message' \
-'emacs_deliver_message \
+'emacs_fcc_message \
     "test signed message 001" \
     "This is a test signed message." \
     "(mml-secure-message-sign)"'
@@ -62,7 +59,7 @@ expected='[[[{"id": "XXXXX",
  "content": "This is a test signed message.\n"},
  {"id": 3,
  "content-type": "application/pgp-signature",
- "content-length": 315}]}]},
+ "content-length": 280}]}]},
  []]]]'
 test_expect_equal_json \
     "$output" \
@@ -97,7 +94,7 @@ expected='[[[{"id": "XXXXX",
  "content": "This is a test signed message.\n"},
  {"id": 3,
  "content-type": "application/pgp-signature",
- "content-length": 315}]}]},
+ "content-length": 280}]}]},
  []]]]'
 test_expect_equal_json \
     "$output" \
@@ -130,7 +127,7 @@ expected='[[[{"id": "XXXXX",
  "content": "This is a test signed message.\n"},
  {"id": 3,
  "content-type": "application/pgp-signature",
- "content-length": 315}]}]},
+ "content-length": 280}]}]},
  []]]]'
 test_expect_equal_json \
     "$output" \
@@ -142,7 +139,7 @@ cat <<EOF >TESTATTACHMENT
 This is a test file.
 EOF
 test_expect_success 'emacs delivery of encrypted message with attachment' \
-'emacs_deliver_message \
+'emacs_fcc_message \
     "test encrypted message 001" \
     "This is a test encrypted message.\n" \
     "(mml-attach-file \"TESTATTACHMENT\") (mml-secure-message-encrypt)"'
@@ -269,7 +266,7 @@ test_expect_equal_json \
 mv "${GNUPGHOME}"{.bak,}
 
 test_expect_success 'emacs delivery of encrypted + signed message' \
-'emacs_deliver_message \
+'emacs_fcc_message \
     "test encrypted message 002" \
     "This is another test encrypted message.\n" \
     "(mml-secure-message-sign-encrypt)"'
@@ -354,7 +351,7 @@ expected='[[[{"id": "XXXXX",
  "content": "This is a test signed message.\n"},
  {"id": 3,
  "content-type": "application/pgp-signature",
- "content-length": 315}]}]},
+ "content-length": 280}]}]},
  []]]]'
 test_expect_equal_json \
     "$output" \
index 5bc3efcc6a87dc65b19031d489a804cc52b5fa2e..863219d9f9e973395e66a8507eeac6b5e044a9c9 100755 (executable)
@@ -86,15 +86,16 @@ add_message "[subject]=\"message-with-invalid-from\"" \
            "[from]=\"\\\"Invalid \\\" From\\\" <test_suite@notmuchmail.org>\""
 thread=$(notmuch search --output=threads subject:message-with-invalid-from)
 test_emacs "(notmuch-show \"$thread\")
-           (test-output)"
+           (test-output \"OUTPUT.raw\")"
 cat <<EOF >EXPECTED
 "Invalid " (2001-01-05) (inbox)
 Subject: message-with-invalid-from
 To: Notmuch Test Suite <test_suite@notmuchmail.org>
-Date: Fri, 05 Jan 2001 15:43:57 +0000
+Date: GENERATED_DATE
 
 This is just a test message (#1)
 EOF
+notmuch_date_sanitize < OUTPUT.raw > OUTPUT
 test_expect_equal_file OUTPUT EXPECTED
 
 test_begin_subtest "Navigation of notmuch-search to thread view"
@@ -122,6 +123,14 @@ test_emacs "(notmuch-search \"$os_x_darwin_thread\")
 output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)"
 
+test_begin_subtest "Add tag (large query)"
+# We use a long query to force us into batch mode and use a funny tag
+# that requires escaping for batch tagging.
+test_emacs "(notmuch-tag (concat \"$os_x_darwin_thread\" \" or \" (make-string notmuch-tag-argument-limit ?x)) (list \"+tag-from-%-large-query\"))"
+output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag-from-%-large-query unread)"
+notmuch tag -tag-from-%-large-query $os_x_darwin_thread
+
 test_begin_subtest "notmuch-show: add single tag to single message"
 test_emacs "(notmuch-show \"$os_x_darwin_thread\")
            (execute-kbd-macro \"+tag-from-show-view\")"
@@ -597,11 +606,11 @@ Q: Why is top-posting such a bad thing?
 A: Top-posting.
 Q: What is the most annoying thing in e-mail?"'
 test_emacs "(notmuch-show \"top-posting\")
-           (test-visible-output)"
+           (test-visible-output \"OUTPUT.raw\")"
 echo "Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox)
 Subject: The problem with top-posting
 To: Notmuch Test Suite <test_suite@notmuchmail.org>
-Date: Fri, 05 Jan 2001 15:43:57 +0000
+Date: GENERATED_DATE
 
 A: Because it messes up the order in which people normally read text.
 Q: Why is top-posting such a bad thing?
@@ -610,13 +619,14 @@ Q: What is the most annoying thing in e-mail?
 Top Poster <top@poster.com> (2001-01-05) (inbox unread)
 Subject: Re: The problem with top-posting
 To: Notmuch Test Suite <test_suite@notmuchmail.org>
-Date: Fri, 05 Jan 2001 15:43:57 +0000
+Date: GENERATED_DATE
 
 Thanks for the advice! I will be sure to put it to good use.
 
 -Top Poster
 
 [ 9-line hidden original message. Click/Enter to show. ]" > EXPECTED
+notmuch_date_sanitize < OUTPUT.raw > OUTPUT
 test_expect_equal_file OUTPUT EXPECTED
 
 test_begin_subtest "Hiding message in notmuch-show view"
@@ -761,7 +771,7 @@ test_expect_equal_file OUTPUT EXPECTED
 
 test_begin_subtest "Do not call notmuch for non-inlinable application/mpeg parts"
 id='message-with-application/mpeg-attachment@notmuchmail.org'
-emacs_deliver_message \
+emacs_fcc_message \
     'Message with application/mpeg attachment' \
     '' \
     "(message-goto-eoh)
@@ -776,7 +786,7 @@ test_expect_equal $(notmuch_counter_value) 1
 
 test_begin_subtest "Do not call notmuch for non-inlinable audio/mpeg parts"
 id='message-with-audio/mpeg-attachment@notmuchmail.org'
-emacs_deliver_message \
+emacs_fcc_message \
     'Message with audio/mpeg attachment' \
     '' \
     "(message-goto-eoh)
@@ -864,6 +874,8 @@ EOF
 chmod a+x notmuch_fail
 test_emacs "(let ((notmuch-command \"$PWD/notmuch_fail\"))
               (with-current-buffer \"*Messages*\" (erase-buffer))
+              (with-current-buffer (get-buffer-create \"*Notmuch errors*\")
+                 (erase-buffer))
               (notmuch-search \"tag:inbox\")
               (notmuch-test-wait)
               (with-current-buffer \"*Messages*\"
@@ -871,15 +883,16 @@ test_emacs "(let ((notmuch-command \"$PWD/notmuch_fail\"))
               (with-current-buffer \"*Notmuch errors*\"
                  (test-output \"ERROR\"))
               (test-output))"
-sed -i -e 's/^\[.*\]$/[XXX]/' ERROR
-test_expect_equal "$(cat OUTPUT; echo ---; cat MESSAGES; echo ---; cat ERROR)" "\
+
+test_expect_equal "$(notmuch_emacs_error_sanitize notmuch_fail OUTPUT MESSAGES ERROR)" "\
+=== OUTPUT ===
 End of search results.
----
-$PWD/notmuch_fail exited with status 1 (see *Notmuch errors* for more details)
----
+=== MESSAGES ===
+YYY/notmuch_fail exited with status 1 (see *Notmuch errors* for more details)
+=== ERROR ===
 [XXX]
-$PWD/notmuch_fail exited with status 1
-command: $PWD/notmuch_fail search --format\=sexp --format-version\=1 --sort\=newest-first tag\:inbox
+YYY/notmuch_fail exited with status 1
+command: YYY/notmuch_fail search --format\=sexp --format-version\=2 --sort\=newest-first tag\:inbox
 exit status: 1"
 
 test_begin_subtest "Search handles subprocess warnings"
@@ -893,7 +906,8 @@ EOF
 chmod a+x notmuch_fail
 test_emacs "(let ((notmuch-command \"$PWD/notmuch_fail\"))
               (with-current-buffer \"*Messages*\" (erase-buffer))
-              (with-current-buffer \"*Notmuch errors*\" (erase-buffer))
+              (with-current-buffer (get-buffer-create \"*Notmuch errors*\")
+                 (erase-buffer))
               (notmuch-search \"tag:inbox\")
               (notmuch-test-wait)
               (with-current-buffer \"*Messages*\"
@@ -911,4 +925,28 @@ This is a warning (see *Notmuch errors* for more details)
 This is a warning
 This is another warning"
 
+test_begin_subtest "Search thread tag operations are race-free"
+add_message '[subject]="Search race test"'
+gen_msg_id_1=$gen_msg_id
+generate_message '[in-reply-to]="<'$gen_msg_id_1'>"' \
+           '[references]="<'$gen_msg_id_1'>"' \
+           '[subject]="Search race test two"'
+test_emacs '(notmuch-search "subject:\"search race test\"")
+           (notmuch-test-wait)
+           (notmuch-poll)
+           (execute-kbd-macro "+search-thread-race-tag")'
+output=$(notmuch search --output=messages 'tag:search-thread-race-tag')
+test_expect_equal "$output" "id:$gen_msg_id_1"
+
+test_begin_subtest "Search global tag operations are race-free"
+generate_message '[in-reply-to]="<'$gen_msg_id_1'>"' \
+           '[references]="<'$gen_msg_id_1'>"' \
+           '[subject]="Re: Search race test"'
+test_emacs '(notmuch-search "subject:\"search race test\" -subject:two")
+           (notmuch-test-wait)
+           (notmuch-poll)
+           (execute-kbd-macro "*+search-global-race-tag")'
+output=$(notmuch search --output=messages 'tag:search-global-race-tag')
+test_expect_equal "$output" "id:$gen_msg_id_1"
+
 test_done
index ae70053a6fb7aad80354268b5f5583e8ed846cee..2a3a5356303a89ccfad1efe26f04bb2bf3dbbfe1 100755 (executable)
@@ -19,13 +19,14 @@ cat <<EOF >EXPECTED
 Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox)
 Subject: Hiding Original Message region at beginning of a message
 To: Notmuch Test Suite <test_suite@notmuchmail.org>
-Date: Fri, 05 Jan 2001 15:43:57 +0000
+Date: GENERATED_DATE
 
 [ 2-line hidden original message. Click/Enter to show. ]
 EOF
 
 test_emacs "(notmuch-show \"id:$message_id\")
-           (test-visible-output)"
+           (test-visible-output \"OUTPUT.raw\")"
+notmuch_date_sanitize < OUTPUT.raw > OUTPUT
 test_expect_equal_file OUTPUT EXPECTED
 
 test_begin_subtest "Bare subject #1"
@@ -91,8 +92,7 @@ test_begin_subtest "notmuch-show: elide non-matching messages (w/ prefix arg to
 test_emacs '(let ((notmuch-show-only-matching-messages nil))
        (notmuch-search "from:lars@seas.harvard.edu and subject:\"Maildir storage\"")
        (notmuch-test-wait)
-       (let ((current-prefix-arg t))
-         (notmuch-search-show-thread))
+       (notmuch-search-show-thread t)
        (notmuch-test-wait)
        (test-visible-output))'
 test_expect_equal_file OUTPUT $EXPECTED/notmuch-show-elide-non-matching-messages-on
@@ -131,12 +131,12 @@ mid:abc%20def
 mid:abc. mid:abc, mid:abc;"'
 test_emacs '(notmuch-show "id:'$gen_msg_id'")
        (notmuch-test-mark-links)
-       (test-visible-output)'
+       (test-visible-output "OUTPUT.raw")'
 cat <<EOF >EXPECTED
 Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox)
 Subject: id buttonization
 To: Notmuch Test Suite <test_suite@notmuchmail.org>
-Date: Fri, 05 Jan 2001 15:43:57 +0000
+Date: GENERATED_DATE
 
 <<id:abc>>
 <<id:abc.def>>. <<id:abc,def>>, <<id:abc;def>>; <<id:abc:def>>:
@@ -160,6 +160,7 @@ cid:xxx
 <<mid:abc%20def>>
 <<mid:abc>>. <<mid:abc>>, <<mid:abc>>;
 EOF
+notmuch_date_sanitize < OUTPUT.raw > OUTPUT
 test_expect_equal_file OUTPUT EXPECTED
 
 
@@ -182,14 +183,14 @@ test_emacs "(let ((notmuch-command \"$PWD/notmuch_fail\"))
               (with-current-buffer \"*Notmuch errors*\"
                  (test-output \"ERROR\"))
               (test-output))"
-sed -i -e 's/^\[.*\]$/[XXX]/' ERROR
-test_expect_equal "$(cat OUTPUT; echo ---; cat MESSAGES; echo ---; cat ERROR)" "\
----
+test_expect_equal "$(notmuch_emacs_error_sanitize notmuch_fail OUTPUT MESSAGES ERROR)" "\
+=== OUTPUT ===
+=== MESSAGES ===
 This is an error (see *Notmuch errors* for more details)
----
+=== ERROR ===
 [XXX]
 This is an error
-command: $PWD/notmuch_fail show --format\\=sexp --format-version\\=1 --exclude\\=false \\' \\* \\'
+command: YYY/notmuch_fail show --format\\=sexp --format-version\\=1 --exclude\\=false \\' \\* \\'
 exit status: 1
 stderr:
 This is an error
diff --git a/test/emacs-tree b/test/emacs-tree
new file mode 100755 (executable)
index 0000000..4bdfddd
--- /dev/null
@@ -0,0 +1,197 @@
+#!/usr/bin/env bash
+
+test_description="emacs tree view interface"
+. test-lib.sh
+
+EXPECTED=$TEST_DIRECTORY/tree.expected-output
+
+add_email_corpus
+
+test_begin_subtest "Basic notmuch-tree view in emacs"
+test_emacs '(add-to-list (quote load-path) "'$PICK_DIR'")
+           (notmuch-tree "tag:inbox")
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file OUTPUT $EXPECTED/notmuch-tree-tag-inbox
+
+test_begin_subtest "Refreshed notmuch-tree view in emacs"
+test_emacs '(add-to-list (quote load-path) "'$PICK_DIR'")
+           (notmuch-tree "tag:inbox")
+           (notmuch-test-wait)
+           (notmuch-tree-refresh-view)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file OUTPUT $EXPECTED/notmuch-tree-tag-inbox
+
+# In the following tag tests we make sure the display is updated
+# correctly and, in a separate test, that the database is updated
+# correctly.
+
+test_begin_subtest "Tag message in notmuch tree view (display)"
+test_emacs '(add-to-list (quote load-path) "'$PICK_DIR'")
+           (notmuch-tree "tag:inbox")
+           (notmuch-test-wait)
+           (forward-line)
+           (notmuch-tree-tag (list "+test_tag"))
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file OUTPUT $EXPECTED/notmuch-tree-tag-inbox-tagged
+
+test_begin_subtest "Tag message in notmuch tree view (database)"
+output=$(notmuch search --output=messages 'tag:test_tag')
+test_expect_equal "$output" "id:877h1wv7mg.fsf@inf-8657.int-evry.fr"
+
+test_begin_subtest "Untag message in notmuch tree view"
+test_emacs '(add-to-list (quote load-path) "'$PICK_DIR'")
+           (notmuch-tree "tag:inbox")
+           (notmuch-test-wait)
+           (forward-line)
+           (notmuch-tree-tag (list "-test_tag"))
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file OUTPUT $EXPECTED/notmuch-tree-tag-inbox
+
+test_begin_subtest "Untag message in notmuch tree view (database)"
+output=$(notmuch search --output=messages 'tag:test_tag')
+test_expect_equal "$output" ""
+
+test_begin_subtest "Tag thread in notmuch tree view"
+test_emacs '(add-to-list (quote load-path) "'$PICK_DIR'")
+           (notmuch-tree "tag:inbox")
+           (notmuch-test-wait)
+           ;; move to a sizable thread
+           (forward-line 26)
+           (notmuch-tree-tag-thread (list "+test_thread_tag"))
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file OUTPUT $EXPECTED/notmuch-tree-tag-inbox-thread-tagged
+
+test_begin_subtest "Tag message in notmuch tree view (database)"
+output=$(notmuch search --output=messages 'tag:test_thread_tag')
+test_expect_equal "$output" \
+"id:87ocn0qh6d.fsf@yoom.home.cworth.org
+id:20091118005040.GA25380@dottiness.seas.harvard.edu
+id:yunaayketfm.fsf@aiko.keithp.com
+id:87fx8can9z.fsf@vertex.dottedmag
+id:20091117203301.GV3165@dottiness.seas.harvard.edu
+id:87iqd9rn3l.fsf@vertex.dottedmag
+id:20091117190054.GU3165@dottiness.seas.harvard.edu"
+
+test_begin_subtest "Untag thread in notmuch tree view"
+test_emacs '(add-to-list (quote load-path) "'$PICK_DIR'")
+           (notmuch-tree "tag:inbox")
+           (notmuch-test-wait)
+           ;; move to the same sizable thread as above
+           (forward-line 26)
+           (notmuch-tree-tag-thread (list "-test_thread_tag"))
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file OUTPUT $EXPECTED/notmuch-tree-tag-inbox
+
+test_begin_subtest "Untag message in notmuch tree view (database)"
+output=$(notmuch search --output=messages 'tag:test_thread_tag')
+test_expect_equal "$output" ""
+
+test_begin_subtest "Navigation of notmuch-hello to search results"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-tree-from-search-current-query)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file OUTPUT $EXPECTED/notmuch-tree-tag-inbox
+
+test_begin_subtest "Tree view of a single thread (from search)"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-tree-from-search-thread)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file OUTPUT $EXPECTED/notmuch-tree-single-thread
+
+test_begin_subtest "Tree view of a single thread (from show)"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-search-show-thread)
+           (notmuch-tree-from-show-current-query)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file OUTPUT $EXPECTED/notmuch-tree-single-thread
+
+test_begin_subtest "Message window of tree view"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-search-next-thread)
+           (notmuch-tree-from-search-thread)
+           (notmuch-test-wait)
+           (select-window notmuch-tree-message-window)
+           (test-output)
+           (delete-other-windows)'
+cp OUTPUT /tmp/mjwout
+test_expect_equal_file OUTPUT $EXPECTED/notmuch-tree-show-window
+
+test_begin_subtest "Stash id"
+output=$(test_emacs '(add-to-list (quote load-path) "'$PICK_DIR'")
+                    (require (quote notmuch-tree))
+                    (notmuch-tree "id:1258498485-sup-142@elly")
+                    (notmuch-test-wait)
+                    (notmuch-show-stash-message-id)')
+test_expect_equal "$output" "\"Stashed: id:1258498485-sup-142@elly\""
+
+test_begin_subtest "Move to next matching message"
+output=$(test_emacs '(add-to-list (quote load-path) "'$PICK_DIR'")
+                    (require (quote notmuch-tree))
+                    (notmuch-tree "from:cworth")
+                    (notmuch-test-wait)
+                    (notmuch-tree-next-matching-message)
+                    (notmuch-show-stash-message-id)')
+test_expect_equal "$output" "\"Stashed: id:878we4qdqf.fsf@yoom.home.cworth.org\""
+
+test_begin_subtest "Move to next thread"
+output=$(test_emacs '(add-to-list (quote load-path) "'$PICK_DIR'")
+                    (require (quote notmuch-tree))
+                    (notmuch-tree "tag:inbox")
+                    (notmuch-test-wait)
+                    (forward-line 26)
+                    (notmuch-tree-next-thread)
+                    (notmuch-show-stash-message-id)')
+test_expect_equal "$output" "\"Stashed: id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net\""
+
+test_begin_subtest "Move to previous thread"
+output=$(test_emacs '(add-to-list (quote load-path) "'$PICK_DIR'")
+                    (require (quote notmuch-tree))
+                    (notmuch-tree "tag:inbox")
+                    (notmuch-test-wait)
+                    (forward-line 26)
+                    (notmuch-tree-prev-thread)
+                    (notmuch-show-stash-message-id)')
+test_expect_equal "$output" "\"Stashed: id:20091117190054.GU3165@dottiness.seas.harvard.edu\""
+
+test_begin_subtest "Move to previous previous thread"
+output=$(test_emacs '(add-to-list (quote load-path) "'$PICK_DIR'")
+                    (require (quote notmuch-tree))
+                    (notmuch-tree "tag:inbox")
+                    (notmuch-test-wait)
+                    (forward-line 26)
+                    (notmuch-tree-prev-thread)
+                    (notmuch-tree-prev-thread)
+                    (notmuch-show-stash-message-id)')
+test_expect_equal "$output" "\"Stashed: id:1258493565-13508-1-git-send-email-keithp@keithp.com\""
+
+test_done
index 2e1326ebe1bca735c461704d8147dd180a1a3aa9..b6c86bf02f11616dbf50fc8db2689882244086df 100755 (executable)
@@ -5,14 +5,14 @@ test_description="encoding issues"
 test_begin_subtest "Message with text of unknown charset"
 add_message '[content-type]="text/plain; charset=unknown-8bit"' \
            "[body]=irrelevant"
-output=$(notmuch show id:${gen_msg_id} 2>&1 | notmuch_show_sanitize)
-test_expect_equal "$output" "\fmessage{ id:msg-001@notmuch-test-suite depth:0 match:1 excluded:0 filename:/XXX/mail/msg-001
+output=$(notmuch show id:${gen_msg_id} 2>&1 | notmuch_show_sanitize_all)
+test_expect_equal "$output" "\fmessage{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
 \fheader{
 Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox unread)
 Subject: Message with text of unknown charset
 From: Notmuch Test Suite <test_suite@notmuchmail.org>
 To: Notmuch Test Suite <test_suite@notmuchmail.org>
-Date: Fri, 05 Jan 2001 15:43:57 +0000
+Date: GENERATED_DATE
 \fheader}
 \fbody{
 \fpart{ ID: 1, Content-type: text/plain
@@ -26,7 +26,22 @@ add_message '[content-type]="text/plain; charset=iso-8859-2"' \
             '[content-transfer-encoding]=8bit' \
             '[subject]="ISO-8859-2 encoded message"' \
             "[body]=$'Czech word tu\350\362\341\350\350\355 means pinguin\'s.'" # ISO-8859-2 characters are generated by shell's escape sequences
-output=$(notmuch search tučňáččí 2>&1 | notmuch_show_sanitize)
+output=$(notmuch search tučňáččí 2>&1 | notmuch_show_sanitize_all)
 test_expect_equal "$output" "thread:0000000000000002   2001-01-05 [1/1] Notmuch Test Suite; ISO-8859-2 encoded message (inbox unread)"
 
+test_begin_subtest "RFC 2047 encoded word with spaces"
+add_message '[subject]="=?utf-8?q?encoded word with spaces?="'
+output=$(notmuch search id:${gen_msg_id} 2>&1 | notmuch_show_sanitize)
+test_expect_equal "$output" "thread:0000000000000003   2001-01-05 [1/1] Notmuch Test Suite; encoded word with spaces (inbox unread)"
+
+test_begin_subtest "RFC 2047 encoded words back to back"
+add_message '[subject]="=?utf-8?q?encoded-words-back?==?utf-8?q?to-back?="'
+output=$(notmuch search id:${gen_msg_id} 2>&1 | notmuch_show_sanitize)
+test_expect_equal "$output" "thread:0000000000000004   2001-01-05 [1/1] Notmuch Test Suite; encoded-words-backto-back (inbox unread)"
+
+test_begin_subtest "RFC 2047 encoded words without space before or after"
+add_message '[subject]="=?utf-8?q?encoded?=word without=?utf-8?q?space?=" '
+output=$(notmuch search id:${gen_msg_id} 2>&1 | notmuch_show_sanitize)
+test_expect_equal "$output" "thread:0000000000000005   2001-01-05 [1/1] Notmuch Test Suite; encodedword withoutspace (inbox unread)"
+
 test_done
index f1ae9ea96c9ad2d1278312aa31bfa722f6607346..8bbbc2dd033700119bc6768264940eab821b0d06 100755 (executable)
@@ -67,7 +67,7 @@ thread:XXX   2001-01-05 [1/2] Notmuch Test Suite; Not deleted reply (deleted inb
 test_begin_subtest "Search, don't exclude \"deleted\" messages when --exclude=flag specified"
 output=$(notmuch search --exclude=flag subject:deleted | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)
-thread:XXX   2001-01-05 [1/2] Notmuch Test Suite; Not deleted reply (deleted inbox unread)"
+thread:XXX   2001-01-05 [1/2] Notmuch Test Suite; Deleted (deleted inbox unread)"
 
 test_begin_subtest "Search, don't exclude \"deleted\" messages from search if not configured"
 notmuch config set search.exclude_tags
@@ -152,7 +152,7 @@ ${matching_message_ids[5]}"
 test_begin_subtest "Search, exclude=flag (thread summary)"
 output=$(notmuch search --exclude=flag tag:test | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2001-01-05 [0/6] Notmuch Test Suite; All messages excluded: single match: reply 2 (deleted inbox test unread)
-thread:XXX   2001-01-05 [0/6] Notmuch Test Suite; All messages excluded: double match: reply 4 (deleted inbox test unread)
+thread:XXX   2001-01-05 [0/6] Notmuch Test Suite; All messages excluded: double match: reply 2 (deleted inbox test unread)
 thread:XXX   2001-01-05 [0/6] Notmuch Test Suite; Some messages excluded: single excluded match: reply 3 (deleted inbox test unread)
 thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; Some messages excluded: single non-excluded match: reply 4 (deleted inbox test unread)
 thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; No messages excluded: single match: reply 3 (inbox test unread)"
index 021edb62d4354b10d88d4ef083c4017569123ed4..550b41329b5968168367b801eb108fd2ee5b80d9 100755 (executable)
@@ -68,27 +68,63 @@ test_expect_equal_json "$output" '["inbox", "unread"]'
 test_begin_subtest "Insert message, add tag"
 gen_insert_msg
 notmuch insert +custom < "$gen_msg_filename"
-output=$(notmuch count tag:custom)
-test_expect_equal "$output" "1"
+output=$(notmuch search --output=messages tag:custom)
+test_expect_equal "$output" "id:$gen_msg_id"
 
 test_begin_subtest "Insert message, add/remove tags"
 gen_insert_msg
 notmuch insert +custom -unread < "$gen_msg_filename"
-output=$(notmuch count tag:custom NOT tag:unread)
-test_expect_equal "$output" "1"
+output=$(notmuch search --output=messages tag:custom NOT tag:unread)
+test_expect_equal "$output" "id:$gen_msg_id"
+
+test_begin_subtest "Insert message with default tags stays in new/"
+gen_insert_msg
+notmuch insert < "$gen_msg_filename"
+output=$(notmuch search --output=files id:$gen_msg_id)
+dirname=$(dirname "$output")
+test_expect_equal "$dirname" "$MAIL_DIR/new"
+
+test_begin_subtest "Insert message with non-maildir synced tags stays in new/"
+gen_insert_msg
+notmuch insert +custom -inbox < "$gen_msg_filename"
+output=$(notmuch search --output=files id:$gen_msg_id)
+dirname=$(dirname "$output")
+test_expect_equal "$dirname" "$MAIL_DIR/new"
+
+test_begin_subtest "Insert message with custom new.tags goes to cur/"
+OLDCONFIG=$(notmuch config get new.tags)
+notmuch config set new.tags test
+gen_insert_msg
+notmuch insert < "$gen_msg_filename"
+output=$(notmuch search --output=files id:$gen_msg_id)
+dirname=$(dirname "$output")
+notmuch config set new.tags $OLDCONFIG
+test_expect_equal "$dirname" "$MAIL_DIR/cur"
+
+# additional check on the previous message
+test_begin_subtest "Insert message with custom new.tags actually gets the tags"
+output=$(notmuch search --output=tags id:$gen_msg_id)
+test_expect_equal "$output" "test"
+
+test_begin_subtest "Insert message with maildir synced tags goes to cur/"
+gen_insert_msg
+notmuch insert +flagged < "$gen_msg_filename"
+output=$(notmuch search --output=files id:$gen_msg_id)
+dirname=$(dirname "$output")
+test_expect_equal "$dirname" "$MAIL_DIR/cur"
 
 test_begin_subtest "Insert message into folder"
 gen_insert_msg
 notmuch insert --folder=Drafts < "$gen_msg_filename"
 output=$(notmuch search --output=files folder:Drafts)
 dirname=$(dirname "$output")
-test_expect_equal "$dirname" "$MAIL_DIR/Drafts/cur"
+test_expect_equal "$dirname" "$MAIL_DIR/Drafts/new"
 
 test_begin_subtest "Insert message into folder, add/remove tags"
 gen_insert_msg
 notmuch insert --folder=Drafts +draft -unread < "$gen_msg_filename"
-output=$(notmuch count folder:Drafts tag:draft NOT tag:unread)
-test_expect_equal "$output" "1"
+output=$(notmuch search --output=messages folder:Drafts tag:draft NOT tag:unread)
+test_expect_equal "$output" "id:$gen_msg_id"
 
 gen_insert_msg
 test_expect_code 1 "Insert message into non-existent folder" \
@@ -99,14 +135,14 @@ gen_insert_msg
 notmuch insert --folder=F --create-folder +folder < "$gen_msg_filename"
 output=$(notmuch search --output=files folder:F tag:folder)
 basename=$(basename "$output")
-test_expect_equal_file "$gen_msg_filename" "$MAIL_DIR/F/cur/${basename}"
+test_expect_equal_file "$gen_msg_filename" "$MAIL_DIR/F/new/${basename}"
 
 test_begin_subtest "Insert message, create subfolder"
 gen_insert_msg
 notmuch insert --folder=F/G/H/I/J --create-folder +folder < "$gen_msg_filename"
 output=$(notmuch search --output=files folder:F/G/H/I/J tag:folder)
 basename=$(basename "$output")
-test_expect_equal_file "$gen_msg_filename" "${MAIL_DIR}/F/G/H/I/J/cur/${basename}"
+test_expect_equal_file "$gen_msg_filename" "${MAIL_DIR}/F/G/H/I/J/new/${basename}"
 
 test_begin_subtest "Insert message, create existing subfolder"
 gen_insert_msg
index b87b7f6d7165d5854792f4971938bd01e00648f8..c1cf649d6dcad15735767911f414aa58361a4060 100755 (executable)
--- a/test/json
+++ b/test/json
@@ -26,6 +26,7 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\",
  \"total\": 1,
  \"authors\": \"Notmuch Test Suite\",
  \"subject\": \"json-search-subject\",
+ \"query\": [\"id:$gen_msg_id\", null],
  \"tags\": [\"inbox\",
  \"unread\"]}]"
 
@@ -37,7 +38,7 @@ test_expect_equal_json "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true
 test_begin_subtest "Show message: json, inline attachment filename"
 subject='json-show-inline-attachment-filename'
 id="json-show-inline-attachment-filename@notmuchmail.org"
-emacs_deliver_message \
+emacs_fcc_message \
     "$subject" \
     'This is a test message with inline attachment with a filename' \
     "(mml-attach-file \"$TEST_DIRECTORY/README\" nil nil \"inline\")
@@ -59,6 +60,7 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\",
  \"total\": 1,
  \"authors\": \"Notmuch Test Suite\",
  \"subject\": \"json-search-utf8-body-sübjéct\",
+ \"query\": [\"id:$gen_msg_id\", null],
  \"tags\": [\"inbox\",
  \"unread\"]}]"
 
index 0fc742a419bac7aacb69a762399bda4b11c745fd..3186e70f56e4c03a3eb45dc85d1fd955f8e59a89 100755 (executable)
@@ -4,11 +4,6 @@ test_description="maildir synchronization"
 
 . ./test-lib.sh
 
-# Avoid including the local value of MAIL_DIR in the result.
-filter_show_json() {
-    sed -e "s|${MAIL_DIR}/|MAIL_DIR/|"
-}
-
 # Create the expected maildir structure
 mkdir $MAIL_DIR/cur
 mkdir $MAIL_DIR/new
@@ -40,18 +35,18 @@ output=$(cd ${MAIL_DIR}/cur; ls -1 adding-replied*)
 test_expect_equal "$output" "adding-replied-tag:2,RS"
 
 test_begin_subtest "notmuch show works with renamed file (without notmuch new)"
-output=$(notmuch show --format=json id:${gen_msg_id} | filter_show_json)
-test_expect_equal_json "$output" '[[[{"id": "adding-replied-tag@notmuch-test-suite",
+output=$(notmuch show --format=json id:${gen_msg_id} | notmuch_json_show_sanitize)
+test_expect_equal_json "$output" '[[[{"id": "XXXXX",
 "match": true,
 "excluded": false,
-"filename": "MAIL_DIR/cur/adding-replied-tag:2,RS",
-"timestamp": 978709437,
+"filename": "YYYYY",
+"timestamp": 42,
 "date_relative": "2001-01-05",
 "tags": ["inbox","replied"],
 "headers": {"Subject": "Adding replied tag",
 "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
 "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
-"Date": "Fri, 05 Jan 2001 15:43:57 +0000"},
+"Date": "GENERATED_DATE"},
 "body": [{"id": 1,
 "content-type": "text/plain",
 "content": "This is just a test message (#3)\n"}]},
@@ -83,6 +78,26 @@ test_expect_equal "$output" "No new mail."
 # creating new directories in the mail store, then it should be
 # creating all necessary database state for those directories.
 
+test_begin_subtest "Adding non-maildir tags does not move message from new to cur"
+add_message [subject]='"Message to stay in new"' \
+    [date]='"Sat, 01 Jan 2000 12:00:00 -0000"' \
+    [filename]='message-to-stay-in-new' [dir]=new
+notmuch tag +donotmove subject:"Message to stay in new"
+output=$(cd "$MAIL_DIR"; ls */message-to-stay-in-new*)
+test_expect_equal "$output" "new/message-to-stay-in-new"
+
+test_begin_subtest "Message in cur lacking maildir info gets one on any tag change"
+add_message [filename]='message-to-get-maildir-info' [dir]=cur
+notmuch tag +anytag id:$gen_msg_id
+output=$(cd "$MAIL_DIR"; ls */message-to-get-maildir-info*)
+test_expect_equal "$output" "cur/message-to-get-maildir-info:2,"
+
+test_begin_subtest "Message in new with maildir info is moved to cur on any tag change"
+add_message [filename]='message-with-info-to-be-moved-to-cur:2,' [dir]=new
+notmuch tag +anytag id:$gen_msg_id
+output=$(cd "$MAIL_DIR"; ls */message-with-info-to-be-moved-to-cur*)
+test_expect_equal "$output" "cur/message-with-info-to-be-moved-to-cur:2,"
+
 test_begin_subtest "Removing 'S' flag from existing filename adds 'unread' tag"
 add_message [subject]='"Removing S flag"' [filename]='removing-s-flag:2,S' [dir]=cur
 output=$(notmuch search subject:"Removing S flag" | notmuch_search_sanitize)
index f14b8784b5c70cdbf63168226d4483b1ab1d08da..cb38301c255a1d0b8dd661636499930862fd5b7f 100755 (executable)
@@ -43,7 +43,8 @@ test_expect_equal_json "$output" '
         ],
         "thread": "XXX",
         "timestamp": 978709437,
-        "total": 1
+        "total": 1,
+        "query": ["id:notmuch-sha1-7a6e4eac383ef958fcd3ebf2143db71b8ff01161", null]
     },
     {
         "authors": "Notmuch Test Suite",
@@ -56,7 +57,8 @@ test_expect_equal_json "$output" '
         ],
         "thread": "XXX",
         "timestamp": 0,
-        "total": 1
+        "total": 1,
+        "query": ["id:notmuch-sha1-ca55943aff7a72baf2ab21fa74fab3d632401334", null]
     }
 ]'
 
@@ -93,7 +95,7 @@ Body
 
 test_begin_subtest "Show: json"
 output=$(notmuch show --format=json '*' | notmuch_json_show_sanitize)
-test_expect_equal_json "$output" '
+expected=$(notmuch_json_show_sanitize <<EOF
 [
     [
         [
@@ -154,7 +156,9 @@ test_expect_equal_json "$output" '
             []
         ]
     ]
-]'
-
+]
+EOF
+)
+test_expect_equal_json "$output" "$expected"
 
 test_done
index c974226efd773ba46fd5be9f900919d3aa29fbcc..85cbf672da89f0c72ffa1b67f597ed47422029b4 100755 (executable)
@@ -594,12 +594,12 @@ test_expect_equal_file OUTPUT EXPECTED
 
 test_begin_subtest "'notmuch reply' to a multipart message with json format"
 notmuch reply --format=json 'id:87liy5ap00.fsf@yoom.home.cworth.org' | notmuch_json_show_sanitize >OUTPUT
-cat <<EOF >EXPECTED
+notmuch_json_show_sanitize <<EOF >EXPECTED
 {"reply-headers": {"Subject": "Re: Multipart message",
  "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
  "To": "Carl Worth <cworth@cworth.org>, cworth@cworth.org",
  "In-reply-to": "<87liy5ap00.fsf@yoom.home.cworth.org>",
- "References": " <87liy5ap00.fsf@yoom.home.cworth.org>"},
+ "References": "<87liy5ap00.fsf@yoom.home.cworth.org>"},
  "original": {"id": "XXXXX",
  "match": false,
  "excluded": false,
@@ -647,4 +647,84 @@ notmuch show --format=raw --part=3 id:base64-part-with-crlf > crlf.out
 echo -n -e "\xEF\x0D\x0A" > crlf.expected
 test_expect_equal_file crlf.out crlf.expected
 
-test_done
\ No newline at end of file
+
+# The ISO-8859-1 encoding of U+00BD is a single byte: octal 275
+# (Portability note: Dollar-Single ($'...', ANSI C-style escape sequences)
+# quoting works on bash, ksh, zsh, *BSD sh but not on dash, ash nor busybox sh)
+readonly u_00bd_latin1=$'\275'
+
+# The Unicode fraction symbol 1/2 is U+00BD and is encoded
+# in UTF-8 as two bytes: octal 302 275
+readonly u_00bd_utf8=$'\302\275'
+
+cat <<EOF > ${MAIL_DIR}/include-html
+From: A <a@example.com>
+To: B <b@example.com>
+Subject: html message
+Date: Sat, 01 January 2000 00:00:00 +0000
+Message-ID: <htmlmessage>
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="==-=="
+
+--==-==
+Content-Type: text/html; charset=UTF-8
+
+<p>0.5 equals ${u_00bd_utf8}</p>
+
+--==-==
+Content-Type: text/html; charset=ISO-8859-1
+
+<p>0.5 equals ${u_00bd_latin1}</p>
+
+--==-==
+Content-Type: text/plain; charset=UTF-8
+
+0.5 equals ${u_00bd_utf8}
+
+--==-==--
+EOF
+
+notmuch new > /dev/null
+
+cat_expected_head ()
+{
+        cat <<EOF
+[[[{"id": "htmlmessage", "match":true, "excluded": false, "date_relative":"2000-01-01",
+   "timestamp": 946684800,
+   "filename": "${MAIL_DIR}/include-html",
+   "tags": ["inbox", "unread"],
+   "headers": { "Date": "Sat, 01 Jan 2000 00:00:00 +0000", "From": "A <a@example.com>",
+                "Subject": "html message", "To": "B <b@example.com>"},
+   "body": [{
+     "content-type": "multipart/alternative", "id": 1,
+EOF
+}
+
+cat_expected_head > EXPECTED.nohtml
+cat <<EOF >> EXPECTED.nohtml
+"content": [
+  { "id": 2, "content-charset": "UTF-8", "content-length": 21, "content-type": "text/html"},
+  { "id": 3, "content-charset": "ISO-8859-1", "content-length": 20, "content-type": "text/html"},
+  { "id": 4, "content-type": "text/plain", "content": "0.5 equals \\u00bd\\n"}
+]}]},[]]]]
+EOF
+
+# Both the UTF-8 and ISO-8859-1 part should have U+00BD
+cat_expected_head > EXPECTED.withhtml
+cat <<EOF >> EXPECTED.withhtml
+"content": [
+  { "id": 2, "content-type": "text/html", "content": "<p>0.5 equals \\u00bd</p>\\n"},
+  { "id": 3, "content-type": "text/html", "content": "<p>0.5 equals \\u00bd</p>\\n"},
+  { "id": 4, "content-type": "text/plain", "content": "0.5 equals \\u00bd\\n"}
+]}]},[]]]]
+EOF
+
+test_begin_subtest "html parts excluded by default"
+notmuch show --format=json id:htmlmessage > OUTPUT
+test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED.nohtml)"
+
+test_begin_subtest "html parts included"
+notmuch show --format=json --include-html id:htmlmessage > OUTPUT
+test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED.withhtml)"
+
+test_done
index 3eff2fe9e22ec8d25f0e85c523bcb914a519bdb9..f27423dac30b13f7fafc317c1559367904c764f2 100755 (executable)
--- a/test/new
+++ b/test/new
@@ -218,9 +218,10 @@ test_expect_equal "$output" "Added 1 new message to the database."
 
 test_begin_subtest "Ignore files and directories specified in new.ignore (multiple occurrences)"
 notmuch config set new.ignore .git ignored_file .ignored_hidden_file
+notmuch new > /dev/null # ensure that files/folders will be printed in ASCII order.
 touch "${MAIL_DIR}"/.git # change .git's mtime for notmuch new to rescan.
+touch "${MAIL_DIR}"      # likewise for MAIL_DIR
 mkdir -p "${MAIL_DIR}"/one/two/three/.git
-notmuch new > /dev/null # ensure that files/folders will be printed in ASCII order.
 touch "${MAIL_DIR}"/{one,one/two,one/two/three}/ignored_file
 output=$(NOTMUCH_NEW --debug 2>&1 | sort)
 test_expect_equal "$output" \
index 6db797955bdb54d852b060f0bae3083335672759..18593f61d23b14d8386e901eeaeec3b5305fc944 100755 (executable)
@@ -19,6 +19,7 @@ cd $(dirname "$0")
 TESTS="
   basic
   help-test
+  compact
   config
   setup
   new
@@ -62,6 +63,7 @@ TESTS="
   emacs-address-cleaning
   emacs-hello
   emacs-show
+  emacs-tree
   missing-headers
   hex-escaping
   parse-time-string
@@ -97,6 +99,9 @@ trap - HUP INT TERM
 
 # Report results
 ./aggregate-results.sh test-results/*
+ev=$?
 
 # Clean up
 rm -rf test-results corpus.mail
+
+exit $ev
index de0b8677943f325afe2601e16b9458b416794d4f..daf5735cd6da3fd8b3ea870e517eee0739d31ca6 100755 (executable)
--- a/test/raw
+++ b/test/raw
@@ -11,22 +11,22 @@ output=$(notmuch show --format=raw "*" 2>&1)
 test_expect_equal "$output" "Error: search term did not match precisely one message."
 
 test_begin_subtest "Show a raw message"
-output=$(notmuch show --format=raw id:msg-001@notmuch-test-suite)
+output=$(notmuch show --format=raw id:msg-001@notmuch-test-suite | notmuch_date_sanitize)
 test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
 To: Notmuch Test Suite <test_suite@notmuchmail.org>
 Message-Id: <msg-001@notmuch-test-suite>
 Subject: Test message #1
-Date: Fri, 05 Jan 2001 15:43:57 +0000
+Date: GENERATED_DATE
 
 This is just a test message (#1)"
 
 test_begin_subtest "Show another raw message"
-output=$(notmuch show --format=raw id:msg-002@notmuch-test-suite)
+output=$(notmuch show --format=raw id:msg-002@notmuch-test-suite | notmuch_date_sanitize)
 test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
 To: Notmuch Test Suite <test_suite@notmuchmail.org>
 Message-Id: <msg-002@notmuch-test-suite>
 Subject: Test message #2
-Date: Fri, 05 Jan 2001 15:43:57 +0000
+Date: GENERATED_DATE
 
 This is just a test message (#2)"
 
index ee5d3618671c5ee33db5843edf1ec631040bf814..b0d854a1fc000a3a3b85089eb7c4ea5d6ab538cf 100755 (executable)
@@ -132,7 +132,9 @@ add_message '[subject]="This subject is exactly 200 bytes in length. Other than
            '[body]="200-byte header"'
 output=$(notmuch reply id:${gen_msg_id})
 test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
-Subject: Re: This subject is exactly 200 bytes in length. Other than its length there is not much of note here. Note that the length of 200 bytes includes the Subject: and Re: prefixes with two spaces
+Subject: Re: This subject is exactly 200 bytes in length. Other than its
+ length there is not much of note here. Note that the length of 200 bytes
+ includes the Subject: and Re: prefixes with two spaces
 In-Reply-To: <${gen_msg_id}>
 References: <${gen_msg_id}>
 
@@ -193,4 +195,63 @@ References: <${gen_msg_id}>
 On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
 > From guessing"
 
+test_begin_subtest "Reply with RFC 2047-encoded headers"
+add_message '[subject]="=?iso-8859-1?q?=e0=df=e7?="' \
+           '[from]="=?utf-8?q?=e2=98=83?= <snowman@example.com>"' \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="Encoding"'
+
+# GMime happens to change from Q- to B-encoding.  We canonicalize the
+# case of the encoding and charset because different versions of GMime
+# capitalize the encoding differently.
+output=$(notmuch reply id:${gen_msg_id} | perl -pe 's/=\?[^?]+\?[bB]\?/lc($&)/ge')
+test_expect_equal "$output" "\
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: =?iso-8859-1?b?4N/n?=
+To: =?utf-8?b?4piD?= <snowman@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, ☃ <snowman@example.com> wrote:
+> Encoding"
+
+test_begin_subtest "Reply with RFC 2047-encoded headers (JSON)"
+output=$(notmuch reply --format=json id:${gen_msg_id})
+test_expect_equal_json "$output" '
+{
+    "original": {
+        "body": [
+            {
+                "content": "Encoding\n",
+                "content-type": "text/plain",
+                "id": 1
+            }
+        ],
+        "date_relative": "2010-01-05",
+        "excluded": false,
+        "filename": "'${MAIL_DIR}'/msg-012",
+        "headers": {
+            "Date": "Tue, 05 Jan 2010 15:43:56 +0000",
+            "From": "\u2603 <snowman@example.com>",
+            "Subject": "\u00e0\u00df\u00e7",
+            "To": "Notmuch Test Suite <test_suite@notmuchmail.org>"
+        },
+        "id": "'${gen_msg_id}'",
+        "match": false,
+        "tags": [
+            "inbox",
+            "unread"
+        ],
+        "timestamp": 1262706236
+    },
+    "reply-headers": {
+        "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+        "In-reply-to": "<'${gen_msg_id}'>",
+        "References": "<'${gen_msg_id}'>",
+        "Subject": "Re: \u00e0\u00df\u00e7",
+        "To": "\u2603 <snowman@example.com>"
+    }
+}'
+
+
 test_done
index c7d15bbed469124fd2b53aa5f239a099e5b3823d..30e5e385e0b32ae2cad2d278192226214424328d 100755 (executable)
@@ -200,7 +200,9 @@ add_message '[subject]="This subject is exactly 200 bytes in length. Other than
             '[body]="200-byte header"'
 output=$(notmuch reply  --reply-to=sender id:${gen_msg_id})
 test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
-Subject: Re: This subject is exactly 200 bytes in length. Other than its length there is not much of note here. Note that the length of 200 bytes includes the Subject: and Re: prefixes with two spaces
+Subject: Re: This subject is exactly 200 bytes in length. Other than its
+ length there is not much of note here. Note that the length of 200 bytes
+ includes the Subject: and Re: prefixes with two spaces
 In-Reply-To: <${gen_msg_id}>
 References: <${gen_msg_id}>
 
index c2a87eb1ca27ceed2d9b290e26bc1efef65bf89c..5ccfeaf914847966b3a2b92b1627d7769a460f09 100755 (executable)
@@ -239,6 +239,64 @@ MAIL_DIR/cur/01:2,
 EOF
 test_expect_equal_file OUTPUT EXPECTED
 
+test_begin_subtest "--output=files --duplicate=1"
+notmuch search --output=files --duplicate=1 '*' | sed -e "s,$MAIL_DIR,MAIL_DIR," >OUTPUT
+cat <<EOF >EXPECTED
+MAIL_DIR/cur/52:2,
+MAIL_DIR/cur/53:2,
+MAIL_DIR/cur/50:2,
+MAIL_DIR/cur/49:2,
+MAIL_DIR/cur/48:2,
+MAIL_DIR/cur/47:2,
+MAIL_DIR/cur/46:2,
+MAIL_DIR/cur/45:2,
+MAIL_DIR/cur/44:2,
+MAIL_DIR/cur/43:2,
+MAIL_DIR/cur/42:2,
+MAIL_DIR/cur/41:2,
+MAIL_DIR/cur/40:2,
+MAIL_DIR/cur/39:2,
+MAIL_DIR/cur/38:2,
+MAIL_DIR/cur/37:2,
+MAIL_DIR/cur/36:2,
+MAIL_DIR/cur/35:2,
+MAIL_DIR/cur/34:2,
+MAIL_DIR/cur/33:2,
+MAIL_DIR/cur/32:2,
+MAIL_DIR/cur/31:2,
+MAIL_DIR/cur/30:2,
+MAIL_DIR/cur/29:2,
+MAIL_DIR/cur/28:2,
+MAIL_DIR/cur/27:2,
+MAIL_DIR/cur/26:2,
+MAIL_DIR/cur/25:2,
+MAIL_DIR/cur/24:2,
+MAIL_DIR/cur/23:2,
+MAIL_DIR/cur/22:2,
+MAIL_DIR/cur/21:2,
+MAIL_DIR/cur/19:2,
+MAIL_DIR/cur/18:2,
+MAIL_DIR/cur/20:2,
+MAIL_DIR/cur/17:2,
+MAIL_DIR/cur/16:2,
+MAIL_DIR/cur/15:2,
+MAIL_DIR/cur/14:2,
+MAIL_DIR/cur/13:2,
+MAIL_DIR/cur/12:2,
+MAIL_DIR/cur/11:2,
+MAIL_DIR/cur/10:2,
+MAIL_DIR/cur/09:2,
+MAIL_DIR/cur/08:2,
+MAIL_DIR/cur/06:2,
+MAIL_DIR/cur/05:2,
+MAIL_DIR/cur/04:2,
+MAIL_DIR/cur/03:2,
+MAIL_DIR/cur/07:2,
+MAIL_DIR/cur/02:2,
+MAIL_DIR/cur/01:2,
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
 test_begin_subtest "--output=files --format=json"
 notmuch search --format=json --output=files '*' | sed -e "s,$MAIL_DIR,MAIL_DIR," >OUTPUT
 cat <<EOF >EXPECTED
@@ -298,6 +356,13 @@ cat <<EOF >EXPECTED
 EOF
 test_expect_equal_file OUTPUT EXPECTED
 
+test_begin_subtest "--output=files --format=json --duplicate=2"
+notmuch search --format=json --output=files --duplicate=2 '*' | sed -e "s,$MAIL_DIR,MAIL_DIR," >OUTPUT
+cat <<EOF >EXPECTED
+["MAIL_DIR/cur/51:2,"]
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
 test_begin_subtest "--output=tags"
 notmuch search --output=tags '*' >OUTPUT
 cat <<EOF >EXPECTED
index 492a82f780d1f15f5cd26609115c9921c578889a..667e3195d836906c2d27498ade17a1f6fa7b0611 100755 (executable)
--- a/test/sexp
+++ b/test/sexp
@@ -19,7 +19,7 @@ test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :f
 test_begin_subtest "Search message: sexp"
 add_message "[subject]=\"sexp-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"sexp-search-message\""
 output=$(notmuch search --format=sexp "sexp-search-message" | notmuch_search_sanitize)
-test_expect_equal "$output" "((:thread \"0000000000000002\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-subject\" :tags (\"inbox\" \"unread\")))"
+test_expect_equal "$output" "((:thread \"0000000000000002\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-subject\" :query (\"id:$gen_msg_id\" nil) :tags (\"inbox\" \"unread\")))"
 
 test_begin_subtest "Show message: sexp, utf-8"
 add_message "[subject]=\"sexp-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-show-méssage\""
@@ -29,7 +29,7 @@ test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :f
 test_begin_subtest "Show message: sexp, inline attachment filename"
 subject='sexp-show-inline-attachment-filename'
 id="sexp-show-inline-attachment-filename@notmuchmail.org"
-emacs_deliver_message \
+emacs_fcc_message \
     "$subject" \
     'This is a test message with inline attachment with a filename' \
     "(mml-attach-file \"$TEST_DIRECTORY/README\" nil nil \"inline\")
@@ -44,7 +44,7 @@ test_expect_equal "$output" "((((:id \"$id\" :match t :excluded nil :filename \"
 test_begin_subtest "Search message: sexp, utf-8"
 add_message "[subject]=\"sexp-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\""
 output=$(notmuch search --format=sexp "jsön-search-méssage" | notmuch_search_sanitize)
-test_expect_equal "$output" "((:thread \"0000000000000005\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-utf8-body-sübjéct\" :tags (\"inbox\" \"unread\")))"
+test_expect_equal "$output" "((:thread \"0000000000000005\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-utf8-body-sübjéct\" :query (\"id:$gen_msg_id\" nil) :tags (\"inbox\" \"unread\")))"
 
 
 test_done
index d26b49f7551f1cd3bc159e241f64976e5e37db58..37fcb3d0e19dd845fa5cbf202fce63cf2df60811 100644 (file)
@@ -77,19 +77,22 @@ invisible text."
        (setq start next-pos)))
     str))
 
+;; process-attributes is not defined everywhere, so define an
+;; alternate way to test if a process still exists.
+
+(defun test-process-running (pid)
+  (= 0
+   (signal-process pid 0)))
+
 (defun orphan-watchdog-check (pid)
   "Periodically check that the process with id PID is still
 running, quit if it terminated."
-  (if (not (process-attributes pid))
+  (if (not (test-process-running pid))
       (kill-emacs)))
 
 (defun orphan-watchdog (pid)
   "Initiate orphan watchdog check."
-  ; If process-attributes returns nil right away, that probably means
-  ; it is unimplimented. So we delay two minutes before killing emacs.
-  (if (process-attributes pid)
-      (run-at-time 60 60 'orphan-watchdog-check pid)
-    (run-at-time 120 60 'orphan-watchdog-check pid)))
+  (run-at-time 60 60 'orphan-watchdog-check pid))
 
 (defun hook-counter (hook)
   "Count how many times a hook is called.  Increments
index ffab1bb509c400e15948b7fbe5df83d6294187d2..efa9fb6ff6245b5183d6577b31e2650c2097e0f1 100644 (file)
@@ -22,6 +22,9 @@ if [ ${BASH_VERSINFO[0]} -lt 4 ]; then
     exit 1
 fi
 
+# Make sure echo builtin does not expand backslash-escape sequences by default.
+shopt -u xpg_echo
+
 # if --tee was passed, write the output not only to the terminal, but
 # additionally to the file test-results/$BASENAME.out, too.
 case "$GIT_TEST_TEE_STARTED, $* " in
@@ -353,7 +356,11 @@ generate_message ()
     fi
 
     if [ -z "${template[date]}" ]; then
-       template[date]="Fri, 05 Jan 2001 15:43:57 +0000"
+       # we use decreasing timestamps here for historical reasons;
+       # the existing test suite when we converted to unique timestamps just
+       # happened to have signicantly fewer failures with that choice.
+       template[date]=$(TZ=UTC printf "%(%a, %d %b %Y %T %z)T\n" \
+                       $((978709437 - gen_msg_cnt)))
     fi
 
     additional_headers=""
@@ -442,9 +449,9 @@ emacs_deliver_message ()
 
     test_emacs \
        "(let ((message-send-mail-function 'message-smtpmail-send-it)
+               (mail-host-address \"example.com\")
               (smtpmail-smtp-server \"localhost\")
               (smtpmail-smtp-service \"25025\"))
-          (notmuch-hello)
           (notmuch-mua-mail)
           (message-goto-to)
           (insert \"test_suite@notmuchmail.org\nDate: 01 Jan 2000 12:00:00 -0000\")
@@ -462,6 +469,36 @@ emacs_deliver_message ()
     notmuch new >/dev/null
 }
 
+# Pretend to deliver a message with emacs. Really save it to a file
+# and add it to the database
+#
+# Uses emacs to generate and deliver a message to the mail store.
+# Accepts arbitrary extra emacs/elisp functions to modify the message
+# before sending, which is useful to doing things like attaching files
+# to the message and encrypting/signing.
+emacs_fcc_message ()
+{
+    local subject="$1"
+    local body="$2"
+    shift 2
+    # before we can send a message, we have to prepare the FCC maildir
+    mkdir -p "$MAIL_DIR"/sent/{cur,new,tmp}
+
+    test_emacs \
+       "(let ((message-send-mail-function (lambda () t))
+               (mail-host-address \"example.com\"))
+          (notmuch-mua-mail)
+          (message-goto-to)
+          (insert \"test_suite@notmuchmail.org\nDate: 01 Jan 2000 12:00:00 -0000\")
+          (message-goto-subject)
+          (insert \"${subject}\")
+          (message-goto-body)
+          (insert \"${body}\")
+          $@
+          (message-send-and-exit))" || return 1
+    notmuch new >/dev/null
+}
+
 # Generate a corpus of email and add it to the database.
 #
 # This corpus is fixed, (it happens to be 50 messages from early in
@@ -614,16 +651,36 @@ notmuch_show_sanitize_all ()
 {
     sed \
        -e 's| filename:.*| filename:XXXXX|' \
-       -e 's| id:[^ ]* | id:XXXXX |'
+       -e 's| id:[^ ]* | id:XXXXX |' | \
+       notmuch_date_sanitize
 }
 
 notmuch_json_show_sanitize ()
 {
     sed \
        -e 's|"id": "[^"]*",|"id": "XXXXX",|g' \
-       -e 's|"filename": "/[^"]*",|"filename": "YYYYY",|g'
+       -e 's|"Date": "Fri, 05 Jan 2001 [^"]*0000"|"Date": "GENERATED_DATE"|g' \
+       -e 's|"filename": "/[^"]*",|"filename": "YYYYY",|g' \
+       -e 's|"timestamp": 97.......|"timestamp": 42|g'
+}
+
+notmuch_emacs_error_sanitize ()
+{
+    local command=$1
+    shift
+    for file in "$@"; do
+       echo "=== $file ==="
+       cat "$file"
+    done | sed  \
+       -e 's/^\[.*\]$/[XXX]/' \
+       -e "s|^\(command: \)\{0,1\}/.*/$command|\1YYY/$command|"
 }
 
+notmuch_date_sanitize ()
+{
+    sed \
+       -e 's/^Date: Fri, 05 Jan 2001 .*0000/Date: GENERATED_DATE/'
+}
 # End of notmuch helper functions
 
 # Use test_set_prereq to tell that a particular prerequisite is available.
diff --git a/test/tree.expected-output/notmuch-tree-show-window b/test/tree.expected-output/notmuch-tree-show-window
new file mode 100644 (file)
index 0000000..e16792b
--- /dev/null
@@ -0,0 +1,40 @@
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 14:00:54 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ text/plain ]
+I saw the LWN article and decided to take a look at notmuch.  I'm
+currently using mutt and mairix to index and read a collection of
+Maildir mail folders (around 40,000 messages total).
+
+notmuch indexed the messages without complaint, but my attempt at
+searching bombed out. Running, for example:
+
+  notmuch search storage
+
+Resulted in 4604 lines of errors along the lines of:
+
+  Error opening
+  /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+  Too many open files
+
+I'm curious if this is expected behavior (i.e., notmuch does not work
+with Maildir) or if something else is going on.
+
+Cheers,
+
+[ 4-line signature. Click/Enter to show. ]
+-- 
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
diff --git a/test/tree.expected-output/notmuch-tree-single-thread b/test/tree.expected-output/notmuch-tree-single-thread
new file mode 100644 (file)
index 0000000..c9e5ef8
--- /dev/null
@@ -0,0 +1,6 @@
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox, unread)
+  2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox, unread)
+  2009-11-17  Keith Packard          ╰┬► ...                                              (inbox, unread)
+  2009-11-18  Carl Worth              ╰─► ...                                             (inbox, unread)
+End of search results.
diff --git a/test/tree.expected-output/notmuch-tree-tag-inbox b/test/tree.expected-output/notmuch-tree-tag-inbox
new file mode 100644 (file)
index 0000000..484141e
--- /dev/null
@@ -0,0 +1,53 @@
+  2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox, unread)
+  2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox, unread)
+  2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox, unread)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search        instead of going into an infinite loop (attachment, inbox, unread)
+  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (inbox, unread)
+  2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox, signed, unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Israel Herraiz        ┬►[notmuch] New to the list                           (inbox, unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] What a great idea!                        (inbox, unread)
+  2009-11-17  Jan Janak             ├─► ...                                               (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] [PATCH] Older versions of install do not support -C. (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox, unread)
+  2009-11-18  Keith Packard         ╰┬► ...                                               (inbox, unread)
+  2009-11-18  Carl Worth             ╰─► ...                                              (inbox, unread)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove    inbox (and unread) tags (inbox, unread)
+  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox, unread)
+  2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox, signed, unread)
+  2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox, signed, unread)
+  2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox, signed, unread)
+  2009-11-17  Mikhail Gusarov       │ ├─► ...                                             (inbox, unread)
+  2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox, unread)
+  2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox, signed, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox, unread)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox, unread)
+  2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox, unread)
+  2009-11-17  Keith Packard          ╰┬► ...                                              (inbox, unread)
+  2009-11-18  Carl Worth              ╰─► ...                                             (inbox, unread)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that    highlights URLs and uses word-wrap (inbox, unread)
+  2009-11-18  Alexander Botero-Low  ╰─►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox, unread)
+  2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox, unread)
+  2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox, unread)
+  2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox, unread)
+  2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox, unread)
+  2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox, unread)
+  2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox, unread)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool       subdirectories into tags (inbox, unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before  statting (inbox, unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox, unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++     libs. (inbox, unread)
+  2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment, inbox, signed, unread)
+  2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment, inbox, signed, unread)
+  2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox, unread)
+  2009-11-17  Alex Botero-Lowry     ┬►[notmuch] preliminary FreeBSD support               (attachment, inbox, unread)
+  2009-11-17  Carl Worth            ╰─► ...                                               (inbox, unread)
+End of search results.
diff --git a/test/tree.expected-output/notmuch-tree-tag-inbox-tagged b/test/tree.expected-output/notmuch-tree-tag-inbox-tagged
new file mode 100644 (file)
index 0000000..1f75a34
--- /dev/null
@@ -0,0 +1,53 @@
+  2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox, unread)
+  2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox, test_tag, unread)
+  2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox, unread)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search        instead of going into an infinite loop (attachment, inbox, unread)
+  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (inbox, unread)
+  2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox, signed, unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Israel Herraiz        ┬►[notmuch] New to the list                           (inbox, unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] What a great idea!                        (inbox, unread)
+  2009-11-17  Jan Janak             ├─► ...                                               (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] [PATCH] Older versions of install do not support -C. (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox, unread)
+  2009-11-18  Keith Packard         ╰┬► ...                                               (inbox, unread)
+  2009-11-18  Carl Worth             ╰─► ...                                              (inbox, unread)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove    inbox (and unread) tags (inbox, unread)
+  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox, unread)
+  2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox, signed, unread)
+  2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox, signed, unread)
+  2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox, signed, unread)
+  2009-11-17  Mikhail Gusarov       │ ├─► ...                                             (inbox, unread)
+  2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox, unread)
+  2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox, signed, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox, unread)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox, unread)
+  2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox, unread)
+  2009-11-17  Keith Packard          ╰┬► ...                                              (inbox, unread)
+  2009-11-18  Carl Worth              ╰─► ...                                             (inbox, unread)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that    highlights URLs and uses word-wrap (inbox, unread)
+  2009-11-18  Alexander Botero-Low  ╰─►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox, unread)
+  2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox, unread)
+  2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox, unread)
+  2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox, unread)
+  2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox, unread)
+  2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox, unread)
+  2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox, unread)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool       subdirectories into tags (inbox, unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before  statting (inbox, unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox, unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++     libs. (inbox, unread)
+  2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment, inbox, signed, unread)
+  2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment, inbox, signed, unread)
+  2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox, unread)
+  2009-11-17  Alex Botero-Lowry     ┬►[notmuch] preliminary FreeBSD support               (attachment, inbox, unread)
+  2009-11-17  Carl Worth            ╰─► ...                                               (inbox, unread)
+End of search results.
diff --git a/test/tree.expected-output/notmuch-tree-tag-inbox-thread-tagged b/test/tree.expected-output/notmuch-tree-tag-inbox-thread-tagged
new file mode 100644 (file)
index 0000000..a7aba6e
--- /dev/null
@@ -0,0 +1,53 @@
+  2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox, unread)
+  2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox, unread)
+  2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox, unread)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search        instead of going into an infinite loop (attachment, inbox, unread)
+  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (inbox, unread)
+  2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox, signed, unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Israel Herraiz        ┬►[notmuch] New to the list                           (inbox, unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] What a great idea!                        (inbox, unread)
+  2009-11-17  Jan Janak             ├─► ...                                               (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] [PATCH] Older versions of install do not support -C. (inbox, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, unread)
+  2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox, unread)
+  2009-11-18  Keith Packard         ╰┬► ...                                               (inbox, unread)
+  2009-11-18  Carl Worth             ╰─► ...                                              (inbox, unread)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove    inbox (and unread) tags (inbox, unread)
+  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox, unread)
+  2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox, signed, test_thread_tag, unread)
+  2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox, signed, test_thread_tag, unread)
+  2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox, signed, test_thread_tag, unread)
+  2009-11-17  Mikhail Gusarov       │ ├─► ...                                             (inbox, test_thread_tag, unread)
+  2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox, test_thread_tag, unread)
+  2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox, signed, test_thread_tag, unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox, test_thread_tag, unread)
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox, unread)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox, unread)
+  2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox, unread)
+  2009-11-17  Keith Packard          ╰┬► ...                                              (inbox, unread)
+  2009-11-18  Carl Worth              ╰─► ...                                             (inbox, unread)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that    highlights URLs and uses word-wrap (inbox, unread)
+  2009-11-18  Alexander Botero-Low  ╰─►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox, unread)
+  2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox, unread)
+  2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox, unread)
+  2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox, unread)
+  2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox, unread)
+  2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox, unread)
+  2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox, unread)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool       subdirectories into tags (inbox, unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before  statting (inbox, unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox, unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++     libs. (inbox, unread)
+  2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment, inbox, signed, unread)
+  2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment, inbox, signed, unread)
+  2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox, unread)
+  2009-11-17  Alex Botero-Lowry     ┬►[notmuch] preliminary FreeBSD support               (attachment, inbox, unread)
+  2009-11-17  Carl Worth            ╰─► ...                                               (inbox, unread)
+End of search results.
diff --git a/util/endian-util.h b/util/endian-util.h
new file mode 100644 (file)
index 0000000..bc80c40
--- /dev/null
@@ -0,0 +1,38 @@
+/* this file mimics the macros present in recent GCC and CLANG */
+
+#ifndef _ENDIAN_UTIL_H
+#define _ENDIAN_UTIL_H
+
+/* This are prefixed with UTIL to avoid collisions
+ *
+ * You can use something like the following to define UTIL_BYTE_ORDER
+ * in a configure script.
+ */
+#if 0
+#include <stdio.h>
+#include <stdint.h>
+uint32_t test = 0x34333231;
+int main() { printf("%.4s\n", (const char*)&test); return 0; }
+#endif
+
+#define UTIL_ORDER_BIG_ENDIAN    4321
+#define UTIL_ORDER_LITTLE_ENDIAN  1234
+
+
+#if !defined(UTIL_BYTE_ORDER) || ((UTIL_BYTE_ORDER != UTIL_ORDER_BIG_ENDIAN) && \
+                                 (UTIL_BYTE_ORDER != UTIL_ORDER_LITTLE_ENDIAN))
+#undef UTIL_BYTE_ORDER
+#ifdef __BYTE_ORDER__
+#  if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+#    define UTIL_BYTE_ORDER UTIL_ORDER_LITTLE_ENDIAN
+#  elif (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+#    define UTIL_BYTE_ORDER UTIL_ORDER_BIG_ENDIAN
+#  else
+#    error "Unsupported __BYTE_ORDER__"
+#  endif
+#else
+#  error "UTIL_BYTE_ORDER not correctly defined and __BYTE_ORDER__ not defined."
+#endif
+#endif
+
+#endif
diff --git a/version b/version
index 8eac30c383c3451e129b736d33e696ce149c0332..50653ad0a6e7db059c47d02e28d65c8e88ab4bd2 100644 (file)
--- a/version
+++ b/version
@@ -1 +1 @@
-0.16
+0.17
index bb3ec608fd11584f1fcfb62f0207ed67a6c749cc..b6f9db786faef26e90d7fef9aed2f20797032f75 100644 (file)
@@ -8,6 +8,7 @@ all:
 
 install:
        $(INSTALL) $(CURDIR)/notmuch.vim $(D)$(prefix)/plugin/notmuch.vim
+       $(INSTALL) $(CURDIR)/notmuch.txt $(D)$(prefix)/doc/notmuch.txt
        @$(foreach file,$(wildcard syntax/*), \
                $(INSTALL) $(CURDIR)/$(file) $(D)$(prefix)/$(file);)
 
diff --git a/vim/notmuch.txt b/vim/notmuch.txt
new file mode 100644 (file)
index 0000000..4374102
--- /dev/null
@@ -0,0 +1,153 @@
+*notmuch.txt*  Plug-in to make vim a nice email client using notmuch
+
+Author: Felipe Contreras <felipe.contreras@gmail.com>
+
+Overview                                       |notmuch-intro|
+Usage                                          |notmuch-usage|
+Mappings                                       |notmuch-mappings|
+Configuration                                  |notmuch-config|
+
+==============================================================================
+OVERVIEW                                       *notmuch-intro*
+
+This is a vim plug-in that provides a fully usable mail client interface,
+utilizing the notmuch framework.
+
+It has three main views: folders, search, and thread. In the folder view you
+can find a summary of saved searches, In the search view you can see all the
+threads that comprise the selected search, and in the thread view you can read
+every mail in the thread.
+
+==============================================================================
+USAGE                                          *notmuch-usage*
+
+To use it, simply run the `:NotMuch` command.
+
+By default you start in the folder view which shows you default searches and
+the number of threads that match those:
+>
+       10 new                  (tag:inbox and tag:unread)
+       20 inbox                (tag:inbox)
+       30 unread               (tag:unread)
+<
+You can see the threads of each by clicking `enter`, which sends you to the
+search view. In both the search and folder views you can type `s` to type a
+new search, or `=` to refresh. To see a thread, type `enter` again.
+
+To exit a view, click `q`.
+
+Also, you can specify a search directly:
+>
+       :NotMuch is:inbox and date:yesterday..
+<
+==============================================================================
+MAPPINGS                                       *notmuch-mappings*
+
+------------------------------------------------------------------------------
+Folder view~
+
+<enter>        Show selected search
+s      Enter a new search
+=      Refresh
+c      Compose a new mail
+
+------------------------------------------------------------------------------
+Search view~
+
+q      Quit view
+<enter>        Show selected search
+<space>        Show selected search with filter
+A      Archive (-inbox -unread)
+I      Mark as read (-unread)
+t      Tag (prompted)
+s      Search
+=      Refresh
+?      Show search information
+c      Compose a new mail
+>
+------------------------------------------------------------------------------
+Thread view~
+
+q      Quit view
+A      Archive (-inbox -unread)
+I      Mark as read (-unread)
+t      Tag (prompted)
+s      Search
+p      Save patches
+r      Reply
+?      Show thread information
+<tab>  Show next message
+c      Compose a new mail
+
+------------------------------------------------------------------------------
+Compose view~
+
+q      Quit view
+s      Send
+
+==============================================================================
+CONFIGURATION                                  *notmuch-config*
+
+You can add the following configurations to your `.vimrc`, or
+`~/.vim/plugin/notmuch.vim`.
+
+                                               *g:notmuch_folders*
+
+The first thing you might want to do is set your custom searches.
+>
+       let g:notmuch_folders = [
+               \ [ 'new', 'tag:inbox and tag:unread' ],
+               \ [ 'inbox', 'tag:inbox' ],
+               \ [ 'unread', 'tag:unread' ],
+               \ [ 'to-do', 'tag:to-do' ],
+               \ [ 'to-me', 'to:john.doe and tag:new' ],
+               \ ]
+<
+
+                                               *g:notmuch_custom_search_maps*
+                                               *g:notmuch_custom_show_maps*
+
+You can also configure the keyboard mappings for the different views:
+>
+       let g:notmuch_custom_search_maps = {
+               \ 't':          'search_tag("+to-do -inbox")',
+               \ 'd':          'search_tag("+deleted -inbox -unread")',
+               \ }
+
+       let g:notmuch_custom_show_maps = {
+               \ 't':          'show_tag("+to-do -inbox")',
+               \ 'd':          'show_tag("+deleted -inbox -unread")',
+               \ }
+<
+
+                                               *g:notmuch_date_format*
+
+To configure the date format you want in the search view:
+>
+       let g:notmuch_date_format = '%d.%m.%y'
+<
+
+                                               *g:notmuch_datetime_format*
+
+You can do the same for the thread view:
+>
+       let g:notmuch_datetime_format = '%d.%m.%y %H:%M:%S'
+<
+
+                                               *g:notmuch_folders_count_threads*
+
+If you want to count the threads instead of the messages in the folder view:
+>
+       let g:notmuch_folders_count_threads = 0
+<
+
+                                               *g:notmuch_reader*
+                                               *g:notmuch_sendmail*
+
+You can also configure your externail mail reader and sendemail program:
+>
+       let g:notmuch_reader = 'mutt -f %s'
+       let g:notmuch_sendmail = 'sendmail'
+<
+
+vim:tw=78:ts=8:noet:ft=help:
index bdf2720fc2bffe569d985207a1e5f98e51901cfb..d7b310c8a07cfa99afb284a8c8ee99d44d5d5028 100644 (file)
@@ -1,4 +1,4 @@
-if exists("g:loaded_notmuch_rb")
+if exists("g:loaded_notmuch")
        finish
 endif
 
@@ -6,15 +6,16 @@ if !has("ruby") || version < 700
        finish
 endif
 
-let g:loaded_notmuch_rb = "yep"
+let g:loaded_notmuch = "yep"
 
-let g:notmuch_rb_folders_maps = {
+let g:notmuch_folders_maps = {
        \ '<Enter>':    'folders_show_search()',
        \ 's':          'folders_search_prompt()',
        \ '=':          'folders_refresh()',
+       \ 'c':          'compose()',
        \ }
 
-let g:notmuch_rb_search_maps = {
+let g:notmuch_search_maps = {
        \ 'q':          'kill_this_buffer()',
        \ '<Enter>':    'search_show_thread(1)',
        \ '<Space>':    'search_show_thread(2)',
@@ -24,9 +25,10 @@ let g:notmuch_rb_search_maps = {
        \ 's':          'search_search_prompt()',
        \ '=':          'search_refresh()',
        \ '?':          'search_info()',
+       \ 'c':          'compose()',
        \ }
 
-let g:notmuch_rb_show_maps = {
+let g:notmuch_show_maps = {
        \ 'q':          'kill_this_buffer()',
        \ 'A':          'show_tag("-inbox -unread")',
        \ 'I':          'show_tag("-unread")',
@@ -34,56 +36,38 @@ let g:notmuch_rb_show_maps = {
        \ 'o':          'show_open_msg()',
        \ 'e':          'show_extract_msg()',
        \ 's':          'show_save_msg()',
+       \ 'p':          'show_save_patches()',
        \ 'r':          'show_reply()',
        \ '?':          'show_info()',
        \ '<Tab>':      'show_next_msg()',
+       \ 'c':          'compose()',
        \ }
 
-let g:notmuch_rb_compose_maps = {
+let g:notmuch_compose_maps = {
        \ ',s':         'compose_send()',
        \ ',q':         'compose_quit()',
        \ }
 
-let s:notmuch_rb_folders_default = [
+let s:notmuch_folders_default = [
        \ [ 'new', 'tag:inbox and tag:unread' ],
        \ [ 'inbox', 'tag:inbox' ],
        \ [ 'unread', 'tag:unread' ],
        \ ]
 
-let s:notmuch_rb_date_format_default = '%d.%m.%y'
-let s:notmuch_rb_datetime_format_default = '%d.%m.%y %H:%M:%S'
-let s:notmuch_rb_reader_default = 'xfce4-terminal -e "mutt -f %s"'
-let s:notmuch_rb_sendmail_default = 'sendmail'
-let s:notmuch_rb_folders_count_threads_default = 0
-
-if !exists('g:notmuch_rb_date_format')
-       let g:notmuch_rb_date_format = s:notmuch_rb_date_format_default
-endif
-
-if !exists('g:notmuch_rb_datetime_format')
-       let g:notmuch_rb_datetime_format = s:notmuch_rb_datetime_format_default
-endif
-
-if !exists('g:notmuch_rb_reader')
-       let g:notmuch_rb_reader = s:notmuch_rb_reader_default
-endif
-
-if !exists('g:notmuch_rb_sendmail')
-       let g:notmuch_rb_sendmail = s:notmuch_rb_sendmail_default
-endif
-
-if !exists('g:notmuch_rb_folders_count_threads')
-       let g:notmuch_rb_folders_count_threads = s:notmuch_rb_folders_count_threads_default
-endif
+let s:notmuch_date_format_default = '%d.%m.%y'
+let s:notmuch_datetime_format_default = '%d.%m.%y %H:%M:%S'
+let s:notmuch_reader_default = 'mutt -f %s'
+let s:notmuch_sendmail_default = 'sendmail'
+let s:notmuch_folders_count_threads_default = 0
 
 function! s:new_file_buffer(type, fname)
        exec printf('edit %s', a:fname)
        execute printf('set filetype=notmuch-%s', a:type)
        execute printf('set syntax=notmuch-%s', a:type)
-       ruby $buf_queue.push($curbuf.number)
+       ruby $curbuf.init(VIM::evaluate('a:type'))
 endfunction
 
-function! s:compose_unload()
+function! s:on_compose_delete()
        if b:compose_done
                return
        endif
@@ -141,8 +125,16 @@ endfunction
 function! s:show_reply()
        ruby open_reply get_message.mail
        let b:compose_done = 0
-       call s:set_map(g:notmuch_rb_compose_maps)
-       autocmd BufUnload <buffer> call s:compose_unload()
+       call s:set_map(g:notmuch_compose_maps)
+       autocmd BufDelete <buffer> call s:on_compose_delete()
+       startinsert!
+endfunction
+
+function! s:compose()
+       ruby open_compose
+       let b:compose_done = 0
+       call s:set_map(g:notmuch_compose_maps)
+       autocmd BufDelete <buffer> call s:on_compose_delete()
        startinsert!
 endfunction
 
@@ -166,7 +158,7 @@ function! s:show_open_msg()
 ruby << EOF
        m = get_message
        mbox = File.expand_path('~/.notmuch/vim_mbox')
-       cmd = VIM::evaluate('g:notmuch_rb_reader') % mbox
+       cmd = VIM::evaluate('g:notmuch_reader') % mbox
        system "notmuch show --format=mbox id:#{m.message_id} > #{mbox} && #{cmd}"
 EOF
 endfunction
@@ -180,6 +172,20 @@ ruby << EOF
 EOF
 endfunction
 
+function! s:show_save_patches()
+ruby << EOF
+       q = $curbuf.query($cur_thread)
+       t = q.search_threads.first
+       n = 0
+       t.toplevel_messages.first.replies.each do |m|
+               next if not m['subject'] =~ /^\[PATCH.*\]/
+               file = "%04d.patch" % [n += 1]
+               system "notmuch show --format=mbox id:#{m.message_id} > #{file}"
+       end
+       vim_puts "Saved #{n} patches"
+EOF
+endfunction
+
 function! s:show_tag(intags)
        if empty(a:intags)
                let tags = input('tags: ')
@@ -192,9 +198,13 @@ endfunction
 
 function! s:search_search_prompt()
        let text = input('Search: ')
+       if text == ""
+         return
+       endif
        setlocal modifiable
 ruby << EOF
        $cur_search = VIM::evaluate('text')
+       $curbuf.reopen
        search_render($cur_search)
 EOF
        setlocal nomodifiable
@@ -206,6 +216,7 @@ endfunction
 
 function! s:search_refresh()
        setlocal modifiable
+       ruby $curbuf.reopen
        ruby search_render($cur_search)
        setlocal nomodifiable
 endfunction
@@ -218,7 +229,6 @@ function! s:search_tag(intags)
        endif
        ruby do_tag(get_thread_id, VIM::evaluate('l:tags'))
        norm j
-       call s:search_refresh()
 endfunction
 
 function! s:folders_search_prompt()
@@ -228,6 +238,7 @@ endfunction
 
 function! s:folders_refresh()
        setlocal modifiable
+       ruby $curbuf.reopen
        ruby folders_render()
        setlocal nomodifiable
 endfunction
@@ -255,11 +266,9 @@ function! s:show_next_thread()
 endfunction
 
 function! s:kill_this_buffer()
-       bdelete!
 ruby << EOF
-       $buf_queue.pop
-       b = $buf_queue.last
-       VIM::command("buffer #{b}") if b
+       $curbuf.close
+       VIM::command("bdelete!")
 EOF
 endfunction
 
@@ -277,7 +286,7 @@ function! s:new_buffer(type)
        keepjumps 0d
        execute printf('set filetype=notmuch-%s', a:type)
        execute printf('set syntax=notmuch-%s', a:type)
-       ruby $buf_queue.push($curbuf.number)
+       ruby $curbuf.init(VIM::evaluate('a:type'))
 endfunction
 
 function! s:set_menu_buffer()
@@ -296,33 +305,31 @@ ruby << EOF
        $cur_thread = thread_id
        $messages.clear
        $curbuf.render do |b|
-               do_read do |db|
-                       q = db.query(get_cur_view)
-                       q.sort = 0
-                       msgs = q.search_messages
-                       msgs.each do |msg|
-                               m = Mail.read(msg.filename)
-                               part = m.find_first_text
-                               nm_m = Message.new(msg, m)
-                               $messages << nm_m
-                               date_fmt = VIM::evaluate('g:notmuch_rb_datetime_format')
-                               date = Time.at(msg.date).strftime(date_fmt)
-                               nm_m.start = b.count
-                               b << "%s %s (%s)" % [msg['from'], date, msg.tags]
-                               b << "Subject: %s" % [msg['subject']]
-                               b << "To: %s" % m['to']
-                               b << "Cc: %s" % m['cc']
-                               b << "Date: %s" % m['date']
-                               nm_m.body_start = b.count
-                               b << "--- %s ---" % part.mime_type
-                               part.convert.each_line do |l|
-                                       b << l.chomp
-                               end
-                               b << ""
-                               nm_m.end = b.count
+               q = $curbuf.query(get_cur_view)
+               q.sort = Notmuch::SORT_OLDEST_FIRST
+               msgs = q.search_messages
+               msgs.each do |msg|
+                       m = Mail.read(msg.filename)
+                       part = m.find_first_text
+                       nm_m = Message.new(msg, m)
+                       $messages << nm_m
+                       date_fmt = VIM::evaluate('g:notmuch_datetime_format')
+                       date = Time.at(msg.date).strftime(date_fmt)
+                       nm_m.start = b.count
+                       b << "%s %s (%s)" % [msg['from'], date, msg.tags]
+                       b << "Subject: %s" % [msg['subject']]
+                       b << "To: %s" % msg['to']
+                       b << "Cc: %s" % msg['cc']
+                       b << "Date: %s" % msg['date']
+                       nm_m.body_start = b.count
+                       b << "--- %s ---" % part.mime_type
+                       part.convert.each_line do |l|
+                               b << l.chomp
                        end
-                       b.delete(b.count)
+                       b << ""
+                       nm_m.end = b.count
                end
+               b.delete(b.count)
        end
        $messages.each_with_index do |msg, i|
                VIM::command("syntax region nmShowMsg#{i}Desc start='\\%%%il' end='\\%%%il' contains=@nmShowMsgDesc" % [msg.start, msg.start + 1])
@@ -331,7 +338,7 @@ ruby << EOF
        end
 EOF
        setlocal nomodifiable
-       call s:set_map(g:notmuch_rb_show_maps)
+       call s:set_map(g:notmuch_show_maps)
 endfunction
 
 function! s:search_show_thread(mode)
@@ -354,7 +361,7 @@ ruby << EOF
        search_render($cur_search)
 EOF
        call s:set_menu_buffer()
-       call s:set_map(g:notmuch_rb_search_maps)
+       call s:set_map(g:notmuch_search_maps)
        autocmd CursorMoved <buffer> call s:show_cursor_moved()
 endfunction
 
@@ -370,46 +377,93 @@ function! s:folders()
        call s:new_buffer('folders')
        ruby folders_render()
        call s:set_menu_buffer()
-       call s:set_map(g:notmuch_rb_folders_maps)
+       call s:set_map(g:notmuch_folders_maps)
 endfunction
 
 "" root
 
 function! s:set_defaults()
-       if exists('g:notmuch_rb_custom_search_maps')
-               call extend(g:notmuch_rb_search_maps, g:notmuch_rb_custom_search_maps)
+       if !exists('g:notmuch_date_format')
+               if exists('g:notmuch_rb_date_format')
+                       let g:notmuch_date_format = g:notmuch_rb_date_format
+               else
+                       let g:notmuch_date_format = s:notmuch_date_format_default
+               endif
        endif
 
-       if exists('g:notmuch_rb_custom_show_maps')
-               call extend(g:notmuch_rb_show_maps, g:notmuch_rb_custom_show_maps)
+       if !exists('g:notmuch_datetime_format')
+               if exists('g:notmuch_rb_datetime_format')
+                       let g:notmuch_datetime_format = g:notmuch_rb_datetime_format
+               else
+                       let g:notmuch_datetime_format = s:notmuch_datetime_format_default
+               endif
        endif
 
-       " TODO for now lets check the old folders too
-       if !exists('g:notmuch_rb_folders')
-               if exists('g:notmuch_folders')
-                       let g:notmuch_rb_folders = g:notmuch_folders
+       if !exists('g:notmuch_reader')
+               if exists('g:notmuch_rb_reader')
+                       let g:notmuch_reader = g:notmuch_rb_reader
                else
-                       let g:notmuch_rb_folders = s:notmuch_rb_folders_default
+                       let g:notmuch_reader = s:notmuch_reader_default
+               endif
+       endif
+
+       if !exists('g:notmuch_sendmail')
+               if exists('g:notmuch_rb_sendmail')
+                       let g:notmuch_sendmail = g:notmuch_rb_sendmail
+               else
+                       let g:notmuch_sendmail = s:notmuch_sendmail_default
+               endif
+       endif
+
+       if !exists('g:notmuch_folders_count_threads')
+               if exists('g:notmuch_rb_count_threads')
+                       let g:notmuch_count_threads = g:notmuch_rb_count_threads
+               else
+                       let g:notmuch_folders_count_threads = s:notmuch_folders_count_threads_default
+               endif
+       endif
+
+       if !exists('g:notmuch_custom_search_maps') && exists('g:notmuch_rb_custom_search_maps')
+               let g:notmuch_custom_search_maps = g:notmuch_rb_custom_search_maps
+       endif
+
+       if !exists('g:notmuch_custom_show_maps') && exists('g:notmuch_rb_custom_show_maps')
+               let g:notmuch_custom_show_maps = g:notmuch_rb_custom_show_maps
+       endif
+
+       if exists('g:notmuch_custom_search_maps')
+               call extend(g:notmuch_search_maps, g:notmuch_custom_search_maps)
+       endif
+
+       if exists('g:notmuch_custom_show_maps')
+               call extend(g:notmuch_show_maps, g:notmuch_custom_show_maps)
+       endif
+
+       if !exists('g:notmuch_folders')
+               if exists('g:notmuch_rb_folders')
+                       let g:notmuch_folders = g:notmuch_rb_folders
+               else
+                       let g:notmuch_folders = s:notmuch_folders_default
                endif
        endif
 endfunction
 
-function! s:NotMuch()
+function! s:NotMuch(...)
        call s:set_defaults()
 
 ruby << EOF
        require 'notmuch'
        require 'rubygems'
        require 'tempfile'
+       require 'socket'
        begin
                require 'mail'
        rescue LoadError
        end
 
        $db_name = nil
-       $email_address = nil
+       $email = $email_name = $email_address = nil
        $searches = []
-       $buf_queue = []
        $threads = []
        $messages = []
        $config = {}
@@ -432,7 +486,9 @@ ruby << EOF
                end
 
                $db_name = $config['database.path']
-               $email_address = "%s <%s>" % [$config['user.name'], $config['user.primary_email']]
+               $email_name = $config['user.name']
+               $email_address = $config['user.primary_email']
+               $email = "%s <%s>" % [$email_name, $email_address]
        end
 
        def vim_puts(s)
@@ -470,111 +526,128 @@ ruby << EOF
                end
        end
 
-       def do_write
-               db = Notmuch::Database.new($db_name, :mode => Notmuch::MODE_READ_WRITE)
-               begin
-                       yield db
-               ensure
-                       db.close
-               end
+       def generate_message_id
+               t = Time.now
+               random_tag = sprintf('%x%x_%x%x%x',
+                       t.to_i, t.tv_usec,
+                       $$, Thread.current.object_id.abs, rand(255))
+               return "<#{random_tag}@#{Socket.gethostname}.notmuch>"
        end
 
-       def do_read
-               db = Notmuch::Database.new($db_name)
-               begin
-                       yield db
-               ensure
-                       db.close
-               end
-       end
-
-       def open_reply(orig)
+       def open_compose_helper(lines, cur)
                help_lines = [
                        'Notmuch-Help: Type in your message here; to help you use these bindings:',
                        'Notmuch-Help:   ,s    - send the message (Notmuch-Help lines will be removed)',
                        'Notmuch-Help:   ,q    - abort the message',
                        ]
+
+               dir = File.expand_path('~/.notmuch/compose')
+               FileUtils.mkdir_p(dir)
+               Tempfile.open(['nm-', '.mail'], dir) do |f|
+                       f.puts(help_lines)
+                       f.puts
+                       f.puts(lines)
+
+                       sig_file = File.expand_path('~/.signature')
+                       if File.exists?(sig_file)
+                               f.puts("-- ")
+                               f.write(File.read(sig_file))
+                       end
+
+                       f.flush
+
+                       cur += help_lines.size + 1
+
+                       VIM::command("let s:reply_from='%s'" % $email_address)
+                       VIM::command("call s:new_file_buffer('compose', '#{f.path}')")
+                       VIM::command("call cursor(#{cur}, 0)")
+               end
+       end
+
+       def open_reply(orig)
                reply = orig.reply do |m|
                        # fix headers
                        if not m[:reply_to]
                                m.to = [orig[:from].to_s, orig[:to].to_s]
                        end
                        m.cc = orig[:cc]
-                       m.from = $email_address
+                       m.from = $email
+                       m.message_id = generate_message_id
                        m.charset = 'utf-8'
                        m.content_transfer_encoding = '7bit'
                end
 
-               dir = File.expand_path('~/.notmuch/compose')
-               FileUtils.mkdir_p(dir)
-               Tempfile.open(['nm-', '.mail'], dir) do |f|
-                       lines = []
-
-                       lines += help_lines
-                       lines << ''
-
-                       body_lines = []
-                       if $mail_installed
-                               addr = Mail::Address.new(orig[:from].value)
-                               name = addr.name
-                               name = addr.local + "@" if name.nil? && !addr.local.nil?
-                       else
-                               name = orig[:from]
-                       end
-                       name = "somebody" if name.nil?
+               lines = []
 
-                       body_lines << "%s wrote:" % name
-                       part = orig.find_first_text
-                       part.convert.each_line do |l|
-                               body_lines << "> %s" % l.chomp
-                       end
-                       body_lines << ""
-                       body_lines << ""
-                       body_lines << ""
-
-                       reply.body = body_lines.join("\n")
+               body_lines = []
+               if $mail_installed
+                       addr = Mail::Address.new(orig[:from].value)
+                       name = addr.name
+                       name = addr.local + "@" if name.nil? && !addr.local.nil?
+               else
+                       name = orig[:from]
+               end
+               name = "somebody" if name.nil?
 
-                       lines += reply.to_s.lines.map { |e| e.chomp }
-                       lines << ""
+               body_lines << "%s wrote:" % name
+               part = orig.find_first_text
+               part.convert.each_line do |l|
+                       body_lines << "> %s" % l.chomp
+               end
+               body_lines << ""
+               body_lines << ""
+               body_lines << ""
 
-                       old_count = lines.count - 1
+               reply.body = body_lines.join("\n")
 
-                       f.puts(lines)
+               lines += reply.to_s.lines.map { |e| e.chomp }
+               lines << ""
 
-                       sig_file = File.expand_path('~/.signature')
-                       if File.exists?(sig_file)
-                               f.puts("-- ")
-                               f.write(File.read(sig_file))
-                       end
+               cur = lines.count - 1
 
-                       f.flush
+               open_compose_helper(lines, cur)
+       end
 
-                       VIM::command("let s:reply_from='%s'" % reply.from.first.to_s)
-                       VIM::command("call s:new_file_buffer('compose', '#{f.path}')")
-                       VIM::command("call cursor(#{old_count}, 0)")
-               end
+       def open_compose()
+               lines = []
+
+               lines << "Date: #{Time.now().strftime('%a, %-d %b %Y %T %z')}"
+               lines << "From: #{$email}"
+               lines << "To: "
+               cur = lines.count
+
+               lines << "Cc: "
+               lines << "Bcc: "
+               lines << "Message-Id: #{generate_message_id}"
+               lines << "Subject: "
+               lines << "Mime-Version: 1.0"
+               lines << "Content-Type: text/plain; charset=utf-8"
+               lines << "Content-Transfer-Encoding: 7bit"
+               lines << ""
+               lines << ""
+               lines << ""
+
+               open_compose_helper(lines, cur)
        end
 
        def folders_render()
                $curbuf.render do |b|
-                       folders = VIM::evaluate('g:notmuch_rb_folders')
-                       count_threads = VIM::evaluate('g:notmuch_rb_folders_count_threads')
+                       folders = VIM::evaluate('g:notmuch_folders')
+                       count_threads = VIM::evaluate('g:notmuch_folders_count_threads')
                        $searches.clear
-                       do_read do |db|
-                               folders.each do |name, search|
-                                       q = db.query(search)
-                                       $searches << search
-                                       count = count_threads ? q.search_threads.count : q.search_messages.count
-                                       b << "%9d %-20s (%s)" % [count, name, search]
-                               end
+                       folders.each do |name, search|
+                               q = $curbuf.query(search)
+                               $searches << search
+                               count = count_threads ? q.search_threads.count : q.search_messages.count
+                               b << "%9d %-20s (%s)" % [count, name, search]
                        end
                end
        end
 
        def search_render(search)
-               date_fmt = VIM::evaluate('g:notmuch_rb_date_format')
-               db = Notmuch::Database.new($db_name)
-               q = db.query(search)
+               date_fmt = VIM::evaluate('g:notmuch_date_format')
+               q = $curbuf.query(search)
+               q.sort = Notmuch::SORT_NEWEST_FIRST
                $threads.clear
                t = q.search_threads
 
@@ -582,10 +655,11 @@ ruby << EOF
                        items.each do |e|
                                authors = e.authors.to_utf8.split(/[,|]/).map { |a| author_filter(a) }.join(",")
                                date = Time.at(e.newest_date).strftime(date_fmt)
+                               subject = e.messages.first['subject']
                                if $mail_installed
-                                       subject = Mail::Field.new("Subject: " + e.subject).to_s
+                                       subject = Mail::Field.new("Subject: " + subject).to_s
                                else
-                                       subject = e.subject.force_encoding('utf-8')
+                                       subject = subject.force_encoding('utf-8')
                                end
                                b << "%-12s %3s %-20.20s | %s (%s)" % [date, e.matched_messages, authors, subject, e.tags]
                                $threads << e.thread_id
@@ -594,7 +668,7 @@ ruby << EOF
        end
 
        def do_tag(filter, tags)
-               do_write do |db|
+               $curbuf.do_write do |db|
                        q = db.query(filter)
                        q.search_messages.each do |e|
                                e.freeze
@@ -611,6 +685,40 @@ ruby << EOF
                                e.thaw
                                e.tags_to_maildir_flags
                        end
+                       q.destroy!
+               end
+       end
+
+       module DbHelper
+               def init(name)
+                       @name = name
+                       @db = Notmuch::Database.new($db_name)
+                       @queries = []
+               end
+
+               def query(*args)
+                       q = @db.query(*args)
+                       @queries << q
+                       q
+               end
+
+               def close
+                       @queries.delete_if { |q| ! q.destroy! }
+                       @db.close
+               end
+
+               def reopen
+                       close if @db
+                       @db = Notmuch::Database.new($db_name)
+               end
+
+               def do_write
+                       db = Notmuch::Database.new($db_name, :mode => Notmuch::MODE_READ_WRITE)
+                       begin
+                               yield db
+                       ensure
+                               db.close
+                       end
                end
        end
 
@@ -659,6 +767,8 @@ ruby << EOF
        end
 
        class VIM::Buffer
+               include DbHelper
+
                def <<(a)
                        append(count(), a)
                end
@@ -828,9 +938,13 @@ ruby << EOF
 
        get_config
 EOF
-       call s:folders()
+       if a:0
+         call s:search(join(a:000))
+       else
+         call s:folders()
+       endif
 endfunction
 
-command NotMuch :call s:NotMuch()
+command -nargs=* NotMuch call s:NotMuch(<f-args>)
 
 " vim: set noexpandtab:
index 6d36810543c4b84b6d58fd5f786281ab348bbe9c..6f3b705c35a124c5dd2d7f2e8db2043b03571286 100644 (file)
@@ -2,6 +2,7 @@ addon: notmuch
 description: "notmuch mail user interface"
 files:
   - plugin/notmuch.vim
+  - doc/notmuch.txt
   - syntax/notmuch-compose.vim
   - syntax/notmuch-folders.vim
   - syntax/notmuch-git-diff.vim