merge changes from upstream

This commit is contained in:
Jameson Graef Rollins 2009-12-05 01:19:53 -05:00
commit e72a6176e3
26 changed files with 958 additions and 357 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
Makefile.config
TAGS TAGS
tags tags
*cscope* *cscope*

View file

@ -1,6 +1,5 @@
# Default FLAGS, (can be overridden by user such as "make CFLAGS=-O2") WARN_CXXFLAGS=-Wall -Wextra -Wwrite-strings -Wswitch-enum
WARN_FLAGS=-Wall -Wextra -Wmissing-declarations -Wwrite-strings -Wswitch-enum WARN_CFLAGS=$(WARN_CXXFLAGS) -Wmissing-declarations
CFLAGS=-O2
# Additional programs that are used during the compilation process. # Additional programs that are used during the compilation process.
EMACS ?= emacs EMACS ?= emacs
@ -8,66 +7,69 @@ EMACS ?= emacs
# arguments to gzip. # arguments to gzip.
gzip = gzip gzip = gzip
# Additional flags that we will append to whatever the user set. bash_completion_dir = /etc/bash_completion.d
# These aren't intended for the user to manipulate.
extra_cflags := $(shell pkg-config --cflags glib-2.0 gmime-2.4 talloc)
extra_cxxflags := $(shell xapian-config --cxxflags)
emacs_lispdir := $(shell pkg-config emacs --variable sitepkglispdir)
# Hard-code if this system doesn't have an emacs.pc file
ifeq ($(emacs_lispdir),)
emacs_lispdir = $(prefix)/share/emacs/site-lisp
endif
all_deps = Makefile Makefile.local Makefile.config \ all_deps = Makefile Makefile.local Makefile.config \
lib/Makefile lib/Makefile.local lib/Makefile lib/Makefile.local
extra_cflags :=
extra_cxxflags :=
# Now smash together user's values with our extra values # Now smash together user's values with our extra values
override CFLAGS += $(WARN_FLAGS) $(extra_cflags) FINAL_CFLAGS = $(CFLAGS) $(WARN_CFLAGS) $(CONFIGURE_CFLAGS) $(extra_cflags)
override CXXFLAGS += $(WARN_FLAGS) $(extra_cflags) $(extra_cxxflags) FINAL_CXXFLAGS = $(CXXFLAGS) $(WARN_CXXFLAGS) $(CONFIGURE_CXXFLAGS) $(extra_cflags) $(extra_cxxflags)
FINAL_LDFLAGS = $(LDFLAGS) $(CONFIGURE_LDFLAGS)
override LDFLAGS += \ all: notmuch notmuch.1.gz
$(shell pkg-config --libs glib-2.0 gmime-2.4 talloc) \
$(shell xapian-config --libs)
# Include our local Makefile.local first so that its first target is default # Before including any other Makefile fragments, get settings from the
include Makefile.local # output of configure
include lib/Makefile.local Makefile.config: configure
@echo ""
@echo "Note: Calling ./configure with no command-line arguments. This is often fine,"
@echo " but if you want to specify any arguments (such as an alternate prefix"
@echo " into which to install), call ./configure explicitly and then make again."
@echo " See \"./configure --help\" for more details."
@echo ""
./configure
# And get user settings from the output of configure
include Makefile.config include Makefile.config
include lib/Makefile.local
include compat/Makefile.local
include Makefile.local
# The user has not set any verbosity, default to quiet mode and inform the # The user has not set any verbosity, default to quiet mode and inform the
# user how to enable verbose compiles. # user how to enable verbose compiles.
ifeq ($(V),) ifeq ($(V),)
quiet_DOC := "Use \"$(MAKE) V=1\" to see the verbose compile lines.\n" quiet_DOC := "Use \"$(MAKE) V=1\" to see the verbose compile lines.\n"
quiet = @echo $(quiet_DOC)$(eval quiet_DOC:=)" $1 $@"; $($1) quiet = @printf $(quiet_DOC)$(eval quiet_DOC:=)" $1 $2 $@\n"; $($1)
endif endif
# The user has explicitly enabled quiet compilation. # The user has explicitly enabled quiet compilation.
ifeq ($(V),0) ifeq ($(V),0)
quiet = @echo " $1 $@"; $($1) quiet = @printf " $1 $@\n"; $($1)
endif endif
# Otherwise, print the full command line. # Otherwise, print the full command line.
quiet ?= $($1) quiet ?= $($1)
%.o: %.cc $(all_deps) %.o: %.cc $(all_deps)
$(call quiet,CXX) -c $(CXXFLAGS) $< -o $@ $(call quiet,CXX,$(CXXFLAGS)) -c $(FINAL_CXXFLAGS) $< -o $@
%.o: %.c $(all_deps) %.o: %.c $(all_deps)
$(call quiet,CC) -c $(CFLAGS) $< -o $@ $(call quiet,CC,$(CFLAGS)) -c $(FINAL_CFLAGS) $< -o $@
%.elc: %.el %.elc: %.el
$(call quiet,EMACS) -batch -f batch-byte-compile $< $(call quiet,EMACS) -batch -f batch-byte-compile $<
.deps/%.d: %.c $(all_deps) .deps/%.d: %.c $(all_deps)
@set -e; rm -f $@; mkdir -p $$(dirname $@) ; \ @set -e; rm -f $@; mkdir -p $$(dirname $@) ; \
$(CC) -M $(CPPFLAGS) $(CFLAGS) $< > $@.$$$$; \ $(CC) -M $(CPPFLAGS) $(FINAL_CFLAGS) $< > $@.$$$$ 2>/dev/null ; \
sed 's,'$$(basename $*)'\.o[ :]*,$*.o $@ : ,g' < $@.$$$$ > $@; \ sed 's,'$$(basename $*)'\.o[ :]*,$*.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$ rm -f $@.$$$$
.deps/%.d: %.cc $(all_deps) .deps/%.d: %.cc $(all_deps)
@set -e; rm -f $@; mkdir -p $$(dirname $@) ; \ @set -e; rm -f $@; mkdir -p $$(dirname $@) ; \
$(CXX) -M $(CPPFLAGS) $(CXXFLAGS) $< > $@.$$$$; \ $(CXX) -M $(CPPFLAGS) $(FINAL_CXXFLAGS) $< > $@.$$$$ 2>/dev/null ; \
sed 's,'$$(basename $*)'\.o[ :]*,$*.o $@ : ,g' < $@.$$$$ > $@; \ sed 's,'$$(basename $*)'\.o[ :]*,$*.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$ rm -f $@.$$$$

View file

@ -1,8 +1,7 @@
all: notmuch notmuch.1.gz
emacs: notmuch.elc emacs: notmuch.elc
notmuch_client_srcs = \ notmuch_client_srcs = \
$(notmuch_compat_srcs) \
debugger.c \ debugger.c \
gmime-filter-reply.c \ gmime-filter-reply.c \
notmuch.c \ notmuch.c \
@ -23,21 +22,18 @@ notmuch_client_srcs = \
notmuch_client_modules = $(notmuch_client_srcs:.c=.o) notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
notmuch: $(notmuch_client_modules) lib/notmuch.a notmuch: $(notmuch_client_modules) lib/notmuch.a
$(call quiet,CXX) $^ $(LDFLAGS) -o $@ $(call quiet,CXX,$(LDFLAGS)) $^ $(FINAL_LDFLAGS) -o $@
notmuch.1.gz: notmuch.1 notmuch.1.gz: notmuch.1
$(call quiet,gzip) --stdout $^ > $@ $(call quiet,gzip) --stdout $^ > $@
install: all notmuch.1.gz install: all notmuch.1.gz
for d in $(DESTDIR)$(prefix)/bin/ $(DESTDIR)$(prefix)/share/man/man1 \ for d in $(DESTDIR)$(prefix)/bin/ $(DESTDIR)$(prefix)/share/man/man1 ; \
$(DESTDIR)$(bash_completion_dir) ; \
do \ do \
install -d $$d ; \ install -d $$d ; \
done ; done ;
install notmuch $(DESTDIR)$(prefix)/bin/ install notmuch $(DESTDIR)$(prefix)/bin/
install -m0644 notmuch.1.gz $(DESTDIR)$(prefix)/share/man/man1/ install -m0644 notmuch.1.gz $(DESTDIR)$(prefix)/share/man/man1/
install -m0644 contrib/notmuch-completion.bash \
$(DESTDIR)$(bash_completion_dir)/notmuch
install-emacs: install emacs install-emacs: install emacs
for d in $(DESTDIR)/$(emacs_lispdir) ; \ for d in $(DESTDIR)/$(emacs_lispdir) ; \
@ -47,5 +43,10 @@ install-emacs: install emacs
install -m0644 notmuch.el $(DESTDIR)$(emacs_lispdir) install -m0644 notmuch.el $(DESTDIR)$(emacs_lispdir)
install -m0644 notmuch.elc $(DESTDIR)$(emacs_lispdir) install -m0644 notmuch.elc $(DESTDIR)$(emacs_lispdir)
install-bash:
install -d $(DESTDIR)$(bash_completion_dir)
install -m0644 contrib/notmuch-completion.bash \
$(DESTDIR)$(bash_completion_dir)/notmuch
SRCS := $(SRCS) $(notmuch_client_srcs) SRCS := $(SRCS) $(notmuch_client_srcs)
CLEAN := $(CLEAN) notmuch $(notmuch_client_modules) notmuch.elc notmuch.1.gz CLEAN := $(CLEAN) notmuch $(notmuch_client_modules) notmuch.elc notmuch.1.gz

31
TODO
View file

