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