├── README.md ├── autoload └── mailquery.vim ├── doc └── mailquery.txt └── plugin └── mailquery.vim /README.md: -------------------------------------------------------------------------------- 1 | This plug-in lets you complete e-mail addresses in Vim by those found in your inbox (or any other mail folder) via [mail-query](https://github.com/pbrisbin/mail-query). 2 | Useful, for example, when using `Vim` as editor for `mutt` (especially with `$edit_headers` set). 3 | 4 | # Usage 5 | 6 | When you're editing a mail file in Vim that reads 7 | ```mail 8 | From: Fulano 9 | To: foo 10 | ``` 11 | and in your Inbox there is an e-mail from 12 | ```mail 13 | Mister Foo 14 | ``` 15 | and your cursor is right after `foo`, then hit `Ctrl+X Ctrl+O` to obtain: 16 | ```mail 17 | From: Fulano 18 | To: Mister Foo 19 | ``` 20 | 21 | # Commands 22 | 23 | To complete an e-mail address inside Vim press `CTRL-X CTRL-O` in insert 24 | mode. See `:help i_CTRL-X_CTRL-O` and `:help compl-omni`. 25 | 26 | # Setup 27 | 28 | 1. Download and install (by `sudo make install`) [mail-query](https://github.com/pbrisbin/mail-query). 29 | If you are missing superuser rights, then compile it (by `make`) add the path of the folder that contains the obtained executable `mail-query` (say `~/bin`) to your environment variable `$PATH`: 30 | If you use `bash` or `zsh`, by adding to `~/.profile` or `~/.zshenv` the line 31 | 32 | ```sh 33 | PATH=$PATH:~/bin 34 | ``` 35 | 36 | To decode [7-bit ASCII encoded MIME-headers](https://tools.ietf.org/html/rfc2047) (which start, for example, with `=?UTF-8?Q?` or `=?ISO-8859-1?Q?`), ensure that `perl` is executable and the [Encode::MIME:Header](https://perldoc.perl.org/Encode/MIME/Header.html) module is installed. 37 | Please note that when completing non-`ASCII` characters, then only `UTF-8` encoded non-`ASCII` characters in the mail folder can be found, but none encoded in `ISO-8859-1(5)` or any other encoding (common before the advent of `UTF-8` in the nineties). 38 | 39 | 2. Completion is enabled in all mail buffers by default. 40 | Add additional file types to the list `g:mailquery_filetypes` which defaults to `[ 'mail' ]`. 41 | To enable completion in other buffers, run `:MailqueryCompletion`. 42 | 43 | 3. The mail folder is automatically set to the value of the variable `$folder` used by `mutt`. 44 | To explicitly set the path to a mail folder `$folder`, add to your `.vimrc` the line 45 | 46 | ```vim 47 | let g:mailquery_folder = '$folder' 48 | ``` 49 | 50 | For example, if you use `mbsync`, then `$folder` could be 51 | 52 | ```sh 53 | $XDG_DATA_HOME/mbsync/INBOX/cur 54 | ``` 55 | 56 | 4. If you would like to filter out most probably impersonal e-mail addresses such as those that come from mailer daemons or accept no reply, then try adding 57 | 58 | ```vim 59 | let g:mailquery_filter = 1 60 | ``` 61 | 62 | to your `vimrc`, which will discard all e-mail addresses that satisfy the regular expression given by the variable `g:mailquery_filter_regex` that defaults to 63 | 64 | ```vim 65 | let g:mailquery_filter_regex = '\v^[[:alnum:]._%+-]*%([0-9]{9,}|([0-9]+[a-z]+){3,}|\+|nicht-?antworten|ne-?pas-?repondre|not?([-_.])?reply|<(un)?subscribe>|)[[:alnum:]._%+-]*\@' 66 | ``` 67 | 68 | # Related Plug-ins 69 | 70 | - The plugin [vim-notmuch-addrlookup](https://github.com/Konfekt/vim-notmuch-addrlookup) lets you complete e-mail addresses in Vim by those indexed by [notmuch](https://notmuchmail.org). 71 | - The [vim-mutt-aliases](https://github.com/Konfekt/vim-mutt-aliases) plug-in lets you complete e-mail addresses in Vim by those in your `mutt` alias file and (when the alias file is periodically populated by the [mutt-alias.sh](https://github.com/Konfekt/mutt-alias.sh) shell script) gives a more static alternative to this plug-in. 72 | 73 | # Credits 74 | 75 | - to Patrick Brisbin's [mail-query](https://github.com/pbrisbin/mail-query). 76 | - to Lu Guanqun as the fork [vim-mutt-aliases](https://github.com/Konfekt/vim-mutt-aliases) of [Lu Guanqun](mailto:guanqun.lu@gmail.com)'s [vim-mutt-aliases-plugin](https://github.com/guanqun/vim-mutt-aliases-plugin/tree/063a7bdd0d852a118253278721f74a053776135d) served as a template. 77 | 78 | # License 79 | 80 | Distributable under the same terms as Vim itself. See `:help license`. 81 | 82 | -------------------------------------------------------------------------------- /autoload/mailquery.vim: -------------------------------------------------------------------------------- 1 | function! mailquery#SetupMailquery() abort 2 | " Setup g:mailquery_filter 3 | if !exists('g:mailquery_filter') 4 | let g:mailquery_filter = 0 5 | elseif g:mailquery_filter 6 | " Setup g:mailquery_filter_regex 7 | if !exists('g:mailquery_filter_regex') 8 | let g:mailquery_filter_regex = '\v^[[:alnum:]._%+-]*%([0-9]{9,}|([0-9]+[a-z]+){3,}|\+|nicht-?antworten|ne-?pas-?repondre|not?([-_.])?reply|<(un)?subscribe>|)[[:alnum:]._%+-]*\@' 9 | endif 10 | endif 11 | 12 | " Setup g:mailquery_folder 13 | if !exists('g:mailquery_folder') 14 | let g:mailquery_folder = '' 15 | 16 | let muttexes = ['neomutt', 'mutt'] 17 | for muttexe in muttexes 18 | if executable(muttexe) | let mutt = muttexe | break | endif 19 | endfor 20 | 21 | if exists('mutt') 22 | silent let output = split(system(mutt . ' -Q "alias_file"'), '\n') 23 | 24 | for line in output 25 | let folder = matchstr(line,'\v^\s*' . 'folder' . '\s*\=\s*[''"]?' . '\zs[^''"]*\ze' . '[''"]?$') 26 | if !empty(folder) 27 | let g:mailquery_folder = resolve(expand(folder)) 28 | break 29 | endif 30 | endfor 31 | else 32 | " pedestrian's way 33 | let muttrcs = ['~/.config/neomutt/neomuttrc', '~/.config/neomutt/muttrc', '~/.config/mutt/neomuttrc', 34 | \ '~/.config/mutt/muttrc', '~/.neomutt/neomuttrc', '~/.neomutt/muttrc', '~/.mutt/neomuttrc', 35 | \ '~/.mutt/muttrc', '~/.neomuttrc', '~/.muttrc'] 36 | let muttrc_content = [] 37 | for muttrc in muttrc 38 | if filereadable(expand(muttrc)) | let muttrc_content = readfile(expand(muttrc)) | break | endif 39 | endfor 40 | 41 | for line in muttrc_content 42 | let folder = matchstr(line,'\v^\s*set\s+' . 'folder' . '\s*\=\s*[''"]?' . '\zs[^''"]*\ze' . '[''"]?$') 43 | if !empty(folder) 44 | let folder = resolve(expand(folder)) 45 | let g:mailquery_folder = folder 46 | break 47 | endif 48 | endfor 49 | endif 50 | endif 51 | if !empty(g:mailquery_folder) 52 | echomsg 'Guessed mail folder by value of $folder used by mutt to be ' . g:mailquery_folder . '.' 53 | " echohl 'Please set g:mailquery_folder in your vimrc to a mail folder!' 54 | endif 55 | 56 | " check whether Perl module to decode MIME headers is installed 57 | " thanks to https://stackoverflow.com/a/15162063 58 | if !exists('s:mailquery_mimeheader') 59 | let s:mailquery_mimeheader = 0 60 | if executable('perl') 61 | silent call system("perl -e 'use Encode::MIME::Header;'") 62 | if v:shell_error == 0 63 | let s:mailquery_mimeheader = 1 64 | endif 65 | endif 66 | endif 67 | 68 | if !isdirectory(g:mailquery_folder) 69 | echoerr 'Please set g:mailquery_folder in your vimrc to a valid (mail) folder path!' 70 | let g:mailquery_folder = '' 71 | endif 72 | 73 | if !exists('s:mailquery_executable') 74 | if executable('mail-query') 75 | let s:mailquery_executable = 1 76 | else 77 | echoerr 'No executable mail-query found.' 78 | echoerr 'Please install mail-query from https://github.com/pbrisbin/mail-query!' 79 | let s:mailquery_executable = 0 80 | endif 81 | endif 82 | endfunction 83 | 84 | function! mailquery#complete(findstart, base) abort 85 | if a:findstart 86 | " locate the start of the word 87 | " we stop when we encounter space character 88 | let col = col('.')-1 89 | let text_before_cursor = getline('.')[0 : col - 1] 90 | " let start = match(text_before_cursor, '\v<([[:digit:][:lower:][:upper:]]+[._%+-@]?)+$') 91 | let start = match(text_before_cursor, '\v<\S+$') 92 | return start 93 | " build perl regex for mail-query 94 | else 95 | if empty(g:mailquery_folder) 96 | return [] 97 | endif 98 | 99 | let before = '^[^@]*' 100 | let base = a:base 101 | if base =~# '[^\x00-\x7F]' && s:mailquery_mimeheader 102 | silent let base = system("perl -CS -MEncode -ne 'print encode(\"MIME-Q\", $_)'", base) 103 | let base = matchstr(base, '\c\v^\V=?UTF-8?Q?\v\zs.*\ze\V?=\v$') 104 | endif 105 | let base = escape(base, '\-[]{}()*+?.^$|') 106 | let after = '[^@]*($|[@])' 107 | let pattern_perl = before . base . after 108 | 109 | let lines = [] 110 | if s:mailquery_executable 111 | silent let lines = split(system("mail-query" . " '" . pattern_perl . "' " . g:mailquery_folder), '\n') 112 | endif 113 | " convert MIME headers via Perl thanks to https://superuser.com/a/972248 114 | if s:mailquery_mimeheader 115 | silent let lines = split(system("perl -CS -MEncode -ne 'print decode(\"MIME-Header\", $_)'", lines), '\n') 116 | endif 117 | 118 | if empty(lines) 119 | return [] 120 | endif 121 | 122 | " build vim regex to sort according to whether pattern matches at 123 | " beginning or after delimiter 124 | let before = '\v^[^@]*' 125 | let base = '\V' . escape(a:base, '\') 126 | let after = '\v[^@]*($|[@])' 127 | 128 | let pattern = before . base . after 129 | let pattern_delim = before . '\v(^|\A)' . base . after 130 | let pattern_begin = '\v^' . base . after 131 | 132 | let results = [ [], [], [], [], [], [], [] ] 133 | for line in lines 134 | if empty(line) 135 | continue 136 | endif 137 | 138 | let words = split(line, '\t') 139 | 140 | if len(words) < 2 141 | continue 142 | endif 143 | 144 | let dict = {} 145 | let address = words[0] 146 | 147 | " skip impersonal addresses 148 | if g:mailquery_filter && address =~? g:mailquery_filter_regex 149 | continue 150 | endif 151 | 152 | " remove double quotes 153 | let name = substitute(words[1], '\v^"|"$', '', 'g') 154 | 155 | " add to completion menu 156 | let dict['word'] = name . ' <' . address . '>' 157 | let dict['abbr'] = strlen(name) < 35 ? name : name[0:30] . '...' 158 | let dict['menu'] = address 159 | 160 | " weigh according to whether pattern matches at 161 | " beginning or after some delimiter in name or address 162 | let pertinence = 0 163 | if name =~? pattern 164 | let pertinence += 1 165 | if name =~? pattern_delim 166 | let pertinence += 1 167 | if name =~? pattern_begin 168 | let pertinence += 1 169 | endif 170 | endif 171 | endif 172 | if address =~? pattern 173 | let pertinence += 1 174 | if address =~? pattern_delim 175 | let pertinence += 1 176 | if address =~? pattern_begin 177 | let pertinence += 1 178 | endif 179 | endif 180 | endif 181 | call add(results[pertinence], dict) 182 | endfor 183 | let results = uniq(sort(results[6], 1) + 184 | \ sort(results[5], 1) + sort(results[4], 1) + sort(results[3], 1) + 185 | \ sort(results[2], 1) + sort(results[1], 1) + sort(results[0], 1), 186 | \ 1) 187 | return results 188 | endif 189 | endfunction 190 | -------------------------------------------------------------------------------- /doc/mailquery.txt: -------------------------------------------------------------------------------- 1 | vim-mailquery *mailquery* *mail-query* 2 | 3 | =========================================================================== 4 | 0. Usage ~ 5 | 6 | This plug-in lets you complete e-mail addresses in Vim by those found in your 7 | inbox (or any other mail folder) via https://github.com/pbrisbin/mail-query . 8 | Useful, for example, when using Vim as editor for mutt (especially with 9 | $edit_headers set). 10 | 11 | When you're editing a mail file in Vim that reads 12 | > 13 | From: Fulano 14 | To: foo 15 | < 16 | and in your Inbox there is an e-mail from 17 | > 18 | Mister Foo 19 | < 20 | and your cursor is right after foo, then hit Ctrl+X Ctrl+O to obtain: 21 | > 22 | From: Fulano 23 | To: Mister Foo 24 | < 25 | 26 | =========================================================================== 27 | 1. Commands ~ 28 | 29 | To complete e-mail addresses inside Vim press CTRL-X CTRL-O in insert 30 | mode. See |i_CTRL-X_CTRL-O| and |compl-omni|. 31 | 32 | =========================================================================== 33 | 2. Setup ~ 34 | 35 | 1. Download and install by sudo make install) mail-query from 36 | https://github.com/pbrisbin/mail-query. 37 | If you are missing superuser rights, then compile it (by make) add the 38 | path of the folder that contains the obtained executable mail-query to your 39 | environment variable $PATH: If you use bash or zsh, by adding to ~/.profile 40 | or ~/.zshenv the line 41 | > 42 | PATH=$PATH:~/bin 43 | < 44 | To decode [7-bit ASCII encoded MIME-headers as specified at 45 | https://tools.ietf.org/html/rfc2047 (which start, for example, with 46 | '=?UTF-8?Q?' or '=?ISO-8859-1?Q?'), ensure that perl is executable and that 47 | the Encode::MIME:Header module is installed. 48 | Please note that if completing non-ASCII characters, then only UTF-8 49 | encoded non-ASCII characters in the mail folder can be found, but none 50 | encoded in ISO-8859-1(5) or any other encoding (common before the advent of 51 | UTF-8 in the nineties). 52 | 53 | 2. Completion is enabled in all mail buffers by default. Add additional file 54 | types to the list *g:mailquery_filetypes* which defaults to [ 'mail' ]. To 55 | enable completion in other buffers, run *:MailqueryCompletion* 56 | 57 | 3. The mail folder is automatically set to the value of the variable $folder 58 | used by mutt. To explicitly set the path to a mail folder 59 | $folder, add to your .vimrc the line 60 | > 61 | let g:mailquery_folder = '$folder' 62 | < 63 | For example, if you use mbsync, then $folder could be 64 | > 65 | $XDG_DATA_HOME/mbsync/INBOX/cur 66 | < 67 | 4. If you would like to filter out most probably impersonal e-mail addresses 68 | such as those coming from mailer daemons or accepting no reply, try 69 | > 70 | let g:mailquery_filter = 1 71 | < 72 | The regular expression which these addresses are checked against can be 73 | customized by the variable g:mailquery_filter_regex. 74 | 75 | =========================================================================== 76 | 3. Related Plug-in ~ 77 | 78 | The plugin https://github.com/Konfekt/vim-mutt-aliases lets you 79 | complete e-mail addresses in Vim by those in your mutt aliases file, 80 | and (when the alias file is periodically populated by the 81 | mutt-alias.sh shell script from https://github.com/Konfekt/mutt-alias.sh) 82 | gives a more static alternative to this plug-in. 83 | 84 | The plugin https://github.com/Konfekt/vim-notmuch-addrlookup lets you 85 | complete e-mail addresses in Vim by those indexed by notmuch. 86 | 87 | vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl 88 | 89 | -------------------------------------------------------------------------------- /plugin/mailquery.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_mailquery') || &cp 2 | finish 3 | endif 4 | let g:loaded_mailquery = 1 5 | 6 | let s:keepcpo = &cpo 7 | set cpo&vim 8 | " ------------------------------------------------------------------------------ 9 | 10 | if !exists('g:mailquery_filetypes') 11 | let g:mailquery_filetypes = [ 'mail' ] 12 | endif 13 | 14 | let s:fts = '' 15 | for ft in g:mailquery_filetypes 16 | let s:fts .= ft . ',' 17 | endfor 18 | let s:fts = s:fts[:-1] 19 | 20 | command! MailqueryCompletion call s:mailquery() 21 | 22 | function! s:mailquery() abort 23 | call mailquery#SetupMailquery() 24 | setlocal omnifunc=mailquery#complete 25 | endfunction 26 | 27 | augroup mailquery 28 | autocmd! 29 | exe 'autocmd FileType' s:fts 'MailqueryCompletion' 30 | augroup end 31 | 32 | " ------------------------------------------------------------------------------ 33 | let &cpo= s:keepcpo 34 | unlet s:keepcpo 35 | --------------------------------------------------------------------------------