Merge branch 'doc' into dev

This commit is contained in:
contrapunctus 2022-02-10 18:18:39 +05:30
commit a83173d0a9
2 changed files with 136 additions and 0 deletions

View File

@ -882,3 +882,37 @@ Records not used for time tracking, but to store data associated with a date or
:END:
1. [ ] default suggested input backend should be the active backend
2. [ ] conserve file local variables prop line
* Support for the Third Time System
:PROPERTIES:
:CREATED: 2022-02-10T14:12:12+0530
:END:
See branch "third"
[[https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work][Third Time: a better way to work]]
#+BEGIN_QUOTE
Lastly, here in one place are all the steps for using Third Time:
1. Note the time, or start a stopwatch
2. /Work/ until you want or need to break
3. Divide how long youve just worked by 3 (or use your chosen fraction), and add any minutes left over from previous breaks
4. Set an alarm for that long
5. /Break/ until the alarm goes off, or you decide to resume work
6. If you resume early, note how much time was left, to add to your next break
7. Go back to step 1.
Additional rules:
+ If you have to stop work for a /non-work-related interruption/, start a break immediately.
+ You can (optionally) take a /big break/ for lunch and/or dinner, lasting as long as you like. Set an alarm at the start for when youll resume work. A big break uses up any saved break minutes, so you cant carry them over to the afternoon/evening.
+ /Avoid taking other unearned breaks/ if possibleso try to do personal tasks during normal or big breaks, or before/after your work day.
#+END_QUOTE
1. =chronometrist-third-fraction=
2. =chronometrist-third-break-time=
3. on clock out, increment =break-time= and start timed notification
4. on clock in, calculate duration of latest break (duration between last =:stop= and now), and subtract it from =break-time=; if the resulting time is negative, set it to zero.
5. persist =break-time= between Emacs sessions
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=

View File

