docs: more docs & screenshot

This commit is contained in:
SqrtMinusOne 2021-11-04 22:30:37 +03:00
parent c81204984c
commit e290e28d99
3 changed files with 68 additions and 36 deletions

View file

@ -1,16 +1,16 @@
#+TITLE: pomm.el
Yet another implementation of a [[https://en.wikipedia.org/wiki/Pomodoro_Technique][pomodoro timer]] for Emacs. Yet another implementation of a [[https://en.wikipedia.org/wiki/Pomodoro_Technique][pomodoro timer]] for Emacs.
[[./img/screenshot.png]]
This particular package features: This particular package features:
- Managing the timer with the excellent [[https://github.com/magit/transient/blob/master/lisp/transient.el][transient.el]]. - Managing the timer with the excellent [[https://github.com/magit/transient/blob/master/lisp/transient.el][transient.el]].
- A persistent state between Emacs sessions. - Persistent state between Emacs sessions.
The timer state isn't reset if you close Emacs. Also, the state file can be syncronized between machines. The timer state isn't reset if you close Emacs. Also, the state file can be synchronized 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. 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 * 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=: While the package is available only in 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 #+begin_src emacs-lisp
(require 'pomm) (require 'pomm)
#+end_src #+end_src
@ -22,7 +22,7 @@ My preferred way is =use-package= with =straight.el=:
:commands (pomm)) :commands (pomm))
#+end_src #+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=. The package requires Emacs 27.1 because the time API of the previous versions is kinda crazy and 27.1 has =time-convert=.
** Alerts ** 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: 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:
@ -40,10 +40,10 @@ If you want the timer to display in the modeline, add the following code to your
(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. This is quite verbose, but as I don't use this feature, I want to avoid adding an unnecessary load to my Emacs.
** Polybar module ** Polybar module
If you want to display the pomodoro status in something like polybar, you can add the following lines to your config: 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 #+begin_src emacs-lisp
(add-hook 'pomm-on-tick-hook 'pomm-update-mode-line-string) (add-hook 'pomm-on-tick-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 'pomm-update-mode-line-string)
@ -64,14 +64,15 @@ exec = /home/pavel/bin/polybar/pomm.sh
interval = 1 interval = 1
#+end_src #+end_src
** State file location
The package stores the current state to a file by the path =pomm-state-file-location=, which is =emacs.d/pomm= by default. Set it to wherever you like.
* Usage * Usage
Run =M-x pomm= to open the transient buffer. 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 rather self-descriptive and match the Pomodoro ideology.
The timer can have 3 states: The timer can have 3 states:
- *Stopped*. - *Stopped*. Can be started with "s" or =M-x pomm-start=. A new iteration of the timer will be started.
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 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=. - *Running*. Can be paused with "p" / =M-x pomm-pause= or stopped with "S" / =M-x pomm-stop=.

BIN
img/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

81
pomm.el
View file

@ -25,7 +25,16 @@
;;; Commentary: ;;; Commentary:
;; TODO ;; An implementation of a Pomodoro timer for Emacs. Distintive features
;; of this particular implementation:
;; - Managing the timer with transient.el (`pomm' command)
;; - Persistent state between Emacs sessions.
;; So one could close & reopen Emacs without interruption the timer.
;;
;; Take a look at `pomm-update-mode-line-string' on how to setup this
;; package with a modeline.
;; Also take a look at README at
;; <https://github.com/SqrtMinusOne/pomm.el> for more information.
;;; Code: ;;; Code:
(require 'alert) (require 'alert)
@ -86,7 +95,7 @@
(defcustom pomm-history-reset-hour 0 (defcustom pomm-history-reset-hour 0
"An hour on which the history will be reset. "An hour on which the history will be reset.
Whenever the pomodoro timer is intializing, it will try to read the Whenever the Pomodoro timer is initializing, it will try to read the
state file from `pomm-state-file-location'. If there are records that state file from `pomm-state-file-location'. If there are records that
were made before this hour, they will be cleared, so that the history were made before this hour, they will be cleared, so that the history
contains records only from the current day." contains records only from the current day."
@ -94,7 +103,7 @@ contains records only from the current day."
:type 'integer) :type 'integer)
(defcustom pomm-remaining-time-format "%m:%.2s" (defcustom pomm-remaining-time-format "%m:%.2s"
"Format the time, remaining in the period. "Format the time remaining in the period.
The format is the same as in `format-seconds'" The format is the same as in `format-seconds'"
:group 'pomm :group 'pomm
@ -106,31 +115,36 @@ The format is the same as in `format-seconds'"
:type 'hook) :type 'hook)
(defcustom pomm-on-status-changed-hook nil (defcustom pomm-on-status-changed-hook nil
"A hook to run on status change." "A hook to run on a status change."
:group 'pomm
:type 'hook)
(defcustom pomm-on-period-changed-hook nil
"A hook to run on a period status change."
:group 'pomm :group 'pomm
:type 'hook) :type 'hook)
(defvar pomm--state nil (defvar pomm--state nil
"Current state of pomm.el. "The current state of pomm.el.
This is an alist of with the following keys: This is an alist of with the following keys:
- status: either 'stopped, 'paused or 'running - status: either 'stopped, 'paused or 'running
- current: an alist with a current period - current: an alist with a current period
- history: a list with today's history - history: a list with today's history
- last-changed-time: a timestamp of a last change in status - last-changed-time: a timestamp of the last change in status
Current period is also an alist with the following keys: 'current 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: History is a list of alists with the following keys:
- kind: same as in current - kind: same as in current
- iteration - iteration
- start-time: start timestamp - start-time: start timestamp
- end-time: end timestamp - end-time: end timestamp
- paused-time: time spent it a paused state") - paused-time: time spent in a paused state")
(defvar pomm--timer nil (defvar pomm--timer nil
"A variable for the pomm timer.") "A variable for the pomm timer.")
@ -155,9 +169,9 @@ Updated by `pomm-update-mode-line-string'.")
(setf (alist-get 'status pomm--state) 'stopped)) (setf (alist-get 'status pomm--state) 'stopped))
(defun pomm--init-state () (defun pomm--init-state ()
"Initialize the pomodoro timer state. "Initialize the Pomodoro timer state.
This function has to be run only once, at the first start of the timer." This function is meant to be ran only once, at the first start of the timer."
(add-hook 'pomm-on-status-changed-hook #'pomm--save-state) (add-hook 'pomm-on-status-changed-hook #'pomm--save-state)
(if (or (not (file-exists-p pomm-state-file-location)) (if (or (not (file-exists-p pomm-state-file-location))
(not pomm-state-file-location)) (not pomm-state-file-location))
@ -171,13 +185,13 @@ This function has to be run only once, at the first start of the timer."
(pomm--cleanup-old-history)) (pomm--cleanup-old-history))
(defun pomm--save-state () (defun pomm--save-state ()
"Save the current pomodoro timer state." "Save the current Pomodoro timer state."
(when pomm-state-file-location (when pomm-state-file-location
(with-temp-file pomm-state-file-location (with-temp-file pomm-state-file-location
(insert (prin1-to-string pomm--state))))) (insert (prin1-to-string pomm--state)))))
(defun pomm--cleanup-old-history () (defun pomm--cleanup-old-history ()
"Clear history of previous days from the pomodoro timer." "Clear history of previous days from the Pomodoro 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
@ -191,7 +205,7 @@ This function has to be run only once, at the first start of the timer."
(alist-get 'history pomm--state)))))) (alist-get 'history pomm--state))))))
(defun pomm-reset () (defun pomm-reset ()
"Reset the pomodoro timer." "Reset the Pomodoro timer."
(interactive) (interactive)
(when (y-or-n-p "Are you sure you want to reset the Pomodoro timer? ") (when (y-or-n-p "Are you sure you want to reset the Pomodoro timer? ")
(pomm--do-reset))) (pomm--do-reset)))
@ -208,7 +222,7 @@ KIND is the same as in `pomm--state'"
:title "Pomodoro")) :title "Pomodoro"))
(defun pomm--new-iteration () (defun pomm--new-iteration ()
"Start a new iteration of the pomodoro timer." "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))
@ -224,7 +238,7 @@ KIND is the same as in `pomm--state'"
(pomm--dispatch-notification 'work)) (pomm--dispatch-notification 'work))
(defun pomm--get-kind-length (kind) (defun pomm--get-kind-length (kind)
"Get a length of period KIND in seconds." "Get the length of a period of type KIND in seconds."
(* 60 (* 60
(pcase kind (pcase kind
('short-break pomm-short-break-period) ('short-break pomm-short-break-period)
@ -242,7 +256,7 @@ The condition is: (effective-start-time + length) < now."
(time-convert nil 'integer))) (time-convert nil 'integer)))
(defun pomm--store-current-to-history () (defun pomm--store-current-to-history ()
"Store the current pomodoro state to the history." "Store the current pomodoro period to the history list."
(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)))
(start-time (alist-get 'start-time (alist-get 'current pomm--state))) (start-time (alist-get 'start-time (alist-get 'current pomm--state)))
@ -292,7 +306,8 @@ The condition is: (effective-start-time + length) < now."
(pomm--dispatch-notification next-kind)) (pomm--dispatch-notification next-kind))
(setf (alist-get 'current pomm--state) nil) (setf (alist-get 'current pomm--state) nil)
(setf (alist-get 'status pomm--state) 'stopped)) (setf (alist-get 'status pomm--state) 'stopped))
(pomm--save-state))) (pomm--save-state)
(run-hooks 'pomm-on-status-changed-hook)))
(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."
@ -345,7 +360,7 @@ paused-time := now - last-changed-time"
This sets the variable `pomm-current-mode-line-string' with a value 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 from `pomm-format-mode-line'. This is made so to minimize the load on
the modeline, because otherwise updates may be quite frequent. the modeline, because otherwise the updates may be quite frequent.
To add this to the modeline, add the following code to your config: 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-to-list 'mode-line-misc-info '\(:eval pomm-current-mode-line-string\)')
@ -499,7 +514,7 @@ The class doesn't actually have any value, but this is necessary for transient."
"Format the history list for the transient buffer." "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 1000))
(mapconcat (mapconcat
(lambda (item) (lambda (item)
(let ((kind (alist-get 'kind item)) (let ((kind (alist-get 'kind item))
@ -508,8 +523,13 @@ The class doesn't actually have any value, but this is necessary for transient."
(end-time (alist-get 'end-time item)) (end-time (alist-get 'end-time item))
(paused-time (alist-get 'paused-time item))) (paused-time (alist-get 'paused-time item)))
(concat (concat
(if (> iteration previous-iteration) (if (< iteration previous-iteration)
(progn (setq previous-iteration iteration) "\n") "") (let ((is-first (= previous-iteration 1000)))
(setq previous-iteration iteration)
(if is-first
""
"\n"))
"")
(format "[%02d] " iteration) (format "[%02d] " iteration)
(propertize (propertize
(format "%12s " (upcase (symbol-name kind))) (format "%12s " (upcase (symbol-name kind)))
@ -523,7 +543,7 @@ The class doesn't actually have any value, but this is necessary for transient."
(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. ;; A dummy key. Seems to be necessary for transient.
;; Just don't press ~ while in buffer. ;; Just don't press ~ while in the buffer.
:key "~~1") :key "~~1")
(transient-define-infix pomm--transient-current-suffix () (transient-define-infix pomm--transient-current-suffix ()
@ -557,9 +577,20 @@ The class doesn't actually have any value, but this is necessary for transient."
;;;###autoload ;;;###autoload
(defun pomm () (defun pomm ()
"A Pomodoro timer. "A Pomodoro technique timer.
This command initialized the state of timer and triggers the transient buffer." 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
timer will be started.
- Paused.
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' /
`pomm-stop'."
(interactive) (interactive)
(unless pomm--state (unless pomm--state
(pomm--init-state)) (pomm--init-state))