391 lines
14 KiB
Org Mode
391 lines
14 KiB
Org Mode
#+TITLE: Chronometrist - an extensible time tracker for Emacs
|
||
#+SUBTITLE: Friendly and powerful personal time tracker and analyzer for Emacs
|
||
#+DESCRIPTION: User Manual
|
||
#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="style.css" />
|
||
|
||
#+BEGIN_EXPORT html
|
||
<a href="https://liberapay.com/contrapunctus/donate">
|
||
<img alt="Donate using Liberapay" src="https://img.shields.io/liberapay/receives/contrapunctus.svg?logo=liberapay">
|
||
</a>
|
||
|
||
<a href="https://melpa.org/#/chronometrist">
|
||
<img src="https://melpa.org/packages/chronometrist-badge.svg">
|
||
</a>
|
||
#+END_EXPORT
|
||
|
||
Chronometrist is a friendly and powerful personal time tracker and analyzer for Emacs.
|
||
|
||
#+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:
|
||
:CUSTOM_ID: benefits
|
||
:END:
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: limitations
|
||
:END:
|
||
1. No support for concurrent tasks.
|
||
|
||
* Comparisons
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: comparisons
|
||
:END:
|
||
** timeclock.el
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: timeclock.el
|
||
:END:
|
||
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: org-time-tracking
|
||
:END:
|
||
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: installation
|
||
:END:
|
||
** from MELPA
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: install-from-melpa
|
||
:END:
|
||
|
||
1. Set up MELPA - https://melpa.org/#/getting-started
|
||
2. =M-x package-install RET chronometrist RET=
|
||
|
||
** from Git
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: install-from-git
|
||
:END:
|
||
|
||
You can get =chronometrist= from https://tildegit.org/contrapunctus/chronometrist or https://codeberg.org/contrapunctus/chronometrist
|
||
|
||
=chronometrist= requires
|
||
+ Emacs v25 or higher
|
||
+ [[https://github.com/magnars/dash.el][dash.el]]
|
||
+ [[https://github.com/alphapapa/ts.el][ts.el]]
|
||
|
||
Add the "elisp/" subdirectory to your load-path, and =(require 'chronometrist)=.
|
||
|
||
* Usage
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: usage
|
||
:END:
|
||
|
||
** chronometrist
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: usage-chronometrist
|
||
:END:
|
||
|
||
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 =<numeric prefix> 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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: usage-chronometrist-report
|
||
:END:
|
||
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: usage-chronometrist-statistics
|
||
:END:
|
||
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: usage-common-commands
|
||
:END:
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: time-goals
|
||
:END:
|
||
|
||
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 [[https://github.com/contrapunctus-1/chronometrist-goal/][chronometrist-goal.el]].
|
||
|
||
* How-to
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: how-to
|
||
:END:
|
||
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: how-to-prompt-when-exiting-emacs
|
||
:END:
|
||
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: how-to-literate-elisp
|
||
:END:
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(add-to-list 'load-path "<directory containing chronometrist.org>")
|
||
|
||
(require 'literate-elisp) ;; or autoload, use-package, ...
|
||
(literate-elisp-load "chronometrist.org")
|
||
#+END_SRC
|
||
|
||
** How to attach tags to time intervals
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: how-to-tags
|
||
:END:
|
||
|
||
1. Add =chronometrist-tags-add= to one or more of these hooks [fn:2] -
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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)
|
||
#+END_SRC
|
||
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=.
|
||
|
||
[fn:2] but not =chronometrist-before-in-functions=
|
||
|
||
** How to attach key-values to time intervals
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: how-to-key-value-pairs
|
||
:END:
|
||
|
||
1. Add =chronometrist-kv-add= to one or more of these hooks [fn:2] -
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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)
|
||
#+END_SRC
|
||
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: how-to-open-files-on-task-start
|
||
:END:
|
||
|
||
An idea from the author's own init -
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(defun my-start-project (project)
|
||
(pcase project
|
||
("Guitar"
|
||
(find-file-other-window "~/repertoire.org"))
|
||
;; ...
|
||
))
|
||
|
||
(add-hook 'chronometrist-before-in-functions 'my-start-project)
|
||
#+END_SRC
|
||
|
||
** How to warn yourself about uncommitted changes
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: how-to-warn-uncommitted-changes
|
||
:END:
|
||
|
||
Another one, prompting the user if they have uncommitted changes in a git repository (assuming they use [[https://magit.vc/][Magit]]) -
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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)
|
||
#+END_SRC
|
||
|
||
** How to display the current time interval in the activity indicator
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: how-to-activity-indicator
|
||
:END:
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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)
|
||
#+END_SRC
|
||
|
||
** How to back up your Chronometrist data
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: how-to-backup
|
||
:END:
|
||
I suggest backing up Chronometrist data on each save using the [[https://tildegit.org/contrapunctus/async-backup][async-backup]] package.[fn:1] Here's how you can do that.
|
||
|
||
1. Add the following to your init.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package async-backup)
|
||
#+END_SRC
|
||
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.
|
||
|
||
[fn:1] 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.
|
||
|
||
** How to configure Vertico for use with Chronometrist
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: howto-vertico
|
||
:END:
|
||
By default, [[https://github.com/minad/vertico][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 -
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq vertico-sort-function nil)
|
||
#+END_SRC
|
||
|
||
Or use =vertico-multiform= to disable sorting for only specific commands -
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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)))))
|
||
#+END_SRC
|
||
|
||
* Explanation
|
||
** Literate Program
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: explanation-literate-program
|
||
:END:
|
||
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 [[https://github.com/jingtaozf/literate-elisp][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-literate-elisp][How to load the program using 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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: contributions-contact
|
||
:END:
|
||
|
||
Feedback and MRs are very welcome. 🙂
|
||
+ [[file:TODO.org]] has a long list of tasks
|
||
+ [[file:elisp/chronometrist.org]] 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 - [[https://conversations.im/j/emacs@salas.suchat.org][xmpp:emacs@salas.suchat.org?join]] ([[https://inverse.chat/#converse/room?jid=emacs@salas.suchat.org][web chat]])
|
||
|
||
(For help in getting started with Jabber, [[https://xmpp.org/getting-started/][click here]])
|
||
|
||
* License
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: license
|
||
:END:
|
||
|
||
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 [[https://unlicense.org/][Unlicense]] or the [[http://www.wtfpl.net/][WTFPL]].
|
||
|
||
(See files [[file:UNLICENSE][UNLICENSE]] and [[file:WTFPL][WTFPL]]).
|
||
|
||
* Thanks
|
||
: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
|
||
|
||
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
|
||
|
||
* Local variables :noexport:
|
||
# Local Variables:
|
||
# my-org-src-default-lang: "emacs-lisp"
|
||
# End:
|