Georg Lukas, 2010-07-28 21:44

As a long-time Mutt user I always looked with envy at you Thunderbird and Kmail and what-not fans, as you could spawn new windows for reading and writing e-mails with a mere click (or sometimes a double-click).

It was just too bothersome to have $EDITOR block my inbox until I finish writing or give up and postpone the mail, losing track. As I am using Screen to run Mutt anyway, it seemed like a logical step to make it spawn new screen windows for writing mails and opening other mailboxes. The problem of multiplexing Mutt seems to have appeared to other people before as well, however the solution is not quite easy to spot.

The Idea Behind Mutt Multiplexing

The suggestion for people like me is: replace editor with a command line that spawns a new terminal and opens up mutt -H on a copy of the temp file. The original mutt will then see that the message was not changed and drop it silently, while the new instance allows editing. Fortunately, you can call screen from inside screen, thus creating a new window, so project mutt-screen was born! Unfortunately, not everything was as easy as expected.

The Gory Details, version 1

So, to make the whole thing better manageable, lets split that editor setting line into two parts: a script that is called in the new screen window and what needs to be done in the original mutt instance.

Lets call the script ~/bin/mutt-compose.sh:

#!/bin/sh
mutt -H "$1"
rm "$1"

Now, lets make a copy of the mail draft (%s), and run the composer in a new screen window:

# edit messages in a new screen window
set editor="cp %s %s.2; screen -t composer ~/bin/mutt-compose.sh %s.2"
unset wait_key

Great! We did it! Oh wait, no! It's a fork bomb! The new composer of course evaluates editor and launches... another copy of itself.

Edit the editor, version 2

Consequently, we need to override editor for the editing instance. Because we need to prevent the new Mutt from inserting a second .sig and more headers, lets create a second config file. Then we can call mutt -P ~/.mutt_compose -H "$1" from the script, with ~/.mutt_compose containing:

# read main config
source ~/.muttrc

# remove hooks, headers and sig, they are already in the draft
unhook send-hook
unset signature
unmy_hdr *

# call the right editor immediately
set autoedit=yes
set editor="vim +'set tw=72' +/^$/+1"

But now its going to work, isn't it? Not quite - something happened to postponed messages! Why are there now two edit windows? It seems, Mutt has a different code-path for messages which have been edited already. Here, the first instance ignores that the message was not edited and falls through to the compose window, while the second instance runs the editor (and permanently deletes the message if you do not explicitly save it, oops!).

Forking the editor - or not? Version 3

Our forking-out of the editor causes problems in three cases, as it seems: <recall-message>, <edit> and <resend-message>/<forward-message>. The former can be worked around by using a custom macro:

# override the <recall-message> hotkey
macro index,pager R "<shell-escape>screen -t postponed mutt -F ~/.mutt/compose -p<enter>"
# prevent recall on <mail>
set recall=no

This comes at the additional discomfort of not being asked if you want to resume writing your last mail when pressing m. That might be possible to achieve with some workaround - feel free to leave a comment if you find one.

For <edit>, the only viable workaround seems to be resetting editor to its former state, editing the message in-place and setting editor back to the screen call. Inconvenient, blocking your main Mutt, but possible.

# bonus points for outsourcing the two different editor settings into their own
# source'able one-liners...
macro index,pager e '<enter-command>set editor="vim +set\ tw=72 +/^$/+1"<enter><edit><enter-command>set editor="cp %s %s.2; screen -t composer ~/bin/mutt-compose.sh %s.2"<enter>'

With <resend-message>/<forward-message>, there is no workaround. I tried pushing keys into the buffer to quit the compose window, changing relevant settings, making blood sacrifices to various gods, all without success. So far I have to live with manually quitting the first composer and editing the message in the second one. Yikes.

The resulting config, version 4 (final)

When we put everything together, the following settings files should be in place.

~/.muttrc

# do not forget your own settings...

# edit messages in a new screen window
set editor="cp %s %s.2; screen -t composer ~/bin/mutt-compose.sh %s.2"
unset wait_key

# override the <recall-message> hotkey
macro index,pager R "<shell-escape>screen -t postponed mutt -F ~/.mutt/compose -p<enter>"
# prevent recall on <mail>
set recall=no

# set the editor for for editing messages in-place
macro index,pager e '<enter-command>set editor="vim +set\ tw=72 +/^$/+1"<enter><edit><enter-command>set editor="cp %s %s.2; screen -t composer ~/bin/mutt-compose.sh %s.2"<enter>'

# open mailbox listing in a new window
macro index,pager y "<shell-escape>screen -t mboxes mutt -y<enter>"

~/bin/mutt-compose.sh

#!/bin/sh
# set the screen window title to the message receiver
awk -F 'To: ' '/^To:/ { print "\033k" $2 "\033\\" }' "$1"
mutt -F ~/.mutt_compose -H "$1"
rm "$1"

~/.mutt_compose

# read main config
source ~/.muttrc

# remove hooks, headers and sig, they are already in the draft
unhook send-hook
unset signature
unmy_hdr *

# call the right editor immediately
set autoedit=yes
set editor="vim +'set tw=72' +/^$/+1"

The following .screenrc.mutt file can be used to spawn a screen with your inbox in it and a status bar showing the outsourced instances:

~/.screenrc.mutt

hardstatus alwayslastline "%-Lw%{=b BW} %50>%n%f* %t %{-}%+Lw%< "
sorendition = bG
vbell off

# start mutt directly in a window
screen -t INBOX mutt

Conclusion

This solution is not perfect. In certain corner cases (<resend-message>) it just does not behave. In other situations (caching the PGP passphrase does not work) it has less comfort. Nevertheless, it has increased my Mutt productivity and decreased my frustration a lot. Now all is missing is clean support for multiple profiles (read: without having many scripts bound to clumsy keyboard shortcuts) and proper in-mutt mechanism for message tags.