]> git.notmuchmail.org Git - notmuch/blob - vim/notmuch.vim
debian: build depend on dh-python
[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         $config = {}
475         $mail_installed = defined?(Mail)
476
477         def get_config
478                 group = nil
479                 config = ENV['NOTMUCH_CONFIG'] || '~/.notmuch-config'
480                 File.open(File.expand_path(config)).each do |l|
481                         l.chomp!
482                         case l
483                         when /^\[(.*)\]$/
484                                 group = $1
485                         when ''
486                         when /^(.*)=(.*)$/
487                                 key = "%s.%s" % [group, $1]
488                                 value = $2
489                                 $config[key] = value
490                         end
491                 end
492
493                 $db_name = $config['database.path']
494                 $email_name = $config['user.name']
495                 $email_address = $config['user.primary_email']
496                 $email = "%s <%s>" % [$email_name, $email_address]
497         end
498
499         def vim_puts(s)
500                 VIM::command("echo '#{s.to_s}'")
501         end
502
503         def vim_p(s)
504                 VIM::command("echo '#{s.inspect}'")
505         end
506
507         def author_filter(a)
508                 # TODO email format, aliases
509                 a.strip!
510                 a.gsub!(/[\.@].*/, '')
511                 a.gsub!(/^ext /, '')
512                 a.gsub!(/ \(.*\)/, '')
513                 a
514         end
515
516         def get_thread_id
517                 n = $curbuf.line_number - 1
518                 return "thread:%s" % $threads[n]
519         end
520
521         def get_message
522                 n = $curbuf.line_number
523                 return $messages.find { |m| n >= m.start && n <= m.end }
524         end
525
526         def get_cur_view
527                 if $cur_filter
528                         return "#{$cur_thread} and (#{$cur_filter})"
529                 else
530                         return $cur_thread
531                 end
532         end
533
534         def generate_message_id
535                 t = Time.now
536                 random_tag = sprintf('%x%x_%x%x%x',
537                         t.to_i, t.tv_usec,
538                         $$, Thread.current.object_id.abs, rand(255))
539                 return "<#{random_tag}@#{Socket.gethostname}.notmuch>"
540         end
541
542         def open_compose_helper(lines, cur)
543                 help_lines = [
544                         'Notmuch-Help: Type in your message here; to help you use these bindings:',
545                         'Notmuch-Help:   ,s    - send the message (Notmuch-Help lines will be removed)',
546                         'Notmuch-Help:   ,q    - abort the message',
547                         ]
548
549                 dir = File.expand_path('~/.notmuch/compose')
550                 FileUtils.mkdir_p(dir)
551                 Tempfile.open(['nm-', '.mail'], dir) do |f|
552                         f.puts(help_lines)
553                         f.puts
554                         f.puts(lines)
555
556                         sig_file = File.expand_path('~/.signature')
557                         if File.exists?(sig_file)
558                                 f.puts("-- ")
559                                 f.write(File.read(sig_file))
560                         end
561
562                         f.flush
563
564                         cur += help_lines.size + 1
565
566                         VIM::command("let s:reply_from='%s'" % $email_address)
567                         VIM::command("call s:new_file_buffer('compose', '#{f.path}')")
568                         VIM::command("call cursor(#{cur}, 0)")
569                 end
570         end
571
572         def open_reply(orig)
573                 reply = orig.reply do |m|
574                         # fix headers
575                         if not m[:reply_to]
576                                 m.to = [orig[:from].to_s, orig[:to].to_s]
577                         end
578                         m.cc = orig[:cc]
579                         m.from = $email
580                         m.charset = 'utf-8'
581                 end
582
583                 lines = []
584
585                 body_lines = []
586                 if $mail_installed
587                         addr = Mail::Address.new(orig[:from].value)
588                         name = addr.name
589                         name = addr.local + "@" if name.nil? && !addr.local.nil?
590                 else
591                         name = orig[:from]
592                 end
593                 name = "somebody" if name.nil?
594
595                 body_lines << "%s wrote:" % name
596                 part = orig.find_first_text
597                 part.convert.each_line do |l|
598                         body_lines << "> %s" % l.chomp
599                 end
600                 body_lines << ""
601                 body_lines << ""
602                 body_lines << ""
603
604                 reply.body = body_lines.join("\n")
605
606                 lines += reply.present.lines.map { |e| e.chomp }
607                 lines << ""
608
609                 cur = lines.count - 1
610
611                 open_compose_helper(lines, cur)
612         end
613
614         def open_compose()
615                 lines = []
616
617                 lines << "From: #{$email}"
618                 lines << "To: "
619                 cur = lines.count
620
621                 lines << "Cc: "
622                 lines << "Bcc: "
623                 lines << "Subject: "
624                 lines << ""
625                 lines << ""
626                 lines << ""
627
628                 open_compose_helper(lines, cur)
629         end
630
631         def folders_render()
632                 $curbuf.render do |b|
633                         folders = VIM::evaluate('g:notmuch_folders')
634                         count_threads = VIM::evaluate('g:notmuch_folders_count_threads') == 1
635                         $searches.clear
636                         folders.each do |name, search|
637                                 q = $curbuf.query(search)
638                                 $searches << search
639                                 count = count_threads ? q.search_threads.count : q.search_messages.count
640                                 b << "%9d %-20s (%s)" % [count, name, search]
641                         end
642                 end
643         end
644
645         def search_render(search)
646                 date_fmt = VIM::evaluate('g:notmuch_date_format')
647                 q = $curbuf.query(search)
648                 q.sort = Notmuch::SORT_NEWEST_FIRST
649                 $threads.clear
650                 t = q.search_threads
651
652                 $render = $curbuf.render_staged(t) do |b, items|
653                         items.each do |e|
654                                 authors = e.authors.to_utf8.split(/[,|]/).map { |a| author_filter(a) }.join(",")
655                                 date = Time.at(e.newest_date).strftime(date_fmt)
656                                 subject = e.messages.first['subject']
657                                 if $mail_installed
658                                         subject = Mail::Field.new("Subject: " + subject).to_s
659                                 else
660                                         subject = subject.force_encoding('utf-8')
661                                 end
662                                 b << "%-12s %3s %-20.20s | %s (%s)" % [date, e.matched_messages, authors, subject, e.tags]
663                                 $threads << e.thread_id
664                         end
665                 end
666         end
667
668         def do_tag(filter, tags)
669                 $curbuf.do_write do |db|
670                         q = db.query(filter)
671                         q.search_messages.each do |e|
672                                 e.freeze
673                                 tags.split.each do |t|
674                                         case t
675                                         when /^-(.*)/
676                                                 e.remove_tag($1)
677                                         when /^\+(.*)/
678                                                 e.add_tag($1)
679                                         when /^([^\+^-].*)/
680                                                 e.add_tag($1)
681                                         end
682                                 end
683                                 e.thaw
684                                 e.tags_to_maildir_flags
685                         end
686                         q.destroy!
687                 end
688         end
689
690         module DbHelper
691                 def init(name)
692                         @name = name
693                         @db = Notmuch::Database.new($db_name)
694                         @queries = []
695                 end
696
697                 def query(*args)
698                         q = @db.query(*args)
699                         @queries << q
700                         q
701                 end
702
703                 def close
704                         @queries.delete_if { |q| ! q.destroy! }
705                         @db.close
706                 end
707
708                 def reopen
709                         close if @db
710                         @db = Notmuch::Database.new($db_name)
711                 end
712
713                 def do_write
714                         db = Notmuch::Database.new($db_name, :mode => Notmuch::MODE_READ_WRITE)
715                         begin
716                                 yield db
717                         ensure
718                                 db.close
719                         end
720                 end
721         end
722
723         class Message
724                 attr_accessor :start, :body_start, :end
725                 attr_reader :message_id, :filename, :mail
726
727                 def initialize(msg, mail)
728                         @message_id = msg.message_id
729                         @filename = msg.filename
730                         @mail = mail
731                         @start = 0
732                         @end = 0
733                         mail.import_headers(msg) if not $mail_installed
734                 end
735
736                 def to_s
737                         "id:%s" % @message_id
738                 end
739
740                 def inspect
741                         "id:%s, file:%s" % [@message_id, @filename]
742                 end
743         end
744
745         class StagedRender
746                 def initialize(buffer, enumerable, block)
747                         @b = buffer
748                         @enumerable = enumerable
749                         @block = block
750                         @last_render = 0
751
752                         @b.render { do_next }
753                 end
754
755                 def is_ready?
756                         @last_render - @b.line_number <= $curwin.height
757                 end
758
759                 def do_next
760                         items = @enumerable.take($curwin.height * 2)
761                         return if items.empty?
762                         @block.call @b, items
763                         @last_render = @b.count
764                 end
765         end
766
767         class VIM::Buffer
768                 include DbHelper
769
770                 def <<(a)
771                         append(count(), a)
772                 end
773
774                 def render_staged(enumerable, &block)
775                         StagedRender.new(self, enumerable, block)
776                 end
777
778                 def render
779                         old_count = count
780                         yield self
781                         (1..old_count).each do
782                                 delete(1)
783                         end
784                 end
785         end
786
787         class Notmuch::Tags
788                 def to_s
789                         to_a.join(" ")
790                 end
791         end
792
793         class Notmuch::Message
794                 def to_s
795                         "id:%s" % message_id
796                 end
797         end
798
799         # workaround for bug in vim's ruby
800         class Object
801                 def flush
802                 end
803         end
804
805         module SimpleMessage
806                 class Header < Array
807                         def self.parse(string)
808                                 return nil if string.empty?
809                                 return Header.new(string.split(/,\s+/))
810                         end
811
812                         def to_s
813                                 self.join(', ')
814                         end
815                 end
816
817                 def initialize(string = nil)
818                         @raw_source = string
819                         @body = nil
820                         @headers = {}
821
822                         return if not string
823
824                         if string =~ /(.*?(\r\n|\n))\2/m
825                                 head, body = $1, $' || '', $2
826                         else
827                                 head, body = string, ''
828                         end
829                         @body = body
830                 end
831
832                 def [](name)
833                         @headers[name.to_sym]
834                 end
835
836                 def []=(name, value)
837                         @headers[name.to_sym] = value
838                 end
839
840                 def format_header(value)
841                         value.to_s.tr('_', '-').gsub(/(\w+)/) { $1.capitalize }
842                 end
843
844                 def to_s
845                         buffer = ''
846                         @headers.each do |key, value|
847                                 buffer << "%s: %s\r\n" %
848                                         [format_header(key), value]
849                         end
850                         buffer << "\r\n"
851                         buffer << @body
852                         buffer
853                 end
854
855                 def body=(value)
856                         @body = value
857                 end
858
859                 def from
860                         @headers[:from]
861                 end
862
863                 def decoded
864                         @body
865                 end
866
867                 def mime_type
868                         'text/plain'
869                 end
870
871                 def multipart?
872                         false
873                 end
874
875                 def reply
876                         r = Mail::Message.new
877                         r[:from] = self[:to]
878                         r[:to] = self[:from]
879                         r[:cc] = self[:cc]
880                         r[:in_reply_to] = self[:message_id]
881                         r[:references] = self[:references]
882                         r
883                 end
884
885                 HEADERS = [ :from, :to, :cc, :references, :in_reply_to, :reply_to, :message_id ]
886
887                 def import_headers(m)
888                         HEADERS.each do |e|
889                                 dashed = format_header(e)
890                                 @headers[e] = Header.parse(m[dashed])
891                         end
892                 end
893         end
894
895         module Mail
896
897                 if not $mail_installed
898                         puts "WARNING: Install the 'mail' gem, without it support is limited"
899
900                         def self.read(filename)
901                                 Message.new(File.open(filename, 'rb') { |f| f.read })
902                         end
903
904                         class Message
905                                 include SimpleMessage
906                         end
907                 end
908
909                 class Message
910
911                         def find_first_text
912                                 return self if not multipart?
913                                 return text_part || html_part
914                         end
915
916                         def convert
917                                 if mime_type != "text/html"
918                                         text = decoded
919                                 else
920                                         IO.popen(VIM::evaluate('exists("g:notmuch_html_converter") ? ' +
921                                                         'g:notmuch_html_converter : "elinks --dump"'), "w+") do |pipe|
922                                                 pipe.write(decode_body)
923                                                 pipe.close_write
924                                                 text = pipe.read
925                                         end
926                                 end
927                                 text
928                         end
929
930                         def present
931                                 buffer = ''
932                                 header.fields.each do |f|
933                                         buffer << "%s: %s\r\n" % [f.name, f.to_s]
934                                 end
935                                 buffer << "\r\n"
936                                 buffer << body.to_s
937                                 buffer
938                         end
939                 end
940         end
941
942         class String
943                 def to_utf8
944                         RUBY_VERSION >= "1.9" ? force_encoding('utf-8') : self
945                 end
946         end
947
948         get_config
949 EOF
950         if a:0
951           call s:search(join(a:000))
952         else
953           call s:folders()
954         endif
955 endfunction
956
957 command -nargs=* NotMuch call s:NotMuch(<f-args>)
958
959 " vim: set noexpandtab: