GTD and Mutt

I found this article about Getting things done with mutt. It talks about using the the techniques from David Allen’s productivity book “Getting Things Done: The Art of Stress-Free Productivity“. I’ve been reading GTD for a week now (taking my time, you know) and think it’s pretty interesting.

Since is my favorite email client, I spent a little playing with it and making changes. I’m using a diferent editlabel script and my way doesn’t require patching and recompiling mutt.

Here are my muttrc changes:

### GETTING THINGS DONE ###
# http://blogs.techrepublic.com.com/opensource/?p=106&tag=nl.e011
# http://auriga.wearlab.de/~alb/other/mutt-labels/
set editor="emacs"
set move=yes
set mbox='=archive'

unignore X-Label:  # make sure to display X-Label on each message
color header            red         default '^X-Label:'
mailboxes =ACTION
mailboxes =RESPOND
mailboxes =WAITFOR
mbox-hook =ACTION  =archive
mbox-hook =RESPOND =archive
macro pager \Ct  "<exit><tag-entry><tag-prefix><mark-as-new><tag-prefix><save-message>=ACTION<enter>"  "Mark message as ACTION"
macro pager \Cr  "<exit><tag-entry><tag-prefix><mark-as-new><tag-prefix><save-message>=RESPOND<enter>" "Mark message as RESPOND"
macro pager \Cw  "<exit><tag-entry><tag-prefix><mark-as-new><tag-prefix><save-message>=WAITFOR<enter>" "Mark message as WAITFOR"
macro index y "<enter-command>set editor=\"~/.mutt/editlabel\"\n\
<edit><sync-mailbox><next-undeleted>\
<enter-command>set editor=emacs\n" "Edit Label"
macro pager y "<enter-command>set editor=\"~/.mutt/editlabel\"\n\
<edit><sync-mailbox><next-undeleted>\
<enter-command>set editor=emacs\n" "Edit Label"
macro index \Cy "<limit>~y " "Limit view to label"

set index_format="%4C %Z %{%b %d} %-15.15L %?M?(#%03M)&(%4l)? %?y?{%.20Y} ?%s"
#### END GTD ###

editlabel also has full editing and history, which is nice. I haven’t added tab-completion from the history; I’m not sure if I will or not.

My editlabel also has the advantage that you can delete a label just by deleting the line; ^a ^k if you use emacs.

Note that if you use the above, you’ll have to replace the occurances of “emacs” with whatever editor you use.

I just found a similar post: GTD with Mutt

I like my editlabel better, even if I’m using ’s evil os.system(). But that narrow-wide trick is neat.

Ciao!

UPDATE 2007-10-08: Added mark-as-new when saving, so that they will attract my notice via the mailbox changing.
UPDATE 2007-10-09: Fixed the mark-as-new stuff…had to use tagging to make it work.

