Task management is a sensitive issue. Even the ones who do not explicitly manage tasks are defensive about how they do it. I was one of them for quite some time, till I realized that it was not working anymore. Like design, it is inevitable, it just happens. The problem is that it stops working after sometime and that too without any alarm.
I spent some time using other tools, reading philosophies like GTD and trying to customize it for my needs. Though there were some improvements some tedium was always present, and it sure did follow Matt Blodgett’s first law of software development.
So I set out to figure out my needs.
- Tasks should be stored in pure text format, so that they are easily available from multiple interfaces. This also reduces the peripheral requirements and makes the system more portable.
- The task management should work even when the Web connection is not available. This eliminates another big dependency.
- I work on projects, so I want to group the tasks by projects. The orphan tasks go under unclassified, which is like an unclassified project.
- A task had to capture the purpose, the action, the context and the constraints like the deadlines.
- Though grouped by projects, I want to easily retrieve the tasks by timeline.
- I need to use a tool that is always handy and available to me. Also, I end up working on multiple platforms, so it has to be a cross-platform tool.
I ended up on using vim as my base tool for this. And am I glad for this decision, take a look at this screenshot.

Schema
I used a schema to capture all information of the task. I wanted clear separation between different projects, so I decided to use one text file per project. The tasks were prefixed with a - . Every task would have a date, priority, action and status, e.g.,
- Discuss design decisions with xyz =01012007 =high :meet
No status implies the task has not even been started. I prefixed the date and the priority with = because it is one of the symbols easily accessible. The action meet indicates what is required to do so, the other ones I use are phone, email and chat. The actions are prefixed with a : so that they can be easily identified.
A task can have sub tasks, with the same schema. A task can also have its own document child, which I enclose within square brackets. This typically includes progress of the task or details which tend to get verbose a lot of times.
These child elements are indented inside the parent task so that I can use vim folding to use it as an outliner.
So the structure of a project file is:
Project Name *projecttag*
*projecttodo*
- Task 1 description =ddmmyyyy =high =started
- Task 1a description =ddmmyyyy =done
[Documentation of task 1a]
- Task 1b description
I typically also note down project specific information in there, like file paths and contact details of people involved.
Retrieval
Now I need a way of retrieving tasks by time. This is where the power of vim’s scripting comes in. I have the following functions in my .vimrc file.
function! Todo(day, ...)
" Doing the first separately to clear the location list
" before adding the first entry.
let _date = strftime("%d%m", localtime()+a:day*86400)
try
exec "lvimgrep /" . _date . "/j ~/wiki/*.txt"
catch /^Vim(\a\+):E480:/
endtry
if a:0 > 0
" These commands will add results to the location list.
for offset in range(a:day+1, a:1)
" Returns 0601 for 1st June.
let _date = strftime("%d%m", localtime()+offset*86400)
try
exec "lvimgrepadd /" . _date . "/j ~/wiki/*.txt"
catch /^Vim(\a\+):E480:/
endtry
endfor
endif
exec "lw"
endfunction
command! Today :call Todo(0)
command! Tomorrow :call Todo(1)
command! Dayafter :call Todo(2)
command! Week :call Todo(0, 7)
It uses the lvimgrep command to look for the date pattern in text files under my wiki directory. The date string is formed using the strftime function. The results are stored in the location list and is invoked using the command lw. The try-catch is used to handle an exception when lvimgrep does not find any results. The script works perfectly for me today and lets me manage my tasks from vim, though I am sure that it can be optimized a lot. Also all my project files, which contain tasks by project are stored in ~/wiki/ directory.
Now all I have to do is use :Today command to get today’s tasks. The :Week command retrieves tasks for the whole week from today. This opens the location list in a split window and you can open the file by hitting Enter on the corresponding line. I like to open the file in a separate tab, so I position the cursor on the filename in the result and use gf command. I have mapped gf as:
map gf :tabe <cfile><CR>
It is possible that the function strftime is not available on some platforms. In such cases you can exploit the fact that it is a plain text format and use native scripting language to extract the data out of vim. Here is an example of how shell scripting can be used to retrieve the same data.
alias today='egrep -H `date +%d%m` ~/wiki/*.txt' alias today+1="egrep -H =`date --date='tomorrow' +%d%m` ~/wiki/*.txt" alias tomorrow='today+1' alias today+2="egrep -H =`date --date='2 days' +%d%m` ~/wiki/*.txt" alias today+3="egrep -H =`date --date='3 days' +%d%m` ~/wiki/*.txt" alias today+4="egrep -H =`date --date='4 days' +%d%m` ~/wiki/*.txt" alias today+5="egrep -H =`date --date='5 days' +%d%m` ~/wiki/*.txt" alias today+6="egrep -H =`date --date='6 days' +%d%m` ~/wiki/*.txt" alias today+7="egrep -H =`date --date='7 days' +%d%m` ~/wiki/*.txt" alias week='today;tomorrow;today+2;today+3;today+4;today+5;today+6;today+7'
Syntax Highlighting
However, the task is still not visually parseable. So I used the following syntax file to make that happen.
" Vim syntax file " Language: Project " Maintainer: Abhijit Nadgouda (http://ifacethoughts.net/) syntax clear syn match projTag "\*[a-zA-z]*\*" syn match projJump "|[a-zA-z]*|" syn match projDate "=\d\d\d\d\d\d\d\d" contained syn match projDone "=done" contained syn match projHigh "=high" contained syn match projKeyword ":phone" contained syn match projKeyword ":email" contained syn match projKeyword ":chat" contained syn match projKeyword ":meet" contained syn match projTask "\-\s.*" contains=projDate,projDone,projHigh,projKeyword syn match projTaskH "\-\s.*=high" contains=projDate,projHigh,projKeyword syn match projTaskD "\-\s.*=done$" contains=projDate,projDone,projKeyword syn region projTaskDoc matchgroup=Comment start=/\[/ end=/\]/ hi def link projTag String hi def link projJump String hi def link projDate Constant hi def link projDone SpecialChar hi def link projHigh SpecialChar hi def link projKeyword SpecialChar hi def link projTask Statement hi def link projTaskDoc Comment hi def link projTaskD Comment hi def link projTaskH Special
I set the file type for every project file to project by including the line vim:sw=4:ts=4:ft=project at the end of every project file. The syntax file is called project.vim and place it in the syntax directory, which is ~/.vim/syntax/ for me. Needless to say even this can be optimized a lot. If the name project causes problems for you, you can use your name and set the filetype accordingly.
I also tag the projects so that they automatically become part of my wiki. Sometimes I also tag individual tasks, especially if I need to access some of them directly.
I now have a full-fledged task management system, which is organized by projects. Completed projects go in the archive and can be searched whenever required.
vim offers extreme extensibility from all aspects. Even you can modify the scripts, keywords and the syntax highlighting as per your needs and enjoy the power and convenience of vim.


