mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-11-21 18:38:08 +01:00
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:
parent
dbc3a247ca
commit
138c6aa098
8 changed files with 993 additions and 0 deletions
14
vim/Makefile
Normal file
14
vim/Makefile
Normal 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
62
vim/README
Normal 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
836
vim/notmuch.vim
Normal 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:
|
7
vim/syntax/notmuch-compose.vim
Normal file
7
vim/syntax/notmuch-compose.vim
Normal 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
|
12
vim/syntax/notmuch-folders.vim
Normal file
12
vim/syntax/notmuch-folders.vim
Normal 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
|
||||
|
26
vim/syntax/notmuch-git-diff.vim
Normal file
26
vim/syntax/notmuch-git-diff.vim
Normal 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
|
12
vim/syntax/notmuch-search.vim
Normal file
12
vim/syntax/notmuch-search.vim
Normal 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
|
24
vim/syntax/notmuch-show.vim
Normal file
24
vim/syntax/notmuch-show.vim
Normal 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
|
Loading…
Reference in a new issue