Parte da série "Como eu me organizo":
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.
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.
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>
.
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?
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.
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 /
.
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.
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.
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.