Compare commits

...

15 commits

Author SHA1 Message Date
ee7b60c65e
Merge pull request #16 from maikol-solis/master
fix: context writing in CSV file for third-time implementation
2025-02-02 15:02:50 +03:00
Maikol Solís
feaca3836b
fix: context writing in CSV file for third-time implementation 2025-01-30 03:48:00 -06:00
ba0b308886 pomm: fix byte-compile (x2) 2024-07-10 00:56:26 +03:00
1b06edc33a pomm: fix byte-compile for Emacs 29 2024-07-10 00:52:33 +03:00
4fc8cfc8a7 readme: add link to comment 2024-07-10 00:43:14 +03:00
2acf51043c pomm: add org-clock integration 2024-07-10 00:36:35 +03:00
14c7b42e15 readme: fix typos 2024-07-10 00:09:10 +03:00
a95343f643
Merge pull request #12 from juergenhoetzel/file-defcustom
Use the more specific 'file type for file-based custom variables
2024-01-31 00:26:09 +03:00
Juergen Hoetzel
121163d149 Use the more specific 'file type for file-based custom variables 2024-01-30 21:58:29 +01:00
a717c3a053 pomm: add Published-At & update copyright 2023-12-26 02:08:28 +03:00
12ce6a68c7 ci: fix ci 2023-06-18 16:24:32 +03:00
bc42fdfa77 ci: update python 2023-06-02 23:46:23 +03:00
d05d9cb333
Merge pull request #10 from vale981/total_time
display total time worked, braked and tracked
2023-06-02 23:45:40 +03:00
Valentin Boettcher
39fd164b1d
display total time worked, braked and tracked 2023-06-02 16:02:11 -04:00
42f03d6ff2 fix: run the right hook on reset 2022-08-15 13:24:40 +05:00
4 changed files with 153 additions and 57 deletions

View file

@ -10,10 +10,11 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v1
with: { python-version: 3.6 }
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install
run: |
python -m pip install --upgrade pip

View file