@ -8,9 +8,6 @@ Fix the things that are causing the most pain to new users
Emacs interface (notmuch.el) Emacs interface (notmuch.el)
---------------------------- ----------------------------
Make the keybindings help ('?') display the summary of each command's
documentation, not the function name.
Add a global keybinding table for notmuch, and then view-specific Add a global keybinding table for notmuch, and then view-specific
tables that add to it. tables that add to it.
@ -18,8 +15,6 @@ Add a command to archive all threads in a search view.
Add a '|' binding from the search view. Add a '|' binding from the search view.
Add a binding to run a search from notmuch-show-mode.
When a thread has been entirely read, start out by closing all When a thread has been entirely read, start out by closing all
messages except those that matched the search terms. messages except those that matched the search terms.
@ -41,10 +36,6 @@ Portability
----------- -----------
Fix configure script to test each compiler warning we want to use. Fix configure script to test each compiler warning we want to use.
Implement strndup locally (or call talloc_strndup instead).
Implement getline locally, (look at gnulib).
Completion Completion
---------- ----------
Fix bash completion to complete multiple search options (both --first Fix bash completion to complete multiple search options (both --first
@ -53,6 +44,11 @@ and *then* --max-threads), and also complete value for --sort=
notmuch command-line tool notmuch command-line tool
------------------------- -------------------------
Fix "notmuch show" so that the UI doesn't fail to show a thread that
is visible in a search buffer, but happens to no longer match the
current search. (Perhaps add a --matching=<secondary-search-terms>
option (or similar) to "notmuch show".)
Teach "notmuch search" to return many different kinds of results. Some Teach "notmuch search" to return many different kinds of results. Some
ideas: ideas:
@ -72,6 +68,10 @@ Give "notmuch restore" some progress indicator. Until we get the
Xapian bugs fixed that are making this operation slow, we really need Xapian bugs fixed that are making this operation slow, we really need
to let the user know that things are still moving. to let the user know that things are still moving.
Fix "notmuch restore" to operate in a single pass much like "notmuch
dump" does, rather than doing N searches into the database, each
matching 1/N messages.
Add a "-f <filename>" option to select an alternate configuration Add a "-f <filename>" option to select an alternate configuration
file. file.
@ -80,10 +80,6 @@ relative to the database path. (Otherwise, moving the database to a
new directory will result in notmuch creating new timestamp documents new directory will result in notmuch creating new timestamp documents
and leaving stale ones behind.) and leaving stale ones behind.)
Ensure that "notmuch new" is sane if its first, giant indexing session
gets interrupted, (that is, ensure that any results indexed so far are
flushed).
Fix notmuch.c to use a DIR prefix for directory timestamps, (the idea Fix notmuch.c to use a DIR prefix for directory timestamps, (the idea
being that it can then add other non-directory timestamps such as for being that it can then add other non-directory timestamps such as for
noting how far back in the past mail has been indexed, and whether it noting how far back in the past mail has been indexed, and whether it
@ -104,10 +100,12 @@ indexing.
notmuch library notmuch library
--------------- ---------------
Index content from citations, please.
Provide a sane syntax for date ranges. First, we don't want to require Provide a sane syntax for date ranges. First, we don't want to require
both endpoints to be specified. For example it would be nice to be both endpoints to be specified. For example it would be nice to be
able to say things like "since:2009-01-1" or "until:2009-01-1" and able to say things like "since:2009-01-1" or "until:2009-01-1" and
have the other enpoint be implicit. Second we'de like to support have the other endpoint be implicit. Second we'd like to support
relative specifications of time such as "since:'2 months ago'". To do relative specifications of time such as "since:'2 months ago'". To do
any of this we're probably going to need to break down an write our any of this we're probably going to need to break down an write our
own parser for the query string rather than using Xapian's QueryParser own parser for the query string rather than using Xapian's QueryParser
@ -132,11 +130,6 @@ Add support for configuring "virtual tags" which are a tuple of
(tag-name, search-specification). The database is responsible for (tag-name, search-specification). The database is responsible for
ensuring that the virtual tag is always consistent. ensuring that the virtual tag is always consistent.
Think about optimizing chunked searches (max-threads > 0) to avoid
repeating work. That would be saving state from the previous chunk and
reusing it if the next search is the next chunk with the same search
string.
General General
------- -------
Audit everything for dealing with out-of-memory (and drop xutil.c). Audit everything for dealing with out-of-memory (and drop xutil.c).

5
compat/Makefile Normal file
View file

@ -0,0 +1,5 @@
all:
$(MAKE) -C .. all
clean:
$(MAKE) -C .. clean

8
compat/Makefile.local Normal file
View file

@ -0,0 +1,8 @@
dir=compat
extra_cflags += -I$(dir)
notmuch_compat_srcs =
ifneq ($(HAVE_GETLINE),1)
notmuch_compat_srcs += $(dir)/getline.c $(dir)/getdelim.c
endif

41
compat/compat.h Normal file
View file

@ -0,0 +1,41 @@
/* notmuch - Not much of an email library, (just index and search)
*
* Copyright © 2009 Carl Worth
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/ .
*
* Author: Carl Worth <cworth@cworth.org>
*/
/* This header file defines functions that will only be conditionally
* compiled for compatibility on systems that don't provide their own
* implementations of the functions.
*/
#ifndef NOTMUCH_COMPAT_H
#define NOTMUCH_COMPAT_H
#if !HAVE_GETLINE
#include <stdio.h>
#include <unistd.h>
ssize_t
getline (char **lineptr, size_t *n, FILE *stream);
ssize_t
getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp);
#endif /* !HAVE_GETLINE */
#endif /* NOTMUCH_COMPAT_H */

133
compat/getdelim.c Normal file
View file

@ -0,0 +1,133 @@
/* getdelim.c --- Implementation of replacement getdelim function.
Copyright (C) 1994, 1996, 1997, 1998, 2001, 2003, 2005, 2006, 2007,
2008, 2009 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 3, or (at
your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA. */
/* Ported from glibc by Simon Josefsson. */
#include "compat.h"
#include <stdio.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#ifndef SSIZE_MAX
# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
#endif
#if USE_UNLOCKED_IO
# include "unlocked-io.h"
# define getc_maybe_unlocked(fp) getc(fp)
#elif !HAVE_FLOCKFILE || !HAVE_FUNLOCKFILE || !HAVE_DECL_GETC_UNLOCKED
# undef flockfile
# undef funlockfile
# define flockfile(x) ((void) 0)
# define funlockfile(x) ((void) 0)
# define getc_maybe_unlocked(fp) getc(fp)
#else
# define getc_maybe_unlocked(fp) getc_unlocked(fp)
#endif
/* Read up to (and including) a DELIMITER from FP into *LINEPTR (and
NUL-terminate it). *LINEPTR is a pointer returned from malloc (or
NULL), pointing to *N characters of space. It is realloc'ed as
necessary. Returns the number of characters read (not including
the null terminator), or -1 on error or EOF. */
ssize_t
getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp)
{
ssize_t result = -1;
size_t cur_len = 0;
if (lineptr == NULL || n == NULL || fp == NULL)
{
errno = EINVAL;
return -1;
}
flockfile (fp);
if (*lineptr == NULL || *n == 0)
{
char *new_lineptr;
*n = 120;
new_lineptr = (char *) realloc (*lineptr, *n);
if (new_lineptr == NULL)
{
result = -1;
goto unlock_return;
}
*lineptr = new_lineptr;
}
for (;;)
{
int i;
i = getc_maybe_unlocked (fp);
if (i == EOF)
{
result = -1;
break;
}
/* Make enough space for len+1 (for final NUL) bytes. */
if (cur_len + 1 >= *n)
{
size_t needed_max =
SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX;
size_t needed = 2 * *n + 1; /* Be generous. */
char *new_lineptr;
if (needed_max < needed)
needed = needed_max;
if (cur_len + 1 >= needed)
{
result = -1;
errno = EOVERFLOW;
goto unlock_return;
}
new_lineptr = (char *) realloc (*lineptr, needed);
if (new_lineptr == NULL)
{
result = -1;
goto unlock_return;
}
*lineptr = new_lineptr;
*n = needed;
}
(*lineptr)[cur_len] = i;
cur_len++;
if (i == delimiter)
break;
}
(*lineptr)[cur_len] = '\0';
result = cur_len ? (ssize_t) cur_len : result;
unlock_return:
funlockfile (fp); /* doesn't set errno */
return result;
}

29
compat/getline.c Normal file
View file

@ -0,0 +1,29 @@
/* getline.c --- Implementation of replacement getline function.
Copyright (C) 2005, 2006, 2007 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 3, or (at
your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA. */
/* Written by Simon Josefsson. */
#include "compat.h"
#include <stdio.h>
ssize_t
getline (char **lineptr, size_t *n, FILE *stream)
{
return getdelim (lineptr, n, '\n', stream);
}

5
config/README Normal file
View file

@ -0,0 +1,5 @@
notmuch/config
This directory consists of small programs used by the notmuch
configure script to test for the availability of certain system
features, (library functions, etc.).

13
config/have_getline.c Normal file
View file

@ -0,0 +1,13 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
int main()
{
ssize_t count = 0;
size_t n = 0;
char **lineptr = NULL;
FILE *stream = NULL;
count = getline(lineptr, &n, stream);
}

233
configure vendored
View file

@ -1,12 +1,70 @@
#! /bin/sh #! /bin/sh
# defaults # Set several defaults (optionally specified by the user in
# environemnt variables)
CC=${CC:-gcc}
CXX=${CXX:-g++}
CFLAGS=${CFLAGS:--O2}
CXXFLAGS=${CXXFLAGS:-\$(CFLAGS)}
# Set the defaults for values the user can specify with command-line
# options.
PREFIX=/usr/local PREFIX=/usr/local
# option parsing usage ()
{
cat <<EOF
Usage: ./configure [options]...
This script configures notmuch to build on your system.
It verifies that dependencies are available, determines flags needed
to compile and link against various required libraries, and identifies
whether various system functions can be used or if locally-provided
replacements will be built instead.
Finally, it allows you to control various aspects of the build and
installation process.
First, some common variables can specified via environment variables:
CC The C compiler to use
CFLAGS Flags to pass to the C compiler
CXX The C++ compiler to use
CXXFLAGS Flags to pass to the C compiler
LDFLAGS Flags to pass when linking
Each of these values can further be controlled by specifying them
later on the "make" command line.
Additionally, various options can be specified on the configure
command line.
--prefix=PREFIX Install files in PREFIX [$PREFIX]
By default, "make install" will install the resulting program to
$PREFIX/bin, documentation to $PREFIX/share, etc. You can
specify an installation prefix other than $PREFIX using
--prefix, for instance:
./configure --prefix=\$HOME
EOF
}
# Parse command-line options
for option; do for option; do
if [ "${option%=*}" = '--prefix' ] ; then if [ "${option}" = '--help' ] ; then
usage
exit 0
elif [ "${option%%=*}" = '--prefix' ] ; then
PREFIX="${option#*=}" PREFIX="${option#*=}"
else
echo "Unrecognized option: ${option}."
echo "See:"
echo " $0 --help"
echo ""
exit 1
fi fi
done done
@ -17,14 +75,16 @@ We hope that the process of building and installing notmuch is quick
and smooth so that you can soon be reading and processing your email and smooth so that you can soon be reading and processing your email
more efficiently than ever. more efficiently than ever.
If anything goes wrong in this process, please do as much as you can If anything goes wrong in the configure process, you can override any
to figure out what could be different on your machine compared to decisions it makes by manually editing the Makefile.config file that
those of the notmuch developers. Then, please email those details to it creates. Also please do as much as you can to figure out what could
the Notmuch list (notmuch@notmuchmail.org) so that we can hopefully make be different on your machine compared to those of the notmuch
future versions of notmuch easier for you to use. developers. Then, please email those details to the Notmuch list
(notmuch@notmuchmail.org) so that we can hopefully make future
versions of notmuch easier for you to use.
We'll now investigate your system to find verify that various software We'll now investigate your system to verify that all required
components that notmuch relies on are available. dependencies are available:
EOF EOF
@ -36,48 +96,57 @@ else
have_pkg_config=0 have_pkg_config=0
fi fi
printf "Checking for Xapian development files... "
if xapian-config --version > /dev/null 2>&1; then if xapian-config --version > /dev/null 2>&1; then
echo "Checking for Xapian development files... Yes." printf "Yes.\n"
have_xapian=1 have_xapian=1
xapian_cxxflags=$(xapian-config --cxxflags)
xapian_ldflags=$(xapian-config --libs)
else else
echo "Checking for Xapian development files... No." printf "No.\n"
have_xapian=0 have_xapian=0
errors=$((errors + 1)) errors=$((errors + 1))
fi fi
printf "Checking for GMime 2.4 development files... "
if pkg-config --modversion gmime-2.4 > /dev/null 2>&1; then if pkg-config --modversion gmime-2.4 > /dev/null 2>&1; then
echo "Checking for GMime 2.4 development files... Yes." printf "Yes.\n"
have_gmime=1 have_gmime=1
gmime_cflags=$(pkg-config --cflags gmime-2.4)
gmime_ldflags=$(pkg-config --libs gmime-2.4)
else else
echo "Checking for GMime 2.4 development files... No." printf "No.\n"
have_gmime=0 have_gmime=0
errors=$((errors + 1)) errors=$((errors + 1))
fi fi
printf "Checking for talloc development files... "
if pkg-config --modversion talloc > /dev/null 2>&1; then if pkg-config --modversion talloc > /dev/null 2>&1; then
echo "Checking for talloc development files... Yes." printf "Yes.\n"
have_talloc=1 have_talloc=1
talloc_cflags=$(pkg-config --cflags talloc)
talloc_ldflags=$(pkg-config --libs talloc)
else else
echo "Checking for talloc development files... No." printf "No.\n"
have_talloc=0 have_talloc=0
talloc_cflags=
errors=$((errors + 1)) errors=$((errors + 1))
fi fi
if printf 'int main(){return 0;}' | gcc -x c -lz -o /dev/null - > /dev/null 2>&1; then printf "Checking for valgrind development files... "
echo "Checking for zlib development files... Yes."
have_zlib=1
else
echo "Checking for zlib development files... No."
have_zlib=0
errors=$((errors + 1))
fi
if pkg-config --modversion valgrind > /dev/null 2>&1; then if pkg-config --modversion valgrind > /dev/null 2>&1; then
echo "Checking for valgrind development files... Yes." printf "Yes.\n"
have_valgrind=-DHAVE_VALGRIND have_valgrind=1
valgrind_cflags=$(pkg-config --cflags valgrind)
else else
echo "Checking for valgrind development files... No." printf "No (but that's fine).\n"
have_valgrind= have_valgrind=0
fi
if pkg-config --modversion emacs > /dev/null 2>&1; then
emacs_lispdir=$(pkg-config emacs --variable sitepkglispdir)
else
emacs_lispdir='$(prefix)/share/emacs/site-lisp'
fi fi
if [ $errors -gt 0 ]; then if [ $errors -gt 0 ]; then
@ -100,34 +169,39 @@ EOF
echo " The talloc library (including development files such as headers)" echo " The talloc library (including development files such as headers)"
echo " http://talloc.samba.org/" echo " http://talloc.samba.org/"
fi fi
if [ $have_zlib -eq 0 ]; then
echo " The zlib library (including development files such as headers)"
fi
cat <<EOF cat <<EOF
On a modern, package-based operating system such as Debian, you can With any luck, you're using a modern, package-based operating system
install all of the dependencies with the following simple command that has all of these packages available in the distribution. In that
line: case a simple command will install everything you need. For example:
sudo apt-get install libxapian-dev libgmime-2.4-dev libtalloc-dev libz-dev On Debian and similar systems:
On other systems, a similar command can be used, but the details of the sudo apt-get install libxapian-dev libgmime-2.4-dev libtalloc-dev
package names may be different, (such as "devel" in place of "dev").
Or on Fedora and similar systems:
sudo yum install xapian-core-devel gmime-devel libtalloc-devel
On other systems, similar commands can be used, but the details of the
package names may be different.
EOF EOF
if [ $have_pkg_config -eq 0 ]; then if [ $have_pkg_config -eq 0 ]; then
cat <<EOF cat <<EOF
Note: the pkg-config program is not available. Both this configure Note: the pkg-config program is not available. This configure script
script and the Makefile of notmuch use pkg-config to find the uses pkg-config to find the compilation flags required to link against
compilation flags required to link against the various libraries the various libraries needed by notmuch. It's possible you simply need
needed by notmuch. It's possible you simply need to install pkg-config to install pkg-config with a command such as:
with a command such as:
sudo apt-get install pkg-config sudo apt-get install pkg-config
Or:
sudo yum install pkgconfig
But if pkg-config is not available for your system, then you will need But if pkg-config is not available for your system, then you will need
to manually edit the notmuch Makefile to set NOTMUCH_CFLAGS and to modify the configure script to manually set the cflags and ldflags
NOTMUCH_LDFLAGS to the correct values without calling pkg-config. variables to the correct values to link against each library in each
case that pkg-config could not be used to determine those values.
EOF EOF
fi fi
@ -140,6 +214,17 @@ EOF
exit 1 exit 1
fi fi
printf "Checking for getline... "
if ${CC} -o config/have_getline config/have_getline.c > /dev/null 2>&1
then
printf "Yes.\n"
have_getline=1
else
printf "No (will use our own instead).\n"
have_getline=0
fi
rm -f config/have_getline
cat <<EOF cat <<EOF
All required packages were found. You may now run the following All required packages were found. You may now run the following
@ -152,7 +237,59 @@ EOF
# construct the Makefile.config # construct the Makefile.config
cat > Makefile.config <<EOF cat > Makefile.config <<EOF
prefix = $PREFIX # This Makefile.config was automatically generated by the ./configure
bash_completion_dir = /etc/bash_completion.d # script of notmuch. If the configure script identified anything
CFLAGS += ${have_valgrind} # incorrectly, then you can edit this file to try to correct things,
# but be warned that if configure is run again it will destroy your
# changes, (and this could happen by simply calling "make" if the
# configure script is updated).
# The C compiler to use
CC = ${CC}
# The C++ compiler to use
CXX = ${CXX}
# Default FLAGS for C compiler (can be overridden by user such as "make CFLAGS=-g")
CFLAGS = ${CFLAGS}
# Default FLAGS for C++ compiler (can be overridden by user such as "make CXXFLAGS=-g")
CXXFLAGS = ${CXXFLAGS}
# The prefix to which notmuch should be installed
prefix = ${PREFIX}
# The directory to which emacs lisp files should be installed
emacs_lispdir=${emacs_lispdir}
# Whether the getline function is available (if not, then notmuch will
# build its own version)
HAVE_GETLINE = ${have_getline}
# Flags needed to compile and link against Xapian
XAPIAN_CXXFLAGS = ${xapian_cxxflags}
XAPIAN_LDFLAGS = ${xapian_ldflags}
# Flags needed to compile and link against GMime-2.4
GMIME_CFLAGS = ${gmime_cflags}
GMIME_LDFLAGS = ${gmime_ldflags}
# Flags needed to compile and link against talloc
TALLOC_CFLAGS = ${talloc_cflags}
TALLOC_LDFLAGS = ${talloc_ldflags}
# Whether valgrind header files are available
HAVE_VALGRIND = ${have_valgrind}
# And if so, flags needed at compile time for valgrind macros
VALGRIND_CFLAGS = ${valgrind_cflags}
# Combined flags for compiling and linking against all of the above
CONFIGURE_CFLAGS = -DHAVE_GETLINE=\$(HAVE_GETLINE) \$(GMIME_CFLAGS) \\
\$(TALLOC_CFLAGS) -DHAVE_VALGRIND=\$(HAVE_VALGRIND) \\
\$(VALGRIND_CFLAGS)
CONFIGURE_CXXFLAGS = -DHAVE_GETLINE=\$(HAVE_GETLINE) \$(GMIME_CFLAGS) \\
\$(TALLOC_CFLAGS) -DHAVE_VALGRIND=\$(HAVE_VALGRIND) \\
\$(VALGRIND_CFLAGS) \$(XAPIAN_CXXFLAGS)
CONFIGURE_LDFLAGS = \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(XAPIAN_LDFLAGS)
EOF EOF

View file

@ -31,7 +31,7 @@ _index_address_mailbox (notmuch_message_t *message,
{ {
InternetAddressMailbox *mailbox = INTERNET_ADDRESS_MAILBOX (address); InternetAddressMailbox *mailbox = INTERNET_ADDRESS_MAILBOX (address);
const char *name, *addr; const char *name, *addr;
int own_name = 0; void *local = talloc_new (NULL);
name = internet_address_get_name (address); name = internet_address_get_name (address);
addr = internet_address_mailbox_get_addr (mailbox); addr = internet_address_mailbox_get_addr (mailbox);
@ -42,16 +42,16 @@ _index_address_mailbox (notmuch_message_t *message,
const char *at; const char *at;
at = strchr (addr, '@'); at = strchr (addr, '@');
if (at) { if (at)
name = strndup (addr, at - addr); name = talloc_strndup (local, addr, at - addr);
own_name = 1;
}
} }
if (name) if (name)
_notmuch_message_gen_terms (message, prefix_name, name); _notmuch_message_gen_terms (message, prefix_name, name);
if (addr) if (addr)
_notmuch_message_gen_terms (message, prefix_name, addr); _notmuch_message_gen_terms (message, prefix_name, addr);
talloc_free (local);
} }
static void static void

View file

@ -26,6 +26,8 @@
#endif #endif
#include <stdio.h> #include <stdio.h>
#include "compat.h"
#include "notmuch.h" #include "notmuch.h"
NOTMUCH_BEGIN_DECLS NOTMUCH_BEGIN_DECLS
@ -330,18 +332,6 @@ void
_notmuch_message_add_reply (notmuch_message_t *message, _notmuch_message_add_reply (notmuch_message_t *message,
notmuch_message_node_t *reply); notmuch_message_node_t *reply);
/* date.c */
/* Parse an RFC 8222 date string to a time_t value.
*
* The tz_offset argument can be used to also obtain the time-zone
* offset, (but can be NULL if the call is not interested in that).
*
* Returns 0 on error.
*/
time_t
notmuch_parse_date (const char *str, int *tz_offset);
/* sha1.c */ /* sha1.c */
char * char *

View file

@ -18,7 +18,6 @@
* Author: Carl Worth <cworth@cworth.org> * Author: Carl Worth <cworth@cworth.org>
*/ */
#define _GNU_SOURCE /* For strndup */
#include "notmuch-private.h" #include "notmuch-private.h"
#include <stdio.h> #include <stdio.h>
@ -84,11 +83,16 @@ xstrndup (const char *s, size_t n)
{ {
char *ret; char *ret;
ret = strndup (s, n); if (strlen (s) <= n)
n = strlen (s);
ret = malloc (n + 1);
if (ret == NULL) { if (ret == NULL) {
fprintf (stderr, "Out of memory.\n"); fprintf (stderr, "Out of memory.\n");
exit (1); exit (1);
} }
memcpy (ret, s, n);
ret[n] = '\0';
return ret; return ret;
} }

View file

@ -21,12 +21,13 @@
#ifndef NOTMUCH_CLIENT_H #ifndef NOTMUCH_CLIENT_H
#define NOTMUCH_CLIENT_H #define NOTMUCH_CLIENT_H
#ifndef _GNU_SOURCE #ifndef _GNU_SOURCE
#define _GNU_SOURCE /* for getline */ #define _GNU_SOURCE /* for getline */
#endif #endif
#include <stdio.h> #include <stdio.h>
#include "compat.h"
#include <gmime/gmime.h> #include <gmime/gmime.h>
#include "notmuch.h" #include "notmuch.h"

View file

