]> git.notmuchmail.org Git - notmuch/blob - vim/notmuch.vim
VIM: Use notmuch CLI for config
[notmuch] / vim / notmuch.vim
1 if exists("g:loaded_notmuch")
2         finish
3 endif
4
5 if !has("ruby") || version < 700
6         finish
7 endif
8
9 let g:loaded_notmuch = "yep"
10
11 let g:notmuch_folders_maps = {
12         \ '<Enter>':    'folders_show_search()',
13         \ 's':          'folders_search_prompt()',
14         \ '=':          'folders_refresh()',
15         \ 'c':          'compose()',
16         \ }
17
18 let g:notmuch_search_maps = {
19         \ 'q':          'kill_this_buffer()',
20         \ '<Enter>':    'search_show_thread(1)',
21         \ '<Space>':    'search_show_thread(2)',
22         \ 'A':          'search_tag("-inbox -unread")',
23         \ 'I':          'search_tag("-unread")',
24         \ 't':          'search_tag("")',
25         \ 's':          'search_search_prompt()',
26         \ '=':          'search_refresh()',
27         \ '?':          'search_info()',
28         \ 'c':          'compose()',
29         \ }
30
31 let g:notmuch_show_maps = {
32         \ 'q':          'kill_this_buffer()',
33         \ 'A':          'show_tag("-inbox -unread")',
34         \ 'I':          'show_tag("-unread")',
35         \ 't':          'show_tag("")',
36         \ 'o':          'show_open_msg()',
37         \ 'e':          'show_extract_msg()',
38         \ 's':          'show_save_msg()',
39         \ 'p':          'show_save_patches()',
40         \ 'r':          'show_reply()',
41         \ '?':          'show_info()',
42         \ '<Tab>':      'show_next_msg()',
43         \ 'c':          'compose()',
44         \ }
45
46 let g:notmuch_compose_maps = {
47         \ ',s':         'compose_send()',
48         \ ',q':         'compose_quit()',
49         \ }
50
51 let s:notmuch_folders_default = [
52         \ [ 'new', 'tag:inbox and tag:unread' ],
53         \ [ 'inbox', 'tag:inbox' ],
54         \ [ 'unread', 'tag:unread' ],
55         \ ]
56
57 let s:notmuch_date_format_default = '%d.%m.%y'
58 let s:notmuch_datetime_format_default = '%d.%m.%y %H:%M:%S'
59 let s:notmuch_reader_default = 'mutt -f %s'
60 let s:notmuch_sendmail_default = 'sendmail'
61 let s:notmuch_folders_count_threads_default = 0
62
63 function! s:new_file_buffer(type, fname)
64         exec printf('edit %s', a:fname)
65         execute printf('set filetype=notmuch-%s', a:type)
66         execute printf('set syntax=notmuch-%s', a:type)
67         ruby $curbuf.init(VIM::evaluate('a:type'))
68 endfunction
69
70 function! s:on_compose_delete()
71         if b:compose_done
72                 return
73         endif
74         if input('[s]end/[q]uit? ') =~ '^s'
75                 call s:compose_send()
76         endif
77 endfunction
78
79 "" actions
80
81 function! s:compose_quit()
82         let b:compose_done = 1
83         call s:kill_this_buffer()
84 endfunction
85
86 function! s:compose_send()
87         let b:compose_done = 1
88         let fname = expand('%')
89         let lines = getline(5, '$')
90
91 ruby << EOF
92         # Generate proper mail to send
93         text = VIM::evaluate('lines').join("\n")
94         fname = VIM::evaluate('fname')
95         transport = Mail.new(text)
96         transport.message_id = generate_message_id
97         transport.charset = 'utf-8'
98         File.write(fname, transport.to_s)
99 EOF
100
101         let cmdtxt = g:notmuch_sendmail . ' -t -f ' . s:reply_from . ' < ' . fname
102         let out = system(cmdtxt)
103         let err = v:shell_error
104         if err
105                 echohl Error
106                 echo 'Eeek! unable to send mail'
107                 echo out
108                 echohl None
109                 return
110         endif
111         call delete(fname)
112         echo 'Mail sent successfully.'
113         call s:kill_this_buffer()
114 endfunction
115
116 function! s:show_next_msg()
117 ruby << EOF
118         r, c = $curwin.cursor
119         n = $curbuf.line_number
120         i = $messages.index { |m| n >= m.start && n <= m.end }
121         m = $messages[i + 1]
122         if m
123                 r = m.body_start + 1
124                 VIM::command("normal #{m.start}zt")
125                 $curwin.cursor = r, c
126         end
127 EOF
128 endfunction
129
130 function! s:show_reply()
131         ruby open_reply get_message.mail
132         let b:compose_done = 0
133         call s:set_map(g:notmuch_compose_maps)
134         autocmd BufDelete <buffer> call s:on_compose_delete()
135         startinsert!
136 endfunction
137
138 function! s:compose()
139         ruby open_compose
140         let b:compose_done = 0
141         call s:set_map(g:notmuch_compose_maps)
142         autocmd BufDelete <buffer> call s:on_compose_delete()
143         startinsert!
144 endfunction
145
146 function! s:show_info()
147         ruby vim_puts get_message.inspect
148 endfunction
149
150 function! s:show_extract_msg()
151 ruby << EOF
152         m = get_message
153         m.mail.attachments.each do |a|
154                 File.open(a.filename, 'w') do |f|
155                         f.write a.body.decoded
156                         print "Extracted '#{a.filename}'"
157                 end
158         end
159 EOF
160 endfunction
161
162 function! s:show_open_msg()
163 ruby << EOF
164         m = get_message
165         mbox = File.expand_path('~/.notmuch/vim_mbox')
166         cmd = VIM::evaluate('g:notmuch_reader') % mbox
167         system "notmuch show --format=mbox id:#{m.message_id} > #{mbox} && #{cmd}"
168 EOF
169 endfunction
170
171 function! s:show_save_msg()
172         let file = input('File name: ')
173 ruby << EOF
174         file = VIM::evaluate('file')
175         m = get_message
176         system "notmuch show --format=mbox id:#{m.message_id} > #{file}"
177 EOF
178 endfunction
179
180 function! s:show_save_patches()
181 ruby << EOF
182         q = $curbuf.query($cur_thread)
183         t = q.search_threads.first
184         n = 0
185         t.toplevel_messages.first.replies.each do |m|
186                 next if not m['subject'] =~ /^\[PATCH.*\]/
187                 file = "%04d.patch" % [n += 1]
188                 system "notmuch show --format=mbox id:#{m.message_id} > #{file}"
189         end
190         vim_puts "Saved #{n} patches"
191 EOF
192 endfunction
193
194 function! s:show_tag(intags)
195         if empty(a:intags)
196                 let tags = input('tags: ')
197         else
198                 let tags = a:intags
199         endif
200         ruby do_tag(get_cur_view, VIM::evaluate('l:tags'))
201         call s:show_next_thread()
202 endfunction
203
204 function! s:search_search_prompt()
205         let text = input('Search: ')
206         if text == ""
207           return
208         endif
209         setlocal modifiable
210 ruby << EOF
211         $cur_search = VIM::evaluate('text')
212         $curbuf.reopen
213         search_render($cur_search)
214 EOF
215         setlocal nomodifiable
216 endfunction
217
218 function! s:search_info()
219         ruby vim_puts get_thread_id
220 endfunction
221
222 function! s:search_refresh()
223         setlocal modifiable
224         ruby $curbuf.reopen
225         ruby search_render($cur_search)
226         setlocal nomodifiable
227 endfunction
228
229 function! s:search_tag(intags)
230         if empty(a:intags)
231                 let tags = input('tags: ')
232         else
233                 let tags = a:intags
234         endif
235         ruby do_tag(get_thread_id, VIM::evaluate('l:tags'))
236         norm j
237 endfunction
238
239 function! s:folders_search_prompt()
240         let text = input('Search: ')
241         call s:search(text)
242 endfunction
243
244 function! s:folders_refresh()
245         setlocal modifiable
246         ruby $curbuf.reopen
247         ruby folders_render()
248         setlocal nomodifiable
249 endfunction
250
251 "" basic
252
253 function! s:show_cursor_moved()
254 ruby << EOF
255         if $render.is_ready?
256                 VIM::command('setlocal modifiable')
257                 $render.do_next
258                 VIM::command('setlocal nomodifiable')
259         end
260 EOF
261 endfunction
262
263 function! s:show_next_thread()
264         call s:kill_this_buffer()
265         if line('.') != line('$')
266                 norm j
267                 call s:search_show_thread(0)
268         else
269                 echo 'No more messages.'
270         endif
271 endfunction
272
273 function! s:kill_this_buffer()
274 ruby << EOF
275         $curbuf.close
276         VIM::command("bdelete!")
277 EOF
278 endfunction
279
280 function! s:set_map(maps)
281         nmapclear <buffer>
282         for [key, code] in items(a:maps)
283                 let cmd = printf(":call <SID>%s<CR>", code)
284                 exec printf('nnoremap <buffer> %s %s', key, cmd)
285         endfor
286 endfunction
287
288 function! s:new_buffer(type)
289         enew
290         setlocal buftype=nofile bufhidden=hide
291         keepjumps 0d
292         execute printf('set filetype=notmuch-%s', a:type)
293         execute printf('set syntax=notmuch-%s', a:type)
294         ruby $curbuf.init(VIM::evaluate('a:type'))
295 endfunction
296
297 function! s:set_menu_buffer()
298         setlocal nomodifiable
299         setlocal cursorline
300         setlocal nowrap
301 endfunction
302
303 "" main
304
305 function! s:show(thread_id)
306         call s:new_buffer('show')
307         setlocal modifiable
308 ruby << EOF
309         thread_id = VIM::evaluate('a:thread_id')
310         $cur_thread = thread_id
311         $messages.clear
312         $curbuf.render do |b|
313                 q = $curbuf.query(get_cur_view)
314                 q.sort = Notmuch::SORT_OLDEST_FIRST
315                 msgs = q.search_messages
316                 msgs.each do |msg|
317                         m = Mail.read(msg.filename)
318                         part = m.find_first_text
319                         nm_m = Message.new(msg, m)
320                         $messages << nm_m
321                         date_fmt = VIM::evaluate('g:notmuch_datetime_format')
322                         date = Time.at(msg.date).strftime(date_fmt)
323                         nm_m.start = b.count
324                         b << "%s %s (%s)" % [msg['from'], date, msg.tags]
325                         b << "Subject: %s" % [msg['subject']]
326                         b << "To: %s" % msg['to']
327                         b << "Cc: %s" % msg['cc']
328                         b << "Date: %s" % msg['date']
329                         nm_m.body_start = b.count
330                         b << "--- %s ---" % part.mime_type
331                         part.convert.each_line do |l|
332                                 b << l.chomp
333                         end
334                         b << ""
335                         nm_m.end = b.count
336                 end
337                 b.delete(b.count)
338         end
339         $messages.each_with_index do |msg, i|
340                 VIM::command("syntax region nmShowMsg#{i}Desc start='\\%%%il' end='\\%%%il' contains=@nmShowMsgDesc" % [msg.start, msg.start + 1])
341                 VIM::command("syntax region nmShowMsg#{i}Head start='\\%%%il' end='\\%%%il' contains=@nmShowMsgHead" % [msg.start + 1, msg.body_start])
342                 VIM::command("syntax region nmShowMsg#{i}Body start='\\%%%il' end='\\%%%dl' contains=@nmShowMsgBody" % [msg.body_start, msg.end])
343         end
344 EOF
345         setlocal nomodifiable
346         call s:set_map(g:notmuch_show_maps)
347 endfunction
348
349 function! s:search_show_thread(mode)
350 ruby << EOF
351         mode = VIM::evaluate('a:mode')
352         id = get_thread_id
353         case mode
354         when 0;
355         when 1; $cur_filter = nil
356         when 2; $cur_filter = $cur_search
357         end
358         VIM::command("call s:show('#{id}')")
359 EOF
360 endfunction
361
362 function! s:search(search)
363         call s:new_buffer('search')
364 ruby << EOF
365         $cur_search = VIM::evaluate('a:search')
366         search_render($cur_search)
367 EOF
368         call s:set_menu_buffer()
369         call s:set_map(g:notmuch_search_maps)
370         autocmd CursorMoved <buffer> call s:show_cursor_moved()
371 endfunction
372
373 function! s:folders_show_search()
374 ruby << EOF
375         n = $curbuf.line_number
376         s = $searches[n - 1]
377         VIM::command("call s:search('#{s}')")
378 EOF
379 endfunction
380
381 function! s:folders()
382         call s:new_buffer('folders')
383         ruby folders_render()
384         call s:set_menu_buffer()
385         call s:set_map(g:notmuch_folders_maps)
386 endfunction
387
388 "" root
389
390 function! s:set_defaults()
391         if !exists('g:notmuch_date_format')
392                 if exists('g:notmuch_rb_date_format')
393                         let g:notmuch_date_format = g:notmuch_rb_date_format
394                 else
395                         let g:notmuch_date_format = s:notmuch_date_format_default
396                 endif
397         endif
398
399         if !exists('g:notmuch_datetime_format')
400                 if exists('g:notmuch_rb_datetime_format')
401                         let g:notmuch_datetime_format = g:notmuch_rb_datetime_format
402                 else
403                         let g:notmuch_datetime_format = s:notmuch_datetime_format_default
404                 endif
405         endif
406
407         if !exists('g:notmuch_reader')
408                 if exists('g:notmuch_rb_reader')
409                         let g:notmuch_reader = g:notmuch_rb_reader
410                 else
411                         let g:notmuch_reader = s:notmuch_reader_default
412                 endif
413         endif
414
415         if !exists('g:notmuch_sendmail')
416                 if exists('g:notmuch_rb_sendmail')
417                         let g:notmuch_sendmail = g:notmuch_rb_sendmail
418                 else
419                         let g:notmuch_sendmail = s:notmuch_sendmail_default
420                 endif
421         endif
422
423         if !exists('g:notmuch_folders_count_threads')
424                 if exists('g:notmuch_rb_count_threads')
425                         let g:notmuch_count_threads = g:notmuch_rb_count_threads
426                 else
427                         let g:notmuch_folders_count_threads = s:notmuch_folders_count_threads_default
428                 endif
429         endif
430
431         if !exists('g:notmuch_custom_search_maps') && exists('g:notmuch_rb_custom_search_maps')
432                 let g:notmuch_custom_search_maps = g:notmuch_rb_custom_search_maps
433         endif
434
435         if !exists('g:notmuch_custom_show_maps') && exists('g:notmuch_rb_custom_show_maps')
436                 let g:notmuch_custom_show_maps = g:notmuch_rb_custom_show_maps
437         endif
438
439         if exists('g:notmuch_custom_search_maps')
440                 call extend(g:notmuch_search_maps, g:notmuch_custom_search_maps)
441         endif
442
443         if exists('g:notmuch_custom_show_maps')
444                 call extend(g:notmuch_show_maps, g:notmuch_custom_show_maps)
445         endif
446
447         if !exists('g:notmuch_folders')
448                 if exists('g:notmuch_rb_folders')
449                         let g:notmuch_folders = g:notmuch_rb_folders
450                 else
451                         let g:notmuch_folders = s:notmuch_folders_default
452                 endif
453         endif
454 endfunction
455
456 function! s:NotMuch(...)
457         call s:set_defaults()
458
459 ruby << EOF
460         require 'notmuch'
461         require 'rubygems'
462         require 'tempfile'
463         require 'socket'
464         begin
465                 require 'mail'
466         rescue LoadError
467         end
468
469         $db_name = nil
470         $email = $email_name = $email_address = nil
471         $searches = []
472         $threads = []
473         $messages = []
474         $mail_installed = defined?(Mail)
475
476         def get_config_item(item)
477                 result = ''
478                 IO.popen(['notmuch', 'config', 'get', item]) { |out|
479                         result = out.read
480                 }
481                 return result.rstrip
482         end
483
484         def get_config
485                 $db_name = get_config_item('database.path')
486                 $email_name = get_config_item('user.name')
487                 $email_address = get_config_item('user.primary_email')
488                 $email_name = get_config_item('user.name')
489                 $email = "%s <%s>" % [$email_name, $email_address]
490         end
491
492         def vim_puts(s)
493                 VIM::command("echo '#{s.to_s}'")
494         end
495
496         def vim_p(s)
497                 VIM::command("echo '#{s.inspect}'")
498         end
499
500         def author_filter(a)
501                 # TODO email format, aliases
502                 a.strip!
503                 a.gsub!(/[\.@].*/, '')
504                 a.gsub!(/^ext /, '')
505                 a.gsub!(/ \(.*\)/, '')
506                 a
507         end
508
509         def get_thread_id
510                 n = $curbuf.line_number - 1
511                 return "thread:%s" % $threads[n]
512         end
513
514         def get_message
515                 n = $curbuf.line_number
516                 return $messages.find { |m| n >= m.start && n <= m.end }
517         end
518
519         def get_cur_view
520                 if $cur_filter
521                         return "#{$cur_thread} and (#{$cur_filter})"
522                 else
523                         return $cur_thread
524                 end
525         end
526
527         def generate_message_id
528                 t = Time.now
529                 random_tag = sprintf('%x%x_%x%x%x',
530                         t.to_i, t.tv_usec,
531                         $$, Thread.current.object_id.abs, rand(255))
532                 return "<#{random_tag}@#{Socket.gethostname}.notmuch>"
533         end
534
535         def open_compose_helper(lines, cur)
536                 help_lines = [
537                         'Notmuch-Help: Type in your message here; to help you use these bindings:',
538                         'Notmuch-Help:   ,s    - send the message (Notmuch-Help lines will be removed)',
539                         'Notmuch-Help:   ,q    - abort the message',
540                         ]
541
542                 dir = File.expand_path('~/.notmuch/compose')
543                 FileUtils.mkdir_p(dir)
544                 Tempfile.open(['nm-', '.mail'], dir) do |f|
545                         f.puts(help_lines)
546                         f.puts
547                         f.puts(lines)
548
549                         sig_file = File.expand_path('~/.signature')
550                         if File.exists?(sig_file)
551                                 f.puts("-- ")
552                                 f.write(File.read(sig_file))
553                         end
554
555                         f.flush
556
557                         cur += help_lines.size + 1
558
559                         VIM::command("let s:reply_from='%s'" % $email_address)
560                         VIM::command("call s:new_file_buffer('compose', '#{f.path}')")
561                         VIM::command("call cursor(#{cur}, 0)")
562                 end
563         end
564
565         def open_reply(orig)
566                 reply = orig.reply do |m|
567                         # fix headers
568                         if not m[:reply_to]
569                                 m.to = [orig[:from].to_s, orig[:to].to_s]
570                         end
571                         m.cc = orig[:cc]
572                         m.from = $email
573                         m.charset = 'utf-8'
574                 end
575
576                 lines = []
577
578                 body_lines = []
579                 if $mail_installed
580                         addr = Mail::Address.new(orig[:from].value)
581                         name = addr.name
582                         name = addr.local + "@" if name.nil? && !addr.local.nil?
583                 else
584                         name = orig[:from]
585                 end
586                 name = "somebody" if name.nil?
587
588                 body_lines << "%s wrote:" % name
589                 part = orig.find_first_text
590                 part.convert.each_line do |l|
591                         body_lines << "> %s" % l.chomp
592                 end
593                 body_lines << ""
594                 body_lines << ""
595                 body_lines << ""
596
597                 reply.body = body_lines.join("\n")
598
599                 lines += reply.present.lines.map { |e| e.chomp }
600                 lines << ""
601
602                 cur = lines.count - 1
603
604                 open_compose_helper(lines, cur)
605         end
606
607         def open_compose()
608                 lines = []
609
610                 lines << "From: #{$email}"
611                 lines << "To: "
612                 cur = lines.count
613
614                 lines << "Cc: "
615                 lines << "Bcc: "
616                 lines << "Subject: "
617                 lines << ""
618                 lines << ""
619                 lines << ""
620
621                 open_compose_helper(lines, cur)
622         end
623
624         def folders_render()
625                 $curbuf.render do |b|
626                         folders = VIM::evaluate('g:notmuch_folders')
627                         count_threads = VIM::evaluate('g:notmuch_folders_count_threads') == 1
628                         $searches.clear
629                         folders.each do |name, search|
630                                 q = $curbuf.query(search)
631                                 $searches << search
632                                 count = count_threads ? q.search_threads.count : q.search_messages.count
633                                 b << "%9d %-20s (%s)" % [count, name, search]
634                         end
635                 end
636         end
637
638         def search_render(search)
639                 date_fmt = VIM::evaluate('g:notmuch_date_format')
640                 q = $curbuf.query(search)
641                 q.sort = Notmuch::SORT_NEWEST_FIRST
642                 $threads.clear
643                 t = q.search_threads
644
645                 $render = $curbuf.render_staged(t) do |b, items|
646                         items.each do |e|
647                                 authors = e.authors.to_utf8.split(/[,|]/).map { |a| author_filter(a) }.join(",")
648                                 date = Time.at(e.newest_date).strftime(date_fmt)
649                                 subject = e.messages.first['subject']
650                                 if $mail_installed
651                                         subject = Mail::Field.new("Subject: " + subject).to_s
652                                 else
653                                         subject = subject.force_encoding('utf-8')
654                                 end
655                                 b << "%-12s %3s %-20.20s | %s (%s)" % [date, e.matched_messages, authors, subject, e.tags]
656                                 $threads << e.thread_id
657                         end
658                 end
659         end
660
661         def do_tag(filter, tags)
662                 $curbuf.do_write do |db|
663                         q = db.query(filter)
664                         q.search_messages.each do |e|
665                                 e.freeze
666                                 tags.split.each do |t|
667                                         case t
668                                         when /^-(.*)/
669                                                 e.remove_tag($1)
670                                         when /^\+(.*)/
671                                                 e.add_tag($1)
672                                         when /^([^\+^-].*)/
673                                                 e.add_tag($1)
674                                         end
675                                 end
676                                 e.thaw
677                                 e.tags_to_maildir_flags
678                         end
679                         q.destroy!
680                 end
681         end
682
683         module DbHelper
684                 def init(name)
685                         @name = name
686                         @db = Notmuch::Database.new($db_name)
687                         @queries = []
688                 end
689
690                 def query(*args)
691                         q = @db.query(*args)
692                         @queries << q
693                         q
694                 end
695
696                 def close
697                         @queries.delete_if { |q| ! q.destroy! }
698                         @db.close
699                 end
700
701                 def reopen
702                         close if @db
703                         @db = Notmuch::Database.new($db_name)
704                 end
705
706                 def do_write
707                         db = Notmuch::Database.new($db_name, :mode => Notmuch::MODE_READ_WRITE)
708                         begin
709                                 yield db
710                         ensure
711                                 db.close
712                         end
713                 end
714         end
715
716         class Message
717                 attr_accessor :start, :body_start, :end
718                 attr_reader :message_id, :filename, :mail
719
720                 def initialize(msg, mail)
721                         @message_id = msg.message_id
722                         @filename = msg.filename
723                         @mail = mail
724                         @start = 0
725                         @end = 0
726                         mail.import_headers(msg) if not $mail_installed
727                 end
728
729                 def to_s
730                         "id:%s" % @message_id
731                 end
732
733                 def inspect
734                         "id:%s, file:%s" % [@message_id, @filename]
735                 end
736         end
737
738         class StagedRender
739                 def initialize(buffer, enumerable, block)
740                         @b = buffer
741                         @enumerable = enumerable
742                         @block = block
743                         @last_render = 0
744
745                         @b.render { do_next }
746                 end
747
748                 def is_ready?
749                         @last_render - @b.line_number <= $curwin.height
750                 end
751
752                 def do_next
753                         items = @enumerable.take($curwin.height * 2)
754                         return if items.empty?
755                         @block.call @b, items
756                         @last_render = @b.count
757                 end
758         end
759
760         class VIM::Buffer
761                 include DbHelper
762
763                 def <<(a)
764                         append(count(), a)
765                 end
766
767                 def render_staged(enumerable, &block)
768                         StagedRender.new(self, enumerable, block)
769                 end
770
771                 def render
772                         old_count = count
773                         yield self
774                         (1..old_count).each do
775                                 delete(1)
776                         end
777                 end
778         end
779
780         class Notmuch::Tags
781                 def to_s
782                         to_a.join(" ")
783                 end
784         end
785
786         class Notmuch::Message
787                 def to_s
788                         "id:%s" % message_id
789                 end
790         end
791
792         # workaround for bug in vim's ruby
793         class Object
794                 def flush
795                 end
796         end
797
798         module SimpleMessage
799                 class Header < Array
800                         def self.parse(string)
801                                 return nil if string.empty?
802                                 return Header.new(string.split(/,\s+/))
803                         end
804
805                         def to_s
806                                 self.join(', ')
807                         end
808                 end
809
810                 def initialize(string = nil)
811                         @raw_source = string
812                         @body = nil
813                         @headers = {}
814
815                         return if not string
816
817                         if string =~ /(.*?(\r\n|\n))\2/m
818                                 head, body = $1, $' || '', $2
819                         else
820                                 head, body = string, ''
821                         end
822                         @body = body
823                 end
824
825                 def [](name)
826                         @headers[name.to_sym]
827                 end
828
829                 def []=(name, value)
830                         @headers[name.to_sym] = value
831                 end
832
833                 def format_header(value)
834                         value.to_s.tr('_', '-').gsub(/(\w+)/) { $1.capitalize }
835                 end
836
837                 def to_s
838                         buffer = ''
839                         @headers.each do |key, value|
840                                 buffer << "%s: %s\r\n" %
841                                         [format_header(key), value]
842                         end
843                         buffer << "\r\n"
844                         buffer << @body
845                         buffer
846                 end
847
848                 def body=(value)
849                         @body = value
850                 end
851
852                 def from
853                         @headers[:from]
854                 end
855
856                 def decoded
857                         @body
858                 end
859
860                 def mime_type
861                         'text/plain'
862                 end
863
864                 def multipart?
865                         false
866                 end
867
868                 def reply
869                         r = Mail::Message.new
870                         r[:from] = self[:to]
871                         r[:to] = self[:from]
872                         r[:cc] = self[:cc]
873                         r[:in_reply_to] = self[:message_id]
874                         r[:references] = self[:references]
875                         r
876                 end
877
878                 HEADERS = [ :from, :to, :cc, :references, :in_reply_to, :reply_to, :message_id ]
879
880                 def import_headers(m)
881                         HEADERS.each do |e|
882                                 dashed = format_header(e)
883                                 @headers[e] = Header.parse(m[dashed])
884                         end
885                 end
886         end
887
888         module Mail
889
890                 if not $mail_installed
891                         puts "WARNING: Install the 'mail' gem, without it support is limited"
892
893                         def self.read(filename)
894                                 Message.new(File.open(filename, 'rb') { |f| f.read })
895                         end
896
897                         class Message
898                                 include SimpleMessage
899                         end
900                 end
901
902                 class Message
903
904                         def find_first_text
905                                 return self if not multipart?
906                                 return text_part || html_part
907                         end
908
909                         def convert
910                                 if mime_type != "text/html"
911                                         text = decoded
912                                 else
913                                         IO.popen(VIM::evaluate('exists("g:notmuch_html_converter") ? ' +
914                                                         'g:notmuch_html_converter : "elinks --dump"'), "w+") do |pipe|
915                                                 pipe.write(decode_body)
916                                                 pipe.close_write
917                                                 text = pipe.read
918                                         end
919                                 end
920                                 text
921                         end
922
923                         def present
924                                 buffer = ''
925                                 header.fields.each do |f|
926                                         buffer << "%s: %s\r\n" % [f.name, f.to_s]
927                                 end
928                                 buffer << "\r\n"
929                                 buffer << body.to_s
930                                 buffer
931                         end
932                 end
933         end
934
935         class String
936                 def to_utf8
937                         RUBY_VERSION >= "1.9" ? force_encoding('utf-8') : self
938                 end
939         end
940
941         get_config
942 EOF
943         if a:0
944           call s:search(join(a:000))
945         else
946           call s:folders()
947         endif
948 endfunction
949
950 command -nargs=* NotMuch call s:NotMuch(<f-args>)
951
952 " vim: set noexpandtab: