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