@ -317,9 +317,11 @@ notmuch_config_save (notmuch_config_t *config)
fprintf (stderr, "Error saving configuration to %s: %s\n", fprintf (stderr, "Error saving configuration to %s: %s\n",
config->filename, error->message); config->filename, error->message);
g_error_free (error); g_error_free (error);
g_free (data);
return 1; return 1;
} }
g_free (data);
return 0; return 0;
} }

View file

@ -35,8 +35,10 @@ static volatile sig_atomic_t interrupted;
static void static void
handle_sigint (unused (int sig)) handle_sigint (unused (int sig))
{ {
ssize_t ignored;
static char msg[] = "Stopping... \n"; static char msg[] = "Stopping... \n";
write(2, msg, sizeof(msg)-1);
ignored = write(2, msg, sizeof(msg)-1);
interrupted = 1; interrupted = 1;
} }

View file

@ -39,11 +39,17 @@ reply_part_content (GMimeObject *part)
{ {
GMimeStream *stream_stdout = NULL, *stream_filter = NULL; GMimeStream *stream_stdout = NULL, *stream_filter = NULL;
GMimeDataWrapper *wrapper; GMimeDataWrapper *wrapper;
const char *charset;
charset = g_mime_object_get_content_type_parameter (part, "charset");
stream_stdout = g_mime_stream_file_new (stdout); stream_stdout = g_mime_stream_file_new (stdout);
if (stream_stdout) { if (stream_stdout) {
g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
stream_filter = g_mime_stream_filter_new(stream_stdout); stream_filter = g_mime_stream_filter_new(stream_stdout);
if (charset) {
g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
g_mime_filter_charset_new(charset, "UTF-8"));
}
} }
g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter), g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
g_mime_filter_reply_new(TRUE)); g_mime_filter_reply_new(TRUE));

View file