2 Comments

  1. sr
    Posted 2007-11-18 at 10:24 am | Permalink

    Thank you! Very useful, even outside of GTD: just for the labels.

    >I like my editlabel better
    The same. But cannot point to anything specific (maybe readline).

    >But that narrow-wide trick is neat.
    What is it?

    >evil os.system()
    Maybe a comment about the dependancy on formail command could help?

    >I haven’t added tab-completion from the >history; I’m not sure if I will or not.
    If this is not too much work, it could be appreciated: for emacs users, tab completion feels natural.

    Minor comment: instead of duplicating the command in the muttrc, one can use:
    macro index,pager

  2. Klaus Weidner
    Posted 2007-12-14 at 12:41 am | Permalink

    Hello,

    thanks for the writeup, this is very close to what I was looking for.

    I’ve been hacking on it a bit to add label completion with the Tab key, and a prompt when using new labels to check if you want to add them to the label list (to avoid typos).

    I’m keeping everything in my Inbox (including my own sent messages) and adding labels like “Done” to messages I don’t want to see in the default view (!~y Done), but they are still available for other searches.

    Also, I’ve used a dirty hack to make the label function work for multiple tagged messages by saving the actions to do (add/remove/set labels) to a temporary file.

    Replace “Mutt-edit” with vim/emacs/editor of your choice, and note that I’ve changed the paths.

    ***** .muttrc

    macro index,pager y "<enter-command>set editor=\"editlabel\"\n\
    <shell-escape>rm -f ~/.label_saved_action<enter>\
    <tag-prefix><edit>\
    <sync-mailbox><next-undeleted>\
    <shell-escape>rm -f ~/.label_saved_action<enter>\
    <enter-command>set editor=Mutt-edit\n" "Edit Label"

    **** editlabel

    #!/usr/bin/python -utWall
    
    import readline, sys, os
    
    histfile = os.path.join(os.environ["HOME"], ".label_history")
    labelfile = os.path.join(os.environ["HOME"], ".labels")
    actfile = os.path.join(os.environ["HOME"], ".label_saved_action")
    
    def get_label_file():
       try:
           return map(lambda s: s.rstrip(), open(labelfile, 'r').readlines())
       except IOError:
           return
    
    def label_completions(text, state):
       labels = get_label_file()
       candidates = filter(lambda x: x.startswith(text), labels)
       try:
           return candidates[state]
       except:
           return
    
    def my_input(prompt, default=None, completions=None):
       if default is not None:
           def pre_input_hook():
               readline.insert_text(str(default))
               readline.redisplay()
           readline.set_pre_input_hook(pre_input_hook)
           val = []
    
       readline.parse_and_bind("tab: complete")
       readline.set_completer(completions)
       return raw_input(prompt)
    
    def get_label(filename):
       header = 'X-Label: '
       fp = file(filename, 'r')
       result = ''
       for line in fp.readlines():
           if line.startswith(header):
               result = line[len(header):].strip()
               break
       fp.close()
       return result
    
    def write_label(filename, label):
       header = ['X-Label:']
       tmpfile = filename + '.tmp'
       if label:
           header.append(' ')
           header.append(label.rstrip().lstrip())
       header = '"%s"' % ((''.join(header)).replace('"', '\"'))
       cmd = ' '.join([ 'formail',
                        '-I',
                        header,
                        '<',
                        filename,
                        '>',
                        tmpfile,
                        '&&',
                        'mv',
                        tmpfile,
                        filename
                        ])
       os.system(cmd)
    
    def ask(prompt, default):
       print prompt + ' ['+default+'] ',
       ans = sys.stdin.readline().rstrip()
       if ans == "":
           return default
       return ans
    
    def edit_label(label):
       while True:
           label = my_input('Label: ', label, label_completions)
           known_labels = set(get_label_file())
           unknown = filter(lambda x: not x in known_labels, label.split())
           if len(unknown) == 0:
               break
           else:
               not_added=0
               for new in unknown:
                   if ask_yn("label '"+new+"' not known, add?", 'n'):
                       open(labelfile, "a").write(new+'\n')
                   else:
                       not_added += 1
               if not_added==0:
                   break
    
       return label
    
    def do_action(action, infile, new_label=None):
       if action == 'a' or action == '+':
           if new_label == None:
               new_label = edit_label("")
           current_labels = get_label(infile)
           updated_labels = current_labels
           for new in new_label.split():
               if not new in set(current_labels.split()):
                   updated_labels += ' ' + new
           if updated_labels != current_labels:
               write_label(infile, updated_labels)
           return new_label
    
       elif action == 'r' or action == '-':
           rm_label = new_label
           if rm_label == None:
               rm_label = edit_label("")
           current_labels = get_label(infile)
           updated_labels = ""
           for new in current_labels.split():
               if not new in set(rm_label.split()):
                   updated_labels += ' ' + new
           if updated_labels != current_labels:
               write_label(infile, updated_labels)
           return rm_label
    
       elif action == 'e' or action == '=':
           label = get_label(infile)
           if new_label == None:
               new_label = edit_label(label)
           if new_label != label:
               write_label(infile, new_label)
           return new_label
    
    def ask_yn(prompt, default):
       return ask(prompt, default) == 'y'
    
    if "__main__" == __name__:
       infile = sys.argv[1]
    
       if hasattr(readline, 'read_history_file'):
           try:
               readline.read_history_file(histfile)
           except IOError:
               pass
    
       try:
           actions = open(actfile, 'r').readlines()
           action = actions[0].rstrip()
           new_label = actions[1].rstrip()
           do_action(action, infile, new_label)
       except IOError:
           # ask user
           os.system("clear")
           action = ask("(a)dd, (r)emove, or (e)dit labels?", "a")
           new_label = do_action(action, infile)
    
           open(actfile, 'w').write(action + '\n' + new_label + '\n')
           readline.write_history_file(histfile)
    

Post a Comment

Your email is never shared. Required fields are marked *

*
*