@ -17,6 +17,7 @@ I hope this book—when completed—passes Tim Daly's [[https://www.youtube.com/
:PROPERTIES:
:DESCRIPTION: The design, the implementation, and a little history
:END:
** Why I wrote Chronometrist
It probably started off with a desire for introspection and self-awareness - what did I do all day? How much time have I spent doing X? It is also a tool to help me stay focused on a single task at a time, and to not go overboard and work on something for 7 hours straight.
@ -120,12 +121,14 @@ These mess up data consumption in all sorts of unforeseen ways, especially inter
2. finding the time spent on a task on a given day - if the day's intervals used for this contain a midnight-spanning interval, you'll have inaccurate results - it will include yesterday's time from the interval as well as today's.
There are a few different approaches of dealing with them. (Currently, Chronometrist uses #3.)
*** Check the code of the first event of the day (timeclock format)
:PROPERTIES:
:DESCRIPTION: When the code of the first event in the day is "o", it's a midnight-spanning event.
:END:
+ Advantage - very simple to detect
+ Disadvantage - "in" and "out" events must be represented separately
*** Split them at the file level
+ Advantage - operation is performed only once for each such event + simpler data-consuming code + reduced post-parsing load.
+ What happens when the user changes their day-start-time? The split-up events are now split wrongly, and the second event may get split /again./
@ -135,9 +138,11 @@ Possible solutions -
2. Add a :split tag to split events. It can denote that the next event was originally a part of this one.
3. Re-check and update the file when the day-start-time changes.
- Possible with ~add-variable-watcher~ or ~:custom-set~ in Customize (thanks bpalmer)
*** Split them at the hash-table-level
Handled by ~chronometrist-sexp-events-populate~
+ Advantage - simpler data-consuming code.
*** Split them at the data-consumer level (e.g. when calculating time for one day/getting events for one day)
+ Advantage - reduced repetitive post-parsing load.
@ -171,6 +176,7 @@ A quick description, starting from the first time [[* chronometrist-report][=chr
:CUSTOM_ID: explanation-literate-programming
:END:
The shift from a bunch of Elisp files to a single Org literate program was born out of frustration with programs stored as text files, which are expensive to restructure (especially in the presence of a VCS). While some dissatisfactions remain, I generally prefer the outcome - tree and source-block folding, tags, properties, and =org-match= have made it trivial to get different views of the program, and literate programming may allow me to express the "explanation" documentation in the same context as the program, without having to try to link between documentation and source.
*** Tangling
At first, I tried tangling. Back when I used =benchmark.el= to test it, =org-babel-tangle= took about 30 seconds to tangle this file. Thus, I wrote a little sed one-liner (in the file-local variables) to do the tangling, which was nearly instant. It emitted anything between lines matching the exact strings ="#+BEGIN_SRC emacs-lisp"= and ="#+END_SRC"= -
@ -220,6 +226,7 @@ Further details are stored in properties -
2. :VALUE: list|hash table|...
* for functions, this is the return value
3. :STATE: <external file or data structure read or written to>
*** TODO Issues [40%]
1. [X] When opening this file, Emacs may freeze at the prompt for file-local variable values; if so, C-g will quit the prompt, and permanently marking them as safe will make the freezing stop. [fn:3]
2. [ ] I like =visual-fill-column-mode= for natural language, but I don't want it applied to code blocks. =polymode.el= may hold answers.
@ -230,6 +237,7 @@ Further details are stored in properties -
* A workaround is to press =M-o M-o=
[fn:3] No longer a problem since we switched to =literate-elisp=
** Currently-Used Time Formats
:PROPERTIES:
:CUSTOM_ID: explanation-time-formats
@ -238,22 +246,27 @@ Further details are stored in properties -
*** ts
ts.el struct
* Used by nearly all internal functions
*** iso-timestamp
="YYYY-MM-DDTHH:MM:SSZ"=
* Used in the s-expression file format
* Read by chronometrist-sexp-events-populate
* Used in the plists in the chronometrist-events hash table values
*** iso-date
="YYYY-MM-DD"=
* Used as hash table keys in chronometrist-events - can't use ts structs for keys, you'd have to make a hash table predicate which uses ts=
*** seconds
integer seconds as duration
* Used for most durations
* May be changed to floating point to allow larger durations. The minimum range of =most-positive-fixnum= is 536870911, which seems to be enough to represent durations of 17 years.
* Used for update intervals (chronometrist-update-interval, chronometrist-change-update-interval)
*** minutes
integer minutes as duration
* Used by [[https://tildegit.org/contrapunctus/chronometrist-goal][chronometrist-goal]] (chronometrist-goals-list, chronometrist-get-goal) - minutes seems like the ideal unit for users to enter
*** list-duration
=(hours minute seconds)=
* Only returned by =chronometrist-seconds-to-hms=, called by =chronometrist-format-time=
@ -491,6 +504,7 @@ supplied, 3 spaces are used."
(format "%02d" s))))
(concat h m s)))))
#+END_SRC
*** file-empty-p :reader:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-file-empty-p (file)
@ -512,6 +526,7 @@ If FIRSTONLY is non-nil, return only the first keybinding found."
(-interpose ", ")
(apply #'concat))))
#+END_SRC
*** day-start-time :custom:variable:
=chronometrist-events-maybe-split= refers to this, but I'm not sure this has the desired effect at the moment—haven't even tried using it.
#+BEGIN_SRC emacs-lisp
@ -521,12 +536,14 @@ If FIRSTONLY is non-nil, return only the first keybinding found."
The default is midnight, i.e. \"00:00:00\"."
:type 'string)
#+END_SRC
*** week-start-day :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-report-week-start-day "Sunday"
"The day used for start of week by `chronometrist-report'."
:type 'string)
#+END_SRC
*** weekday-number-alist :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-report-weekday-number-alist
@ -540,6 +557,7 @@ The default is midnight, i.e. \"00:00:00\"."
"Alist in the form (\"NAME\" . NUMBER), where \"NAME\" is the name of a weekday and NUMBER its associated number."
:type 'alist)
#+END_SRC
*** previous-week-start :reader:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-previous-week-start (ts)
@ -557,6 +575,7 @@ TS must be a ts struct (see `ts.el')."
do (ts-decf (ts-day ts))
finally return ts))
#+END_SRC
*** plist-remove :function:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-plist-remove (plist &rest keys)
@ -727,6 +746,7 @@ Reading directly from the file could be difficult, especially when your most com
The data from =chronometrist-events= is used by most (all?) interval-consuming functions, but is never written to the user's file itself.
[fn:4] it might be the case that the [[#program-backend][file format]] is not suited to our most frequent operation...
*** reset :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-reset ()
@ -750,6 +770,7 @@ Return value is a ts struct (see `ts.el')."
(ts-apply :hour h :minute m :second s
(chronometrist-iso-to-ts timestamp))))
#+END_SRC
**** tests
#+BEGIN_SRC emacs-lisp :tangle chronometrist-tests.el :load test
(ert-deftest chronometrist-apply-time ()
@ -839,6 +860,7 @@ If REPLACE is non-nil, replace the last interval with PLIST."
(last it)
(car it))))
#+END_SRC
*** events-subset :reader:
:PROPERTIES:
:VALUE: hash table
@ -933,6 +955,7 @@ TIMESTAMP must be an ISO-8601 timestamp, as handled by
:dow dow :tz-offset utcoff))))
#+END_SRC
**** tests
#+BEGIN_SRC emacs-lisp :tangle chronometrist-tests.el :load test
(ert-deftest chronometrist-iso-to-ts ()
@ -944,6 +967,7 @@ TIMESTAMP must be an ISO-8601 timestamp, as handled by
:hour 1 :minute 1 :second 1))))
#+END_SRC
*** events-to-durations :function:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-events-to-durations (events)
@ -964,6 +988,7 @@ Return 0 if EVENTS is nil."
(ts-diff stop-ts start-ts)))
0))
#+END_SRC
*** date-iso :function:
#+BEGIN_SRC emacs-lisp
(cl-defun chronometrist-date-iso (&optional (ts (ts-now)))
@ -990,6 +1015,7 @@ Optional argument UNIX-TIME should be a time value (see
;; Note - this assumes that an event never crosses >1 day. This seems
;; sufficient for all conceivable cases.
#+END_SRC
*** FIXME split-time :reader:
It does not matter here that the =:stop= dates in the returned plists are different from the =:start=, because =chronometrist-events-populate= uses only the date segment of the =:start= values as hash table keys. (The hash table keys form the rest of the program's notion of "days", and that of which plists belong to which day.)
@ -1059,6 +1085,7 @@ SECONDS must be a positive integer."
(h (/ seconds 3600)))
(list h m s)))
#+END_SRC
*** interval :function:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-interval (event)
@ -1069,6 +1096,7 @@ EVENT should be a plist (see `chronometrist-file')."
(time-subtract (parse-iso8601-time-string stop)
(parse-iso8601-time-string start))))
#+END_SRC
*** format-duration-long :function:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-format-duration-long (seconds)
@ -1088,6 +1116,7 @@ SECONDS is less than 60, return a blank string."
hours hour-string
minutes minute-string)))))
#+END_SRC
**** tests
#+BEGIN_SRC emacs-lisp :tangle ../tests/chronometrist-tests.el :load test
(ert-deftest chronometrist-format-duration-long ()
@ -1103,6 +1132,7 @@ SECONDS is less than 60, return a blank string."
(should (equal (chronometrist-format-duration-long 7260) "2 hours, 1 minute"))
(should (equal (chronometrist-format-duration-long 7320) "2 hours, 2 minutes")))
#+END_SRC
** Plist pretty-printing
:PROPERTIES:
:CUSTOM_ID: program-pretty-printer
@ -1129,6 +1159,7 @@ Point is placed at the end of the space."
(delete-region (match-beginning 0) (match-end 0))
(insert " ")))
#+END_SRC
*** column :reader:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-pp-column ()
@ -1136,12 +1167,14 @@ Point is placed at the end of the space."
0 means point is at the beginning of the line."
(- (point) (point-at-bol)))
#+END_SRC
*** pair-p :function:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-pp-pair-p (cons)
"Return non-nil if CONS is a pair, i.e. (CAR . CDR)."
(and (listp cons) (not (listp (cdr cons)))))
#+END_SRC
*** alist-p :function:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-pp-alist-p (list)
@ -1152,6 +1185,7 @@ considers it an alist."
(when (listp list)
(cl-loop for elt in list thereis (chronometrist-pp-pair-p elt))))
#+END_SRC
*** plist-group-p :function:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-plist-group-p (list)
@ -1174,6 +1208,7 @@ that point is after the first opening parenthesis."
when (keywordp sexp)
maximize (length (symbol-name sexp)))))
#+END_SRC
*** indent-sexp :function:
#+BEGIN_SRC emacs-lisp
(cl-defun chronometrist-pp-indent-sexp (sexp &optional (right-indent 0))
@ -1181,6 +1216,7 @@ that point is after the first opening parenthesis."
(format (concat "% -" (number-to-string right-indent) "s")
sexp))
#+END_SRC
*** buffer :writer:
It might help to make =in-sublist= an integer representing depth, instead of a boolean. But at the moment, it's getting the job done.
@ -1312,10 +1348,12 @@ IN-SUBLIST, if non-nil, means point is inside an inner list."
(princ (chronometrist-pp-to-string object)
(or stream standard-output)))
#+END_SRC
** Backends
:PROPERTIES:
:CUSTOM_ID: program-backend
:END:
*** chronometrist-file :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-file
@ -1832,6 +1870,7 @@ These can be implemented in terms of the minimal protocol above.
`(with-current-buffer (find-file-noselect ,file)
(save-excursion ,@body)))
#+END_SRC
**** pre-read-check :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-sexp-pre-read-check (buffer)
@ -1912,6 +1951,7 @@ expression first)."
(should (= 1256 last-start))
(should (= 1426 last-end))))
#+END_SRC
**** file-change-type :reader:
+ rest-start - start of first sexp
+ rest-end - end of second last sexp
@ -2236,6 +2276,7 @@ Boilerplate for updating state between file operations in tests.
(list :last (chronometrist-file-hash :before-last nil nil ,file)
:rest (chronometrist-file-hash nil :before-last t ,file)))))
#+END_SRC
**** backend :class:
#+BEGIN_SRC emacs-lisp
(defclass chronometrist-plist-backend (chronometrist-elisp-sexp-backend)
@ -2877,6 +2918,7 @@ We apply the same hack as in the [[hack-note-plist-group-insert][insert]] method
output-file)
(message "Conversion aborted."))))
#+END_SRC
*** table :variable:
:PROPERTIES:
:VALUE: hash table
@ -2884,6 +2926,7 @@ We apply the same hack as in the [[hack-note-plist-group-insert][insert]] method
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-migrate-table (make-hash-table))
#+END_SRC
*** EXTEND populate :writer:
:PROPERTIES:
:STATE: chronometrist-migrate-table
@ -2977,6 +3020,7 @@ file names respectively."
chronometrist-migrate-table)
(save-buffer)))))
#+END_SRC
*** check :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-migrate-check ()
@ -2989,6 +3033,7 @@ file names respectively."
(chronometrist-migrate-timelog-file-to-sexp-file timeclock-file chronometrist-file)
(message "You can migrate later using `chronometrist-migrate-timelog-file-to-sexp-file'."))))
#+END_SRC
** Timer
Instead of the Emacs convention of pressing ~g~ to update, we keep buffers updated with a timer.
@ -3002,10 +3047,12 @@ Note - sometimes, when hacking or dealing with errors, timers may result in subt
This is not guaranteed to be accurate - see (info \"(elisp)Timers\")."
:type 'integer)
#+END_SRC
*** timer-object :internal:variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist--timer-object nil)
#+END_SRC
*** timer-hook :hook:custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-timer-hook nil
@ -3090,6 +3137,7 @@ modified."
All four of these use [[info:elisp#Tabulated List Mode][=(info "(elisp)Tabulated List Mode")=]]. Each of them also contains a "-print-non-tabular" function, which prints the non-tabular parts of the buffer.
1. [ ] There is some duplication between the four frontend commands, e.g. all four act as toggles for their respective buffers, point preservation, etc.
*** Chronometrist
:PROPERTIES:
:CUSTOM_ID: program-frontend-chronometrist
@ -3099,12 +3147,14 @@ All four of these use [[info:elisp#Tabulated List Mode][=(info "(elisp)Tabulated
1. [X] Define hooks with defcustom instead of defvar
2. [ ] Change abnormal hooks to normal hooks
3. [ ] midnight-spanning plist not displayed (may have to do with partial updates)
**** buffer-name :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-buffer-name "*Chronometrist*"
"The name of the buffer created by `chronometrist'."
:type 'string)
#+END_SRC
**** hide-cursor :custom:variable:
I have not yet gotten this to work as well as I wanted.
@ -3113,6 +3163,7 @@ I have not yet gotten this to work as well as I wanted.
"If non-nil, hide the cursor and only highlight the current line in the `chronometrist' buffer."
:type 'boolean)
#+END_SRC
**** activity-indicator :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-activity-indicator "*"
@ -3121,10 +3172,12 @@ Can be a string to be displayed, or a function which returns this string.
The default is \"*\""
:type '(choice string function))
#+END_SRC
**** point :internal:variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist--point nil)
#+END_SRC
**** open-log :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-open-log (&optional _button)
@ -3181,11 +3234,13 @@ Return the value returned by Fₙ."
See `tabulated-list-format'."
:type '(vector))
#+END_SRC
**** chronometrist-mode-hook :hook:normal:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-mode-hook nil
"Normal hook run at the very end of `chronometrist-mode'.")
#+END_SRC
**** schema-transformers :extension:variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-schema-transformers nil
@ -3369,6 +3424,7 @@ refresh the `chronometrist' buffer."
(chronometrist-out))
t))
#+END_SRC
**** chronometrist-in :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-in (task &optional _prefix)
@ -3379,6 +3435,7 @@ TASK is the name of the task, a string. PREFIX is ignored."
(chronometrist-insert (chronometrist-active-backend) plist)
(chronometrist-refresh)))
#+END_SRC
**** chronometrist-out :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-out (&optional _prefix)
@ -3389,6 +3446,7 @@ PREFIX is ignored."
(plist (plist-put latest :stop (chronometrist-format-time-iso8601))))
(chronometrist-replace-last (chronometrist-active-backend) plist)))
#+END_SRC
**** run-functions-and-clock-in :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-run-functions-and-clock-in (task)
@ -3397,6 +3455,7 @@ PREFIX is ignored."
(chronometrist-in task)
(run-hook-with-args 'chronometrist-after-in-functions task))
#+END_SRC
**** run-functions-and-clock-out :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-run-functions-and-clock-out (task)
@ -3405,6 +3464,7 @@ PREFIX is ignored."
(chronometrist-out)
(run-hook-with-args 'chronometrist-after-out-functions task)))
#+END_SRC
**** chronometrist-mode-map :keymap:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-mode-map
@ -3425,6 +3485,7 @@ PREFIX is ignored."
map)
"Keymap used by `chronometrist-mode'.")
#+END_SRC
**** chronometrist-menu :menu:
#+BEGIN_SRC emacs-lisp
(easy-menu-define chronometrist-menu chronometrist-mode-map
@ -3450,6 +3511,7 @@ PREFIX is ignored."
["Reset state" chronometrist-reset]
["Import/export data" chronometrist-migrate]))
#+END_SRC
**** chronometrist-mode :major:mode:
#+BEGIN_SRC emacs-lisp
(define-derived-mode chronometrist-mode tabulated-list-mode "Chronometrist"
@ -3465,6 +3527,7 @@ PREFIX is ignored."
(setq revert-buffer-function #'chronometrist-refresh)
(run-hooks 'chronometrist-mode-hook))
#+END_SRC
**** toggle-task-button :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-toggle-task-button (_button)
@ -3483,6 +3546,7 @@ action, and is ignored."
(unless (equal at-point current)
(chronometrist-run-functions-and-clock-in at-point))))
#+END_SRC
**** add-new-task-button :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-add-new-task-button (_button)
@ -3495,6 +3559,7 @@ action, and is ignored."
(let ((task (read-from-minibuffer "New task name: " nil nil nil nil nil t)))
(chronometrist-run-functions-and-clock-in task))))
#+END_SRC
**** toggle-task :command:
#+BEGIN_SRC emacs-lisp
;; TODO - if clocked in and point not on a task, just clock out
@ -3663,18 +3728,21 @@ run `chronometrist-statistics'."
*** Report
**** TODO [0%]
1. [ ] preserve point when clicking buttons
**** report :custom:group:
#+BEGIN_SRC emacs-lisp
(defgroup chronometrist-report nil
"Weekly report for the `chronometrist' time tracker."
:group 'chronometrist)
#+END_SRC
**** buffer-name :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-report-buffer-name "*Chronometrist-Report*"
"The name of the buffer created by `chronometrist-report'."
:type 'string)
#+END_SRC
**** ui-date :internal:variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-report--ui-date nil
@ -3682,16 +3750,19 @@ run `chronometrist-statistics'."
A value of nil means the current week. Otherwise, it must be a
date in the form \"YYYY-MM-DD\".")
#+END_SRC
**** ui-week-dates :internal:variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-report--ui-week-dates nil
"List of dates currently displayed by `chronometrist-report'.
Each date is a list containing calendrical information (see (info \"(elisp)Time Conversion\"))")
#+END_SRC
**** point :internal:variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-report--point nil)
#+END_SRC
**** date-to-dates-in-week :function:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-report-date-to-dates-in-week (first-date-in-week)
@ -3702,6 +3773,7 @@ FIRST-DATE-IN-WEEK must be a ts struct representing the first date."
(cl-loop for i from 0 to 6 collect
(ts-adjust 'day i first-date-in-week)))
#+END_SRC
**** date-to-week-dates :function:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-report-date-to-week-dates ()
@ -3715,6 +3787,7 @@ The first date is the first occurrence of
(chronometrist-previous-week-start)
(chronometrist-report-date-to-dates-in-week)))
#+END_SRC
**** rows :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-report-rows ()
@ -3736,6 +3809,7 @@ The first date is the first occurrence of
duration-strings ;; vconcat converts lists to vectors
total-duration)))))
#+END_SRC
**** print-keybind :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-report-print-keybind (command &optional description firstonly)
@ -3748,6 +3822,7 @@ If FIRSTONLY is non-nil, insert only the first keybinding found."
" - "
(if description description "")))
#+END_SRC
**** CLEANUP print-non-tabular :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-report-print-non-tabular ()
@ -3785,6 +3860,7 @@ If FIRSTONLY is non-nil, insert only the first keybinding found."
(chronometrist-report-print-keybind 'chronometrist-open-log)
(insert-text-button "open log file" 'action #'chronometrist-open-log 'follow-link t)))
#+END_SRC
**** REVIEW refresh :procedure:
Merge this into `chronometrist-refresh-file', while moving the -refresh call to the call site?
@ -3799,6 +3875,7 @@ Merge this into `chronometrist-refresh-file', while moving the -refresh call to
(chronometrist-maybe-start-timer)
(set-window-point w p))))
#+END_SRC
**** refresh-file :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-report-refresh-file (fs-event)
@ -3807,6 +3884,7 @@ Merge this into `chronometrist-refresh-file', while moving the -refresh call to
(chronometrist-on-change (chronometrist-active-backend) fs-event)
(chronometrist-report-refresh))
#+END_SRC
**** report-mode-map :keymap:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-report-mode-map
@ -3826,6 +3904,7 @@ Merge this into `chronometrist-refresh-file', while moving the -refresh call to
map)
"Keymap used by `chronometrist-report-mode'.")
#+END_SRC
**** report-mode :major:mode:
#+BEGIN_SRC emacs-lisp
(define-derived-mode chronometrist-report-mode tabulated-list-mode "Chronometrist-Report"
@ -3882,6 +3961,7 @@ current week. Otherwise, display data from the week specified by
(chronometrist-report-refresh-file nil)
(goto-char (or chronometrist-report--point 1)))))))
#+END_SRC
**** report-previous-week :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-report-previous-week (arg)
@ -3900,6 +3980,7 @@ With prefix argument ARG, move back ARG weeks."
(kill-buffer)
(chronometrist-report t))
#+END_SRC
**** report-next-week :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-report-next-week (arg)
@ -3918,6 +3999,7 @@ With prefix argument ARG, move forward ARG weeks."
(kill-buffer)
(chronometrist-report t)))
#+END_SRC
*** Statistics
**** statistics :custom:group:
#+BEGIN_SRC emacs-lisp
@ -3925,12 +4007,14 @@ With prefix argument ARG, move forward ARG weeks."
"Statistics buffer for the `chronometrist' time tracker."
:group 'chronometrist)
#+END_SRC
**** buffer-name :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-statistics-buffer-name "*Chronometrist-Statistics*"
"The name of the buffer created by `chronometrist-statistics'."
:type 'string)
#+END_SRC
**** ui-state :internal:variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-statistics--ui-state nil
@ -3950,14 +4034,17 @@ weekly/monthly/yearly respectively.
:START and :END are the start and end of the date range to be
displayed. They must be ts structs (see `ts.el').")
#+END_SRC
**** point :internal:variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-statistics--point nil)
#+END_SRC
**** mode-map :keymap:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-statistics-mode-map)
#+END_SRC
**** count-average-time-spent :function:
#+BEGIN_SRC emacs-lisp
(cl-defun chronometrist-statistics-count-average-time-spent (task &optional (backend (chronometrist-active-backend)))
@ -3975,6 +4062,7 @@ displayed. They must be ts structs (see `ts.el').")
(/ (-reduce #'+ per-day-time-list) days)
0)))
#+END_SRC
**** rows-internal :reader:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-statistics-rows-internal (table)
@ -4001,6 +4089,7 @@ reduced to the desired range using
(content (vector task active-days active-percent average-time)))
(list task content))))
#+END_SRC
**** TEST rows :reader:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-statistics-rows ()
@ -4021,6 +4110,7 @@ reduced to the desired range using
(setq chronometrist-statistics--ui-state `(:mode week :start ,start :end ,end))
(chronometrist-statistics-rows-internal ht))))))
#+END_SRC
**** print-keybind :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-statistics-print-keybind (command &optional description firstonly)
@ -4034,6 +4124,7 @@ If FIRSTONLY is non-nil, return only the first keybinding found."
" - "
(if description description "")))
#+END_SRC
**** print-non-tabular :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-statistics-print-non-tabular ()
@ -4052,6 +4143,7 @@ If FIRSTONLY is non-nil, return only the first keybinding found."
(ts-format "%F" (plist-get chronometrist-statistics--ui-state :start))
(ts-format "%F" (plist-get chronometrist-statistics--ui-state :end))))))
#+END_SRC
**** refresh :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-statistics-refresh (&optional _ignore-auto _noconfirm)
@ -4069,6 +4161,7 @@ value of `revert-buffer-function'."
(chronometrist-maybe-start-timer)
(set-window-point w p))))
#+END_SRC
**** mode-map :keymap:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-statistics-mode-map
@ -4079,6 +4172,7 @@ value of `revert-buffer-function'."
map)
"Keymap used by `chronometrist-statistics-mode'.")
#+END_SRC
**** statistics-mode :major:mode:
#+BEGIN_SRC emacs-lisp
(define-derived-mode chronometrist-statistics-mode tabulated-list-mode "Chronometrist-Statistics"
@ -4137,6 +4231,7 @@ specified by `chronometrist-statistics--ui-state'."
(switch-to-buffer buffer)
(chronometrist-statistics-refresh))))))
#+END_SRC
**** previous-range :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-statistics-previous-range (arg)
@ -4157,6 +4252,7 @@ If ARG is a numeric argument, go back that many times."
(kill-buffer)
(chronometrist-statistics t)))
#+END_SRC
**** next-range :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-statistics-next-range (arg)
@ -4177,6 +4273,7 @@ If ARG is a numeric argument, go forward that many times."
(kill-buffer)
(chronometrist-statistics t)))
#+END_SRC
*** Details
=chronometrist= displays the total time spent on a task - but what were the details? That's where =chronometrist-details= comes in - to display details of recorded time intervals for a given day, in a format terser and more informative than the plists in the file.
@ -4211,6 +4308,7 @@ If ARG is a numeric argument, go forward that many times."
(format "*%s*" chronometrist-details-buffer-name-base)))
#+END_SRC
**** display-tags :custom:variable:
If the value of this variable is a function and the string it returns contains a newline, the results may be undesirable...but hardly unrecoverable, so try it and see, if you wish.
@ -4350,6 +4448,7 @@ using `chronometrist-details-schema-transformers'.")
map))
#+END_SRC
**** chronometrist-details-menu :menu:
#+BEGIN_SRC emacs-lisp
(easy-menu-define chronometrist-details-menu chronometrist-details-mode-map
@ -4361,6 +4460,7 @@ using `chronometrist-details-schema-transformers'.")
["View/edit log file" chronometrist-open-log]
["Reset state" chronometrist-reset]))
#+END_SRC
**** chronometrist-details-mode :major:mode:
#+BEGIN_SRC emacs-lisp
(define-derived-mode chronometrist-details-mode tabulated-list-mode "Details"
@ -4386,6 +4486,7 @@ BUFFER-OR-NAME must be an existing buffer."
(tabulated-list-print)))
#+END_SRC
**** chronometrist-details :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-details ()
@ -4424,6 +4525,7 @@ range.")
string))
#+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.