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