simfish:

Emacs/Tasks

Tasks is org-mode based productivity workflow useful for capturing organizing, and tracking task information.

cover image


Requirements


Introduction

This workflow uses Emacs org-mode features for capturing, managing, and tracking tasks. Once a task is captured it is added to a default inbox org file. Using org-mode todo states and calendar scheduler the task start date, deadline can be added. To track time spent working on a task, org Pomodoro timer is used. Tasks captured in different files are also integrated into a single view using org-super-agenda.

Usage

CLOCK: [2026-06-24 Wed 21:52]–[2026-06-24 Wed 21:52] => 0:00

Clone repository

git clone https://codeberg.org/distroconfig/tasks

Switch to directory and build it,

cd tasks && guix ld build tasks

Run Emacs and launch tasks using M-x workflow::select. When prompted for input, select tasks. Follow the on screen instruction.

Customizations

The default inbox file

(defcustom tasks:default-inbox "${HOME}/Documents/planner/inbox.org"
  "specifies the default inbox file for saving captured information"
  :type '(file)
  :group 'tasks)

capture template

(setq org-capture-templates
      `(("t"
         "Task"
         entry
         (file+headline  tasks::inbox-location    "Tasks")
         "* TODO %?\nDEADLINE:%^t\n%i\n/see/: %a")))

Task search paths

(defcustom agenda::search-paths
 `( ,(expand-file-name "~/Documents/KB")
    ,(expand-file-name "~/Documents/planner")
    ,(expand-file-name "~/Documents/research/notes"))
  "List of static directory paths where org-agenda files are stored."
  :type '(file))

Key Bindings

Capturing information

(kb::def :states 'normal
  "o"    '(:ignore t            :which-key    "Org" )
  "oc"   '(org-capture          :which-key    "Capture")
  "oi"   '(tasks::find-inbox    :which-key    "Inbox"))

Reviewing information

(kb::def :states 'normal
  "o"  '(:ignore t "Org")
  "oa" '(tasks::org-agenda-open   :which-key "agenda"))

Org pomodoro time tracking,

 (kb::def :states 'normal
   "o"  '(:ignore t "Org")
   "op" '(org-pomodoro     :which-key "🍅 pomodoro"))

Commands

Capture commands

org-capture

Agenda commands

tasks::org-agenda-open

Functions

Inbox Location

(defun tasks::inbox-location ()
  "Returns the location for storing captured information."
  (require 'fs)
  (let ((inbox-file (substitute-in-file-name  tasks:default-inbox)))
    ;;setup capture file path
    (fs::dir-ensure (file-name-directory inbox-file))
    inbox-file))

Open Inbox

(defun  tasks::find-inbox ()
  "Opens the current project's inbox.org file"
  (interactive)
  (require 'fs)
  (let ((inbox-file  (tasks::inbox-location)))
    (find-file inbox-file)))

Agenda

(defun tasks::org-agenda-load ()
  "scan and initialize  `org-agenda-files'"
  (require 's)
  (let ((search-path-list agenda::search-paths)
        (cmd                   nil)
        (r                     nil)
        (org-agenda-files      nil)
        (org-agenda-file       nil))

    (setq cmd (with-temp-buffer
                (cl-loop
                 for p in search-path-list
                 do (insert "ag -l -U --hidden --org 'TODO|WORKING|BLOCKED|DEADLINE|SCHEDULED' " "\""p"\""  " | grep -v 'forks'" "\n" ))
                (buffer-string)))

    (setq r   (shell-command-to-string cmd))
    (setq r   (s-lines r))

    (while (setq org-agenda-file (car r))
      (setq r (cdr r))
      (when (and  (stringp org-agenda-file)
                  (not (string-empty-p org-agenda-file))
                  (file-exists-p org-agenda-file))
        (cl-pushnew  (expand-file-name org-agenda-file)  org-agenda-files)))

    org-agenda-files))


(defun tasks::org-agenda-open ( )
  "Launches current project agenda browser and the journal directory."
  (interactive)
  (require 'log)
  (require 'org-super-agenda)
  (setq org-agenda-files (tasks::org-agenda-load))
  (org-agenda nil "c" ))

Task States: task state is tracked using org todo states. Time spent on a task can also be tracked using org-pomodoro package.

(defvar tasks:pomodoro-ignore-state-changed nil)

(defun tasks::start-or-resume-clock ( )
  "Start or resume org pomodoro clock"
  (require 'org-pomodoro)
  (unless  tasks:pomodoro-ignore-state-changed    
    (cond (;; no pomodoro started yet?
	   (eq org-pomodoro-state :none)
	   (org-pomodoro))
          (;; is pomodoro on a short break?
	   (or 
	    (eq org-pomodoro-state :break)
	    (eq org-pomodoro-state :long-break))
	   (let ((org-pomodoro-keep-killed-pomodoro-time t))
	     (org-pomodoro-killed)
	     (org-pomodoro)))
          (;; nothing to do
	   t))))

(defun tasks::stop-clock ()
  "Stop current org pomodoro clock"
  (require 'org-pomodoro)
  (when (or (eq org-pomodoro-state  :pomodoro)
	    (eq org-pomodoro-state  :break)
	    (eq org-pomodoro-state  :long-break))
	   (let ((org-pomodoro-keep-killed-pomodoro-time t))
	     (org-pomodoro-killed))))
             

(defun tasks::pomodoro-on-break-finished ( )
  "Update task state to WORKING and resume pomodoro."
  (let ((tasks:pomodoro-ignore-state-changed t)
        (m (car org-clock-history)))
    (save-window-excursion
      (pop-to-buffer-same-window (marker-buffer m))
      (if (or (< m (point-min)) (> m (point-max))) (widen))
      (goto-char m)
      (cond ((yes-or-no-p "Ready for another pomodoro session?")
             (org-todo "WORKING")
             (org-show-entry)
             (org-pomodoro))
            (t
             (org-todo "TODO")
             (org-show-entry))))))

             
(defun tasks::pomodoro-on-finished ( )
  (save-window-excursion
    (let ((m (car org-clock-history))
          (tasks:pomodoro-ignore-state-changed t))
      (pop-to-buffer-same-window (marker-buffer m))
      (when (or
             (< m (point-min))
             (> m (point-max)))
        (widen))
      (goto-char m)
      (org-todo "TODO")
      (org-show-entry))))


(defun tasks::pomodoro-on-started ( )
  (save-window-excursion
    (let ((m org-clock-marker)
          (tasks:pomodoro-ignore-state-changed t))
      (pop-to-buffer-same-window (marker-buffer m))
      (when (or
             (< m (point-min))
             (> m (point-max)))
        (widen))
      (goto-char m)
      (org-todo "WORKING")
      (org-show-entry))))

(defun tasks::on-todo-state-changed ()
  "Tasks TODO state changed"
  (require 'org-pomodoro)
  (unless (or tasks:pomodoro-ignore-state-changed
              (eq org-pomodoro-state :none)
              (eq org-state "WORKING")
              (eq org-state "WORKING"))
    (cond
     (;; * -> WORKING: start or resume clock?
      (equal org-state "WORKING")
      (tasks::start-or-resume-clock))
     
     (;; WORKING -> *
      (equal org-last-state "WORKING") 
      (tasks::stop-clock))
     
     (;; ignore anything else 
      t))))

Packages

Package: org-capture

(use-package org-capture
  :demand t
  :commands
  <<org-capture-commands>>
  :general
  <<org-capture-kb>>
  :config
  <<org-capture-config>>)

Package: org-agenda

(use-package org-agenda
  :demand t
  :commands
  <<org-agenda-commands>>
  :init
  (defalias 'tasks::org-agenda-open-super   'org-super-agenda)
  <<org-agenda-init>>
  :general
  <<org-agenda-kb>>
  :config
  (setq org-agenda-todo-list-sublevels nil)   
  (require 'org-super-agenda))

Package: org-super-agenda

(use-package ts
  :guix (:name emacs-ts))

(use-package org-super-agenda
  :guix (:name emacs-org-super-agenda)
  :demand t
  :after org-agenda
  :init
  (setq org-agenda-skip-scheduled-if-done t
        org-agenda-skip-deadline-if-done t
        org-agenda-include-deadlines nil
        org-agenda-block-separator nil
        org-agenda-compact-blocks t
        org-agenda-start-day nil ;; i.e. today
        org-agenda-span 1
        org-agenda-start-on-weekday nil)
  (setq org-agenda-custom-commands
        '(("c" "Super view"
           ((alltodo  "" ((org-agenda-overriding-header "")
                         (org-super-agenda-groups
                          '((:log t)
                            (:name "Overdue\n"   :deadline past  :order 1)
                            (:name "Important\n" :priority "A"   :order 2)
                            (:name "Due Today\n" :deadline today :order 3)
                            (:name "Tasks\n"     :tag "@incoming" :order 15)
                            (:name "Weekly\n"    :deadline today :order 6)
                            (:name "Monthly\n"   :deadline today :order 7)
                            (:name "Scheduled Soon\n" :scheduled future :order 4)
                            (:discard (:anything t))
                            ))))))))
  :config
  (org-super-agenda-mode))

Package: silver searcher

(use-package the-silver-searcher
 :guix (:name the-silver-searcher)
 :disabled)

Package: pomodoro

(use-package org-pomodoro
  :guix (:name emacs-org-pomodoro)
  :demand t
  :commands
  org-pomodoro
  :hook
  (org-after-todo-state-change    . tasks::on-todo-state-changed)
  (org-pomodoro-finished          .  tasks::pomodoro-on-finished)
  (org-pomodoro-break-finished    .  tasks::pomodoro-on-break-finished)
  (org-pomodoro-started           .  tasks::pomodoro-on-started)
  :custom
  (org-pomodoro-length 24)
  (org-pomodoro-ask-upon-killing nil)
  (org-pomodoro-format "%s") 
  (org-pomodoro-short-break-format "%s")
  (org-pomodoro-long-break-format  "%s")
  (org-pomodoro-play-sounds t)
  :custom-face
  (org-pomodoro-mode-line ((t (:foreground "#E9967a"))))
  (org-pomodoro-mode-line-break   ((t (:foreground "#90ee90")))))

Sox package

(use-package sox
  :guix (:name sox)
  :disabled)