@ -36,30 +36,31 @@ The package requires Emacs 27.1 because the time API of the previous versions is
** Pomodoro
Run =M-x pomm= to open the transient buffer.
The listed commands are rather self-descriptive and match the Pomodoro ideology.
The listed commands are self-descriptive and match the Pomodoro ideology.
The timer can have 3 states:
- *Stopped*. Can be started with "s" or =M-x pomm-start=. A new iteration of the timer will be started.
- *Paused*. Can be continuted with "s" / =M-x pomm-start= or stopped competely with "S" / =M-x pomm-stop=.
- *Paused*. Can be continued with "s" / =M-x pomm-start= or stopped completely with "S" / =M-x pomm-stop=.
- *Running*. Can be paused with "p" / =M-x pomm-pause= or stopped with "S" / =M-x pomm-stop=.
The state of the timer can be reset with "R" or =M-x pomm-reset=.
"u" updates the transient buffer. The update is manual because I didn't figure out how to automate this, and I think this is not /really/ necessary.
With "r" or =M-x pomm-set-context= you can set the current "context", that is some description of the task you are currently working on. This description will show up in history and in the csv file. Also, =M-x pomm-start-with-context= will prompt for the context and then start the timer.
With "r" or =M-x pomm-set-context= you can set the current "context", that is some description of the task you are currently working on. This description will show up in history and in the CSV file. Also, =M-x pomm-start-with-context= will prompt for the context and then start the timer.
** Third Time
Run =M-x pomm-third-time= to open the transient buffer for the Third Time technique.
[[./img/screenshot-tt.png]]
Essentially, the techique is designed aroud the formula:
Essentially, the technique is designed around the formula:
#+begin_example
Time of break = 1/3 x Time of work.
Time of break = 1/3 x Time of work.
#+end_example
I.e. you work as long as you want or need, and then take a break with the maximum duration =1/3= of the time worked. If you take a shorter break, the remaining break time is saved and added to the next break within the same session. [[https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work][Here is a more detailed explanation]].
I.e., you work as long as you want or need, and then take a break with the maximum duration of =1/3= of the time worked. If you take a shorter break, the remaining break time is saved and added to the next break within the same session. [[https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work][Here is a more detailed explanation]].
The Third Time timer can have 2 states:
- *Stopped*. Can be started with "s" or =M-x pomm-third-time-start=.
@ -77,7 +78,7 @@ The package sends alerts via =alert.el=. The default style of alert is a plain =
#+end_src
** Sounds
By default sounds are disabled. Set =pomm-audio-enabled= to =t= to toggle them. Set =pomm-audio-tick-enabled= to =t= if you want the ticking sound.
By default, sounds are disabled. Set =pomm-audio-enabled= to =t= to toggle them. Set =pomm-audio-tick-enabled= to =t= if you want the ticking sound.
This functionality needs =pomm-audio-player-executable= to be set so that the program could be invoked like: =<executable> /path/to/sound.wav=.
@ -108,7 +109,7 @@ interval = 1
#+end_src
** State file location
To implement pesistence between Emacs sessions, the package stores its state in the following files:
To implement persistence between Emacs sessions, the package stores its state in the following files:
- =pomm-state-file-location=, =.emacs.d/pomm= by default
- =pomm-third-time-state-file-location=, =/.emacs.d/pomm-third-time= by default
@ -123,7 +124,7 @@ The file for the Pomodoro technique has the following columns:
- =iteration=
- =context=
One for the Third Time technique has an extra column called =break-time-remaining=.
The one for the Third Time technique has an extra column called =break-time-remaining=.
A new entry is written after a particular state of the timer comes into being.
@ -133,21 +134,35 @@ To customize timestamp, set the =pomm-csv-history-file-timestamp-format= variabl
#+end_src
The format is the same as in =format-time-string=.
** Usage with =org-clock=
The package can be used with [[https://orgmode.org/manual/Clocking-commands.html][org-clock]] in the following way. Set up these two hooks:
#+begin_src emacs-lisp
(add-hook 'pomm-on-status-changed-hook #'pomm--sync-org-clock)
(add-hook 'pomm-third-time-on-status-changed-hook
#'pomm-third-time--sync-org-clock
#+end_src
Then, start the timer (either =pomm= or =pomm-third-time=) and =org-clock-in=, in whichever order. The package will call =org-clock-out= when a break starts and =org-clock-in-last= when it ends.
Setting =pomm-org-clock-in-immediately= to =nil= "defers" calling =org-clock-in-last= until after any command from the user (via =post-command-hook=). I've added this because I occasionally return to my PC a few minutes after the break ends, so I don't want these minutes to show up in =org-clock=.
Also see [[https://github.com/SqrtMinusOne/pomm.el/issues/13#issuecomment-2216868331][this comment]] ([[https://github.com/SqrtMinusOne/pomm.el/issues/13][#13]]) for an alternative approach.
* Alternatives
There is a number of packages with a similar purpose, here is a rough comparison of features:
| Package | 3rd party integrations | Control method (1) | Persistent history | Persistent state | Notifications |
|------------------------+------------------------+--------------------------------+--------------------------+----------------------------------------------+---------------------------|
| [[https://github.com/SqrtMinusOne/pomm.el][pomm.el]] | - | transient.el | CSV | + | alert.el + sounds |
| [[https://github.com/marcinkoziej/org-pomodoro/tree/master][org-pomodoro]] | Org Mode! | via Org commands | via Org mode | - | alert.el + sounds |
| [[https://github.com/TatriX/pomidor/][pomidor]] | - | self-cooked interactive buffer | custom delimited format? | +, but saving on-demand | alert.el + sounds |
| [[https://github.com/baudtack/pomodoro.el/][pomodoro.el]] | - | - | - | - | notifications.el + sounds |
| [[https://github.com/konr/tomatinho/][tomatinho]] | - | self-cooked interactive buffer | - | - | message + sounds |
| [[https://github.com/ferfebles/redtick][redtick]] | - | mode-line icon | + | - | sounds |
| [[https://github.com/abo-abo/gtk-pomodoro-indicator][gtk-pomodoro-indicator]] | GTK panel | CLI | - | -, but the program is independent from Emacs | GTK notifications |
| Package | 3rd party integrations | Control method (1) | Persistent history | Persistent state | Notifications |
|------------------------+------------------------+--------------------------------+--------------------------+--------------------------------------------+---------------------------|
| [[https://github.com/SqrtMinusOne/pomm.el][pomm.el]] | - | transient.el | CSV | + | alert.el + sounds |
| [[https://github.com/marcinkoziej/org-pomodoro/tree/master][org-pomodoro]] | Org Mode! | via Org commands | via Org mode | - | alert.el + sounds |
| [[https://github.com/TatriX/pomidor/][pomidor]] | - | self-cooked interactive buffer | custom delimited format? | +, but saving on-demand | alert.el + sounds |
| [[https://github.com/baudtack/pomodoro.el/][pomodoro.el]] | - | - | - | - | notifications.el + sounds |
| [[https://github.com/konr/tomatinho/][tomatinho]] | - | self-cooked interactive buffer | - | - | message + sounds |
| [[https://github.com/ferfebles/redtick][redtick]] | - | mode-line icon | + | - | sounds |
| [[https://github.com/abo-abo/gtk-pomodoro-indicator][gtk-pomodoro-indicator]] | GTK panel | CLI | - | -, but the program is independent of Emacs | GTK notifications |
Be sure to check those out if this one doesn't quite fit your workflow!
(1) Means of timer control with exception of Emacs interactive commands
(1) Means of timer control with exception to Emacs interactive commands
Also take a look at [[https://github.com/telotortium/org-pomodoro-third-time][org-pomodoro-third-time]], which adapts =org-pomodoro= for the Third Time technique.

View file

@ -1,6 +1,6 @@
;;; pomm-third-time.el --- Implementation of the third time technique in Emacs -*- lexical-binding: t -*-
;; Copyright (C) 2022 Korytov Pavel
;; Copyright (C) 2023 Korytov Pavel
;; Author: Korytov Pavel <thexcloud@gmail.com>
;; Maintainer: Korytov Pavel <thexcloud@gmail.com>
@ -35,6 +35,11 @@
(require 'transient)
(require 'calc)
;; XXX optional dependency on org-clock
(declare-function org-clock-in-last "org-clock")
(declare-function org-clock-out "org-clock")
(defvar org-clock-current-task)
(defgroup pomm-third-time nil
"Third Time timer implementation."
:group 'pomm)
@ -90,21 +95,21 @@ of period. The format is as follows:
"The current state of the Third Time timer.
This is an alist with the following keys:
- status: either 'stopped or 'running
- status: either \\='stopped or \\='running
(having a pause state seems to make little sense here)
- current: an alist with a current period
- history: a list with history for today
- last-changed-time: a timestamp of the last change in status
- context: a string that describes the current task
'current is an alist with the following keys:
- kind: either 'work or 'break
\\='current is an alist with the following keys:
- kind: either \\='work or \\='break
- start-time: start timestamp
- break-time-bank: break time, postpone from previous periods
- iteration: number of the current iteration
'history is a list of alists with the following keys:
- kind: same as in 'current
\\='history is a list of alists with the following keys:
- kind: same as in \\='current
- iteration
- start-time: start timestamp
- end-time: end timestamp
@ -126,7 +131,7 @@ This is an alist with the following keys:
(last-changed-time ,(time-convert nil 'integer)))
pomm-current-mode-line-string "")
(setf (alist-get 'status pomm-third-time--state) 'stopped)
(run-hooks 'pomm-on-status-changed-hook))
(run-hooks 'pomm-third-time-on-status-changed-hook))
(defun pomm-third-time--init-state ()
"Initialize the Third Time timer state."
@ -186,7 +191,7 @@ If the variable is nil, the function does nothing."
(symbol-name (alist-get 'kind (alist-get 'current pomm-third-time--state)))
(or (alist-get 'iteration (alist-get 'current pomm-third-time--state)) 0)
(pomm-third-time--break-time)
(alist-get 'context pomm--state))
(alist-get 'context pomm-third-time--state))
nil pomm-third-time-csv-history-file 'append 1)))
(transient-define-prefix pomm-third-time-reset ()
@ -248,14 +253,17 @@ return it, otherwise, return a value like a calc error."
('nil 0)))
0))
(defun pomm-third-time--worked-time ()
"Get total time worked in the current iteration."
(let ((iteration (alist-get 'iteration
(alist-get 'current pomm-third-time--state)))
(current-kind (alist-get 'kind (alist-get 'current pomm-third-time--state))))
(defun pomm-third-time--total-time (&optional kind)
"Get total time spent in `state` in the current iteration.
KIND is the same as in `pomm-third-time--state'."
(let* ((kind (if kind kind 'work))
(iteration (alist-get 'iteration
(alist-get 'current pomm-third-time--state)))
(current-kind (alist-get 'kind (alist-get 'current pomm-third-time--state))))
(apply
#'+
(if (eq current-kind 'work)
(if (eq current-kind kind)
(pomm-third-time--current-period-time)
0)
(mapcar
@ -265,9 +273,10 @@ return it, otherwise, return a value like a calc error."
(seq-filter
(lambda (item)
(and (= (alist-get 'iteration item) iteration)
(eq (alist-get 'kind item) 'work)))
(eq (alist-get 'kind item) kind)))
(alist-get 'history pomm-third-time--state))))))
(defun pomm-third-time--need-switch-p ()
"Check if the break period has to end."
(and
@ -436,6 +445,23 @@ Take a look at the `pomm-third-time' function for more details."
(setf (alist-get 'context pomm-third-time--state)
(prin1-to-string (read-minibuffer "Context: " (current-word)))))
(defun pomm-third-time--sync-org-clock ()
"Sync org-clock with the pomodoro timer."
(let* ((status (alist-get 'status pomm-third-time--state))
(kind (alist-get 'kind (alist-get 'current pomm-third-time--state)))
(active-p (and (eq kind 'work)
(eq status 'running)))
(resume-next-time-p (not (eq status 'stopped))))
(cond
((and active-p (not org-clock-current-task)
pomm--sync-org-clock-was-stopped)
(if pomm-org-clock-in-immediately
(org-clock-in-last)
(pomm--org-clock-in-last-after-action)))
((and (not active-p) org-clock-current-task)
(org-clock-out)
(setq pomm--sync-org-clock-was-stopped resume-next-time-p)))))
;;;###autoload
(defun pomm-third-time-start-with-context ()
"Prompt for context call call `pomm-third-time-start'."
@ -536,7 +562,9 @@ KIND is the same as in `pomm-third-time--state'"
(iteration (alist-get 'iteration
(alist-get 'current pomm-third-time--state)))
(break-time (pomm-third-time--break-time))
(period-time (pomm-third-time--current-period-time)))
(period-time (pomm-third-time--current-period-time))
(total-work (pomm-third-time--total-time 'work))
(total-break (pomm-third-time--total-time 'break)))
(concat
(format "Iteration #%d. " iteration)
"State: "
@ -556,9 +584,17 @@ KIND is the same as in `pomm-third-time--state'"
(propertize
(pomm-third-time--format-period break-time)
'face 'success)
". Total time worked: "
".\nTotal time worked: "
(propertize
(pomm-third-time--format-period (pomm-third-time--worked-time))
(pomm-third-time--format-period total-work)
'face 'success)
". Total time braked: "
(propertize
(pomm-third-time--format-period total-break)
'face 'success)
". Total time tracked: "
(propertize
(pomm-third-time--format-period (+ total-work total-break))
'face 'success))))))
(defclass pomm-third-time--transient-history (transient-suffix)
@ -649,16 +685,16 @@ The idea of the technique is as follows:
The timer can have two states:
- Stopped.
Can be started with 's' or `pomm-third-time-start'.
Can be started with \\='s' or `pomm-third-time-start'.
- Running.
Can be stopped with 'S' or `pomm-third-time-stop'.
Can be stopped with \\='S' or `pomm-third-time-stop'.
If the timer is running, the current period type (work or break) can
be switched by 'b' or `pomm-third-time-switch'. If the break time
be switched by \\='b' or `pomm-third-time-switch'. If the break time
runs out, the timer automatically switches to work.
The timer supports setting \"context\", for example, a task on which
you're working on. It can be set with '-c' or
you\\='re working on. It can be set with \\='-c' or
`pomm-third-time-set-context'. This is useful together with CSV
logging, which is enabled if `pomm-third-time-csv-history-file' is
non-nil.

68
pomm.el
View file

@ -1,12 +1,13 @@
;;; pomm.el --- Pomodoro and Third Time timers -*- lexical-binding: t -*-
;; Copyright (C) 2022 Korytov Pavel
;; Copyright (C) 2023 Korytov Pavel
;; Author: Korytov Pavel <thexcloud@gmail.com>
;; Maintainer: Korytov Pavel <thexcloud@gmail.com>
;; Version: 0.2.0
;; Package-Requires: ((emacs "27.1") (alert "1.2") (seq "2.22") (transient "0.3.0"))
;; Homepage: https://github.com/SqrtMinusOne/pomm.el
;; Published-At: 2021-11-05
;; This file is NOT part of GNU Emacs.
@ -44,6 +45,11 @@
(require 'eieio)
(require 'transient)
;; XXX optional dependency on org-clock
(declare-function org-clock-in-last "org-clock")
(declare-function org-clock-out "org-clock")
(defvar org-clock-current-task)
(defgroup pomm nil
"Pomodoro and Third Time timers."
:group 'tools)
@ -102,7 +108,7 @@
(locate-user-emacs-file "pomm")
"Location of the pomm state file."
:group 'pomm
:type 'string)
:type 'file)
(defcustom pomm-history-reset-hour 0
"An hour on which the history will be reset.
@ -134,7 +140,7 @@ of period. The format is as follows:
- iteration
- context"
:group 'pomm
:type 'string)
:type '(choice file (const nil)))
(defcustom pomm-csv-history-file-timestamp-format "%s"
"Timestamp format in the csv file.
@ -208,18 +214,25 @@ Each element of the list is a cons cell, where:
:group 'pomm
:type 'hook)
(defcustom pomm-org-clock-in-immediately t
"Run `org-clock-in-last' immediately after the break ends.
If nil, the clock in happens after the first command."
:group 'pomm
:type 'boolean)
(defvar pomm--state nil
"The current state of the Pomodoro timer.
This is an alist with the following keys:
- status: either 'stopped, 'paused or 'running
- status: either \\='stopped, \\='paused or \\='running
- current: an alist with a current period
- history: a list with history for today
- last-changed-time: a timestamp of the last change in status
- context: a string that describes the current task
'current is also an alist with the following keys:
- kind: either 'short-break, 'long-break or 'work
\\='current is also an alist with the following keys:
- kind: either \\='short-break, \\='long-break or \\='work
- start-time: start timestamp
- effective-start-time: start timestamp, corrected for pauses
- iteration: number of the current Pomodoro iteration
@ -476,7 +489,7 @@ The condition is: (effective-start-time + length) < now."
The formula is:
\(effective-start-time + length\) - now + paused-time,
where paused-time is 0 if status is not 'paused, otherwise:
where paused-time is 0 if status is not \\='paused, otherwise:
paused-time := now - last-changed-time"
(+
(+ (or (alist-get 'effective-start-time (alist-get 'current pomm--state)) 0)
@ -535,6 +548,37 @@ minor mode."
(remove-hook 'pomm-on-status-changed-hook #'pomm-update-mode-line-string)
(remove-hook 'pomm-on-status-changed-hook #'force-mode-line-update))))
(defvar pomm--sync-org-clock-was-stopped nil
"If t, `pomm--sync-org-clock' had stopped `org-clock.")
(defun pomm--org-clock-in-last-after-action ()
"Run `org-clock-in-last' after some action by the user.
This exists because I sometimes return to PC after a the break ends."
(add-hook 'post-command-hook #'pomm--org-clock-in-last-and-remove-from-hook))
(defun pomm--org-clock-in-last-and-remove-from-hook ()
"Run `org-clock-in-last' and remove self from the `post-command-hook'."
(org-clock-in-last)
(remove-hook 'post-command-hook #'pomm--org-clock-in-last-and-remove-from-hook))
(defun pomm--sync-org-clock ()
"Sync org-clock with the pomodoro timer."
(let* ((status (alist-get 'status pomm--state))
(kind (alist-get 'kind (alist-get 'current pomm--state)))
(active-p (and (eq kind 'work)
(eq status 'running)))
(resume-next-time-p (not (eq status 'stopped))))
(cond
((and active-p (not org-clock-current-task)
pomm--sync-org-clock-was-stopped)
(if pomm-org-clock-in-immediately
(org-clock-in-last)
(pomm--org-clock-in-last-after-action)))
((and (not active-p) org-clock-current-task)
(org-clock-out)
(setq pomm--sync-org-clock-was-stopped resume-next-time-p)))))
;;;###autoload
(defun pomm-start ()
"Start or continue the pomodoro timer.
@ -827,17 +871,17 @@ This command initializes the timer and triggers the transient buffer.
The timer can have 3 states:
- Stopped.
Can be started with 's' or `pomm-start'. A new iteration of the
Can be started with \\='s' or `pomm-start'. A new iteration of the
timer will be started.
- Paused.
Can be continuted with 's' / `pomm-start' or stopped competely with
'S' / `pomm-stop'.
Can be continuted with \\='s' / `pomm-start' or stopped competely with
\\='S' / `pomm-stop'.
- Running.
Can be paused with 'p' / `pomm-pause' or stopped with 'S' /
Can be paused with \\='p' / `pomm-pause' or stopped with \\='S' /
`pomm-stop'.
The timer supports setting \"context\", for example, a task on which
you're working on. It can be set with '-c' or `pomm-set-context'.
you're working on. It can be set with \\='-c' or `pomm-set-context'.
This is useful together with CSV logging, which is enabled if
`pomm-csv-history-file' is non-nil.