@ -104,7 +104,10 @@ notmuch_setup_command (unused (void *ctx),
do { \ do { \
printf (format, ##__VA_ARGS__); \ printf (format, ##__VA_ARGS__); \
fflush (stdout); \ fflush (stdout); \
getline (&response, &response_size, stdin); \ if (getline (&response, &response_size, stdin) < 0) { \
printf ("Exiting.\n"); \
exit (1); \
} \
chomp_newline (response); \ chomp_newline (response); \
} while (0) } while (0)

View file

@ -184,9 +184,12 @@ show_message (void *ctx, notmuch_message_t *message, int indent)
static void static void
show_messages (void *ctx, notmuch_messages_t *messages, int indent) show_messages (void *ctx, notmuch_messages_t *messages, int indent,
notmuch_bool_t entire_thread)
{ {
notmuch_message_t *message; notmuch_message_t *message;
notmuch_bool_t match;
int next_indent;
for (; for (;
notmuch_messages_has_more (messages); notmuch_messages_has_more (messages);
@ -194,9 +197,17 @@ show_messages (void *ctx, notmuch_messages_t *messages, int indent)
{ {
message = notmuch_messages_get (messages); message = notmuch_messages_get (messages);
show_message (ctx, message, indent); match = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);
show_messages (ctx, notmuch_message_get_replies (message), indent + 1); next_indent = indent;
if (match || entire_thread) {
show_message (ctx, message, indent);
next_indent = indent + 1;
}
show_messages (ctx, notmuch_message_get_replies (message),
next_indent, entire_thread);
notmuch_message_destroy (message); notmuch_message_destroy (message);
} }
@ -212,6 +223,24 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
notmuch_thread_t *thread; notmuch_thread_t *thread;
notmuch_messages_t *messages; notmuch_messages_t *messages;
char *query_string; char *query_string;
int entire_thread = 0;
int i;
for (i = 0; i < argc && argv[i][0] == '-'; i++) {
if (strcmp (argv[i], "--") == 0) {
i++;
break;
}
if (strcmp(argv[i], "--entire-thread") == 0) {
entire_thread = 1;
} else {
fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
return 1;
}
}
argc -= i;
argv += i;
config = notmuch_config_open (ctx, NULL, NULL); config = notmuch_config_open (ctx, NULL, NULL);
if (config == NULL) if (config == NULL)
@ -251,7 +280,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
INTERNAL_ERROR ("Thread %s has no toplevel messages.\n", INTERNAL_ERROR ("Thread %s has no toplevel messages.\n",
notmuch_thread_get_thread_id (thread)); notmuch_thread_get_thread_id (thread));
show_messages (ctx, messages, 0); show_messages (ctx, messages, 0, entire_thread);
notmuch_thread_destroy (thread); notmuch_thread_destroy (thread);
} }

View file

@ -25,8 +25,10 @@ static volatile sig_atomic_t interrupted;
static void static void
handle_sigint (unused (int sig)) handle_sigint (unused (int sig))
{ {
ssize_t ignored;
static char msg[] = "Stopping... \n"; static char msg[] = "Stopping... \n";
write(2, msg, sizeof(msg)-1); ignored = write(2, msg, sizeof(msg)-1);
interrupted = 1; interrupted = 1;
} }

View file

@ -169,6 +169,8 @@ when sorting by
.B newest\-first .B newest\-first
the threads will be sorted by the newest message in each thread. the threads will be sorted by the newest message in each thread.
.RE
.RS 4
By default, results will be displayed in reverse chronological order, By default, results will be displayed in reverse chronological order,
(that is, the newest results will be displayed first). (that is, the newest results will be displayed first).
@ -177,7 +179,7 @@ See the
section below for details of the supported syntax for <search-terms>. section below for details of the supported syntax for <search-terms>.
.RE .RE
.TP .TP
.BR show " <search-term>..." .BR show " [options...] <search-term>..."
Shows all messages matching the search terms. Shows all messages matching the search terms.
@ -187,6 +189,19 @@ message in date order). The output is not indented by default, but
depth tags are printed so that proper indentation can be performed by depth tags are printed so that proper indentation can be performed by
a post-processor (such as the emacs interface to notmuch). a post-processor (such as the emacs interface to notmuch).
Supported options for
.B show
include
.RS 4
.TP 4
.B \-\-entire\-thread
By default only those messages that match the search terms will be
displayed. With this option, all messages in the same thread as any
matched message will be displayed.
.RE
.RS 4
The output format is plain-text, with all text-content MIME parts The output format is plain-text, with all text-content MIME parts
decoded. Various components in the output, decoded. Various components in the output,
.RB ( message ", " header ", " body ", " attachment ", and MIME " part ), .RB ( message ", " header ", " body ", " attachment ", and MIME " part ),
@ -207,6 +222,7 @@ See the
.B "SEARCH SYNTAX" .B "SEARCH SYNTAX"
section below for details of the supported syntax for <search-terms>. section below for details of the supported syntax for <search-terms>.
.RE .RE
.RE
The The
.B reply .B reply

View file

@ -177,6 +177,15 @@ command_t commands[] = {
"\t\t(all replies to a particular message appear immediately\n" "\t\t(all replies to a particular message appear immediately\n"
"\t\tafter that message in date order).\n" "\t\tafter that message in date order).\n"
"\n" "\n"
"\t\tSupported options for show include:\n"
"\n"
"\t\t--entire-thread\n"
"\n"
"\t\t\tBy default only those messages that match the\n"
"\t\t\tsearch terms will be displayed. With this option,\n"
"\t\t\tall messages in the same thread as any matched\n"
"\t\t\tmessage will be displayed.\n"
"\n"
"\t\tThe output format is plain-text, with all text-content\n" "\t\tThe output format is plain-text, with all text-content\n"
"\t\tMIME parts decoded. Various components in the output,\n" "\t\tMIME parts decoded. Various components in the output,\n"
"\t\t('message', 'header', 'body', 'attachment', and MIME 'part')\n" "\t\t('message', 'header', 'body', 'attachment', and MIME 'part')\n"

View file

@ -53,37 +53,31 @@
(defvar notmuch-show-mode-map (defvar notmuch-show-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
; I don't actually want all of these toggle commands occupying (define-key map "?" 'notmuch-help)
; keybindings. They steal valuable key-binding space, are hard
; to remember, and act globally rather than locally.
;
; Will be much preferable to switch to direct manipulation for
; toggling visibility of these components. Probably using
; overlays-at to query and manipulate the current overlay.
(define-key map "a" 'notmuch-show-archive-thread)
(define-key map "A" 'notmuch-show-mark-read-then-archive-thread)
(define-key map "f" 'notmuch-show-forward-current)
(define-key map "m" 'message-mail)
(define-key map "n" 'notmuch-show-next-message)
(define-key map "N" 'notmuch-show-mark-read-then-next-open-message)
(define-key map "p" 'notmuch-show-previous-message)
(define-key map (kbd "C-n") 'notmuch-show-next-line)
(define-key map (kbd "C-p") 'notmuch-show-previous-line)
(define-key map "q" 'kill-this-buffer) (define-key map "q" 'kill-this-buffer)
(define-key map "r" 'notmuch-show-reply) (define-key map (kbd "C-p") 'notmuch-show-previous-line)
(define-key map (kbd "C-n") 'notmuch-show-next-line)
(define-key map (kbd "M-TAB") 'notmuch-show-previous-button)
(define-key map (kbd "TAB") 'notmuch-show-next-button)
(define-key map "s" 'notmuch-search) (define-key map "s" 'notmuch-search)
(define-key map "v" 'notmuch-show-view-all-mime-parts) (define-key map "m" 'message-mail)
(define-key map "V" 'notmuch-show-view-raw-message) (define-key map "f" 'notmuch-show-forward-current)
(define-key map "r" 'notmuch-show-reply)
(define-key map "|" 'notmuch-show-pipe-message)
(define-key map "w" 'notmuch-show-save-attachments) (define-key map "w" 'notmuch-show-save-attachments)
(define-key map "x" 'kill-this-buffer) (define-key map "V" 'notmuch-show-view-raw-message)
(define-key map "+" 'notmuch-show-add-tag) (define-key map "v" 'notmuch-show-view-all-mime-parts)
(define-key map "-" 'notmuch-show-remove-tag) (define-key map "-" 'notmuch-show-remove-tag)
(define-key map "+" 'notmuch-show-add-tag)
(define-key map "X" 'notmuch-show-mark-read-then-archive-then-exit)
(define-key map "x" 'notmuch-show-archive-thread-then-exit)
(define-key map "A" 'notmuch-show-mark-read-then-archive-thread)
(define-key map "a" 'notmuch-show-archive-thread)
(define-key map "p" 'notmuch-show-previous-message)
(define-key map "N" 'notmuch-show-mark-read-then-next-open-message)
(define-key map "n" 'notmuch-show-next-message)
(define-key map (kbd "DEL") 'notmuch-show-rewind) (define-key map (kbd "DEL") 'notmuch-show-rewind)
(define-key map " " 'notmuch-show-advance-marking-read-and-archiving) (define-key map " " 'notmuch-show-advance-marking-read-and-archiving)
(define-key map "|" 'notmuch-show-pipe-message)
(define-key map "?" 'describe-mode)
(define-key map (kbd "TAB") 'notmuch-show-next-button)
(define-key map (kbd "M-TAB") 'notmuch-show-previous-button)
map) map)
"Keymap for \"notmuch show\" buffers.") "Keymap for \"notmuch show\" buffers.")
(fset 'notmuch-show-mode-map notmuch-show-mode-map) (fset 'notmuch-show-mode-map notmuch-show-mode-map)
@ -117,7 +111,7 @@ pattern can still test against the entire line).")
(defvar notmuch-show-marker-regexp "\f\\(message\\|header\\|body\\|attachment\\|part\\)[{}].*$") (defvar notmuch-show-marker-regexp "\f\\(message\\|header\\|body\\|attachment\\|part\\)[{}].*$")
(defvar notmuch-show-id-regexp "\\(id:[^ ]*\\)") (defvar notmuch-show-id-regexp "\\(id:[^ ]*\\)")
(defvar notmuch-show-depth-regexp " depth:\\([0-9]*\\) ") (defvar notmuch-show-depth-match-regexp " depth:\\([0-9]*\\).*match:\\([01]\\) ")
(defvar notmuch-show-filename-regexp "filename:\\(.*\\)$") (defvar notmuch-show-filename-regexp "filename:\\(.*\\)$")
(defvar notmuch-show-tags-regexp "(\\([^)]*\\))$") (defvar notmuch-show-tags-regexp "(\\([^)]*\\))$")
@ -165,7 +159,7 @@ Unlike builtin `next-line' this version accepts no arguments."
By advancing forward until reaching a visible character. By advancing forward until reaching a visible character.
Unlike builtin `next-line' this version accepts no arguments." Unlike builtin `previous-line' this version accepts no arguments."
(interactive) (interactive)
(set 'this-command 'previous-line) (set 'this-command 'previous-line)
(call-interactively 'previous-line) (call-interactively 'previous-line)
@ -252,7 +246,7 @@ Unlike builtin `next-line' this version accepts no arguments."
(notmuch-search-show-thread))))) (notmuch-search-show-thread)))))
(defun notmuch-show-mark-read-then-archive-thread () (defun notmuch-show-mark-read-then-archive-thread ()
"Remove \"unread\" tag from each message, then archive and show next thread. "Remove unread tags from thread, then archive and show next thread.
Archive each message currently shown by removing the \"unread\" Archive each message currently shown by removing the \"unread\"
and \"inbox\" tag from each. Then kill this buffer and show the and \"inbox\" tag from each. Then kill this buffer and show the
@ -267,7 +261,7 @@ buffer."
(notmuch-show-archive-thread-maybe-mark-read t)) (notmuch-show-archive-thread-maybe-mark-read t))
(defun notmuch-show-archive-thread () (defun notmuch-show-archive-thread ()
"Archive each message in thread, and show next thread from search. "Archive each message in thread, then show next thread from search.
Archive each message currently shown by removing the \"inbox\" Archive each message currently shown by removing the \"inbox\"
tag from each. Then kill this buffer and show the next thread tag from each. Then kill this buffer and show the next thread
@ -280,6 +274,18 @@ buffer."
(interactive) (interactive)
(notmuch-show-archive-thread-maybe-mark-read nil)) (notmuch-show-archive-thread-maybe-mark-read nil))
(defun notmuch-show-archive-thread-then-exit ()
"Archive each message in thread, then exit back to search results."
(interactive)
(notmuch-show-archive-thread)
(kill-this-buffer))
(defun notmuch-show-mark-read-then-archive-then-exit ()
"Remove unread tags from thread, then archive and exit to search results."
(interactive)
(notmuch-show-mark-read-then-archive-thread)
(kill-this-buffer))
(defun notmuch-show-view-raw-message () (defun notmuch-show-view-raw-message ()
"View the raw email of the current message." "View the raw email of the current message."
(interactive) (interactive)
@ -296,7 +302,7 @@ buffer."
(kill-buffer buf))))) (kill-buffer buf)))))
(defun notmuch-show-view-all-mime-parts () (defun notmuch-show-view-all-mime-parts ()
"Use external viewers (according to mailcap) to view all MIME-encoded parts." "Use external viewers to view all attachments from the current message."
(interactive) (interactive)
(with-current-notmuch-show-message (with-current-notmuch-show-message
(mm-display-parts (mm-dissect-buffer)))) (mm-display-parts (mm-dissect-buffer))))
@ -334,7 +340,7 @@ buffer."
mm-handle)) mm-handle))
(defun notmuch-show-save-attachments () (defun notmuch-show-save-attachments ()
"Save the attachments to a message" "Save all attachments from the current message."
(interactive) (interactive)
(with-current-notmuch-show-message (with-current-notmuch-show-message
(let ((mm-handle (mm-dissect-buffer))) (let ((mm-handle (mm-dissect-buffer)))
@ -360,7 +366,7 @@ buffer."
(notmuch-reply message-id))) (notmuch-reply message-id)))
(defun notmuch-show-forward-current () (defun notmuch-show-forward-current ()
"Forward a the current message." "Forward the current message."
(interactive) (interactive)
(with-current-notmuch-show-message (with-current-notmuch-show-message
(message-forward))) (message-forward)))
@ -385,7 +391,7 @@ point either forward or backward to the next visible character
when a command ends with point on an invisible character). when a command ends with point on an invisible character).
Emits an error if point is not within a valid message, (that is Emits an error if point is not within a valid message, (that is
not pattern of `notmuch-show-message-begin-regexp' could be found no pattern of `notmuch-show-message-begin-regexp' could be found
by searching backward)." by searching backward)."
(beginning-of-line) (beginning-of-line)
(if (not (looking-at notmuch-show-message-begin-regexp)) (if (not (looking-at notmuch-show-message-begin-regexp))
@ -402,22 +408,35 @@ by searching backward)."
(not (re-search-forward notmuch-show-message-begin-regexp nil t))))) (not (re-search-forward notmuch-show-message-begin-regexp nil t)))))
(defun notmuch-show-message-unread-p () (defun notmuch-show-message-unread-p ()
"Preficate testing whether current message is unread." "Predicate testing whether current message is unread."
(member "unread" (notmuch-show-get-tags))) (member "unread" (notmuch-show-get-tags)))
(defun notmuch-show-message-open-p ()
"Predicate testing whether current message is open (body is visible)."
(let ((btn (previous-button (point) t)))
(while (not (button-has-type-p btn 'notmuch-button-body-toggle-type))
(setq btn (previous-button (button-start btn))))
(not (invisible-p (button-get btn 'invisibility-spec)))))
(defun notmuch-show-next-message () (defun notmuch-show-next-message ()
"Advance to the beginning of the next message in the buffer. "Advance to the beginning of the next message in the buffer.
Moves to the last visible character of the current message if Moves to the last visible character of the current message if
already on the last message in the buffer." already on the last message in the buffer.
Returns nil if already on the last message in the buffer."
(interactive) (interactive)
(notmuch-show-move-to-current-message-summary-line) (notmuch-show-move-to-current-message-summary-line)
(if (re-search-forward notmuch-show-message-begin-regexp nil t) (if (re-search-forward notmuch-show-message-begin-regexp nil t)
(progn
(notmuch-show-move-to-current-message-summary-line) (notmuch-show-move-to-current-message-summary-line)
(recenter 0)
t)
(goto-char (- (point-max) 1)) (goto-char (- (point-max) 1))
(while (point-invisible-p) (while (point-invisible-p)
(backward-char))) (backward-char))
(recenter 0)) (recenter 0)
nil))
(defun notmuch-show-find-next-message () (defun notmuch-show-find-next-message ()
"Returns the position of the next message in the buffer. "Returns the position of the next message in the buffer.
@ -445,14 +464,9 @@ there are no more unread messages past the current point."
(notmuch-show-next-message))) (notmuch-show-next-message)))
(defun notmuch-show-next-open-message () (defun notmuch-show-next-open-message ()
"Advance to the next message which is not hidden. "Advance to the next open message (that is, body is not invisible)."
(while (and (notmuch-show-next-message)
If read messages are currently hidden, advance to the next unread (not (notmuch-show-message-open-p)))))
message. Otherwise, advance to the next message."
(if (or (memq 'notmuch-show-body-read buffer-invisibility-spec)
(assq 'notmuch-show-body-read buffer-invisibility-spec))
(notmuch-show-next-unread-message)
(notmuch-show-next-message)))
(defun notmuch-show-previous-message () (defun notmuch-show-previous-message ()
"Backup to the beginning of the previous message in the buffer. "Backup to the beginning of the previous message in the buffer.
@ -486,13 +500,13 @@ it."
(point)))) (point))))
(defun notmuch-show-mark-read-then-next-open-message () (defun notmuch-show-mark-read-then-next-open-message ()
"Remove unread tag from current message, then advance to next unread message." "Remove unread tag from this message, then advance to next open message."
(interactive) (interactive)
(notmuch-show-remove-tag "unread") (notmuch-show-remove-tag "unread")
(notmuch-show-next-open-message)) (notmuch-show-next-open-message))
(defun notmuch-show-rewind () (defun notmuch-show-rewind ()
"Do reverse scrolling compared to `notmuch-show-advance-marking-read-and-archiving' "Backup through the thread, (reverse scrolling compared to \\[notmuch-show-advance-marking-read-and-archiving]).
Specifically, if the beginning of the previous email is fewer Specifically, if the beginning of the previous email is fewer
than `window-height' lines from the current point, move to it than `window-height' lines from the current point, move to it
@ -514,7 +528,7 @@ any effects from previous calls to
(notmuch-show-previous-message)))) (notmuch-show-previous-message))))
(defun notmuch-show-advance-marking-read-and-archiving () (defun notmuch-show-advance-marking-read-and-archiving ()
"Advance through buffer, marking read and archiving. "Advance through thread, marking read and archiving.
This command is intended to be one of the simplest ways to This command is intended to be one of the simplest ways to
process a thread of email. It does the following: process a thread of email. It does the following:
@ -552,7 +566,7 @@ which this thread was originally shown."
(goto-char (button-start (previous-button (point))))) (goto-char (button-start (previous-button (point)))))
(defun notmuch-toggle-invisible-action (cite-button) (defun notmuch-toggle-invisible-action (cite-button)
(let ((invis-spec (button-get button 'invisibility-spec))) (let ((invis-spec (button-get cite-button 'invisibility-spec)))
(if (invisible-p invis-spec) (if (invisible-p invis-spec)
(remove-from-invisibility-spec invis-spec) (remove-from-invisibility-spec invis-spec)
(add-to-invisibility-spec invis-spec) (add-to-invisibility-spec invis-spec)
@ -560,14 +574,19 @@ which this thread was originally shown."
(force-window-update) (force-window-update)
(redisplay t)) (redisplay t))
(define-button-type 'notmuch-button-invisibility-toggle-type 'action 'notmuch-toggle-invisible-action 'follow-link t) (define-button-type 'notmuch-button-invisibility-toggle-type
'action 'notmuch-toggle-invisible-action
'follow-link t
'face "default")
(define-button-type 'notmuch-button-citation-toggle-type 'help-echo "mouse-1, RET: Show citation" (define-button-type 'notmuch-button-citation-toggle-type 'help-echo "mouse-1, RET: Show citation"
:supertype 'notmuch-button-invisibility-toggle-type) :supertype 'notmuch-button-invisibility-toggle-type)
(define-button-type 'notmuch-button-signature-toggle-type 'help-echo "mouse-1, RET: Show signature" (define-button-type 'notmuch-button-signature-toggle-type 'help-echo "mouse-1, RET: Show signature"
:supertype 'notmuch-button-invisibility-toggle-type) :supertype 'notmuch-button-invisibility-toggle-type)
(define-button-type 'notmuch-button-headers-toggle-type 'help-echo "mouse-1, RET: Show headers" (define-button-type 'notmuch-button-headers-toggle-type 'help-echo "mouse-1, RET: Show headers"
:supertype 'notmuch-button-invisibility-toggle-type) :supertype 'notmuch-button-invisibility-toggle-type)
(define-button-type 'notmuch-button-body-toggle-type 'help-echo "mouse-1, RET: Show message" (define-button-type 'notmuch-button-body-toggle-type
'help-echo "mouse-1, RET: Show message"
'face 'notmuch-message-summary-face
:supertype 'notmuch-button-invisibility-toggle-type) :supertype 'notmuch-button-invisibility-toggle-type)
(defun notmuch-show-markup-citations-region (beg end depth) (defun notmuch-show-markup-citations-region (beg end depth)
@ -665,7 +684,20 @@ which this thread was originally shown."
(notmuch-show-markup-part (notmuch-show-markup-part
beg end depth mime-message)))))) beg end depth mime-message))))))
(defun notmuch-show-markup-body (depth btn) (defun notmuch-show-markup-body (depth match btn)
"Markup a message body, (indenting, buttonizing citations,
etc.), and conditionally hiding the body itself if the message
has been read and does not match the current search.
DEPTH specifies the depth at which this message appears in the
tree of the current thread, (the top-level messages have depth 0
and each reply increases depth by 1). MATCH indicates whether
this message is regarded as matching the current search. BTN is
the button which is used to toggle the visibility of this
message.
When this function is called, point must be within the message, but
before the delimiter marking the beginning of the body."
(re-search-forward notmuch-show-body-begin-regexp) (re-search-forward notmuch-show-body-begin-regexp)
(forward-line) (forward-line)
(let ((beg (point-marker))) (let ((beg (point-marker)))
@ -676,13 +708,15 @@ which this thread was originally shown."
(overlay-put (make-overlay beg end) (overlay-put (make-overlay beg end)
'invisible invis-spec) 'invisible invis-spec)
(button-put btn 'invisibility-spec invis-spec) (button-put btn 'invisibility-spec invis-spec)
(if (not (notmuch-show-message-unread-p)) (if (not (or (notmuch-show-message-unread-p) match))
(add-to-invisibility-spec invis-spec))) (add-to-invisibility-spec invis-spec)))
(set-marker beg nil) (set-marker beg nil)
(set-marker end nil) (set-marker end nil)
))) )))
(defun notmuch-fontify-headers () (defun notmuch-fontify-headers ()
(progn (while (looking-at "[[:space:]]")
(forward-char))
(if (looking-at "[Tt]o:") (if (looking-at "[Tt]o:")
(progn (progn
(overlay-put (make-overlay (point) (re-search-forward ":")) (overlay-put (make-overlay (point) (re-search-forward ":"))
@ -706,56 +740,63 @@ which this thread was originally shown."
(overlay-put (make-overlay (point) (re-search-forward ":")) (overlay-put (make-overlay (point) (re-search-forward ":"))
'face 'message-header-name) 'face 'message-header-name)
(overlay-put (make-overlay (point) (re-search-forward ".*$")) (overlay-put (make-overlay (point) (re-search-forward ".*$"))
'face 'message-header-other)))))))) 'face 'message-header-other)))))))
(defun notmuch-show-markup-header (depth) (defun notmuch-show-markup-header (message-begin depth)
"Buttonize and decorate faces in a message header.
MESSAGE-BEGIN is the position of the absolute first character in
the message (including all delimiters that will end up being
invisible etc.). This is to allow a button to reliably extend to
the beginning of the message even if point is positioned at an
invisible character (such as the beginning of the buffer).
DEPTH specifies the depth at which this message appears in the
tree of the current thread, (the top-level messages have depth 0
and each reply increases depth by 1)."
(re-search-forward notmuch-show-header-begin-regexp) (re-search-forward notmuch-show-header-begin-regexp)
(forward-line) (forward-line)
(let ((beg (point-marker)) (let ((beg (point-marker))
(summary-end (copy-marker (line-beginning-position 2)))
(subject-end (copy-marker (line-end-position 2)))
(invis-spec (make-symbol "notmuch-show-header"))
(btn nil)) (btn nil))
(end-of-line)
; Inverse video for subject
(overlay-put (make-overlay beg (point)) 'face '(:inverse-video t))
(setq btn (make-button beg (point) :type 'notmuch-button-body-toggle-type))
(forward-line 1)
(end-of-line)
(let ((beg-hidden (point-marker)))
(re-search-forward notmuch-show-header-end-regexp) (re-search-forward notmuch-show-header-end-regexp)
(beginning-of-line) (beginning-of-line)
(let ((end (point-marker))) (let ((end (point-marker)))
(indent-rigidly beg end depth)
(goto-char beg) (goto-char beg)
(setq btn (make-button message-begin summary-end :type 'notmuch-button-body-toggle-type))
(forward-line) (forward-line)
(while (looking-at "[A-Za-z][-A-Za-z0-9]*:") (add-to-invisibility-spec invis-spec)
(overlay-put (make-overlay subject-end end)
'invisible invis-spec)
(make-button (line-beginning-position) subject-end
'invisibility-spec invis-spec
:type 'notmuch-button-headers-toggle-type)
(while (looking-at "[[:space:]]*[A-Za-z][-A-Za-z0-9]*:")
(beginning-of-line) (beginning-of-line)
(notmuch-fontify-headers) (notmuch-fontify-headers)
(forward-line) (forward-line)
) )
(indent-rigidly beg end depth)
(let ((invis-spec (make-symbol "notmuch-show-header")))
(add-to-invisibility-spec (cons invis-spec t))
(overlay-put (make-overlay beg-hidden end)
'invisible invis-spec)
(goto-char beg)
(forward-line)
(make-button (line-beginning-position) (line-end-position)
'invisibility-spec (cons invis-spec t)
:type 'notmuch-button-headers-toggle-type))
(goto-char end) (goto-char end)
(insert "\n") (insert "\n")
(set-marker beg nil) (set-marker beg nil)
(set-marker beg-hidden nil) (set-marker summary-end nil)
(set-marker subject-end nil)
(set-marker end nil) (set-marker end nil)
)) )
btn)) btn))
(defun notmuch-show-markup-message () (defun notmuch-show-markup-message ()
(if (re-search-forward notmuch-show-message-begin-regexp nil t) (if (re-search-forward notmuch-show-message-begin-regexp nil t)
(progn (let ((message-begin (match-beginning 0)))
(re-search-forward notmuch-show-depth-regexp) (re-search-forward notmuch-show-depth-match-regexp)
(let ((depth (string-to-number (buffer-substring (match-beginning 1) (match-end 1)))) (let ((depth (string-to-number (buffer-substring (match-beginning 1) (match-end 1))))
(match (string= "1" (buffer-substring (match-beginning 2) (match-end 2))))
(btn nil)) (btn nil))
(setq btn (notmuch-show-markup-header depth)) (setq btn (notmuch-show-markup-header message-begin depth))
(notmuch-show-markup-body depth btn))) (notmuch-show-markup-body depth match btn)))
(goto-char (point-max)))) (goto-char (point-max))))
(defun notmuch-show-hide-markers () (defun notmuch-show-hide-markers ()
@ -775,6 +816,72 @@ which this thread was originally shown."
(notmuch-show-markup-message))) (notmuch-show-markup-message)))
(notmuch-show-hide-markers)) (notmuch-show-hide-markers))
(defun notmuch-documentation-first-line (symbol)
"Return the first line of the documentation string for SYMBOL."
(let ((doc (documentation symbol)))
(if doc
(with-temp-buffer
(insert (documentation symbol t))
(goto-char (point-min))
(let ((beg (point)))
(end-of-line)
(buffer-substring beg (point))))
"")))
(defun notmuch-prefix-key-description (key)
"Given a prefix key code, return a human-readable string representation.
This is basically just `format-kbd-macro' but we also convert ESC to M-."
(let ((desc (format-kbd-macro (vector key))))
(if (string= desc "ESC")
"M-"
(concat desc " "))))
; I would think that emacs would have code handy for walking a keymap
; and generating strings for each key, and I would prefer to just call
; that. But I couldn't find any (could be all implemented in C I
; suppose), so I wrote my own here.
(defun notmuch-substitute-one-command-key-with-prefix (prefix binding)
"For a key binding, return a string showing a human-readable
representation of the prefixed key as well as the first line of
documentation from the bound function.
For a mouse binding, return nil."
(let ((key (car binding))
(action (cdr binding)))
(if (mouse-event-p key)
nil
(if (keymapp action)
(let ((substitute (apply-partially 'notmuch-substitute-one-command-key-with-prefix (notmuch-prefix-key-description key))))
(mapconcat substitute (cdr action) "\n"))
(concat prefix (format-kbd-macro (vector key))
"\t"
(notmuch-documentation-first-line action))))))
(defalias 'notmuch-substitute-one-command-key
(apply-partially 'notmuch-substitute-one-command-key-with-prefix nil))
(defun notmuch-substitute-command-keys (doc)
"Like `substitute-command-keys' but with documentation, not function names."
(let ((beg 0))
(while (string-match "\\\\{\\([^}[:space:]]*\\)}" doc beg)
(let ((map (substring doc (match-beginning 1) (match-end 1))))
(setq doc (replace-match (mapconcat 'notmuch-substitute-one-command-key
(cdr (symbol-value (intern map))) "\n") 1 1 doc)))
(setq beg (match-end 0)))
doc))
(defun notmuch-help ()
"Display help for the current notmuch mode."
(interactive)
(let* ((mode major-mode)
(doc (substitute-command-keys (notmuch-substitute-command-keys (documentation mode t)))))
(with-current-buffer (generate-new-buffer "*notmuch-help*")
(insert doc)
(goto-char (point-min))
(set-buffer-modified-p nil)
(view-buffer (current-buffer) 'kill-buffer-if-not-modified))))
;;;###autoload ;;;###autoload
(defun notmuch-show-mode () (defun notmuch-show-mode ()
"Major mode for viewing a thread with notmuch. "Major mode for viewing a thread with notmuch.
@ -783,22 +890,28 @@ This buffer contains the results of the \"notmuch show\" command
for displaying a single thread of email from your email archives. for displaying a single thread of email from your email archives.
By default, various components of email messages, (citations, By default, various components of email messages, (citations,
signatures, already-read messages), are invisible to help you signatures, already-read messages), are hidden. You can make
focus on the most important things, (new text from unread these parts visible by clicking with the mouse button or by
messages). See the various commands below for toggling the pressing RET after positioning the cursor on a hidden part, (for
visibility of hidden components. which \\[notmuch-show-next-button] and \\[notmuch-show-previous-button] are helpful).
The `notmuch-show-next-message' and Reading the thread sequentially is well-supported by pressing
`notmuch-show-previous-message' commands, (bound to 'n' and 'p by \\[notmuch-show-advance-marking-read-and-archiving]. This will scroll the current message (if necessary),
default), allow you to navigate to the next and previous advance to the next message, or advance to the next thread (if
messages. Each time you navigate away from a message with already on the last message of a thread). As each message is
`notmuch-show-next-message' the current message will have its scrolled away its \"unread\" tag will be removed, and as each
\"unread\" tag removed. thread is scrolled away the \"inbox\" tag will be removed from
each message in the thread.
You can add or remove tags from the current message with '+' and Other commands are available to read or manipulate the thread more
'-'. You can also archive all messages in the current selectively, (such as '\\[notmuch-show-next-message]' and '\\[notmuch-show-previous-message]' to advance to messages without
view, (remove the \"inbox\" tag from each), with removing any tags, and '\\[notmuch-show-archive-thread]' to archive an entire thread without
`notmuch-show-archive-thread' (bound to 'a' by default). scrolling through with \\[notmuch-show-advance-marking-read-and-archiving]).
You can add or remove arbitary tags from the current message with
'\\[notmuch-show-add-tag]' or '\\[notmuch-show-remove-tag]'.
All currently available key bindings:
\\{notmuch-show-mode-map}" \\{notmuch-show-mode-map}"
(interactive) (interactive)
@ -843,7 +956,8 @@ The optional PARENT-BUFFER is the notmuch-search buffer from
which this notmuch-show command was executed, (so that the next which this notmuch-show command was executed, (so that the next
thread from that buffer can be show when done with this one)." thread from that buffer can be show when done with this one)."
(interactive "sNotmuch show: ") (interactive "sNotmuch show: ")
(let ((buffer (get-buffer-create (concat "*notmuch-show-" thread-id "*")))) (let ((query notmuch-search-query-string)
(buffer (get-buffer-create (concat "*notmuch-show-" thread-id "*"))))
(switch-to-buffer buffer) (switch-to-buffer buffer)
(notmuch-show-mode) (notmuch-show-mode)
(set (make-local-variable 'notmuch-show-parent-buffer) parent-buffer) (set (make-local-variable 'notmuch-show-parent-buffer) parent-buffer)
@ -855,30 +969,13 @@ thread from that buffer can be show when done with this one)."
(erase-buffer) (erase-buffer)
(goto-char (point-min)) (goto-char (point-min))
(save-excursion (save-excursion
(call-process notmuch-command nil t nil "show" thread-id) (call-process notmuch-command nil t nil "show" "--entire-thread" thread-id "and (" query ")")
(notmuch-show-markup-messages) (notmuch-show-markup-messages)
) )
(run-hooks 'notmuch-show-hook) (run-hooks 'notmuch-show-hook)
; Move straight to the first unread message ; Move straight to the first open message
(if (not (notmuch-show-message-unread-p)) (if (not (notmuch-show-message-open-p))
(progn (notmuch-show-next-open-message))
(notmuch-show-next-unread-message)
; But if there are no unread messages, go back to the
; beginning of the buffer, and open up the bodies of all
; read message.
(if (not (notmuch-show-message-unread-p))
(progn
(goto-char (point-min))
(let ((btn (forward-button 1)))
(while btn
(if (button-has-type-p btn 'notmuch-button-body-toggle-type)
(push-button))
(condition-case err
(setq btn (forward-button 1))
(error (setq btn nil)))
))
(beginning-of-buffer)
))))
))) )))
(defvar notmuch-search-authors-width 40 (defvar notmuch-search-authors-width 40
@ -886,30 +983,29 @@ thread from that buffer can be show when done with this one)."
(defvar notmuch-search-mode-map (defvar notmuch-search-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
(define-key map "a" 'notmuch-search-archive-thread) (define-key map "?" 'notmuch-help)
(define-key map "b" 'notmuch-search-scroll-down)
(define-key map "f" 'notmuch-search-filter)
(define-key map "m" 'message-mail)
(define-key map "n" 'next-line)
(define-key map "o" 'notmuch-search-toggle-order)
(define-key map "p" 'previous-line)
(define-key map "q" 'kill-this-buffer) (define-key map "q" 'kill-this-buffer)
(define-key map "r" 'notmuch-search-reply-to-thread)
(define-key map "s" 'notmuch-search)
(define-key map "t" 'notmuch-search-filter-by-tag)
(define-key map "x" 'kill-this-buffer) (define-key map "x" 'kill-this-buffer)
(define-key map (kbd "RET") 'notmuch-search-show-thread)
(define-key map [mouse-1] 'notmuch-search-show-thread)
(define-key map "+" 'notmuch-search-add-tag)
(define-key map "-" 'notmuch-search-remove-tag)
(define-key map "*" 'notmuch-search-operate-all)
(define-key map "<" 'beginning-of-buffer)
(define-key map ">" 'notmuch-search-goto-last-thread)
(define-key map "=" 'notmuch-search-refresh-view)
(define-key map "\M->" 'notmuch-search-goto-last-thread)
(define-key map " " 'notmuch-search-scroll-up)
(define-key map (kbd "<DEL>") 'notmuch-search-scroll-down) (define-key map (kbd "<DEL>") 'notmuch-search-scroll-down)
(define-key map "?" 'describe-mode) (define-key map "b" 'notmuch-search-scroll-down)
(define-key map " " 'notmuch-search-scroll-up)
(define-key map "<" 'notmuch-search-first-thread)
(define-key map ">" 'notmuch-search-last-thread)
(define-key map "p" 'notmuch-search-previous-thread)
(define-key map "n" 'notmuch-search-next-thread)
(define-key map "r" 'notmuch-search-reply-to-thread)
(define-key map "m" 'message-mail)
(define-key map "s" 'notmuch-search)
(define-key map "o" 'notmuch-search-toggle-order)
(define-key map "=" 'notmuch-search-refresh-view)
(define-key map "t" 'notmuch-search-filter-by-tag)
(define-key map "f" 'notmuch-search-filter)
(define-key map [mouse-1] 'notmuch-search-show-thread)
(define-key map "*" 'notmuch-search-operate-all)
(define-key map "a" 'notmuch-search-archive-thread)
(define-key map "-" 'notmuch-search-remove-tag)
(define-key map "+" 'notmuch-search-add-tag)
(define-key map (kbd "RET") 'notmuch-search-show-thread)
map) map)
"Keymap for \"notmuch search\" buffers.") "Keymap for \"notmuch search\" buffers.")
(fset 'notmuch-search-mode-map notmuch-search-mode-map) (fset 'notmuch-search-mode-map notmuch-search-mode-map)
@ -918,16 +1014,17 @@ thread from that buffer can be show when done with this one)."
(defvar notmuch-search-oldest-first t (defvar notmuch-search-oldest-first t
"Show the oldest mail first in the search-mode") "Show the oldest mail first in the search-mode")
(defvar notmuch-search-disjunctive-regexp "\\<[oO][rR]\\>")
(defun notmuch-search-scroll-up () (defun notmuch-search-scroll-up ()
"Scroll up, moving point to last message in thread if at end." "Move forward through search results by one window's worth."
(interactive) (interactive)
(condition-case nil (condition-case nil
(scroll-up nil) (scroll-up nil)
((end-of-buffer) (notmuch-search-goto-last-thread)))) ((end-of-buffer) (notmuch-search-last-thread))))
(defun notmuch-search-scroll-down () (defun notmuch-search-scroll-down ()
"Scroll down, moving point to first message in thread if at beginning." "Move backward through the search results by one window's worth."
(interactive) (interactive)
; I don't know why scroll-down doesn't signal beginning-of-buffer ; I don't know why scroll-down doesn't signal beginning-of-buffer
; the way that scroll-up signals end-of-buffer, but c'est la vie. ; the way that scroll-up signals end-of-buffer, but c'est la vie.
@ -937,15 +1034,36 @@ thread from that buffer can be show when done with this one)."
; directly to that position. (We have to count lines since the ; directly to that position. (We have to count lines since the
; window-start position is not the same as point-min due to the ; window-start position is not the same as point-min due to the
; invisible thread-ID characters on the first line. ; invisible thread-ID characters on the first line.
(if (equal (count-lines (point-min) (window-start)) 1) (if (equal (count-lines (point-min) (window-start)) 0)
(goto-char (window-start)) (goto-char (point-min))
(scroll-down nil))) (scroll-down nil)))
(defun notmuch-search-goto-last-thread () (defun notmuch-search-next-thread ()
"Move point to the last thread in the buffer." "Select the next thread in the search results."
(interactive)
(forward-line 1))
(defun notmuch-search-previous-thread ()
"Select the previous thread in the search results."
(interactive)
(forward-line -1))
(defun notmuch-search-last-thread ()
"Select the last thread in the search results."
(interactive) (interactive)
(goto-char (point-max)) (goto-char (point-max))
(forward-line -1)) (forward-line -2))
(defun notmuch-search-first-thread ()
"Select the first thread in the search results."
(interactive)
(goto-char (point-min)))
(defface notmuch-message-summary-face
'((((class color) (background light)) (:background "#f0f0f0"))
(((class color) (background dark)) (:background "#303030")))
"Face for the single-line message summary in notmuch-show-mode."
:group 'notmuch)
(defface notmuch-tag-face (defface notmuch-tag-face
'((((class color) '((((class color)
@ -966,22 +1084,27 @@ thread from that buffer can be show when done with this one)."
;;;###autoload ;;;###autoload
(defun notmuch-search-mode () (defun notmuch-search-mode ()
"Major mode for searching mail with notmuch. "Major mode displaying results of a notmuch search.
This buffer contains the results of a \"notmuch search\" of your This buffer contains the results of a \"notmuch search\" of your
email archives. Each line in the buffer represents a single email archives. Each line in the buffer represents a single
thread giving a relative date for the thread and a subject. thread giving a summary of the thread (a relative date, the
number of matched messages and total messages in the thread,
participants in the thread, a representative subject line, and
any tags).
Pressing RET on any line displays that thread. The '+' and '-' Pressing \\[notmuch-search-show-thread] on any line displays that thread. The '\\[notmuch-search-add-tag]' and '\\[notmuch-search-remove-tag]'
keys can be used to add or remove tags from a thread. The 'a' key keys can be used to add or remove tags from a thread. The '\\[notmuch-search-archive-thread]' key
is a convenience key for archiving a thread (removing the is a convenience for archiving a thread (removing the \"inbox\"
\"inbox\" tag). tag). The '\\[notmuch-search-operate-all]' key can be used to add or remove a tag from all
threads in the current buffer.
Other useful commands are `notmuch-search-filter' for filtering Other useful commands are '\\[notmuch-search-filter]' for filtering the current search
the current search based on an additional query string, based on an additional query string, '\\[notmuch-search-filter-by-tag]' for filtering to include
`notmuch-search-filter-by-tag' for filtering to include only only messages with a given tag, and '\\[notmuch-search]' to execute a new, global
messages with a given tag, and `notmuch-search' to execute a new, search.
global search.
Complete list of currently available key bindings:
\\{notmuch-search-mode-map}" \\{notmuch-search-mode-map}"
(interactive) (interactive)
@ -998,8 +1121,7 @@ global search.
(if (not notmuch-tag-face-alist) (if (not notmuch-tag-face-alist)
(add-to-list 'notmuch-search-font-lock-keywords (list (add-to-list 'notmuch-search-font-lock-keywords (list
"(\\([^)]*\\))$" '(1 'notmuch-tag-face))) "(\\([^)]*\\))$" '(1 'notmuch-tag-face)))
(progn (let ((notmuch-search-tags (mapcar 'car notmuch-tag-face-alist)))
(setq notmuch-search-tags (mapcar 'car notmuch-tag-face-alist))
(loop for notmuch-search-tag in notmuch-search-tags (loop for notmuch-search-tag in notmuch-search-tags
do (add-to-list 'notmuch-search-font-lock-keywords (list do (add-to-list 'notmuch-search-font-lock-keywords (list
(concat "([^)]*\\(" notmuch-search-tag "\\)[^)]*)$") (concat "([^)]*\\(" notmuch-search-tag "\\)[^)]*)$")
@ -1012,6 +1134,7 @@ global search.
(get-text-property (point) 'notmuch-search-thread-id)) (get-text-property (point) 'notmuch-search-thread-id))
(defun notmuch-search-show-thread () (defun notmuch-search-show-thread ()
"Display the currently selected thread."
(interactive) (interactive)
(let ((thread-id (notmuch-search-find-thread-id))) (let ((thread-id (notmuch-search-find-thread-id)))
(if (> (length thread-id) 0) (if (> (length thread-id) 0)
@ -1064,25 +1187,29 @@ and will also appear in a buffer named \"*Notmuch errors*\"."
(split-string (buffer-substring beg end)))))) (split-string (buffer-substring beg end))))))
(defun notmuch-search-add-tag (tag) (defun notmuch-search-add-tag (tag)
"Add a tag to messages in the current thread matching the "Add a tag to the currently selected thread.
active query."
The tag is added to messages in the currently selected thread
which match the current search terms."
(interactive (interactive
(list (notmuch-select-tag-with-completion "Tag to add: "))) (list (notmuch-select-tag-with-completion "Tag to add: ")))
(notmuch-call-notmuch-process "tag" (concat "+" tag) (notmuch-search-find-thread-id) " and " notmuch-search-query-string) (notmuch-call-notmuch-process "tag" (concat "+" tag) (notmuch-search-find-thread-id) " and " notmuch-search-query-string)
(notmuch-search-set-tags (delete-dups (sort (cons tag (notmuch-search-get-tags)) 'string<)))) (notmuch-search-set-tags (delete-dups (sort (cons tag (notmuch-search-get-tags)) 'string<))))
(defun notmuch-search-remove-tag (tag) (defun notmuch-search-remove-tag (tag)
"Remove a tag from messages in the current thread matching the "Remove a tag from the currently selected thread.
active query."
The tag is removed from messages in the currently selected thread
which match the current search terms."
(interactive (interactive
(list (notmuch-select-tag-with-completion "Tag to remove: " (notmuch-search-find-thread-id)))) (list (notmuch-select-tag-with-completion "Tag to remove: " (notmuch-search-find-thread-id))))
(notmuch-call-notmuch-process "tag" (concat "-" tag) (notmuch-search-find-thread-id) " and " notmuch-search-query-string) (notmuch-call-notmuch-process "tag" (concat "-" tag) (notmuch-search-find-thread-id) " and " notmuch-search-query-string)
(notmuch-search-set-tags (delete tag (notmuch-search-get-tags)))) (notmuch-search-set-tags (delete tag (notmuch-search-get-tags))))
(defun notmuch-search-archive-thread () (defun notmuch-search-archive-thread ()
"Archive the current thread (remove its \"inbox\" tag). "Archive the currently selected thread (remove its \"inbox\" tag).
This function advances point to the next line when finished." This function advances the next thread when finished."
(interactive) (interactive)
(notmuch-search-remove-tag "inbox") (notmuch-search-remove-tag "inbox")
(forward-line)) (forward-line))
@ -1136,12 +1263,12 @@ This function advances point to the next line when finished."
(delete-process proc)))) (delete-process proc))))
(defun notmuch-search-operate-all (action) (defun notmuch-search-operate-all (action)
"Operate on all messages matching the current query. Any "Add/remove tags from all matching messages.
number of whitespace separated actions can be given. Each action
must have one of the two forms
+tagname Add the tag `tagname' Tis command adds or removes tags from all messages matching the
-tagname Remove the tag `tagname' current search terms. When called interactively, this command
will prompt for tags to be added or removed. Tags prefixed with
'+' will be added and tags prefixed with '-' will be removed.
Each character of the tag name may consist of alphanumeric Each character of the tag name may consist of alphanumeric
characters as well as `_.+-'. characters as well as `_.+-'.
@ -1227,7 +1354,8 @@ search."
Runs a new search matching only messages that match both the Runs a new search matching only messages that match both the
current search results AND the additional query string provided." current search results AND the additional query string provided."
(interactive "sFilter search: ") (interactive "sFilter search: ")
(notmuch-search (concat notmuch-search-query-string " and " query) notmuch-search-oldest-first)) (let ((grouped-query (if (string-match-p notmuch-search-disjunctive-regexp query) (concat "( " query " )") query)))
(notmuch-search (concat notmuch-search-query-string " and " grouped-query) notmuch-search-oldest-first)))
(defun notmuch-search-filter-by-tag (tag) (defun notmuch-search-filter-by-tag (tag)
"Filter the current search results based on a single tag. "Filter the current search results based on a single tag.
@ -1249,16 +1377,17 @@ current search results AND that are tagged with the given tag."
(defvar notmuch-folder-mode-map (defvar notmuch-folder-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
(define-key map "n" 'next-line) (define-key map "?" 'notmuch-help)
(define-key map "p" 'previous-line)
(define-key map "x" 'kill-this-buffer) (define-key map "x" 'kill-this-buffer)
(define-key map "q" 'kill-this-buffer) (define-key map "q" 'kill-this-buffer)
(define-key map "s" 'notmuch-search) (define-key map ">" 'notmuch-folder-last)
(define-key map (kbd "RET") 'notmuch-folder-show-search) (define-key map "<" 'notmuch-folder-first)
(define-key map "<" 'beginning-of-buffer)
(define-key map "=" 'notmuch-folder) (define-key map "=" 'notmuch-folder)
(define-key map "?" 'describe-mode) (define-key map "s" 'notmuch-search)
(define-key map [mouse-1] 'notmuch-folder-show-search) (define-key map [mouse-1] 'notmuch-folder-show-search)
(define-key map (kbd "RET") 'notmuch-folder-show-search)
(define-key map "p" 'notmuch-folder-previous)
(define-key map "n" 'notmuch-folder-next)
map) map)
"Keymap for \"notmuch folder\" buffers.") "Keymap for \"notmuch folder\" buffers.")
@ -1272,12 +1401,26 @@ current search results AND that are tagged with the given tag."
(defun notmuch-folder-mode () (defun notmuch-folder-mode ()
"Major mode for showing notmuch 'folders'. "Major mode for showing notmuch 'folders'.
This buffer contains a list of messages counts returned by a This buffer contains a list of message counts returned by a
customizable set of searches of your email archives. Each line customizable set of searches of your email archives. Each line in
in the buffer shows the search terms and the resulting message count. the buffer shows the name of a saved search and the resulting
message count.
Pressing RET on any line opens a search window containing the Pressing RET on any line opens a search window containing the
results for the search terms in that line. results for the saved search on that line.
Here is an example of how the search list could be
customized, (the following text would be placed in your ~/.emacs
file):
(setq notmuch-folders '((\"inbox\" . \"tag:inbox\")
(\"unread\" . \"tag:inbox AND tag:unread\")
(\"notmuch\" . \"tag:inbox AND to:notmuchmail.org\")))
Of course, you can have any number of folders, each configured
with any supported search terms (see \"notmuch help search-terms\").
Currently available key bindings:
\\{notmuch-folder-mode-map}" \\{notmuch-folder-mode-map}"
(interactive) (interactive)
@ -1289,6 +1432,29 @@ results for the search terms in that line.
mode-name "notmuch-folder") mode-name "notmuch-folder")
(setq buffer-read-only t)) (setq buffer-read-only t))
(defun notmuch-folder-next ()
"Select the next folder in the list."
(interactive)
(forward-line 1)
(if (eobp)
(forward-line -1)))
(defun notmuch-folder-previous ()
"Select the previous folder in the list."
(interactive)
(forward-line -1))
(defun notmuch-folder-first ()
"Select the first folder in the list."
(interactive)
(goto-char (point-min)))
(defun notmuch-folder-last ()
"Select the last folder in the list."
(interactive)
(goto-char (point-max))
(forward-line -1))
(defun notmuch-folder-add (folders) (defun notmuch-folder-add (folders)
(if folders (if folders
(let ((name (car (car folders))) (let ((name (car (car folders)))

View file

@ -275,6 +275,7 @@ function! s:NM_search_show_thread(everything)
call add(words, ')') call add(words, ')')
endif endif
call <SID>NM_cmd_show(words) call <SID>NM_cmd_show(words)
let b:nm_show_everything = a:everything
endfunction endfunction
function! s:NM_search_prompt() function! s:NM_search_prompt()
@ -408,7 +409,7 @@ endfunction
function! s:NM_cmd_show(words) function! s:NM_cmd_show(words)
let prev_bufnr = bufnr('%') let prev_bufnr = bufnr('%')
let data = s:NM_run(['show'] + a:words) let data = s:NM_run(['show', '--entire-thread'] + a:words)
let lines = split(data, "\n") let lines = split(data, "\n")
let info = s:NM_cmd_show_parse(lines) let info = s:NM_cmd_show_parse(lines)
@ -430,6 +431,7 @@ function! s:NM_cmd_show(words)
endfunction endfunction
function! s:NM_show_previous(can_change_thread, find_matching) function! s:NM_show_previous(can_change_thread, find_matching)
let everything = exists('b:nm_show_everything') ? b:nm_show_everything : 0
let info = b:nm_raw_info let info = b:nm_raw_info
let lnum = line('.') let lnum = line('.')
for msg in reverse(copy(info['msgs'])) for msg in reverse(copy(info['msgs']))
@ -450,7 +452,7 @@ function! s:NM_show_previous(can_change_thread, find_matching)
call <SID>NM_kill_this_buffer() call <SID>NM_kill_this_buffer()
if line('.') > 1 if line('.') > 1
norm k norm k
call <SID>NM_search_show_thread() call <SID>NM_search_show_thread(everything)
norm G norm G
call <SID>NM_show_previous(0, a:find_matching) call <SID>NM_show_previous(0, a:find_matching)
else else
@ -479,10 +481,11 @@ function! s:NM_show_next(can_change_thread, find_matching)
endfunction endfunction
function! s:NM_show_next_thread() function! s:NM_show_next_thread()
let everything = exists('b:nm_show_everything') ? b:nm_show_everything : 0
call <SID>NM_kill_this_buffer() call <SID>NM_kill_this_buffer()
if line('.') != line('$') if line('.') != line('$')
norm j norm j
call <SID>NM_search_show_thread() call <SID>NM_search_show_thread(everything)
else else
echo 'No more messages.' echo 'No more messages.'
endif endif