]> git.cworth.org Git - notmuch/blob - vim/notmuch.vim
Vim: Ensure that every queries respect excluded tags
[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                                 $searches << search
644                                 count = count_threads ? q.count_threads : q.count_messages
645                                 b << "%9d %-20s (%s)" % [count, name, search]
646                         end
647                 end
648         end
649
650         def search_render(search)
651                 date_fmt = VIM::evaluate('g:notmuch_date_format')
652                 q = $curbuf.query(search)
653                 q.sort = Notmuch::SORT_NEWEST_FIRST
654                 $threads.clear
655                 t = q.search_threads
656
657                 $render = $curbuf.render_staged(t) do |b, items|
658                         items.each do |e|
659                                 authors = e.authors.to_utf8.split(/[,|]/).map { |a| author_filter(a) }.join(",")
660                                 date = Time.at(e.newest_date).strftime(date_fmt)
661                                 subject = e.messages.first['subject']
662                                 if $mail_installed
663                                         subject = Mail::Field.parse("Subject: " + subject).to_s
664                                 else
665                                         subject = subject.force_encoding('utf-8')
666                                 end
667                                 b << "%-12s %3s %-20.20s | %s (%s)" % [date, e.matched_messages, authors, subject, e.tags]
668                                 $threads << e.thread_id
669                         end
670                 end
671         end
672
673         def do_tag(filter, tags)
674                 $curbuf.do_write do |db|
675                         q = db.query(filter)
676                         q.search_messages.each do |e|
677                                 e.freeze
678                                 tags.split.each do |t|
679                                         case t
680                                         when /^-(.*)/
681                                                 e.remove_tag($1)
682                                         when /^\+(.*)/
683                                                 e.add_tag($1)
684                                         when /^([^\+^-].*)/
685                                                 e.add_tag($1)
686                                         end
687                                 end
688                                 e.thaw
689                                 e.tags_to_maildir_flags
690                         end
691                         q.destroy!
692                 end
693         end
694
695         module DbHelper
696                 def init(name)
697                         @name = name
698                         @db = Notmuch::Database.new($db_name)
699                         @queries = []
700                 end
701
702                 def query(*args)
703                         q = @db.query(*args)
704                         @queries << q
705                         $exclude_tags.each { |t|
706                             q.add_tag_exclude(t)
707                         }
708                         q
709                 end
710
711                 def close
712                         @queries.delete_if { |q| ! q.destroy! }
713                         @db.close
714                 end
715
716                 def reopen
717                         close if @db
718                         @db = Notmuch::Database.new($db_name)
719                 end
720
721                 def do_write
722                         db = Notmuch::Database.new($db_name, :mode => Notmuch::MODE_READ_WRITE)
723                         begin
724                                 yield db
725                         ensure
726                                 db.close
727                         end
728                 end
729         end
730
731         class Message
732                 attr_accessor :start, :body_start, :end
733                 attr_reader :message_id, :filename, :mail
734
735                 def initialize(msg, mail)
736                         @message_id = msg.message_id
737                         @filename = msg.filename
738                         @mail = mail
739                         @start = 0
740                         @end = 0
741                         mail.import_headers(msg) if not $mail_installed
742                 end
743
744                 def to_s
745                         "id:%s" % @message_id
746                 end
747
748                 def inspect
749                         "id:%s, file:%s" % [@message_id, @filename]
750                 end
751         end
752
753         class StagedRender
754                 def initialize(buffer, enumerable, block)
755                         @b = buffer
756                         @enumerable = enumerable
757                         @block = block
758                         @last_render = 0
759
760                         @b.render { do_next }
761                 end
762
763                 def is_ready?
764                         @last_render - @b.line_number <= $curwin.height
765                 end
766
767                 def do_next
768                         items = @enumerable.take($curwin.height * 2)
769                         return if items.empty?
770                         @block.call @b, items
771                         @last_render = @b.count
772                 end
773         end
774
775         class VIM::Buffer
776                 include DbHelper
777
778                 def <<(a)
779                         append(count(), a)
780                 end
781
782                 def render_staged(enumerable, &block)
783                         StagedRender.new(self, enumerable, block)
784                 end
785
786                 def render
787                         old_count = count
788                         yield self
789                         (1..old_count).each do
790                                 delete(1)
791                         end
792                 end
793         end
794
795         class Notmuch::Tags
796                 def to_s
797                         to_a.join(" ")
798                 end
799         end
800
801         class Notmuch::Message
802                 def to_s
803                         "id:%s" % message_id
804                 end
805         end
806
807         # workaround for bug in vim's ruby
808         class Object
809                 def flush
810                 end
811         end
812
813         module SimpleMessage
814                 class Header < Array
815                         def self.parse(string)
816                                 return nil if string.empty?
817                                 return Header.new(string.split(/,\s+/))
818                         end
819
820                         def to_s
821                                 self.join(', ')
822                         end
823                 end
824
825                 def initialize(string = nil)
826                         @raw_source = string
827                         @body = nil
828                         @headers = {}
829
830                         return if not string
831
832                         if string =~ /(.*?(\r\n|\n))\2/m
833                                 head, body = $1, $' || '', $2
834                         else
835                                 head, body = string, ''
836                         end
837                         @body = body
838                 end
839
840                 def [](name)
841                         @headers[name.to_sym]
842                 end
843
844                 def []=(name, value)
845                         @headers[name.to_sym] = value
846                 end
847
848                 def format_header(value)
849                         value.to_s.tr('_', '-').gsub(/(\w+)/) { $1.capitalize }
850                 end
851
852                 def to_s
853                         buffer = ''
854                         @headers.each do |key, value|
855                                 buffer << "%s: %s\r\n" %
856                                         [format_header(key), value]
857                         end
858                         buffer << "\r\n"
859                         buffer << @body
860                         buffer
861                 end
862
863                 def body=(value)
864                         @body = value
865                 end
866
867                 def from
868                         @headers[:from]
869                 end
870
871                 def decoded
872                         @body
873                 end
874
875                 def mime_type
876                         'text/plain'
877                 end
878
879                 def multipart?
880                         false
881                 end
882
883                 def reply
884                         r = Mail::Message.new
885                         r[:from] = self[:to]
886                         r[:to] = self[:from]
887                         r[:cc] = self[:cc]
888                         r[:in_reply_to] = self[:message_id]
889                         r[:references] = self[:references]
890                         r
891                 end
892
893                 HEADERS = [ :from, :to, :cc, :references, :in_reply_to, :reply_to, :message_id ]
894
895                 def import_headers(m)
896                         HEADERS.each do |e|
897                                 dashed = format_header(e)
898                                 @headers[e] = Header.parse(m[dashed])
899                         end
900                 end
901         end
902
903         module Mail
904
905                 if not $mail_installed
906                         puts "WARNING: Install the 'mail' gem, without it support is limited"
907
908                         def self.read(filename)
909                                 Message.new(File.open(filename, 'rb') { |f| f.read })
910                         end
911
912                         class Message
913                                 include SimpleMessage
914                         end
915                 end
916
917                 class Message
918
919                         def find_first_text
920                                 return self if not multipart?
921                                 return text_part || html_part
922                         end
923
924                         def convert
925                                 if mime_type != "text/html"
926                                         text = decoded
927                                 else
928                                         IO.popen(VIM::evaluate('exists("g:notmuch_html_converter") ? ' +
929                                                         'g:notmuch_html_converter : "elinks --dump"'), "w+") do |pipe|
930                                                 pipe.write(decode_body)
931                                                 pipe.close_write
932                                                 text = pipe.read
933                                         end
934                                 end
935                                 text
936                         end
937
938                         def present
939                                 buffer = ''
940                                 header.fields.each do |f|
941                                         buffer << "%s: %s\r\n" % [f.name, f.to_s]
942                                 end
943                                 buffer << "\r\n"
944                                 buffer << body.to_s
945                                 buffer
946                         end
947                 end
948         end
949
950         class String
951                 def to_utf8
952                         RUBY_VERSION >= "1.9" ? force_encoding('utf-8') : self
953                 end
954         end
955
956         get_config
957 EOF
958         if a:0
959           call s:search(join(a:000))
960         else
961           call s:folders()
962         endif
963 endfunction
964
965 command -nargs=* NotMuch call s:NotMuch(<f-args>)
966
967 " vim: set noexpandtab: