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