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