1 if exists("g:loaded_notmuch_rb")
5 if !has("ruby") || version < 700
9 let g:loaded_notmuch_rb = "yep"
11 let g:notmuch_rb_folders_maps = {
12 \ '<Enter>': 'folders_show_search()',
13 \ 's': 'folders_search_prompt()',
14 \ '=': 'folders_refresh()',
17 let g:notmuch_rb_search_maps = {
18 \ 'q': 'kill_this_buffer()',
19 \ '<Enter>': 'search_show_thread(1)',
20 \ '<Space>': 'search_show_thread(2)',
21 \ 'A': 'search_tag("-inbox -unread")',
22 \ 'I': 'search_tag("-unread")',
23 \ 't': 'search_tag("")',
24 \ 's': 'search_search_prompt()',
25 \ '=': 'search_refresh()',
26 \ '?': 'search_info()',
29 let g:notmuch_rb_show_maps = {
30 \ 'q': 'kill_this_buffer()',
31 \ 'A': 'show_tag("-inbox -unread")',
32 \ 'I': 'show_tag("-unread")',
33 \ 't': 'show_tag("")',
34 \ 'o': 'show_open_msg()',
35 \ 'e': 'show_extract_msg()',
36 \ 's': 'show_save_msg()',
37 \ 'r': 'show_reply()',
39 \ '<Tab>': 'show_next_msg()',
42 let g:notmuch_rb_compose_maps = {
43 \ ',s': 'compose_send()',
44 \ ',q': 'compose_quit()',
47 let s:notmuch_rb_folders_default = [
48 \ [ 'new', 'tag:inbox and tag:unread' ],
49 \ [ 'inbox', 'tag:inbox' ],
50 \ [ 'unread', 'tag:unread' ],
53 let s:notmuch_rb_date_format_default = '%d.%m.%y'
54 let s:notmuch_rb_datetime_format_default = '%d.%m.%y %H:%M:%S'
55 let s:notmuch_rb_reader_default = 'xfce4-terminal -e "mutt -f %s"'
56 let s:notmuch_rb_sendmail_default = 'sendmail'
57 let s:notmuch_rb_folders_count_threads_default = 0
59 if !exists('g:notmuch_rb_date_format')
60 let g:notmuch_rb_date_format = s:notmuch_rb_date_format_default
63 if !exists('g:notmuch_rb_datetime_format')
64 let g:notmuch_rb_datetime_format = s:notmuch_rb_datetime_format_default
67 if !exists('g:notmuch_rb_reader')
68 let g:notmuch_rb_reader = s:notmuch_rb_reader_default
71 if !exists('g:notmuch_rb_sendmail')
72 let g:notmuch_rb_sendmail = s:notmuch_rb_sendmail_default
75 if !exists('g:notmuch_rb_folders_count_threads')
76 let g:notmuch_rb_folders_count_threads = s:notmuch_rb_folders_count_threads_default
79 function! s:new_file_buffer(type, fname)
80 exec printf('edit %s', a:fname)
81 execute printf('set filetype=notmuch-%s', a:type)
82 execute printf('set syntax=notmuch-%s', a:type)
83 ruby $buf_queue.push($curbuf.number)
86 function! s:compose_unload()
90 if input('[s]end/[q]uit? ') =~ '^s'
97 function! s:compose_quit()
98 let b:compose_done = 1
99 call s:kill_this_buffer()
102 function! s:compose_send()
103 let b:compose_done = 1
104 let fname = expand('%')
110 let cmdtxt = g:notmuch_sendmail . ' -t -f ' . s:reply_from . ' < ' . fname
111 let out = system(cmdtxt)
112 let err = v:shell_error
117 echo 'Eeek! unable to send mail'
123 echo 'Mail sent successfully.'
124 call s:kill_this_buffer()
127 function! s:show_next_msg()
129 r, c = $curwin.cursor
130 n = $curbuf.line_number
131 i = $messages.index { |m| n >= m.start && n <= m.end }
135 VIM::command("normal #{m.start}zt")
136 $curwin.cursor = r, c
141 function! s:show_reply()
142 ruby open_reply get_message.mail
143 let b:compose_done = 0
144 call s:set_map(g:notmuch_rb_compose_maps)
145 autocmd BufUnload <buffer> call s:compose_unload()
149 function! s:show_info()
150 ruby vim_puts get_message.inspect
153 function! s:show_extract_msg()
156 m.mail.attachments.each do |a|
157 File.open(a.filename, 'w') do |f|
158 f.write a.body.decoded
159 print "Extracted '#{a.filename}'"
165 function! s:show_open_msg()
168 mbox = File.expand_path('~/.notmuch/vim_mbox')
169 cmd = VIM::evaluate('g:notmuch_rb_reader') % mbox
170 system "notmuch show --format=mbox id:#{m.message_id} > #{mbox} && #{cmd}"
174 function! s:show_save_msg()
175 let file = input('File name: ')
177 file = VIM::evaluate('file')
179 system "notmuch show --format=mbox id:#{m.message_id} > #{file}"
183 function! s:show_tag(intags)
185 let tags = input('tags: ')
189 ruby do_tag(get_cur_view, VIM::evaluate('l:tags'))
190 call s:show_next_thread()
193 function! s:search_search_prompt()
194 let text = input('Search: ')
197 $cur_search = VIM::evaluate('text')
198 search_render($cur_search)
200 setlocal nomodifiable
203 function! s:search_info()
204 ruby vim_puts get_thread_id
207 function! s:search_refresh()
209 ruby search_render($cur_search)
210 setlocal nomodifiable
213 function! s:search_tag(intags)
215 let tags = input('tags: ')
219 ruby do_tag(get_thread_id, VIM::evaluate('l:tags'))
221 call s:search_refresh()
224 function! s:folders_search_prompt()
225 let text = input('Search: ')
229 function! s:folders_refresh()
231 ruby folders_render()
232 setlocal nomodifiable
237 function! s:show_cursor_moved()
240 VIM::command('setlocal modifiable')
242 VIM::command('setlocal nomodifiable')
247 function! s:show_next_thread()
248 call s:kill_this_buffer()
249 if line('.') != line('$')
251 call s:search_show_thread(0)
253 echo 'No more messages.'
257 function! s:kill_this_buffer()
262 VIM::command("buffer #{b}") if b
266 function! s:set_map(maps)
268 for [key, code] in items(a:maps)
269 let cmd = printf(":call <SID>%s<CR>", code)
270 exec printf('nnoremap <buffer> %s %s', key, cmd)
274 function! s:new_buffer(type)
276 setlocal buftype=nofile bufhidden=hide
278 execute printf('set filetype=notmuch-%s', a:type)
279 execute printf('set syntax=notmuch-%s', a:type)
280 ruby $buf_queue.push($curbuf.number)
283 function! s:set_menu_buffer()
284 setlocal nomodifiable
291 function! s:show(thread_id)
292 call s:new_buffer('show')
295 thread_id = VIM::evaluate('a:thread_id')
296 $cur_thread = thread_id
298 $curbuf.render do |b|
300 q = db.query(get_cur_view)
302 msgs = q.search_messages
304 m = Mail.read(msg.filename)
305 part = m.find_first_text
306 nm_m = Message.new(msg, m)
308 date_fmt = VIM::evaluate('g:notmuch_rb_datetime_format')
309 date = Time.at(msg.date).strftime(date_fmt)
311 b << "%s %s (%s)" % [msg['from'], date, msg.tags]
312 b << "Subject: %s" % [msg['subject']]
313 b << "To: %s" % m['to']
314 b << "Cc: %s" % m['cc']
315 b << "Date: %s" % m['date']
316 nm_m.body_start = b.count
317 b << "--- %s ---" % part.mime_type
318 part.convert.each_line do |l|
327 $messages.each_with_index do |msg, i|
328 VIM::command("syntax region nmShowMsg#{i}Desc start='\\%%%il' end='\\%%%il' contains=@nmShowMsgDesc" % [msg.start, msg.start + 1])
329 VIM::command("syntax region nmShowMsg#{i}Head start='\\%%%il' end='\\%%%il' contains=@nmShowMsgHead" % [msg.start + 1, msg.body_start])
330 VIM::command("syntax region nmShowMsg#{i}Body start='\\%%%il' end='\\%%%dl' contains=@nmShowMsgBody" % [msg.body_start, msg.end])
333 setlocal nomodifiable
334 call s:set_map(g:notmuch_rb_show_maps)
337 function! s:search_show_thread(mode)
339 mode = VIM::evaluate('a:mode')
343 when 1; $cur_filter = nil
344 when 2; $cur_filter = $cur_search
346 VIM::command("call s:show('#{id}')")
350 function! s:search(search)
351 call s:new_buffer('search')
353 $cur_search = VIM::evaluate('a:search')
354 search_render($cur_search)
356 call s:set_menu_buffer()
357 call s:set_map(g:notmuch_rb_search_maps)
358 autocmd CursorMoved <buffer> call s:show_cursor_moved()
361 function! s:folders_show_search()
363 n = $curbuf.line_number
365 VIM::command("call s:search('#{s}')")
369 function! s:folders()
370 call s:new_buffer('folders')
371 ruby folders_render()
372 call s:set_menu_buffer()
373 call s:set_map(g:notmuch_rb_folders_maps)
378 function! s:set_defaults()
379 if exists('g:notmuch_rb_custom_search_maps')
380 call extend(g:notmuch_rb_search_maps, g:notmuch_rb_custom_search_maps)
383 if exists('g:notmuch_rb_custom_show_maps')
384 call extend(g:notmuch_rb_show_maps, g:notmuch_rb_custom_show_maps)
387 " TODO for now lets check the old folders too
388 if !exists('g:notmuch_rb_folders')
389 if exists('g:notmuch_folders')
390 let g:notmuch_rb_folders = g:notmuch_folders
392 let g:notmuch_rb_folders = s:notmuch_rb_folders_default
397 function! s:NotMuch()
398 call s:set_defaults()
416 $mail_installed = defined?(Mail)
420 config = ENV['NOTMUCH_CONFIG'] || '~/.notmuch-config'
421 File.open(File.expand_path(config)).each do |l|
428 key = "%s.%s" % [group, $1]
434 $db_name = $config['database.path']
435 $email_address = "%s <%s>" % [$config['user.name'], $config['user.primary_email']]
439 VIM::command("echo '#{s.to_s}'")
443 VIM::command("echo '#{s.inspect}'")
447 # TODO email format, aliases
449 a.gsub!(/[\.@].*/, '')
451 a.gsub!(/ \(.*\)/, '')
456 n = $curbuf.line_number - 1
457 return "thread:%s" % $threads[n]
461 n = $curbuf.line_number
462 return $messages.find { |m| n >= m.start && n <= m.end }
467 return "#{$cur_thread} and (#{$cur_filter})"
474 db = Notmuch::Database.new($db_name, :mode => Notmuch::MODE_READ_WRITE)
483 db = Notmuch::Database.new($db_name)
493 'Notmuch-Help: Type in your message here; to help you use these bindings:',
494 'Notmuch-Help: ,s - send the message (Notmuch-Help lines will be removed)',
495 'Notmuch-Help: ,q - abort the message',
497 reply = orig.reply do |m|
500 m.to = [orig[:from].to_s, orig[:to].to_s]
503 m.from = $email_address
505 m.content_transfer_encoding = '7bit'
508 dir = File.expand_path('~/.notmuch/compose')
509 FileUtils.mkdir_p(dir)
510 Tempfile.open(['nm-', '.mail'], dir) do |f|
518 addr = Mail::Address.new(orig[:from].value)
520 name = addr.local + "@" if name.nil? && !addr.local.nil?
524 name = "somebody" if name.nil?
526 body_lines << "%s wrote:" % name
527 part = orig.find_first_text
528 part.convert.each_line do |l|
529 body_lines << "> %s" % l.chomp
535 reply.body = body_lines.join("\n")
537 lines += reply.to_s.lines.map { |e| e.chomp }
540 old_count = lines.count - 1
544 sig_file = File.expand_path('~/.signature')
545 if File.exists?(sig_file)
547 f.write(File.read(sig_file))
552 VIM::command("let s:reply_from='%s'" % reply.from.first.to_s)
553 VIM::command("call s:new_file_buffer('compose', '#{f.path}')")
554 VIM::command("call cursor(#{old_count}, 0)")
559 $curbuf.render do |b|
560 folders = VIM::evaluate('g:notmuch_rb_folders')
561 count_threads = VIM::evaluate('g:notmuch_rb_folders_count_threads')
564 folders.each do |name, search|
567 count = count_threads ? q.search_threads.count : q.search_messages.count
568 b << "%9d %-20s (%s)" % [count, name, search]
574 def search_render(search)
575 date_fmt = VIM::evaluate('g:notmuch_rb_date_format')
576 db = Notmuch::Database.new($db_name)
581 $render = $curbuf.render_staged(t) do |b, items|
583 authors = e.authors.to_utf8.split(/[,|]/).map { |a| author_filter(a) }.join(",")
584 date = Time.at(e.newest_date).strftime(date_fmt)
586 subject = Mail::Field.new("Subject: " + e.subject).to_s
588 subject = e.subject.force_encoding('utf-8')
590 b << "%-12s %3s %-20.20s | %s (%s)" % [date, e.matched_messages, authors, subject, e.tags]
591 $threads << e.thread_id
596 def do_tag(filter, tags)
599 q.search_messages.each do |e|
601 tags.split.each do |t|
612 e.tags_to_maildir_flags
618 attr_accessor :start, :body_start, :end
619 attr_reader :message_id, :filename, :mail
621 def initialize(msg, mail)
622 @message_id = msg.message_id
623 @filename = msg.filename
627 mail.import_headers(msg) if not $mail_installed
631 "id:%s" % @message_id
635 "id:%s, file:%s" % [@message_id, @filename]
640 def initialize(buffer, enumerable, block)
642 @enumerable = enumerable
646 @b.render { do_next }
650 @last_render - @b.line_number <= $curwin.height
654 items = @enumerable.take($curwin.height * 2)
655 return if items.empty?
656 @block.call @b, items
657 @last_render = @b.count
666 def render_staged(enumerable, &block)
667 StagedRender.new(self, enumerable, block)
673 (1..old_count).each do
685 class Notmuch::Message
691 # workaround for bug in vim's ruby
699 def self.parse(string)
700 return nil if string.empty?
701 return Header.new(string.split(/,\s+/))
709 def initialize(string = nil)
716 if string =~ /(.*?(\r\n|\n))\2/m
717 head, body = $1, $' || '', $2
719 head, body = string, ''
725 @headers[name.to_sym]
729 @headers[name.to_sym] = value
732 def format_header(value)
733 value.to_s.tr('_', '-').gsub(/(\w+)/) { $1.capitalize }
738 @headers.each do |key, value|
739 buffer << "%s: %s\r\n" %
740 [format_header(key), value]
768 r = Mail::Message.new
772 r[:in_reply_to] = self[:message_id]
773 r[:references] = self[:references]
777 HEADERS = [ :from, :to, :cc, :references, :in_reply_to, :reply_to, :message_id ]
779 def import_headers(m)
781 dashed = format_header(e)
782 @headers[e] = Header.parse(m[dashed])
789 if not $mail_installed
790 puts "WARNING: Install the 'mail' gem, without it support is limited"
792 def self.read(filename)
793 Message.new(File.open(filename, 'rb') { |f| f.read })
797 include SimpleMessage
804 return self if not multipart?
805 return text_part || html_part
809 if mime_type != "text/html"
812 IO.popen("elinks --dump", "w+") do |pipe|
813 pipe.write(decode_body)
825 RUBY_VERSION >= "1.9" ? force_encoding('utf-8') : self
834 command NotMuch :call s:NotMuch()
836 " vim: set noexpandtab: