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