Gerenciando minhas tarefas usando o VIT

Traduções: en
22/12/2020

Parte da série "Como eu me organizo":

  1. Gerenciando minhas tarefas usando o VIT
  2. Organização além do Taskwarrior

Dois anos atrás eu decidi organizar melhor minha vida. Durante esse tempo eu li o livro Getting Things Done e descobri o Taskwarrior, um gerenciador de tarefas para o terminal que não atrapalha.

Eu apreciei bastante a simplicidade e customizabilidade do Taskwarrior, mas depois de algum tempo, precisar digitar um comando para cada ação, como task list para listar as tarefas, se torna cansativo. Mesmo usando apelidos para encurtar os comandos, como tl para task list.

Buscando uma TUI para o Taskwarrior, eu encontrei o VIT. Eu na verdade só fiquei no VIT mesmo na segunda tentativa, já que na primeira vez que eu o encontrei, ele era escrito em Perl e não era uma interface muita boa. Mas com o lançamento do VIT 2.0 reescrito em python pelo thehunmonkgroup, ele se tornou a interface perfeita para o Taskwarrior.

Antes de eu começar a explicar sobre a minha configuração, tenha em mente que eu não vou entrar em detalhes sobre o GTD em si, então se você não estiver familiarizado com ele, talvez dê uma olhada em GTD in 15 minutes. As coisas vão fazer mais sentido.

Configuração do Taskwarrior

Para organizar minhas tarefas seguindo o método GTD, eu preciso adicionar alguns atributos (chamados UDAs) e relatórios personalizados no meu arquivo de configuração do Taskwarrior (.taskrc).

As minhas definições de UDAs são as seguintes:

uda.type.label = Type
uda.type.values = in,next,objective,someday,standby,cal

uda.priority.values = H,L,
urgency.uda.priority.H.coefficient = 6.0
urgency.uda.priority.L.coefficient = -6.0

uda.difficulty.type = string
uda.difficulty.label = Difficulty
uda.difficulty.values = H,L,

Isso adiciona três atributos diferentes. O primeiro e mais importante é o atributo type. Eu uso ele para atribuir uma tarefa a uma das principais listas do GTD:

  • in atribui a tarefa à lista "In", de entrada, onde eu coleto as tarefas inicialmente para tirá-las da minha cabeça.
  • next a coloca na lista "Next actions", de próximas ações, onde vivem as próximas tarefas que eu preciso fazer.
  • objective a atribui à lista "Projects", de projetos, onde cada tarefa descreve o objetivo de um dos meus projetos atuais, e guia a criação das tarefas em "Next actions" para cada um dos projetos.
  • someday coloca a tarefa na lista "Some day/maybe". Tarefas colocadas lá não precisam ser feitas logo, e podem até ser ideias incertas que talvez nunca sejam feitas. Apesar disso, quando eu tenho certeza de que não quero fazer uma tarefa, ela é deletada.
  • standby a atribui à lista "Waiting for". É nela que as tarefas que dependem da ação de outras pessoas ficam esperando.
  • cal coloca a tarefa na lista "Calendar", para que ela apareça no meu calendário, e com a data configurada.

O outro atributo é o priority, que eu uso para priorizar tarefas. Sem prioridade significa prioridade média. H significa prioridade alta, e L, prioridade baixa, e eles aumentam e diminuem a urgência da tarefa, respectivamente. O relatório next ordena as tarefas por urgência, então colocar prioridade alta faz a tarefa aparecer mais alto no relatório, fazendo com que chame mais minha atenção quando eu estiver passando pelo relatório de cima para baixo procurando a próxima tarefa para fazer.

O último atributo é o difficulty, que indica a dificuldade da tarefa, ou seja, quanta energia me custa para completá-la. Atualmente, eu raramente o uso, mas a ideia é poder, por exemplo, filtrar o relatório next com apenas as tarefas com difficulty igual a L (ou seja, as fáceis) sempre que eu estiver cansado.

