]> git.notmuchmail.org Git - notmuch/blob - vim/notmuch.vim
vim: make the html handler configurable
[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
90         " remove headers
91         0,4d
92         write
93
94         let cmdtxt = g:notmuch_sendmail . ' -t -f ' . s:reply_from . ' < ' . fname
95         let out = system(cmdtxt)
96         let err = v:shell_error
97         if err
98                 undo
99                 write
100                 echohl Error
101                 echo 'Eeek! unable to send mail'
102                 echo out
103                 echohl None
104                 return
105         endif
106         call delete(fname)
107         echo 'Mail sent successfully.'
108         call s:kill_this_buffer()
109 endfunction
110
111 function! s:show_next_msg()
112 ruby << EOF
113         r, c = $curwin.cursor
114         n = $curbuf.line_number
115         i = $messages.index { |m| n >= m.start && n <= m.end }
116         m = $messages[i + 1]
117         if m
118                 r = m.body_start + 1
119                 VIM::command("normal #{m.start}zt")
120                 $curwin.cursor = r, c
121         end
122 EOF
123 endfunction
124
125 function! s:show_reply()
126         ruby open_reply get_message.mail
127         let b:compose_done = 0
128         call s:set_map(g:notmuch_compose_maps)
129         autocmd BufDelete <buffer> call s:on_compose_delete()
130         startinsert!
131 endfunction
132
133 function! s:compose()
134         ruby open_compose
135         let b:compose_done = 0
136         call s:set_map(g:notmuch_compose_maps)
137         autocmd BufDelete <buffer> call s:on_compose_delete()
138         startinsert!
139 endfunction
140
141 function! s:show_info()
142         ruby vim_puts get_message.inspect
143 endfunction
144
145 function! s:show_extract_msg()
146 ruby << EOF
147         m = get_message
148         m.mail.attachments.each do |a|
149                 File.open(a.filename, 'w') do |f|
150                         f.write a.body.decoded
151                         print "Extracted '#{a.filename}'"
152                 end
153         end
154 EOF
155 endfunction
156
157 function! s:show_open_msg()
158 ruby << EOF
159         m = get_message
160         mbox = File.expand_path('~/.notmuch/vim_mbox')
161         cmd = VIM::evaluate('g:notmuch_reader') % mbox
162         system "notmuch show --format=mbox id:#{m.message_id} > #{mbox} && #{cmd}"
163 EOF
164 endfunction
165
166 function! s:show_save_msg()
167         let file = input('File name: ')
168 ruby << EOF
169         file = VIM::evaluate('file')
170         m = get_message
171         system "notmuch show --format=mbox id:#{m.message_id} > #{file}"
172 EOF
173 endfunction
174
175 function! s:show_save_patches()
176 ruby << EOF
177         q = $curbuf.query($cur_thread)
178         t = q.search_threads.first
179         n = 0
180         t.toplevel_messages.first.replies.each do |m|
181                 next if not m['subject'] =~ /^\[PATCH.*\]/
182                 file = "%04d.patch" % [n += 1]
183                 system "notmuch show --format=mbox id:#{m.message_id} > #{file}"
184         end
185         vim_puts "Saved #{n} patches"
186 EOF
187 endfunction
188
189 function! s:show_tag(intags)
190         if empty(a:intags)
191                 let tags = input('tags: ')
192         else
193                 let tags = a:intags
194         endif
195         ruby do_tag(get_cur_view, VIM::evaluate('l:tags'))
196         call s:show_next_thread()
197 endfunction
198
199 function! s:search_search_prompt()
200         let text = input('Search: ')
201         if text == ""
202           return
203         endif
204         setlocal modifiable
205 ruby << EOF
206         $cur_search = VIM::evaluate('text')
207         $curbuf.reopen
208         search_render($cur_search)
209 EOF
210         setlocal nomodifiable
211 endfunction
212
213 function! s:search_info()
214         ruby vim_puts get_thread_id
215 endfunction
216
217 function! s:search_refresh()
218         setlocal modifiable
219         ruby $curbuf.reopen
220         ruby search_render($cur_search)
221         setlocal nomodifiable
222 endfunction
223
224 function! s:search_tag(intags)
225         if empty(a:intags)
226                 let tags = input('tags: ')
227         else
228                 let tags = a:intags
229         endif
230         ruby do_tag(get_thread_id, VIM::evaluate('l:tags'))
231         norm j
232 endfunction
233
234 function! s:folders_search_prompt()
235         let text = input('Search: ')
236         call s:search(text)
237 endfunction
238
239 function! s:folders_refresh()
240         setlocal modifiable
241         ruby $curbuf.reopen
242         ruby folders_render()
243         setlocal nomodifiable
244 endfunction
245
246 "" basic
247
248 function! s:show_cursor_moved()
249 ruby << EOF
250         if $render.is_ready?
251                 VIM::command('setlocal modifiable')
252                 $render.do_next
253                 VIM::command('setlocal nomodifiable')
254         end
255 EOF
256 endfunction
257
258 function! s:show_next_thread()
259         call s:kill_this_buffer()
260         if line('.') != line('$')
261                 norm j
262                 call s:search_show_thread(0)
263         else
264                 echo 'No more messages.'
265         endif
266 endfunction
267
268 function! s:kill_this_buffer()
269 ruby << EOF
270         $curbuf.close
271         VIM::command("bdelete!")
272 EOF
273 endfunction
274
275 function! s:set_map(maps)
276         nmapclear <buffer>
277         for [key, code] in items(a:maps)
278                 let cmd = printf(":call <SID>%s<CR>", code)
279                 exec printf('nnoremap <buffer> %s %s', key, cmd)
280         endfor
281 endfunction
282
283 function! s:new_buffer(type)
284         enew
285         setlocal buftype=nofile bufhidden=hide
286         keepjumps 0d
287         execute printf('set filetype=notmuch-%s', a:type)
288         execute printf('set syntax=notmuch-%s', a:type)
289         ruby $curbuf.init(VIM::evaluate('a:type'))
290 endfunction
291
292 function! s:set_menu_buffer()
293         setlocal nomodifiable
294         setlocal cursorline
295         setlocal nowrap
296 endfunction
297
298 "" main
299
300 function! s:show(thread_id)
301         call s:new_buffer('show')
302         setlocal modifiable
303 ruby << EOF
304         thread_id = VIM::evaluate('a:thread_id')
305         $cur_thread = thread_id
306         $messages.clear
307         $curbuf.render do |b|
308                 q = $curbuf.query(get_cur_view)
309                 q.sort = Notmuch::SORT_OLDEST_FIRST
310                 msgs = q.search_messages
311                 msgs.each do |msg|
312                         m = Mail.read(msg.filename)
313                         part = m.find_first_text
314                         nm_m = Message.new(msg, m)
315                         $messages << nm_m
316                         date_fmt = VIM::evaluate('g:notmuch_datetime_format')
317                         date = Time.at(msg.date).strftime(date_fmt)
318                         nm_m.start = b.count
319                         b << "%s %s (%s)" % [msg['from'], date, msg.tags]
320                         b << "Subject: %s" % [msg['subject']]
321                         b << "To: %s" % msg['to']
322                         b << "Cc: %s" % msg['cc']
323                         b << "Date: %s" % msg['date']
324                         nm_m.body_start = b.count
325                         b << "--- %s ---" % part.mime_type
326                         part.convert.each_line do |l|
327                                 b << l.chomp
328                         end
329                         b << ""
330                         nm_m.end = b.count
331                 end
332                 b.delete(b.count)
333         end
334         $messages.each_with_index do |msg, i|
335                 VIM::command("syntax region nmShowMsg#{i}Desc start='\\%%%il' end='\\%%%il' contains=@nmShowMsgDesc" % [msg.start, msg.start + 1])
336                 VIM::command("syntax region nmShowMsg#{i}Head start='\\%%%il' end='\\%%%il' contains=@nmShowMsgHead" % [msg.start + 1, msg.body_start])
337                 VIM::command("syntax region nmShowMsg#{i}Body start='\\%%%il' end='\\%%%dl' contains=@nmShowMsgBody" % [msg.body_start, msg.end])
338         end
339 EOF
340         setlocal nomodifiable
341         call s:set_map(g:notmuch_show_maps)
342 endfunction
343
344 function! s:search_show_thread(mode)
345 ruby << EOF
346         mode = VIM::evaluate('a:mode')
347         id = get_thread_id
348         case mode
349         when 0;
350         when 1; $cur_filter = nil
351         when 2; $cur_filter = $cur_search
352         end
353         VIM::command("call s:show('#{id}')")
354 EOF
355 endfunction
356
357 function! s:search(search)
358         call s:new_buffer('search')
359 ruby << EOF
360         $cur_search = VIM::evaluate('a:search')
361         search_render($cur_search)
362 EOF
363         call s:set_menu_buffer()
364         call s:set_map(g:notmuch_search_maps)
365         autocmd CursorMoved <buffer> call s:show_cursor_moved()
366 endfunction
367
368 function! s:folders_show_search()
369 ruby << EOF
370         n = $curbuf.line_number
371         s = $searches[n - 1]
372         VIM::command("call s:search('#{s}')")
373 EOF
374 endfunction
375
376 function! s:folders()
377         call s:new_buffer('folders')
378         ruby folders_render()
379         call s:set_menu_buffer()
380         call s:set_map(g:notmuch_folders_maps)
381 endfunction
382
383 "" root
384
385 function! s:set_defaults()
386         if !exists('g:notmuch_date_format')
387                 if exists('g:notmuch_rb_date_format')
388                         let g:notmuch_date_format = g:notmuch_rb_date_format
389                 else
390                         let g:notmuch_date_format = s:notmuch_date_format_default
391                 endif
392         endif
393
394         if !exists('g:notmuch_datetime_format')
395                 if exists('g:notmuch_rb_datetime_format')
396                         let g:notmuch_datetime_format = g:notmuch_rb_datetime_format
397                 else
398                         let g:notmuch_datetime_format = s:notmuch_datetime_format_default
399                 endif
400         endif
401
402         if !exists('g:notmuch_reader')
403                 if exists('g:notmuch_rb_reader')
404                         let g:notmuch_reader = g:notmuch_rb_reader
405                 else
406                         let g:notmuch_reader = s:notmuch_reader_default
407                 endif
408         endif
409
410         if !exists('g:notmuch_sendmail')
411                 if exists('g:notmuch_rb_sendmail')
412                         let g:notmuch_sendmail = g:notmuch_rb_sendmail
413                 else
414                         let g:notmuch_sendmail = s:notmuch_sendmail_default
415                 endif
416         endif
417
418         if !exists('g:notmuch_folders_count_threads')
419                 if exists('g:notmuch_rb_count_threads')
420                         let g:notmuch_count_threads = g:notmuch_rb_count_threads
421                 else
422                         let g:notmuch_folders_count_threads = s:notmuch_folders_count_threads_default
423                 endif
424         endif
425
426         if !exists('g:notmuch_custom_search_maps') && exists('g:notmuch_rb_custom_search_maps')
427                 let g:notmuch_custom_search_maps = g:notmuch_rb_custom_search_maps
428         endif
429
430         if !exists('g:notmuch_custom_show_maps') && exists('g:notmuch_rb_custom_show_maps')
431                 let g:notmuch_custom_show_maps = g:notmuch_rb_custom_show_maps
432         endif
433
434         if exists('g:notmuch_custom_search_maps')
435                 call extend(g:notmuch_search_maps, g:notmuch_custom_search_maps)
436         endif
437
438         if exists('g:notmuch_custom_show_maps')
439                 call extend(g:notmuch_show_maps, g:notmuch_custom_show_maps)
440         endif
441
442         if !exists('g:notmuch_folders')
443                 if exists('g:notmuch_rb_folders')
444                         let g:notmuch_folders = g:notmuch_rb_folders
445                 else
446                         let g:notmuch_folders = s:notmuch_folders_default
447                 endif
448         endif
449 endfunction
450
451 function! s:NotMuch(...)
452         call s:set_defaults()
453
454 ruby << EOF
455         require 'notmuch'
456         require 'rubygems'
457         require 'tempfile'
458         require 'socket'
459         begin
460                 require 'mail'
461         rescue LoadError
462         end
463
464         $db_name = nil
465         $email = $email_name = $email_address = nil
466         $searches = []
467         $threads = []
468         $messages = []
469         $config = {}
470         $mail_installed = defined?(Mail)
471
472         def get_config
473                 group = nil
474                 config = ENV['NOTMUCH_CONFIG'] || '~/.notmuch-config'
475                 File.open(File.expand_path(config)).each do |l|
476                         l.chomp!
477                         case l
478                         when /^\[(.*)\]$/
479                                 group = $1
480                         when ''
481                         when /^(.*)=(.*)$/
482                                 key = "%s.%s" % [group, $1]
483                                 value = $2
484                                 $config[key] = value
485                         end
486                 end
487
488                 $db_name = $config['database.path']
489                 $email_name = $config['user.name']
490                 $email_address = $config['user.primary_email']
491                 $email = "%s <%s>" % [$email_name, $email_address]
492         end
493
494         def vim_puts(s)
495                 VIM::command("echo '#{s.to_s}'")
496         end
497
498         def vim_p(s)
499                 VIM::command("echo '#{s.inspect}'")
500         end
501
502         def author_filter(a)
503                 # TODO email format, aliases
504                 a.strip!
505                 a.gsub!(/[\.@].*/, '')
506                 a.gsub!(/^ext /, '')
507                 a.gsub!(/ \(.*\)/, '')
508                 a
509         end
510
511         def get_thread_id
512                 n = $curbuf.line_number - 1
513                 return "thread:%s" % $threads[n]
514         end
515
516         def get_message
517                 n = $curbuf.line_number
518                 return $messages.find { |m| n >= m.start && n <= m.end }
519         end
520
521         def get_cur_view
522                 if $cur_filter
523                         return "#{$cur_thread} and (#{$cur_filter})"
524                 else
525                         return $cur_thread
526                 end
527         end
528
529         def generate_message_id
530                 t = Time.now
531                 random_tag = sprintf('%x%x_%x%x%x',
532                         t.to_i, t.tv_usec,
533                         $$, Thread.current.object_id.abs, rand(255))
534                 return "<#{random_tag}@#{Socket.gethostname}.notmuch>"
535         end
536
537         def open_compose_helper(lines, cur)
538                 help_lines = [
539                         'Notmuch-Help: Type in your message here; to help you use these bindings:',
540                         'Notmuch-Help:   ,s    - send the message (Notmuch-Help lines will be removed)',
541                         'Notmuch-Help:   ,q    - abort the message',
542                         ]
543
544                 dir = File.expand_path('~/.notmuch/compose')
545                 FileUtils.mkdir_p(dir)
546                 Tempfile.open(['nm-', '.mail'], dir) do |f|
547                         f.puts(help_lines)
548                         f.puts
549                         f.puts(lines)
550
551                         sig_file = File.expand_path('~/.signature')
552                         if File.exists?(sig_file)
553                                 f.puts("-- ")
554                                 f.write(File.read(sig_file))
555                         end
556
557                         f.flush
558
559                         cur += help_lines.size + 1
560
561                         VIM::command("let s:reply_from='%s'" % $email_address)
562                         VIM::command("call s:new_file_buffer('compose', '#{f.path}')")
563                         VIM::command("call cursor(#{cur}, 0)")
564                 end
565         end
566
567         def open_reply(orig)
568                 reply = orig.reply do |m|
569                         # fix headers
570                         if not m[:reply_to]
571                                 m.to = [orig[:from].to_s, orig[:to].to_s]
572                         end
573                         m.cc = orig[:cc]
574                         m.from = $email
575                         m.message_id = generate_message_id
576                         m.charset = 'utf-8'
577                         m.content_transfer_encoding = '7bit'
578                 end
579
580                 lines = []
581
582                 body_lines = []
583                 if $mail_installed
584                         addr = Mail::Address.new(orig[:from].value)
585                         name = addr.name
586                         name = addr.local + "@" if name.nil? && !addr.local.nil?
587                 else
588                         name = orig[:from]
589                 end
590                 name = "somebody" if name.nil?
591
592                 body_lines << "%s wrote:" % name
593                 part = orig.find_first_text
594                 part.convert.each_line do |l|
595                         body_lines << "> %s" % l.chomp
596                 end
597                 body_lines << ""
598                 body_lines << ""
599                 body_lines << ""
600
601                 reply.body = body_lines.join("\n")
602
603                 lines += reply.to_s.lines.map { |e| e.chomp }
604                 lines << ""
605
606                 cur = lines.count - 1
607
608                 open_compose_helper(lines, cur)
609         end
610
611         def open_compose()
612                 lines = []
613
614                 lines << "Date: #{Time.now().strftime('%a, %-d %b %Y %T %z')}"
615                 lines << "From: #{$email}"
616                 lines << "To: "
617                 cur = lines.count
618
619                 lines << "Cc: "
620                 lines << "Bcc: "
621                 lines << "Message-Id: #{generate_message_id}"
622                 lines << "Subject: "
623                 lines << "Mime-Version: 1.0"
624                 lines << "Content-Type: text/plain; charset=utf-8"
625                 lines << "Content-Transfer-Encoding: 7bit"
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                 end
932         end
933
934         class String
935                 def to_utf8
936                         RUBY_VERSION >= "1.9" ? force_encoding('utf-8') : self
937                 end
938         end
939
940         get_config
941 EOF
942         if a:0
943           call s:search(join(a:000))
944         else
945           call s:folders()
946         endif
947 endfunction
948
949 command -nargs=* NotMuch call s:NotMuch(<f-args>)
950
951 " vim: set noexpandtab: