aboutsummaryrefslogtreecommitdiffhomepage
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rwxr-xr-xcontrib/nmbug/nmbug648
-rwxr-xr-xcontrib/nmbug/nmbug-status176
-rw-r--r--contrib/nmbug/status-config.json65
-rwxr-xr-xcontrib/notmuch-mutt/notmuch-mutt24
-rw-r--r--contrib/notmuch-mutt/notmuch-mutt.rc16
-rw-r--r--contrib/notmuch-vim/Makefile23
-rw-r--r--contrib/notmuch-vim/README86
-rw-r--r--contrib/notmuch-vim/notmuch.yaml8
-rw-r--r--contrib/notmuch-vim/plugin/notmuch.vim1432
-rw-r--r--contrib/notmuch-vim/syntax/notmuch-compose.vim7
-rw-r--r--contrib/notmuch-vim/syntax/notmuch-folders.vim12
-rw-r--r--contrib/notmuch-vim/syntax/notmuch-git-diff.vim26
-rw-r--r--contrib/notmuch-vim/syntax/notmuch-search.vim12
-rw-r--r--contrib/notmuch-vim/syntax/notmuch-show.vim24
14 files changed, 1663 insertions, 896 deletions
diff --git a/contrib/nmbug/nmbug b/contrib/nmbug/nmbug
deleted file mode 100755
index f003ef9e..00000000
--- a/contrib/nmbug/nmbug
+++ /dev/null
@@ -1,648 +0,0 @@
-#!/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.
diff --git a/contrib/nmbug/nmbug-status b/contrib/nmbug/nmbug-status
deleted file mode 100755
index d08ca08d..00000000
--- a/contrib/nmbug/nmbug-status
+++ /dev/null
@@ -1,176 +0,0 @@
-#!/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>'
diff --git a/contrib/nmbug/status-config.json b/contrib/nmbug/status-config.json
deleted file mode 100644
index 6b4934fa..00000000
--- a/contrib/nmbug/status-config.json
+++ /dev/null
@@ -1,65 +0,0 @@
-{
- "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"
- }
- ]
-}
diff --git a/contrib/notmuch-mutt/notmuch-mutt b/contrib/notmuch-mutt/notmuch-mutt
index d14709df..00c5ef82 100755
--- a/contrib/notmuch-mutt/notmuch-mutt
+++ b/contrib/notmuch-mutt/notmuch-mutt
@@ -121,7 +121,8 @@ sub prompt($$) {
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;
}
@@ -142,6 +143,10 @@ sub thread_action($$@) {
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);
@@ -151,6 +156,7 @@ sub thread_action($$@) {
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(' ', @_))
@@ -264,13 +270,23 @@ the following in your Mutt configuration (usually one of: F<~/.muttrc>,
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
diff --git a/contrib/notmuch-mutt/notmuch-mutt.rc b/contrib/notmuch-mutt/notmuch-mutt.rc
index ddc4b480..6b299dc9 100644
--- a/contrib/notmuch-mutt/notmuch-mutt.rc
+++ b/contrib/notmuch-mutt/notmuch-mutt.rc
@@ -1,9 +1,19 @@
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"
diff --git a/contrib/notmuch-vim/Makefile b/contrib/notmuch-vim/Makefile
new file mode 100644
index 00000000..f17bebfe
--- /dev/null
+++ b/contrib/notmuch-vim/Makefile
@@ -0,0 +1,23 @@
+.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
diff --git a/contrib/notmuch-vim/README b/contrib/notmuch-vim/README
new file mode 100644
index 00000000..53f1c4e1
--- /dev/null
+++ b/contrib/notmuch-vim/README
@@ -0,0 +1,86 @@
+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
+
diff --git a/contrib/notmuch-vim/notmuch.yaml b/contrib/notmuch-vim/notmuch.yaml
new file mode 100644
index 00000000..3d8422c8
--- /dev/null
+++ b/contrib/notmuch-vim/notmuch.yaml
@@ -0,0 +1,8 @@
+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
diff --git a/contrib/notmuch-vim/plugin/notmuch.vim b/contrib/notmuch-vim/plugin/notmuch.vim
new file mode 100644
index 00000000..8f27fb92
--- /dev/null
+++ b/contrib/notmuch-vim/plugin/notmuch.vim
@@ -0,0 +1,1432 @@
+" 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': ' message{' ,
+ \ 'g:notmuch_show_message_end_regexp': ' message}' ,
+ \ 'g:notmuch_show_header_begin_regexp': ' header{' ,
+ \ 'g:notmuch_show_header_end_regexp': ' header}' ,
+ \ 'g:notmuch_show_body_begin_regexp': ' body{' ,
+ \ 'g:notmuch_show_body_end_regexp': ' body}' ,
+ \ 'g:notmuch_show_attachment_begin_regexp': ' attachment{' ,
+ \ 'g:notmuch_show_attachment_end_regexp': ' attachment}' ,
+ \ 'g:notmuch_show_part_begin_regexp': ' part{' ,
+ \ 'g:notmuch_show_part_end_regexp': ' part}' ,
+ \ 'g:notmuch_show_marker_regexp': ' \\(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 :
diff --git a/contrib/notmuch-vim/syntax/notmuch-compose.vim b/contrib/notmuch-vim/syntax/notmuch-compose.vim
new file mode 100644
index 00000000..19adb756
--- /dev/null
+++ b/contrib/notmuch-vim/syntax/notmuch-compose.vim
@@ -0,0 +1,7 @@
+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
diff --git a/contrib/notmuch-vim/syntax/notmuch-folders.vim b/contrib/notmuch-vim/syntax/notmuch-folders.vim
new file mode 100644
index 00000000..9477f86f
--- /dev/null
+++ b/contrib/notmuch-vim/syntax/notmuch-folders.vim
@@ -0,0 +1,12 @@
+" 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
+
diff --git a/contrib/notmuch-vim/syntax/notmuch-git-diff.vim b/contrib/notmuch-vim/syntax/notmuch-git-diff.vim
new file mode 100644
index 00000000..6f15fdc7
--- /dev/null
+++ b/contrib/notmuch-vim/syntax/notmuch-git-diff.vim
@@ -0,0 +1,26 @@
+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
diff --git a/contrib/notmuch-vim/syntax/notmuch-search.vim b/contrib/notmuch-vim/syntax/notmuch-search.vim
new file mode 100644
index 00000000..f458d778
--- /dev/null
+++ b/contrib/notmuch-vim/syntax/notmuch-search.vim
@@ -0,0 +1,12 @@
+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
diff --git a/contrib/notmuch-vim/syntax/notmuch-show.vim b/contrib/notmuch-vim/syntax/notmuch-show.vim
new file mode 100644
index 00000000..c3a98b77
--- /dev/null
+++ b/contrib/notmuch-vim/syntax/notmuch-show.vim
@@ -0,0 +1,24 @@
+" 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