diff --git a/CHANGELOG.md b/CHANGELOG.md index ed0046a..4562961 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## unreleased +### Added +1. `chronometrist-third`, an extension to add support for the [Third Time](https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work) system. +2. New custom variable `chronometrist-key-value-preset-alist`, to define completion suggestions in advance. +3. New custom variable `chronometrist-key-value-use-database-history`, to control whether database history is used for key-value suggestions. + +## [0.10.0] - 2022-02-15 ### Changed 1. The value of `chronometrist-file` must now be a file path _without extension._ Please update your configurations. 2. The existing file format used by Chronometrist is now called the `plist` format. @@ -14,10 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 1. Multiple backend support - new custom variable `chronometrist-active-backend` to determine active backend, new command `chronometrist-switch-backend` to temporarily select a backend (with completion). 2. New `plist-group` backend, reducing time taken in startup and after changes to the file. 3. Unified migration interface with command `chronometrist-migrate`. -4. New custom variable `chronometrist-task-list`, to add/hide tasks without modifying the database. +4. New custom variable `chronometrist-task-list`, to add/hide tasks without modifying the database. Setting it also disables generation of the task list from the database, speeding up many operations. 5. New command `chronometrist-discard-active`, to discard the active interval. 6. Debug logging messages - to view them, set `chronometrist-debug-enable`. -7. `chronometrist-third`, an extension to add support for the [Third Time](https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work) system. ### Fixed 1. Code to detect the type of change made to the file has been rewritten, hopefully fixing some uncommon `read` errors and `args out of range` errors. diff --git a/README.md b/README.md deleted file mode 120000 index 9d0493a..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -manual.md \ No newline at end of file diff --git a/README.org b/README.org new file mode 120000 index 0000000..e3c17fe --- /dev/null +++ b/README.org @@ -0,0 +1 @@ +manual.org \ No newline at end of file diff --git a/TODO.org b/TODO.org index dd5a4dc..928ce00 100644 --- a/TODO.org +++ b/TODO.org @@ -799,12 +799,18 @@ protocol implementation progress 20. [ ] [superclass] chronometrist-timer * New backends :feature: -1. Org time tracking -2. timeclock -3. [[https://klog.jotaen.net/][klog]] -4. SQLite -5. https://github.com/projecthamster/hamster - + https://github.com/projecthamster/hamster/wiki/Our-datamodel +** Org time tracking +** timeclock +See docstring of =timeclock-log-data= + +** [[https://klog.jotaen.net/][klog]] +:PROPERTIES: +:CUSTOM_ID: new-backends-klog +:END: +** SQLite +** Project Hamster +https://github.com/projecthamster/hamster ++ https://github.com/projecthamster/hamster/wiki/Our-datamodel * Functions doing similar things :code: 1. =task-time-one-day= @@ -812,9 +818,10 @@ protocol implementation progress 3. =events-to-durations= * Use ISO date for functions operating on dates :time:format: -* STARTED customizable task list :feature: -1. Interactive, buffer-local modification of task list, with completion (=completing-read-multiple=) -2. Adding a task? We can modify the task list, but how to persist it? +* STARTED customizable task list [33%] :feature: +1. [X] Make =chronometrist-task-list= customizable +2. [ ] Interactive, buffer-local modification of task list, with completion (=completing-read-multiple=) +3. [ ] Adding a task? We can modify the task list, but how to persist it? * Extend time range prompt :feature: Support inputs like "today", "yesterday", "5 days ago", etc. @@ -848,6 +855,9 @@ With different checks, for different levels of speed/thoroughness - * format changes ** multiple intervals per record :feature: +:PROPERTIES: +:CUSTOM_ID: multiple-intervals-per-record +:END: #+BEGIN_SRC emacs-lisp (:name "" [] ... @@ -858,8 +868,9 @@ With different checks, for different levels of speed/thoroughness - It doesn't do anything not already possible in the current formats. At best, it removes some duplication when the same task is "paused" and "resumed". + Sometimes you pause and resume a task and don't want to split your key-values between >1 intervals (to avoid messing up completion suggestions for the future). An alternative means to the same end could be to add a key like =:deduct ""= or =:deduct ("" . "")=. - - This will also make it easier to support formats like [[https://klog.jotaen.net/][klog]], which support this feature. + - This will also make it easier to support formats like [[#new-backends-klog][klog]], which support this feature. - It will probably complicate all data consuming code, though...think of =chronometrist-details= 🤔 + - An alternative idea could be to define a custom variable to hold the user's key values. If this variable is defined, it would be used instead of generating suggestions from past key-values. That way, such situations will not affect key-value suggestions. ** "event records" - records with only a timestamp :feature: :PROPERTIES: @@ -871,7 +882,7 @@ Records not used for time tracking, but to store data associated with a date or [] ...) #+END_SRC -** STARTED tagging dates with key-values :feature: +** STARTED tagging dates with key-values [0%] :feature: :PROPERTIES: :CREATED: 2022-02-10T22:59:45+0530 :CUSTOM_ID: date-key-values @@ -896,6 +907,10 @@ Alternatively, this would be easier to work with - easier to select the key-valu The plist backend could theoretically be extended to support it, but I'd rather deprecate that format (since it suffers from performance issues) and not deal with it again. +1. [ ] Update call sites of =chronometrist-latest-date-records= (which may now also contain key-values) +2. [ ] Update code accessing the hash table +3. [ ] Update =plist-pp= to handle + * undesired file watcher created after literate-elisp-load :bug: :PROPERTIES: :CREATED: 2021-12-18T15:13:35+0530 @@ -911,7 +926,7 @@ The plist backend could theoretically be extended to support it, but I'd rather :CREATED: 2022-01-08T23:32:37+0530 :END: 1. [ ] default suggested input backend should be the active backend -2. [ ] conserve file local variables prop line +2. [ ] conserve file local variables prop line for text backends * STARTED Support for the Third Time System [57%] :extension: :PROPERTIES: @@ -935,12 +950,6 @@ Additional rules: + /Avoid taking other unearned breaks/ if possible — so try to do personal tasks during normal or big breaks, or before/after your work day. #+END_QUOTE -** Example flow -1. work for 30m -2. clock out - add 10m to =break-time= -3. clock in after a 5m break - subtract 5m from =break-time= - -** Tasks 1. [X] =chronometrist-third-fraction= 2. [X] =chronometrist-third-break-time= 3. [X] on clock out, increment =break-time= and start timed notification @@ -952,6 +961,11 @@ Additional rules: * [ ] Hook function which prompts for work hours whenever you first clock in on a date. If work hours are defined in the custom variable, ask whether to use them - on negative answer, prompt for today's work hours. If work hours are not defined, prompt for today's work hours. 7. [ ] Displaying/recording breaks. Probably done implicitly - when work hours are defined, any time not spent working can be interpreted as break time. +** Example flow +1. work for 30m +2. clock out - add 10m to =break-time= +3. clock in after a 5m break - subtract 5m from =break-time= + ** Extras 1. persist =break-time= between Emacs sessions 2. audible alerts @@ -963,13 +977,15 @@ Additional rules: 2. edit stop time to add +20 minutes of work (30m total) * compare with old data in hash table - decrement break time added by old plist, increment break time added by new plist -* Customizable alerts +* STARTED Customizable alerts [50%] :feature:code: :PROPERTIES: :CREATED: 2022-02-14T08:22:36+0530 +:CUSTOM_ID: customizable-alerts +:branch: alert-macro :END: =chronometrist-third= and =chronometrist-goal= have nearly identical alert code; additionally, users cannot customize the alert style per-alert without basically rewriting the alert functions. -Convert =chronometrist-third-alert-functions= and =chronometrist-goal-alert-functions= to customizable alists whose entries take the form =(SYMBOL FUNCTION ALERT-FN &rest ALERT-ARGS)=, where +Convert =chronometrist-third-alert-functions= and =chronometrist-goal-alert-functions= to customizable alists[fn:4] whose entries take the form =(SYMBOL FUNCTION ALERT-FN &rest ALERT-ARGS)=, where + =SYMBOL= uniquely identifies the alert, + =FUNCTION= is a function calling =chronometrist-*-run-at-time= and =ALERT-FN=, and + =ALERT-ARGS= are passed to =ALERT-FN=. @@ -978,3 +994,29 @@ Convert =chronometrist-third-alert-functions= and =chronometrist-goal-alert-func Similar to how they behave now, these packages will start/stop functions for all entries provided in these alists. (So the user can still control which alerts are run.) Also define =chronometrist--timer-alist=, which associates =symbol= with a timer object. + +1. [X] write macro +2. [ ] use in =chronometrist-goal= and =chronometrist-third= + +[fn:4] Actually, make a macro to define such alists, since the docstrings are likely to be near-identical. + +* DONE Predefined key-values for tasks +:PROPERTIES: +:CREATED: 2022-02-17T23:34:17+0530 +:END: +Benefits +1. Speeds up key-value completion for very large data sets. +2. Prevents key-value suggestions from being affected by [[#multiple-intervals-per-record][splitting of tasks across multiple intervals]]. + +#+BEGIN_SRC emacs-lisp +'(("Task" ...) + ...) +#+END_SRC + +* no-goal-alert - "You have spent on " [0%] :bug: +:PROPERTIES: +:CREATED: 2022-02-25T00:19:49+0530 +:END: +=chronometrist-goal-no-goal-alert= uses =chronometrist-task-time-one-day=, which calls =chronometrist-task-records-for-date= with the /current/ date. + +1. [ ] Use the data for the latest date recorded in the database, rather than the current date. diff --git a/doc/2022-02-20 13-26-53.png b/doc/2022-02-20 13-26-53.png new file mode 100644 index 0000000..4a60980 Binary files /dev/null and b/doc/2022-02-20 13-26-53.png differ diff --git a/elisp/chronometrist-key-values.el b/elisp/chronometrist-key-values.el index 468c28e..339fdd5 100644 --- a/elisp/chronometrist-key-values.el +++ b/elisp/chronometrist-key-values.el @@ -192,6 +192,29 @@ used in `chronometrist-before-out-functions'." "Add key-values to Chronometrist time intervals." :group 'chronometrist) +(defcustom chronometrist-key-value-use-database-history t + "If non-nil, use database to generate key-value suggestions. +If nil, only `chronometrist-key-value-preset-alist' is used." + :type 'boolean + :group 'chronometrist-key-value) + +(defcustom chronometrist-key-value-preset-alist nil + "Alist of key-value suggestions for `chronometrist-key-value' prompts. +Each element must be in the form (\"TASK\" ...)" + :type + '(repeat + (cons + (string :tag "Task name") + (repeat :tag "Property preset" + (plist :tag "Property" + ;; :key-type 'keyword :value-type 'sexp + )))) + :group 'chronometrist-key-values) + +(defun chronometrist-key-value-get-presets (task) + "Return presets for TASK from `chronometrist-key-value-preset-alist' as a list of plists." + (alist-get task chronometrist-key-value-preset-alist nil nil #'equal)) + (defcustom chronometrist-kv-buffer-name "*Chronometrist-Key-Values*" "Name of buffer in which key-values are entered." :group 'chronometrist-key-values @@ -404,30 +427,35 @@ used in `chronometrist-before-out-functions'." ["Change tags and key-values for active/last interval" chronometrist-key-values-unified-prompt])) -(cl-defun chronometrist-key-values-unified-prompt (&optional (task (plist-get (chronometrist-latest-record (chronometrist-active-backend)) :name))) +(cl-defun chronometrist-key-values-unified-prompt + (&optional (task (plist-get (chronometrist-latest-record (chronometrist-active-backend)) :name))) "Query user for tags and key-values to be added for TASK. Return t, to permit use in `chronometrist-before-out-functions'." (interactive) (let* ((backend (chronometrist-active-backend)) + (presets (--map (format "%S" it) + (chronometrist-key-value-get-presets task))) (key-values - (cl-loop for plist in (chronometrist-to-list backend) - when (equal (plist-get plist :name) task) - collect - (let ((plist (chronometrist-plist-remove plist :name :start :stop))) - (when plist (format "%S" plist))) - into key-value-plists - finally return - (--> (seq-filter #'identity key-value-plists) - (cl-remove-duplicates it :test #'equal :from-end t)))) + (when chronometrist-key-value-use-database-history + (cl-loop for plist in (chronometrist-to-list backend) + when (equal (plist-get plist :name) task) + collect + (let ((plist (chronometrist-plist-remove plist :name :start :stop))) + (when plist (format "%S" plist))) + into key-value-plists + finally return + (--> (seq-filter #'identity key-value-plists) + (cl-remove-duplicates it :test #'equal :from-end t))))) (latest (chronometrist-latest-record backend))) - (if (null key-values) + (if (and (null presets) (null key-values)) (progn (chronometrist-tags-add) (chronometrist-kv-add)) - (chronometrist-replace-last - backend - (chronometrist-plist-update - latest - (read (completing-read (format "Key-values for %s: " task) - key-values nil nil nil 'chronometrist-key-values-unified-prompt-history)))))) + (let* ((candidates (append presets key-values)) + (input (completing-read + (format "Key-values for %s: " task) + candidates nil nil nil 'chronometrist-key-values-unified-prompt-history))) + (chronometrist-replace-last backend + (chronometrist-plist-update latest + (read input)))))) t) (provide 'chronometrist-key-values) diff --git a/elisp/chronometrist-key-values.org b/elisp/chronometrist-key-values.org index 37a4767..3a1d891 100644 --- a/elisp/chronometrist-key-values.org +++ b/elisp/chronometrist-key-values.org @@ -330,6 +330,39 @@ used in `chronometrist-before-out-functions'." "Add key-values to Chronometrist time intervals." :group 'chronometrist) #+END_SRC + +*** use-database-history :custom:variable: +#+BEGIN_SRC emacs-lisp +(defcustom chronometrist-key-value-use-database-history t + "If non-nil, use database to generate key-value suggestions. +If nil, only `chronometrist-key-value-preset-alist' is used." + :type 'boolean + :group 'chronometrist-key-value) +#+END_SRC + +*** preset-alist :custom:variable: +#+BEGIN_SRC emacs-lisp +(defcustom chronometrist-key-value-preset-alist nil + "Alist of key-value suggestions for `chronometrist-key-value' prompts. +Each element must be in the form (\"TASK\" ...)" + :type + '(repeat + (cons + (string :tag "Task name") + (repeat :tag "Property preset" + (plist :tag "Property" + ;; :key-type 'keyword :value-type 'sexp + )))) + :group 'chronometrist-key-values) +#+END_SRC + +**** get-presets +#+BEGIN_SRC emacs-lisp +(defun chronometrist-key-value-get-presets (task) + "Return presets for TASK from `chronometrist-key-value-preset-alist' as a list of plists." + (alist-get task chronometrist-key-value-preset-alist nil nil #'equal)) +#+END_SRC + *** kv-buffer-name :custom:variable: #+BEGIN_SRC emacs-lisp (defcustom chronometrist-kv-buffer-name "*Chronometrist-Key-Values*" @@ -337,6 +370,7 @@ used in `chronometrist-before-out-functions'." :group 'chronometrist-key-values :type 'string) #+END_SRC + *** key-history :variable: :PROPERTIES: :VALUE: hash table @@ -707,41 +741,48 @@ Return t, to permit use in `chronometrist-before-out-functions'." :CUSTOM_ID: unified-prompt :END: 1. [ ] Improve appearance - is there an easy way to syntax highlight the plists? + #+BEGIN_SRC emacs-lisp -(cl-defun chronometrist-key-values-unified-prompt (&optional (task (plist-get (chronometrist-latest-record (chronometrist-active-backend)) :name))) +(cl-defun chronometrist-key-values-unified-prompt + (&optional (task (plist-get (chronometrist-latest-record (chronometrist-active-backend)) :name))) "Query user for tags and key-values to be added for TASK. Return t, to permit use in `chronometrist-before-out-functions'." (interactive) (let* ((backend (chronometrist-active-backend)) + (presets (--map (format "%S" it) + (chronometrist-key-value-get-presets task))) (key-values - (cl-loop for plist in (chronometrist-to-list backend) - when (equal (plist-get plist :name) task) - collect - (let ((plist (chronometrist-plist-remove plist :name :start :stop))) - (when plist (format "%S" plist))) - into key-value-plists - finally return - (--> (seq-filter #'identity key-value-plists) - (cl-remove-duplicates it :test #'equal :from-end t)))) + (when chronometrist-key-value-use-database-history + (cl-loop for plist in (chronometrist-to-list backend) + when (equal (plist-get plist :name) task) + collect + (let ((plist (chronometrist-plist-remove plist :name :start :stop))) + (when plist (format "%S" plist))) + into key-value-plists + finally return + (--> (seq-filter #'identity key-value-plists) + (cl-remove-duplicates it :test #'equal :from-end t))))) (latest (chronometrist-latest-record backend))) - (if (null key-values) + (if (and (null presets) (null key-values)) (progn (chronometrist-tags-add) (chronometrist-kv-add)) - (chronometrist-replace-last - backend - (chronometrist-plist-update - latest - (read (completing-read (format "Key-values for %s: " task) - key-values nil nil nil 'chronometrist-key-values-unified-prompt-history)))))) + (let* ((candidates (append presets key-values)) + (input (completing-read + (format "Key-values for %s: " task) + candidates nil nil nil 'chronometrist-key-values-unified-prompt-history))) + (chronometrist-replace-last backend + (chronometrist-plist-update latest + (read input)))))) t) - #+END_SRC + * Provide #+BEGIN_SRC emacs-lisp (provide 'chronometrist-key-values) ;;; chronometrist-key-values.el ends here #+END_SRC -* Local variables :NOEXPORT: +* Local variables :noexport: # Local Variables: +# my-org-src-default-lang: "emacs-lisp" # eval: (when (package-installed-p 'literate-elisp) (require 'literate-elisp) (literate-elisp-load (buffer-file-name))) # End: diff --git a/elisp/chronometrist-spark.org b/elisp/chronometrist-spark.org index cb645cf..c06ff16 100644 --- a/elisp/chronometrist-spark.org +++ b/elisp/chronometrist-spark.org @@ -171,7 +171,7 @@ SCHEMA should be a vector as specified by `tabulated-list-format'." ;;; chronometrist-spark.el ends here #+END_SRC -* Local variables :NOEXPORT: +* Local variables :noexport: # Local Variables: # eval: (when (package-installed-p 'literate-elisp) (require 'literate-elisp) (literate-elisp-load (buffer-file-name))) # End: diff --git a/elisp/chronometrist-third.el b/elisp/chronometrist-third.el index 6e21302..1c47eda 100644 --- a/elisp/chronometrist-third.el +++ b/elisp/chronometrist-third.el @@ -71,7 +71,7 @@ For the possible values of DEFAULT-HOURS, see the variable ;; divisor:1 ends here ;; [[file:chronometrist-third.org::*duration-format][duration-format:1]] -(defcustom chronometrist-third-duration-format "%H, %M, and %S%z" +(defcustom chronometrist-third-duration-format "%H, %M and %S%z" "Format string for durations, passed to `format-seconds'." :type 'string) ;; duration-format:1 ends here @@ -160,19 +160,19 @@ and stopped when they clock in." "Stop alert timers and update break time." (chronometrist-third-stop-alert-timers) (unless (zerop chronometrist-third-break-time) - (-let* (((&plist :stop stop) (cl-second (chronometrist-to-list (chronometrist-active-backend)))) - (used-break-duration (ts-diff (ts-now) (chronometrist-iso-to-ts stop))) - (new-break-time (- chronometrist-third-break-time used-break-duration)) - (old-break-time chronometrist-third-break-time) - (used-break-duration-string (format-seconds chronometrist-third-duration-format - used-break-duration))) - (setq chronometrist-third-break-time (if (> new-break-time 0) new-break-time 0)) + (-let* (((&plist :stop stop) (cl-second (chronometrist-to-list (chronometrist-active-backend)))) + (used-break (ts-diff (ts-now) (chronometrist-iso-to-ts stop))) + (used-break-string (format-seconds chronometrist-third-duration-format used-break)) + (new-break (- chronometrist-third-break-time used-break)) + (old-break chronometrist-third-break-time)) + (setq chronometrist-third-break-time (if (> new-break 0) new-break 0)) (alert (if (zerop chronometrist-third-break-time) (format "You have used up all %s of your break time\n(%s break)" - old-break-time used-break-duration-string) + (format-seconds chronometrist-third-duration-format old-break) + used-break-string) (format "You have used %s of your break time\n(%s left)" - used-break-duration-string + used-break-string (format-seconds chronometrist-third-duration-format chronometrist-third-break-time))))))) ;; clock-in:1 ends here diff --git a/elisp/chronometrist-third.org b/elisp/chronometrist-third.org index 1a40937..59ce9ca 100644 --- a/elisp/chronometrist-third.org +++ b/elisp/chronometrist-third.org @@ -89,7 +89,7 @@ For the possible values of DEFAULT-HOURS, see the variable ** duration-format :custom:variable: #+BEGIN_SRC emacs-lisp -(defcustom chronometrist-third-duration-format "%H, %M, and %S%z" +(defcustom chronometrist-third-duration-format "%H, %M and %S%z" "Format string for durations, passed to `format-seconds'." :type 'string) #+END_SRC @@ -188,19 +188,19 @@ and stopped when they clock in." "Stop alert timers and update break time." (chronometrist-third-stop-alert-timers) (unless (zerop chronometrist-third-break-time) - (-let* (((&plist :stop stop) (cl-second (chronometrist-to-list (chronometrist-active-backend)))) - (used-break-duration (ts-diff (ts-now) (chronometrist-iso-to-ts stop))) - (new-break-time (- chronometrist-third-break-time used-break-duration)) - (old-break-time chronometrist-third-break-time) - (used-break-duration-string (format-seconds chronometrist-third-duration-format - used-break-duration))) - (setq chronometrist-third-break-time (if (> new-break-time 0) new-break-time 0)) + (-let* (((&plist :stop stop) (cl-second (chronometrist-to-list (chronometrist-active-backend)))) + (used-break (ts-diff (ts-now) (chronometrist-iso-to-ts stop))) + (used-break-string (format-seconds chronometrist-third-duration-format used-break)) + (new-break (- chronometrist-third-break-time used-break)) + (old-break chronometrist-third-break-time)) + (setq chronometrist-third-break-time (if (> new-break 0) new-break 0)) (alert (if (zerop chronometrist-third-break-time) (format "You have used up all %s of your break time\n(%s break)" - old-break-time used-break-duration-string) + (format-seconds chronometrist-third-duration-format old-break) + used-break-string) (format "You have used %s of your break time\n(%s left)" - used-break-duration-string + used-break-string (format-seconds chronometrist-third-duration-format chronometrist-third-break-time))))))) #+END_SRC @@ -239,7 +239,7 @@ break time is up." ;;; chronometrist-third.el ends here #+END_SRC -* Local variables :NOEXPORT: +* Local variables :noexport: # Local Variables: # my-org-src-default-lang: "emacs-lisp" # eval: (when (package-installed-p 'literate-elisp) (require 'literate-elisp) (literate-elisp-load (buffer-file-name))) diff --git a/elisp/chronometrist.el b/elisp/chronometrist.el index d6e6688..3bfcc5c 100644 --- a/elisp/chronometrist.el +++ b/elisp/chronometrist.el @@ -5,7 +5,7 @@ ;; Keywords: calendar ;; Homepage: https://tildegit.org/contrapunctus/chronometrist ;; Package-Requires: ((emacs "27.1") (dash "2.16.0") (seq "2.20") (ts "0.2")) -;; Version: 0.9.0 +;; Version: 0.10.0 ;; This is free and unencumbered software released into the public domain. ;; @@ -315,8 +315,8 @@ Return value is a ts struct (see `ts.el')." (plist-put :stop stop-2)))))))) ;; split-plist:1 ends here -;; [[file:chronometrist.org::*events-update][events-update:1]] -(defun chronometrist-events-update (plist hash-table &optional replace) +;; [[file:chronometrist.org::*ht-update][ht-update:1]] +(defun chronometrist-ht-update (plist hash-table &optional replace) "Return HASH-TABLE with PLIST added as the latest interval. If REPLACE is non-nil, replace the last interval with PLIST." (let* ((date (->> (plist-get plist :start) @@ -327,28 +327,29 @@ If REPLACE is non-nil, replace the last interval with PLIST." (append it (list plist)) (puthash date it hash-table)) hash-table)) -;; events-update:1 ends here +;; ht-update:1 ends here -;; [[file:chronometrist.org::*last-date][last-date:1]] -(defun chronometrist-events-last-date (hash-table) +;; [[file:chronometrist.org::*ht-last-date][ht-last-date:1]] +(defun chronometrist-ht-last-date (hash-table) "Return an ISO-8601 date string for the latest date present in `chronometrist-events'." (--> (hash-table-keys hash-table) + (sort it #'string-lessp) (last it) - (car it))) -;; last-date:1 ends here + (cl-first it))) +;; ht-last-date:1 ends here -;; [[file:chronometrist.org::*events-last][events-last:1]] -(cl-defun chronometrist-events-last (&optional (backend (chronometrist-active-backend))) +;; [[file:chronometrist.org::*ht-last][ht-last:1]] +(cl-defun chronometrist-ht-last (&optional (backend (chronometrist-active-backend))) "Return the last plist from `chronometrist-events'." (let* ((hash-table (chronometrist-backend-hash-table backend)) - (last-date (chronometrist-events-last-date hash-table))) + (last-date (chronometrist-ht-last-date hash-table))) (--> (gethash last-date hash-table) (last it) (car it)))) -;; events-last:1 ends here +;; ht-last:1 ends here -;; [[file:chronometrist.org::#program-data-structures-events-subset][events-subset:1]] -(defun chronometrist-events-subset (start end hash-table) +;; [[file:chronometrist.org::#program-data-structures-ht-subset][ht-subset:1]] +(defun chronometrist-ht-subset (start end hash-table) "Return a subset of HASH-TABLE. The subset will contain values between dates START and END (both inclusive). @@ -363,7 +364,7 @@ treated as though their time is 00:00:00." (puthash key value subset))) hash-table) subset)) -;; events-subset:1 ends here +;; ht-subset:1 ends here ;; [[file:chronometrist.org::*task-time-one-day][task-time-one-day:1]] (cl-defun chronometrist-task-time-one-day (task &optional (date (chronometrist-date-ts)) (backend (chronometrist-active-backend))) @@ -1388,7 +1389,7 @@ STREAM (which is the value of `current-buffer')." (chronometrist-backend-run-assertions backend) (with-slots (hash-table) backend (when-let* - ((latest-date (chronometrist-events-last-date hash-table)) + ((latest-date (chronometrist-ht-last-date hash-table)) (records (gethash latest-date hash-table))) (cons latest-date records)))) ;; latest-date-records:1 ends here @@ -1497,7 +1498,7 @@ This is meant to be run in `chronometrist-file' when using an s-expression backe `chronometrist-plist-backend' file." (with-slots (hash-table) backend (-let [(new-plist &as &plist :name new-task) (chronometrist-latest-record backend)] - (setf hash-table (chronometrist-events-update new-plist hash-table)) + (setf hash-table (chronometrist-ht-update new-plist hash-table)) (chronometrist-add-to-task-list new-task backend)))) ;; on-add:1 ends here @@ -1507,8 +1508,8 @@ This is meant to be run in `chronometrist-file' when using an s-expression backe `chronometrist-plist-backend' file is modified." (with-slots (hash-table) backend (-let (((new-plist &as &plist :name new-task) (chronometrist-latest-record backend)) - ((&plist :name old-task) (chronometrist-events-last backend))) - (setf hash-table (chronometrist-events-update new-plist hash-table t)) + ((&plist :name old-task) (chronometrist-ht-last backend))) + (setf hash-table (chronometrist-ht-update new-plist hash-table t)) (chronometrist-remove-from-task-list old-task backend) (chronometrist-add-to-task-list new-task backend)))) ;; on-modify:1 ends here @@ -1518,8 +1519,8 @@ This is meant to be run in `chronometrist-file' when using an s-expression backe "Function run when the newest plist in a `chronometrist-plist-backend' file is deleted." (with-slots (hash-table) backend - (-let (((&plist :name old-task) (chronometrist-events-last)) - (date (chronometrist-events-last-date hash-table))) + (-let (((&plist :name old-task) (chronometrist-ht-last)) + (date (chronometrist-ht-last-date hash-table))) ;; `chronometrist-remove-from-task-list' checks the hash table to determine ;; if `chronometrist-task-list' is to be updated. Thus, the hash table must ;; not be updated until the task list is. @@ -1788,7 +1789,7 @@ Return value is either a list in the form `chronometrist-plist-group-backend' file is modified." (with-slots (hash-table) backend (-let* (((date . plists) (chronometrist-latest-date-records backend)) - (old-date (chronometrist-events-last-date hash-table)) + (old-date (chronometrist-ht-last-date hash-table)) (old-plists (gethash old-date hash-table))) (puthash date plists hash-table) (cl-loop for plist in old-plists @@ -1802,7 +1803,7 @@ Return value is either a list in the form "Function run when the newest plist-group in a `chronometrist-plist-group-backend' file is deleted." (with-slots (hash-table) backend - (-let* ((old-date (chronometrist-events-last-date hash-table)) + (-let* ((old-date (chronometrist-ht-last-date hash-table)) (old-plists (gethash old-date hash-table))) (cl-loop for plist in old-plists do (chronometrist-remove-from-task-list (plist-get plist :name) backend)) @@ -2946,7 +2947,7 @@ displayed. They must be ts structs (see `ts.el').") It simply operates on the entire hash table TABLE (see `chronometrist-to-hash-table' for table format), so ensure that TABLE is reduced to the desired range using -`chronometrist-events-subset'." +`chronometrist-ht-subset'." (cl-loop for task in (chronometrist-task-list) collect (let* ((active-days (chronometrist-statistics-count-active-days task table)) (active-percent (cl-case (plist-get chronometrist-statistics--ui-state :mode) @@ -2975,12 +2976,12 @@ reduced to the desired range using ('week (let* ((start (plist-get chronometrist-statistics--ui-state :start)) (end (plist-get chronometrist-statistics--ui-state :end)) - (ht (chronometrist-events-subset start end hash-table))) + (ht (chronometrist-ht-subset start end hash-table))) (chronometrist-statistics-rows-internal ht))) (t ;; `chronometrist-statistics--ui-state' is nil, show current week's data (let* ((start (chronometrist-previous-week-start (chronometrist-date-ts))) (end (ts-adjust 'day 7 start)) - (ht (chronometrist-events-subset start end hash-table))) + (ht (chronometrist-ht-subset start end hash-table))) (setq chronometrist-statistics--ui-state `(:mode week :start ,start :end ,end)) (chronometrist-statistics-rows-internal ht)))))) ;; rows:1 ends here diff --git a/elisp/chronometrist.org b/elisp/chronometrist.org index 0d9d25c..11dfd5c 100644 --- a/elisp/chronometrist.org +++ b/elisp/chronometrist.org @@ -412,7 +412,7 @@ But I discovered that if I do that, =package-lint= says - =error: Couldn't parse ;; Keywords: calendar ;; Homepage: https://tildegit.org/contrapunctus/chronometrist ;; Package-Requires: ((emacs "27.1") (dash "2.16.0") (seq "2.20") (ts "0.2")) -;; Version: 0.9.0 +;; Version: 0.10.0 ;; This is free and unencumbered software released into the public domain. ;; @@ -833,9 +833,9 @@ Return value is a ts struct (see `ts.el')." :stop "2021-02-13T00:03:46+0530"))))) #+END_SRC -*** events-update :writer: +*** ht-update :writer: #+BEGIN_SRC emacs-lisp -(defun chronometrist-events-update (plist hash-table &optional replace) +(defun chronometrist-ht-update (plist hash-table &optional replace) "Return HASH-TABLE with PLIST added as the latest interval. If REPLACE is non-nil, replace the last interval with PLIST." (let* ((date (->> (plist-get plist :start) @@ -848,33 +848,34 @@ If REPLACE is non-nil, replace the last interval with PLIST." hash-table)) #+END_SRC -*** last-date :reader: +*** ht-last-date :reader: #+BEGIN_SRC emacs-lisp -(defun chronometrist-events-last-date (hash-table) +(defun chronometrist-ht-last-date (hash-table) "Return an ISO-8601 date string for the latest date present in `chronometrist-events'." (--> (hash-table-keys hash-table) + (sort it #'string-lessp) (last it) - (car it))) + (cl-first it))) #+END_SRC -*** events-last :reader: +*** ht-last :reader: #+BEGIN_SRC emacs-lisp -(cl-defun chronometrist-events-last (&optional (backend (chronometrist-active-backend))) +(cl-defun chronometrist-ht-last (&optional (backend (chronometrist-active-backend))) "Return the last plist from `chronometrist-events'." (let* ((hash-table (chronometrist-backend-hash-table backend)) - (last-date (chronometrist-events-last-date hash-table))) + (last-date (chronometrist-ht-last-date hash-table))) (--> (gethash last-date hash-table) (last it) (car it)))) #+END_SRC -*** events-subset :reader: +*** ht-subset :reader: :PROPERTIES: :VALUE: hash table -:CUSTOM_ID: program-data-structures-events-subset +:CUSTOM_ID: program-data-structures-ht-subset :END: #+BEGIN_SRC emacs-lisp -(defun chronometrist-events-subset (start end hash-table) +(defun chronometrist-ht-subset (start end hash-table) "Return a subset of HASH-TABLE. The subset will contain values between dates START and END (both inclusive). @@ -2311,7 +2312,7 @@ In this backend, it's easier to implement this in terms of [[#program-backend-pl (chronometrist-backend-run-assertions backend) (with-slots (hash-table) backend (when-let* - ((latest-date (chronometrist-events-last-date hash-table)) + ((latest-date (chronometrist-ht-last-date hash-table)) (records (gethash latest-date hash-table))) (cons latest-date records)))) #+END_SRC @@ -2427,7 +2428,7 @@ This is meant to be run in `chronometrist-file' when using an s-expression backe `chronometrist-plist-backend' file." (with-slots (hash-table) backend (-let [(new-plist &as &plist :name new-task) (chronometrist-latest-record backend)] - (setf hash-table (chronometrist-events-update new-plist hash-table)) + (setf hash-table (chronometrist-ht-update new-plist hash-table)) (chronometrist-add-to-task-list new-task backend)))) #+END_SRC @@ -2438,8 +2439,8 @@ This is meant to be run in `chronometrist-file' when using an s-expression backe `chronometrist-plist-backend' file is modified." (with-slots (hash-table) backend (-let (((new-plist &as &plist :name new-task) (chronometrist-latest-record backend)) - ((&plist :name old-task) (chronometrist-events-last backend))) - (setf hash-table (chronometrist-events-update new-plist hash-table t)) + ((&plist :name old-task) (chronometrist-ht-last backend))) + (setf hash-table (chronometrist-ht-update new-plist hash-table t)) (chronometrist-remove-from-task-list old-task backend) (chronometrist-add-to-task-list new-task backend)))) #+END_SRC @@ -2450,8 +2451,8 @@ This is meant to be run in `chronometrist-file' when using an s-expression backe "Function run when the newest plist in a `chronometrist-plist-backend' file is deleted." (with-slots (hash-table) backend - (-let (((&plist :name old-task) (chronometrist-events-last)) - (date (chronometrist-events-last-date hash-table))) + (-let (((&plist :name old-task) (chronometrist-ht-last)) + (date (chronometrist-ht-last-date hash-table))) ;; `chronometrist-remove-from-task-list' checks the hash table to determine ;; if `chronometrist-task-list' is to be updated. Thus, the hash table must ;; not be updated until the task list is. @@ -2786,7 +2787,7 @@ Return value is either a list in the form `chronometrist-plist-group-backend' file is modified." (with-slots (hash-table) backend (-let* (((date . plists) (chronometrist-latest-date-records backend)) - (old-date (chronometrist-events-last-date hash-table)) + (old-date (chronometrist-ht-last-date hash-table)) (old-plists (gethash old-date hash-table))) (puthash date plists hash-table) (cl-loop for plist in old-plists @@ -2801,7 +2802,7 @@ Return value is either a list in the form "Function run when the newest plist-group in a `chronometrist-plist-group-backend' file is deleted." (with-slots (hash-table) backend - (-let* ((old-date (chronometrist-events-last-date hash-table)) + (-let* ((old-date (chronometrist-ht-last-date hash-table)) (old-plists (gethash old-date hash-table))) (cl-loop for plist in old-plists do (chronometrist-remove-from-task-list (plist-get plist :name) backend)) @@ -2852,7 +2853,7 @@ Return value is either a list in the form collect plist)) #+END_SRC -***** TODO active-days :reader:method:NOEXPORT: +***** TODO active-days :reader:method:noexport: #+BEGIN_SRC emacs-lisp :tangle no (cl-defmethod chronometrist-active-days ((backend chronometrist-plist-group-backend) task &key start end) (cl-check-type task string) @@ -2877,7 +2878,7 @@ We apply the same hack as in the [[hack-note-plist-group-insert][insert]] method t)) #+END_SRC -***** count-records :reader:method:NOEXPORT: +***** count-records :reader:method:noexport: #+BEGIN_SRC emacs-lisp :tangle no (cl-defmethod chronometrist-count-records ((backend chronometrist-plist-group-backend))) #+END_SRC @@ -4102,7 +4103,7 @@ displayed. They must be ts structs (see `ts.el').") It simply operates on the entire hash table TABLE (see `chronometrist-to-hash-table' for table format), so ensure that TABLE is reduced to the desired range using -`chronometrist-events-subset'." +`chronometrist-ht-subset'." (cl-loop for task in (chronometrist-task-list) collect (let* ((active-days (chronometrist-statistics-count-active-days task table)) (active-percent (cl-case (plist-get chronometrist-statistics--ui-state :mode) @@ -4132,12 +4133,12 @@ reduced to the desired range using ('week (let* ((start (plist-get chronometrist-statistics--ui-state :start)) (end (plist-get chronometrist-statistics--ui-state :end)) - (ht (chronometrist-events-subset start end hash-table))) + (ht (chronometrist-ht-subset start end hash-table))) (chronometrist-statistics-rows-internal ht))) (t ;; `chronometrist-statistics--ui-state' is nil, show current week's data (let* ((start (chronometrist-previous-week-start (chronometrist-date-ts))) (end (ts-adjust 'day 7 start)) - (ht (chronometrist-events-subset start end hash-table))) + (ht (chronometrist-ht-subset start end hash-table))) (setq chronometrist-statistics--ui-state `(:mode week :start ,start :end ,end)) (chronometrist-statistics-rows-internal ht)))))) #+END_SRC @@ -4561,7 +4562,7 @@ range.") #+END_SRC **** intervals-for-range :reader: -This is basically like [[#program-data-structures-events-subset][chronometrist-events-subset]], but returns a list instead of a hash table. Might replace one with the other in the future. +This is basically like [[#program-data-structures-ht-subset][chronometrist-ht-subset]], but returns a list instead of a hash table. Might replace one with the other in the future. #+BEGIN_SRC emacs-lisp (defun chronometrist-details-intervals-for-range (range table) @@ -4764,7 +4765,7 @@ Return value is a list as specified by `tabulated-list-entries'." ;;; chronometrist.el ends here #+END_SRC -* Local variables :NOEXPORT: +* Local variables :noexport: Evaluate this to be able to insert the package prefix via =nameless-insert-name=, at the cost of having all =nameless-aliases= break (= less readable code). #+BEGIN_SRC emacs-lisp :load no :tangle no diff --git a/manual.md b/manual.md deleted file mode 100644 index e1eb305..0000000 --- a/manual.md +++ /dev/null @@ -1,428 +0,0 @@ - -# Table of Contents - -1. [Benefits](#benefits) -2. [Limitations](#limitations) -3. [Comparisons](#comparisons) - 1. [timeclock.el](#timeclock.el) - 2. [Org time tracking](#org-time-tracking) -4. [Installation](#installation) - 1. [from MELPA](#install-from-melpa) - 2. [from Git](#install-from-git) -5. [Usage](#usage) - 1. [chronometrist](#usage-chronometrist) - 2. [chronometrist-report](#usage-chronometrist-report) - 3. [chronometrist-statistics](#usage-chronometrist-statistics) - 4. [chronometrist-details](#org09eaa12) - 5. [common commands](#usage-common-commands) - 6. [Time goals/targets](#time-goals) -6. [How-to](#how-to) - 1. [How to display a prompt when exiting with an active task](#how-to-prompt-when-exiting-emacs) - 2. [How to load the program using literate-elisp](#how-to-literate-elisp) - 3. [How to attach tags to time intervals](#how-to-tags) - 4. [How to attach key-values to time intervals](#how-to-key-value-pairs) - 5. [How to skip running hooks/attaching tags and key values](#orga569ce7) - 6. [How to open certain files when you start a task](#how-to-open-files-on-task-start) - 7. [How to warn yourself about uncommitted changes](#how-to-warn-uncommitted-changes) - 8. [How to display the current time interval in the activity indicator](#how-to-activity-indicator) - 9. [How to back up your Chronometrist data](#how-to-backup) - 10. [How to configure Vertico for use with Chronometrist](#howto-vertico) -7. [Explanation](#org2a03f3a) - 1. [Literate Program](#explanation-literate-program) -8. [User's reference](#org2585c1f) -9. [Contributions and contact](#contributions-contact) -10. [License](#license) -11. [Thanks](#thanks) - -Donate using Liberapay - - - -A time tracker in Emacs with a nice interface - -Largely modelled after the Android application, [A Time Tracker](https://github.com/netmackan/ATimeTracker) - - - - -# Benefits - -1. Extremely simple and efficient to use -2. Displays useful information about your time usage -3. Support for both mouse and keyboard -4. Human errors in tracking are easily fixed by editing a plain text file -5. Hooks to let you perform arbitrary actions when starting/stopping tasks -6. Fancy graphs with the `chronometrist-spark` extension - - - - -# Limitations - -1. No support for concurrent tasks. - - - - -# Comparisons - - - - -## timeclock.el - -Compared to timeclock.el, Chronometrist - -- stores data in an s-expression format rather than a line-based one -- supports attaching tags and arbitrary key-values to time intervals -- has commands to shows useful summaries -- has more hooks - - - - -## Org time tracking - -Chronometrist and Org time tracking seem to be equivalent in terms of capabilities, approaching the same ends through different means. - -- Chronometrist doesn't have a mode line indicator at the moment. (planned) -- Chronometrist doesn't have Org's sophisticated querying facilities. (an SQLite backend is planned) -- Org does so many things that keybindings seem to necessarily get longer. Chronometrist has far fewer commands than Org, so most of the keybindings are single keys, without modifiers. -- Chronometrist's UI is cleaner, since the storage is separate from the display. It doesn't show tasks as trees like Org, but it uses tags and key-values to achieve that. Additionally, navigating a flat list takes fewer user operations than navigating a tree. -- Chronometrist data is just s-expressions (plists), and may be easier to parse than a complex text format with numerous use-cases. - - - - -# Installation - - - - -## from MELPA - -1. Set up MELPA - - - (Chronometrist uses Semantic Versioning and the developer is accident-prone, so using MELPA Stable is suggested 😏) -2. `M-x package-install RET chronometrist RET` - - - - -## from Git - -You can get `chronometrist` from or - -`chronometrist` requires - -- Emacs v25 or higher -- [dash.el](https://github.com/magnars/dash.el) -- [ts.el](https://github.com/alphapapa/ts.el) - -Add the "elisp/" subdirectory to your load-path, and `(require 'chronometrist)`. - - - - -# Usage - - - - -## chronometrist - -Run `M-x chronometrist` to see your projects, the time you spent on them today, which one is active, and the total time clocked today. - -Click or hit `RET` (`chronometrist-toggle-task`) on a project to start tracking time for it. If it's already clocked in, it will be clocked out. - -You can also hit ` RET` anywhere in the buffer to toggle the corresponding project, e.g. =C-1 RET= will toggle the project with index 1. - -Press `r` to see a weekly report (see `chronometrist-report`) - - - - -## chronometrist-report - -Run `M-x chronometrist-report` (or `chronometrist` with a prefix argument of 1, or press `r` in the `chronometrist` buffer) to see a weekly report. - -Press `b` to look at past weeks, and `f` for future weeks. - - - - -## chronometrist-statistics - -Run `M-x chronometrist-statistics` (or `chronometrist` with a prefix argument of 2) to view statistics. - -Press `b` to look at past time ranges, and `f` for future ones. - - - - -## chronometrist-details - - - - -## common commands - -In the buffers created by the previous three commands, you can press `l` (`chronometrist-open-log`) to view/edit your `chronometrist-file`, which by default is `~/.emacs.d/chronometrist.sexp`. - -All of these commands will kill their buffer when run again with the buffer visible, so the keys you bind them to behave as a toggle. - -All buffers keep themselves updated via an idle timer - no need to frequently press `g` to update. - - - - -## Time goals/targets - -If you wish you could define time goals for some tasks, and have Chronometrist notify you when you're approaching the goal, completing it, or exceeding it, check out the extension [chronometrist-goal.el](https://github.com/contrapunctus-1/chronometrist-goal/). - - - - -# How-to - -See the Customize groups `chronometrist` and `chronometrist-report` for variables intended to be user-customizable. - - - - -## How to display a prompt when exiting with an active task - -Evaluate or add to your init.el the following - -`(add-hook 'kill-emacs-query-functions 'chronometrist-query-stop)` - - - - -## How to load the program using literate-elisp - - (add-to-list 'load-path "") - - (require 'literate-elisp) ;; or autoload, use-package, ... - (literate-elisp-load "chronometrist.org") - - - - -## How to attach tags to time intervals - -1. Add `chronometrist-tags-add` to one or more of these hooks 1 - - - (add-to-list 'chronometrist-after-in-functions 'chronometrist-tags-add) - (add-to-list 'chronometrist-before-out-functions 'chronometrist-tags-add) - (add-to-list 'chronometrist-after-out-functions 'chronometrist-tags-add) -2. clock in/clock out to trigger the hook. - - The prompt suggests past combinations you used for the current task, which you can browse with `M-p=/=M-n`. You can leave it blank by pressing `RET`. - - - - -## How to attach key-values to time intervals - -1. Add `chronometrist-kv-add` to one or more of these hooks 1 - - - (add-to-list 'chronometrist-after-in-functions 'chronometrist-kv-add) - (add-to-list 'chronometrist-before-out-functions 'chronometrist-kv-add) - (add-to-list 'chronometrist-after-out-functions 'chronometrist-kv-add) - -To exit the prompt, press the key it indicates for quitting - you can then edit the resulting key-values by hand if required. Press `C-c C-c` to accept the key-values, or `C-c C-k` to cancel. - - - - -## How to skip running hooks/attaching tags and key values - -Use `M-RET` (`chronometrist-toggle-task-no-hooks`) to clock in/out. - - - - -## How to open certain files when you start a task - -An idea from the author's own init - - - (defun my-start-project (project) - (pcase project - ("Guitar" - (find-file-other-window "~/repertoire.org")) - ;; ... - )) - - (add-hook 'chronometrist-before-in-functions 'my-start-project) - - - - -## How to warn yourself about uncommitted changes - -Another one, prompting the user if they have uncommitted changes in a git repository (assuming they use [Magit](https://magit.vc/)) - - - (autoload 'magit-anything-modified-p "magit") - - (defun my-commit-prompt () - "Prompt user if `default-directory' is a dirty Git repository. - Return t if the user answers yes, if the repository is clean, or - if there is no Git repository. - - Return nil (and run `magit-status') if the user answers no." - (cond ((not (magit-anything-modified-p)) t) - ((yes-or-no-p - (format "You have uncommitted changes in %S. Really clock out? " - default-directory)) t) - (t (magit-status) nil))) - - (add-hook 'chronometrist-before-out-functions 'my-commit-prompt) - - - - -## How to display the current time interval in the activity indicator - - (defun my-activity-indicator () - (--> (chronometrist-latest-record (chronometrist-active-backend)) - (plist-put it :stop (chronometrist-format-time-iso8601)) - (list it) - (chronometrist-events-to-durations it) - (-reduce #'+ it) - (truncate it) - (chronometrist-format-duration it))) - - (setq chronometrist-activity-indicator #'my-activity-indicator) - - - - -## How to back up your Chronometrist data - -I suggest backing up Chronometrist data on each save using the [async-backup](https://tildegit.org/contrapunctus/async-backup) package.2 Here's how you can do that. - -1. Add the following to your init. - - (use-package async-backup) -2. Open your Chronometrist file and add `async-backup` to a buffer-local `after-save-hook`. - - M-x chronometrist-open-log - M-x add-file-local-variable-prop-line RET eval RET (add-hook 'after-save-hook #'async-backup nil t) RET -3. Optionally, configure `backup-directory-alist` to set a specific directory for the backups. - - - - -## How to configure Vertico for use with Chronometrist - -By default, [Vertico](https://github.com/minad/vertico) uses its own sorting function - for some commands (such as `chronometrist-key-values-unified-prompt`) this results in *worse* suggestions, since Chronometrist sorts suggestions in most-recent-first order. - -You can either disable Vertico's sorting entirely - - - (setq vertico-sort-function nil) - -Or use `vertico-multiform` to disable sorting for only specific commands - - - (use-package vertico-multiform - :init (vertico-multiform-mode) - :config - (setq vertico-multiform-commands - '((chronometrist-toggle-task (vertico-sort-function . nil)) - (chronometrist-toggle-task-no-hooks (vertico-sort-function . nil)) - (chronometrist-key-values-unified-prompt (vertico-sort-function . nil))))) - - - - -# Explanation - - - - -## Literate Program - -Chronometrist is a literate program, made using Org - the canonical source is the `chronometrist.org` file, which contains source blocks. These are provided to users after *tangling* (extracting the source into an Emacs Lisp file). - -The Org file can also be loaded directly using the [literate-elisp](https://github.com/jingtaozf/literate-elisp) package, so that all source links (e.g. `xref`, `describe-function`) lead to the Org file, within the context of the concerned documentation. See [How to load the program using literate-elisp](#how-to-literate-elisp). - -`chronometrist.org` is also included in MELPA installs, although not used directly by default, since doing so would interfere with automatic generation of autoloads. - - - - -# User's reference - -All variables intended for user customization are listed here. They serve as the public API for this project for the purpose of semantic versioning. Any changes to these which require a user to modify their configuration are considered breaking changes. - -1. `chronometrist-file` -2. `chronometrist-buffer-name` -3. `chronometrist-report-buffer-name` -4. `chronometrist-details-buffer-name` -5. `chronometrist-sexp-pretty-print-function` -6. `chronometrist-hide-cursor` -7. `chronometrist-update-interval` -8. `chronometrist-activity-indicator` - -Buffer schemas - -1. `chronometrist-schema` -2. `chronometrist-details-schema` - -Hooks - -1. `chronometrist-mode-hook` -2. `chronometrist-schema-transformers` -3. `chronometrist-row-transformers` -4. `chronometrist-before-in-functions` -5. `chronometrist-after-in-functions` -6. `chronometrist-before-out-functions` -7. `chronometrist-after-out-functions` -8. `chronometrist-file-change-hook` -9. `chronometrist-timer-hook` - - - - -# Contributions and contact - -Feedback and MRs are very welcome. 🙂 - -- has a long list of tasks -- contains all developer-oriented documentation - -If you have tried using Chronometrist, I'd love to hear your experiences! Get in touch with the author and other Emacs users in the Emacs channel on the Jabber network - [xmpp:emacs@salas.suchat.org?join](https://conversations.im/j/emacs@salas.suchat.org) ([web chat](https://inverse.chat/#converse/room?jid=emacs@salas.suchat.org)) - -(For help in getting started with Jabber, [click here](https://xmpp.org/getting-started/)) - - - - -# License - -I dream of a world where all software is liberated - transparent, trustable, and accessible for anyone to use or improve. But I don't want to make demands or threats (e.g. via legal conditions) to get there. - -I'd rather make a request - please do everything you can to help that dream come true. Please Unlicense as much software as you can. - -Chronometrist is released under your choice of [Unlicense](https://unlicense.org/) or the [WTFPL](http://www.wtfpl.net/). - -(See files [UNLICENSE](UNLICENSE) and [WTFPL](WTFPL)). - - - - -# Thanks - -wasamasa, bpalmer, aidalgol, pjb and the rest of #emacs for their tireless help and support - -jwiegley for `timeclock.el`, which we used as a backend in earlier versions - -blandest for helping me with the name - -fiete and wu-lee for testing and bug reports - - -# Footnotes - -1 but not `chronometrist-before-in-functions` - -2 It is possible to use Emacs' built-in backup system to do it, but since it is synchronous, doing so will greatly slow down saving of the Chronometrist file. diff --git a/manual.org b/manual.org index 0d8abfa..0de28d9 100644 --- a/manual.org +++ b/manual.org @@ -4,14 +4,19 @@ #+HTML_HEAD: #+BEGIN_EXPORT html -Donate using Liberapay + + Donate using Liberapay + - + + + #+END_EXPORT -A time tracker in Emacs with a nice interface +Chronometrist is a friendly and powerful personal time tracker and analyzer for Emacs. -Largely modelled after the Android application, [[https://github.com/netmackan/ATimeTracker][A Time Tracker]] +#+CAPTION: The main Chronometrist buffer, with the enabled extensions [[#time-goals][chronometrist-goal]] ("Targets" column + alerts) and chronometrist-spark ("Graph" column displaying the activity for the past 4 weeks). +[[file:doc/2022-02-20 13-26-53.png]] * Benefits :PROPERTIES: @@ -67,8 +72,6 @@ Chronometrist and Org time tracking seem to be equivalent in terms of capabiliti :END: 1. Set up MELPA - https://melpa.org/#/getting-started - - (Chronometrist uses Semantic Versioning and the developer is accident-prone, so using MELPA Stable is suggested 😏) 2. =M-x package-install RET chronometrist RET= ** from Git @@ -371,6 +374,7 @@ Chronometrist is released under your choice of [[https://unlicense.org/][Unlicen :PROPERTIES: :CUSTOM_ID: thanks :END: +The main buffer and the report buffer are copied from the Android application, [[https://github.com/netmackan/ATimeTracker][A Time Tracker]] wasamasa, bpalmer, aidalgol, pjb and the rest of #emacs for their tireless help and support @@ -382,6 +386,5 @@ fiete and wu-lee for testing and bug reports * Local variables :noexport: # Local Variables: -# eval: (progn (require 'ox-md) (add-hook 'after-save-hook (lambda () (org-export-to-file 'md "manual.md")) nil t)) # my-org-src-default-lang: "emacs-lisp" # End: