docs: improve docs

This commit is contained in:
SqrtMinusOne 2021-11-04 14:22:49 +03:00
parent abbf649d63
commit 78cb9cd5f9
2 changed files with 172 additions and 30 deletions

View file

@ -1,7 +1,37 @@
#+TITLE: pomm.el
Yet another implementation of a [[https://en.wikipedia.org/wiki/Pomodoro_Technique][pomodoro timer]] for Emacs.
This particular package features:
- Managing the timer with the excellent [[https://github.com/magit/transient/blob/master/lisp/transient.el][transient.el]].
- A persistent state between Emacs sessions.
The timer state isn't reset if you close Emacs. Also, the state file can be syncronized between machines.
None of the available [[*Alternatives][alternatives]] were doing quite what I wanted, and the idea of the timer is quite simple, so I figured I'd implement one myself.
* Setup
While the package is available only this this repository, one way to install is to clone the repository, add the package to the =load-path= and load it with =require=:
#+begin_src emacs-lisp
(require 'pomm)
#+end_src
My preferred way is =use-package= with =straight.el=:
#+begin_src emacs-lisp
(use-package pomm
:straight (:host github :repo "SqrtMinusOne/pomm.el")
:commands (pomm))
#+end_src
The package requires Emacs 27.1, because the time API of the previous versions is kinda crazy and 27.1 has =time-convert=.
** Alerts
The package sends alerts via =alert.el=. The default style of alert is a plain =message=, but if you want an actual notification, set =alert-default-style= accordingly:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(setq alert-default-style 'libnotify) (setq alert-default-style 'libnotify)
#+end_src #+end_src
** Modeline
If you want the timer to display in the modeline, add the following code to your config:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(add-to-list 'mode-line-misc-info '(:eval pomm-current-mode-line-string)) (add-to-list 'mode-line-misc-info '(:eval pomm-current-mode-line-string))
(add-hook 'pomm-on-tick-hook 'pomm-update-mode-line-string) (add-hook 'pomm-on-tick-hook 'pomm-update-mode-line-string)
@ -9,3 +39,56 @@
(add-hook 'pomm-on-status-changed-hook 'pomm-update-mode-line-string) (add-hook 'pomm-on-status-changed-hook 'pomm-update-mode-line-string)
(add-hook 'pomm-on-status-changed-hook 'force-mode-line-update) (add-hook 'pomm-on-status-changed-hook 'force-mode-line-update)
#+end_src #+end_src
This is quite verbose, but as I don't use this feature, I want to avoid adding an unnecesary load to my Emacs.
** Polybar module
If you want to display the pomodoro status in something like polybar, you can add the following lines to your config:
#+begin_src emacs-lisp
(add-hook 'pomm-on-tick-hook 'pomm-update-mode-line-string)
(add-hook 'pomm-on-status-changed-hook 'pomm-update-mode-line-string)
#+end_src
Create a script like this:
#+begin_src bash
if ps -e | grep emacs >> /dev/null; then
emacsclient --eval "pomm-current-mode-line-string" | xargs echo -e
fi
#+end_src
And add a polybar module definition to your polybar config:
#+begin_src conf-windows
[module/pomm]
type = custom/script
exec = /home/pavel/bin/polybar/pomm.sh
interval = 1
#+end_src
* Usage
Run =M-x pomm= to open the transient buffer.
The listed commands are rather 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=.
- *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.
Some settings are available in the transient buffer, but you can customize the relevant variables to make them permanent. Check =M-x customize-group= =pomm= for more information.
* Alternatives
There is a number of packages with a similar purpose, here are some:
- [[https://github.com/marcinkoziej/org-pomodoro/tree/master][org-pomodoro]]
- [[https://github.com/TatriX/pomidor/][pomidor]]
- [[https://github.com/baudtack/pomodoro.el/][pomodoro.el]]
- [[https://github.com/konr/tomatinho/][tomatinho]]
- [[https://github.com/ferfebles/redtick][redtick]]
Be sure to check those out if this one doesn't quite fit your workflow!
* P.S.
The package name is not an abbreviation. I just hope it doesn't mean something horrible in some language I don't know.

119
pomm.el
View file

@ -91,7 +91,7 @@ The format is the same as in `format-seconds'"
:type 'string) :type 'string)
(defcustom pomm-on-tick-hook nil (defcustom pomm-on-tick-hook nil
"A hook to run on a tick when the timer is running." "A hook to run on every tick when the timer is running."
:group 'pomm :group 'pomm
:type 'hook) :type 'hook)
@ -113,19 +113,29 @@ Current period is also an alist with the following keys:
- kind: either 'short-break, 'long-break or 'work - kind: either 'short-break, 'long-break or 'work
- start-time: start timestamp - start-time: start timestamp
- effective-start-time: start timestamp, corrected for pauses - effective-start-time: start timestamp, corrected for pauses
- iteration: number the current pomodoro iteration") - iteration: number the current pomodoro iteration
History is a list of alists with the following keys:
- kind: same as in current
- iteration
- start-time: start timestamp
- end-time: end timestamp
- paused-time: time spent it a paused state")
(defvar pomm--timer nil (defvar pomm--timer nil
"A variable for the pomm timer.") "A variable for the pomm timer.")
(defvar pomm-current-mode-line-string nil (defvar pomm-current-mode-line-string nil
"Current mode-line of the pomodoro timer.") "Current mode-line string of the pomodoro timer.
Updated by `pomm-update-mode-line-string'.")
(defun pomm--do-reset () (defun pomm--do-reset ()
"Reset the pomodoro timer state." "Reset the pomodoro timer state."
(when pomm--timer (when pomm--timer
(cancel-timer pomm--timer) (cancel-timer pomm--timer)
(setq pomm--timer nil)) (setq pomm--timer nil))
;; This is necessary to make the reset work with setf on the variable
(setq pomm--state (setq pomm--state
`((status . ,'stopped) `((status . ,'stopped)
(current . ,nil) (current . ,nil)
@ -156,7 +166,7 @@ KIND is the same as in `pomm--state'"
:title "Pomodoro")) :title "Pomodoro"))
(defun pomm--new-iteration () (defun pomm--new-iteration ()
"Initialize state as a new iteration of pomodoro." "Start a new iteration of the pomodoro timer."
(setf (alist-get 'current pomm--state) (setf (alist-get 'current pomm--state)
`((kind . work) `((kind . work)
(start-time . ,(time-convert nil 'integer)) (start-time . ,(time-convert nil 'integer))
@ -181,7 +191,9 @@ KIND is the same as in `pomm--state'"
(_ 0)))) (_ 0))))
(defun pomm--need-switch-p () (defun pomm--need-switch-p ()
"Check if it is necessary to switch a period." "Check if it is necessary to switch a period.
The condition is: (effective-start-time + length) < now."
(< (+ (alist-get 'effective-start-time (alist-get 'current pomm--state)) (< (+ (alist-get 'effective-start-time (alist-get 'current pomm--state))
(pomm--get-kind-length (pomm--get-kind-length
(alist-get 'kind (alist-get 'current pomm--state)))) (alist-get 'kind (alist-get 'current pomm--state))))
@ -207,6 +219,7 @@ KIND is the same as in `pomm--state'"
"Switch to the next period." "Switch to the next period."
(let* ((current-kind (alist-get 'kind (alist-get 'current pomm--state))) (let* ((current-kind (alist-get 'kind (alist-get 'current pomm--state)))
(current-iteration (alist-get 'iteration (alist-get 'current pomm--state))) (current-iteration (alist-get 'iteration (alist-get 'current pomm--state)))
;; Number of work periods in the current iteration
(work-periods (+ (seq-count (work-periods (+ (seq-count
(lambda (item) (lambda (item)
(and (= (alist-get 'iteration item) current-iteration) (and (= (alist-get 'iteration item) current-iteration)
@ -240,7 +253,6 @@ KIND is the same as in `pomm--state'"
(defun pomm--on-tick () (defun pomm--on-tick ()
"A function to be ran on a timer tick." "A function to be ran on a timer tick."
(pcase (alist-get 'status pomm--state) (pcase (alist-get 'status pomm--state)
('stopped (when pomm--timer ('stopped (when pomm--timer
(cancel-timer pomm--timer) (cancel-timer pomm--timer)
@ -253,7 +265,12 @@ KIND is the same as in `pomm--state'"
(run-hooks 'pomm-on-tick-hook))))) (run-hooks 'pomm-on-tick-hook)))))
(defun pomm--get-time-remaning () (defun pomm--get-time-remaning ()
"Get time remaining in the current pomodoro period." "Get time remaining in the current pomodoro period.
The formula is:
\(effective-start-time + length\) - now + paused-time,
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) (+ (or (alist-get 'effective-start-time (alist-get 'current pomm--state)) 0)
(pomm--get-kind-length (pomm--get-kind-length
@ -281,12 +298,27 @@ KIND is the same as in `pomm--state'"
(format-seconds pomm-remaining-time-format time-remaining)))))) (format-seconds pomm-remaining-time-format time-remaining))))))
(defun pomm-update-mode-line-string () (defun pomm-update-mode-line-string ()
"Update the modeline string." "Update the modeline string for the pomodoro timer.
This sets the variable `pomm-current-mode-line-string' with a value
from `pomm-format-mode-line'. This is made so to minimize the load on
the modeline, because otherwise updates may be quite frequent.
To add this to the modeline, add the following code to your config:
\(add-to-list 'mode-line-misc-info '\(:eval pomm-current-mode-line-string\)')
\(add-hook 'pomm-on-tick-hook 'pomm-update-mode-line-string\)
\(add-hook 'pomm-on-tick-hook 'force-mode-line-update\)
\(add-hook 'pomm-on-status-changed-hook 'pomm-update-mode-line-string\)
\(add-hook 'pomm-on-status-changed-hook 'force-mode-line-update)"
(setq pomm-current-mode-line-string (pomm-format-mode-line))) (setq pomm-current-mode-line-string (pomm-format-mode-line)))
;;;###autoload ;;;###autoload
(defun pomm-start () (defun pomm-start ()
"Start or continue the pomodoro timer." "Start or continue the pomodoro timer.
- If the timer is not initialized, initialize the state.
- If the timer is stopped, start a new iteration.
- If the timer is paused, unpause the timer."
(interactive) (interactive)
(unless pomm--state (unless pomm--state
(pomm--init-state)) (pomm--init-state))
@ -297,7 +329,8 @@ KIND is the same as in `pomm--state'"
(alist-get 'effective-start-time (alist-get 'current pomm--state)) (alist-get 'effective-start-time (alist-get 'current pomm--state))
(+ (alist-get 'effective-start-time (alist-get 'current pomm--state)) (+ (alist-get 'effective-start-time (alist-get 'current pomm--state))
(- (time-convert nil 'integer) (alist-get 'last-changed-time pomm--state))) (- (time-convert nil 'integer) (alist-get 'last-changed-time pomm--state)))
(alist-get 'last-changed-time pomm--state) (time-convert nil 'integer)))) (alist-get 'last-changed-time pomm--state) (time-convert nil 'integer)))
((eq (alist-get 'status pomm--state) 'running) (message "The timer is running!")))
(run-hooks 'pomm-on-status-changed-hook) (run-hooks 'pomm-on-status-changed-hook)
(unless pomm--timer (unless pomm--timer
(setq pomm--timer (run-with-timer 0 1 'pomm--on-tick)))) (setq pomm--timer (run-with-timer 0 1 'pomm--on-tick))))
@ -305,21 +338,25 @@ KIND is the same as in `pomm--state'"
(defun pomm-stop () (defun pomm-stop ()
"Stop the current iteration of the pomodoro timer." "Stop the current iteration of the pomodoro timer."
(interactive) (interactive)
(pomm--store-current-to-history) (if (eq (alist-get 'status pomm--state) 'stopped)
(setf (alist-get 'status pomm--state) 'stopped (message "The timer is already stopped!")
(alist-get 'current pomm--state) nil (pomm--store-current-to-history)
(alist-get 'last-changed-time pomm--state) (time-convert nil 'integer)) (setf (alist-get 'status pomm--state) 'stopped
(run-hooks 'pomm-on-status-changed-hook)) (alist-get 'current pomm--state) nil
(alist-get 'last-changed-time pomm--state) (time-convert nil 'integer))
(run-hooks 'pomm-on-status-changed-hook)))
(defun pomm-pause () (defun pomm-pause ()
"Pause the pomodoro timer." "Pause the pomodoro timer."
(interactive) (interactive)
(setf (alist-get 'status pomm--state) 'paused (if (eq (alist-get 'status pomm--state) 'running)
(alist-get 'last-changed-time pomm--state) (time-convert nil 'integer)) (progn
(run-hooks 'pomm-on-status-changed-hook)) (setf (alist-get 'status pomm--state) 'paused
(alist-get 'last-changed-time pomm--state) (time-convert nil 'integer))
(run-hooks 'pomm-on-status-changed-hook))
(message "The timer is not running!")))
;;;; Transient ;;;; Transient
(transient-define-infix pomm--set-short-break-period () (transient-define-infix pomm--set-short-break-period ()
:class 'transient-lisp-variable :class 'transient-lisp-variable
:variable 'pomm-short-break-period :variable 'pomm-short-break-period
@ -353,23 +390,30 @@ KIND is the same as in `pomm--state'"
:key "-p" :key "-p"
:description "Number of work periods before long break: " :description "Number of work periods before long break: "
:reader (lambda (&rest _) :reader (lambda (&rest _)
(read-number "Number of work periods before long break:" (read-number "Number of work periods before a long break:"
pomm-number-of-periods))) pomm-number-of-periods)))
(defclass pomm--transient-current (transient-suffix) (defclass pomm--transient-current (transient-suffix)
(transient :initform t)) (transient :initform t)
"A transient class to display the current state of the timer.")
(cl-defmethod transient-init-value ((obj pomm--transient-current)) (cl-defmethod transient-init-value ((_ pomm--transient-current))
"A dummy method for `pomm--transient-current'.
The class doesn't actually have any value, but this is necessary for transient."
nil) nil)
(defun pomm--get-kind-face (kind) (defun pomm--get-kind-face (kind)
"Get a face for a KIND of period.
KIND is the same as in `pomm--state'"
(pcase kind (pcase kind
('work 'success) ('work 'success)
('short-break 'warning) ('short-break 'warning)
('long-break 'error))) ('long-break 'error)))
(cl-defmethod transient-format ((obj pomm--transient-current)) (cl-defmethod transient-format ((_ pomm--transient-current))
"hello!" "Format the state of the pomodoro timer."
(let ((status (alist-get 'status pomm--state))) (let ((status (alist-get 'status pomm--state)))
(if (or (eq 'stopped status) (not (alist-get 'current pomm--state))) (if (or (eq 'stopped status) (not (alist-get 'current pomm--state)))
"The timer is not running" "The timer is not running"
@ -399,13 +443,17 @@ KIND is the same as in `pomm--state'"
'face 'success)))))) 'face 'success))))))
(defclass pomm--transient-history (transient-suffix) (defclass pomm--transient-history (transient-suffix)
(transient :initform t)) (transient :initform t)
"A transient class to display the history of the pomodoro timer.")
(cl-defmethod transient-init-value ((obj pomm--transient-history)) (cl-defmethod transient-init-value ((_ pomm--transient-history))
"A dummy method for `pomm--transient-history'.
The class doesn't actually have any value, but this is necessary for transient."
nil) nil)
(cl-defmethod transient-format ((obj pomm--transient-history)) (cl-defmethod transient-format ((_ pomm--transient-history))
"hello!" "Format the history list for the transient buffer."
(if (not (alist-get 'history pomm--state)) (if (not (alist-get 'history pomm--state))
"No history yet" "No history yet"
(let ((previous-iteration -1)) (let ((previous-iteration -1))
@ -431,14 +479,15 @@ KIND is the same as in `pomm--state'"
(transient-define-infix pomm--transient-history-suffix () (transient-define-infix pomm--transient-history-suffix ()
:class 'pomm--transient-history :class 'pomm--transient-history
;; A dummy key. Seems to be necessary for transient.
;; Just don't press ~ while in buffer.
:key "~~1") :key "~~1")
(transient-define-infix pomm--transient-current-suffix () (transient-define-infix pomm--transient-current-suffix ()
:class 'pomm--transient-current :class 'pomm--transient-current
:key "~~2") :key "~~2")
;;;###autoload (autoload 'pomm "Pomodoro timer" nil t) (transient-define-prefix pomm-transient ()
(transient-define-prefix pomm ()
["Settings" ["Settings"
(pomm--set-short-break-period) (pomm--set-short-break-period)
(pomm--set-long-break-period) (pomm--set-long-break-period)
@ -457,5 +506,15 @@ KIND is the same as in `pomm--state'"
["History" ["History"
(pomm--transient-history-suffix)]) (pomm--transient-history-suffix)])
;;;###autoload
(defun pomm ()
"A Pomodoro timer.
This command initialized the state of timer and triggers the transient buffer."
(interactive)
(unless pomm--state
(pomm--init-state))
(call-interactively #'pomm-transient))
(provide 'pomm) (provide 'pomm)
;;; pomm.el ends here ;;; pomm.el ends here