Eu também penso em adicionar uma UDA duration para indicar, também usando H ou L, se a tarefa gasta mais ou menos tempo do que o normal para completar, para eu poder filtrar as tarefas com base no tempo que eu tiver disponível. Mas até o momento eu ainda não senti necessidade de adicioná-la.

Depois, tem os relatórios:

report.next.columns = id,start.age,priority,project,tags,recur,scheduled.countdown,due.relative,until.remaining,description.count,urgency
report.next.labels = ID,Active,P,Project,Tag,Recur,S,Due,Until,Description,Urg
report.next.sort = urgency-

report.all.columns = id,status.short,uuid.short,start.active,entry.age,end.age,type,depends.indicator,priority,project,tags.count,recur.indicator,wait.remaining,scheduled.remaining,due,until.remaining,description
report.all.labels = ID,St,UUID,A,Age,Done,Type,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description

report.all_valid.columns = id,status.short,uuid.short,start.active,entry.age,end.age,type,depends.indicator,priority,project,tags.count,recur.indicator,wait.remaining,scheduled.remaining,due,until.remaining,description
report.all_valid.labels = ID,St,UUID,A,Age,Done,Type,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description
report.all_valid.filter = (status:pending or status:waiting)

report.in.columns = id,description
report.in.description = Inbox
report.in.filter = status:pending limit:page (type:in)
report.in.labels = ID,Description

report.someday.columns = id,description.count
report.someday.description = Someday/Maybe
report.someday.filter = limit: type:someday status:pending
report.someday.labels = ID,Description

report.standby.columns = id,priority,project,due.relative,description.count,urgency
report.standby.description = WaitingFor
report.standby.labels = ID,P,Project,Due,Description,Urgency
report.standby.filter = limit: type:standby status:pending +READY
report.standby.sort = urgency-

report.objectives.columns = id,priority,project,description.count,urgency
report.objectives.description = Projects
report.objectives.labels = ID,P,Project,Description,Urgency
report.objectives.filter = limit: type:objective status:pending +UNBLOCKED
report.objectives.sort = urgency-

report.type.columns = id,description,type
report.type.description = Type
report.type.filter = status:pending limit:page
report.type.labels = ID,Description,Type

report.cal.columns = id,entry.age,recur.indicator,scheduled,due,description
report.cal.description = Calendar
report.cal.filter = type:cal status:pending limit:page
report.cal.labels = ID,Age,R,Scheduled,Due,Description
report.cal.sort = scheduled

Eu tenho um relatório para cada um dos tipos mencionados para que eu possa ver a lista de tarefas para cada um deles: in, next, objectives (com um 's'), someday, standby e cal. Além disso, também há o relatório all que mostra todas as tarefas, o all_valid que é igual ao all mas não mostra as tarefas concluídas e deletadas, e o relatório type que mostra cada uma das tarefas e seu tipo.

Configuração do VIT

Se configurar o Taskwarrior consiste em configurar os atributos e relatórios para permitir o meu fluxo de trabalho, configurar o VIT consiste em configurar os atalhos para tornar esse fluxo o mais fluido possível.

Os meus atalhos (que são definidos no arquivo config.ini dentro da pasta .vit) são os seguintes:

q = {ACTION_QUIT}

a = {ACTION_NOOP}
aa = {ACTION_TASK_ADD}
ai = aatype:in<Space>
an = aatype:next<Space>
ap = aatype:objective project:
as = aatype:someday<Space>
aw = aatype:standby<Space>
ac = aatype:cal schedule:
aN = aatype:next project:{TASK_PROJECT}<Space>
aP = aaproject:{TASK_PROJECT}<Space>

gi = :in<Enter>
gn = :next<Enter>
gp = :objectives<Enter>
gs = :someday<Enter>
gw = :standby<Enter>
gc = :cal<Enter>
gl = :list<Enter>
ga = :all_valid<Enter>
gA = :all<Enter>
gP = :all_valid project:{TASK_PROJECT}<Enter>

M = m{TASK_DESCRIPTION}
S = :!r task modify type:someday {TASK_UUID}<Enter>
W = :!r task modify type:standby {TASK_UUID}<Enter>
P = :!r task modify {TASK_UUID} priority:
F = :!r task modify {TASK_UUID} difficulty:
Y = :!r task duplicate {TASK_UUID}<Enter>

