home

posts

notes

rss

Pomodoro timer in a terminal


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 the time_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_TIME
display_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-14
type: research journal
pomodori: 🍅🍅🍅🍅
---

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_TIME
sed -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
fi
fi

Putting it all together

#!/usr/bin/env bash
clear
 
# Paths and file names
JOURNAL="(⋯)/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 timer
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
}
 
# Start the work session
display_timer "Work left" $WORK_TIME
 
# Add a 🍅 emoji to the fourth line of the journal entry
sed -i '4s/$/🍅/' "$ENTRY"
 
# Prompt to take a break
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
fi
fi

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:

Demonstration of Pomodoro timer