Add new notmuch vim plugin

The old one was not properly maintained and is now deprecated. The new
one has much better support.

Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
This commit is contained in:
Felipe Contreras 2013-06-02 19:31:59 -05:00
parent dbc3a247ca
commit 138c6aa098
8 changed files with 993 additions and 0 deletions

14
vim/Makefile Normal file
View file

@ -0,0 +1,14 @@
prefix = $(HOME)/.vim
INSTALL = install -v -D -m644
D = $(DESTDIR)
all:
@echo "Nothing to build"
install:
$(INSTALL) $(CURDIR)/notmuch.vim $(D)$(prefix)/plugin/notmuch.vim
@$(foreach file,$(wildcard syntax/*), \
$(INSTALL) $(CURDIR)/$(file) $(D)$(prefix)/$(file);)
.PHONY: all install

62
vim/README Normal file
View file

@ -0,0 +1,62 @@
== notmuch vim ruby ==
This is a vim plug-in that provides a fully usable mail client interface,
utilizing the notmuch framework, through it's ruby bindings.
== install ==
Simply run 'make install'. However, check that you have the depencies below.
=== vim +ruby ===
Make sure your vim version has ruby support: check for +ruby in 'vim --version'
features.
=== ruby bindings ===
Check if you are able to run the following command cleanly:
% ruby -e "require 'notmuch'"
If you don't see any errors, it means it's working and you can go to the next
section.
If it's not, you would need to compile them. Go to the 'bindings/ruby'
directory in the notmuch source tree.
=== mail gem ===
Since libnotmuch library concentrates on things other than handling mail, we
need a library to do that, and for Ruby the best library for that is called
'mail'. The easiest way to install it is with ruby's gem. In most distro's the
package is called 'rubygems'.
Once you have gem, run:
% gem install mail
In some systems gems are installed on a per-user basis by default, so make sure
you are running as the same user as the one that installed them.
This gem is not mandatory, but it's extremely recommended.
== Running ==
Simple:
% gvim -c ':NotMuchR'
Enjoy ;)
== More stuff ==
As an example to configure a key mapping to add the tag 'to-do' and archive,
this is what I use:
let g:notmuch_rb_custom_search_maps = {
\ 't': 'search_tag("+to-do -inbox")',
\ }
let g:notmuch_rb_custom_show_maps = {
\ 't': 'show_tag("+to-do -inbox")',
\ }

836
vim/notmuch.vim Normal file
View file

@ -0,0 +1,836 @@
if exists("g:loaded_notmuch_rb")
finish
endif
if !has("ruby") || version < 700
finish
endif
let g:loaded_notmuch_rb = "yep"
let g:notmuch_rb_folders_maps = {
\ '<Enter>': 'folders_show_search()',
\ 's': 'folders_search_prompt()',
\ '=': 'folders_refresh()',
\ }
let g:notmuch_rb_search_maps = {
\ 'q': 'kill_this_buffer()',
\ '<Enter>': 'search_show_thread(1)',
\ '<Space>': 'search_show_thread(2)',
\ 'A': 'search_tag("-inbox -unread")',
\ 'I': 'search_tag("-unread")',
\ 't': 'search_tag("")',
\ 's': 'search_search_prompt()',
\ '=': 'search_refresh()',
\ '?': 'search_info()',
\ }
let g:notmuch_rb_show_maps = {
\ 'q': 'kill_this_buffer()',
\ 'A': 'show_tag("-inbox -unread")',
\ 'I': 'show_tag("-unread")',
\ 't': 'show_tag("")',
\ 'o': 'show_open_msg()',
\ 'e': 'show_extract_msg()',
\ 's': 'show_save_msg()',
\ 'r': 'show_reply()',
\ '?': 'show_info()',
\ '<Tab>': 'show_next_msg()',
\ }
let g:notmuch_rb_compose_maps = {
\ ',s': 'compose_send()',
\ ',q': 'compose_quit()',
\ }
let s:notmuch_rb_folders_default = [
\ [ 'new', 'tag:inbox and tag:unread' ],
\ [ 'inbox', 'tag:inbox' ],
\ [ 'unread', 'tag:unread' ],
\ ]
let s:notmuch_rb_date_format_default = '%d.%m.%y'
let s:notmuch_rb_datetime_format_default = '%d.%m.%y %H:%M:%S'
let s:notmuch_rb_reader_default = 'xfce4-terminal -e "mutt -f %s"'
let s:notmuch_rb_sendmail_default = 'sendmail'
let s:notmuch_rb_folders_count_threads_default = 0
if !exists('g:notmuch_rb_date_format')
let g:notmuch_rb_date_format = s:notmuch_rb_date_format_default
endif
if !exists('g:notmuch_rb_datetime_format')
let g:notmuch_rb_datetime_format = s:notmuch_rb_datetime_format_default
endif
if !exists('g:notmuch_rb_reader')
let g:notmuch_rb_reader = s:notmuch_rb_reader_default
endif
if !exists('g:notmuch_rb_sendmail')
let g:notmuch_rb_sendmail = s:notmuch_rb_sendmail_default
endif
if !exists('g:notmuch_rb_folders_count_threads')
let g:notmuch_rb_folders_count_threads = s:notmuch_rb_folders_count_threads_default
endif
function! s:new_file_buffer(type, fname)
exec printf('edit %s', a:fname)
execute printf('set filetype=notmuch-%s', a:type)
execute printf('set syntax=notmuch-%s', a:type)
ruby $buf_queue.push($curbuf.number)
endfunction
function! s:compose_unload()
if b:compose_done
return
endif
if input('[s]end/[q]uit? ') =~ '^s'
call s:compose_send()
endif
endfunction
"" actions
function! s:compose_quit()
let b:compose_done = 1
call s:kill_this_buffer()
endfunction
function! s:compose_send()
let b:compose_done = 1
let fname = expand('%')
" remove headers
0,4d
write
let cmdtxt = g:notmuch_sendmail . ' -t -f ' . s:reply_from . ' < ' . fname
let out = system(cmdtxt)
let err = v:shell_error
if err
undo
write
echohl Error
echo 'Eeek! unable to send mail'
echo out
echohl None
return
endif
call delete(fname)
echo 'Mail sent successfully.'
call s:kill_this_buffer()
endfunction
function! s:show_next_msg()
ruby << EOF
r, c = $curwin.cursor
n = $curbuf.line_number
i = $messages.index { |m| n >= m.start && n <= m.end }
m = $messages[i + 1]
if m
r = m.body_start + 1
VIM::command("normal #{m.start}zt")
$curwin.cursor = r, c
end
EOF
endfunction
function! s:show_reply()
ruby open_reply get_message.mail
let b:compose_done = 0
call s:set_map(g:notmuch_rb_compose_maps)
autocmd BufUnload <buffer> call s:compose_unload()
startinsert!
endfunction
function! s:show_info()
ruby vim_puts get_message.inspect
endfunction
function! s:show_extract_msg()
ruby << EOF
m = get_message
m.mail.attachments.each do |a|
File.open(a.filename, 'w') do |f|
f.write a.body.decoded
print "Extracted '#{a.filename}'"
end
end
EOF
endfunction
function! s:show_open_msg()
ruby << EOF
m = get_message
mbox = File.expand_path('~/.notmuch/vim_mbox')
cmd = VIM::evaluate('g:notmuch_rb_reader') % mbox
system "notmuch show --format=mbox id:#{m.message_id} > #{mbox} && #{cmd}"
EOF
endfunction
function! s:show_save_msg()
let file = input('File name: ')
ruby << EOF
file = VIM::evaluate('file')
m = get_message
system "notmuch show --format=mbox id:#{m.message_id} > #{file}"
EOF
endfunction
function! s:show_tag(intags)
if empty(a:intags)
let tags = input('tags: ')
else
let tags = a:intags
endif
ruby do_tag(get_cur_view, VIM::evaluate('l:tags'))
call s:show_next_thread()
endfunction
function! s:search_search_prompt()
let text = input('Search: ')
setlocal modifiable
ruby << EOF
$cur_search = VIM::evaluate('text')
search_render($cur_search)
EOF
setlocal nomodifiable
endfunction
function! s:search_info()
ruby vim_puts get_thread_id
endfunction
function! s:search_refresh()
setlocal modifiable
ruby search_render($cur_search)
setlocal nomodifiable
endfunction
function! s:search_tag(intags)
if empty(a:intags)
let tags = input('tags: ')
else
let tags = a:intags
endif
ruby do_tag(get_thread_id, VIM::evaluate('l:tags'))
norm j
call s:search_refresh()
endfunction
function! s:folders_search_prompt()
let text = input('Search: ')
call s:search(text)
endfunction
function! s:folders_refresh()
setlocal modifiable
ruby folders_render()
setlocal nomodifiable
endfunction
"" basic
function! s:show_cursor_moved()
ruby << EOF
if $render.is_ready?
VIM::command('setlocal modifiable')
$render.do_next
VIM::command('setlocal nomodifiable')
end
EOF
endfunction
function! s:show_next_thread()
call s:kill_this_buffer()
if line('.') != line('$')
norm j
call s:search_show_thread(0)
else
echo 'No more messages.'
endif
endfunction
function! s:kill_this_buffer()
bdelete!
ruby << EOF
$buf_queue.pop
b = $buf_queue.last
VIM::command("buffer #{b}") if b
EOF
endfunction
function! s:set_map(maps)
nmapclear <buffer>
for [key, code] in items(a:maps)
let cmd = printf(":call <SID>%s<CR>", code)
exec printf('nnoremap <buffer> %s %s', key, cmd)
endfor
endfunction
function! s:new_buffer(type)
enew
setlocal buftype=nofile bufhidden=hide
keepjumps 0d
execute printf('set filetype=notmuch-%s', a:type)
execute printf('set syntax=notmuch-%s', a:type)
ruby $buf_queue.push($curbuf.number)
endfunction
function! s:set_menu_buffer()
setlocal nomodifiable
setlocal cursorline
setlocal nowrap
endfunction
"" main
function! s:show(thread_id)
call s:new_buffer('show')
setlocal modifiable
ruby << EOF
thread_id = VIM::evaluate('a:thread_id')
$cur_thread = thread_id
$messages.clear
$curbuf.render do |b|
do_read do |db|
q = db.query(get_cur_view)
q.sort = 0
msgs = q.search_messages
msgs.each do |msg|
m = Mail.read(msg.filename)
part = m.find_first_text
nm_m = Message.new(msg, m)
$messages << nm_m
date_fmt = VIM::evaluate('g:notmuch_rb_datetime_format')
date = Time.at(msg.date).strftime(date_fmt)
nm_m.start = b.count
b << "%s %s (%s)" % [msg['from'], date, msg.tags]
b << "Subject: %s" % [msg['subject']]
b << "To: %s" % m['to']
b << "Cc: %s" % m['cc']
b << "Date: %s" % m['date']
nm_m.body_start = b.count
b << "--- %s ---" % part.mime_type
part.convert.each_line do |l|
b << l.chomp
end
b << ""
nm_m.end = b.count
end
b.delete(b.count)
end
end
$messages.each_with_index do |msg, i|
VIM::command("syntax region nmShowMsg#{i}Desc start='\\%%%il' end='\\%%%il' contains=@nmShowMsgDesc" % [msg.start, msg.start + 1])
VIM::command("syntax region nmShowMsg#{i}Head start='\\%%%il' end='\\%%%il' contains=@nmShowMsgHead" % [msg.start + 1, msg.body_start])
VIM::command("syntax region nmShowMsg#{i}Body start='\\%%%il' end='\\%%%dl' contains=@nmShowMsgBody" % [msg.body_start, msg.end])
end
EOF
setlocal nomodifiable
call s:set_map(g:notmuch_rb_show_maps)
endfunction
function! s:search_show_thread(mode)
ruby << EOF
mode = VIM::evaluate('a:mode')
id = get_thread_id
case mode
when 0;
when 1; $cur_filter = nil
when 2; $cur_filter = $cur_search
end
VIM::command("call s:show('#{id}')")
EOF
endfunction
function! s:search(search)
call s:new_buffer('search')
ruby << EOF
$cur_search = VIM::evaluate('a:search')
search_render($cur_search)
EOF
call s:set_menu_buffer()
call s:set_map(g:notmuch_rb_search_maps)
autocmd CursorMoved <buffer> call s:show_cursor_moved()
endfunction
function! s:folders_show_search()
ruby << EOF
n = $curbuf.line_number
s = $searches[n - 1]
VIM::command("call s:search('#{s}')")
EOF
endfunction
function! s:folders()
call s:new_buffer('folders')
ruby folders_render()
call s:set_menu_buffer()
call s:set_map(g:notmuch_rb_folders_maps)
endfunction
"" root
function! s:set_defaults()
if exists('g:notmuch_rb_custom_search_maps')
call extend(g:notmuch_rb_search_maps, g:notmuch_rb_custom_search_maps)
endif
if exists('g:notmuch_rb_custom_show_maps')
call extend(g:notmuch_rb_show_maps, g:notmuch_rb_custom_show_maps)
endif
" TODO for now lets check the old folders too
if !exists('g:notmuch_rb_folders')
if exists('g:notmuch_folders')
let g:notmuch_rb_folders = g:notmuch_folders
else
let g:notmuch_rb_folders = s:notmuch_rb_folders_default
endif
endif
endfunction
function! s:NotMuchR()
call s:set_defaults()
ruby << EOF
require 'notmuch'
require 'rubygems'
require 'tempfile'
begin
require 'mail'
rescue LoadError
end
$db_name = nil
$email_address = nil
$searches = []
$buf_queue = []
$threads = []
$messages = []
$config = {}
$mail_installed = defined?(Mail)
def get_config
group = nil
config = ENV['NOTMUCH_CONFIG'] || '~/.notmuch-config'
File.open(File.expand_path(config)).each do |l|
l.chomp!
case l
when /^\[(.*)\]$/
group = $1
when ''
when /^(.*)=(.*)$/
key = "%s.%s" % [group, $1]
value = $2
$config[key] = value
end
end
$db_name = $config['database.path']
$email_address = "%s <%s>" % [$config['user.name'], $config['user.primary_email']]
end
def vim_puts(s)
VIM::command("echo '#{s.to_s}'")
end
def vim_p(s)
VIM::command("echo '#{s.inspect}'")
end
def author_filter(a)
# TODO email format, aliases
a.strip!
a.gsub!(/[\.@].*/, '')
a.gsub!(/^ext /, '')
a.gsub!(/ \(.*\)/, '')
a
end
def get_thread_id
n = $curbuf.line_number - 1
return "thread:%s" % $threads[n]
end
def get_message
n = $curbuf.line_number
return $messages.find { |m| n >= m.start && n <= m.end }
end
def get_cur_view
if $cur_filter
return "#{$cur_thread} and (#{$cur_filter})"
else
return $cur_thread
end
end
def do_write
db = Notmuch::Database.new($db_name, :mode => Notmuch::MODE_READ_WRITE)
begin
yield db
ensure
db.close
end
end
def do_read
db = Notmuch::Database.new($db_name)
begin
yield db
ensure
db.close
end
end
def open_reply(orig)
help_lines = [
'Notmuch-Help: Type in your message here; to help you use these bindings:',
'Notmuch-Help: ,s - send the message (Notmuch-Help lines will be removed)',
'Notmuch-Help: ,q - abort the message',
]
reply = orig.reply do |m|
# fix headers
if not m[:reply_to]
m.to = [orig[:from].to_s, orig[:to].to_s]
end
m.cc = orig[:cc]
m.from = $email_address
m.charset = 'utf-8'
m.content_transfer_encoding = '7bit'
end
dir = File.expand_path('~/.notmuch/compose')
FileUtils.mkdir_p(dir)
Tempfile.open(['nm-', '.mail'], dir) do |f|
lines = []
lines += help_lines
lines << ''
body_lines = []
if $mail_installed
addr = Mail::Address.new(orig[:from].value)
name = addr.name
name = addr.local + "@" if name.nil? && !addr.local.nil?
else
name = orig[:from]
end
name = "somebody" if name.nil?
body_lines << "%s wrote:" % name
part = orig.find_first_text
part.convert.each_line do |l|
body_lines << "> %s" % l.chomp
end
body_lines << ""
body_lines << ""
body_lines << ""
reply.body = body_lines.join("\n")
lines += reply.to_s.lines.map { |e| e.chomp }
lines << ""
old_count = lines.count - 1
f.puts(lines)
sig_file = File.expand_path('~/.signature')
if File.exists?(sig_file)
f.puts("-- ")
f.write(File.read(sig_file))
end
f.flush
VIM::command("let s:reply_from='%s'" % reply.from.first.to_s)
VIM::command("call s:new_file_buffer('compose', '#{f.path}')")
VIM::command("call cursor(#{old_count}, 0)")
end
end
def folders_render()
$curbuf.render do |b|
folders = VIM::evaluate('g:notmuch_rb_folders')
count_threads = VIM::evaluate('g:notmuch_rb_folders_count_threads')
$searches.clear
do_read do |db|
folders.each do |name, search|
q = db.query(search)
$searches << search
count = count_threads ? q.search_threads.count : q.search_messages.count
b << "%9d %-20s (%s)" % [count, name, search]
end
end
end
end
def search_render(search)
date_fmt = VIM::evaluate('g:notmuch_rb_date_format')
db = Notmuch::Database.new($db_name)
q = db.query(search)
$threads.clear
t = q.search_threads
$render = $curbuf.render_staged(t) do |b, items|
items.each do |e|
authors = e.authors.to_utf8.split(/[,|]/).map { |a| author_filter(a) }.join(",")
date = Time.at(e.newest_date).strftime(date_fmt)
if $mail_installed
subject = Mail::Field.new("Subject: " + e.subject).to_s
else
subject = e.subject.force_encoding('utf-8')
end
b << "%-12s %3s %-20.20s | %s (%s)" % [date, e.matched_messages, authors, subject, e.tags]
$threads << e.thread_id
end
end
end
def do_tag(filter, tags)
do_write do |db|
q = db.query(filter)
q.search_messages.each do |e|
e.freeze
tags.split.each do |t|
case t
when /^-(.*)/
e.remove_tag($1)
when /^\+(.*)/
e.add_tag($1)
when /^([^\+^-].*)/
e.add_tag($1)
end
end
e.thaw
e.tags_to_maildir_flags
end
end
end
class Message
attr_accessor :start, :body_start, :end
attr_reader :message_id, :filename, :mail
def initialize(msg, mail)
@message_id = msg.message_id
@filename = msg.filename
@mail = mail
@start = 0
@end = 0
mail.import_headers(msg) if not $mail_installed
end
def to_s
"id:%s" % @message_id
end
def inspect
"id:%s, file:%s" % [@message_id, @filename]
end
end
class StagedRender
def initialize(buffer, enumerable, block)
@b = buffer
@enumerable = enumerable
@block = block
@last_render = 0
@b.render { do_next }
end
def is_ready?
@last_render - @b.line_number <= $curwin.height
end
def do_next
items = @enumerable.take($curwin.height * 2)
return if items.empty?
@block.call @b, items
@last_render = @b.count
end
end
class VIM::Buffer
def <<(a)
append(count(), a)
end
def render_staged(enumerable, &block)
StagedRender.new(self, enumerable, block)
end
def render
old_count = count
yield self
(1..old_count).each do
delete(1)
end
end
end
class Notmuch::Tags
def to_s
to_a.join(" ")
end
end
class Notmuch::Message
def to_s
"id:%s" % message_id
end
end
# workaround for bug in vim's ruby
class Object
def flush
end
end
module SimpleMessage
class Header < Array
def self.parse(string)
return nil if string.empty?
return Header.new(string.split(/,\s+/))
end
def to_s
self.join(', ')
end
end
def initialize(string = nil)
@raw_source = string
@body = nil
@headers = {}
return if not string
if string =~ /(.*?(\r\n|\n))\2/m
head, body = $1, $' || '', $2
else
head, body = string, ''
end
@body = body
end
def [](name)
@headers[name.to_sym]
end
def []=(name, value)
@headers[name.to_sym] = value
end
def format_header(value)
value.to_s.tr('_', '-').gsub(/(\w+)/) { $1.capitalize }
end
def to_s
buffer = ''
@headers.each do |key, value|
buffer << "%s: %s\r\n" %
[format_header(key), value]
end
buffer << "\r\n"
buffer << @body
buffer
end
def body=(value)
@body = value
end
def from
@headers[:from]
end
def decoded
@body
end
def mime_type
'text/plain'
end
def multipart?
false
end
def reply
r = Mail::Message.new
r[:from] = self[:to]
r[:to] = self[:from]
r[:cc] = self[:cc]
r[:in_reply_to] = self[:message_id]
r[:references] = self[:references]
r
end
HEADERS = [ :from, :to, :cc, :references, :in_reply_to, :reply_to, :message_id ]
def import_headers(m)
HEADERS.each do |e|
dashed = format_header(e)
@headers[e] = Header.parse(m[dashed])
end
end
end
module Mail
if not $mail_installed
puts "WARNING: Install the 'mail' gem, without it support is limited"
def self.read(filename)
Message.new(File.open(filename, 'rb') { |f| f.read })
end
class Message
include SimpleMessage
end
end
class Message
def find_first_text
return self if not multipart?
return text_part || html_part
end
def convert
if mime_type != "text/html"
text = decoded
else
IO.popen("elinks --dump", "w+") do |pipe|
pipe.write(decode_body)
pipe.close_write
text = pipe.read
end
end
text
end
end
end
class String
def to_utf8
RUBY_VERSION >= "1.9" ? force_encoding('utf-8') : self
end
end
get_config
EOF
call s:folders()
endfunction
command NotMuchR :call s:NotMuchR()
" vim: set noexpandtab:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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