teaching machines

Automatic Alias Creation in Mutt/Vim

March 17, 2013 by . Filed under mutt, vim.

A friend has got me thinking about using Mutt again. I gave it up years ago after I was forced to use IMAP for one of my mail accounts, and I didn’t think Mutt was up to the task. I switched to Thunderbird, which lets me do certain things faster. However, I’ve really missed a few features from Mutt: textual and portable configuration files, multiple message replies, and full Vim support.

As I return to Mutt, I also miss a few things from Thunderbird. The most prominent of these is autocompletion of my recipients. Mutt will tab-complete aliases that you’ve previously registered in its configuration files, but Thunderbird one-ups Mutt by automatically storing the addresses of people you send messages to. I wanted to make Mutt do that too.

I looked around for hooks, but I didn’t see a way for Mutt to automatically record aliases when I sent a message. However, since I was using Vim to edit my messages, I saw that I could create an autocommand that would strip out the recipients’ addresses from my message and append them to my alias file. So, I first wrote a function to do the heavy-lifting:

" This function, which is to be run only on a Mutt email message,
" finds all the addresses in the To, Cc, and Bcc headers. If
" no aliases exist for these addresses, they are added to the
" alias file.
function! AddMuttAliases()

  " This is where my alias file lives. Change this to something
  " that suits you.
  let aliasfile = $UU . '/configs/mutt/aliases'

  " Why isn't there a match + matchstr? match only gives me the
  " start, which is not enough to extract the substring.
  " matchstr doesn't give me the location, which is not enough
  " information to advance forward.

  " Find all email addresses. My pattern may not be the best yet.
  let addresses = []
  silent vimgrep /^\(To\|Cc\|Bcc\):/j %
  for line in getqflist()
    let pattern = '[-A-z._]\+@[-A-z._]\+'
    let start = match(line.text, pattern)
    let address = matchstr(line.text, pattern, start)
    while address != ""
      call add(addresses, address)
      let start += len(address)
      let address = matchstr(line.text, pattern, start)
    endwhile
  endfor

  " Check to see if each is in aliases file. If not, add it.
  " Mutt's alias registration command has this form:
  "
  "   alias name address
  "
  " Usually the name is something short and mnemonic. I prefer
  " to continually work with people's addresses so that I'm not
  " too crippled when I lose my configuration.  So, my aliases
  " have this form:
  "
  "   alias address address
  "
  " Isn't this overly verbose? Yes. What have I gained? Well,
  " mutt will tab-complete the addresses for me. Verbosity
  " doesn't matter. And that's sweet.
  for address in addresses
    execute 'silent! vimgrep /' . address . '/j ' . aliasfile
    if len(getqflist()) == 0
      let aliasline = 'alias ' . address . ' ' . address
      silent! execute '!echo ' . aliasline . ' >> ' . aliasfile
    endif
  endfor
endfunction

To call this function when Mutt messages are written, I registered the following autocommand in my .vimrc file:

autocmd BufWritePost ~/.tmp/mutt-* call AddMuttAliases()

This command assumes Mutt messages are temporarily stored in ~/.tmp, which I set in my .muttrc.

Now everytime I write a message to someone new, I get an alias added to my aliases file. The next time, I can simply tab-complete to enter the person’s address!