From 138c6aa098fe4236dc334cc55ac4fc414ddb298c Mon Sep 17 00:00:00 2001 From: Felipe Contreras Date: Sun, 2 Jun 2013 19:31:59 -0500 Subject: [PATCH] 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 --- vim/Makefile | 14 + vim/README | 62 +++ vim/notmuch.vim | 836 ++++++++++++++++++++++++++++++++ vim/syntax/notmuch-compose.vim | 7 + vim/syntax/notmuch-folders.vim | 12 + vim/syntax/notmuch-git-diff.vim | 26 + vim/syntax/notmuch-search.vim | 12 + vim/syntax/notmuch-show.vim | 24 + 8 files changed, 993 insertions(+) create mode 100644 vim/Makefile create mode 100644 vim/README create mode 100644 vim/notmuch.vim create mode 100644 vim/syntax/notmuch-compose.vim create mode 100644 vim/syntax/notmuch-folders.vim create mode 100644 vim/syntax/notmuch-git-diff.vim create mode 100644 vim/syntax/notmuch-search.vim create mode 100644 vim/syntax/notmuch-show.vim diff --git a/vim/Makefile b/vim/Makefile new file mode 100644 index 00000000..bb3ec608 --- /dev/null +++ b/vim/Makefile @@ -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 diff --git a/vim/README b/vim/README new file mode 100644 index 00000000..a6619005 --- /dev/null +++ b/vim/README @@ -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")', + \ } diff --git a/vim/notmuch.vim b/vim/notmuch.vim new file mode 100644 index 00000000..e0fc1400 --- /dev/null +++ b/vim/notmuch.vim @@ -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 = { + \ '': 'folders_show_search()', + \ 's': 'folders_search_prompt()', + \ '=': 'folders_refresh()', + \ } + +let g:notmuch_rb_search_maps = { + \ 'q': 'kill_this_buffer()', + \ '': 'search_show_thread(1)', + \ '': '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()', + \ '': '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 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 + for [key, code] in items(a:maps) + let cmd = printf(":call %s", code) + exec printf('nnoremap %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 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: diff --git a/vim/syntax/notmuch-compose.vim b/vim/syntax/notmuch-compose.vim new file mode 100644 index 00000000..19adb756 --- /dev/null +++ b/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/vim/syntax/notmuch-folders.vim b/vim/syntax/notmuch-folders.vim new file mode 100644 index 00000000..9477f86f --- /dev/null +++ b/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/vim/syntax/notmuch-git-diff.vim b/vim/syntax/notmuch-git-diff.vim new file mode 100644 index 00000000..6f15fdc7 --- /dev/null +++ b/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/vim/syntax/notmuch-search.vim b/vim/syntax/notmuch-search.vim new file mode 100644 index 00000000..f458d778 --- /dev/null +++ b/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/vim/syntax/notmuch-show.vim b/vim/syntax/notmuch-show.vim new file mode 100644 index 00000000..c3a98b77 --- /dev/null +++ b/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