#$ = :!r task sync<Enter>
zp = gpf{STUCK_PROJS}<Enter>
o = :! taskopen {TASK_UUID}<Enter>
C = :!r taskwarrior-update_context<Enter>

# Convenience mappings
1 = :1
2 = :2
3 = :3
4 = :4
5 = :5
6 = :6
7 = :7
8 = :8
9 = :9

- = m-
+ = m+

Os atalhos principais são os que começam com a ou g. Esses são os que eu uso o dia todo. Os que começam com a são para adicionar tarefas, com um deles para cada tipo, para que eu possa rapidamente adicionar uma tarefa de qualquer tipo. Já os que começam com g são para ir para um relatório, então eu também consigo rapidamente mudar para um relatório específico e ver as tarefas desse tipo.

Mas alguns deles são especiais. Por exemplo, aN adiciona uma tarefa do tipo next e com o projeto igual ao da tarefa atualmente selecionada. Eu uso isso quando eu estou revisando meus projetos atuais no relatório objectives e quero adicionar uma tarefa para a próxima ação do projeto que está selecionado.

gP é outro que eu uso bastante. Ele mostra todas as tarefas com o mesmo projeto da tarefa atualmente selecionada. Eu uso isso quando estou olhando uma tarefa de um projeto e quero ver todas as outras tarefas desse projeto, como o objetivo definido pela tarefa objective, as próximas ações mostradas pelas tarefas next, se há coisas esperando por outras pessoas em standby ou algo marcado no calendário em cal.

Aí vem alguns atalhos de utilidades usados menos frequentemente. M edita a descrição da tarefa selecionada. S e W mudam o tipo da tarefa selecionada para someday e standby, respectivamente. O primeiro é útil quando eu decido que uma tarefa pode ser feita mais para o futuro ao invés de agora, enquanto o segundo não é muito usado. P edita a prioridade da tarefa, enquanto F, sua dificuldade. Y duplica a tarefa selecionada.

O $ que está comentado é usado para sincronizar as tarefas para um servidor do Taskwarrior central usando o Taskserver. Era essencial para manter as tarefas sincronizadas entre meu computador e meu celular mesmo quando eu saía. Mas como ultimamente sair não tem sido uma atividade frequente, fazendo com que eu sempre esteja no meu computador, eu desabilitei esse atalho por enquanto.

O atalho zp é um que é um pouco mais complicado, mas muito útil. Ele vai para o relatório objectives, que mostra meus projetos atuais, e filtra para que só os projetos sem tarefas next sejam mostrados. É importante no GTD sempre garantir que todos os seus projetos tenham próximas ações atribuídas a eles, para que o próximo passo para avançá-los seja óbvio. Com esse atalho, durante minha revisão semanal de todas as tarefas, eu posso facilmente ver quais projetos não possuem próximas ações e então criar uma para cada projeto usando o aN já mostrado. Se você estiver se perguntando o que o {STUCK_PROJS} significa, não se preocupe, eu já vou explicá-lo.

o usa o taskopen na tarefa atualmente selecionada, e esse é outro que eu uso o tempo todo. O que o taskopen faz é ler as anotações da tarefa e abrir uma delas (perguntando qual, se houver múltiplas opções). Se a anotação for uma URL, ela vai ser aberta no navegador de internet. Se for a palavra "Notes", o taskopen vai abrir o arquivo de texto associado a essa tarefa (criando ele se essa for a primeira vez) onde você pode escrever anotações mais compridas. Esses dois são os que eu conheço e uso o tempo todo. Anotações com texto normal são ignoradas pelo taskopen.

O atalho C roda um script para atualizar o contexto atual do Taskwarrior, mas eu normalmente não preciso rodá-lo já que ele executa automaticamente como explicado no artigo "Detecção de contexto automática para o Taskwarrior".

