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