feat: remaining third time features

This commit is contained in:
SqrtMinusOne 2022-08-13 20:47:26 +05:00
parent 1691d36473
commit f66b7cade1
2 changed files with 110 additions and 33 deletions

View file

@ -36,7 +36,7 @@
(require 'calc) (require 'calc)
(defgroup pomm-third-time nil (defgroup pomm-third-time nil
"Third time timer implementation." "Third Time timer implementation."
:group 'pomm) :group 'pomm)
(defcustom pomm-third-time-fraction "1/3" (defcustom pomm-third-time-fraction "1/3"
@ -60,7 +60,7 @@ Can be string or number, a string is interpreted with
(defcustom pomm-third-time-state-file-location (defcustom pomm-third-time-state-file-location
(locate-user-emacs-file "pomm-third-time") (locate-user-emacs-file "pomm-third-time")
"Location of the pomm-third-time state file." "Location of the `pomm-third-time' state file."
:group 'pomm-third-time :group 'pomm-third-time
:type 'string) :type 'string)
@ -69,8 +69,25 @@ Can be string or number, a string is interpreted with
:group 'pomm :group 'pomm
:type 'string) :type 'string)
(defcustom pomm-third-time-csv-history-file nil
"If non-nil, save timer history in a CSV format.
The parent directory has to exist!
A new entry is written whenever the timer changes status or kind
of period. The format is as follows:
- timestamp
- status
- kind
- iteration
- break-time-remaining
- context"
:group 'pomm-third-time
:type '(choice (string :tag "Path")
(const nil :tag "Do not save")))
(defvar pomm-third-time--state nil (defvar pomm-third-time--state nil
"The current state of pomm-third-time.el. "The current state of the Third Time timer.
This is an alist with the following keys: This is an alist with the following keys:
- status: either 'stopped or 'running - status: either 'stopped or 'running
@ -113,7 +130,7 @@ This is an alist with the following keys:
(defun pomm-third-time--init-state () (defun pomm-third-time--init-state ()
"Initialize the Third Time timer state." "Initialize the Third Time timer state."
(add-hook 'pomm-third-time-on-status-changed-hook #'pomm-third-time--save-state) (add-hook 'pomm-third-time-on-status-changed-hook #'pomm-third-time--save-state)
;; TODO (add-hook 'pomm-on-status-changed-hook #'pomm-third-time--maybe-save-csv) (add-hook 'pomm-on-status-changed-hook #'pomm-third-time--maybe-save-csv)
(add-hook 'pomm-third-time-on-status-changed-hook (add-hook 'pomm-third-time-on-status-changed-hook
#'pomm-third-time--dispatch-current-sound) #'pomm-third-time--dispatch-current-sound)
(add-hook 'pomm-mode-line-mode-hook (add-hook 'pomm-mode-line-mode-hook
@ -131,13 +148,13 @@ This is an alist with the following keys:
(pomm-third-time--cleanup-old-history)) (pomm-third-time--cleanup-old-history))
(defun pomm-third-time--save-state () (defun pomm-third-time--save-state ()
"Save the current Pomodoro timer state." "Save the current Third Time timer state."
(when pomm-third-time-state-file-location (when pomm-third-time-state-file-location
(with-temp-file pomm-third-time-state-file-location (with-temp-file pomm-third-time-state-file-location
(insert (prin1-to-string pomm-third-time--state))))) (insert (prin1-to-string pomm-third-time--state)))))
(defun pomm-third-time--cleanup-old-history () (defun pomm-third-time--cleanup-old-history ()
"Clear history of previous days from the Pomodoro timer." "Clear history of previous days from the Third Time timer."
(let ((cleanup-time (decode-time))) (let ((cleanup-time (decode-time)))
(setf (decoded-time-second cleanup-time) 0 (setf (decoded-time-second cleanup-time) 0
(decoded-time-minute cleanup-time) 0 (decoded-time-minute cleanup-time) 0
@ -150,6 +167,25 @@ This is an alist with the following keys:
(> (alist-get 'start-time item) cleanup-timestamp)) (> (alist-get 'start-time item) cleanup-timestamp))
(alist-get 'history pomm-third-time--state)))))) (alist-get 'history pomm-third-time--state))))))
(defun pomm-third-time--maybe-save-csv ()
"Log the current state of the timer to a CSV history file.
Set `pomm-third-time-csv-history-file' to customize the file location.
If the variable is nil, the function does nothing."
(when pomm-third-time-csv-history-file
(unless (file-exists-p pomm-third-time-csv-history-file)
(with-temp-file pomm-third-time-csv-history-file
(insert "timestamp,status,period,iteration,break-time-remaining,context\n")))
(write-region
(format "%s,%s,%s,%d,%d,%s\n"
(format-time-string pomm-csv-history-file-timestamp-format)
(symbol-name (alist-get 'status pomm--state))
(symbol-name (alist-get 'kind (alist-get 'current pomm--state)))
(or (alist-get 'iteration (alist-get 'current pomm--state)) 0)
(pomm-third-time--break-time)
(or (alist-get 'context pomm--state) ""))
nil pomm-csv-history-file 'append 1)))
(defun pomm-third-time-reset () (defun pomm-third-time-reset ()
"Reset the Third Time timer." "Reset the Third Time timer."
(interactive) (interactive)
@ -166,13 +202,13 @@ This is an alist with the following keys:
(alist-get 'kind (alist-get 'current pomm-third-time--state)))))) (alist-get 'kind (alist-get 'current pomm-third-time--state))))))
(defun pomm-third-time--calc-eval (value) (defun pomm-third-time--calc-eval (value)
"Convert VALUE to number. "Evaluate VALUE and return number.
If VALUE is not a string, return it. If VALUE is not a string, return it.
Otherwise, try to evaluate with `calc-eval'. If unsuccessful, return Otherwise, try to evaluate with `calc-eval'. If unsuccessful, return
calc error. If the result is numeric, convert it to number and return the calc error. If the result is numeric, convert it to number and
it, otherwise, return a list with an error." return it, otherwise, return a value like a calc error."
(if (stringp value) (if (stringp value)
(let ((res (calc-eval value))) (let ((res (calc-eval value)))
(if (listp res) (if (listp res)
@ -241,7 +277,7 @@ it, otherwise, return a list with an error."
(defun pomm-third-time--dispatch-notification (kind) (defun pomm-third-time--dispatch-notification (kind)
"Dispatch a notification about a start of a period. "Dispatch a notification about a start of a period.
KIND is the same as in `pomm--state'" KIND is the same as in `pomm-third-time--state'"
(alert (alert
(pcase kind (pcase kind
('break (concat pomm-third-time-break-message) ('break (concat pomm-third-time-break-message)
@ -275,7 +311,7 @@ KIND is the same as in `pomm--state'"
(run-hooks 'pomm-third-time-on-status-changed-hook))) (run-hooks 'pomm-third-time-on-status-changed-hook)))
(defun pomm-third-time--on-tick () (defun pomm-third-time--on-tick ()
"A function to execute on each time tick." "Function to execute on each timer tick."
(pcase (alist-get 'status pomm-third-time--state) (pcase (alist-get 'status pomm-third-time--state)
('stopped (when pomm-third-time--timer ('stopped (when pomm-third-time--timer
(cancel-timer pomm-third-time--timer) (cancel-timer pomm-third-time--timer)
@ -334,12 +370,12 @@ Take a look at the `pomm-third-time' function for more details."
(setf (alist-get 'context pomm-third-time--state) nil)))))) (setf (alist-get 'context pomm-third-time--state) nil))))))
(defun pomm-third-time-switch () (defun pomm-third-time-switch ()
"Switch between work and break in the Third Time timer." "Toggle work/break in the Third Time timer."
(interactive) (interactive)
(pomm-third-time--switch)) (pomm-third-time--switch))
(defun pomm-third-time-format-mode-line () (defun pomm-third-time-format-mode-line ()
"Format mode string for the Third Time timer." "Format the modeline string for the Third Time timer."
(let ((current-status (alist-get 'status pomm-third-time--state))) (let ((current-status (alist-get 'status pomm-third-time--state)))
(if (or (eq current-status 'stopped) (if (or (eq current-status 'stopped)
(not (alist-get 'current pomm-third-time--state))) (not (alist-get 'current pomm-third-time--state)))
@ -376,6 +412,7 @@ Take a look at the `pomm-third-time' function for more details."
(setf (alist-get 'context pomm-third-time--state) (setf (alist-get 'context pomm-third-time--state)
(prin1-to-string (read-minibuffer "Context: " (current-word))))) (prin1-to-string (read-minibuffer "Context: " (current-word)))))
;;;###autoload
(defun pomm-third-time-start-with-context () (defun pomm-third-time-start-with-context ()
"Prompt for context call call `pomm-third-time-start'." "Prompt for context call call `pomm-third-time-start'."
(interactive) (interactive)
@ -384,6 +421,7 @@ Take a look at the `pomm-third-time' function for more details."
;;;; Transient ;;;; Transient
(defun pomm-third-time--completing-read-calc () (defun pomm-third-time--completing-read-calc ()
"Do `completing-read' with `calc-eval'."
(let ((res (completing-read (let ((res (completing-read
"Time: " "Time: "
(lambda (string fun flag) (lambda (string fun flag)
@ -563,7 +601,36 @@ The class doesn't actually have any value, but this is necessary for transient."
;;;###autoload ;;;###autoload
(defun pomm-third-time () (defun pomm-third-time ()
"TODO" "Implementation of the Third Time timer in Emacs.
The idea of the technique is as follows:
- Work as long as you need, take a break as 1/3 of the work time (the
fraction of work time to break time is set in
`pomm-third-time-fraction')
- If you've ended a break early, unused break time is saved and added
to the next break within the same session.
- If you've finished the session, either to take a longer break or to
end working, remaining break time is discarded. Each session starts
from a clean slate.
The timer can have two states:
- Stopped.
Can be started with 's' or `pomm-third-time-start'.
- Running.
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
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
`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.
Enable `pomm-mode-line-mode' to display the timer state in the
modeline."
(interactive) (interactive)
(unless pomm-third-time--state (unless pomm-third-time--state
(pomm-third-time--init-state)) (pomm-third-time--init-state))

48
pomm.el
View file

@ -1,10 +1,10 @@
;;; pomm.el --- Yet another Pomodoro timer implementation -*- lexical-binding: t -*- ;;; pomm.el --- Pomodoro and Third Time timers. -*- lexical-binding: t -*-
;; Copyright (C) 2021 Korytov Pavel ;; Copyright (C) 2022 Korytov Pavel
;; Author: Korytov Pavel <thexcloud@gmail.com> ;; Author: Korytov Pavel <thexcloud@gmail.com>
;; Maintainer: Korytov Pavel <thexcloud@gmail.com> ;; Maintainer: Korytov Pavel <thexcloud@gmail.com>
;; Version: 0.1.4 ;; Version: 0.2.0
;; Package-Requires: ((emacs "27.1") (alert "1.2") (seq "2.22") (transient "0.3.0")) ;; Package-Requires: ((emacs "27.1") (alert "1.2") (seq "2.22") (transient "0.3.0"))
;; Homepage: https://github.com/SqrtMinusOne/pomm.el ;; Homepage: https://github.com/SqrtMinusOne/pomm.el
@ -25,14 +25,16 @@
;;; Commentary: ;;; Commentary:
;; An implementation of a Pomodoro timer for Emacs. Distintive features ;; Implementation of two time management methods in Emacs: Pomodoro
;; of this particular implementation: ;; and Third Time.
;; - Managing the timer with transient.el (`pomm' command) ;; This implementation features:
;; - Managing the timer with transient.el
;; - Persistent state between Emacs sessions. ;; - Persistent state between Emacs sessions.
;; So one could close & reopen Emacs without interruption the timer. ;; So one could close & reopen Emacs without interruption the timer.
;; ;;
;; Take a look at `pomm-update-mode-line-string' on how to setup this ;; Main entrypoints are: `pomm' for Pomodoro and `pomm-third-time' for
;; package with a modeline. ;; Third Time.
;;
;; Also take a look at README at ;; Also take a look at README at
;; <https://github.com/SqrtMinusOne/pomm.el> for more information. ;; <https://github.com/SqrtMinusOne/pomm.el> for more information.
@ -43,7 +45,7 @@
(require 'transient) (require 'transient)
(defgroup pomm nil (defgroup pomm nil
"Yet another Pomodoro timer implementation." "Pomodoro and Third Time timers."
:group 'tools) :group 'tools)
(defcustom pomm-work-period 25 (defcustom pomm-work-period 25
@ -77,12 +79,12 @@
:type 'string) :type 'string)
(defcustom pomm-ask-before-long-break t (defcustom pomm-ask-before-long-break t
"Ask a user whether to do a long break or stop the pomodoros." "Ask the user whether to do a long break or stop the pomodoros."
:group 'pomm :group 'pomm
:type 'boolean) :type 'boolean)
(defcustom pomm-ask-before-work nil (defcustom pomm-ask-before-work nil
"Ask a user whether to start a new pomodoro period." "Ask the user whether to start a new pomodoro period."
:group 'pomm :group 'pomm
:type 'boolean) :type 'boolean)
@ -120,9 +122,9 @@ The format is the same as in `format-seconds'"
:type 'string) :type 'string)
(defcustom pomm-csv-history-file nil (defcustom pomm-csv-history-file nil
"The csv history file location. "If non-nil, save timer history in a CSV format.
The parent directory has to exists! The parent directory has to exist!
A new entry is written whenever the timer changes status or kind A new entry is written whenever the timer changes status or kind
of period. The format is as follows: of period. The format is as follows:
@ -173,9 +175,8 @@ When loading the package, `load-file-name' should point to the
location of this file, which means that resources folder should location of this file, which means that resources folder should
be in the same directory. be in the same directory.
If the file is evaluated interactively (for development If the file is evaluated interactively (for development purposes), the
purposes), the `default-directory' is most likely the project `default-directory' variable is most likely the project root."
root."
(or (and load-file-name (concat (file-name-directory load-file-name) name)) (or (and load-file-name (concat (file-name-directory load-file-name) name))
(concat default-directory name))) (concat default-directory name)))
@ -208,7 +209,7 @@ Each element of the list is a cons cell, where:
:type 'hook) :type 'hook)
(defvar pomm--state nil (defvar pomm--state nil
"The current state of pomm.el. "The current state of the Pomodoro timer.
This is an alist with the following keys: This is an alist with the following keys:
- status: either 'stopped, 'paused or 'running - status: either 'stopped, 'paused or 'running
@ -228,7 +229,8 @@ History is a list of alists with the following keys:
- iteration - iteration
- start-time: start timestamp - start-time: start timestamp
- end-time: end timestamp - end-time: end timestamp
- paused-time: time spent in a paused state") - paused-time: time spent in a paused state
- context: current context.")
(defvar pomm--timer nil (defvar pomm--timer nil
"A variable for the pomm timer.") "A variable for the pomm timer.")
@ -818,7 +820,15 @@ The timer can have 3 states:
'S' / `pomm-stop'. 'S' / `pomm-stop'.
- Running. - 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'." `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'.
This is useful together with CSV logging, which is enabled if
`pomm-csv-history-file' is non-nil.
Enable `pomm-mode-line-mode' to display the timer state in the
modeline."
(interactive) (interactive)
(unless pomm--state (unless pomm--state
(pomm--init-state)) (pomm--init-state))