4 " To install place in ~/.vim/after/ftplugin/mail.vim
7 " Email: freesoftware@4321.tv
9 " This file was modified from Cedric Duval's version.
10 " http://cedricduval.free.fr/download/vimrc/mail
12 " Only do this when not done yet for this buffer
13 if exists("b:did_mail_after_ftplugin")
16 let b:did_mail_after_ftplugin = 1
18 if !exists ("mail_alias_program")
19 let mail_alias_program="Abook"
22 " ====================================================================
24 " ====================================================================
26 if !exists ("mail_quote_chars")
27 let s:quote_chars = ':!|>'
29 let s:quote_chars = mail_quote_chars
32 " This re defines a 'quote'
33 let s:quote_re = '\(\s\?\w*[' . s:quote_chars . ']\)'
34 " \s\? => 0 or one whitespace char
36 " spaces in the quote,
39 " \w* => maybe word chars (b/c
40 " some ppl put initals in
42 " the rest => actual quote chars
43 " \( \) => this is a quote "level"
45 " This re defines the quoting level at the *beginning* of a line
46 let s:quote_start = '^' . s:quote_re . s:quote_re . '*'
47 " ^s:quote_re => quote at beginning of
49 " s:quote_re* => perhaps followed by
53 " let b:quote_chars = s:quote_chars
54 " let b:quote_re = s:quote_re
55 " let b:quote_start = s:quote_start
57 " ====================================================================
59 " ====================================================================
61 if !exists("no_plugin_maps") && !exists("no_mail_maps")
64 " get alias list mappings
66 if !hasmapto('<Plug>MailAliasList', 'n')
67 nmap <buffer> <unique> <LocalLeader>al <Plug>MailAliasList
69 if !hasmapto('<Plug>MailAliasList', 'i')
70 imap <buffer> <unique> <LocalLeader>al <Plug>MailAliasList
73 nnoremap <buffer> <unique> <script> <Plug>MailAliasList <SID>AliasList
74 inoremap <buffer> <unique> <script> <Plug>MailAliasList <SID>AliasList
76 " Redraw is there b/c my screen was messed up after abook finished.
77 " The 'set paste' is in the function b/c I couldn't figure out how to put it in
79 " The 'set nopaste' is in the mapping b/c it didn't work for me in the script.
80 nnoremap <buffer> <SID>AliasList A<c-r>=<SID>AliasList{mail_alias_program}()<cr><c-o>:set nopaste<cr><c-o>:redraw!<cr><c-o>:echo b:AliasListMsg<cr><esc>
81 inoremap <buffer> <SID>AliasList <c-r>=<SID>AliasList{mail_alias_program}()<cr><c-o>:set nopaste<cr><c-o>:redraw!<cr><c-o>:echo b:AliasListMsg<cr>
84 " get alias query mappings
86 if !hasmapto('<Plug>MailAliasQuery', 'n')
87 nmap <buffer> <unique> <LocalLeader>aq <Plug>MailAliasQuery
89 if !hasmapto('<Plug>MailAliasQuery', 'i')
90 imap <buffer> <unique> <LocalLeader>aq <Plug>MailAliasQuery
93 nnoremap <buffer> <unique> <script> <Plug>MailAliasQuery <SID>AliasQuery
94 inoremap <buffer> <unique> <script> <Plug>MailAliasQuery <SID>AliasQuery
96 nnoremap <buffer> <SID>AliasQuery :call <SID>AliasQuery{mail_alias_program}()<cr>:echo b:AliasQueryMsg<cr>
97 inoremap <buffer> <SID>AliasQuery <c-o>:call <SID>AliasQuery{mail_alias_program}()<cr><c-o>:echo b:AliasQueryMsg<cr><right>
100 " mail formatting mappings
103 " * <F1> to re-format a quotelvl
104 " * <F2> to format a line which is too long, and go to the next line
105 " * <F3> to merge the previous line with the current one, with a correct
106 " formatting (sometimes useful associated with <F2>)
107 " * <F4> to re-format the current paragraph correctly
109 if !hasmapto('<Plug>MailFormatQuoteLvl', 'n')
110 nmap <buffer> <unique> <F1> <Plug>MailFormatQuoteLvl
112 if !hasmapto('<Plug>MailFormatLine', 'n')
113 nmap <buffer> <unique> <F2> <Plug>MailFormatLine
115 if !hasmapto('<Plug>MailFormatMerge', 'n')
116 nmap <buffer> <unique> <F3> <Plug>MailFormatMerge
118 if !hasmapto('<Plug>MailFormatParagraph', 'n')
119 nmap <buffer> <unique> <F4> <Plug>MailFormatParagraph
122 if !hasmapto('<Plug>MailFormatQuoteLvl', 'i')
123 imap <buffer> <unique> <F1> <Plug>MailFormatQuoteLvl
125 if !hasmapto('<Plug>MailFormatLine', 'i')
126 imap <buffer> <unique> <F2> <Plug>MailFormatLine
128 if !hasmapto('<Plug>MailFormatMerge', 'i')
129 imap <buffer> <unique> <F3> <Plug>MailFormatMerge
131 if !hasmapto('<Plug>MailFormatParagraph', 'i')
132 imap <buffer> <unique> <F4> <Plug>MailFormatParagraph
135 nnoremap <buffer> <unique> <script> <Plug>MailFormatQuoteLvl <SID>FormatQuoteLvl
136 nnoremap <buffer> <unique> <script> <Plug>MailFormatLine <SID>FormatLine
137 nnoremap <buffer> <unique> <script> <Plug>MailFormatMerge <SID>FormatMerge
138 nnoremap <buffer> <unique> <script> <Plug>MailFormatParagraph <SID>FormatParagraph
139 inoremap <buffer> <unique> <script> <Plug>MailFormatQuoteLvl <SID>FormatQuoteLvl
140 inoremap <buffer> <unique> <script> <Plug>MailFormatLine <SID>FormatLine
141 inoremap <buffer> <unique> <script> <Plug>MailFormatMerge <SID>FormatMerge
142 inoremap <buffer> <unique> <script> <Plug>MailFormatParagraph <SID>FormatParagraph
144 nnoremap <buffer> <script> <SID>FormatQuoteLvl gq<SID>QuoteLvlMotion
145 nnoremap <buffer> <SID>FormatLine gqqj
146 nnoremap <buffer> <SID>FormatMerge kgqj
147 nnoremap <buffer> <SID>FormatParagraph gqap
148 inoremap <buffer> <script> <SID>FormatQuoteLvl <ESC>gq<SID>QuoteLvlMotioni
149 inoremap <buffer> <SID>FormatLine <ESC>gqqji
150 inoremap <buffer> <SID>FormatMerge <ESC>kgqji
151 inoremap <buffer> <SID>FormatParagraph <ESC>gqapi
154 " sig removal mappings
156 if !hasmapto('<Plug>MailEraseQuotedSig', 'n')
157 nmap <silent> <buffer> <unique> <LocalLeader>eqs <Plug>MailEraseQuotedSig
159 nnoremap <buffer> <unique> <script> <Plug>MailEraseQuotedSig <SID>EraseQuotedSig
160 nnoremap <buffer> <SID>EraseQuotedSig :call<SID>EraseQuotedSig()<CR>
163 " Provide a motion operator for commands (so you can delete a quote
164 " segment, or format quoted segment)
166 if !hasmapto('<Plug>MailQuoteLvlMotion', 'o')
167 omap <silent> <buffer> <unique> q <Plug>MailQuoteLvlMotion
169 onoremap <buffer> <unique> <script> <Plug>MailQuoteLvlMotion <SID>QuoteLvlMotion
170 onoremap <buffer> <script> <SID>QuoteLvlMotion :execute "normal!" . <SID>QuoteLvlMotion(line("."))<cr>
174 " ====================================================================
175 " Mail Manipulation Functions
176 " ====================================================================
178 " --------------------------------------------------------------------
180 " --------------------------------------------------------------------
184 " This function will try and remove 'quoted' signatures.
186 " If someone responds with an email that doesn't use '>' as the
187 " quote character this will try and take care of that:
188 " | Yeah, I agree vim is cool.
193 " If there is a signature inside a 'multi-quoted' email this will try and get
195 " > | No, I don't agree with you.
197 " > Nonsense. You are wrong. Grow up.
199 " > | I can't believe I'm even replying to this.
201 " > | Some power user
203 " > Yeah, believe it, brother.
205 if !exists("*s:EraseQuotedSig")
206 function s:EraseQuotedSig()
207 while 0 != search((s:quote_start . '\s*--\s*$'), 'w')
208 let motion = s:QuoteLvlMotion(line("."))
209 exe "normal! d" . motion
216 " Replacing empty quoted lines (i.e. "> $") with empty lines
217 " (convenient to automatically reformat one paragraph)
219 if !exists("*s:DelEmptyQuoted")
220 function s:DelEmptyQuoted()
221 let empty_quote = s:quote_start . '\s*$'
223 " goto start of email and jump passed headers
225 if 0 == search('^$', 'W')
229 while 0 != search (empty_quote, 'W')
230 let newline = substitute(getline("."), empty_quote, '', '')
231 call setline(line("."), newline)
238 " This function will output a motion command that operatates over a "quote
239 " level" segment. This makes it possible to perform vi commands on quotes.
241 " dq => delete an entire quote section
242 " gqq => format an entire quote section
244 if !exists("*s:QuoteLvlMotion")
245 function s:QuoteLvlMotion(line)
246 let quote = matchstr(getline(a:line), s:quote_start)
247 " abort command if no quote
252 let len = s:LenQuoteLvl(a:line, quote)
254 " the 'V' makes the motion linewise
256 return "V" . line(".") . "G"
258 return "V" . (len - 1) . "j"
265 " This tries to figure out when the quoting level changes
267 if !exists("s:LenQuoteLvl")
268 function s:LenQuoteLvl(start, quote)
271 let quote = '^' . a:quote
275 " check if quote level decreased
276 if -1 == match(getline(i), quote)
280 " check if quote level increased
281 if -1 != match(getline(i), (quote . s:quote_re))
293 " --------------------------------------------------------------------
294 " Location Manipulator Functions
295 " --------------------------------------------------------------------
299 " Moves the cursor to a 'sensible' position.
301 if !exists("*s:CursorStart")
302 function s:CursorStart()
303 " put cursor in known position
306 if search('^From: $', 'W')
308 elseif search('^To: $', 'W')
310 elseif search('^Subject: $', 'W')
313 " check if we are editing a reply
314 elseif search('^On.*wrote:', 'W')
317 elseif search('^$', 'W')
324 " ================================================
325 " Process Mutt Aliases
326 " ================================================
328 " ------------------------------------------------
330 " ------------------------------------------------
334 " This function will launch abook and spit out what the user selected from the
335 " application (by pressing 'Q'). It's always called from 'insert' mode, so
336 " the text will be inserted like it was typed.
338 " That's why 'paste' is set and reset. So that the text that we insert won't
339 " be 'mangled' by the user's settings.
341 if !exists("*s:AliasListAbook")
342 function s:AliasListAbook()
343 let b:AliasListMsg = ""
347 silent exe '!abook 2> ' . f
348 exe 'let addresses=system("cat ' . f . '")'
350 let b:AliasListMsg = "Nothing found to lookup"
354 " - parses the output from abook
355 let addresses=s:ParseMuttQuery(addresses)
357 let b:AliasListMsg = b:ParseMuttQueryErr
361 " so that they will be aligned under the 'to' or 'cc' line
362 let addresses=substitute(addresses, "\n", ",\n ", "g")
368 " ------------------------------------------------
370 " ------------------------------------------------
374 " This function assumes that user has the cursor on an alias to lookup. Based
376 " - retrieves the alias(es) from abook
377 " - parses the output from abook
378 " - actually replaces the alias with the parsed output
380 if !exists("*s:AliasQueryAbook")
381 function s:AliasQueryAbook()
382 let b:AliasQueryMsg = ""
384 " - retrieves the alias(es) from abook
385 let lookup=expand("<cword>")
387 let b:AliasQueryMsg = "Nothing found to lookup"
391 silent exe 'let output=system("abook --mutt-query ' . lookup . '")'
393 let b:AliasQueryMsg = output
397 " - parses the output from abook
398 let replacement=s:ParseMuttQuery(output)
400 let b:AliasQueryMsg = b:ParseMuttQueryErr
404 " so that they will be aligned under the 'to' or 'cc' line
405 let replacement=substitute(replacement, "\n", ",\n ", "g")
407 " - actually replaces the alias with the parsed output
408 " paste is set/unset so that the email addresses aren't "mangled" by the
409 " user's formating options
411 exe "normal! ciw" . replacement . "\<Esc>"
416 " --------------------------------------------------------------------
418 " --------------------------------------------------------------------
422 " This function will take the output of a "mutt query" (as defined by the mutt
423 " documenation) and parses it.
425 " It returns the email addresses formatted as follows:
426 " - each address is on a line
428 if !exists("*s:ParseMuttQuery")
429 function s:ParseMuttQuery(aliases)
430 " remove first informational line
431 let aliases = substitute (a:aliases, "\n", "", "")
435 " whip off the name and address
436 let line = matchstr(aliases, ".\\{-}\n")
437 let address = matchstr(line, ".\\{-}\t")
438 let address = substitute(address, "\t", "", "g")
440 let b:ParseMuttQueryErr = "Unable to parse address from ouput"
444 let name = matchstr(line, "\t.*\t")
445 let name = substitute(name, "\t", "", "g")
447 let b:ParseMuttQueryErr = "Unable to parse name from ouput"
452 " echo "line: " . line . "|"
453 " echo "address: " . address . "|"
454 " echo "name: " . name . "|"
455 " let a=input("hit enter")
457 " make into valid email address
458 let needquote = match (name, '"')
460 let name = '"' . name . '" '
463 let needquote = match (address, '<')
465 let address = '<' . address . '>'
468 " add email address to list
469 let expansion = expansion . name
470 let expansion = expansion . address
473 " echo "address: " . address . "|"
474 " echo "name: " . name . "|"
475 " let a=input("hit enter")
477 " process next line (if there is one)
478 let aliases = substitute(aliases, ".\\{-}\n", "", "")
480 let b:ParseMuttQueryErr = ""
484 let expansion = expansion . "\n"
489 " ====================================================================
490 " Abbreviation Manipulation
491 " ====================================================================
495 " This will generate vi abbreviations from your mutt alias file.
498 " However, remember that the abbreviation will be replaced *everywhere*. For
499 " example, if you have the alias 'Mary', then if you try and type "Hi, Mary
500 " vim is cool", then it won't work. This is because the 'Mary' will be
501 " expanded as an alias.
503 if !exists("*s:MakeAliasAbbrev")
504 function s:MakeAliasAbbrev()
505 let aliasfile = tempname()
506 silent exe "!sed -e 's/alias/iab/' ~/.mutt/aliases > " . aliasfile
507 exe "source " . aliasfile
512 " ====================================================================
514 " ====================================================================
516 if exists ("mail_erase_quoted_sig")
517 call s:EraseQuotedSig()
520 if exists ("mail_delete_empty_quoted")
521 call s:DelEmptyQuoted()
524 if exists ("mail_generate_abbrev")
525 call s:MakeAliasAbbrev()
528 if exists ("mail_cursor_start")