Por fim, eu tenho alguns atalhos só por conveniência. Cada um dos dígitos mapeia para : seguido por esse dígito, para que eu possa pular para uma tarefa pressionando uma tecla a menos. Mais detalhadamente, normalmente para pular para a tarefa de id 42 eu precisaria digitar :, 4, 2 e <Enter>. Com esse atalho, eu não preciso do :, então eu só digito 4, 2 e <Enter>. Como é muito comum pular para tarefas no VIT, essa uma tecla a menos vale a pena. Além disso, - e + mapeiam para m- e m+, respectivamente, sendo que m é o comando padrão para modificar a tarefa, então para adicionar um rótulo em uma tarefa, por exemplo, eu só preciso pressionar +, escrever o nome do rótulo e apertar <Enter>.

Substituição de variável no VIT

Você deve ter notado alguns {ALGUMA_COISA} nos atalhos do VIT acima. Eu só queria dar uma pequena explicação sobre eles (já que uma explicação completa deve ser lida na documentação do VIT) e também mostrar a minha substituição de variável personalizada.

Em primeiro lugar, o {ACTION_QUIT} no atalho q não é uma substituição de variável, apesar da sintaxe ser parecida (a diferença sendo que ela é a única coisa depois do =). Isso é só uma das ações do VIT que podem ser mapeadas. Uma substituição de variável ocorre no atalho aN por exemplo:

aN = aatype:next project:{TASK_PROJECT}<Space>

Aqui, {TASK_PROJECT} vai ser substituído pelo atributo de projeto da tarefa atualmente selecionada. Então é por isso que esse atalho faz o que ele faz. O aa no começo está mapeado para a ação de adicionar uma nova tarefa, e então o tipo é colocado como next e o projeto, como o projeto da tarefa selecionada. Todos os {TASK_*} são substituições de variável já integradas do VIT, que podem ser usadas para qualquer um dos atributos das tarefas (incluindo UDAs).

Agora, no caso do atalho zp:

zp = gpf{STUCK_PROJS}<Enter>

{STUCK_PROJS} é uma substituição de variável personalizada que eu criei. Foi bem simples, eu só segui o CUSTOMIZE.md do VIT.

Dentro da minha pasta .vit, eu adicionei um keybinding/keybinding.py com o seguinte:

from task_proj_stuck import get_stuck_proj_ids

class Keybinding():
    def replacements(self):
        def _custom_match(variable):
            if variable in ['STUCK_PROJS']:
                return [variable]
        def _custom_replace(task, arg):
            if arg == 'STUCK_PROJS':
                return ' '.join(list(get_stuck_proj_ids()))
        return [
            {
                'match_callback': _custom_match,
                'replacement_callback': _custom_replace,
            },
        ]

E eu tenho um módulo python task_proj_stuck com o seguinte:

from tasklib import TaskWarrior

tw = TaskWarrior()

def get_stuck_projects():
    """ Get taskwarrior projects that don't have any next actions assigned to
    them.  Next actions here mean actions of type 'next', 'standby' or 'cal',
    either pending or waiting. """

    projects = tw.tasks.pending().filter('+READY', type='objective')

    next_tasks = tw.tasks.filter('(status:pending or status:waiting) and type:next')
    standby_tasks = tw.tasks.filter('(status:pending or status:waiting) and type:standby')
    cal_tasks = tw.tasks.filter('(status:pending or status:waiting) and type:cal')

    for project in projects:
        count_next = len(next_tasks.filter(project=project['project']))
        count_standby = len(standby_tasks.filter(project=project['project']))
        count_cal = len(cal_tasks.filter(project=project['project']))
        if count_next + count_standby + count_cal == 0:
            yield project


def get_stuck_proj_ids():
    return (str(project['id']) for project in get_stuck_projects())

Então o que acontece é que a função get_stuck_proj_ids() retorna um gerador contendo o id de cada tarefa objective cujo projeto não possui nenhuma tarefa next. A substituição de variável {STUCK_PROJS} então apenas chama essa função e junta os ids usando espaço.

Por exemplo, suponha que existem os projetos limpar-quarto e escrever-artigo-vit. A tarefa objective do projeto limpar-quarto tem id 42 e a tarefa do projeto escrever-artigo-vit tem id 99. Ambos os projetos não tem nenhuma tarefa next, enquanto todos os outros projetos têm. Então, pressionando zp, o VIT vai executar gpf42 99<Enter>, que vai para o relatório objectives e filtra apenas pelas tarefas 42 e 99, para que eu possa focar em adicionar as próximas tarefas para cada um desses projetos empacados com aN. Bem conveniente, não?

Hooks e taskpirate

Outra poderosa funcionalidade do Taskwarrior que permite extensibilidade é a API de hooks. Se você já usou Git, talvez já esteja familiarizado com esse conceito. Ele permite que um script personalizado execute quando um certo evento ocorrer no programa, nesse caso, no Taskwarrior.

Ao invés de criar um hook diretamente no Taskwarrior, eu decidi usar o taskpirate, que torna as tarefas mais diretamente acessíveis em python. E como você já deve saber, eu gosto de python.

Atualmente eu tenho um único hook chamado pirate_add_inherit.py, dentro da pasta hooks, e o que ele faz é tornar certos atributos herdáveis da tarefa objectives de um projeto. O código é o seguinte:

#!/bin/python

from tasklib import TaskWarrior

def hook_inherit(task):
    if task['project'] and task['type'] != 'objective':
        tw = TaskWarrior('/home/nfraprado/.task/data')
        try:
            obj_task = tw.tasks.filter(type='objective', project=task['project'])[0]
        except:
            return

        for field in ('due', 'priority'):
            if not task[field] and obj_task[field]:
                task[field] = obj_task[field]

Como ele é um hook add, ele executa toda vez que uma nova tarefa é criada. O que ele faz é o seguinte: sempre que uma tarefa é criada com um projeto, seus atributos due e priority são colocados iguais aos valores que tiverem na tarefa objective do projeto, a não ser que eles tenham sido configurados explicitamente na nova tarefa.

Demonstração

Depois de falar tanto, eu te devo no mínimo alguns gifs mostrando como tudo isso funciona. Vale dizer que as tarefas mostradas a seguir não são minhas tarefas reais, caso contrário você veria mais de cem no relatório someday, por exemplo.

No primeiro gif, eu mudo para cada um dos relatórios (next, in, standby, objectives e someday) usando os atalhos g, e então adiciono uma tarefa in usando ai e em seguida uso S para movê-la para someday. Eu também uso o atalho padrão <Enter> para inspecionar a tarefa. Você também pode me ver pulando para tarefas usando usando o id e pesquisando por uma string usando o atalho padrão /.

Navegação e criação de tarefas no VIT usando meus atalhos personalizados

Em asciinema.org

Nesse segundo gif, eu uso o atalho padrão A para anotar uma tarefa com um texto simples e em seguida com a string "Notes". Então eu uso o para fazer o taskopen abrir uma nota para a tarefa onde eu escrevo mais anotações.

Anotação de tarefas e uso do taskopen no VIT

Em asciinema.org

Nesse último gif, eu uso o atalho zp para mostrar apenas os projetos empacados, e então uso aN em dois deles para criar tarefas next. Por fim, eu uso gP em uma tarefa para mostrar apenas as tarefas de seu projeto. Aqui você também pode ver o efeito do hook, já que a tarefa Proxima acao 1 tem os mesmos valores nos atributos priority e due da tarefa Novo Projeto 1, sendo que eu não os especifiquei.

Uso dos meus atalhos personalizados zp, aN e gP para facilitar o acompanhamento de projetos no VIT

Em asciinema.org

E é isso. Se isso te interessou, dê uma olhada no VIT. Eu apenas mostrei meus atalhos e meu fluxo de trabalho com ele, mas o VIT é capaz de muito mais.

Por fim, esse artigo apenas cobriu as coisas relacionadas a gerenciar tarefas e ao VIT. Ainda há outras partes importantes da minha organização para serem explicadas, como como eu vejo meu calendário e como eu mantenho uma rotina. Eu vou falar sobre elas no próximo artigo.