I usually use the Pomodoro Technique to schedule my work: intervals of 25 minutes of work, followed by a 5 minute break. Traditionally, I used pomofocus.io as a timer in my browser that alerts me when an interval is over. As I often have a terminal emulator opened somewhere — and as I wanted to keep track of the number of finished pomodori in Obsidian, it made more sense to create my own shell script that:
- Starts a 25 minute timer, alerting me when time runs out
- Gives me the option to take a break, starting a 5 minute timer
- Keeps alternating between work and break intervals until I stop working
- Keeps track of finished pomodori for the day in my Obsidian work vault
Displaying a timer
Below, we define a function that takes a label (e.g., working or taking a break) and the amount of seconds the countdown should take. We start by moving the text cursor to the upper left corner using echo -ne '\033[2A'
1, and then, as long as our variable time_left
is larger than -1:
- Show the label in bold, using
\033[1m• $label\033[0m
- Format remaining time using
$(date -d@$time_left -u +%M:%S)
, so that it shows as remaining time in minutes and seconds, instead of just seconds - Use
\033[0K
to delete text to the end of the line, and\r
to move to the beginning of the current line2 - Wait one second using
sleep
, and substract 1 from thetime_left
variable
display_timer() { local label=$1 local time_left=$2 echo -ne '\033[2A' while [ $time_left -gt -1 ]; do echo -ne "\033[1m• $label\033[0m — $(date -d@$time_left -u +%M:%S)\033[0K\r" sleep 1 ((time_left--)) done}
We can start and display our work timer and break timer as shown below. We start a 25 minute timer (displayed as • Work left — MM:SS
), immediately followed by a 5 minute timer (displayed as • Break left — MM:SS
):
WORK_TIME=$((60 * 25)) # Work time (25 minutes)BREAK_TIME=$((60 * 5)) # Break time (5 minutes) display_timer "Work left" $WORK_TIMEdisplay_timer "Break left" $BREAK_TIME
Keeping track of 🍅’s
In my Obsidian journal, I have daily notes where I keep track of work done, to-dos, and ideas. I recently added a pomodori
field to the properties of my Markdown files, where I keep track of how many work intervals I have done that day:
---date: 2024-08-14type: research journalpomodori: 🍅🍅🍅🍅---
I want this script to automatically add a new interval to this property, by first defining the path to today’s note and then appending a 🍅 to the end of the line of that property using sed
. As the property will always be on the fourth line of my notes, we can write:
WORK_TIME=$((60 * 25)) # Work time (25 minutes)BREAK_TIME=$((60 * 5)) # Break time (5 minutes)JOURNAL="(⋯)/work/Research Journal" ENTRY="$JOURNAL/$(date +%Y-%m-%d) Research Journal.md" display_timer "Work left" $WORK_TIMEsed -i '4s/$/🍅/' "$ENTRY" display_timer "Break left" $BREAK_TIME
User input to keep going
Using zenity
, we can request user input to check if we want to switch from working to taking a break, and vice versa. We use exec "$(readlink -f "$0")"
3 to restart the script when switching from taking a break back to working:
if zenity --question --title="Pomodoro" --text="Time for a break?"; then display_timer "Break left" $BREAK_TIME if zenity --question --title="Pomodoro" --text="Time to work?"; then exec "$(readlink -f "$0")" # Restart the script for another Pomodoro cycle fifi
Putting it all together
#!/usr/bin/env bashclear # Paths and file namesJOURNAL="(⋯)/work/Research Journal"ENTRY="$JOURNAL/$(date +%Y-%m-%d) Research Journal.md" # Pomodoro timers (in seconds)WORK_TIME=$((60 * 25)) # Work time (25 minutes)BREAK_TIME=$((60 * 5)) # Break time (5 minutes) # Function to display the timerdisplay_timer() { local label=$1 local time_left=$2 echo -ne '\033[2A' while [ $time_left -gt -1 ]; do echo -ne "\033[1m• $label\033[0m — $(date -d@$time_left -u +%M:%S)\033[0K\r" sleep 1 ((time_left--)) done} # Start the work sessiondisplay_timer "Work left" $WORK_TIME # Add a 🍅 emoji to the fourth line of the journal entrysed -i '4s/$/🍅/' "$ENTRY" # Prompt to take a breakif zenity --question --title="Pomodoro" --text="Time for a break?"; then display_timer "Break left" $BREAK_TIME if zenity --question --title="Pomodoro" --text="Time to work?"; then exec "$(readlink -f "$0")" # Restart the script for another Pomodoro cycle fifi
I have added clear
to the beginning of the script to clear the terminal screen before the script runs, just to ensure a clean display of the timer: