Mutt and Vim Custom Autocompletion

Recently I switched to mutt (after hearing Nigel and Penny talk about it a whole lot).

I'm using a combination of mutt, offlineimap, vim (for actually editing the emails), and up until now extreme annoyance at contact management/insertion.

There are all sorts of methods you can choose from to manage your contacts and deal with getting them in the to/cc/bcc fields of your outgoing emails. Some examples are ...

  • Nigel: just type the bloody addresses out! (aka the HTFU approach)
  • Penny: some weird combination of abook, mutt aliases, and constant confusion along the lines of "crap!, what screen am I in now?"
  • Martyn: similar to Nigel's approach with lots of swearing and conviction there must be a better way

I wrote Nigel off as a lost cause and had a chat to Penny about how best to do it. After a bit of chat, I worked out that while her way was okay, being able to do all the address addition/removal inside vim (where you're actually editing the message) would by far suit me better.

This is where I wandered off to Freenode's #vim and started asking around. A guy there suggested I might be interested in lbdb (Little Brother's Database). Now the name is a little confusing because lbdb isn't actually a data store itself, instead I think of it as a query proxy. You give it a query (e.g. "martyn"), and it passes the query to all the selected modules (backend), each module returning a list of results (e.g. "Martyn Smith" martyn@dollyfish.net.nz).

This was a good start, given the modules already available to choose from, I started using a simple mail list (built from filtering From addresses out of a piece of mail) module and module that connects to my mutt alises file. Penny managed to get the ldap module working too (so we can query against the Catalyst directory).

Now here's where the fun starts. There's a lbdb plugin for vim. You can install this in Debian like so ...

sudo apt-get install vim-addon-manager
vim-addon-manager install lbdbq

which basically symlinks it into your ~/.vim directory for you.

Using the plugin is fairly straight forward. When editing an email you simply write your To/Cc/Bcc fields using search queries ...

To: fred, martyn smith, nigel

Then with the cursor on the appropriate line hit \lb and it does lookups for you and turns it into ...

To: "Fred Jones" <fred@example.org>, "Martyn Smith" <martyn@dollyfish.net.nz>, "Nigel McNie" <nigel@example.com>

For each one of your "queries" that return more than one result, the plugin will stop and ask you to disambiguate (using a menu similar to :ls in vim).

So, this was already a huge improvement. I could now actually do useful address placement with it, and Penny started to use it too.

Difficult to be completely satisfied I continued my quest. I thought "What if I could just use the standard CTRL-n and CTRL-p completion commands in vim to do address completion?" (and if you don't know what those keys do you really need to read :help i_CTRL-N). This lead me down a winding path of learning about vim's custom completion support. Ultimately this led to a chunk of vim code that implements a completion function (using the lbdb vim plugin's query function) allowing me to simply type a query then hit <C-X><C-U> to invoke the vim completion menu.

Of course, the final piece of the puzzle was to wrap this up in my .vimrc with some bindings to make it work without having to explicitly invoke user completion (i.e. change the default completion from keyword to user).

The final .vimrc snippet is at http://paste.dollyfish.net.nz/576ec0?filetype=vim :-)

Enjoy!, and feel free to send questions/comments/ideas to me :-)

p.s. Nigel, note that $post !~ /^So/ ;-)

I am _NOT_ a lost cause!!!

And I bet it took you half an hour to work out how to open this post without /^So/ too :P

In all seriousness though - extremely cool. You should mention some of your muttrc improvements too - like editheaders (obviously needed for this), and autoedit :)

True true ...

So as you say, you need ..

# Let editor see/edit the headers
set edit_headers
# Don't ask me for To/Subject, just load the editor
set autoedit
# Use vim with mutt_mode set (to enable my completion magic)
set editor="gvim -f -c 'set nohlsearch noshowmatch modelines=0 tw=75 et noai' --cmd 'let mutt_mode=1'"

Hi, i am a friend of Penny's and also know the author of lbdbq very well. It would be cool to get the appropriate parts of your work integrated in lbdbq. If you would be willing to make a patch, I'll see to having it integrated and published with the next stable vim release, and even earlier in Debian (think: lenny).

Cheers,
dollyfish.net.nz@pobox.madduck.net

Some more notes, after playing with this some more:

* The binary from vim-addon-manager is called 'vim-addons' - so it's 'vim-addons install lbdbq'
* You need to install vim-scripts to get the extension in the first place
* I'm not 100% sure, but I think that the mutt aliases file stores apostrophes escaped with a \. I tried removing this using vim's substitute function but failed. Might be worth investigating

I'm actually curious how you tie together offlineimap and lbdb since offlineimap doesn't have a way to pass mail through some other program, as far as I know. Do you have a cron job (ooh, or maybe some inotify-based mechanism) for feeding email to lbdb?

At the moment I'm just feeding lbdb manually from within Mutt (i.e. I'm manually picking messages to pass through to lbdb's in_mail module). Yeah, a cron job could work (and looking around, it's probably the easiest solution so far :-)

Either that, or try get hook support into offlineimap, I think Penny knows the author, so that might be possible :-)

" At the moment I'm just feeding lbdb manually from within Mutt (i.e. I'm manually picking messages to pass through to lbdb's in_mail module)."

Could say more about how you do that? Sounds interesting.

I'm also using offlineimap & mutt, with msmtp. I've just discovered lbdb, and currently I have implemented the compromise of running outgoing e-mails through lbdb-fetchaddr, by having "set sendmail" to a bash script that contains:


#!/bin/bash
tee >(lbdb-fetchaddr -a)|/usr/bin/msmtp -a default $@

So it turns out that's a bit of a lie now ;-)

Basically I gave up on using the in_mail filter thing (I was doing it very manually :p

I'm now just using mutt aliases, and I have lbdb reading my mutt aliases file.

Interesting idea about the shell wrapper, that looks quite funky :-)

Most of the time my use case is wanting to add a received email to my addressbook rather than outgoing ones, so my solution is working fine for me at the moment :-)

Your comments and replies are confusingly backwards... and you know I can only ever post anonymously. I'll let you guess who I am...

Anyway, instead of this mutt_mode hack, why not use $VIM/ftplugin/mail.vim for your code? If that doesn't work, it's because something in /usr/share/vim overwrites what you do, so then you use $VIM/after/ftplugin/mail.vim.

Yeah, the comments are definitely sucky :-(, I'll have to get around to doing something about that (along with all the other stuff I need to get around to).

I'll check out mail.vim too, and see what I can do :-)