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