+Notmuch 0.16 (2013-MM-DD)
+=========================
+
+Command-Line Interface
+----------------------
+
+Deprecated commands "part" and "search-tags" are removed.
+
+Vim Front-End
+-------------
+
+The vim based front end to notmuch is deprecated and moved to contrib.
+We haven't been able to support this as well as we would like, and it
+has accumulated bugs and gaps in functionality. We recommend that
+people packaging notmuch no longer provide binary packages for
+notmuch-vim, but of course that is their decision.
+
Notmuch 0.15.2 (2013-02-17)
===========================
The emacsclient binary is now user-configurable
- The test framework now accepts TEST_EMACSCLIENT in addition to
- TEST_EMACS for configuring the emacsclient to use. This is
+ The test framework now accepts `TEST_EMACSCLIENT` in addition to
+ `TEST_EMACS` for configuring the emacsclient to use. This is
necessary to avoid using an old emacsclient with a new emacs, which
can result in buggy behavior.
"already has an open one.")
db = NotmuchDatabaseP()
- status = Database._create(_str(path), Database.MODE.READ_WRITE, byref(db))
+ status = Database._create(_str(path), byref(db))
if status != STATUS.SUCCESS:
raise NotmuchError(status)
VALUE
notmuch_rb_thread_get_toplevel_messages (VALUE self);
+VALUE
+notmuch_rb_thread_get_messages (VALUE self);
+
VALUE
notmuch_rb_thread_get_matched_messages (VALUE self);
rb_define_method (notmuch_rb_cThread, "thread_id", notmuch_rb_thread_get_thread_id, 0); /* in thread.c */
rb_define_method (notmuch_rb_cThread, "total_messages", notmuch_rb_thread_get_total_messages, 0); /* in thread.c */
rb_define_method (notmuch_rb_cThread, "toplevel_messages", notmuch_rb_thread_get_toplevel_messages, 0); /* in thread.c */
+ rb_define_method (notmuch_rb_cThread, "messages", notmuch_rb_thread_get_messages, 0); /* in thread.c */
rb_define_method (notmuch_rb_cThread, "matched_messages", notmuch_rb_thread_get_matched_messages, 0); /* in thread.c */
rb_define_method (notmuch_rb_cThread, "authors", notmuch_rb_thread_get_authors, 0); /* in thread.c */
rb_define_method (notmuch_rb_cThread, "subject", notmuch_rb_thread_get_subject, 0); /* in thread.c */
return Data_Wrap_Struct (notmuch_rb_cMessages, NULL, NULL, messages);
}
+/*
+ * call-seq: THREAD.messages => MESSAGES
+ *
+ * Get a Notmuch::Messages iterator for the all messages in thread.
+ */
+VALUE
+notmuch_rb_thread_get_messages (VALUE self)
+{
+ notmuch_messages_t *messages;
+ notmuch_thread_t *thread;
+
+ Data_Get_Notmuch_Thread (self, thread);
+
+ messages = notmuch_thread_get_messages (thread);
+ if (!messages)
+ rb_raise (notmuch_rb_eMemoryError, "Out of memory");
+
+ return Data_Wrap_Struct (notmuch_rb_cMessages, NULL, NULL, messages);
+}
+
/*
* call-seq: THREAD.matched_messages => fixnum
*
+++ /dev/null
-#!/usr/bin/env perl
-# Copyright (c) 2011 David Bremner
-# License: same as notmuch
-
-use strict;
-use warnings;
-use File::Temp qw(tempdir);
-use Pod::Usage;
-
-no encoding;
-
-my $NMBGIT = $ENV{NMBGIT} || $ENV{HOME}.'/.nmbug';
-
-$NMBGIT .= '/.git' if (-d $NMBGIT.'/.git');
-
-my $TAGPREFIX = $ENV{NMBPREFIX} || 'notmuch::';
-
-# magic hash for git
-my $EMPTYBLOB = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391';
-
-# for encoding
-
-my $ESCAPE_CHAR = '%';
-my $NO_ESCAPE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.
- '0123456789+-_@=.:,';
-my $MUST_ENCODE = qr{[^\Q$NO_ESCAPE\E]};
-my $ESCAPED_RX = qr{$ESCAPE_CHAR([A-Fa-f0-9]{2})};
-
-my %command = (
- archive => \&do_archive,
- checkout => \&do_checkout,
- commit => \&do_commit,
- fetch => \&do_fetch,
- help => \&do_help,
- log => \&do_log,
- merge => \&do_merge,
- pull => \&do_pull,
- push => \&do_push,
- status => \&do_status,
- );
-
-my $subcommand = shift || usage ();
-
-if (!exists $command{$subcommand}) {
- usage ();
-}
-
-&{$command{$subcommand}}(@ARGV);
-
-sub git_pipe {
- my $envref = (ref $_[0] eq 'HASH') ? shift : {};
- my $ioref = (ref $_[0] eq 'ARRAY') ? shift : undef;
- my $dir = ($_[0] eq '-|' or $_[0] eq '|-') ? shift : undef;
-
- unshift @_, 'git';
- $envref->{GIT_DIR} ||= $NMBGIT;
- spawn ($envref, defined $ioref ? $ioref : (), defined $dir ? $dir : (), @_);
-}
-
-sub git {
- my $fh = git_pipe (@_);
- my $str = join ('', <$fh>);
- unless (close $fh) {
- die "'git @_' exited with nonzero value\n";
- }
- chomp($str);
- return $str;
-}
-
-sub spawn {
- my $envref = (ref $_[0] eq 'HASH') ? shift : {};
- my $ioref = (ref $_[0] eq 'ARRAY') ? shift : undef;
- my $dir = ($_[0] eq '-|' or $_[0] eq '|-') ? shift : '-|';
-
- die unless @_;
-
- if (open my $child, $dir) {
- return $child;
- }
- # child
- while (my ($key, $value) = each %{$envref}) {
- $ENV{$key} = $value;
- }
-
- if (defined $ioref && $dir eq '-|') {
- open my $fh, '|-', @_ or die "open |- @_: $!";
- foreach my $line (@{$ioref}) {
- print $fh $line, "\n";
- }
- exit ! close $fh;
- } else {
- if ($dir ne '|-') {
- open STDIN, '<', '/dev/null' or die "reopening stdin: $!"
- }
- exec @_;
- die "exec @_: $!";
- }
-}
-
-
-sub get_tags {
- my $prefix = shift;
- my @tags;
-
- my $fh = spawn ('-|', qw/notmuch search --output=tags/, "*")
- or die 'error dumping tags';
-
- while (<$fh>) {
- chomp ();
- push @tags, $_ if (m/^$prefix/);
- }
- unless (close $fh) {
- die "'notmuch search --output=tags *' exited with nonzero value\n";
- }
- return @tags;
-}
-
-
-sub do_archive {
- system ('git', "--git-dir=$NMBGIT", 'archive', 'HEAD');
-}
-
-
-sub is_committed {
- my $status = shift;
- return scalar (@{$status->{added}} ) + scalar (@{$status->{deleted}} ) == 0;
-}
-
-
-sub do_commit {
- my @args = @_;
-
- my $status = compute_status ();
-
- if ( is_committed ($status) ) {
- print "Nothing to commit\n";
- return;
- }
-
- my $index = read_tree ('HEAD');
-
- update_index ($index, $status);
-
- my $tree = git ( { GIT_INDEX_FILE => $index }, 'write-tree')
- or die 'no output from write-tree';
-
- my $parent = git ( 'rev-parse', 'HEAD' )
- or die 'no output from rev-parse';
-
- my $commit = git ([ @args ], 'commit-tree', $tree, '-p', $parent)
- or die 'commit-tree';
-
- git ('update-ref', 'HEAD', $commit);
-
- unlink $index || die "unlink: $!";
-
-}
-
-sub read_tree {
- my $treeish = shift;
- my $index = $NMBGIT.'/nmbug.index';
- git ({ GIT_INDEX_FILE => $index }, 'read-tree', '--empty');
- git ({ GIT_INDEX_FILE => $index }, 'read-tree', $treeish);
- return $index;
-}
-
-sub update_index {
- my $index = shift;
- my $status = shift;
-
- my $git = spawn ({ GIT_DIR => $NMBGIT, GIT_INDEX_FILE => $index },
- '|-', qw/git update-index --index-info/)
- or die 'git update-index';
-
- foreach my $pair (@{$status->{deleted}}) {
- index_tags_for_msg ($git, $pair->{id}, 'D', $pair->{tag});
- }
-
- foreach my $pair (@{$status->{added}}) {
- index_tags_for_msg ($git, $pair->{id}, 'A', $pair->{tag});
- }
- unless (close $git) {
- die "'git update-index --index-info' exited with nonzero value\n";
- }
-
-}
-
-
-sub do_fetch {
- my $remote = shift || 'origin';
-
- git ('fetch', $remote);
-}
-
-
-sub notmuch {
- my @args = @_;
- system ('notmuch', @args) == 0 or die "notmuch @args failed: $?";
-}
-
-
-sub index_tags {
-
- my $index = $NMBGIT.'/nmbug.index';
-
- my $query = join ' ', map ("tag:$_", get_tags ($TAGPREFIX));
-
- my $fh = spawn ('-|', qw/notmuch dump --/, $query)
- or die "notmuch dump: $!";
-
- git ('read-tree', '--empty');
- my $git = spawn ({ GIT_DIR => $NMBGIT, GIT_INDEX_FILE => $index },
- '|-', qw/git update-index --index-info/)
- or die 'git update-index';
-
- while (<$fh>) {
- m/ ( [^ ]* ) \s+ \( ([^\)]* ) \) /x || die 'syntax error in dump';
- my ($id,$rest) = ($1,$2);
-
- #strip prefixes before writing
- my @tags = grep { s/^$TAGPREFIX//; } split (' ', $rest);
- index_tags_for_msg ($git,$id, 'A', @tags);
- }
- unless (close $git) {
- die "'git update-index --index-info' exited with nonzero value\n";
- }
- unless (close $fh) {
- die "'notmuch dump -- $query' exited with nonzero value\n";
- }
- return $index;
-}
-
-sub index_tags_for_msg {
- my $fh = shift;
- my $msgid = shift;
- my $mode = shift;
-
- my $hash = $EMPTYBLOB;
- my $blobmode = '100644';
-
- if ($mode eq 'D') {
- $blobmode = '0';
- $hash = '0000000000000000000000000000000000000000';
- }
-
- foreach my $tag (@_) {
- my $tagpath = 'tags/' . encode_for_fs ($msgid) . '/' . encode_for_fs ($tag);
- print $fh "$blobmode $hash\t$tagpath\n";
- }
-}
-
-
-sub do_checkout {
- do_sync (action => 'checkout');
-}
-
-
-sub do_sync {
-
- my %args = @_;
-
- my $status = compute_status ();
- my ($A_action, $D_action);
-
- if ($args{action} eq 'checkout') {
- $A_action = '-';
- $D_action = '+';
- } else {
- $A_action = '+';
- $D_action = '-';
- }
-
- foreach my $pair (@{$status->{added}}) {
-
- notmuch ('tag', $A_action.$TAGPREFIX.$pair->{tag},
- 'id:'.$pair->{id});
- }
-
- foreach my $pair (@{$status->{deleted}}) {
- notmuch ('tag', $D_action.$TAGPREFIX.$pair->{tag},
- 'id:'.$pair->{id});
- }
-
-}
-
-
-sub insist_committed {
-
- my $status=compute_status();
- if ( !is_committed ($status) ) {
- print "Uncommitted changes to $TAGPREFIX* tags in notmuch
-
-For a summary of changes, run 'nmbug status'
-To save your changes, run 'nmbug commit' before merging/pull
-To discard your changes, run 'nmbug checkout'
-";
- exit (1);
- }
-
-}
-
-
-sub do_pull {
- my $remote = shift || 'origin';
-
- git ( 'fetch', $remote);
-
- do_merge ();
-}
-
-
-sub do_merge {
- insist_committed ();
-
- my $tempwork = tempdir ('/tmp/nmbug-merge.XXXXXX', CLEANUP => 1);
-
- git ( { GIT_WORK_TREE => $tempwork }, 'checkout', '-f', 'HEAD');
-
- git ( { GIT_WORK_TREE => $tempwork }, 'merge', 'FETCH_HEAD');
-
- do_checkout ();
-}
-
-
-sub do_log {
- # we don't want output trapping here, because we want the pager.
- system ( 'git', "--git-dir=$NMBGIT", 'log', '--name-status', @_);
-}
-
-
-sub do_push {
- my $remote = shift || 'origin';
-
- git ('push', $remote);
-}
-
-
-sub do_status {
- my $status = compute_status ();
-
- my %output = ();
- foreach my $pair (@{$status->{added}}) {
- $output{$pair->{id}} ||= {};
- $output{$pair->{id}}{$pair->{tag}} = 'A'
- }
-
- foreach my $pair (@{$status->{deleted}}) {
- $output{$pair->{id}} ||= {};
- $output{$pair->{id}}{$pair->{tag}} = 'D'
- }
-
- foreach my $pair (@{$status->{missing}}) {
- $output{$pair->{id}} ||= {};
- $output{$pair->{id}}{$pair->{tag}} = 'U'
- }
-
- if (is_unmerged ()) {
- foreach my $pair (diff_refs ('A')) {
- $output{$pair->{id}} ||= {};
- $output{$pair->{id}}{$pair->{tag}} ||= ' ';
- $output{$pair->{id}}{$pair->{tag}} .= 'a';
- }
-
- foreach my $pair (diff_refs ('D')) {
- $output{$pair->{id}} ||= {};
- $output{$pair->{id}}{$pair->{tag}} ||= ' ';
- $output{$pair->{id}}{$pair->{tag}} .= 'd';
- }
- }
-
- foreach my $id (sort keys %output) {
- foreach my $tag (sort keys %{$output{$id}}) {
- printf "%s\t%s\t%s\n", $output{$id}{$tag}, $id, $tag;
- }
- }
-}
-
-
-sub is_unmerged {
-
- return 0 if (! -f $NMBGIT.'/FETCH_HEAD');
-
- my $fetch_head = git ('rev-parse', 'FETCH_HEAD');
- my $base = git ( 'merge-base', 'HEAD', 'FETCH_HEAD');
-
- return ($base ne $fetch_head);
-
-}
-
-sub compute_status {
- my %args = @_;
-
- my @added;
- my @deleted;
- my @missing;
-
- my $index = index_tags ();
-
- my @maybe_deleted = diff_index ($index, 'D');
-
- foreach my $pair (@maybe_deleted) {
-
- my $id = $pair->{id};
-
- my $fh = spawn ('-|', qw/notmuch search --output=files/,"id:$id")
- or die "searching for $id";
- if (!<$fh>) {
- push @missing, $pair;
- } else {
- push @deleted, $pair;
- }
- unless (close $fh) {
- die "'notmuch search --output=files id:$id' exited with nonzero value\n";
- }
- }
-
-
- @added = diff_index ($index, 'A');
-
- unlink $index || die "unlink $index: $!";
-
- return { added => [@added], deleted => [@deleted], missing => [@missing] };
-}
-
-
-sub diff_index {
- my $index = shift;
- my $filter = shift;
-
- my $fh = git_pipe ({ GIT_INDEX_FILE => $index },
- qw/diff-index --cached/,
- "--diff-filter=$filter", qw/--name-only HEAD/ );
-
- my @lines = unpack_diff_lines ($fh);
- unless (close $fh) {
- die "'git diff-index --cached --diff-filter=$filter --name-only HEAD' ",
- "exited with nonzero value\n";
- }
- return @lines;
-}
-
-
-sub diff_refs {
- my $filter = shift;
- my $ref1 = shift || 'HEAD';
- my $ref2 = shift || 'FETCH_HEAD';
-
- my $fh= git_pipe ( 'diff', "--diff-filter=$filter", '--name-only',
- $ref1, $ref2);
-
- my @lines = unpack_diff_lines ($fh);
- unless (close $fh) {
- die "'git diff --diff-filter=$filter --name-only $ref1 $ref2' ",
- "exited with nonzero value\n";
- }
- return @lines;
-}
-
-
-sub unpack_diff_lines {
- my $fh = shift;
-
- my @found;
- while(<$fh>) {
- chomp ();
- my ($id,$tag) = m|tags/ ([^/]+) / ([^/]+) |x;
-
- $id = decode_from_fs ($id);
- $tag = decode_from_fs ($tag);
-
- push @found, { id => $id, tag => $tag };
- }
-
- return @found;
-}
-
-
-sub encode_for_fs {
- my $str = shift;
-
- $str =~ s/($MUST_ENCODE)/"$ESCAPE_CHAR".sprintf ("%02x",ord ($1))/ge;
- return $str;
-}
-
-
-sub decode_from_fs {
- my $str = shift;
-
- $str =~ s/$ESCAPED_RX/ chr (hex ($1))/eg;
-
- return $str;
-
-}
-
-
-sub usage {
- pod2usage ();
- exit (1);
-}
-
-
-sub do_help {
- pod2usage ( -verbose => 2 );
- exit (0);
-}
-
-__END__
-
-=head1 NAME
-
-nmbug - manage notmuch tags about notmuch
-
-=head1 SYNOPSIS
-
-nmbug subcommand [options]
-
-B<nmbug help> for more help
-
-=head1 OPTIONS
-
-=head2 Most common commands
-
-=over 8
-
-=item B<commit> [message]
-
-Commit appropriately prefixed tags from the notmuch database to
-git. Any extra arguments are used (one per line) as a commit message.
-
-=item B<push> [remote]
-
-push local nmbug git state to remote repo
-
-=item B<pull> [remote]
-
-pull (merge) remote repo changes to notmuch. B<pull> is equivalent to
-B<fetch> followed by B<merge>.
-
-=back
-
-=head2 Other Useful Commands
-
-=over 8
-
-=item B<checkout>
-
-Update the notmuch database from git. This is mainly useful to discard
-your changes in notmuch relative to git.
-
-=item B<fetch> [remote]
-
-Fetch changes from the remote repo (see merge to bring those changes
-into notmuch).
-
-=item B<help> [subcommand]
-
-print help [for subcommand]
-
-=item B<log> [parameters]
-
-A simple wrapper for git log. After running C<nmbug fetch>, you can
-inspect the changes with C<nmbug log HEAD..FETCH_HEAD>
-
-=item B<merge>
-
-Merge changes from FETCH_HEAD into HEAD, and load the result into
-notmuch.
-
-=item B<status>
-
-Show pending updates in notmuch or git repo. See below for more
-information about the output format.
-
-=back
-
-=head2 Less common commands
-
-=over 8
-
-=item B<archive>
-
-Dump a tar archive (using git archive) of the current nmbug tag set.
-
-=back
-
-=head1 STATUS FORMAT
-
-B<nmbug status> prints lines of the form
-
- ng Message-Id tag
-
-where n is a single character representing notmuch database status
-
-=over 8
-
-=item B<A>
-
-Tag is present in notmuch database, but not committed to nmbug
-(equivalently, tag has been deleted in nmbug repo, e.g. by a pull, but
-not restored to notmuch database).
-
-=item B<D>
-
-Tag is present in nmbug repo, but not restored to notmuch database
-(equivalently, tag has been deleted in notmuch)
-
-=item B<U>
-
-Message is unknown (missing from local notmuch database)
-
-=back
-
-The second character (if present) represents a difference between remote
-git and local. Typically C<nmbug fetch> needs to be run to update this.
-
-=over 8
-
-
-=item B<a>
-
-Tag is present in remote, but not in local git.
-
-
-=item B<d>
-
-Tag is present in local git, but not in remote git.
-
-
-=back
-
-=head1 DUMP FORMAT
-
-Each tag $tag for message with Message-Id $id is written to
-an empty file
-
- tags/encode($id)/encode($tag)
-
-The encoding preserves alphanumerics, and the characters "+-_@=.:,"
-(not the quotes). All other octets are replaced with '%' followed by
-a two digit hex number.
-
-=head1 ENVIRONMENT
-
-B<NMBGIT> specifies the location of the git repository used by nmbug.
-If not specified $HOME/.nmbug is used.
-
-B<NMBPREFIX> specifies the prefix in the notmuch database for tags of
-interest to nmbug. If not specified 'notmuch::' is used.
+++ /dev/null
-#!/usr/bin/python
-#
-# Copyright (c) 2011-2012 David Bremner <david@tethera.net>
-# License: Same as notmuch
-# dependencies
-# - python 2.6 for json
-# - argparse; either python 2.7, or install separately
-
-import datetime
-import notmuch
-import rfc822
-import urllib
-import json
-import argparse
-import os
-import subprocess
-
-# parse command line arguments
-
-parser = argparse.ArgumentParser()
-parser.add_argument('--text', help='output plain text format',
- action='store_true')
-
-parser.add_argument('--config', help='load config from given file')
-
-
-args = parser.parse_args()
-
-# read config from json file
-
-if args.config != None:
- fp = open(args.config)
-else:
- nmbhome = os.getenv('NMBGIT', os.path.expanduser('~/.nmbug'))
-
- # read only the first line from the pipe
- sha1 = subprocess.Popen(['git', '--git-dir', nmbhome,
- 'show-ref', '-s', 'config'],
- stdout=subprocess.PIPE).stdout.readline()
-
- sha1 = sha1.rstrip()
-
- fp = subprocess.Popen(['git', '--git-dir', nmbhome,
- 'cat-file', 'blob', sha1+':status-config.json'],
- stdout=subprocess.PIPE).stdout
-
-config = json.load(fp)
-
-if args.text:
- output_format = 'text'
-else:
- output_format = 'html'
-
-class Thread:
- def __init__(self, last, lines):
- self.last = last
- self.lines = lines
-
- def join_utf8_with_newlines(self):
- return '\n'.join( (line.encode('utf-8') for line in self.lines) )
-
-def output_with_separator(threadlist, sep):
- outputs = (thread.join_utf8_with_newlines() for thread in threadlist)
- print sep.join(outputs)
-
-headers = ['date', 'from', 'subject']
-
-def print_view(title, query, comment):
-
- query_string = ' and '.join(query)
- q_new = notmuch.Query(db, query_string)
- q_new.set_sort(notmuch.Query.SORT.OLDEST_FIRST)
-
- last_thread_id = ''
- threads = {}
- threadlist = []
- out = {}
- last = None
- lines = None
-
- if output_format == 'html':
- print '<h3><a name="%s" />%s</h3>' % (title, title)
- print comment
- print 'The view is generated from the following query:'
- print '<blockquote>'
- print query_string
- print '</blockquote>'
- print '<table>\n'
-
- for m in q_new.search_messages():
-
- thread_id = m.get_thread_id()
-
- if thread_id != last_thread_id:
- if threads.has_key(thread_id):
- last = threads[thread_id].last
- lines = threads[thread_id].lines
- else:
- last = {}
- lines = []
- thread = Thread(last, lines)
- threads[thread_id] = thread
- for h in headers:
- last[h] = ''
- threadlist.append(thread)
- last_thread_id = thread_id
-
- for header in headers:
- val = m.get_header(header)
-
- if header == 'date':
- val = str.join(' ', val.split(None)[1:4])
- val = str(datetime.datetime.strptime(val, '%d %b %Y').date())
- elif header == 'from':
- (val, addr) = rfc822.parseaddr(val)
- if val == '':
- val = addr.split('@')[0]
-
- if header != 'subject' and last[header] == val:
- out[header] = ''
- else:
- out[header] = val
- last[header] = val
-
- mid = m.get_message_id()
- out['id'] = 'id:"%s"' % mid
-
- if output_format == 'html':
-
- out['subject'] = '<a href="http://mid.gmane.org/%s">%s</a>' \
- % (urllib.quote(mid), out['subject'])
-
- lines.append(' <tr><td>%s' % out['date'])
- lines.append('</td><td>%s' % out['id'])
- lines.append('</td></tr>')
- lines.append(' <tr><td>%s' % out['from'])
- lines.append('</td><td>%s' % out['subject'])
- lines.append('</td></tr>')
- else:
- lines.append('%(date)-10.10s %(from)-20.20s %(subject)-40.40s\n%(id)72s' % out)
-
- if output_format == 'html':
- output_with_separator(threadlist,
- '\n<tr><td colspan="2"><br /></td></tr>\n')
- print '</table>'
- else:
- output_with_separator(threadlist, '\n\n')
-
-# main program
-
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
-
-if output_format == 'html':
- print '''<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-<title>Notmuch Patches</title>
-</head>
-<body>'''
- print '<h2>Notmuch Patches</h2>'
- print 'Generated: %s<br />' % datetime.datetime.utcnow().date()
- print 'For more infomation see <a href="http://notmuchmail.org/nmbug">nmbug</a>'
-
- print '<h3>Views</h3>'
- print '<ul>'
- for view in config['views']:
- print '<li><a href="#%(title)s">%(title)s</a></li>' % view
- print '</ul>'
-
-for view in config['views']:
- print_view(**view)
-
-if output_format == 'html':
- print '</body>\n</html>'
+++ /dev/null
-{
- "views": [
- {
- "comment": "Unresolved bugs (or just need tag updating).",
- "query": [
- "tag:notmuch::bug",
- "not tag:notmuch::fixed",
- "not tag:notmuch::wontfix"
- ],
- "title": "Bugs"
- },
- {
- "comment": "These patches are under consideration for pushing.",
- "query": [
- "tag:notmuch::patch and not tag:notmuch::pushed",
- "not tag:notmuch::obsolete and not tag:notmuch::wip",
- "not tag:notmuch::stale and not tag:notmuch::contrib",
- "not tag:notmuch::moreinfo",
- "not tag:notmuch::python",
- "not tag:notmuch::vim",
- "not tag:notmuch::wontfix",
- "not tag:notmuch::needs-review"
- ],
- "title": "Maybe Ready (Core and Emacs)"
- },
- {
- "comment": "These python related patches might be ready to push, or they might just need updated tags.",
- "query": [
- "tag:notmuch::patch and not tag:notmuch::pushed",
- "not tag:notmuch::obsolete and not tag:notmuch::wip",
- "not tag:notmuch::stale and not tag:notmuch::contrib",
- "not tag:notmuch::moreinfo",
- "not tag:notmuch::wontfix",
- " tag:notmuch::python",
- "not tag:notmuch::needs-review"
- ],
- "title": "Maybe Ready (Python)"
- },
- {
- "comment": "These vim related patches might be ready to push, or they might just need updated tags.",
- "query": [
- "tag:notmuch::patch and not tag:notmuch::pushed",
- "not tag:notmuch::obsolete and not tag:notmuch::wip",
- "not tag:notmuch::stale and not tag:notmuch::contrib",
- "not tag:notmuch::moreinfo",
- "not tag:notmuch::wontfix",
- "tag:notmuch::vim",
- "not tag:notmuch::needs-review"
- ],
- "title": "Maybe Ready (vim)"
- },
- {
- "comment": "These patches are under review, or waiting for feedback.",
- "query": [
- "tag:notmuch::patch",
- "not tag:notmuch::pushed",
- "not tag:notmuch::obsolete",
- "not tag:notmuch::stale",
- "not tag:notmuch::wontfix",
- "(tag:notmuch::moreinfo or tag:notmuch::needs-review)"
- ],
- "title": "Review"
- }
- ]
-}
sub get_message_id() {
my $mail = Mail::Internet->new(\*STDIN);
- $mail->head->get("message-id") =~ /^<(.*)>$/; # get message-id
+ my $mid = $mail->head->get("message-id") or return undef;
+ $mid =~ /^<(.*)>$/; # get message-id value
return $1;
}
my ($results_dir, $remove_dups, @params) = @_;
my $mid = get_message_id();
+ if (! defined $mid) {
+ empty_maildir($results_dir);
+ die "notmuch-mutt: cannot find Message-Id, abort.\n";
+ }
my $search_cmd = 'notmuch search --output=threads ' . shell_quote("id:$mid");
my $tid = `$search_cmd`; # get thread id
chomp($tid);
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(' ', @_))
F</etc/Muttrc>, or a configuration snippet under F</etc/Muttrc.d/>):
macro index <F8> \
- "<enter-command>unset wait_key<enter><shell-escape>notmuch-mutt -r --prompt search<enter><change-folder-readonly>~/.cache/notmuch/mutt/results<enter>" \
+ "<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+ <shell-escape>notmuch-mutt -r --prompt search<enter>\
+ <change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>\
+ <enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
"notmuch: search mail"
+
macro index <F9> \
- "<enter-command>unset wait_key<enter><pipe-message>notmuch-mutt -r thread<enter><change-folder-readonly>~/.cache/notmuch/mutt/results<enter><enter-command>set wait_key<enter>" \
+ "<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+ <pipe-message>notmuch-mutt -r thread<enter>\
+ <change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>\
+ <enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
"notmuch: reconstruct thread"
+
macro index <F6> \
- "<enter-command>unset wait_key<enter><pipe-message>notmuch-mutt tag -- -inbox<enter>" \
+ "<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+ <pipe-message>notmuch-mutt tag -- -inbox<enter>\
+ <enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
"notmuch: remove message from inbox"
The first macro (activated by <F8>) prompts the user for notmuch search terms
macro index <F8> \
- "<enter-command>unset wait_key<enter><shell-escape>notmuch-mutt -r --prompt search<enter><change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>" \
+"<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+<shell-escape>notmuch-mutt -r --prompt search<enter>\
+<change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>\
+<enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
"notmuch: search mail"
+
macro index <F9> \
- "<enter-command>unset wait_key<enter><pipe-message>notmuch-mutt -r thread<enter><change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter><enter-command>set wait_key<enter>" \
+"<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+<pipe-message>notmuch-mutt -r thread<enter>\
+<change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>\
+<enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
"notmuch: reconstruct thread"
+
macro index <F6> \
- "<enter-command>unset wait_key<enter><pipe-message>notmuch-mutt tag -- -inbox<enter>" \
+"<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+<pipe-message>notmuch-mutt tag -- -inbox<enter>\
+<enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
"notmuch: remove message from inbox"
--- /dev/null
+.PHONY: all help install link symlink
+
+files = plugin/notmuch.vim \
+ $(wildcard syntax/notmuch-*.vim)
+prefix = $(HOME)/.vim
+destdir = $(prefix)/plugin
+
+INSTALL = install -D -m644
+
+all: help
+
+help:
+ @echo "I don't actually build anything, but I will help you install"
+ @echo "notmuch support for vim."
+ @echo
+ @echo " make install - copy plugin scripts and syntax files to ~/.vim"
+ @echo " make symlink - create symlinks in ~/.vim (useful for development)"
+
+install:
+ @for x in $(files); do $(INSTALL) $(CURDIR)/$$x $(prefix)/$$x; done
+
+link symlink: INSTALL = ln -fs
+link symlink: install
--- /dev/null
+This directory contains a vim script that allows reading notmuch mail
+through vim.
+
+NOTE: this is a work in progress. Patches welcome. <bart@jukie.net>
+
+Dependencies:
+ notmuch:
+ Naturally, it expects you have notmuch installed and configured.
+
+ sendmail:
+ To send mail, notmuch.vim uses sendmail as default. Most modern MTAs
+ provide a compatibility binary, and so should work well.
+
+
+To install:
+ make install
+
+
+To run:
+ vim -c ':NotMuch'
+
+ from vim:
+ :NotMuch
+ :NotMuch new to:bart@jukie.net 'subject:this is a test'
+
+
+Buffer types:
+ [notmuch-folders]
+ Folder list, or technically a list of saved searches.
+
+ Keybindings:
+ <Enter> - show the selected search
+ m - compose a new message
+ s - enter search criteria
+ = - refresh display
+
+ [notmuch-search]
+ You are presented with the search results when you run :NotMuch.
+
+ Keybindings:
+ <Space> - show the selected thread collapsing unmatched items
+ <Enter> - show the entire selected thread
+ a - archive message (remove inbox tag)
+ f - filter the current search terms
+ o - toggle search screen order
+ m - compose a new message
+ r - reply to thread
+ s - enter search criteria
+ ,s - alter search criteria
+ t - filter the current search terms with tags
+ q - return to folder display, or undo filter
+ + - add tag(s) to selected message
+ - - remove tag(s) from selected message
+ = - refresh display
+ ? - reveal the thread ID of what's under cursor
+ ^] - search using word under cursor
+
+ [notmuch-show]
+ This is the display of the message.
+
+ Keybindings:
+ <Space> - mark read, archive, go to next matching message
+ ^n - next message
+ ^p - previous message
+ b - toggle folding of message bodies
+ c - toggle folding of citations
+ h - toggle folding of extra header lines
+ i - toggle folding of signatures
+ m - compose a new message
+ r - reply to the message
+ s - enter search criteria
+ q - return to search display
+ ? - reveal the message and thread IDs of what's under cursor
+ ^] - search using word under cursor
+
+ [notmuch-compose]
+ When you're writing an email, you're in this mode.
+
+ Insert-mode keybindings:
+ <Tab> - go to the next header line
+
+ Normal-mode keybindings:
+ <Tab> - go to the next header line
+ ,s - send this message
+ ,q - abort this message
+
--- /dev/null
+addon: notmuch
+description: "notmuch mail user interface"
+files:
+ - plugin/notmuch.vim
+ - syntax/notmuch-compose.vim
+ - syntax/notmuch-folders.vim
+ - syntax/notmuch-search.vim
+ - syntax/notmuch-show.vim
--- /dev/null
+" notmuch.vim plugin --- run notmuch within vim
+"
+" Copyright © Carl Worth
+"
+" 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: Bart Trojanowski <bart@jukie.net>
+" Contributors: Felipe Contreras <felipe.contreras@gmail.com>,
+" Peter Hartman <peterjohnhartman@gmail.com>
+"
+" --- configuration defaults {{{1
+
+let s:notmuch_defaults = {
+ \ 'g:notmuch_cmd': 'notmuch' ,
+ \ 'g:notmuch_sendmail': '/usr/sbin/sendmail' ,
+ \ 'g:notmuch_debug': 0 ,
+ \
+ \ 'g:notmuch_search_newest_first': 1 ,
+ \ 'g:notmuch_search_from_column_width': 20 ,
+ \
+ \ 'g:notmuch_show_fold_signatures': 1 ,
+ \ 'g:notmuch_show_fold_citations': 1 ,
+ \ 'g:notmuch_show_fold_bodies': 0 ,
+ \ 'g:notmuch_show_fold_headers': 1 ,
+ \
+ \ 'g:notmuch_show_message_begin_regexp': '\fmessage{' ,
+ \ 'g:notmuch_show_message_end_regexp': '\fmessage}' ,
+ \ 'g:notmuch_show_header_begin_regexp': '\fheader{' ,
+ \ 'g:notmuch_show_header_end_regexp': '\fheader}' ,
+ \ 'g:notmuch_show_body_begin_regexp': '\fbody{' ,
+ \ 'g:notmuch_show_body_end_regexp': '\fbody}' ,
+ \ 'g:notmuch_show_attachment_begin_regexp': '\fattachment{' ,
+ \ 'g:notmuch_show_attachment_end_regexp': '\fattachment}' ,
+ \ 'g:notmuch_show_part_begin_regexp': '\fpart{' ,
+ \ 'g:notmuch_show_part_end_regexp': '\fpart}' ,
+ \ 'g:notmuch_show_marker_regexp': '\f\\(message\\|header\\|body\\|attachment\\|part\\)[{}].*$',
+ \
+ \ 'g:notmuch_show_message_parse_regexp': '\(id:[^ ]*\) depth:\([0-9]*\) match:\([0-9]*\) excluded:\([0-9]*\) filename:\(.*\)$',
+ \ 'g:notmuch_show_tags_regexp': '(\([^)]*\))$' ,
+ \
+ \ 'g:notmuch_show_signature_regexp': '^\(-- \?\|_\+\)$' ,
+ \ 'g:notmuch_show_signature_lines_max': 12 ,
+ \
+ \ 'g:notmuch_show_citation_regexp': '^\s*>' ,
+ \
+ \ 'g:notmuch_compose_insert_mode_start': 1 ,
+ \ 'g:notmuch_compose_header_help': 1 ,
+ \ 'g:notmuch_compose_temp_file_dir': '~/.notmuch/compose' ,
+ \ }
+
+" defaults for g:notmuch_initial_search_words
+" override with: let g:notmuch_initial_search_words = [ ... ]
+let s:notmuch_initial_search_words_defaults = [
+ \ 'tag:inbox and tag:unread',
+ \ ]
+
+" defaults for g:notmuch_show_headers
+" override with: let g:notmuch_show_headers = [ ... ]
+let s:notmuch_show_headers_defaults = [
+ \ 'Subject',
+ \ 'To',
+ \ 'Cc',
+ \ 'Bcc',
+ \ 'Date',
+ \ ]
+
+" defaults for g:notmuch_folders
+" override with: let g:notmuch_folders = [ ... ]
+let s:notmuch_folders_defaults = [
+ \ [ 'new', 'tag:inbox and tag:unread' ],
+ \ [ 'inbox', 'tag:inbox' ],
+ \ [ 'unread', 'tag:unread' ],
+ \ ]
+
+" defaults for g:notmuch_signature
+" override with: let g:notmuch_signature = [ ... ]
+let s:notmuch_signature_defaults = [
+ \ '',
+ \ '-- ',
+ \ 'email sent from notmuch.vim plugin'
+ \ ]
+
+" defaults for g:notmuch_compose_headers
+" override with: let g:notmuch_compose_headers = [ ... ]
+let s:notmuch_compose_headers_defaults = [
+ \ 'From',
+ \ 'To',
+ \ 'Cc',
+ \ 'Bcc',
+ \ 'Subject'
+ \ ]
+
+" --- keyboard mapping definitions {{{1
+
+" --- --- bindings for folders mode {{{2
+
+let g:notmuch_folders_maps = {
+ \ 'm': ':call <SID>NM_new_mail()<CR>',
+ \ 's': ':call <SID>NM_search_prompt()<CR>',
+ \ 'q': ':call <SID>NM_kill_this_buffer()<CR>',
+ \ '=': ':call <SID>NM_folders_refresh_view()<CR>',
+ \ '<Enter>': ':call <SID>NM_folders_show_search()<CR>',
+ \ }
+
+" --- --- bindings for search screen {{{2
+let g:notmuch_search_maps = {
+ \ '<Space>': ':call <SID>NM_search_show_thread(0)<CR>',
+ \ '<Enter>': ':call <SID>NM_search_show_thread(1)<CR>',
+ \ '<C-]>': ':call <SID>NM_search_expand(''<cword>'')<CR>',
+ \ 'I': ':call <SID>NM_search_mark_read_thread()<CR>',
+ \ 'a': ':call <SID>NM_search_archive_thread()<CR>',
+ \ 'A': ':call <SID>NM_search_mark_read_then_archive_thread()<CR>',
+ \ 'D': ':call <SID>NM_search_delete_thread()<CR>',
+ \ 'f': ':call <SID>NM_search_filter()<CR>',
+ \ 'm': ':call <SID>NM_new_mail()<CR>',
+ \ 'o': ':call <SID>NM_search_toggle_order()<CR>',
+ \ 'r': ':call <SID>NM_search_reply_to_thread()<CR>',
+ \ 's': ':call <SID>NM_search_prompt()<CR>',
+ \ ',s': ':call <SID>NM_search_edit()<CR>',
+ \ 't': ':call <SID>NM_search_filter_by_tag()<CR>',
+ \ 'q': ':call <SID>NM_kill_this_buffer()<CR>',
+ \ '+': ':call <SID>NM_search_add_tags([])<CR>',
+ \ '-': ':call <SID>NM_search_remove_tags([])<CR>',
+ \ '=': ':call <SID>NM_search_refresh_view()<CR>',
+ \ '?': ':echo <SID>NM_search_thread_id() . '' @ '' . join(<SID>NM_get_search_words())<CR>',
+ \ }
+
+" --- --- bindings for show screen {{{2
+let g:notmuch_show_maps = {
+ \ '<C-P>': ':call <SID>NM_show_previous(1, 0)<CR>',
+ \ '<C-N>': ':call <SID>NM_show_next(1, 0)<CR>',
+ \ '<C-]>': ':call <SID>NM_search_expand(''<cword>'')<CR>',
+ \ 'q': ':call <SID>NM_kill_this_buffer()<CR>',
+ \ 's': ':call <SID>NM_search_prompt()<CR>',
+ \
+ \ 'b': ':call <SID>NM_show_fold_toggle(''b'', ''bdy'', !g:notmuch_show_fold_bodies)<CR>',
+ \ 'c': ':call <SID>NM_show_fold_toggle(''c'', ''cit'', !g:notmuch_show_fold_citations)<CR>',
+ \ 'h': ':call <SID>NM_show_fold_toggle(''h'', ''hdr'', !g:notmuch_show_fold_headers)<CR>',
+ \ 'i': ':call <SID>NM_show_fold_toggle(''i'', ''sig'', !g:notmuch_show_fold_signatures)<CR>',
+ \
+ \ 'I': ':call <SID>NM_show_mark_read_thread()<CR>',
+ \ 'a': ':call <SID>NM_show_archive_thread()<CR>',
+ \ 'A': ':call <SID>NM_show_mark_read_then_archive_thread()<CR>',
+ \ 'D': ':call <SID>NM_show_delete_thread()<CR>',
+ \ 'd': ':call <SID>NM_show_delete_message()<CR>',
+ \ 'N': ':call <SID>NM_show_mark_read_then_next_open_message()<CR>',
+ \ 'v': ':call <SID>NM_show_view_all_mime_parts()<CR>',
+ \ '+': ':call <SID>NM_show_add_tag()<CR>',
+ \ '-': ':call <SID>NM_show_remove_tag()<CR>',
+ \ '<Space>': ':call <SID>NM_show_advance_marking_read_and_archiving()<CR>',
+ \ '\|': ':call <SID>NM_show_pipe_message()<CR>',
+ \
+ \ '<S-Tab>': ':call <SID>NM_show_previous_fold()<CR>',
+ \ '<Tab>': ':call <SID>NM_show_next_fold()<CR>',
+ \ '<Enter>': ':call <SID>NM_show_toggle_fold()<CR>',
+ \
+ \ 'r': ':call <SID>NM_show_reply()<CR>',
+ \ 'm': ':call <SID>NM_new_mail()<CR>',
+ \ '?': ':echo <SID>NM_show_message_id() . '' @ '' . join(<SID>NM_get_search_words())<CR>',
+ \ }
+
+" --- --- bindings for compose screen {{{2
+let g:notmuch_compose_nmaps = {
+ \ ',s': ':call <SID>NM_compose_send()<CR>',
+ \ ',a': ':call <SID>NM_compose_attach()<CR>',
+ \ ',q': ':call <SID>NM_kill_this_buffer()<CR>',
+ \ '<Tab>': ':call <SID>NM_compose_next_entry_area()<CR>',
+ \ }
+let g:notmuch_compose_imaps = {
+ \ '<Tab>': '<C-r>=<SID>NM_compose_next_entry_area()<CR>',
+ \ }
+
+" --- implement folders screen {{{1
+
+function! s:NM_cmd_folders(words)
+ if len(a:words)
+ throw 'Not expecting any arguments for folders command.'
+ endif
+ let cmd = ['count']
+ let disp = []
+ let searches = []
+ for entry in g:notmuch_folders
+ let [ name, search ] = entry
+ let data = s:NM_run(cmd + [search])
+ let cnt = matchlist(data, '\(\d\+\)')[1]
+ call add(disp, printf('%9d %-20s (%s)', cnt, name, search))
+ call add(searches, search)
+ endfor
+
+ call <SID>NM_newBuffer('', 'folders', join(disp, "\n"))
+ let b:nm_searches = searches
+ let b:nm_timestamp = reltime()
+
+ call <SID>NM_cmd_folders_mksyntax()
+ call <SID>NM_set_map('n', g:notmuch_folders_maps)
+ setlocal cursorline
+ setlocal nowrap
+endfunction
+
+function! s:NM_cmd_folders_mksyntax()
+endfunction
+
+" --- --- folders screen action functions {{{2
+
+function! s:NM_folders_refresh_view()
+ let lno = line('.')
+ setlocal bufhidden=delete
+ call s:NM_cmd_folders([])
+ exec printf('norm %dG', lno)
+endfunction
+
+function! s:NM_folders_show_search()
+ let line = line('.')
+ let search = b:nm_searches[line-1]
+
+ let prev_bufnr = bufnr('%')
+ setlocal bufhidden=hide
+ call <SID>NM_cmd_search([search])
+ setlocal bufhidden=delete
+ let b:nm_prev_bufnr = prev_bufnr
+endfunction
+
+
+" --- implement search screen {{{1
+
+function! s:NM_cmd_search(words)
+ let cmd = ['search']
+ if g:notmuch_search_newest_first
+ let cmd = cmd + ['--sort=newest-first']
+ else
+ let cmd = cmd + ['--sort=oldest-first']
+ endif
+ let data = s:NM_run(cmd + a:words)
+ let lines = split(data, "\n")
+ let disp = copy(lines)
+ call map(disp, 's:NM_cmd_search_fmtline(v:val)')
+
+ call <SID>NM_newBuffer('', 'search', join(disp, "\n"))
+ let b:nm_raw_lines = lines
+ let b:nm_search_words = a:words
+
+ call <SID>NM_set_map('n', g:notmuch_search_maps)
+ setlocal cursorline
+ setlocal nowrap
+endfunction
+function! s:NM_cmd_search_fmtline(line)
+ let m = matchlist(a:line, '^\(thread:\S\+\)\s\(.\{12\}\) \[\(\d\+\)/\d\+\] \([^;]\+\); \%(\[[^\[]\+\] \)*\(.*\) (\([^(]*\))$')
+ if !len(m)
+ return 'ERROR PARSING: ' . a:line
+ endif
+ let max = g:notmuch_search_from_column_width
+ let flist = {}
+ for at in split(m[4], '[|,] ')
+ let p = split(at, '[@.]')
+ let flist[p[0]] = 1
+ endfor
+ let from = join(keys(flist), ", ")
+ return printf("%-12s %3s %-20.20s | %s (%s)", m[2], m[3], from, m[5], m[6])
+endfunction
+
+" --- --- search screen action functions {{{2
+
+function! s:NM_search_show_thread(everything)
+ let words = [ <SID>NM_search_thread_id() ]
+ if !a:everything && exists('b:nm_search_words')
+ call extend(words, ['AND', '('])
+ call extend(words, b:nm_search_words)
+ call add(words, ')')
+ endif
+ call <SID>NM_cmd_show(words)
+ let b:nm_show_everything = a:everything
+endfunction
+
+function! s:NM_search_prompt()
+ " TODO: input() can support completion
+ let text = input('NotMuch Search: ')
+ if strlen(text)
+ let tags = split(text)
+ else
+ let tags = s:notmuch_initial_search_words_defaults
+ endif
+ let prev_bufnr = bufnr('%')
+ if b:nm_type == 'search' && exists('b:nm_prev_bufnr')
+ " TODO: we intend to replace the current buffer,
+ " ... maybe we could just clear it
+ let prev_bufnr = b:nm_prev_bufnr
+ setlocal bufhidden=delete
+ else
+ setlocal bufhidden=hide
+ endif
+ call <SID>NM_cmd_search(tags)
+ setlocal bufhidden=delete
+ let b:nm_prev_bufnr = prev_bufnr
+endfunction
+
+function! s:NM_search_edit()
+ " TODO: input() can support completion
+ let text = input('NotMuch Search: ', join(b:nm_search_words, ' '))
+ if strlen(text)
+ call <SID>NM_cmd_search(split(text))
+ endif
+endfunction
+
+function! s:NM_search_mark_read_thread()
+ call <SID>NM_tag([], ['-unread'])
+ norm j
+endfunction
+
+function! s:NM_search_archive_thread()
+ call <SID>NM_tag([], ['-inbox'])
+ norm j
+endfunction
+
+function! s:NM_search_mark_read_then_archive_thread()
+ call <SID>NM_tag([], ['-unread', '-inbox'])
+ norm j
+endfunction
+
+function! s:NM_search_delete_thread()
+ call <SID>NM_tag([], ['+delete','-inbox','-unread'])
+ norm j
+endfunction
+
+function! s:NM_search_filter()
+ call <SID>NM_search_filter_helper('Filter: ', '', '')
+endfunction
+
+function! s:NM_search_filter_by_tag()
+ call <SID>NM_search_filter_helper('Filter Tag(s): ', 'tag:', 'and')
+endfunction
+
+function! s:NM_search_filter_helper(prompt, prefix, joiner)
+ " TODO: input() can support completion
+ let text = substitute(input(a:prompt), '\v(^\s*|\s*$|\n)', '', 'g')
+ if !strlen(text)
+ return
+ endif
+
+ let tags = b:nm_search_words + ['AND']
+ \ + <SID>NM_combine_tags(a:prefix, split(text), a:joiner, '()')
+
+ let prev_bufnr = bufnr('%')
+ setlocal bufhidden=hide
+ call <SID>NM_cmd_search(tags)
+ setlocal bufhidden=delete
+ let b:nm_prev_bufnr = prev_bufnr
+endfunction
+
+function! s:NM_search_toggle_order()
+ let g:notmuch_search_newest_first = !g:notmuch_search_newest_first
+ " FIXME: maybe this would be better done w/o reading re-reading the lines
+ " reversing the b:nm_raw_lines and the buffer lines would be better
+ call <SID>NM_search_refresh_view()
+endfunction
+
+function! s:NM_search_reply_to_thread()
+ let cmd = ['reply']
+ call add(cmd, <SID>NM_search_thread_id())
+ call add(cmd, 'AND')
+ call extend(cmd, <SID>NM_get_search_words())
+
+ let data = <SID>NM_run(cmd)
+ let lines = split(data, "\n")
+ call <SID>NM_newComposeBuffer(lines, 0)
+endfunction
+
+function! s:NM_search_add_tags(tags)
+ call <SID>NM_search_add_remove_tags('Add Tag(s): ', '+', a:tags)
+endfunction
+
+function! s:NM_search_remove_tags(tags)
+ call <SID>NM_search_add_remove_tags('Remove Tag(s): ', '-', a:tags)
+endfunction
+
+function! s:NM_search_refresh_view()
+ let lno = line('.')
+ let prev_bufnr = b:nm_prev_bufnr
+ setlocal bufhidden=delete
+ call <SID>NM_cmd_search(b:nm_search_words)
+ let b:nm_prev_bufnr = prev_bufnr
+ " FIXME: should find the line of the thread we were on if possible
+ exec printf('norm %dG', lno)
+endfunction
+
+" --- --- search screen helper functions {{{2
+
+function! s:NM_search_thread_id()
+ if !exists('b:nm_raw_lines')
+ throw 'Eeek! no b:nm_raw_lines'
+ endif
+ let mnum = line('.') - 1
+ if len(b:nm_raw_lines) <= mnum
+ return ''
+ endif
+ let info = b:nm_raw_lines[mnum]
+ let what = split(info, '\s\+')[0]
+ return what
+endfunction
+
+function! s:NM_search_add_remove_tags(prompt, prefix, intags)
+ if type(a:intags) != type([]) || len(a:intags) == 0
+ " TODO: input() can support completion
+ let text = input(a:prompt)
+ if !strlen(text)
+ return
+ endif
+ let tags = split(text, ' ')
+ else
+ let tags = a:intags
+ endif
+ call map(tags, 'a:prefix . v:val')
+ call <SID>NM_tag([], tags)
+endfunction
+
+" --- implement show screen {{{1
+
+function! s:NM_cmd_show(words)
+ let prev_bufnr = bufnr('%')
+ let data = s:NM_run(['show', '--entire-thread'] + a:words)
+ let lines = split(data, "\n")
+
+ let info = s:NM_cmd_show_parse(lines)
+
+ setlocal bufhidden=hide
+ call <SID>NM_newBuffer('', 'show', join(info['disp'], "\n"))
+ setlocal bufhidden=delete
+ let b:nm_search_words = a:words
+ let b:nm_raw_info = info
+ let b:nm_prev_bufnr = prev_bufnr
+
+ call <SID>NM_cmd_show_mkfolds()
+ call <SID>NM_cmd_show_mksyntax()
+ call <SID>NM_set_map('n', g:notmuch_show_maps)
+ setlocal foldtext=NM_cmd_show_foldtext()
+ setlocal fillchars=
+ setlocal foldcolumn=6
+
+endfunction
+
+function! s:NM_show_previous(can_change_thread, find_matching)
+ let everything = exists('b:nm_show_everything') ? b:nm_show_everything : 0
+ let info = b:nm_raw_info
+ let lnum = line('.')
+ for msg in reverse(copy(info['msgs']))
+ if a:find_matching && msg['match'] == '0'
+ continue
+ endif
+ if lnum <= msg['start']
+ continue
+ endif
+
+ exec printf('norm %dGzt', msg['start'])
+ " TODO: try to fit the message on screen
+ return
+ endfor
+ if !a:can_change_thread
+ return
+ endif
+ call <SID>NM_kill_this_buffer()
+ if line('.') > 1
+ norm k
+ call <SID>NM_search_show_thread(everything)
+ norm G
+ call <SID>NM_show_previous(0, a:find_matching)
+ else
+ echo 'No more messages.'
+ endif
+endfunction
+
+function! s:NM_show_next(can_change_thread, find_matching)
+ let info = b:nm_raw_info
+ let lnum = line('.')
+ for msg in info['msgs']
+ if a:find_matching && msg['match'] == '0'
+ continue
+ endif
+ if lnum >= msg['start']
+ continue
+ endif
+
+ exec printf('norm %dGzt', msg['start'])
+ " TODO: try to fit the message on screen
+ return
+ endfor
+ if a:can_change_thread
+ call <SID>NM_show_next_thread()
+ endif
+endfunction
+
+function! s:NM_show_next_thread()
+ let everything = exists('b:nm_show_everything') ? b:nm_show_everything : 0
+ call <SID>NM_kill_this_buffer()
+ if line('.') != line('$')
+ norm j
+ call <SID>NM_search_show_thread(everything)
+ else
+ echo 'No more messages.'
+ endif
+endfunction
+
+function! s:NM_show_mark_read_thread()
+ call <SID>NM_tag(b:nm_search_words, ['-unread'])
+ call <SID>NM_show_next_thread()
+endfunction
+
+function! s:NM_show_archive_thread()
+ call <SID>NM_tag(b:nm_search_words, ['-inbox'])
+ call <SID>NM_show_next_thread()
+endfunction
+
+function! s:NM_show_mark_read_then_archive_thread()
+ call <SID>NM_tag(b:nm_search_words, ['-unread', '-inbox'])
+ call <SID>NM_show_next_thread()
+endfunction
+
+function! s:NM_show_delete_thread()
+ call <SID>NM_tag(b:nm_search_words, ['+delete', '-inbox', '-unread'])
+ call <SID>NM_show_next_thread()
+endfunction
+
+function! s:NM_show_delete_message()
+ let msg = <SID>NM_show_get_message_for_line(line('.'))
+ call <SID>NM_tag([msg['id']], ['+delete', '-inbox', '-unread'])
+endfunction
+
+function! s:NM_show_mark_read_then_next_open_message()
+ echo 'not implemented'
+endfunction
+
+function! s:NM_show_previous_message()
+ echo 'not implemented'
+endfunction
+
+function! s:NM_show_reply()
+ let cmd = ['reply']
+ call add(cmd, <SID>NM_show_message_id())
+ call add(cmd, 'AND')
+ call extend(cmd, <SID>NM_get_search_words())
+
+ let data = <SID>NM_run(cmd)
+ let lines = split(data, "\n")
+ call <SID>NM_newComposeBuffer(lines, 0)
+endfunction
+
+function! s:NM_show_view_all_mime_parts()
+ echo 'not implemented'
+endfunction
+
+function! s:NM_show_view_raw_message()
+ echo 'not implemented'
+endfunction
+
+function! s:NM_show_add_tag()
+ echo 'not implemented'
+endfunction
+
+function! s:NM_show_remove_tag()
+ echo 'not implemented'
+endfunction
+
+" if entire message is not visible scroll down 1/2 page or less to get to the bottom of message
+" otherwise go to next message
+" any message that is viewed entirely has inbox and unread tags removed
+function! s:NM_show_advance_marking_read_and_archiving()
+ let advance_tags = ['unread', 'inbox']
+
+ let vis_top = line('w0')
+ let vis_bot = line('w$')
+
+ let msg_top = <SID>NM_show_get_message_for_line(vis_top)
+ if !has_key(msg_top,'id')
+ throw "No top visible message."
+ endif
+
+ " if the top message is the last message, just expunge the entire thread and move on
+ if msg_top['end'] == line('$')
+ let ids = []
+ for msg in b:nm_raw_info['msgs']
+ if has_key(msg,'match') && msg['match'] != '0'
+ call add(ids, msg['id'])
+ endif
+ endfor
+ let filter = <SID>NM_combine_tags('tag:', advance_tags, 'OR', '()')
+ \ + ['AND']
+ \ + <SID>NM_combine_tags('', ids, 'OR', '()')
+ call map(advance_tags, '"-" . v:val')
+ call <SID>NM_tag(filter, advance_tags)
+ call <SID>NM_show_next(1, 1)
+ return
+ endif
+
+ let msg_bot = <SID>NM_show_get_message_for_line(vis_bot)
+ if !has_key(msg_bot,'id')
+ throw "No bottom visible message."
+ endif
+
+ " if entire message fits on the screen, read/archive it, move to the next one
+ if msg_top['id'] != msg_bot['id'] || msg_top['end'] <= vis_bot
+ exec printf('norm %dG', vis_top)
+ call <SID>NM_show_next(0, 1)
+ if has_key(msg_top,'match') && msg_top['match'] != '0'
+ redraw
+ " do this last to hide the latency
+ let filter = <SID>NM_combine_tags('tag:', advance_tags, 'OR', '()')
+ \ + ['AND', msg_top['id']]
+ call map(advance_tags, '"-" . v:val')
+ call <SID>NM_tag(filter, advance_tags)
+ endif
+ return
+ endif
+
+ " entire message does not fit on the screen, scroll down to bottom, max 1/2 screen
+ let jmp = winheight(winnr()) / 2
+ let max = msg_bot['end'] - vis_bot
+ if jmp > max
+ let jmp = max
+ endif
+ exec printf('norm %dGzt', vis_top + jmp)
+ return
+endfunction
+
+function! s:NM_show_pipe_message()
+ echo 'not implemented'
+endfunction
+
+function! s:NM_show_previous_fold()
+ echo 'not implemented'
+endfunction
+
+function! s:NM_show_next_fold()
+ echo 'not implemented'
+endfunction
+
+function! s:NM_show_toggle_fold()
+ echo 'not implemented'
+endfunction
+
+
+" --- --- show screen helper functions {{{2
+
+function! s:NM_show_get_message_for_line(line)
+ for msg in b:nm_raw_info['msgs']
+ if a:line > msg['end']
+ continue
+ endif
+ return msg
+ endfor
+ return {}
+endfunction
+
+function! s:NM_show_message_id()
+ if !exists('b:nm_raw_info')
+ throw 'Eeek! no b:nm_raw_info'
+ endif
+ let msg = <SID>NM_show_get_message_for_line(line('.'))
+ if has_key(msg,'id')
+ return msg['id']
+ endif
+ return ''
+endfunction
+
+function! s:NM_show_fold_toggle(key, type, fold)
+ let info = b:nm_raw_info
+ let act = 'open'
+ if a:fold
+ let act = 'close'
+ endif
+ for fld in info['folds']
+ if fld[0] != a:type
+ continue
+ endif
+ "let idx = fld[3]
+ "let msg = info['msgs'][idx]
+ "if has_key(msg,'match') && msg['match'] == '0'
+ " continue
+ "endif
+ let cls = foldclosed(fld[1])
+ if cls != -1 && cls != fld[1]
+ continue
+ endif
+ exec printf('%dfold%s', fld[1], act)
+ endfor
+ exec printf('nnoremap <buffer> %s :call <SID>NM_show_fold_toggle(''%s'', ''%s'', %d)<CR>', a:key, a:key, a:type, !a:fold)
+endfunction
+
+
+" s:NM_cmd_show_parse returns the following dictionary:
+" 'disp': lines to display
+" 'msgs': message info dicts { start, end, id, depth, filename, descr, header }
+" 'folds': fold info arrays [ type, start, end ]
+" 'foldtext': fold text indexed by start line
+function! s:NM_cmd_show_parse(inlines)
+ let info = { 'disp': [],
+ \ 'msgs': [],
+ \ 'folds': [],
+ \ 'foldtext': {} }
+ let msg = {}
+ let hdr = {}
+
+ let in_message = 0
+ let in_header = 0
+ let in_body = 0
+ let in_part = ''
+
+ let body_start = -1
+ let part_start = -1
+
+ let mode_type = ''
+ let mode_start = -1
+
+ let inlnum = 0
+ for line in a:inlines
+ let inlnum = inlnum + 1
+ let foldinfo = []
+
+ if strlen(in_part)
+ let part_end = 0
+
+ if match(line, g:notmuch_show_part_end_regexp) != -1
+ let part_end = len(info['disp'])
+ else
+ call add(info['disp'], line)
+ endif
+
+ if in_part == 'text/plain'
+ if !part_end && mode_type == ''
+ if match(line, g:notmuch_show_signature_regexp) != -1
+ let mode_type = 'sig'
+ let mode_start = len(info['disp'])
+ elseif match(line, g:notmuch_show_citation_regexp) != -1
+ let mode_type = 'cit'
+ let mode_start = len(info['disp'])
+ endif
+ elseif mode_type == 'cit'
+ if part_end || match(line, g:notmuch_show_citation_regexp) == -1
+ let outlnum = len(info['disp'])
+ if !part_end
+ let outlnum = outlnum - 1
+ endif
+ let foldinfo = [ mode_type, mode_start, outlnum, len(info['msgs']),
+ \ printf('[ %d-line citation. Press "c" to show. ]', 1 + outlnum - mode_start) ]
+ let mode_type = ''
+ endif
+ elseif mode_type == 'sig'
+ let outlnum = len(info['disp'])
+ if (outlnum - mode_start) > g:notmuch_show_signature_lines_max
+ let mode_type = ''
+ elseif part_end
+ let foldinfo = [ mode_type, mode_start, outlnum, len(info['msgs']),
+ \ printf('[ %d-line signature. Press "i" to show. ]', 1 + outlnum - mode_start) ]
+ let mode_type = ''
+ endif
+ endif
+ endif
+
+ if part_end
+ " FIXME: this is a hack for handling two folds being added for one line
+ " we should handle adding a fold in a function
+ if len(foldinfo) && foldinfo[1] < foldinfo[2]
+ call add(info['folds'], foldinfo[0:3])
+ let info['foldtext'][foldinfo[1]] = foldinfo[4]
+ endif
+
+ let foldinfo = [ 'text', part_start, part_end, len(info['msgs']),
+ \ printf('[ %d-line %s. Press "p" to show. ]', part_end - part_start, in_part) ]
+ let in_part = ''
+ call add(info['disp'], '')
+ endif
+
+ elseif in_body
+ if !has_key(msg,'body_start')
+ let msg['body_start'] = len(info['disp']) + 1
+ endif
+ if match(line, g:notmuch_show_body_end_regexp) != -1
+ let body_end = len(info['disp'])
+ let foldinfo = [ 'bdy', body_start, body_end, len(info['msgs']),
+ \ printf('[ BODY %d - %d lines ]', len(info['msgs']), body_end - body_start) ]
+
+ let in_body = 0
+
+ elseif match(line, g:notmuch_show_part_begin_regexp) != -1
+ let m = matchlist(line, 'ID: \(\d\+\), Content-type: \(\S\+\)')
+ let in_part = 'unknown'
+ if len(m)
+ let in_part = m[2]
+ endif
+ call add(info['disp'],
+ \ printf('--- %s ---', in_part))
+ " We don't yet handle nested parts, so pop
+ " multipart/* immediately so text/plain
+ " sub-parts are parsed properly
+ if match(in_part, '^multipart/') != -1
+ let in_part = ''
+ else
+ let part_start = len(info['disp']) + 1
+ endif
+ endif
+
+ elseif in_header
+ if in_header == 1
+ let msg['descr'] = line
+ call add(info['disp'], line)
+ let in_header = 2
+ let msg['hdr_start'] = len(info['disp']) + 1
+
+ else
+ if match(line, g:notmuch_show_header_end_regexp) != -1
+ let hdr_start = msg['hdr_start']+1
+ let hdr_end = len(info['disp'])
+ let foldinfo = [ 'hdr', hdr_start, hdr_end, len(info['msgs']),
+ \ printf('[ %d-line headers. Press "h" to show. ]', hdr_end + 1 - hdr_start) ]
+ let msg['header'] = hdr
+ let in_header = 0
+ let hdr = {}
+ else
+ let m = matchlist(line, '^\(\w\+\):\s*\(.*\)$')
+ if len(m)
+ let hdr[m[1]] = m[2]
+ if match(g:notmuch_show_headers, m[1]) != -1
+ call add(info['disp'], line)
+ endif
+ endif
+ endif
+ endif
+
+ elseif in_message
+ if match(line, g:notmuch_show_message_end_regexp) != -1
+ let msg['end'] = len(info['disp'])
+ call add(info['disp'], '')
+
+ let foldinfo = [ 'msg', msg['start'], msg['end'], len(info['msgs']),
+ \ printf('[ MSG %d - %s ]', len(info['msgs']), msg['descr']) ]
+
+ call add(info['msgs'], msg)
+ let msg = {}
+ let in_message = 0
+ let in_header = 0
+ let in_body = 0
+ let in_part = ''
+
+ elseif match(line, g:notmuch_show_header_begin_regexp) != -1
+ let in_header = 1
+ continue
+
+ elseif match(line, g:notmuch_show_body_begin_regexp) != -1
+ let body_start = len(info['disp']) + 1
+ let in_body = 1
+ continue
+ endif
+
+ else
+ if match(line, g:notmuch_show_message_begin_regexp) != -1
+ let msg['start'] = len(info['disp']) + 1
+
+ let m = matchlist(line, g:notmuch_show_message_parse_regexp)
+ if len(m)
+ let msg['id'] = m[1]
+ let msg['depth'] = m[2]
+ let msg['match'] = m[3]
+ let msg['excluded'] = m[4]
+ let msg['filename'] = m[5]
+ endif
+
+ let in_message = 1
+ endif
+ endif
+
+ if len(foldinfo) && foldinfo[1] < foldinfo[2]
+ call add(info['folds'], foldinfo[0:3])
+ let info['foldtext'][foldinfo[1]] = foldinfo[4]
+ endif
+ endfor
+ return info
+endfunction
+
+function! s:NM_cmd_show_mkfolds()
+ let info = b:nm_raw_info
+
+ for afold in info['folds']
+ exec printf('%d,%dfold', afold[1], afold[2])
+ let state = 'open'
+ if (afold[0] == 'sig' && g:notmuch_show_fold_signatures)
+ \ || (afold[0] == 'cit' && g:notmuch_show_fold_citations)
+ \ || (afold[0] == 'bdy' && g:notmuch_show_fold_bodies)
+ \ || (afold[0] == 'hdr' && g:notmuch_show_fold_headers)
+ let state = 'close'
+ elseif afold[0] == 'msg'
+ let idx = afold[3]
+ let msg = info['msgs'][idx]
+ if has_key(msg,'match') && msg['match'] == '0'
+ let state = 'close'
+ endif
+ endif
+ exec printf('%dfold%s', afold[1], state)
+ endfor
+endfunction
+
+function! s:NM_cmd_show_mksyntax()
+ let info = b:nm_raw_info
+ let cnt = 0
+ for msg in info['msgs']
+ let cnt = cnt + 1
+ let start = msg['start']
+ let hdr_start = msg['hdr_start']
+ let body_start = msg['body_start']
+ let end = msg['end']
+ exec printf('syntax region nmShowMsg%dDesc start=''\%%%dl'' end=''\%%%dl'' contains=@nmShowMsgDesc', cnt, start, start+1)
+ exec printf('syntax region nmShowMsg%dHead start=''\%%%dl'' end=''\%%%dl'' contains=@nmShowMsgHead', cnt, hdr_start, body_start)
+ exec printf('syntax region nmShowMsg%dBody start=''\%%%dl'' end=''\%%%dl'' contains=@nmShowMsgBody', cnt, body_start, end)
+ endfor
+endfunction
+
+function! NM_cmd_show_foldtext()
+ let foldtext = b:nm_raw_info['foldtext']
+ return foldtext[v:foldstart]
+endfunction
+
+
+" --- implement compose screen {{{1
+
+function! s:NM_cmd_compose(words, body_lines)
+ let lines = []
+ let start_on_line = 0
+
+ let hdrs = { }
+ for word in a:words
+ let m = matchlist(word, '^\(\w[^:]*\):\s*\(.*\)\s*$')
+ if !len(m)
+ throw 'Eeek! bad parameter ''' . string(word) . ''''
+ endif
+ let key = substitute(m[1], '\<\w', '\U&', 'g')
+ if !has_key(hdrs, key)
+ let hdrs[key] = []
+ endif
+ if strlen(m[2])
+ call add(hdrs[key], m[2])
+ endif
+ endfor
+
+ if !has_key(hdrs, 'From') || !len(hdrs['From'])
+ let me = <SID>NM_compose_get_user_email()
+ let hdrs['From'] = [ me ]
+ endif
+
+ for key in g:notmuch_compose_headers
+ let text = has_key(hdrs, key) ? join(hdrs[key], ', ') : ''
+ call add(lines, key . ': ' . text)
+ if !start_on_line && !strlen(text)
+ let start_on_line = len(lines)
+ endif
+ endfor
+
+ for [key,val] in items(hdrs)
+ if match(g:notmuch_compose_headers, key) == -1
+ let line = key . ': ' . join(val, ', ')
+ call add(lines, line)
+ endif
+ endfor
+
+ call add(lines, '')
+ if !start_on_line
+ let start_on_line = len(lines) + 1
+ endif
+
+ if len(a:body_lines)
+ call extend(lines, a:body_lines)
+ else
+ call extend(lines, [ '', '' ])
+ endif
+
+ call <SID>NM_newComposeBuffer(lines, start_on_line)
+endfunction
+
+function! s:NM_compose_send()
+ call <SID>NM_assert_buffer_type('compose')
+ let fname = expand('%')
+ let lnum = 1
+ let line = getline(lnum)
+ let lst_hdr = ''
+ while match(line, '^$') == -1
+ if !exists("hdr_starts") && match(line, '^Notmuch-Help:') == -1
+ let hdr_starts = lnum - 1
+ endif
+ let lnum = lnum + 1
+ let line = getline(lnum)
+ endwhile
+ let body_starts = lnum - 1
+
+ call append(body_starts, 'Date: ' . strftime('%a, %d %b %Y %H:%M:%S %z'))
+ exec printf(':0,%dd', hdr_starts)
+ write
+
+ let line = getline(1)
+ let m = matchlist(line, '^From:\s*\(.*\)\s*<\(.*\)>$')
+ if (len(m) >= 2)
+ let from = m[2]
+ else
+ let m = matchlist(line, '^From:\s*\(.*\)$')
+ let from = m[1]
+ endif
+
+ let cmdtxt = g:notmuch_sendmail . ' -t -f ' . from . ' < ' . fname
+ let out = system(cmdtxt)
+ let err = v:shell_error
+ if err
+ undo
+ write
+ call <SID>NM_newBuffer('new', 'error',
+ \ "While running...\n" .
+ \ ' ' . cmdtxt . "\n" .
+ \ "\n" .
+ \ "Failed with...\n" .
+ \ substitute(out, '^', ' ', 'g'))
+ echohl Error
+ echo 'Eeek! unable to send mail'
+ echohl None
+ return
+ endif
+
+ if !exists('b:nm_prev_bufnr')
+ bdelete
+ else
+ let prev_bufnr = b:nm_prev_bufnr
+ bdelete
+ if prev_bufnr == bufnr('%')
+ exec printf("buffer %d", prev_bufnr)
+ endif
+ endif
+ call delete(fname)
+ echo 'Mail sent successfully.'
+endfunction
+
+function! s:NM_compose_attach()
+ echo 'not implemented'
+endfunction
+
+function! s:NM_compose_next_entry_area()
+ let lnum = line('.')
+ let hdr_end = <SID>NM_compose_find_line_match(1,'^$',1)
+ if lnum < hdr_end
+ let lnum = lnum + 1
+ let line = getline(lnum)
+ if match(line, '^\([^:]\+\):\s*$') == -1
+ call cursor(lnum, strlen(line) + 1)
+ return ''
+ endif
+ while match(getline(lnum+1), '^\s') != -1
+ let lnum = lnum + 1
+ endwhile
+ call cursor(lnum, strlen(getline(lnum)) + 1)
+ return ''
+
+ elseif lnum == hdr_end
+ call cursor(lnum+1, strlen(getline(lnum+1)) + 1)
+ return ''
+ endif
+ if mode() == 'i'
+ if !getbufvar(bufnr('.'), '&et')
+ return "\t"
+ endif
+ let space = ''
+ let shiftwidth = a:shiftwidth
+ let shiftwidth = shiftwidth - ((virtcol('.')-1) % shiftwidth)
+ " we assume no one has shiftwidth set to more than 40 :)
+ return ' '[0:shiftwidth]
+ endif
+endfunction
+
+" --- --- compose screen helper functions {{{2
+
+function! s:NM_compose_get_user_email()
+ " TODO: do this properly (still), i.e., allow for multiple email accounts
+ let email = substitute(system('notmuch config get user.primary_email'), '\v(^\s*|\s*$|\n)', '', 'g')
+ return email
+endfunction
+
+function! s:NM_compose_find_line_match(start, pattern, failure)
+ let lnum = a:start
+ let lend = line('$')
+ while lnum < lend
+ if match(getline(lnum), a:pattern) != -1
+ return lnum
+ endif
+ let lnum = lnum + 1
+ endwhile
+ return a:failure
+endfunction
+
+
+" --- notmuch helper functions {{{1
+
+function! s:NM_newBuffer(how, type, content)
+ if strlen(a:how)
+ exec a:how
+ else
+ enew
+ endif
+ setlocal buftype=nofile readonly modifiable scrolloff=0 sidescrolloff=0
+ silent put=a:content
+ keepjumps 0d
+ setlocal nomodifiable
+ execute printf('set filetype=notmuch-%s', a:type)
+ execute printf('set syntax=notmuch-%s', a:type)
+ let b:nm_type = a:type
+endfunction
+
+function! s:NM_newFileBuffer(fdir, fname, type, lines)
+ let fdir = expand(a:fdir)
+ if !isdirectory(fdir)
+ call mkdir(fdir, 'p')
+ endif
+ let file_name = <SID>NM_mktemp(fdir, a:fname)
+ if writefile(a:lines, file_name)
+ throw 'Eeek! couldn''t write to temporary file ' . file_name
+ endif
+ exec printf('edit %s', file_name)
+ setlocal buftype= noreadonly modifiable scrolloff=0 sidescrolloff=0
+ execute printf('set filetype=notmuch-%s', a:type)
+ execute printf('set syntax=notmuch-%s', a:type)
+ let b:nm_type = a:type
+endfunction
+
+function! s:NM_newComposeBuffer(lines, start_on_line)
+ let lines = a:lines
+ let start_on_line = a:start_on_line
+ let real_hdr_start = 1
+ if g:notmuch_compose_header_help
+ let help_lines = [
+ \ 'Notmuch-Help: Type in your message here; to help you use these bindings:',
+ \ 'Notmuch-Help: ,a - attach a file',
+ \ 'Notmuch-Help: ,s - send the message (Notmuch-Help lines will be removed)',
+ \ 'Notmuch-Help: ,q - abort the message',
+ \ 'Notmuch-Help: <Tab> - skip through header lines',
+ \ ]
+ call extend(lines, help_lines, 0)
+ let real_hdr_start = len(help_lines)
+ if start_on_line > 0
+ let start_on_line = start_on_line + len(help_lines)
+ endif
+ endif
+ call extend(lines, g:notmuch_signature)
+
+
+ let prev_bufnr = bufnr('%')
+ setlocal bufhidden=hide
+ call <SID>NM_newFileBuffer(g:notmuch_compose_temp_file_dir, '%s.mail',
+ \ 'compose', lines)
+ setlocal bufhidden=hide
+ let b:nm_prev_bufnr = prev_bufnr
+
+ call <SID>NM_set_map('n', g:notmuch_compose_nmaps)
+ call <SID>NM_set_map('i', g:notmuch_compose_imaps)
+
+ if start_on_line > 0 && start_on_line <= len(lines)
+ call cursor(start_on_line, strlen(getline(start_on_line)) + 1)
+ else
+ call cursor(real_hdr_start, strlen(getline(real_hdr_start)) + 1)
+ call <SID>NM_compose_next_entry_area()
+ endif
+
+ if g:notmuch_compose_insert_mode_start
+ startinsert!
+ endif
+ echo 'Type your message, use <TAB> to jump to next header and then body.'
+endfunction
+
+function! s:NM_assert_buffer_type(type)
+ if !exists('b:nm_type') || b:nm_type != a:type
+ throw printf('Eeek! expected type %s, but got %s.', a:type,
+ \ exists(b:nm_type) ? b:nm_type : 'something else')
+ endif
+endfunction
+
+function! s:NM_mktemp(dir, name)
+ let time_stamp = strftime('%Y%m%d-%H%M%S')
+ let file_name = substitute(a:dir,'/*$','/','') . printf(a:name, time_stamp)
+ " TODO: check if it exists, try again
+ return file_name
+endfunction
+
+function! s:NM_shell_escape(word)
+ " TODO: use shellescape()
+ let word = substitute(a:word, '''', '\\''', 'g')
+ return '''' . word . ''''
+endfunction
+
+" this function was taken from git.vim, then fixed up
+" http://github.com/motemen/git-vim
+function! s:NM_shell_split(cmd)
+ let l:split_cmd = []
+ let cmd = a:cmd
+ let iStart = 0
+ while 1
+ let t = match(cmd, '\S', iStart)
+ if t < iStart
+ break
+ endif
+ let iStart = t
+
+ let iSpace = match(cmd, '\v(\s|$)', iStart)
+ if iSpace < iStart
+ break
+ endif
+
+ let iQuote1 = match(cmd, '\(^["'']\|[^\\]\@<=["'']\)', iStart)
+ if iQuote1 > iSpace || iQuote1 < iStart
+ let iEnd = iSpace - 1
+ let l:split_cmd += [ cmd[iStart : iEnd] ]
+ else
+ let q = cmd[iQuote1]
+ let iQuote2 = match(cmd, '[^\\]\@<=[' . q . ']', iQuote1 + 1)
+ if iQuote2 < iQuote1
+ throw 'No matching ' . q . ' quote'
+ endif
+ let iEnd = iQuote2
+ let l:split_cmd += [ cmd[iStart+1 : iEnd-1 ] ]
+ endif
+
+
+ let iStart = iEnd + 1
+ endwhile
+
+ return l:split_cmd
+endfunction
+
+
+function! s:NM_run(args)
+ let words = a:args
+ call map(words, 's:NM_shell_escape(v:val)')
+ let cmd = g:notmuch_cmd . ' ' . join(words) . '< /dev/null'
+
+ if exists('g:notmuch_debug') && g:notmuch_debug
+ let start = reltime()
+ let out = system(cmd)
+ let err = v:shell_error
+ let delta = reltime(start)
+
+ echo printf('[%s] {%s} %s', reltimestr(delta), string(err), string(cmd))
+ else
+ let out = system(cmd)
+ let err = v:shell_error
+ endif
+
+ if err
+ echohl Error
+ echo substitute(out, '\n*$', '', '')
+ echohl None
+ return ''
+ else
+ return out
+ endif
+endfunction
+
+" --- external mail handling helpers {{{1
+
+function! s:NM_new_mail()
+ call <SID>NM_cmd_compose([], [])
+endfunction
+
+" --- tag manipulation helpers {{{1
+
+" used to combine an array of words with prefixes and separators
+" example:
+" NM_combine_tags('tag:', ['one', 'two', 'three'], 'OR', '()')
+" -> ['(', 'tag:one', 'OR', 'tag:two', 'OR', 'tag:three', ')']
+function! s:NM_combine_tags(word_prefix, words, separator, brackets)
+ let res = []
+ for word in a:words
+ if len(res) && strlen(a:separator)
+ call add(res, a:separator)
+ endif
+ call add(res, a:word_prefix . word)
+ endfor
+ if len(res) > 1 && strlen(a:brackets)
+ if strlen(a:brackets) != 2
+ throw 'Eeek! brackets arg to NM_combine_tags must be 2 chars'
+ endif
+ call insert(res, a:brackets[0])
+ call add(res, a:brackets[1])
+ endif
+ return res
+endfunction
+
+" --- other helpers {{{1
+
+function! s:NM_get_search_words()
+ if !exists('b:nm_search_words')
+ throw 'Eeek! no b:nm_search_words'
+ endif
+ return b:nm_search_words
+endfunction
+
+function! s:NM_kill_this_buffer()
+ if exists('b:nm_prev_bufnr')
+ let prev_bufnr = b:nm_prev_bufnr
+ bdelete!
+ exec printf("buffer %d", prev_bufnr)
+ else
+ echo "This is the last buffer; use :q<CR> to quit."
+ endif
+endfunction
+
+function! s:NM_search_expand(arg)
+ let word = expand(a:arg)
+ let prev_bufnr = bufnr('%')
+ setlocal bufhidden=hide
+ call <SID>NM_cmd_search([word])
+ setlocal bufhidden=delete
+ let b:nm_prev_bufnr = prev_bufnr
+endfunction
+
+function! s:NM_tag(filter, tags)
+ let filter = len(a:filter) ? a:filter : [<SID>NM_search_thread_id()]
+ if !len(filter)
+ throw 'Eeek! I couldn''t find the thread id!'
+ endif
+ let args = ['tag']
+ call extend(args, a:tags)
+ call add(args, '--')
+ call extend(args, filter)
+ " TODO: handle errors
+ call <SID>NM_run(args)
+endfunction
+
+" --- process and set the defaults {{{1
+
+function! NM_set_defaults(force)
+ for [key, dflt] in items(s:notmuch_defaults)
+ let cmd = ''
+ if !a:force && exists(key) && type(dflt) == type(eval(key))
+ continue
+ elseif type(dflt) == type(0)
+ let cmd = printf('let %s = %d', key, dflt)
+ elseif type(dflt) == type('')
+ let cmd = printf('let %s = ''%s''', key, dflt)
+ " FIXME: not sure why this didn't work when dflt is an array
+ "elseif type(dflt) == type([])
+ " let cmd = printf('let %s = %s', key, string(dflt))
+ else
+ echoe printf('E: Unknown type in NM_set_defaults(%d) using [%s,%s]',
+ \ a:force, key, string(dflt))
+ continue
+ endif
+ exec cmd
+ endfor
+endfunction
+call NM_set_defaults(0)
+
+" for some reason NM_set_defaults() didn't work for arrays...
+if !exists('g:notmuch_show_headers')
+ let g:notmuch_show_headers = s:notmuch_show_headers_defaults
+endif
+if !exists('g:notmuch_initial_search_words')
+ let g:notmuch_initial_search_words = s:notmuch_initial_search_words_defaults
+endif
+if !exists('g:notmuch_folders')
+ let g:notmuch_folders = s:notmuch_folders_defaults
+endif
+
+if !exists('g:notmuch_signature')
+ let g:notmuch_signature = s:notmuch_signature_defaults
+endif
+if !exists('g:notmuch_compose_headers')
+ let g:notmuch_compose_headers = s:notmuch_compose_headers_defaults
+endif
+
+" --- assign keymaps {{{1
+
+function! s:NM_set_map(type, maps)
+ nmapclear
+ for [key, code] in items(a:maps)
+ exec printf('%snoremap <buffer> %s %s', a:type, key, code)
+ endfor
+ " --- this is a hack for development :)
+ nnoremap ,nmr :runtime! plugin/notmuch.vim<CR>
+endfunction
+
+" --- command handler {{{1
+
+function! NotMuch(args)
+ let args = a:args
+ if !strlen(args)
+ let args = 'folders'
+ endif
+
+ let words = <SID>NM_shell_split(args)
+ if words[0] == 'folders' || words[0] == 'f'
+ let words = words[1:]
+ call <SID>NM_cmd_folders(words)
+
+ elseif words[0] == 'search' || words[0] == 's'
+ if len(words) > 1
+ let words = words[1:]
+ elseif exists('b:nm_search_words')
+ let words = b:nm_search_words
+ else
+ let words = g:notmuch_initial_search_words
+ endif
+ call <SID>NM_cmd_search(words)
+
+ elseif words[0] == 'show'
+ echoe 'show is not yet implemented.'
+
+ elseif words[0] == 'new' || words[0] == 'compose'
+ let words = words[1:]
+ call <SID>NM_cmd_compose(words, [])
+ endif
+endfunction
+function! CompleteNotMuch(arg_lead, cmd_line, cursor_pos)
+ return []
+endfunction
+
+
+" --- glue {{{1
+
+command! -nargs=* -complete=customlist,CompleteNotMuch NotMuch call NotMuch(<q-args>)
+cabbrev notmuch <c-r>=(getcmdtype()==':' && getcmdpos()==1 ? 'NotMuch' : 'notmuch')<CR>
+
+" vim: set ft=vim ts=8 sw=8 et foldmethod=marker :
--- /dev/null
+runtime! syntax/mail.vim
+
+syntax region nmComposeHelp contains=nmComposeHelpLine start='^Notmuch-Help:\%1l' end='^\(Notmuch-Help:\)\@!'
+syntax match nmComposeHelpLine /Notmuch-Help:/ contained
+
+highlight link nmComposeHelp Include
+highlight link nmComposeHelpLine Error
--- /dev/null
+" notmuch folders mode syntax file
+
+syntax region nmFoldersCount start='^' end='\%10v'
+syntax region nmFoldersName start='\%11v' end='\%31v'
+syntax match nmFoldersSearch /([^()]\+)$/
+
+highlight link nmFoldersCount Statement
+highlight link nmFoldersName Type
+highlight link nmFoldersSearch String
+
+highlight CursorLine term=reverse cterm=reverse gui=reverse
+
--- /dev/null
+syn match diffRemoved "^-.*"
+syn match diffAdded "^+.*"
+
+syn match diffSeparator "^---$"
+syn match diffSubname " @@..*"ms=s+3 contained
+syn match diffLine "^@.*" contains=diffSubname
+
+syn match diffFile "^diff .*"
+syn match diffNewFile "^+++ .*"
+syn match diffOldFile "^--- .*"
+
+hi def link diffOldFile diffFile
+hi def link diffNewFile diffFile
+
+hi def link diffFile Type
+hi def link diffRemoved Special
+hi def link diffAdded Identifier
+hi def link diffLine Statement
+hi def link diffSubname PreProc
+
+syntax match gitDiffStatLine /^ .\{-}\zs[+-]\+$/ contains=gitDiffStatAdd,gitDiffStatDelete
+syntax match gitDiffStatAdd /+/ contained
+syntax match gitDiffStatDelete /-/ contained
+
+hi def link gitDiffStatAdd diffAdded
+hi def link gitDiffStatDelete diffRemoved
--- /dev/null
+syntax region nmSearch start=/^/ end=/$/ oneline contains=nmSearchDate
+syntax match nmSearchDate /^.\{-13}/ contained nextgroup=nmSearchNum
+syntax match nmSearchNum /.\{-4}/ contained nextgroup=nmSearchFrom
+syntax match nmSearchFrom /.\{-21}/ contained nextgroup=nmSearchSubject
+syntax match nmSearchSubject /.\{0,}\(([^()]\+)$\)\@=/ contained nextgroup=nmSearchTags
+syntax match nmSearchTags /.\+$/ contained
+
+highlight link nmSearchDate Statement
+highlight link nmSearchNum Type
+highlight link nmSearchFrom Include
+highlight link nmSearchSubject Normal
+highlight link nmSearchTags String
--- /dev/null
+" notmuch show mode syntax file
+
+syntax cluster nmShowMsgDesc contains=nmShowMsgDescWho,nmShowMsgDescDate,nmShowMsgDescTags
+syntax match nmShowMsgDescWho /[^)]\+)/ contained
+syntax match nmShowMsgDescDate / ([^)]\+[0-9]) / contained
+syntax match nmShowMsgDescTags /([^)]\+)$/ contained
+
+syntax cluster nmShowMsgHead contains=nmShowMsgHeadKey,nmShowMsgHeadVal
+syntax match nmShowMsgHeadKey /^[^:]\+: / contained
+syntax match nmShowMsgHeadVal /^\([^:]\+: \)\@<=.*/ contained
+
+syntax cluster nmShowMsgBody contains=@nmShowMsgBodyMail,@nmShowMsgBodyGit
+syntax include @nmShowMsgBodyMail syntax/mail.vim
+
+silent! syntax include @nmShowMsgBodyGit syntax/notmuch-git-diff.vim
+
+highlight nmShowMsgDescWho term=reverse cterm=reverse gui=reverse
+highlight link nmShowMsgDescDate Type
+highlight link nmShowMsgDescTags String
+
+highlight link nmShowMsgHeadKey Macro
+"highlight link nmShowMsgHeadVal NONE
+
+highlight Folded term=reverse ctermfg=LightGrey ctermbg=Black guifg=LightGray guibg=Black
+notmuch (0.16-1) unstable; urgency=low
+
+ * The vim interface is no longer provided as a Debian package, due
+ to upstream deprecation.
+
+ -- 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
This package provides an emacs based mail user agent based on
notmuch.
-Package: notmuch-vim
-Architecture: all
-Section: mail
-Breaks: notmuch (<<0.6~254~)
-Replaces: notmuch (<<0.6~254~)
-Depends: ${misc:Depends}, notmuch, vim-addon-manager
-Description: thread-based email index, search and tagging (vim interface)
- Notmuch is a system for indexing, searching, reading, and tagging
- large collections of email messages in maildir or mh format. It uses
- the Xapian library to provide fast, full-text search with a very
- convenient search syntax.
- .
- This package provides a vim based mail user agent based on
- notmuch.
-
Package: notmuch-mutt
Architecture: all
Depends: notmuch, libmail-box-perl, libmailtools-perl,
Change 'a' command in thread-view mode to only archive open messages.
-Add a binding to open all closed messages.
-
-Change the 'a'rchive command in the thread view to only archive open
-messages.
-
Completion
----------
Fix bash completion to complete multiple search options (both --first
Message-ID). I'm not sure what the option should be named. Perhaps
--with-duplicates ?
-Add a -0 option to "notmuch search" so that one can safely deal with
-any filename with:
-
- notmuch search --output=files -0 <terms> | xargs -0 <command>
-
"notmuch setup" should use realpath() before replacing the
configuration file. The ensures that the final target file of any
intermediate symbolic links is what is actually replaced, (rather than
Allow configuration for filename patterns that should be ignored when
indexing.
-Replace the "notmuch part --part=id" command with "notmuch show
---part=id", (David Edmondson wants to rewrite some of "notmuch show" to
-provide more MIME-structure information in its output first).
-
-Replace the "notmuch search-tags" command with "notmuch search
---output=tags".
-
Fix to avoid this ugly message:
(process:17197): gmime-CRITICAL **: g_mime_message_get_mime_part: assertion `GMIME_IS_MESSAGE (message)' failed
Add an interface to accept a "key" and a byte stream, rather than a
filename.
-Provide a sane syntax for date ranges. First, we don't want to require
-both endpoints to be specified. For example it would be nice to be
-able to say things like "since:2009-01-1" or "until:2009-01-1" and
-have the other endpoint be implicit. Second we'd like to support
-relative specifications of time such as "since:'2 months ago'". To do
-any of this we're probably going to need to break down an write our
+Improve syntax for date ranges queries. date:expr should be
+interpreted as date:expr..expr so that, for example, "date:2013-01-22"
+would cover the whole of the specified day (currently that's not even
+recognized as a date range expression). It might be nice to be able to
+use things like "since:2013-01-22" and "until:2013-01-22" as synonyms
+to "date:2013-01-22.." and "date:..2013-01-22", respectively. To do
+any of this we're probably going to need to break down and write our
own parser for the query string rather than using Xapian's QueryParser
class.
--- /dev/null
+#!/usr/bin/env perl
+# Copyright (c) 2011 David Bremner
+# License: same as notmuch
+
+use strict;
+use warnings;
+use File::Temp qw(tempdir);
+use Pod::Usage;
+
+no encoding;
+
+my $NMBGIT = $ENV{NMBGIT} || $ENV{HOME}.'/.nmbug';
+
+$NMBGIT .= '/.git' if (-d $NMBGIT.'/.git');
+
+my $TAGPREFIX = $ENV{NMBPREFIX} || 'notmuch::';
+
+# magic hash for git
+my $EMPTYBLOB = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391';
+
+# for encoding
+
+my $ESCAPE_CHAR = '%';
+my $NO_ESCAPE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.
+ '0123456789+-_@=.:,';
+my $MUST_ENCODE = qr{[^\Q$NO_ESCAPE\E]};
+my $ESCAPED_RX = qr{$ESCAPE_CHAR([A-Fa-f0-9]{2})};
+
+my %command = (
+ archive => \&do_archive,
+ checkout => \&do_checkout,
+ commit => \&do_commit,
+ fetch => \&do_fetch,
+ help => \&do_help,
+ log => \&do_log,
+ merge => \&do_merge,
+ pull => \&do_pull,
+ push => \&do_push,
+ status => \&do_status,
+ );
+
+my $subcommand = shift || usage ();
+
+if (!exists $command{$subcommand}) {
+ usage ();
+}
+
+&{$command{$subcommand}}(@ARGV);
+
+sub git_pipe {
+ my $envref = (ref $_[0] eq 'HASH') ? shift : {};
+ my $ioref = (ref $_[0] eq 'ARRAY') ? shift : undef;
+ my $dir = ($_[0] eq '-|' or $_[0] eq '|-') ? shift : undef;
+
+ unshift @_, 'git';
+ $envref->{GIT_DIR} ||= $NMBGIT;
+ spawn ($envref, defined $ioref ? $ioref : (), defined $dir ? $dir : (), @_);
+}
+
+sub git {
+ my $fh = git_pipe (@_);
+ my $str = join ('', <$fh>);
+ unless (close $fh) {
+ die "'git @_' exited with nonzero value\n";
+ }
+ chomp($str);
+ return $str;
+}
+
+sub spawn {
+ my $envref = (ref $_[0] eq 'HASH') ? shift : {};
+ my $ioref = (ref $_[0] eq 'ARRAY') ? shift : undef;
+ my $dir = ($_[0] eq '-|' or $_[0] eq '|-') ? shift : '-|';
+
+ die unless @_;
+
+ if (open my $child, $dir) {
+ return $child;
+ }
+ # child
+ while (my ($key, $value) = each %{$envref}) {
+ $ENV{$key} = $value;
+ }
+
+ if (defined $ioref && $dir eq '-|') {
+ open my $fh, '|-', @_ or die "open |- @_: $!";
+ foreach my $line (@{$ioref}) {
+ print $fh $line, "\n";
+ }
+ exit ! close $fh;
+ } else {
+ if ($dir ne '|-') {
+ open STDIN, '<', '/dev/null' or die "reopening stdin: $!"
+ }
+ exec @_;
+ die "exec @_: $!";
+ }
+}
+
+
+sub get_tags {
+ my $prefix = shift;
+ my @tags;
+
+ my $fh = spawn ('-|', qw/notmuch search --output=tags/, "*")
+ or die 'error dumping tags';
+
+ while (<$fh>) {
+ chomp ();
+ push @tags, $_ if (m/^$prefix/);
+ }
+ unless (close $fh) {
+ die "'notmuch search --output=tags *' exited with nonzero value\n";
+ }
+ return @tags;
+}
+
+
+sub do_archive {
+ system ('git', "--git-dir=$NMBGIT", 'archive', 'HEAD');
+}
+
+
+sub is_committed {
+ my $status = shift;
+ return scalar (@{$status->{added}} ) + scalar (@{$status->{deleted}} ) == 0;
+}
+
+
+sub do_commit {
+ my @args = @_;
+
+ my $status = compute_status ();
+
+ if ( is_committed ($status) ) {
+ print "Nothing to commit\n";
+ return;
+ }
+
+ my $index = read_tree ('HEAD');
+
+ update_index ($index, $status);
+
+ my $tree = git ( { GIT_INDEX_FILE => $index }, 'write-tree')
+ or die 'no output from write-tree';
+
+ my $parent = git ( 'rev-parse', 'HEAD' )
+ or die 'no output from rev-parse';
+
+ my $commit = git ([ @args ], 'commit-tree', $tree, '-p', $parent)
+ or die 'commit-tree';
+
+ git ('update-ref', 'HEAD', $commit);
+
+ unlink $index || die "unlink: $!";
+
+}
+
+sub read_tree {
+ my $treeish = shift;
+ my $index = $NMBGIT.'/nmbug.index';
+ git ({ GIT_INDEX_FILE => $index }, 'read-tree', '--empty');
+ git ({ GIT_INDEX_FILE => $index }, 'read-tree', $treeish);
+ return $index;
+}
+
+sub update_index {
+ my $index = shift;
+ my $status = shift;
+
+ my $git = spawn ({ GIT_DIR => $NMBGIT, GIT_INDEX_FILE => $index },
+ '|-', qw/git update-index --index-info/)
+ or die 'git update-index';
+
+ foreach my $pair (@{$status->{deleted}}) {
+ index_tags_for_msg ($git, $pair->{id}, 'D', $pair->{tag});
+ }
+
+ foreach my $pair (@{$status->{added}}) {
+ index_tags_for_msg ($git, $pair->{id}, 'A', $pair->{tag});
+ }
+ unless (close $git) {
+ die "'git update-index --index-info' exited with nonzero value\n";
+ }
+
+}
+
+
+sub do_fetch {
+ my $remote = shift || 'origin';
+
+ git ('fetch', $remote);
+}
+
+
+sub notmuch {
+ my @args = @_;
+ system ('notmuch', @args) == 0 or die "notmuch @args failed: $?";
+}
+
+
+sub index_tags {
+
+ my $index = $NMBGIT.'/nmbug.index';
+
+ my $query = join ' ', map ("tag:$_", get_tags ($TAGPREFIX));
+
+ my $fh = spawn ('-|', qw/notmuch dump --/, $query)
+ or die "notmuch dump: $!";
+
+ git ('read-tree', '--empty');
+ my $git = spawn ({ GIT_DIR => $NMBGIT, GIT_INDEX_FILE => $index },
+ '|-', qw/git update-index --index-info/)
+ or die 'git update-index';
+
+ while (<$fh>) {
+ m/ ( [^ ]* ) \s+ \( ([^\)]* ) \) /x || die 'syntax error in dump';
+ my ($id,$rest) = ($1,$2);
+
+ #strip prefixes before writing
+ my @tags = grep { s/^$TAGPREFIX//; } split (' ', $rest);
+ index_tags_for_msg ($git,$id, 'A', @tags);
+ }
+ unless (close $git) {
+ die "'git update-index --index-info' exited with nonzero value\n";
+ }
+ unless (close $fh) {
+ die "'notmuch dump -- $query' exited with nonzero value\n";
+ }
+ return $index;
+}
+
+sub index_tags_for_msg {
+ my $fh = shift;
+ my $msgid = shift;
+ my $mode = shift;
+
+ my $hash = $EMPTYBLOB;
+ my $blobmode = '100644';
+
+ if ($mode eq 'D') {
+ $blobmode = '0';
+ $hash = '0000000000000000000000000000000000000000';
+ }
+
+ foreach my $tag (@_) {
+ my $tagpath = 'tags/' . encode_for_fs ($msgid) . '/' . encode_for_fs ($tag);
+ print $fh "$blobmode $hash\t$tagpath\n";
+ }
+}
+
+
+sub do_checkout {
+ do_sync (action => 'checkout');
+}
+
+
+sub do_sync {
+
+ my %args = @_;
+
+ my $status = compute_status ();
+ my ($A_action, $D_action);
+
+ if ($args{action} eq 'checkout') {
+ $A_action = '-';
+ $D_action = '+';
+ } else {
+ $A_action = '+';
+ $D_action = '-';
+ }
+
+ foreach my $pair (@{$status->{added}}) {
+
+ notmuch ('tag', $A_action.$TAGPREFIX.$pair->{tag},
+ 'id:'.$pair->{id});
+ }
+
+ foreach my $pair (@{$status->{deleted}}) {
+ notmuch ('tag', $D_action.$TAGPREFIX.$pair->{tag},
+ 'id:'.$pair->{id});
+ }
+
+}
+
+
+sub insist_committed {
+
+ my $status=compute_status();
+ if ( !is_committed ($status) ) {
+ print "Uncommitted changes to $TAGPREFIX* tags in notmuch
+
+For a summary of changes, run 'nmbug status'
+To save your changes, run 'nmbug commit' before merging/pull
+To discard your changes, run 'nmbug checkout'
+";
+ exit (1);
+ }
+
+}
+
+
+sub do_pull {
+ my $remote = shift || 'origin';
+
+ git ( 'fetch', $remote);
+
+ do_merge ();
+}
+
+
+sub do_merge {
+ insist_committed ();
+
+ my $tempwork = tempdir ('/tmp/nmbug-merge.XXXXXX', CLEANUP => 1);
+
+ git ( { GIT_WORK_TREE => $tempwork }, 'checkout', '-f', 'HEAD');
+
+ git ( { GIT_WORK_TREE => $tempwork }, 'merge', 'FETCH_HEAD');
+
+ do_checkout ();
+}
+
+
+sub do_log {
+ # we don't want output trapping here, because we want the pager.
+ system ( 'git', "--git-dir=$NMBGIT", 'log', '--name-status', @_);
+}
+
+
+sub do_push {
+ my $remote = shift || 'origin';
+
+ git ('push', $remote, 'master');
+}
+
+
+sub do_status {
+ my $status = compute_status ();
+
+ my %output = ();
+ foreach my $pair (@{$status->{added}}) {
+ $output{$pair->{id}} ||= {};
+ $output{$pair->{id}}{$pair->{tag}} = 'A'
+ }
+
+ foreach my $pair (@{$status->{deleted}}) {
+ $output{$pair->{id}} ||= {};
+ $output{$pair->{id}}{$pair->{tag}} = 'D'
+ }
+
+ foreach my $pair (@{$status->{missing}}) {
+ $output{$pair->{id}} ||= {};
+ $output{$pair->{id}}{$pair->{tag}} = 'U'
+ }
+
+ if (is_unmerged ()) {
+ foreach my $pair (diff_refs ('A')) {
+ $output{$pair->{id}} ||= {};
+ $output{$pair->{id}}{$pair->{tag}} ||= ' ';
+ $output{$pair->{id}}{$pair->{tag}} .= 'a';
+ }
+
+ foreach my $pair (diff_refs ('D')) {
+ $output{$pair->{id}} ||= {};
+ $output{$pair->{id}}{$pair->{tag}} ||= ' ';
+ $output{$pair->{id}}{$pair->{tag}} .= 'd';
+ }
+ }
+
+ foreach my $id (sort keys %output) {
+ foreach my $tag (sort keys %{$output{$id}}) {
+ printf "%s\t%s\t%s\n", $output{$id}{$tag}, $id, $tag;
+ }
+ }
+}
+
+
+sub is_unmerged {
+
+ return 0 if (! -f $NMBGIT.'/FETCH_HEAD');
+
+ my $fetch_head = git ('rev-parse', 'FETCH_HEAD');
+ my $base = git ( 'merge-base', 'HEAD', 'FETCH_HEAD');
+
+ return ($base ne $fetch_head);
+
+}
+
+sub compute_status {
+ my %args = @_;
+
+ my @added;
+ my @deleted;
+ my @missing;
+
+ my $index = index_tags ();
+
+ my @maybe_deleted = diff_index ($index, 'D');
+
+ foreach my $pair (@maybe_deleted) {
+
+ my $id = $pair->{id};
+
+ my $fh = spawn ('-|', qw/notmuch search --output=files/,"id:$id")
+ or die "searching for $id";
+ if (!<$fh>) {
+ push @missing, $pair;
+ } else {
+ push @deleted, $pair;
+ }
+ unless (close $fh) {
+ die "'notmuch search --output=files id:$id' exited with nonzero value\n";
+ }
+ }
+
+
+ @added = diff_index ($index, 'A');
+
+ unlink $index || die "unlink $index: $!";
+
+ return { added => [@added], deleted => [@deleted], missing => [@missing] };
+}
+
+
+sub diff_index {
+ my $index = shift;
+ my $filter = shift;
+
+ my $fh = git_pipe ({ GIT_INDEX_FILE => $index },
+ qw/diff-index --cached/,
+ "--diff-filter=$filter", qw/--name-only HEAD/ );
+
+ my @lines = unpack_diff_lines ($fh);
+ unless (close $fh) {
+ die "'git diff-index --cached --diff-filter=$filter --name-only HEAD' ",
+ "exited with nonzero value\n";
+ }
+ return @lines;
+}
+
+
+sub diff_refs {
+ my $filter = shift;
+ my $ref1 = shift || 'HEAD';
+ my $ref2 = shift || 'FETCH_HEAD';
+
+ my $fh= git_pipe ( 'diff', "--diff-filter=$filter", '--name-only',
+ $ref1, $ref2);
+
+ my @lines = unpack_diff_lines ($fh);
+ unless (close $fh) {
+ die "'git diff --diff-filter=$filter --name-only $ref1 $ref2' ",
+ "exited with nonzero value\n";
+ }
+ return @lines;
+}
+
+
+sub unpack_diff_lines {
+ my $fh = shift;
+
+ my @found;
+ while(<$fh>) {
+ chomp ();
+ my ($id,$tag) = m|tags/ ([^/]+) / ([^/]+) |x;
+
+ $id = decode_from_fs ($id);
+ $tag = decode_from_fs ($tag);
+
+ push @found, { id => $id, tag => $tag };
+ }
+
+ return @found;
+}
+
+
+sub encode_for_fs {
+ my $str = shift;
+
+ $str =~ s/($MUST_ENCODE)/"$ESCAPE_CHAR".sprintf ("%02x",ord ($1))/ge;
+ return $str;
+}
+
+
+sub decode_from_fs {
+ my $str = shift;
+
+ $str =~ s/$ESCAPED_RX/ chr (hex ($1))/eg;
+
+ return $str;
+
+}
+
+
+sub usage {
+ pod2usage ();
+ exit (1);
+}
+
+
+sub do_help {
+ pod2usage ( -verbose => 2 );
+ exit (0);
+}
+
+__END__
+
+=head1 NAME
+
+nmbug - manage notmuch tags about notmuch
+
+=head1 SYNOPSIS
+
+nmbug subcommand [options]
+
+B<nmbug help> for more help
+
+=head1 OPTIONS
+
+=head2 Most common commands
+
+=over 8
+
+=item B<commit> [message]
+
+Commit appropriately prefixed tags from the notmuch database to
+git. Any extra arguments are used (one per line) as a commit message.
+
+=item B<push> [remote]
+
+push local nmbug git state to remote repo
+
+=item B<pull> [remote]
+
+pull (merge) remote repo changes to notmuch. B<pull> is equivalent to
+B<fetch> followed by B<merge>.
+
+=back
+
+=head2 Other Useful Commands
+
+=over 8
+
+=item B<checkout>
+
+Update the notmuch database from git. This is mainly useful to discard
+your changes in notmuch relative to git.
+
+=item B<fetch> [remote]
+
+Fetch changes from the remote repo (see merge to bring those changes
+into notmuch).
+
+=item B<help> [subcommand]
+
+print help [for subcommand]
+
+=item B<log> [parameters]
+
+A simple wrapper for git log. After running C<nmbug fetch>, you can
+inspect the changes with C<nmbug log HEAD..FETCH_HEAD>
+
+=item B<merge>
+
+Merge changes from FETCH_HEAD into HEAD, and load the result into
+notmuch.
+
+=item B<status>
+
+Show pending updates in notmuch or git repo. See below for more
+information about the output format.
+
+=back
+
+=head2 Less common commands
+
+=over 8
+
+=item B<archive>
+
+Dump a tar archive (using git archive) of the current nmbug tag set.
+
+=back
+
+=head1 STATUS FORMAT
+
+B<nmbug status> prints lines of the form
+
+ ng Message-Id tag
+
+where n is a single character representing notmuch database status
+
+=over 8
+
+=item B<A>
+
+Tag is present in notmuch database, but not committed to nmbug
+(equivalently, tag has been deleted in nmbug repo, e.g. by a pull, but
+not restored to notmuch database).
+
+=item B<D>
+
+Tag is present in nmbug repo, but not restored to notmuch database
+(equivalently, tag has been deleted in notmuch)
+
+=item B<U>
+
+Message is unknown (missing from local notmuch database)
+
+=back
+
+The second character (if present) represents a difference between remote
+git and local. Typically C<nmbug fetch> needs to be run to update this.
+
+=over 8
+
+
+=item B<a>
+
+Tag is present in remote, but not in local git.
+
+
+=item B<d>
+
+Tag is present in local git, but not in remote git.
+
+
+=back
+
+=head1 DUMP FORMAT
+
+Each tag $tag for message with Message-Id $id is written to
+an empty file
+
+ tags/encode($id)/encode($tag)
+
+The encoding preserves alphanumerics, and the characters "+-_@=.:,"
+(not the quotes). All other octets are replaced with '%' followed by
+a two digit hex number.
+
+=head1 ENVIRONMENT
+
+B<NMBGIT> specifies the location of the git repository used by nmbug.
+If not specified $HOME/.nmbug is used.
+
+B<NMBPREFIX> specifies the prefix in the notmuch database for tags of
+interest to nmbug. If not specified 'notmuch::' is used.
--- /dev/null
+#!/usr/bin/python
+#
+# Copyright (c) 2011-2012 David Bremner <david@tethera.net>
+# License: Same as notmuch
+# dependencies
+# - python 2.6 for json
+# - argparse; either python 2.7, or install separately
+
+import datetime
+import notmuch
+import rfc822
+import urllib
+import json
+import argparse
+import os
+import subprocess
+
+# parse command line arguments
+
+parser = argparse.ArgumentParser()
+parser.add_argument('--text', help='output plain text format',
+ action='store_true')
+
+parser.add_argument('--config', help='load config from given file')
+
+
+args = parser.parse_args()
+
+# read config from json file
+
+if args.config != None:
+ fp = open(args.config)
+else:
+ nmbhome = os.getenv('NMBGIT', os.path.expanduser('~/.nmbug'))
+
+ # read only the first line from the pipe
+ sha1 = subprocess.Popen(['git', '--git-dir', nmbhome,
+ 'show-ref', '-s', 'config'],
+ stdout=subprocess.PIPE).stdout.readline()
+
+ sha1 = sha1.rstrip()
+
+ fp = subprocess.Popen(['git', '--git-dir', nmbhome,
+ 'cat-file', 'blob', sha1+':status-config.json'],
+ stdout=subprocess.PIPE).stdout
+
+config = json.load(fp)
+
+if args.text:
+ output_format = 'text'
+else:
+ output_format = 'html'
+
+class Thread:
+ def __init__(self, last, lines):
+ self.last = last
+ self.lines = lines
+
+ def join_utf8_with_newlines(self):
+ return '\n'.join( (line.encode('utf-8') for line in self.lines) )
+
+def output_with_separator(threadlist, sep):
+ outputs = (thread.join_utf8_with_newlines() for thread in threadlist)
+ print sep.join(outputs)
+
+headers = ['date', 'from', 'subject']
+
+def print_view(title, query, comment):
+
+ query_string = ' and '.join(query)
+ q_new = notmuch.Query(db, query_string)
+ q_new.set_sort(notmuch.Query.SORT.OLDEST_FIRST)
+
+ last_thread_id = ''
+ threads = {}
+ threadlist = []
+ out = {}
+ last = None
+ lines = None
+
+ if output_format == 'html':
+ print '<h3><a name="%s" />%s</h3>' % (title, title)
+ print comment
+ print 'The view is generated from the following query:'
+ print '<blockquote>'
+ print query_string
+ print '</blockquote>'
+ print '<table>\n'
+
+ for m in q_new.search_messages():
+
+ thread_id = m.get_thread_id()
+
+ if thread_id != last_thread_id:
+ if threads.has_key(thread_id):
+ last = threads[thread_id].last
+ lines = threads[thread_id].lines
+ else:
+ last = {}
+ lines = []
+ thread = Thread(last, lines)
+ threads[thread_id] = thread
+ for h in headers:
+ last[h] = ''
+ threadlist.append(thread)
+ last_thread_id = thread_id
+
+ for header in headers:
+ val = m.get_header(header)
+
+ if header == 'date':
+ val = str.join(' ', val.split(None)[1:4])
+ val = str(datetime.datetime.strptime(val, '%d %b %Y').date())
+ elif header == 'from':
+ (val, addr) = rfc822.parseaddr(val)
+ if val == '':
+ val = addr.split('@')[0]
+
+ if header != 'subject' and last[header] == val:
+ out[header] = ''
+ else:
+ out[header] = val
+ last[header] = val
+
+ mid = m.get_message_id()
+ out['id'] = 'id:"%s"' % mid
+
+ if output_format == 'html':
+
+ out['subject'] = '<a href="http://mid.gmane.org/%s">%s</a>' \
+ % (urllib.quote(mid), out['subject'])
+
+ lines.append(' <tr><td>%s' % out['date'])
+ lines.append('</td><td>%s' % out['id'])
+ lines.append('</td></tr>')
+ lines.append(' <tr><td>%s' % out['from'])
+ lines.append('</td><td>%s' % out['subject'])
+ lines.append('</td></tr>')
+ else:
+ lines.append('%(date)-10.10s %(from)-20.20s %(subject)-40.40s\n%(id)72s' % out)
+
+ if output_format == 'html':
+ output_with_separator(threadlist,
+ '\n<tr><td colspan="2"><br /></td></tr>\n')
+ print '</table>'
+ else:
+ output_with_separator(threadlist, '\n\n')
+
+# main program
+
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
+
+if output_format == 'html':
+ print '''<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>Notmuch Patches</title>
+</head>
+<body>'''
+ print '<h2>Notmuch Patches</h2>'
+ print 'Generated: %s<br />' % datetime.datetime.utcnow().date()
+ print 'For more infomation see <a href="http://notmuchmail.org/nmbug">nmbug</a>'
+
+ print '<h3>Views</h3>'
+ print '<ul>'
+ for view in config['views']:
+ print '<li><a href="#%(title)s">%(title)s</a></li>' % view
+ print '</ul>'
+
+for view in config['views']:
+ print_view(**view)
+
+if output_format == 'html':
+ print '</body>\n</html>'
--- /dev/null
+{
+ "views": [
+ {
+ "comment": "Unresolved bugs (or just need tag updating).",
+ "query": [
+ "tag:notmuch::bug",
+ "not tag:notmuch::fixed",
+ "not tag:notmuch::wontfix"
+ ],
+ "title": "Bugs"
+ },
+ {
+ "comment": "These patches are under consideration for pushing.",
+ "query": [
+ "tag:notmuch::patch and not tag:notmuch::pushed",
+ "not tag:notmuch::obsolete and not tag:notmuch::wip",
+ "not tag:notmuch::stale and not tag:notmuch::contrib",
+ "not tag:notmuch::moreinfo",
+ "not tag:notmuch::python",
+ "not tag:notmuch::vim",
+ "not tag:notmuch::wontfix",
+ "not tag:notmuch::needs-review"
+ ],
+ "title": "Maybe Ready (Core and Emacs)"
+ },
+ {
+ "comment": "These python related patches might be ready to push, or they might just need updated tags.",
+ "query": [
+ "tag:notmuch::patch and not tag:notmuch::pushed",
+ "not tag:notmuch::obsolete and not tag:notmuch::wip",
+ "not tag:notmuch::stale and not tag:notmuch::contrib",
+ "not tag:notmuch::moreinfo",
+ "not tag:notmuch::wontfix",
+ " tag:notmuch::python",
+ "not tag:notmuch::needs-review"
+ ],
+ "title": "Maybe Ready (Python)"
+ },
+ {
+ "comment": "These vim related patches might be ready to push, or they might just need updated tags.",
+ "query": [
+ "tag:notmuch::patch and not tag:notmuch::pushed",
+ "not tag:notmuch::obsolete and not tag:notmuch::wip",
+ "not tag:notmuch::stale and not tag:notmuch::contrib",
+ "not tag:notmuch::moreinfo",
+ "not tag:notmuch::wontfix",
+ "tag:notmuch::vim",
+ "not tag:notmuch::needs-review"
+ ],
+ "title": "Maybe Ready (vim)"
+ },
+ {
+ "comment": "These patches are under review, or waiting for feedback.",
+ "query": [
+ "tag:notmuch::patch",
+ "not tag:notmuch::pushed",
+ "not tag:notmuch::obsolete",
+ "not tag:notmuch::stale",
+ "not tag:notmuch::wontfix",
+ "(tag:notmuch::moreinfo or tag:notmuch::needs-review)"
+ ],
+ "title": "Review"
+ }
+ ]
+}
readonly VERSION
+# In the rest of this file, tests collect list of errors to be fixed
+
verfail ()
{
echo No.
- echo "$@"
- echo "Please follow the instructions in RELEASING to choose a version"
- exit 1
+ append_emsg "$@"
+ append_emsg " Please follow the instructions in RELEASING to choose a version"
}
echo -n "Checking that '$VERSION' is good with digits and periods... "
esac
-# In the rest of this file, tests collect list of errors to be fixed
-
echo -n "Checking that this is Debian package for notmuch... "
read deb_notmuch deb_version rest < debian/changelog
if [ "$deb_notmuch" = 'notmuch' ]
append_emsg "Version '$py_version' is not '$VERSION' in $PV_FILE"
fi
+echo -n "Checking that NEWS header is tidy... "
+if [ "`exec sed 's/./=/g; 1q' NEWS`" = "`exec sed '1d; 2q' NEWS`" ]
+then
+ echo Yes.
+else
+ echo No.
+ if [ "`exec sed '1d; s/=//g; 2q' NEWS`" != '' ]
+ then
+ append_emsg "Line 2 in NEWS file is not all '=':s"
+ else
+ append_emsg "Line 2 in NEWS file does not have the same length as line 1"
+ fi
+fi
+
echo -n "Checking that this is Notmuch NEWS... "
read news_notmuch news_version news_date < NEWS
if [ "$news_notmuch" = "Notmuch" ]
;;
;; Authors: Jameson Rollins <jrollins@finestructure.net>
+(require 'notmuch-lib)
+
(defcustom notmuch-crypto-process-mime nil
"Should cryptographic MIME parts be processed?
(define-button-type 'notmuch-crypto-status-button-type
'action (lambda (button) (message (button-get button 'help-echo)))
'follow-link t
- 'help-echo "Set notmuch-crypto-process-mime to process cryptographic mime parts.")
+ 'help-echo "Set notmuch-crypto-process-mime to process cryptographic mime parts."
+ :supertype 'notmuch-button-type)
(defun notmuch-crypto-insert-sigstatus-button (sigstatus from)
(let* ((status (plist-get sigstatus :status))
(notmuch-remove-if-not
(lambda (tag)
(not (member tag hide-tags)))
- (process-lines notmuch-command "search-tags"))))
+ (process-lines notmuch-command "search" "--output=tags" "*"))))
(defun notmuch-hello-insert-header ()
"Insert the default notmuch-hello header."
:group 'notmuch-search
:group 'notmuch-show)
+;; 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
+;; current selected window which can cause problems if the button
+;; changes the buffer (e.g., id: links) or moves point.
+;;
+;; This provides a button type which overrides mouse-action so that
+;; the button's window is selected before the action is run. Other
+;; notmuch buttons can get the same behaviour by inheriting from this
+;; button type.
+(define-button-type 'notmuch-button-type
+ 'mouse-action (lambda (button)
+ (select-window (posn-window (event-start last-input-event)))
+ (button-activate button)))
+
(defun notmuch-version ()
"Return a string with the notmuch version number."
(let ((long-string
'action 'notmuch-show-part-button-default
'keymap 'notmuch-show-part-button-map
'follow-link t
- 'face 'message-mml)
+ 'face 'message-mml
+ :supertype 'notmuch-button-type)
(defvar notmuch-show-part-button-map
(let ((map (make-sparse-keymap)))
;; Remove the overlay created by goto-address-mode
(remove-overlays (first link) (second link) 'goto-address t)
(make-text-button (first link) (second link)
+ :type 'notmuch-button-type
'action `(lambda (arg)
(notmuch-show ,(third link)))
'follow-link t
(define-button-type 'notmuch-wash-button-invisibility-toggle-type
'action 'notmuch-wash-toggle-invisible-action
'follow-link t
- 'face 'font-lock-comment-face)
+ 'face 'font-lock-comment-face
+ :supertype 'notmuch-button-type)
(define-button-type 'notmuch-wash-button-citation-toggle-type
'help-echo "mouse-1, RET: Show citation"
void
_notmuch_message_add_reply (notmuch_message_t *message,
- notmuch_message_node_t *reply)
+ notmuch_message_t *reply)
{
- _notmuch_message_list_append (message->replies, reply);
+ _notmuch_message_list_add_message (message->replies, reply);
}
notmuch_messages_t *
return list;
}
-/* Append a single 'node' to the end of 'list'.
- */
-void
-_notmuch_message_list_append (notmuch_message_list_t *list,
- notmuch_message_node_t *node)
-{
- *(list->tail) = node;
- list->tail = &node->next;
-}
-
-/* Allocate a new node for 'message' and append it to the end of
- * 'list'.
- */
+/* Append 'message' to the end of 'list'. */
void
_notmuch_message_list_add_message (notmuch_message_list_t *list,
notmuch_message_t *message)
node->message = message;
node->next = NULL;
- _notmuch_message_list_append (list, node);
+ *(list->tail) = node;
+ list->tail = &node->next;
}
notmuch_messages_t *
notmuch_message_list_t *
_notmuch_message_list_create (const void *ctx);
-void
-_notmuch_message_list_append (notmuch_message_list_t *list,
- notmuch_message_node_t *node);
-
void
_notmuch_message_list_add_message (notmuch_message_list_t *list,
notmuch_message_t *message);
void
_notmuch_message_add_reply (notmuch_message_t *message,
- notmuch_message_node_t *reply);
+ notmuch_message_t *reply);
/* sha1.c */
notmuch_thread_get_total_messages (notmuch_thread_t *thread);
/* Get a notmuch_messages_t iterator for the top-level messages in
- * 'thread'.
+ * 'thread' in oldest-first order.
*
* 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.
- *
- * To iterate over all messages in the thread, the caller will need to
- * iterate over the result of notmuch_message_get_replies for each
- * top-level message (and do that recursively for the resulting
- * messages, etc.).
*/
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.
+ */
+notmuch_messages_t *
+notmuch_thread_get_messages (notmuch_thread_t *thread);
+
/* Get the number of messages in 'thread' that matched the search.
*
* This count includes only the messages in this thread that were
} notmuch_mset_messages_t;
struct _notmuch_doc_id_set {
- unsigned int *bitmap;
+ unsigned char *bitmap;
unsigned int bound;
};
-#define DOCIDSET_WORD(bit) ((bit) / sizeof (unsigned int))
-#define DOCIDSET_BIT(bit) ((bit) % sizeof (unsigned int))
+#define DOCIDSET_WORD(bit) ((bit) / CHAR_BIT)
+#define DOCIDSET_BIT(bit) ((bit) % CHAR_BIT)
struct visible _notmuch_threads {
notmuch_query_t *query;
GArray *arr)
{
unsigned int max = 0;
- unsigned int *bitmap;
+ unsigned char *bitmap;
for (unsigned int i = 0; i < arr->len; i++)
max = MAX(max, g_array_index (arr, unsigned int, i));
- bitmap = talloc_zero_array (ctx, unsigned int, 1 + max / sizeof (*bitmap));
+ bitmap = talloc_zero_array (ctx, unsigned char, DOCIDSET_WORD(max) + 1);
if (bitmap == NULL)
return FALSE;
char *authors;
GHashTable *tags;
+ /* All messages, oldest first. */
notmuch_message_list_t *message_list;
+ /* Top-level messages, oldest first. */
+ notmuch_message_list_t *toplevel_list;
+
GHashTable *message_hash;
int total_messages;
int matched_messages;
}
static void
-_resolve_thread_relationships (unused (notmuch_thread_t *thread))
+_resolve_thread_relationships (notmuch_thread_t *thread)
{
- notmuch_message_node_t **prev, *node;
+ notmuch_message_node_t *node;
notmuch_message_t *message, *parent;
const char *in_reply_to;
- prev = &thread->message_list->head;
- while ((node = *prev)) {
+ for (node = thread->message_list->head; node; node = node->next) {
message = node->message;
in_reply_to = _notmuch_message_get_in_reply_to (message);
if (in_reply_to && strlen (in_reply_to) &&
g_hash_table_lookup_extended (thread->message_hash,
in_reply_to, NULL,
(void **) &parent))
- {
- *prev = node->next;
- if (thread->message_list->tail == &node->next)
- thread->message_list->tail = prev;
- node->next = NULL;
- _notmuch_message_add_reply (parent, node);
- } else {
- prev = &((*prev)->next);
- }
+ _notmuch_message_add_reply (parent, message);
+ else
+ _notmuch_message_list_add_message (thread->toplevel_list, message);
}
/* XXX: After scanning through the entire list looking for parents
notmuch_string_list_t *exclude_terms,
notmuch_sort_t sort)
{
- notmuch_thread_t *thread;
+ void *local = talloc_new (ctx);
+ notmuch_thread_t *thread = NULL;
notmuch_message_t *seed_message;
const char *thread_id;
char *thread_id_query_string;
notmuch_messages_t *messages;
notmuch_message_t *message;
- seed_message = _notmuch_message_create (ctx, notmuch, seed_doc_id, NULL);
+ seed_message = _notmuch_message_create (local, notmuch, seed_doc_id, NULL);
if (! seed_message)
INTERNAL_ERROR ("Thread seed message %u does not exist", seed_doc_id);
thread_id = notmuch_message_get_thread_id (seed_message);
- thread_id_query_string = talloc_asprintf (ctx, "thread:%s", thread_id);
+ thread_id_query_string = talloc_asprintf (local, "thread:%s", thread_id);
if (unlikely (thread_id_query_string == NULL))
- return NULL;
+ goto DONE;
- thread_id_query = notmuch_query_create (notmuch, thread_id_query_string);
+ thread_id_query = talloc_steal (
+ local, notmuch_query_create (notmuch, thread_id_query_string));
if (unlikely (thread_id_query == NULL))
- return NULL;
+ goto DONE;
- talloc_free (thread_id_query_string);
-
- thread = talloc (ctx, notmuch_thread_t);
+ thread = talloc (local, notmuch_thread_t);
if (unlikely (thread == NULL))
- return NULL;
+ goto DONE;
talloc_set_destructor (thread, _notmuch_thread_destructor);
free, NULL);
thread->message_list = _notmuch_message_list_create (thread);
- if (unlikely (thread->message_list == NULL))
- return NULL;
+ thread->toplevel_list = _notmuch_message_list_create (thread);
+ if (unlikely (thread->message_list == NULL ||
+ thread->toplevel_list == NULL)) {
+ thread = NULL;
+ goto DONE;
+ }
thread->message_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
free, NULL);
_notmuch_message_close (message);
}
- notmuch_query_destroy (thread_id_query);
-
_resolve_thread_authors_string (thread);
_resolve_thread_relationships (thread);
+ /* Commit to returning thread. */
+ talloc_steal (ctx, thread);
+
+ DONE:
+ talloc_free (local);
return thread;
}
notmuch_messages_t *
notmuch_thread_get_toplevel_messages (notmuch_thread_t *thread)
+{
+ return _notmuch_messages_create (thread->toplevel_list);
+}
+
+notmuch_messages_t *
+notmuch_thread_get_messages (notmuch_thread_t *thread)
{
return _notmuch_messages_create (thread->message_list);
}
notmuch \- thread-based email index, search, and tagging
.SH SYNOPSIS
.B notmuch
-.IR command " [" args " ...]"
+.RI "[" option " ...] " command " [" arg " ...]"
.SH DESCRIPTION
Notmuch is a command-line based program for indexing, searching,
reading, and tagging large collections of email messages.
in the Notmuch source distribution) is probably the most widely used at
this time.
+.SH OPTIONS
+
+Supported global options for
+.B notmuch
+include
+
+.RS 4
+.TP 4
+.B \-\-help
+
+Print a synopsis of available commands and exit.
+.RE
+
+.RS 4
+.TP 4
+.B \-\-version
+
+Print the installed version of notmuch, and exit.
+.RE
+
.SH COMMANDS
.B NOTMUCH_CONFIG
Specifies the location of the notmuch configuration file. Notmuch will
use ${HOME}/.notmuch\-config if this variable is not set.
+
+.TP
+.B NOTMUCH_TALLOC_REPORT
+Location to write a talloc memory usage report. See
+.B talloc_enable_leak_report_full
+in \fBtalloc\fR(3)
+for more information.
+
.SH SEE ALSO
\fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
notmuch_query_t *query;
notmuch_messages_t *messages;
notmuch_message_t *message;
- int ret = 0;
+ int ret = NOTMUCH_STATUS_SUCCESS;
/* Optimize the query so it excludes messages that already have
* the specified set of tags. */
const char *summary;
} command_t;
-#define MAX_ALIAS_SUBSTITUTIONS 3
-
-typedef struct alias {
- const char *name;
- const char *substitutions[MAX_ALIAS_SUBSTITUTIONS];
-} alias_t;
-
-alias_t aliases[] = {
- { "part", { "show", "--format=raw"}},
- { "search-tags", {"search", "--output=tags", "*"}}
-};
-
static int
notmuch_help_command (void *ctx, int argc, char *argv[]);
{
void *local;
command_t *command;
- alias_t *alias;
- unsigned int i, j;
- const char **argv_local;
+ unsigned int i;
+ notmuch_bool_t print_help=FALSE, print_version=FALSE;
+ int opt_index;
+
+ notmuch_opt_desc_t options[] = {
+ { NOTMUCH_OPT_BOOLEAN, &print_help, "help", 'h', 0 },
+ { NOTMUCH_OPT_BOOLEAN, &print_version, "version", 'v', 0 },
+ { 0, 0, 0, 0, 0 }
+ };
talloc_enable_null_tracking ();
if (argc == 1)
return notmuch (local);
- if (strcmp (argv[1], "--help") == 0)
+ opt_index = parse_arguments (argc, argv, options, 1);
+ if (opt_index < 0) {
+ /* diagnostics already printed */
+ return 1;
+ }
+
+ if (print_help)
return notmuch_help_command (NULL, argc - 1, &argv[1]);
- if (strcmp (argv[1], "--version") == 0) {
+ if (print_version) {
printf ("notmuch " STRINGIFY(NOTMUCH_VERSION) "\n");
return 0;
}
- for (i = 0; i < ARRAY_SIZE (aliases); i++) {
- alias = &aliases[i];
-
- if (strcmp (argv[1], alias->name) == 0)
- {
- int substitutions;
-
- argv_local = talloc_size (local, sizeof (char *) *
- (argc + MAX_ALIAS_SUBSTITUTIONS - 1));
- if (argv_local == NULL) {
- fprintf (stderr, "Out of memory.\n");
- return 1;
- }
-
- /* Copy all substution arguments from the alias. */
- argv_local[0] = argv[0];
- for (j = 0; j < MAX_ALIAS_SUBSTITUTIONS; j++) {
- if (alias->substitutions[j] == NULL)
- break;
- argv_local[j+1] = alias->substitutions[j];
- }
- substitutions = j;
-
- /* And copy all original arguments (skipping the argument
- * that matched the alias of course. */
- for (j = 2; j < (unsigned) argc; j++) {
- argv_local[substitutions+j-1] = argv[j];
- }
-
- argc += substitutions - 1;
- argv = (char **) argv_local;
- }
- }
-
for (i = 0; i < ARRAY_SIZE (commands); i++) {
command = &commands[i];
- if (strcmp (argv[1], command->name) == 0) {
+ if (strcmp (argv[opt_index], command->name) == 0) {
int ret;
char *talloc_report;
- ret = (command->function)(local, argc - 1, &argv[1]);
-
- /* in the future support for this environment variable may
- * be supplemented or replaced by command line arguments
- * --leak-report and/or --leak-report-full */
+ ret = (command->function)(local, argc - opt_index, argv + opt_index);
talloc_report = getenv ("NOTMUCH_TALLOC_REPORT");
if (talloc_report && strcmp (talloc_report, "") != 0) {
FILE *report = fopen (talloc_report, "w");
- talloc_report_full (NULL, report);
+ if (report) {
+ talloc_report_full (NULL, report);
+ } else {
+ ret = 1;
+ fprintf (stderr, "ERROR: unable to write talloc log. ");
+ perror (talloc_report);
+ }
}
return ret;
test_broken=0
test_success=0
-die () {
+_die_common () {
code=$?
+ trap - EXIT
+ set +ex
rm -rf "$TEST_TMPDIR"
+}
+
+die () {
+ _die_common
if test -n "$GIT_EXIT_OK"
then
exit $code
else
- echo >&5 "FATAL: Unexpected exit with code $code"
+ exec >&5
+ say_color error '%-6s' FATAL
+ echo " $test_subtest_name"
+ echo
+ echo "Unexpected exit while executing $0. Exit code $code."
exit 1
fi
}
+die_signal () {
+ _die_common
+ echo >&5 "FATAL: $0: interrupted by signal" $((code - 128))
+ exit $code
+}
+
GIT_EXIT_OK=
# Note: TEST_TMPDIR *NOT* exported!
TEST_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/notmuch-test-$$.XXXXXX")
trap 'die' EXIT
+trap 'die_signal' HUP INT TERM
test_decode_color () {
sed -e 's/.\[1m/<WHITE>/g' \
if ! test_skip "$test_subtest_name"
then
if [ "$output" = "$expected" ]; then
- test_ok_ "$test_subtest_name"
+ test_ok_
else
testname=$this_test.$test_count
echo "$expected" > $testname.expected
echo "$output" > $testname.output
- test_failure_ "$test_subtest_name" "$(diff -u $testname.expected $testname.output)"
+ test_failure_ "$(diff -u $testname.expected $testname.output)"
fi
fi
}
if ! test_skip "$test_subtest_name"
then
if diff -q "$file1" "$file2" >/dev/null ; then
- test_ok_ "$test_subtest_name"
+ test_ok_
else
testname=$this_test.$test_count
cp "$file1" "$testname.$basename1"
cp "$file2" "$testname.$basename2"
- test_failure_ "$test_subtest_name" "$(diff -u "$testname.$basename1" "$testname.$basename2")"
+ test_failure_ "$(diff -u "$testname.$basename1" "$testname.$basename2")"
fi
fi
}
result=$(cat OUTPUT)
if [ "$result" = t ]
then
- test_ok_ "$test_subtest_name"
+ test_ok_
else
- test_failure_ "$test_subtest_name" "${result}"
+ test_failure_ "${result}"
fi
else
# Restore state after the (non) test.
test_ok_ () {
if test "$test_subtest_known_broken_" = "t"; then
- test_known_broken_ok_ "$@"
+ test_known_broken_ok_
return
fi
test_success=$(($test_success + 1))
say_color pass "%-6s" "PASS"
- echo " $@"
+ echo " $test_subtest_name"
}
test_failure_ () {
return
fi
test_failure=$(($test_failure + 1))
- test_failure_message_ "FAIL" "$@"
+ test_failure_message_ "FAIL" "$test_subtest_name" "$@"
test "$immediate" = "" || { GIT_EXIT_OK=t; exit 1; }
return 1
}
test_reset_state_
test_fixed=$(($test_fixed+1))
say_color pass "%-6s" "FIXED"
- echo " $@"
+ echo " $test_subtest_name"
}
test_known_broken_failure_ () {
test_reset_state_
test_broken=$(($test_broken+1))
- test_failure_message_ "BROKEN" "$@"
+ test_failure_message_ "BROKEN" "$test_subtest_name" "$@"
return 1
}
test "$#" = 3 && { prereq=$1; shift; } || prereq=
test "$#" = 2 ||
error "bug in the test script: not 2 or 3 parameters to test-expect-success"
+ test_subtest_name="$1"
test_reset_state_
if ! test_skip "$@"
then
test_check_missing_external_prereqs_ "$@" ||
if [ "$run_ret" = 0 -a "$eval_ret" = 0 ]
then
- test_ok_ "$1"
+ test_ok_
else
- test_failure_ "$@"
+ test_failure_ "$2"
fi
fi
}
test "$#" = 4 && { prereq=$1; shift; } || prereq=
test "$#" = 3 ||
error "bug in the test script: not 3 or 4 parameters to test-expect-code"
+ test_subtest_name="$2"
test_reset_state_
if ! test_skip "$@"
then
test_check_missing_external_prereqs_ "$@" ||
if [ "$run_ret" = 0 -a "$eval_ret" = "$1" ]
then
- test_ok_ "$2"
+ test_ok_
else
- test_failure_ "$@"
+ test_failure_ "exit code $eval_ret, expected $1" "$3"
fi
fi
}
test "$#" = 4 && { prereq=$1; shift; } || prereq=
test "$#" = 3 ||
error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
- descr="$1"
+ test_subtest_name="$1"
shift
test_reset_state_
- if ! test_skip "$descr" "$@"
+ if ! test_skip "$test_subtest_name" "$@"
then
# Announce the script to reduce confusion about the
# test output that follows.
"$@" 2>&4
if [ "$?" = 0 ]
then
- test_ok_ "$descr"
+ test_ok_
else
- test_failure_ "$descr" "$@"
+ test_failure_ "$@"
fi
fi
}
stderr="$tmp/git-external-stderr.$$.tmp"
test_external "$@" 4> "$stderr"
[ -f "$stderr" ] || error "Internal error: $stderr disappeared."
- descr="no stderr: $1"
+ test_subtest_name="no stderr: $1"
shift
if [ ! -s "$stderr" ]; then
rm "$stderr"
- test_ok_ "$descr"
+ test_ok_
else
if [ "$verbose" = t ]; then
output=`echo; echo Stderr is:; cat "$stderr"`
fi
# rm first in case test_failure exits.
rm "$stderr"
- test_failure_ "$descr" "$@" "$output"
+ test_failure_ "$@" "$output"
fi
}
+++ /dev/null
-.PHONY: all help install link symlink
-
-files = plugin/notmuch.vim \
- $(wildcard syntax/notmuch-*.vim)
-prefix = $(HOME)/.vim
-destdir = $(prefix)/plugin
-
-INSTALL = install -D -m644
-
-all: help
-
-help:
- @echo "I don't actually build anything, but I will help you install"
- @echo "notmuch support for vim."
- @echo
- @echo " make install - copy plugin scripts and syntax files to ~/.vim"
- @echo " make symlink - create symlinks in ~/.vim (useful for development)"
-
-install:
- @for x in $(files); do $(INSTALL) $(CURDIR)/$$x $(prefix)/$$x; done
-
-link symlink: INSTALL = ln -fs
-link symlink: install
+++ /dev/null
-This directory contains a vim script that allows reading notmuch mail
-through vim.
-
-NOTE: this is a work in progress. Patches welcome. <bart@jukie.net>
-
-Dependencies:
- notmuch:
- Naturally, it expects you have notmuch installed and configured.
-
- sendmail:
- To send mail, notmuch.vim uses sendmail as default. Most modern MTAs
- provide a compatibility binary, and so should work well.
-
-
-To install:
- make install
-
-
-To run:
- vim -c ':NotMuch'
-
- from vim:
- :NotMuch
- :NotMuch new to:bart@jukie.net 'subject:this is a test'
-
-
-Buffer types:
- [notmuch-folders]
- Folder list, or technically a list of saved searches.
-
- Keybindings:
- <Enter> - show the selected search
- m - compose a new message
- s - enter search criteria
- = - refresh display
-
- [notmuch-search]
- You are presented with the search results when you run :NotMuch.
-
- Keybindings:
- <Space> - show the selected thread collapsing unmatched items
- <Enter> - show the entire selected thread
- a - archive message (remove inbox tag)
- f - filter the current search terms
- o - toggle search screen order
- m - compose a new message
- r - reply to thread
- s - enter search criteria
- ,s - alter search criteria
- t - filter the current search terms with tags
- q - return to folder display, or undo filter
- + - add tag(s) to selected message
- - - remove tag(s) from selected message
- = - refresh display
- ? - reveal the thread ID of what's under cursor
- ^] - search using word under cursor
-
- [notmuch-show]
- This is the display of the message.
-
- Keybindings:
- <Space> - mark read, archive, go to next matching message
- ^n - next message
- ^p - previous message
- b - toggle folding of message bodies
- c - toggle folding of citations
- h - toggle folding of extra header lines
- i - toggle folding of signatures
- m - compose a new message
- r - reply to the message
- s - enter search criteria
- q - return to search display
- ? - reveal the message and thread IDs of what's under cursor
- ^] - search using word under cursor
-
- [notmuch-compose]
- When you're writing an email, you're in this mode.
-
- Insert-mode keybindings:
- <Tab> - go to the next header line
-
- Normal-mode keybindings:
- <Tab> - go to the next header line
- ,s - send this message
- ,q - abort this message
-
+++ /dev/null
-addon: notmuch
-description: "notmuch mail user interface"
-files:
- - plugin/notmuch.vim
- - syntax/notmuch-compose.vim
- - syntax/notmuch-folders.vim
- - syntax/notmuch-search.vim
- - syntax/notmuch-show.vim
+++ /dev/null
-" notmuch.vim plugin --- run notmuch within vim
-"
-" Copyright © Carl Worth
-"
-" 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: Bart Trojanowski <bart@jukie.net>
-" Contributors: Felipe Contreras <felipe.contreras@gmail.com>,
-" Peter Hartman <peterjohnhartman@gmail.com>
-"
-" --- configuration defaults {{{1
-
-let s:notmuch_defaults = {
- \ 'g:notmuch_cmd': 'notmuch' ,
- \ 'g:notmuch_sendmail': '/usr/sbin/sendmail' ,
- \ 'g:notmuch_debug': 0 ,
- \
- \ 'g:notmuch_search_newest_first': 1 ,
- \ 'g:notmuch_search_from_column_width': 20 ,
- \
- \ 'g:notmuch_show_fold_signatures': 1 ,
- \ 'g:notmuch_show_fold_citations': 1 ,
- \ 'g:notmuch_show_fold_bodies': 0 ,
- \ 'g:notmuch_show_fold_headers': 1 ,
- \
- \ 'g:notmuch_show_message_begin_regexp': '\fmessage{' ,
- \ 'g:notmuch_show_message_end_regexp': '\fmessage}' ,
- \ 'g:notmuch_show_header_begin_regexp': '\fheader{' ,
- \ 'g:notmuch_show_header_end_regexp': '\fheader}' ,
- \ 'g:notmuch_show_body_begin_regexp': '\fbody{' ,
- \ 'g:notmuch_show_body_end_regexp': '\fbody}' ,
- \ 'g:notmuch_show_attachment_begin_regexp': '\fattachment{' ,
- \ 'g:notmuch_show_attachment_end_regexp': '\fattachment}' ,
- \ 'g:notmuch_show_part_begin_regexp': '\fpart{' ,
- \ 'g:notmuch_show_part_end_regexp': '\fpart}' ,
- \ 'g:notmuch_show_marker_regexp': '\f\\(message\\|header\\|body\\|attachment\\|part\\)[{}].*$',
- \
- \ 'g:notmuch_show_message_parse_regexp': '\(id:[^ ]*\) depth:\([0-9]*\) match:\([0-9]*\) excluded:\([0-9]*\) filename:\(.*\)$',
- \ 'g:notmuch_show_tags_regexp': '(\([^)]*\))$' ,
- \
- \ 'g:notmuch_show_signature_regexp': '^\(-- \?\|_\+\)$' ,
- \ 'g:notmuch_show_signature_lines_max': 12 ,
- \
- \ 'g:notmuch_show_citation_regexp': '^\s*>' ,
- \
- \ 'g:notmuch_compose_insert_mode_start': 1 ,
- \ 'g:notmuch_compose_header_help': 1 ,
- \ 'g:notmuch_compose_temp_file_dir': '~/.notmuch/compose' ,
- \ }
-
-" defaults for g:notmuch_initial_search_words
-" override with: let g:notmuch_initial_search_words = [ ... ]
-let s:notmuch_initial_search_words_defaults = [
- \ 'tag:inbox and tag:unread',
- \ ]
-
-" defaults for g:notmuch_show_headers
-" override with: let g:notmuch_show_headers = [ ... ]
-let s:notmuch_show_headers_defaults = [
- \ 'Subject',
- \ 'To',
- \ 'Cc',
- \ 'Bcc',
- \ 'Date',
- \ ]
-
-" defaults for g:notmuch_folders
-" override with: let g:notmuch_folders = [ ... ]
-let s:notmuch_folders_defaults = [
- \ [ 'new', 'tag:inbox and tag:unread' ],
- \ [ 'inbox', 'tag:inbox' ],
- \ [ 'unread', 'tag:unread' ],
- \ ]
-
-" defaults for g:notmuch_signature
-" override with: let g:notmuch_signature = [ ... ]
-let s:notmuch_signature_defaults = [
- \ '',
- \ '-- ',
- \ 'email sent from notmuch.vim plugin'
- \ ]
-
-" defaults for g:notmuch_compose_headers
-" override with: let g:notmuch_compose_headers = [ ... ]
-let s:notmuch_compose_headers_defaults = [
- \ 'From',
- \ 'To',
- \ 'Cc',
- \ 'Bcc',
- \ 'Subject'
- \ ]
-
-" --- keyboard mapping definitions {{{1
-
-" --- --- bindings for folders mode {{{2
-
-let g:notmuch_folders_maps = {
- \ 'm': ':call <SID>NM_new_mail()<CR>',
- \ 's': ':call <SID>NM_search_prompt()<CR>',
- \ 'q': ':call <SID>NM_kill_this_buffer()<CR>',
- \ '=': ':call <SID>NM_folders_refresh_view()<CR>',
- \ '<Enter>': ':call <SID>NM_folders_show_search()<CR>',
- \ }
-
-" --- --- bindings for search screen {{{2
-let g:notmuch_search_maps = {
- \ '<Space>': ':call <SID>NM_search_show_thread(0)<CR>',
- \ '<Enter>': ':call <SID>NM_search_show_thread(1)<CR>',
- \ '<C-]>': ':call <SID>NM_search_expand(''<cword>'')<CR>',
- \ 'I': ':call <SID>NM_search_mark_read_thread()<CR>',
- \ 'a': ':call <SID>NM_search_archive_thread()<CR>',
- \ 'A': ':call <SID>NM_search_mark_read_then_archive_thread()<CR>',
- \ 'D': ':call <SID>NM_search_delete_thread()<CR>',
- \ 'f': ':call <SID>NM_search_filter()<CR>',
- \ 'm': ':call <SID>NM_new_mail()<CR>',
- \ 'o': ':call <SID>NM_search_toggle_order()<CR>',
- \ 'r': ':call <SID>NM_search_reply_to_thread()<CR>',
- \ 's': ':call <SID>NM_search_prompt()<CR>',
- \ ',s': ':call <SID>NM_search_edit()<CR>',
- \ 't': ':call <SID>NM_search_filter_by_tag()<CR>',
- \ 'q': ':call <SID>NM_kill_this_buffer()<CR>',
- \ '+': ':call <SID>NM_search_add_tags([])<CR>',
- \ '-': ':call <SID>NM_search_remove_tags([])<CR>',
- \ '=': ':call <SID>NM_search_refresh_view()<CR>',
- \ '?': ':echo <SID>NM_search_thread_id() . '' @ '' . join(<SID>NM_get_search_words())<CR>',
- \ }
-
-" --- --- bindings for show screen {{{2
-let g:notmuch_show_maps = {
- \ '<C-P>': ':call <SID>NM_show_previous(1, 0)<CR>',
- \ '<C-N>': ':call <SID>NM_show_next(1, 0)<CR>',
- \ '<C-]>': ':call <SID>NM_search_expand(''<cword>'')<CR>',
- \ 'q': ':call <SID>NM_kill_this_buffer()<CR>',
- \ 's': ':call <SID>NM_search_prompt()<CR>',
- \
- \ 'b': ':call <SID>NM_show_fold_toggle(''b'', ''bdy'', !g:notmuch_show_fold_bodies)<CR>',
- \ 'c': ':call <SID>NM_show_fold_toggle(''c'', ''cit'', !g:notmuch_show_fold_citations)<CR>',
- \ 'h': ':call <SID>NM_show_fold_toggle(''h'', ''hdr'', !g:notmuch_show_fold_headers)<CR>',
- \ 'i': ':call <SID>NM_show_fold_toggle(''i'', ''sig'', !g:notmuch_show_fold_signatures)<CR>',
- \
- \ 'I': ':call <SID>NM_show_mark_read_thread()<CR>',
- \ 'a': ':call <SID>NM_show_archive_thread()<CR>',
- \ 'A': ':call <SID>NM_show_mark_read_then_archive_thread()<CR>',
- \ 'D': ':call <SID>NM_show_delete_thread()<CR>',
- \ 'd': ':call <SID>NM_show_delete_message()<CR>',
- \ 'N': ':call <SID>NM_show_mark_read_then_next_open_message()<CR>',
- \ 'v': ':call <SID>NM_show_view_all_mime_parts()<CR>',
- \ '+': ':call <SID>NM_show_add_tag()<CR>',
- \ '-': ':call <SID>NM_show_remove_tag()<CR>',
- \ '<Space>': ':call <SID>NM_show_advance_marking_read_and_archiving()<CR>',
- \ '\|': ':call <SID>NM_show_pipe_message()<CR>',
- \
- \ '<S-Tab>': ':call <SID>NM_show_previous_fold()<CR>',
- \ '<Tab>': ':call <SID>NM_show_next_fold()<CR>',
- \ '<Enter>': ':call <SID>NM_show_toggle_fold()<CR>',
- \
- \ 'r': ':call <SID>NM_show_reply()<CR>',
- \ 'm': ':call <SID>NM_new_mail()<CR>',
- \ '?': ':echo <SID>NM_show_message_id() . '' @ '' . join(<SID>NM_get_search_words())<CR>',
- \ }
-
-" --- --- bindings for compose screen {{{2
-let g:notmuch_compose_nmaps = {
- \ ',s': ':call <SID>NM_compose_send()<CR>',
- \ ',a': ':call <SID>NM_compose_attach()<CR>',
- \ ',q': ':call <SID>NM_kill_this_buffer()<CR>',
- \ '<Tab>': ':call <SID>NM_compose_next_entry_area()<CR>',
- \ }
-let g:notmuch_compose_imaps = {
- \ '<Tab>': '<C-r>=<SID>NM_compose_next_entry_area()<CR>',
- \ }
-
-" --- implement folders screen {{{1
-
-function! s:NM_cmd_folders(words)
- if len(a:words)
- throw 'Not expecting any arguments for folders command.'
- endif
- let cmd = ['count']
- let disp = []
- let searches = []
- for entry in g:notmuch_folders
- let [ name, search ] = entry
- let data = s:NM_run(cmd + [search])
- let cnt = matchlist(data, '\(\d\+\)')[1]
- call add(disp, printf('%9d %-20s (%s)', cnt, name, search))
- call add(searches, search)
- endfor
-
- call <SID>NM_newBuffer('', 'folders', join(disp, "\n"))
- let b:nm_searches = searches
- let b:nm_timestamp = reltime()
-
- call <SID>NM_cmd_folders_mksyntax()
- call <SID>NM_set_map('n', g:notmuch_folders_maps)
- setlocal cursorline
- setlocal nowrap
-endfunction
-
-function! s:NM_cmd_folders_mksyntax()
-endfunction
-
-" --- --- folders screen action functions {{{2
-
-function! s:NM_folders_refresh_view()
- let lno = line('.')
- setlocal bufhidden=delete
- call s:NM_cmd_folders([])
- exec printf('norm %dG', lno)
-endfunction
-
-function! s:NM_folders_show_search()
- let line = line('.')
- let search = b:nm_searches[line-1]
-
- let prev_bufnr = bufnr('%')
- setlocal bufhidden=hide
- call <SID>NM_cmd_search([search])
- setlocal bufhidden=delete
- let b:nm_prev_bufnr = prev_bufnr
-endfunction
-
-
-" --- implement search screen {{{1
-
-function! s:NM_cmd_search(words)
- let cmd = ['search']
- if g:notmuch_search_newest_first
- let cmd = cmd + ['--sort=newest-first']
- else
- let cmd = cmd + ['--sort=oldest-first']
- endif
- let data = s:NM_run(cmd + a:words)
- let lines = split(data, "\n")
- let disp = copy(lines)
- call map(disp, 's:NM_cmd_search_fmtline(v:val)')
-
- call <SID>NM_newBuffer('', 'search', join(disp, "\n"))
- let b:nm_raw_lines = lines
- let b:nm_search_words = a:words
-
- call <SID>NM_set_map('n', g:notmuch_search_maps)
- setlocal cursorline
- setlocal nowrap
-endfunction
-function! s:NM_cmd_search_fmtline(line)
- let m = matchlist(a:line, '^\(thread:\S\+\)\s\(.\{12\}\) \[\(\d\+\)/\d\+\] \([^;]\+\); \%(\[[^\[]\+\] \)*\(.*\) (\([^(]*\))$')
- if !len(m)
- return 'ERROR PARSING: ' . a:line
- endif
- let max = g:notmuch_search_from_column_width
- let flist = {}
- for at in split(m[4], '[|,] ')
- let p = split(at, '[@.]')
- let flist[p[0]] = 1
- endfor
- let from = join(keys(flist), ", ")
- return printf("%-12s %3s %-20.20s | %s (%s)", m[2], m[3], from, m[5], m[6])
-endfunction
-
-" --- --- search screen action functions {{{2
-
-function! s:NM_search_show_thread(everything)
- let words = [ <SID>NM_search_thread_id() ]
- if !a:everything && exists('b:nm_search_words')
- call extend(words, ['AND', '('])
- call extend(words, b:nm_search_words)
- call add(words, ')')
- endif
- call <SID>NM_cmd_show(words)
- let b:nm_show_everything = a:everything
-endfunction
-
-function! s:NM_search_prompt()
- " TODO: input() can support completion
- let text = input('NotMuch Search: ')
- if strlen(text)
- let tags = split(text)
- else
- let tags = s:notmuch_initial_search_words_defaults
- endif
- let prev_bufnr = bufnr('%')
- if b:nm_type == 'search' && exists('b:nm_prev_bufnr')
- " TODO: we intend to replace the current buffer,
- " ... maybe we could just clear it
- let prev_bufnr = b:nm_prev_bufnr
- setlocal bufhidden=delete
- else
- setlocal bufhidden=hide
- endif
- call <SID>NM_cmd_search(tags)
- setlocal bufhidden=delete
- let b:nm_prev_bufnr = prev_bufnr
-endfunction
-
-function! s:NM_search_edit()
- " TODO: input() can support completion
- let text = input('NotMuch Search: ', join(b:nm_search_words, ' '))
- if strlen(text)
- call <SID>NM_cmd_search(split(text))
- endif
-endfunction
-
-function! s:NM_search_mark_read_thread()
- call <SID>NM_tag([], ['-unread'])
- norm j
-endfunction
-
-function! s:NM_search_archive_thread()
- call <SID>NM_tag([], ['-inbox'])
- norm j
-endfunction
-
-function! s:NM_search_mark_read_then_archive_thread()
- call <SID>NM_tag([], ['-unread', '-inbox'])
- norm j
-endfunction
-
-function! s:NM_search_delete_thread()
- call <SID>NM_tag([], ['+delete','-inbox','-unread'])
- norm j
-endfunction
-
-function! s:NM_search_filter()
- call <SID>NM_search_filter_helper('Filter: ', '', '')
-endfunction
-
-function! s:NM_search_filter_by_tag()
- call <SID>NM_search_filter_helper('Filter Tag(s): ', 'tag:', 'and')
-endfunction
-
-function! s:NM_search_filter_helper(prompt, prefix, joiner)
- " TODO: input() can support completion
- let text = substitute(input(a:prompt), '\v(^\s*|\s*$|\n)', '', 'g')
- if !strlen(text)
- return
- endif
-
- let tags = b:nm_search_words + ['AND']
- \ + <SID>NM_combine_tags(a:prefix, split(text), a:joiner, '()')
-
- let prev_bufnr = bufnr('%')
- setlocal bufhidden=hide
- call <SID>NM_cmd_search(tags)
- setlocal bufhidden=delete
- let b:nm_prev_bufnr = prev_bufnr
-endfunction
-
-function! s:NM_search_toggle_order()
- let g:notmuch_search_newest_first = !g:notmuch_search_newest_first
- " FIXME: maybe this would be better done w/o reading re-reading the lines
- " reversing the b:nm_raw_lines and the buffer lines would be better
- call <SID>NM_search_refresh_view()
-endfunction
-
-function! s:NM_search_reply_to_thread()
- let cmd = ['reply']
- call add(cmd, <SID>NM_search_thread_id())
- call add(cmd, 'AND')
- call extend(cmd, <SID>NM_get_search_words())
-
- let data = <SID>NM_run(cmd)
- let lines = split(data, "\n")
- call <SID>NM_newComposeBuffer(lines, 0)
-endfunction
-
-function! s:NM_search_add_tags(tags)
- call <SID>NM_search_add_remove_tags('Add Tag(s): ', '+', a:tags)
-endfunction
-
-function! s:NM_search_remove_tags(tags)
- call <SID>NM_search_add_remove_tags('Remove Tag(s): ', '-', a:tags)
-endfunction
-
-function! s:NM_search_refresh_view()
- let lno = line('.')
- let prev_bufnr = b:nm_prev_bufnr
- setlocal bufhidden=delete
- call <SID>NM_cmd_search(b:nm_search_words)
- let b:nm_prev_bufnr = prev_bufnr
- " FIXME: should find the line of the thread we were on if possible
- exec printf('norm %dG', lno)
-endfunction
-
-" --- --- search screen helper functions {{{2
-
-function! s:NM_search_thread_id()
- if !exists('b:nm_raw_lines')
- throw 'Eeek! no b:nm_raw_lines'
- endif
- let mnum = line('.') - 1
- if len(b:nm_raw_lines) <= mnum
- return ''
- endif
- let info = b:nm_raw_lines[mnum]
- let what = split(info, '\s\+')[0]
- return what
-endfunction
-
-function! s:NM_search_add_remove_tags(prompt, prefix, intags)
- if type(a:intags) != type([]) || len(a:intags) == 0
- " TODO: input() can support completion
- let text = input(a:prompt)
- if !strlen(text)
- return
- endif
- let tags = split(text, ' ')
- else
- let tags = a:intags
- endif
- call map(tags, 'a:prefix . v:val')
- call <SID>NM_tag([], tags)
-endfunction
-
-" --- implement show screen {{{1
-
-function! s:NM_cmd_show(words)
- let prev_bufnr = bufnr('%')
- let data = s:NM_run(['show', '--entire-thread'] + a:words)
- let lines = split(data, "\n")
-
- let info = s:NM_cmd_show_parse(lines)
-
- setlocal bufhidden=hide
- call <SID>NM_newBuffer('', 'show', join(info['disp'], "\n"))
- setlocal bufhidden=delete
- let b:nm_search_words = a:words
- let b:nm_raw_info = info
- let b:nm_prev_bufnr = prev_bufnr
-
- call <SID>NM_cmd_show_mkfolds()
- call <SID>NM_cmd_show_mksyntax()
- call <SID>NM_set_map('n', g:notmuch_show_maps)
- setlocal foldtext=NM_cmd_show_foldtext()
- setlocal fillchars=
- setlocal foldcolumn=6
-
-endfunction
-
-function! s:NM_show_previous(can_change_thread, find_matching)
- let everything = exists('b:nm_show_everything') ? b:nm_show_everything : 0
- let info = b:nm_raw_info
- let lnum = line('.')
- for msg in reverse(copy(info['msgs']))
- if a:find_matching && msg['match'] == '0'
- continue
- endif
- if lnum <= msg['start']
- continue
- endif
-
- exec printf('norm %dGzt', msg['start'])
- " TODO: try to fit the message on screen
- return
- endfor
- if !a:can_change_thread
- return
- endif
- call <SID>NM_kill_this_buffer()
- if line('.') > 1
- norm k
- call <SID>NM_search_show_thread(everything)
- norm G
- call <SID>NM_show_previous(0, a:find_matching)
- else
- echo 'No more messages.'
- endif
-endfunction
-
-function! s:NM_show_next(can_change_thread, find_matching)
- let info = b:nm_raw_info
- let lnum = line('.')
- for msg in info['msgs']
- if a:find_matching && msg['match'] == '0'
- continue
- endif
- if lnum >= msg['start']
- continue
- endif
-
- exec printf('norm %dGzt', msg['start'])
- " TODO: try to fit the message on screen
- return
- endfor
- if a:can_change_thread
- call <SID>NM_show_next_thread()
- endif
-endfunction
-
-function! s:NM_show_next_thread()
- let everything = exists('b:nm_show_everything') ? b:nm_show_everything : 0
- call <SID>NM_kill_this_buffer()
- if line('.') != line('$')
- norm j
- call <SID>NM_search_show_thread(everything)
- else
- echo 'No more messages.'
- endif
-endfunction
-
-function! s:NM_show_mark_read_thread()
- call <SID>NM_tag(b:nm_search_words, ['-unread'])
- call <SID>NM_show_next_thread()
-endfunction
-
-function! s:NM_show_archive_thread()
- call <SID>NM_tag(b:nm_search_words, ['-inbox'])
- call <SID>NM_show_next_thread()
-endfunction
-
-function! s:NM_show_mark_read_then_archive_thread()
- call <SID>NM_tag(b:nm_search_words, ['-unread', '-inbox'])
- call <SID>NM_show_next_thread()
-endfunction
-
-function! s:NM_show_delete_thread()
- call <SID>NM_tag(b:nm_search_words, ['+delete', '-inbox', '-unread'])
- call <SID>NM_show_next_thread()
-endfunction
-
-function! s:NM_show_delete_message()
- let msg = <SID>NM_show_get_message_for_line(line('.'))
- call <SID>NM_tag([msg['id']], ['+delete', '-inbox', '-unread'])
-endfunction
-
-function! s:NM_show_mark_read_then_next_open_message()
- echo 'not implemented'
-endfunction
-
-function! s:NM_show_previous_message()
- echo 'not implemented'
-endfunction
-
-function! s:NM_show_reply()
- let cmd = ['reply']
- call add(cmd, <SID>NM_show_message_id())
- call add(cmd, 'AND')
- call extend(cmd, <SID>NM_get_search_words())
-
- let data = <SID>NM_run(cmd)
- let lines = split(data, "\n")
- call <SID>NM_newComposeBuffer(lines, 0)
-endfunction
-
-function! s:NM_show_view_all_mime_parts()
- echo 'not implemented'
-endfunction
-
-function! s:NM_show_view_raw_message()
- echo 'not implemented'
-endfunction
-
-function! s:NM_show_add_tag()
- echo 'not implemented'
-endfunction
-
-function! s:NM_show_remove_tag()
- echo 'not implemented'
-endfunction
-
-" if entire message is not visible scroll down 1/2 page or less to get to the bottom of message
-" otherwise go to next message
-" any message that is viewed entirely has inbox and unread tags removed
-function! s:NM_show_advance_marking_read_and_archiving()
- let advance_tags = ['unread', 'inbox']
-
- let vis_top = line('w0')
- let vis_bot = line('w$')
-
- let msg_top = <SID>NM_show_get_message_for_line(vis_top)
- if !has_key(msg_top,'id')
- throw "No top visible message."
- endif
-
- " if the top message is the last message, just expunge the entire thread and move on
- if msg_top['end'] == line('$')
- let ids = []
- for msg in b:nm_raw_info['msgs']
- if has_key(msg,'match') && msg['match'] != '0'
- call add(ids, msg['id'])
- endif
- endfor
- let filter = <SID>NM_combine_tags('tag:', advance_tags, 'OR', '()')
- \ + ['AND']
- \ + <SID>NM_combine_tags('', ids, 'OR', '()')
- call map(advance_tags, '"-" . v:val')
- call <SID>NM_tag(filter, advance_tags)
- call <SID>NM_show_next(1, 1)
- return
- endif
-
- let msg_bot = <SID>NM_show_get_message_for_line(vis_bot)
- if !has_key(msg_bot,'id')
- throw "No bottom visible message."
- endif
-
- " if entire message fits on the screen, read/archive it, move to the next one
- if msg_top['id'] != msg_bot['id'] || msg_top['end'] <= vis_bot
- exec printf('norm %dG', vis_top)
- call <SID>NM_show_next(0, 1)
- if has_key(msg_top,'match') && msg_top['match'] != '0'
- redraw
- " do this last to hide the latency
- let filter = <SID>NM_combine_tags('tag:', advance_tags, 'OR', '()')
- \ + ['AND', msg_top['id']]
- call map(advance_tags, '"-" . v:val')
- call <SID>NM_tag(filter, advance_tags)
- endif
- return
- endif
-
- " entire message does not fit on the screen, scroll down to bottom, max 1/2 screen
- let jmp = winheight(winnr()) / 2
- let max = msg_bot['end'] - vis_bot
- if jmp > max
- let jmp = max
- endif
- exec printf('norm %dGzt', vis_top + jmp)
- return
-endfunction
-
-function! s:NM_show_pipe_message()
- echo 'not implemented'
-endfunction
-
-function! s:NM_show_previous_fold()
- echo 'not implemented'
-endfunction
-
-function! s:NM_show_next_fold()
- echo 'not implemented'
-endfunction
-
-function! s:NM_show_toggle_fold()
- echo 'not implemented'
-endfunction
-
-
-" --- --- show screen helper functions {{{2
-
-function! s:NM_show_get_message_for_line(line)
- for msg in b:nm_raw_info['msgs']
- if a:line > msg['end']
- continue
- endif
- return msg
- endfor
- return {}
-endfunction
-
-function! s:NM_show_message_id()
- if !exists('b:nm_raw_info')
- throw 'Eeek! no b:nm_raw_info'
- endif
- let msg = <SID>NM_show_get_message_for_line(line('.'))
- if has_key(msg,'id')
- return msg['id']
- endif
- return ''
-endfunction
-
-function! s:NM_show_fold_toggle(key, type, fold)
- let info = b:nm_raw_info
- let act = 'open'
- if a:fold
- let act = 'close'
- endif
- for fld in info['folds']
- if fld[0] != a:type
- continue
- endif
- "let idx = fld[3]
- "let msg = info['msgs'][idx]
- "if has_key(msg,'match') && msg['match'] == '0'
- " continue
- "endif
- let cls = foldclosed(fld[1])
- if cls != -1 && cls != fld[1]
- continue
- endif
- exec printf('%dfold%s', fld[1], act)
- endfor
- exec printf('nnoremap <buffer> %s :call <SID>NM_show_fold_toggle(''%s'', ''%s'', %d)<CR>', a:key, a:key, a:type, !a:fold)
-endfunction
-
-
-" s:NM_cmd_show_parse returns the following dictionary:
-" 'disp': lines to display
-" 'msgs': message info dicts { start, end, id, depth, filename, descr, header }
-" 'folds': fold info arrays [ type, start, end ]
-" 'foldtext': fold text indexed by start line
-function! s:NM_cmd_show_parse(inlines)
- let info = { 'disp': [],
- \ 'msgs': [],
- \ 'folds': [],
- \ 'foldtext': {} }
- let msg = {}
- let hdr = {}
-
- let in_message = 0
- let in_header = 0
- let in_body = 0
- let in_part = ''
-
- let body_start = -1
- let part_start = -1
-
- let mode_type = ''
- let mode_start = -1
-
- let inlnum = 0
- for line in a:inlines
- let inlnum = inlnum + 1
- let foldinfo = []
-
- if strlen(in_part)
- let part_end = 0
-
- if match(line, g:notmuch_show_part_end_regexp) != -1
- let part_end = len(info['disp'])
- else
- call add(info['disp'], line)
- endif
-
- if in_part == 'text/plain'
- if !part_end && mode_type == ''
- if match(line, g:notmuch_show_signature_regexp) != -1
- let mode_type = 'sig'
- let mode_start = len(info['disp'])
- elseif match(line, g:notmuch_show_citation_regexp) != -1
- let mode_type = 'cit'
- let mode_start = len(info['disp'])
- endif
- elseif mode_type == 'cit'
- if part_end || match(line, g:notmuch_show_citation_regexp) == -1
- let outlnum = len(info['disp'])
- if !part_end
- let outlnum = outlnum - 1
- endif
- let foldinfo = [ mode_type, mode_start, outlnum, len(info['msgs']),
- \ printf('[ %d-line citation. Press "c" to show. ]', 1 + outlnum - mode_start) ]
- let mode_type = ''
- endif
- elseif mode_type == 'sig'
- let outlnum = len(info['disp'])
- if (outlnum - mode_start) > g:notmuch_show_signature_lines_max
- let mode_type = ''
- elseif part_end
- let foldinfo = [ mode_type, mode_start, outlnum, len(info['msgs']),
- \ printf('[ %d-line signature. Press "i" to show. ]', 1 + outlnum - mode_start) ]
- let mode_type = ''
- endif
- endif
- endif
-
- if part_end
- " FIXME: this is a hack for handling two folds being added for one line
- " we should handle adding a fold in a function
- if len(foldinfo) && foldinfo[1] < foldinfo[2]
- call add(info['folds'], foldinfo[0:3])
- let info['foldtext'][foldinfo[1]] = foldinfo[4]
- endif
-
- let foldinfo = [ 'text', part_start, part_end, len(info['msgs']),
- \ printf('[ %d-line %s. Press "p" to show. ]', part_end - part_start, in_part) ]
- let in_part = ''
- call add(info['disp'], '')
- endif
-
- elseif in_body
- if !has_key(msg,'body_start')
- let msg['body_start'] = len(info['disp']) + 1
- endif
- if match(line, g:notmuch_show_body_end_regexp) != -1
- let body_end = len(info['disp'])
- let foldinfo = [ 'bdy', body_start, body_end, len(info['msgs']),
- \ printf('[ BODY %d - %d lines ]', len(info['msgs']), body_end - body_start) ]
-
- let in_body = 0
-
- elseif match(line, g:notmuch_show_part_begin_regexp) != -1
- let m = matchlist(line, 'ID: \(\d\+\), Content-type: \(\S\+\)')
- let in_part = 'unknown'
- if len(m)
- let in_part = m[2]
- endif
- call add(info['disp'],
- \ printf('--- %s ---', in_part))
- " We don't yet handle nested parts, so pop
- " multipart/* immediately so text/plain
- " sub-parts are parsed properly
- if match(in_part, '^multipart/') != -1
- let in_part = ''
- else
- let part_start = len(info['disp']) + 1
- endif
- endif
-
- elseif in_header
- if in_header == 1
- let msg['descr'] = line
- call add(info['disp'], line)
- let in_header = 2
- let msg['hdr_start'] = len(info['disp']) + 1
-
- else
- if match(line, g:notmuch_show_header_end_regexp) != -1
- let hdr_start = msg['hdr_start']+1
- let hdr_end = len(info['disp'])
- let foldinfo = [ 'hdr', hdr_start, hdr_end, len(info['msgs']),
- \ printf('[ %d-line headers. Press "h" to show. ]', hdr_end + 1 - hdr_start) ]
- let msg['header'] = hdr
- let in_header = 0
- let hdr = {}
- else
- let m = matchlist(line, '^\(\w\+\):\s*\(.*\)$')
- if len(m)
- let hdr[m[1]] = m[2]
- if match(g:notmuch_show_headers, m[1]) != -1
- call add(info['disp'], line)
- endif
- endif
- endif
- endif
-
- elseif in_message
- if match(line, g:notmuch_show_message_end_regexp) != -1
- let msg['end'] = len(info['disp'])
- call add(info['disp'], '')
-
- let foldinfo = [ 'msg', msg['start'], msg['end'], len(info['msgs']),
- \ printf('[ MSG %d - %s ]', len(info['msgs']), msg['descr']) ]
-
- call add(info['msgs'], msg)
- let msg = {}
- let in_message = 0
- let in_header = 0
- let in_body = 0
- let in_part = ''
-
- elseif match(line, g:notmuch_show_header_begin_regexp) != -1
- let in_header = 1
- continue
-
- elseif match(line, g:notmuch_show_body_begin_regexp) != -1
- let body_start = len(info['disp']) + 1
- let in_body = 1
- continue
- endif
-
- else
- if match(line, g:notmuch_show_message_begin_regexp) != -1
- let msg['start'] = len(info['disp']) + 1
-
- let m = matchlist(line, g:notmuch_show_message_parse_regexp)
- if len(m)
- let msg['id'] = m[1]
- let msg['depth'] = m[2]
- let msg['match'] = m[3]
- let msg['excluded'] = m[4]
- let msg['filename'] = m[5]
- endif
-
- let in_message = 1
- endif
- endif
-
- if len(foldinfo) && foldinfo[1] < foldinfo[2]
- call add(info['folds'], foldinfo[0:3])
- let info['foldtext'][foldinfo[1]] = foldinfo[4]
- endif
- endfor
- return info
-endfunction
-
-function! s:NM_cmd_show_mkfolds()
- let info = b:nm_raw_info
-
- for afold in info['folds']
- exec printf('%d,%dfold', afold[1], afold[2])
- let state = 'open'
- if (afold[0] == 'sig' && g:notmuch_show_fold_signatures)
- \ || (afold[0] == 'cit' && g:notmuch_show_fold_citations)
- \ || (afold[0] == 'bdy' && g:notmuch_show_fold_bodies)
- \ || (afold[0] == 'hdr' && g:notmuch_show_fold_headers)
- let state = 'close'
- elseif afold[0] == 'msg'
- let idx = afold[3]
- let msg = info['msgs'][idx]
- if has_key(msg,'match') && msg['match'] == '0'
- let state = 'close'
- endif
- endif
- exec printf('%dfold%s', afold[1], state)
- endfor
-endfunction
-
-function! s:NM_cmd_show_mksyntax()
- let info = b:nm_raw_info
- let cnt = 0
- for msg in info['msgs']
- let cnt = cnt + 1
- let start = msg['start']
- let hdr_start = msg['hdr_start']
- let body_start = msg['body_start']
- let end = msg['end']
- exec printf('syntax region nmShowMsg%dDesc start=''\%%%dl'' end=''\%%%dl'' contains=@nmShowMsgDesc', cnt, start, start+1)
- exec printf('syntax region nmShowMsg%dHead start=''\%%%dl'' end=''\%%%dl'' contains=@nmShowMsgHead', cnt, hdr_start, body_start)
- exec printf('syntax region nmShowMsg%dBody start=''\%%%dl'' end=''\%%%dl'' contains=@nmShowMsgBody', cnt, body_start, end)
- endfor
-endfunction
-
-function! NM_cmd_show_foldtext()
- let foldtext = b:nm_raw_info['foldtext']
- return foldtext[v:foldstart]
-endfunction
-
-
-" --- implement compose screen {{{1
-
-function! s:NM_cmd_compose(words, body_lines)
- let lines = []
- let start_on_line = 0
-
- let hdrs = { }
- for word in a:words
- let m = matchlist(word, '^\(\w[^:]*\):\s*\(.*\)\s*$')
- if !len(m)
- throw 'Eeek! bad parameter ''' . string(word) . ''''
- endif
- let key = substitute(m[1], '\<\w', '\U&', 'g')
- if !has_key(hdrs, key)
- let hdrs[key] = []
- endif
- if strlen(m[2])
- call add(hdrs[key], m[2])
- endif
- endfor
-
- if !has_key(hdrs, 'From') || !len(hdrs['From'])
- let me = <SID>NM_compose_get_user_email()
- let hdrs['From'] = [ me ]
- endif
-
- for key in g:notmuch_compose_headers
- let text = has_key(hdrs, key) ? join(hdrs[key], ', ') : ''
- call add(lines, key . ': ' . text)
- if !start_on_line && !strlen(text)
- let start_on_line = len(lines)
- endif
- endfor
-
- for [key,val] in items(hdrs)
- if match(g:notmuch_compose_headers, key) == -1
- let line = key . ': ' . join(val, ', ')
- call add(lines, line)
- endif
- endfor
-
- call add(lines, '')
- if !start_on_line
- let start_on_line = len(lines) + 1
- endif
-
- if len(a:body_lines)
- call extend(lines, a:body_lines)
- else
- call extend(lines, [ '', '' ])
- endif
-
- call <SID>NM_newComposeBuffer(lines, start_on_line)
-endfunction
-
-function! s:NM_compose_send()
- call <SID>NM_assert_buffer_type('compose')
- let fname = expand('%')
- let lnum = 1
- let line = getline(lnum)
- let lst_hdr = ''
- while match(line, '^$') == -1
- if !exists("hdr_starts") && match(line, '^Notmuch-Help:') == -1
- let hdr_starts = lnum - 1
- endif
- let lnum = lnum + 1
- let line = getline(lnum)
- endwhile
- let body_starts = lnum - 1
-
- call append(body_starts, 'Date: ' . strftime('%a, %d %b %Y %H:%M:%S %z'))
- exec printf(':0,%dd', hdr_starts)
- write
-
- let line = getline(1)
- let m = matchlist(line, '^From:\s*\(.*\)\s*<\(.*\)>$')
- if (len(m) >= 2)
- let from = m[2]
- else
- let m = matchlist(line, '^From:\s*\(.*\)$')
- let from = m[1]
- endif
-
- let cmdtxt = g:notmuch_sendmail . ' -t -f ' . from . ' < ' . fname
- let out = system(cmdtxt)
- let err = v:shell_error
- if err
- undo
- write
- call <SID>NM_newBuffer('new', 'error',
- \ "While running...\n" .
- \ ' ' . cmdtxt . "\n" .
- \ "\n" .
- \ "Failed with...\n" .
- \ substitute(out, '^', ' ', 'g'))
- echohl Error
- echo 'Eeek! unable to send mail'
- echohl None
- return
- endif
-
- if !exists('b:nm_prev_bufnr')
- bdelete
- else
- let prev_bufnr = b:nm_prev_bufnr
- bdelete
- if prev_bufnr == bufnr('%')
- exec printf("buffer %d", prev_bufnr)
- endif
- endif
- call delete(fname)
- echo 'Mail sent successfully.'
-endfunction
-
-function! s:NM_compose_attach()
- echo 'not implemented'
-endfunction
-
-function! s:NM_compose_next_entry_area()
- let lnum = line('.')
- let hdr_end = <SID>NM_compose_find_line_match(1,'^$',1)
- if lnum < hdr_end
- let lnum = lnum + 1
- let line = getline(lnum)
- if match(line, '^\([^:]\+\):\s*$') == -1
- call cursor(lnum, strlen(line) + 1)
- return ''
- endif
- while match(getline(lnum+1), '^\s') != -1
- let lnum = lnum + 1
- endwhile
- call cursor(lnum, strlen(getline(lnum)) + 1)
- return ''
-
- elseif lnum == hdr_end
- call cursor(lnum+1, strlen(getline(lnum+1)) + 1)
- return ''
- endif
- if mode() == 'i'
- if !getbufvar(bufnr('.'), '&et')
- return "\t"
- endif
- let space = ''
- let shiftwidth = a:shiftwidth
- let shiftwidth = shiftwidth - ((virtcol('.')-1) % shiftwidth)
- " we assume no one has shiftwidth set to more than 40 :)
- return ' '[0:shiftwidth]
- endif
-endfunction
-
-" --- --- compose screen helper functions {{{2
-
-function! s:NM_compose_get_user_email()
- " TODO: do this properly (still), i.e., allow for multiple email accounts
- let email = substitute(system('notmuch config get user.primary_email'), '\v(^\s*|\s*$|\n)', '', 'g')
- return email
-endfunction
-
-function! s:NM_compose_find_line_match(start, pattern, failure)
- let lnum = a:start
- let lend = line('$')
- while lnum < lend
- if match(getline(lnum), a:pattern) != -1
- return lnum
- endif
- let lnum = lnum + 1
- endwhile
- return a:failure
-endfunction
-
-
-" --- notmuch helper functions {{{1
-
-function! s:NM_newBuffer(how, type, content)
- if strlen(a:how)
- exec a:how
- else
- enew
- endif
- setlocal buftype=nofile readonly modifiable scrolloff=0 sidescrolloff=0
- silent put=a:content
- keepjumps 0d
- setlocal nomodifiable
- execute printf('set filetype=notmuch-%s', a:type)
- execute printf('set syntax=notmuch-%s', a:type)
- let b:nm_type = a:type
-endfunction
-
-function! s:NM_newFileBuffer(fdir, fname, type, lines)
- let fdir = expand(a:fdir)
- if !isdirectory(fdir)
- call mkdir(fdir, 'p')
- endif
- let file_name = <SID>NM_mktemp(fdir, a:fname)
- if writefile(a:lines, file_name)
- throw 'Eeek! couldn''t write to temporary file ' . file_name
- endif
- exec printf('edit %s', file_name)
- setlocal buftype= noreadonly modifiable scrolloff=0 sidescrolloff=0
- execute printf('set filetype=notmuch-%s', a:type)
- execute printf('set syntax=notmuch-%s', a:type)
- let b:nm_type = a:type
-endfunction
-
-function! s:NM_newComposeBuffer(lines, start_on_line)
- let lines = a:lines
- let start_on_line = a:start_on_line
- let real_hdr_start = 1
- if g:notmuch_compose_header_help
- let help_lines = [
- \ 'Notmuch-Help: Type in your message here; to help you use these bindings:',
- \ 'Notmuch-Help: ,a - attach a file',
- \ 'Notmuch-Help: ,s - send the message (Notmuch-Help lines will be removed)',
- \ 'Notmuch-Help: ,q - abort the message',
- \ 'Notmuch-Help: <Tab> - skip through header lines',
- \ ]
- call extend(lines, help_lines, 0)
- let real_hdr_start = len(help_lines)
- if start_on_line > 0
- let start_on_line = start_on_line + len(help_lines)
- endif
- endif
- call extend(lines, g:notmuch_signature)
-
-
- let prev_bufnr = bufnr('%')
- setlocal bufhidden=hide
- call <SID>NM_newFileBuffer(g:notmuch_compose_temp_file_dir, '%s.mail',
- \ 'compose', lines)
- setlocal bufhidden=hide
- let b:nm_prev_bufnr = prev_bufnr
-
- call <SID>NM_set_map('n', g:notmuch_compose_nmaps)
- call <SID>NM_set_map('i', g:notmuch_compose_imaps)
-
- if start_on_line > 0 && start_on_line <= len(lines)
- call cursor(start_on_line, strlen(getline(start_on_line)) + 1)
- else
- call cursor(real_hdr_start, strlen(getline(real_hdr_start)) + 1)
- call <SID>NM_compose_next_entry_area()
- endif
-
- if g:notmuch_compose_insert_mode_start
- startinsert!
- endif
- echo 'Type your message, use <TAB> to jump to next header and then body.'
-endfunction
-
-function! s:NM_assert_buffer_type(type)
- if !exists('b:nm_type') || b:nm_type != a:type
- throw printf('Eeek! expected type %s, but got %s.', a:type,
- \ exists(b:nm_type) ? b:nm_type : 'something else')
- endif
-endfunction
-
-function! s:NM_mktemp(dir, name)
- let time_stamp = strftime('%Y%m%d-%H%M%S')
- let file_name = substitute(a:dir,'/*$','/','') . printf(a:name, time_stamp)
- " TODO: check if it exists, try again
- return file_name
-endfunction
-
-function! s:NM_shell_escape(word)
- " TODO: use shellescape()
- let word = substitute(a:word, '''', '\\''', 'g')
- return '''' . word . ''''
-endfunction
-
-" this function was taken from git.vim, then fixed up
-" http://github.com/motemen/git-vim
-function! s:NM_shell_split(cmd)
- let l:split_cmd = []
- let cmd = a:cmd
- let iStart = 0
- while 1
- let t = match(cmd, '\S', iStart)
- if t < iStart
- break
- endif
- let iStart = t
-
- let iSpace = match(cmd, '\v(\s|$)', iStart)
- if iSpace < iStart
- break
- endif
-
- let iQuote1 = match(cmd, '\(^["'']\|[^\\]\@<=["'']\)', iStart)
- if iQuote1 > iSpace || iQuote1 < iStart
- let iEnd = iSpace - 1
- let l:split_cmd += [ cmd[iStart : iEnd] ]
- else
- let q = cmd[iQuote1]
- let iQuote2 = match(cmd, '[^\\]\@<=[' . q . ']', iQuote1 + 1)
- if iQuote2 < iQuote1
- throw 'No matching ' . q . ' quote'
- endif
- let iEnd = iQuote2
- let l:split_cmd += [ cmd[iStart+1 : iEnd-1 ] ]
- endif
-
-
- let iStart = iEnd + 1
- endwhile
-
- return l:split_cmd
-endfunction
-
-
-function! s:NM_run(args)
- let words = a:args
- call map(words, 's:NM_shell_escape(v:val)')
- let cmd = g:notmuch_cmd . ' ' . join(words) . '< /dev/null'
-
- if exists('g:notmuch_debug') && g:notmuch_debug
- let start = reltime()
- let out = system(cmd)
- let err = v:shell_error
- let delta = reltime(start)
-
- echo printf('[%s] {%s} %s', reltimestr(delta), string(err), string(cmd))
- else
- let out = system(cmd)
- let err = v:shell_error
- endif
-
- if err
- echohl Error
- echo substitute(out, '\n*$', '', '')
- echohl None
- return ''
- else
- return out
- endif
-endfunction
-
-" --- external mail handling helpers {{{1
-
-function! s:NM_new_mail()
- call <SID>NM_cmd_compose([], [])
-endfunction
-
-" --- tag manipulation helpers {{{1
-
-" used to combine an array of words with prefixes and separators
-" example:
-" NM_combine_tags('tag:', ['one', 'two', 'three'], 'OR', '()')
-" -> ['(', 'tag:one', 'OR', 'tag:two', 'OR', 'tag:three', ')']
-function! s:NM_combine_tags(word_prefix, words, separator, brackets)
- let res = []
- for word in a:words
- if len(res) && strlen(a:separator)
- call add(res, a:separator)
- endif
- call add(res, a:word_prefix . word)
- endfor
- if len(res) > 1 && strlen(a:brackets)
- if strlen(a:brackets) != 2
- throw 'Eeek! brackets arg to NM_combine_tags must be 2 chars'
- endif
- call insert(res, a:brackets[0])
- call add(res, a:brackets[1])
- endif
- return res
-endfunction
-
-" --- other helpers {{{1
-
-function! s:NM_get_search_words()
- if !exists('b:nm_search_words')
- throw 'Eeek! no b:nm_search_words'
- endif
- return b:nm_search_words
-endfunction
-
-function! s:NM_kill_this_buffer()
- if exists('b:nm_prev_bufnr')
- let prev_bufnr = b:nm_prev_bufnr
- bdelete!
- exec printf("buffer %d", prev_bufnr)
- else
- echo "This is the last buffer; use :q<CR> to quit."
- endif
-endfunction
-
-function! s:NM_search_expand(arg)
- let word = expand(a:arg)
- let prev_bufnr = bufnr('%')
- setlocal bufhidden=hide
- call <SID>NM_cmd_search([word])
- setlocal bufhidden=delete
- let b:nm_prev_bufnr = prev_bufnr
-endfunction
-
-function! s:NM_tag(filter, tags)
- let filter = len(a:filter) ? a:filter : [<SID>NM_search_thread_id()]
- if !len(filter)
- throw 'Eeek! I couldn''t find the thread id!'
- endif
- let args = ['tag']
- call extend(args, a:tags)
- call add(args, '--')
- call extend(args, filter)
- " TODO: handle errors
- call <SID>NM_run(args)
-endfunction
-
-" --- process and set the defaults {{{1
-
-function! NM_set_defaults(force)
- for [key, dflt] in items(s:notmuch_defaults)
- let cmd = ''
- if !a:force && exists(key) && type(dflt) == type(eval(key))
- continue
- elseif type(dflt) == type(0)
- let cmd = printf('let %s = %d', key, dflt)
- elseif type(dflt) == type('')
- let cmd = printf('let %s = ''%s''', key, dflt)
- " FIXME: not sure why this didn't work when dflt is an array
- "elseif type(dflt) == type([])
- " let cmd = printf('let %s = %s', key, string(dflt))
- else
- echoe printf('E: Unknown type in NM_set_defaults(%d) using [%s,%s]',
- \ a:force, key, string(dflt))
- continue
- endif
- exec cmd
- endfor
-endfunction
-call NM_set_defaults(0)
-
-" for some reason NM_set_defaults() didn't work for arrays...
-if !exists('g:notmuch_show_headers')
- let g:notmuch_show_headers = s:notmuch_show_headers_defaults
-endif
-if !exists('g:notmuch_initial_search_words')
- let g:notmuch_initial_search_words = s:notmuch_initial_search_words_defaults
-endif
-if !exists('g:notmuch_folders')
- let g:notmuch_folders = s:notmuch_folders_defaults
-endif
-
-if !exists('g:notmuch_signature')
- let g:notmuch_signature = s:notmuch_signature_defaults
-endif
-if !exists('g:notmuch_compose_headers')
- let g:notmuch_compose_headers = s:notmuch_compose_headers_defaults
-endif
-
-" --- assign keymaps {{{1
-
-function! s:NM_set_map(type, maps)
- nmapclear
- for [key, code] in items(a:maps)
- exec printf('%snoremap <buffer> %s %s', a:type, key, code)
- endfor
- " --- this is a hack for development :)
- nnoremap ,nmr :runtime! plugin/notmuch.vim<CR>
-endfunction
-
-" --- command handler {{{1
-
-function! NotMuch(args)
- let args = a:args
- if !strlen(args)
- let args = 'folders'
- endif
-
- let words = <SID>NM_shell_split(args)
- if words[0] == 'folders' || words[0] == 'f'
- let words = words[1:]
- call <SID>NM_cmd_folders(words)
-
- elseif words[0] == 'search' || words[0] == 's'
- if len(words) > 1
- let words = words[1:]
- elseif exists('b:nm_search_words')
- let words = b:nm_search_words
- else
- let words = g:notmuch_initial_search_words
- endif
- call <SID>NM_cmd_search(words)
-
- elseif words[0] == 'show'
- echoe 'show is not yet implemented.'
-
- elseif words[0] == 'new' || words[0] == 'compose'
- let words = words[1:]
- call <SID>NM_cmd_compose(words, [])
- endif
-endfunction
-function! CompleteNotMuch(arg_lead, cmd_line, cursor_pos)
- return []
-endfunction
-
-
-" --- glue {{{1
-
-command! -nargs=* -complete=customlist,CompleteNotMuch NotMuch call NotMuch(<q-args>)
-cabbrev notmuch <c-r>=(getcmdtype()==':' && getcmdpos()==1 ? 'NotMuch' : 'notmuch')<CR>
-
-" vim: set ft=vim ts=8 sw=8 et foldmethod=marker :
+++ /dev/null
-runtime! syntax/mail.vim
-
-syntax region nmComposeHelp contains=nmComposeHelpLine start='^Notmuch-Help:\%1l' end='^\(Notmuch-Help:\)\@!'
-syntax match nmComposeHelpLine /Notmuch-Help:/ contained
-
-highlight link nmComposeHelp Include
-highlight link nmComposeHelpLine Error
+++ /dev/null
-" notmuch folders mode syntax file
-
-syntax region nmFoldersCount start='^' end='\%10v'
-syntax region nmFoldersName start='\%11v' end='\%31v'
-syntax match nmFoldersSearch /([^()]\+)$/
-
-highlight link nmFoldersCount Statement
-highlight link nmFoldersName Type
-highlight link nmFoldersSearch String
-
-highlight CursorLine term=reverse cterm=reverse gui=reverse
-
+++ /dev/null
-syn match diffRemoved "^-.*"
-syn match diffAdded "^+.*"
-
-syn match diffSeparator "^---$"
-syn match diffSubname " @@..*"ms=s+3 contained
-syn match diffLine "^@.*" contains=diffSubname
-
-syn match diffFile "^diff .*"
-syn match diffNewFile "^+++ .*"
-syn match diffOldFile "^--- .*"
-
-hi def link diffOldFile diffFile
-hi def link diffNewFile diffFile
-
-hi def link diffFile Type
-hi def link diffRemoved Special
-hi def link diffAdded Identifier
-hi def link diffLine Statement
-hi def link diffSubname PreProc
-
-syntax match gitDiffStatLine /^ .\{-}\zs[+-]\+$/ contains=gitDiffStatAdd,gitDiffStatDelete
-syntax match gitDiffStatAdd /+/ contained
-syntax match gitDiffStatDelete /-/ contained
-
-hi def link gitDiffStatAdd diffAdded
-hi def link gitDiffStatDelete diffRemoved
+++ /dev/null
-syntax region nmSearch start=/^/ end=/$/ oneline contains=nmSearchDate
-syntax match nmSearchDate /^.\{-13}/ contained nextgroup=nmSearchNum
-syntax match nmSearchNum /.\{-4}/ contained nextgroup=nmSearchFrom
-syntax match nmSearchFrom /.\{-21}/ contained nextgroup=nmSearchSubject
-syntax match nmSearchSubject /.\{0,}\(([^()]\+)$\)\@=/ contained nextgroup=nmSearchTags
-syntax match nmSearchTags /.\+$/ contained
-
-highlight link nmSearchDate Statement
-highlight link nmSearchNum Type
-highlight link nmSearchFrom Include
-highlight link nmSearchSubject Normal
-highlight link nmSearchTags String
+++ /dev/null
-" notmuch show mode syntax file
-
-syntax cluster nmShowMsgDesc contains=nmShowMsgDescWho,nmShowMsgDescDate,nmShowMsgDescTags
-syntax match nmShowMsgDescWho /[^)]\+)/ contained
-syntax match nmShowMsgDescDate / ([^)]\+[0-9]) / contained
-syntax match nmShowMsgDescTags /([^)]\+)$/ contained
-
-syntax cluster nmShowMsgHead contains=nmShowMsgHeadKey,nmShowMsgHeadVal
-syntax match nmShowMsgHeadKey /^[^:]\+: / contained
-syntax match nmShowMsgHeadVal /^\([^:]\+: \)\@<=.*/ contained
-
-syntax cluster nmShowMsgBody contains=@nmShowMsgBodyMail,@nmShowMsgBodyGit
-syntax include @nmShowMsgBodyMail syntax/mail.vim
-
-silent! syntax include @nmShowMsgBodyGit syntax/notmuch-git-diff.vim
-
-highlight nmShowMsgDescWho term=reverse cterm=reverse gui=reverse
-highlight link nmShowMsgDescDate Type
-highlight link nmShowMsgDescTags String
-
-highlight link nmShowMsgHeadKey Macro
-"highlight link nmShowMsgHeadVal NONE
-
-highlight Folded term=reverse ctermfg=LightGrey ctermbg=Black guifg=LightGray guibg=Black