444 lines
18 KiB
Org Mode
444 lines
18 KiB
Org Mode
#+TITLE: Chronometrist
|
||
#+SUBTITLE: Friendly and powerful personal time tracker/analyzer with Emacs and CLIM frontends
|
||
#+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
|
||
|
||
* Explanation
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: explanation
|
||
:END:
|
||
Chronometrist is a friendly and powerful personal time tracker and analyzer. It has frontends for Emacs and [[https://mcclim.common-lisp.dev/][CLIM]].
|
||
|
||
#+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 (including fancy graphs with the =chronometrist-spark= extension)
|
||
3. Support for both mouse and keyboard
|
||
4. Human errors in tracking can be easily fixed by editing a plain text file
|
||
5. Hooks to integrate time tracking into your workflow
|
||
|
||
** Limitations
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: limitations
|
||
:END:
|
||
1. No support for concurrent tasks.
|
||
|
||
** Comparisons
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: comparisons
|
||
:END:
|
||
*** timeclock.el (Emacs built-in)
|
||
: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.
|
||
|
||
** Common Lisp port
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: common-lisp-port
|
||
:END:
|
||
In March 2022, work began on the long-awaited Common Lisp port of Chronometrist, which aims to create -
|
||
1. a greater variety of backends (e.g. SQLite)
|
||
2. a common reusable library for frontends to use,
|
||
3. a greater variety of frontends, such as -
|
||
* a command line interface (CLI), for UNIX scripting;
|
||
* a terminal user inteface (TUI), for those so inclined;
|
||
* a CLIM (Common Lisp Interface Manager) GUI [fn:1],
|
||
* Qt and Android interfaces using [[https://gitlab.com/eql/lqml][LQML]],
|
||
* web frontends (possibly via [[https://common-lisp.net/project/parenscript/][Parenscript]] or [[https://github.com/rabbibotton/clog][CLOG]]),
|
||
* and perhaps even an interface for wearable devices!
|
||
|
||
The port was also driven by the desire to have access to Common Lisp's better performance, and features such as namespaces, a /de facto/ standard build system, multithreading, SQLite bindings, a more fully-featured implementation of CLOS and MOP, and type annotations, checking, and inference.
|
||
|
||
The literate sources for the Common Lisp port may be found in [[file:cl/chronometrist.org][cl/chronometrist.org]]. Currently, this port can -
|
||
1. import from a plist-group file and export to an SQLite database
|
||
#+BEGIN_SRC lisp
|
||
(chronometrist:to-file (chronometrist:to-hash-table
|
||
(make-instance 'chronometrist.plist-group:plist-group-backend
|
||
:file "/path/to/file.plg"))
|
||
(make-instance 'chronometrist.sqlite:sqlite-backend)
|
||
"/path/to/file.sqlite")
|
||
#+END_SRC
|
||
2. display a (WIP) CLIM GUI - =(chronometrist.clim:run-chronometrist)=
|
||
|
||
The Emacs Lisp codebase will probably become an Emacs frontend to a future Common Lisp CLI client.
|
||
|
||
[fn:1] McCLIM also has an incomplete ncurses backend - when completed, a CLIM frontend could provide a TUI "for free".
|
||
|
||
** Literate program
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: explanation-literate-program
|
||
:END:
|
||
Chronometrist is written as an Org literate program, which makes it easy to obtain different views of the program source, thanks to tree- and source-block folding, tags, properties, and the =org-match= command.
|
||
|
||
The canonical source file is [[file:elisp/chronometrist.org][elisp/chronometrist.org]], which contains source blocks. These are provided to users after /tangling/ (extracting the source into an Emacs Lisp file). [fn:2]
|
||
|
||
The Org literate program 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. See [[#how-to-literate-elisp][How to load the program using literate-elisp]].
|
||
|
||
[fn:2] the literate source is also included in MELPA installs, although not loaded through =literate-elisp-load= by default, since doing so would interfere with automatic generation of autoloads.
|
||
|
||
** Source code overview
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: source-code-overview
|
||
:END:
|
||
At its most basic, we read data from a [[file:elisp/chronometrist.org::#program-backend][backend]] and [[file:elisp/chronometrist.org::#program-frontend-chronometrist][display it]] as a [[elisp:(find-library "tabulated-list")][=tabulated-list-mode=]] buffer.
|
||
|
||
The plist and plist-group backends (collectively known as the s-expression backends) =read= a text file containing s-expressions into a [[file:elisp/chronometrist.org::#program-data-structures][hash table]], and query that. When the file is changed—whether by the program or the user—they [[file:elisp/chronometrist.org::refresh-file][update the hash table]] and the [[file:elisp/chronometrist.org::#program-frontend-chronometrist-refresh][buffer]]. The s-expression backends also make use of a [[file:elisp/chronometrist.org::#program-pretty-printer][plist pretty-printer]] of their own.
|
||
|
||
There are also some [[file:elisp/chronometrist.org::#program-migration][migration commands]].
|
||
|
||
Extensions exist for -
|
||
1. [[file:elisp/chronometrist-key-values.org][attaching arbitrary metadata]] to time intervals,
|
||
2. [[https://tildegit.org/contrapunctus/chronometrist-goal][time goals and alerts]], and
|
||
3. support for the [[file:elisp/chronometrist-third.org][Third Time system]]
|
||
|
||
** 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'd /like/ for all software to be liberated - transparent, trustable, and accessible for anyone to use, study, or improve.
|
||
|
||
I'd /like/ anyone using my software to credit me for the work.
|
||
|
||
I'd /like/ to receive financial support for my efforts, so I can spend all my time doing what I find meaningful.
|
||
|
||
But I don't want to make demands or threats (e.g. via legal conditions) to accomplish all that, nor restrict my services to only those who can pay.
|
||
|
||
Thus, 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
|
||
|
||
* Tutorials
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: usage
|
||
:END:
|
||
** 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)=.
|
||
|
||
** 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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: chronometrist-details
|
||
:END:
|
||
|
||
** 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 Guides
|
||
: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:
|
||
The literate Org document will automatically =literate-elisp-load= itself when opened, if =literate-elisp= is installed via =package.el=.
|
||
|
||
If you want it to be loaded with =literate-elisp-load= on Emacs startup, add the following to your init.el -
|
||
#+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:3] -
|
||
|
||
#+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:3] 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:3] -
|
||
|
||
#+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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: how-to-skip-running-hooks/attaching-tags-and-key-values
|
||
:END:
|
||
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:4] 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 =async-backup-location= to set a specific directory for the backups -
|
||
: (setq async-backup-location "/path/to/backup/dir/")
|
||
|
||
[fn:4] 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
|
||
|
||
* User's reference
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: users-reference
|
||
:END:
|
||
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=
|
||
|
||
* Local variables :noexport:
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: local-variables
|
||
:END:
|
||
# Local Variables:
|
||
# my-org-src-default-lang: "emacs-lisp"
|
||
# End:
|