71d4b321de58490bce0e8e3ad2c601832498b2cb
[notmuch] / vim / notmuch.vim
1 if exists("g:loaded_notmuch")
2         finish
3 endif
4
5 if !has("ruby") || version < 700
6         finish
7 endif
8
9 let g:loaded_notmuch = "yep"
10
11 let g:notmuch_folders_maps = {
12         \ '<Enter>':    'folders_show_search()',
13         \ 's':          'folders_search_prompt()',
14         \ '=':          'folders_refresh()',
15         \ 'c':          'compose()',
16         \ }
17
18 let g:notmuch_search_maps = {
19         \ 'q':          'kill_this_buffer()',
20         \ '<Enter>':    'search_show_thread(1)',
21         \ '<Space>':    'search_show_thread(2)',
22         \ 'A':          'search_tag("-inbox -unread")',
23         \ 'I':          'search_tag("-unread")',
24         \ 't':          'search_tag("")',
25         \ 's':          'search_search_prompt()',
26         \ '=':          'search_refresh()',
27         \ '?':          'search_info()',
28         \ 'c':          'compose()',
29         \ }
30
31 let g:notmuch_show_maps = {
32         \ 'q':          'kill_this_buffer()',
33         \ 'A':          'show_tag("-inbox -unread")',
34         \ 'I':          'show_tag("-unread")',
35         \ 't':          'show_tag("")',
36         \ 'o':          'show_open_msg()',
37         \ 'e':          'show_extract_msg()',
38         \ 's':          'show_save_msg()',
39         \ 'p':          'show_save_patches()',
40         \ 'r':          'show_reply()',
41         \ '?':          'show_info()',
42         \ '<Tab>':      'show_next_msg()',
43         \ 'c':          'compose()',
44         \ }
45
46 let g:notmuch_compose_maps = {
47         \ ',s':         'compose_send()',
48         \ ',q':         'compose_quit()',
49         \ }
50
51 let s:notmuch_folders_default = [
52         \ [ 'new', 'tag:inbox and tag:unread' ],
53         \ [ 'inbox', 'tag:inbox' ],
54         \ [ 'unread', 'tag:unread' ],
55         \ ]
56
57 let s:notmuch_date_format_default = '%d.%m.%y'
58 let s:notmuch_datetime_format_default = '%d.%m.%y %H:%M:%S'
59 let s:notmuch_reader_default = 'mutt -f %s'
60 let s:notmuch_sendmail_default = 'sendmail'
61 let s:notmuch_folders_count_threads_default = 0
62
63 function! s:new_file_buffer(type, fname)
64         exec printf('edit %s', a:fname)
65         execute printf('set filetype=notmuch-%s', a:type)
66         execute printf('set syntax=notmuch-%s', a:type)
67         ruby $curbuf.init(VIM::evaluate('a:type'))
68         ruby $buf_queue.push($curbuf.number)
69 endfunction
70
71 function! s:compose_unload()
72         if b:compose_done
73                 return
74         endif
75         if input('[s]end/[q]uit? ') =~ '^s'
76                 call s:compose_send()
77         endif
78 endfunction
79
80 "" actions
81
82 function! s:compose_quit()
83         let b:compose_done = 1
84         call s:kill_this_buffer()
85 endfunction
86
87 function! s:compose_send()
88         let b:compose_done = 1
89         let fname = expand('%')
90
91         " remove headers
92         0,4d
93         write
94
95         let cmdtxt = g:notmuch_sendmail . ' -t -f ' . s:reply_from . ' < ' . fname
96         let out = system(cmdtxt)
97         let err = v:shell_error
98         if err
99                 undo
100                 write
101                 echohl Error
102                 echo 'Eeek! unable to send mail'
103                 echo out
104                 echohl None
105                 return
106         endif
107         call delete(fname)
108         echo 'Mail sent successfully.'
109         call s:kill_this_buffer()
110 endfunction
111
112 function! s:show_next_msg()
113 ruby << EOF
114         r, c = $curwin.cursor
115         n = $curbuf.line_number
116         i = $messages.index { |m| n >= m.start && n <= m.end }
117         m = $messages[i + 1]
118         if m
119                 r = m.body_start + 1
120                 VIM::command("normal #{m.start}zt")
121                 $curwin.cursor = r, c
122         end
123 EOF
124 endfunction
125
126 function! s:show_reply()
127         ruby open_reply get_message.mail
128         let b:compose_done = 0
129         call s:set_map(g:notmuch_compose_maps)
130         autocmd BufUnload <buffer> call s:compose_unload()
131         startinsert!
132 endfunction
133
134 function! s:compose()
135         ruby open_compose
136         let b:compose_done = 0
137         call s:set_map(g:notmuch_compose_maps)
138         autocmd BufUnload <buffer> call s:compose_unload()
139         startinsert!
140 endfunction
141
142 function! s:show_info()
143         ruby vim_puts get_message.inspect
144 endfunction
145
146 function! s:show_extract_msg()
147 ruby << EOF
148         m = get_message
149         m.mail.attachments.each do |a|
150                 File.open(a.filename, 'w') do |f|
151                         f.write a.body.decoded
152                         print "Extracted '#{a.filename}'"
153                 end
154         end
155 EOF
156 endfunction
157
158 function! s:show_open_msg()
159 ruby << EOF
160         m = get_message
161         mbox = File.expand_path('~/.notmuch/vim_mbox')
162         cmd = VIM::evaluate('g:notmuch_reader') % mbox
163         system "notmuch show --format=mbox id:#{m.message_id} > #{mbox} && #{cmd}"
164 EOF
165 endfunction
166
167 function! s:show_save_msg()
168         let file = input('File name: ')
169 ruby << EOF
170         file = VIM::evaluate('file')
171         m = get_message
172         system "notmuch show --format=mbox id:#{m.message_id} > #{file}"
173 EOF
174 endfunction
175
176 function! s:show_save_patches()
177 ruby << EOF
178         q = $curbuf.query($cur_thread)
179         t = q.search_threads.first
180         n = 0
181         t.toplevel_messages.first.replies.each do |m|
182                 next if not m['subject'] =~ /^\[PATCH.*\]/
183                 file = "%04d.patch" % [n += 1]
184                 system "notmuch show --format=mbox id:#{m.message_id} > #{file}"
185         end
186         vim_puts "Saved #{n} patches"
187 EOF
188 endfunction
189
190 function! s:show_tag(intags)
191         if empty(a:intags)
192                 let tags = input('tags: ')
193         else
194                 let tags = a:intags
195         endif
196         ruby do_tag(get_cur_view, VIM::evaluate('l:tags'))
197         call s:show_next_thread()
198 endfunction
199
200 function! s:search_search_prompt()
201         let text = input('Search: ')
202         if text == ""
203           return
204         endif
205         setlocal modifiable
206 ruby << EOF
207         $cur_search = VIM::evaluate('text')
208         $curbuf.reopen
209         search_render($cur_search)
210 EOF
211         setlocal nomodifiable
212 endfunction
213
214 function! s:search_info()
215         ruby vim_puts get_thread_id
216 endfunction
217
218 function! s:search_refresh()
219         setlocal modifiable
220         ruby $curbuf.reopen
221         ruby search_render($cur_search)
222         setlocal nomodifiable
223 endfunction
224
225 function! s:search_tag(intags)
226         if empty(a:intags)
227                 let tags = input('tags: ')
228         else
229                 let tags = a:intags
230         endif
231         ruby do_tag(get_thread_id, VIM::evaluate('l:tags'))
232         norm j
233 endfunction
234
235 function! s:folders_search_prompt()
236         let text = input('Search: ')
237         call s:search(text)
238 endfunction
239
240 function! s:folders_refresh()
241         setlocal modifiable
242         ruby $curbuf.reopen
243         ruby folders_render()
244         setlocal nomodifiable
245 endfunction
246
247 "" basic
248
249 function! s:show_cursor_moved()
250 ruby << EOF
251         if $render.is_ready?
252                 VIM::command('setlocal modifiable')
253                 $render.do_next
254                 VIM::command('setlocal nomodifiable')
255         end
256 EOF
257 endfunction
258
259 function! s:show_next_thread()
260         call s:kill_this_buffer()
261         if line('.') != line('$')
262                 norm j
263                 call s:search_show_thread(0)
264         else
265                 echo 'No more messages.'
266         endif
267 endfunction
268
269 function! s:kill_this_buffer()
270 ruby << EOF
271         if $buf_queue.size > 1
272                 $curbuf.close
273                 VIM::command("bdelete!")
274                 $buf_queue.pop
275                 b = $buf_queue.last
276                 VIM::command("buffer #{b}") if b
277         end
278 EOF
279 endfunction
280
281 function! s:set_map(maps)
282         nmapclear <buffer>
283         for [key, code] in items(a:maps)
284                 let cmd = printf(":call <SID>%s<CR>", code)
285                 exec printf('nnoremap <buffer> %s %s', key, cmd)
286         endfor
287 endfunction
288
289 function! s:new_buffer(type)
290         enew
291         setlocal buftype=nofile bufhidden=hide
292         keepjumps 0d
293         execute printf('set filetype=notmuch-%s', a:type)
294         execute printf('set syntax=notmuch-%s', a:type)
295         ruby $curbuf.init(VIM::evaluate('a:type'))
296         ruby $buf_queue.push($curbuf.number)
297 endfunction
298
299 function! s:set_menu_buffer()
300         setlocal nomodifiable
301         setlocal cursorline
302         setlocal nowrap
303 endfunction
304
305 "" main
306
307 function! s:show(thread_id)
308         call s:new_buffer('show')
309         setlocal modifiable
310 ruby << EOF
311         thread_id = VIM::evaluate('a:thread_id')
312         $cur_thread = thread_id
313         $messages.clear
314         $curbuf.render do |b|
315                 q = $curbuf.query(get_cur_view)
316                 q.sort = Notmuch::SORT_OLDEST_FIRST
317                 msgs = q.search_messages
318                 msgs.each do |msg|
319                         m = Mail.read(msg.filename)
320                         part = m.find_first_text
321                         nm_m = Message.new(msg, m)
322                         $messages << nm_m
323                         date_fmt = VIM::evaluate('g:notmuch_datetime_format')
324                         date = Time.at(msg.date).strftime(date_fmt)
325                         nm_m.start = b.count
326                         b << "%s %s (%s)" % [msg['from'], date, msg.tags]
327                         b << "Subject: %s" % [msg['subject']]
328                         b << "To: %s" % msg['to']
329                         b << "Cc: %s" % msg['cc']
330                         b << "Date: %s" % msg['date']
331                         nm_m.body_start = b.count
332                         b << "--- %s ---" % part.mime_type
333                         part.convert.each_line do |l|
334                                 b << l.chomp
335                         end
336                         b << ""
337                         nm_m.end = b.count
338                 end
339                 b.delete(b.count)
340         end
341         $messages.each_with_index do |msg, i|
342                 VIM::command("syntax region nmShowMsg#{i}Desc start='\\%%%il' end='\\%%%il' contains=@nmShowMsgDesc" % [msg.start, msg.start + 1])
343                 VIM::command("syntax region nmShowMsg#{i}Head start='\\%%%il' end='\\%%%il' contains=@nmShowMsgHead" % [msg.start + 1, msg.body_start])
344                 VIM::command("syntax region nmShowMsg#{i}Body start='\\%%%il' end='\\%%%dl' contains=@nmShowMsgBody" % [msg.body_start, msg.end])
345         end
346 EOF
347         setlocal nomodifiable
348         call s:set_map(g:notmuch_show_maps)
349 endfunction
350
351 function! s:search_show_thread(mode)
352 ruby << EOF
353         mode = VIM::evaluate('a:mode')
354         id = get_thread_id
355         case mode
356         when 0;
357         when 1; $cur_filter = nil
358         when 2; $cur_filter = $cur_search
359         end
360         VIM::command("call s:show('#{id}')")
361 EOF
362 endfunction
363
364 function! s:search(search)
365         call s:new_buffer('search')
366 ruby << EOF
367         $cur_search = VIM::evaluate('a:search')
368         search_render($cur_search)
369 EOF
370         call s:set_menu_buffer()
371         call s:set_map(g:notmuch_search_maps)
372         autocmd CursorMoved <buffer> call s:show_cursor_moved()
373 endfunction
374
375 function! s:folders_show_search()
376 ruby << EOF
377         n = $curbuf.line_number
378         s = $searches[n - 1]
379         VIM::command("call s:search('#{s}')")
380 EOF
381 endfunction
382
383 function! s:folders()
384         call s:new_buffer('folders')
385         ruby folders_render()
386         call s:set_menu_buffer()
387         call s:set_map(g:notmuch_folders_maps)
388 endfunction
389
390 "" root
391
392 function! s:set_defaults()
393         if !exists('g:notmuch_date_format')
394                 let g:notmuch_date_format = s:notmuch_date_format_default
395         endif
396
397         if !exists('g:notmuch_datetime_format')
398                 let g:notmuch_datetime_format = s:notmuch_datetime_format_default
399         endif
400
401         if !exists('g:notmuch_reader')
402                 let g:notmuch_reader = s:notmuch_reader_default
403         endif
404
405         if !exists('g:notmuch_sendmail')
406                 let g:notmuch_sendmail = s:notmuch_sendmail_default
407         endif
408
409         if !exists('g:notmuch_folders_count_threads')
410                 let g:notmuch_folders_count_threads = s:notmuch_folders_count_threads_default
411         endif
412
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: