]> git.deb.at Git - pkg/abook.git/blob - contrib/abook+vim/mail.vim
Merge remote-tracking branch 'upstream/master' into upstream
[pkg/abook.git] / contrib / abook+vim / mail.vim
1 " mail FTplugin
2 "
3 " Requires vim 6.x.  
4 " To install place in ~/.vim/after/ftplugin/mail.vim
5 "
6 " Author: Brian Medley
7 " Email:  freesoftware@4321.tv
8 "
9 " This file was modified from Cedric Duval's version.
10 " http://cedricduval.free.fr/download/vimrc/mail
11
12 " Only do this when not done yet for this buffer
13 if exists("b:did_mail_after_ftplugin")
14   finish
15 endif
16 let b:did_mail_after_ftplugin = 1
17
18 if !exists ("mail_alias_program")
19     let mail_alias_program="Abook"
20 endif
21
22 " ====================================================================
23 "                               Globals
24 " ====================================================================
25
26 if !exists ("mail_quote_chars")
27     let s:quote_chars = ':!|>'
28 else
29     let s:quote_chars = mail_quote_chars
30 endif
31
32 " This re defines a 'quote'
33 let s:quote_re = '\(\s\?\w*[' . s:quote_chars . ']\)'
34 "                   \s\?                             => 0 or one whitespace char 
35 "                                                       (b/c some ppl put
36 "                                                       spaces in the quote,
37 "                                                       and others don't)
38 "                                                   
39 "                       \w*                          => maybe word chars (b/c
40 "                                                       some ppl put initals in
41 "                                                       the quotes)
42 "                               the rest             => actual quote chars 
43 "                 \(                              \) => this is a quote "level"
44
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
48 "                                                          line
49 "                                      s:quote_re*      => perhaps followed by
50 "                                                          more quotes
51
52 " For debugging:
53 " let b:quote_chars = s:quote_chars
54 " let b:quote_re = s:quote_re
55 " let b:quote_start = s:quote_start
56
57 " ====================================================================
58 "                               Mappings
59 " ====================================================================
60
61 if !exists("no_plugin_maps") && !exists("no_mail_maps")
62     
63     "
64     " get alias list mappings
65     " 
66     if !hasmapto('<Plug>MailAliasList', 'n')
67         nmap <buffer> <unique> <LocalLeader>al  <Plug>MailAliasList
68     endif
69     if !hasmapto('<Plug>MailAliasList', 'i')
70         imap <buffer> <unique> <LocalLeader>al  <Plug>MailAliasList
71     endif
72     
73     nnoremap <buffer> <unique> <script> <Plug>MailAliasList <SID>AliasList
74     inoremap <buffer> <unique> <script> <Plug>MailAliasList <SID>AliasList
75     
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
78     "   the mapping.
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>
82
83     "
84     " get alias query mappings
85     "
86     if !hasmapto('<Plug>MailAliasQuery', 'n')
87         nmap <buffer> <unique> <LocalLeader>aq  <Plug>MailAliasQuery
88     endif
89     if !hasmapto('<Plug>MailAliasQuery', 'i')
90         imap <buffer> <unique> <LocalLeader>aq  <Plug>MailAliasQuery
91     endif
92     
93     nnoremap <buffer> <unique> <script> <Plug>MailAliasQuery <SID>AliasQuery
94     inoremap <buffer> <unique> <script> <Plug>MailAliasQuery <SID>AliasQuery
95     
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>
98
99     " 
100     " mail formatting mappings
101     "
102
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
108
109     if !hasmapto('<Plug>MailFormatQuoteLvl', 'n')
110         nmap <buffer> <unique> <F1> <Plug>MailFormatQuoteLvl
111     endif
112     if !hasmapto('<Plug>MailFormatLine', 'n')
113         nmap <buffer> <unique> <F2> <Plug>MailFormatLine
114     endif
115     if !hasmapto('<Plug>MailFormatMerge', 'n')
116         nmap <buffer> <unique> <F3> <Plug>MailFormatMerge
117     endif
118     if !hasmapto('<Plug>MailFormatParagraph', 'n')
119         nmap <buffer> <unique> <F4> <Plug>MailFormatParagraph
120     endif
121
122     if !hasmapto('<Plug>MailFormatQuoteLvl', 'i')
123         imap <buffer> <unique> <F1> <Plug>MailFormatQuoteLvl
124     endif
125     if !hasmapto('<Plug>MailFormatLine', 'i')
126         imap <buffer> <unique> <F2> <Plug>MailFormatLine
127     endif
128     if !hasmapto('<Plug>MailFormatMerge', 'i')
129         imap <buffer> <unique> <F3> <Plug>MailFormatMerge
130     endif
131     if !hasmapto('<Plug>MailFormatParagraph', 'i')
132         imap <buffer> <unique> <F4> <Plug>MailFormatParagraph
133     endif
134
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
143
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
152
153     " 
154     " sig removal mappings
155     "
156     if !hasmapto('<Plug>MailEraseQuotedSig', 'n')
157         nmap <silent> <buffer> <unique> <LocalLeader>eqs <Plug>MailEraseQuotedSig
158     endif
159     nnoremap <buffer> <unique> <script> <Plug>MailEraseQuotedSig <SID>EraseQuotedSig
160     nnoremap <buffer> <SID>EraseQuotedSig :call<SID>EraseQuotedSig()<CR>
161
162     "
163     " Provide a motion operator for commands (so you can delete a quote
164     " segment, or format quoted segment)
165     "
166     if !hasmapto('<Plug>MailQuoteLvlMotion', 'o')
167         omap <silent> <buffer> <unique> q <Plug>MailQuoteLvlMotion
168     endif
169     onoremap <buffer> <unique> <script> <Plug>MailQuoteLvlMotion <SID>QuoteLvlMotion
170     onoremap <buffer> <script> <SID>QuoteLvlMotion :execute "normal!" . <SID>QuoteLvlMotion(line("."))<cr>
171     
172 endif
173
174 " ====================================================================
175 "                     Mail Manipulation Functions
176 " ====================================================================
177
178 " --------------------------------------------------------------------
179 "                          Manipulate Quotes
180 " --------------------------------------------------------------------
181
182 "
183 " Description: 
184 " This function will try and remove 'quoted' signatures.
185 "
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.
189 "   | 
190 "   | -- 
191 "   | Some power user
192 "
193 " If there is a signature inside a 'multi-quoted' email this will try and get
194 " rid of it:
195 "   > | No, I don't agree with you.
196 "   >
197 "   > Nonsense.  You are wrong.  Grow up.
198 "   >
199 "   > | I can't believe I'm even replying to this.
200 "   > | -- 
201 "   > | Some power user
202 "   >
203 "   > Yeah, believe it, brother.
204
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
210     endwhile
211 endfunction
212 endif
213
214 "
215 " Description:
216 " Replacing empty quoted lines (i.e. "> $") with empty lines
217 " (convenient to automatically reformat one paragraph)
218 "
219 if !exists("*s:DelEmptyQuoted")
220 function s:DelEmptyQuoted()
221     let empty_quote = s:quote_start . '\s*$'
222
223     " goto start of email and jump passed headers
224     normal gg
225     if 0 == search('^$', 'W')
226         return
227     endif
228     
229     while 0 != search (empty_quote, 'W')
230         let newline = substitute(getline("."), empty_quote, '', '')
231         call setline(line("."), newline)
232     endwhile
233 endfunction
234 endif
235
236 "
237 " Description:
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.
240 " E.g:
241 "   dq  => delete an entire quote section
242 "   gqq => format an entire quote section
243 "
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
248     if "" == quote
249         return "\<esc>"
250     endif
251         
252     let len = s:LenQuoteLvl(a:line, quote)
253
254     " the 'V' makes the motion linewise
255     if 1 == len
256         return "V" . line(".") . "G"
257     else
258         return "V" . (len - 1) . "j"
259     endif
260 endfunction
261 endif
262
263 "
264 " Description:
265 " This tries to figure out when the quoting level changes
266 "
267 if !exists("s:LenQuoteLvl")
268 function s:LenQuoteLvl(start, quote)
269     let i = a:start + 1
270     let len = 1
271     let quote = '^' . a:quote
272     
273     " find end of quote
274     while i <= line('$')
275         " check if quote level decreased
276         if -1 == match(getline(i), quote)
277             break
278         endif
279
280         " check if quote level increased
281         if -1 != match(getline(i), (quote . s:quote_re))
282             break
283         endif
284         
285         let i   = i   + 1 
286         let len = len + 1
287     endwhile
288
289     return len
290 endfunction
291 endif
292
293 " --------------------------------------------------------------------
294 "                    Location Manipulator Functions
295 " --------------------------------------------------------------------
296
297 "
298 " Description:
299 " Moves the cursor to a 'sensible' position.
300
301 if !exists("*s:CursorStart")
302 function s:CursorStart()
303     " put cursor in known position
304     silent normal gg
305     
306     if search('^From: $', 'W')
307         silent startinsert!
308     elseif search('^To: $', 'W')
309         silent startinsert!
310     elseif search('^Subject: $', 'W')
311         silent startinsert!
312         
313     " check if we are editing a reply
314     elseif search('^On.*wrote:', 'W')
315         normal 2j
316         
317     elseif search('^$', 'W')
318         normal j
319         silent startinsert!
320     endif
321 endfunction
322 endif
323
324 " ================================================
325 "               Process Mutt Aliases
326 " ================================================
327
328 " ------------------------------------------------
329 "                  Get Email List
330 " ------------------------------------------------
331
332 "
333 " Description:
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.
337 "
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.
340 "
341 if !exists("*s:AliasListAbook")
342 function s:AliasListAbook()
343     let b:AliasListMsg = ""
344     let f = tempname()
345
346     set paste
347     silent exe '!abook 2> ' . f
348     exe 'let addresses=system("cat ' . f . '")'
349     if "" == addresses
350         let b:AliasListMsg = "Nothing found to lookup"
351         return ""
352     endif
353
354     " - parses the output from abook
355     let addresses=s:ParseMuttQuery(addresses)
356     if "" == addresses
357         let b:AliasListMsg = b:ParseMuttQueryErr
358         return ""
359     endif
360
361     " so that they will be aligned under the 'to' or 'cc' line
362     let addresses=substitute(addresses, "\n", ",\n    ", "g")
363
364     return addresses
365 endfunction
366 endif
367
368 " ------------------------------------------------
369 "                 Get Email Query
370 " ------------------------------------------------
371
372 "
373 " Description:
374 " This function assumes that user has the cursor on an alias to lookup.  Based
375 " on this it:
376 " - retrieves the alias(es) from abook
377 " - parses the output from abook
378 " - actually replaces the alias with the parsed output
379 "
380 if !exists("*s:AliasQueryAbook")
381 function s:AliasQueryAbook()
382     let b:AliasQueryMsg = ""
383
384     " - retrieves the alias(es) from abook
385     let lookup=expand("<cword>")
386     if "" == lookup
387         let b:AliasQueryMsg = "Nothing found to lookup"
388         return
389     endif
390
391     silent exe 'let output=system("abook --mutt-query ' . lookup . '")'
392     if v:shell_error
393         let b:AliasQueryMsg = output
394         return
395     endif
396
397     " - parses the output from abook
398     let replacement=s:ParseMuttQuery(output)
399     if "" == replacement
400         let b:AliasQueryMsg = b:ParseMuttQueryErr
401         return
402     endif
403
404     " so that they will be aligned under the 'to' or 'cc' line
405     let replacement=substitute(replacement, "\n", ",\n    ", "g")
406
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
410     set paste
411     exe "normal! ciw" . replacement . "\<Esc>"
412     set nopaste
413 endfunction
414 endif
415
416 " --------------------------------------------------------------------
417 "                          Utility Functions
418 " --------------------------------------------------------------------
419
420 "
421 " Description:
422 " This function will take the output of a "mutt query" (as defined by the mutt
423 " documenation) and parses it.  
424 "
425 " It returns the email addresses formatted as follows:
426 " - each address is on a line
427 "
428 if !exists("*s:ParseMuttQuery")
429 function s:ParseMuttQuery(aliases)
430     " remove first informational line
431     let aliases   = substitute (a:aliases, "\n", "", "")
432     let expansion = ""
433
434     while 1
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")
439         if "" == address
440             let b:ParseMuttQueryErr = "Unable to parse address from ouput"
441             return ""
442         endif
443
444         let name = matchstr(line, "\t.*\t")
445         let name = substitute(name, "\t", "", "g")
446         if "" == name
447             let b:ParseMuttQueryErr = "Unable to parse name from ouput"
448             return ""
449         endif
450
451         " debugging:
452         " echo "line: " . line . "|"
453         " echo "address: " . address . "|"
454         " echo "name: " . name . "|"
455         " let a=input("hit enter")
456
457         " make into valid email address
458         let needquote = match (name, '"')
459         if (-1 == needquote)
460             let name = '"' . name    . '" '
461         endif
462         
463         let needquote = match (address, '<')
464         if (-1 == needquote)
465             let address = '<' . address . '>'
466         endif
467         
468         " add email address to list
469         let expansion = expansion . name
470         let expansion = expansion . address
471
472         " debugging:
473         " echo "address: " . address . "|"
474         " echo "name: " . name . "|"
475         " let a=input("hit enter")
476         
477         " process next line (if there is one)
478         let aliases = substitute(aliases, ".\\{-}\n", "", "")
479         if "" == aliases
480             let b:ParseMuttQueryErr = ""
481             return expansion
482         endif
483
484         let expansion = expansion . "\n"
485     endwhile
486 endfunction
487 endif
488
489 " ====================================================================
490 "                      Abbreviation Manipulation
491 " ====================================================================
492
493 "
494 " Description:
495 " This will generate vi abbreviations from your mutt alias file.
496
497 " Note:
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.
502 "
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
508 endfunction
509 endif
510
511
512 " ====================================================================
513 "                           Initializations
514 " ====================================================================
515
516 if exists ("mail_erase_quoted_sig")
517     call s:EraseQuotedSig()
518 endif
519
520 if exists ("mail_delete_empty_quoted")
521     call s:DelEmptyQuoted()
522 endif
523
524 if exists ("mail_generate_abbrev")
525     call s:MakeAliasAbbrev()
526 endif
527
528 if exists ("mail_cursor_start")
529     call s:CursorStart()
530 endif