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