2019-07-29 10:59:17 +00:00
|
|
|
;;; chronometrist.el --- A time tracker for Emacs with a nice interface, built timeclock.el
|
|
|
|
|
2019-04-17 18:21:01 +00:00
|
|
|
(require 'filenotify)
|
2018-11-02 01:59:24 +00:00
|
|
|
(require 'chronometrist-common)
|
2019-04-08 16:40:26 +00:00
|
|
|
(require 'chronometrist-timer)
|
2018-09-23 09:21:15 +00:00
|
|
|
(require 'chronometrist-custom)
|
2018-09-18 21:21:29 +00:00
|
|
|
(require 'chronometrist-report)
|
2018-11-04 17:40:42 +00:00
|
|
|
(require 'chronometrist-statistics)
|
2018-08-27 07:56:04 +00:00
|
|
|
|
2019-08-04 19:27:22 +00:00
|
|
|
;;; Commentary:
|
|
|
|
;;
|
2019-01-08 08:03:12 +00:00
|
|
|
|
|
|
|
;; modifiers to toggling -
|
|
|
|
;; Nth task
|
|
|
|
;; reason (ask on start/ask on end/don't ask on end)
|
|
|
|
;; run/don't run hooks (maybe there should be a function to toggle this)
|
|
|
|
|
2018-08-28 03:47:35 +00:00
|
|
|
;; Style issues
|
2018-09-01 12:31:25 +00:00
|
|
|
;; 1. Uses Scheme-style ? and x->y naming conventions instead of
|
2018-08-28 18:17:25 +00:00
|
|
|
;; Elisp/CL-style "-p" and "x-to-y"
|
2018-09-01 12:31:25 +00:00
|
|
|
;; - ido uses ? for 'completion help', so you can't type ? unless
|
|
|
|
;; you unset that o\
|
2018-08-28 18:17:25 +00:00
|
|
|
;; 2. Should use *earmuffs* for global variables for clarity
|
2018-09-18 21:21:29 +00:00
|
|
|
;; 3. Should names of major modes (chronometrist-mode,
|
|
|
|
;; chronometrist-report-mode) end with -major-mode ?
|
2018-08-27 20:22:06 +00:00
|
|
|
|
|
|
|
;; Limitations of timeclock.el
|
|
|
|
;; 1. Concurrent tasks not permitted
|
2018-08-28 18:17:25 +00:00
|
|
|
;; 2. timeclock-project-list contains only the projects found in the
|
|
|
|
;; timeclock-file - no way for a user to specify tasks beforehand.
|
2018-09-01 12:31:25 +00:00
|
|
|
;; 3. Uses non-standard slashes in the date instead of dashes (e.g.
|
|
|
|
;; "2018/01/01" instead of "2018-01-01") and a space for the
|
|
|
|
;; date-time separator instead of T
|
2018-09-09 04:22:59 +00:00
|
|
|
|
2018-09-01 16:19:47 +00:00
|
|
|
;; Limitations of tabulated-list-mode
|
|
|
|
;; 1. Can't mix tabulated and non-tabulated data!!! What if I want
|
|
|
|
;; some buttons, separate from the data but part of the same
|
|
|
|
;; buffer?!
|
2018-09-09 04:22:59 +00:00
|
|
|
;; - adding non-tabular data after calling `tabulated-list-print' -
|
|
|
|
;; as we do - works, but is hacky and doesn't always print (e.g.
|
|
|
|
;; it vanishes when you sort). Then, you have to ensure you call
|
|
|
|
;; it after each time you call `tabulated-list-print' :\
|
|
|
|
;; - a post-print hook could help
|
|
|
|
;; - maybe use advice?
|
|
|
|
;; 2. Can't have multi-line headers
|
2018-09-24 08:24:37 +00:00
|
|
|
;; 3. Can't have multiple tables in a buffer
|
2018-09-11 12:28:25 +00:00
|
|
|
|
2018-09-01 12:31:25 +00:00
|
|
|
;; ## VARIABLES ##
|
2019-07-29 10:59:17 +00:00
|
|
|
;;; Code:
|
|
|
|
|
2018-09-20 12:21:58 +00:00
|
|
|
(defvar chronometrist--timer-object nil)
|
|
|
|
|
2018-10-03 19:23:37 +00:00
|
|
|
(defvar chronometrist--point nil)
|
|
|
|
|
2018-09-01 12:31:25 +00:00
|
|
|
;; ## FUNCTIONS ##
|
2018-09-18 21:21:29 +00:00
|
|
|
(defun chronometrist-current-project ()
|
2019-07-29 10:59:17 +00:00
|
|
|
"Return the name of the currently clocked-in project, or nil if the user is not clocked in."
|
2018-08-27 09:14:57 +00:00
|
|
|
(if (not (timeclock-currently-in-p))
|
|
|
|
nil
|
|
|
|
(with-current-buffer (find-file-noselect timeclock-file)
|
2018-08-31 14:29:50 +00:00
|
|
|
(save-excursion
|
|
|
|
(goto-char (point-max))
|
|
|
|
(forward-line -1)
|
2018-09-18 21:21:29 +00:00
|
|
|
(re-search-forward (concat chronometrist-time-re-file " ") nil t)
|
2018-08-31 14:29:50 +00:00
|
|
|
(buffer-substring-no-properties (point) (point-at-eol))))))
|
2018-08-27 09:14:57 +00:00
|
|
|
|
2018-09-18 21:21:29 +00:00
|
|
|
(defun chronometrist-project-active? (project)
|
2018-09-03 05:25:25 +00:00
|
|
|
"Return t if PROJECT is currently clocked in, else nil."
|
2018-09-18 21:21:29 +00:00
|
|
|
(equal (chronometrist-current-project) project))
|
2018-08-27 07:56:04 +00:00
|
|
|
|
2018-09-18 21:21:29 +00:00
|
|
|
(defun chronometrist-seconds-to-hms (seconds)
|
2019-07-29 10:59:17 +00:00
|
|
|
"Convert SECONDS to a vector in the form [HOURS MINUTES SECONDS].
|
|
|
|
SECONDS must be a positive integer."
|
2018-08-27 07:58:35 +00:00
|
|
|
(setq seconds (truncate seconds))
|
|
|
|
(let* ((s (% seconds 60))
|
|
|
|
(m (% (/ seconds 60) 60))
|
|
|
|
(h (/ seconds 3600)))
|
|
|
|
(vector h m s)))
|
2018-08-27 07:56:04 +00:00
|
|
|
|
2018-09-18 21:21:29 +00:00
|
|
|
(defun chronometrist-entries ()
|
2018-09-03 05:25:25 +00:00
|
|
|
"Create entries to be displayed in the buffer created by
|
2018-09-18 21:21:29 +00:00
|
|
|
`chronometrist'."
|
2018-09-01 20:46:05 +00:00
|
|
|
(timeclock-reread-log)
|
2019-01-08 19:21:03 +00:00
|
|
|
(chronometrist-events-populate)
|
|
|
|
(chronometrist-events-clean)
|
2018-09-02 10:55:25 +00:00
|
|
|
(->> timeclock-project-list
|
|
|
|
(-sort #'string-lessp)
|
2018-09-23 14:36:47 +00:00
|
|
|
(--map-indexed
|
|
|
|
(list it
|
|
|
|
(vector (number-to-string (1+ it-index))
|
|
|
|
(list it
|
|
|
|
'action 'chronometrist-toggle-project-button
|
|
|
|
'follow-link t)
|
|
|
|
(-> (chronometrist-project-time-one-day it)
|
|
|
|
(chronometrist-format-time))
|
|
|
|
(if (chronometrist-project-active? it)
|
|
|
|
"*" ""))))))
|
2018-09-11 12:27:34 +00:00
|
|
|
|
2018-09-18 21:21:29 +00:00
|
|
|
(defun chronometrist-project-at-point ()
|
2018-09-21 18:50:46 +00:00
|
|
|
"Return the project at point in the `chronometrist' buffer, or
|
|
|
|
nil if there is no project at point."
|
2018-09-02 10:55:25 +00:00
|
|
|
(save-excursion
|
|
|
|
(beginning-of-line)
|
2018-09-21 18:50:46 +00:00
|
|
|
(if (re-search-forward "[0-9]+ +" nil t)
|
|
|
|
(--> (buffer-substring-no-properties
|
|
|
|
(point)
|
|
|
|
(progn
|
|
|
|
(re-search-forward chronometrist-time-re-ui nil t)
|
|
|
|
(match-beginning 0)))
|
|
|
|
(replace-regexp-in-string "[ \t]*$" "" it))
|
|
|
|
nil)))
|
2018-09-02 05:45:46 +00:00
|
|
|
|
2018-09-18 21:21:29 +00:00
|
|
|
(defun chronometrist-goto-last-project ()
|
2018-09-02 11:07:26 +00:00
|
|
|
(goto-char (point-min))
|
|
|
|
(re-search-forward timeclock-last-project nil t)
|
|
|
|
(beginning-of-line))
|
|
|
|
|
2018-09-18 21:21:29 +00:00
|
|
|
(defun chronometrist-time-add (a b)
|
2018-09-03 05:29:33 +00:00
|
|
|
"Add two vectors in the form [HOURS MINUTES SECONDS] and
|
|
|
|
return a vector in the same form."
|
|
|
|
(let ((h1 (elt a 0))
|
|
|
|
(m1 (elt a 1))
|
|
|
|
(s1 (elt a 2))
|
2018-09-13 19:57:02 +00:00
|
|
|
(h2 (elt b 0))
|
|
|
|
(m2 (elt b 1))
|
2018-09-03 05:29:33 +00:00
|
|
|
(s2 (elt b 2)))
|
2018-09-18 21:21:29 +00:00
|
|
|
(chronometrist-seconds-to-hms (+ (* h1 3600) (* h2 3600)
|
2018-09-11 12:44:03 +00:00
|
|
|
(* m1 60) (* m2 60)
|
|
|
|
s1 s2))))
|
2018-09-03 05:29:33 +00:00
|
|
|
|
2018-09-18 21:21:29 +00:00
|
|
|
(defun chronometrist-total-time-one-day (&optional date)
|
2018-09-11 11:36:16 +00:00
|
|
|
"Return the total time clocked on DATE (if non-nil) or
|
|
|
|
today, as a vector in the form [HOURS MINUTES SECONDS].
|
|
|
|
|
|
|
|
DATE must be calendrical information calendrical
|
|
|
|
information (see (info \"(elisp)Time Conversion\"))."
|
|
|
|
(->> timeclock-project-list
|
2018-09-18 21:21:29 +00:00
|
|
|
(--map (chronometrist-project-time-one-day it date))
|
|
|
|
(-reduce #'chronometrist-time-add)))
|
2018-09-03 05:29:33 +00:00
|
|
|
|
2018-09-28 07:03:52 +00:00
|
|
|
(defun chronometrist-format-keybinds (command &optional firstonly)
|
|
|
|
(if firstonly
|
|
|
|
(key-description
|
|
|
|
(where-is-internal command chronometrist-mode-map firstonly))
|
|
|
|
(->> (where-is-internal command chronometrist-mode-map)
|
|
|
|
(mapcar #'key-description)
|
|
|
|
(-take 2)
|
2018-09-28 13:53:44 +00:00
|
|
|
(-interpose ", ")
|
2018-09-28 07:03:52 +00:00
|
|
|
(apply #'concat))))
|
|
|
|
|
|
|
|
(defun chronometrist-print-keybind (command &optional description firstonly)
|
|
|
|
(insert
|
2018-09-28 13:53:44 +00:00
|
|
|
"\n"
|
|
|
|
(format "% 18s - %s"
|
2018-09-28 07:03:52 +00:00
|
|
|
(chronometrist-format-keybinds command firstonly)
|
|
|
|
(if description description ""))))
|
|
|
|
|
2018-09-18 21:21:29 +00:00
|
|
|
(defun chronometrist-print-non-tabular ()
|
|
|
|
"Print the non-tabular part of the buffer in `chronometrist'."
|
2018-09-28 07:03:52 +00:00
|
|
|
(with-current-buffer chronometrist-buffer-name
|
|
|
|
(let ((inhibit-read-only t)
|
|
|
|
(w "\n ")
|
|
|
|
(keybind-start-new (chronometrist-format-keybinds 'chronometrist-add-new-project))
|
|
|
|
(keybind-toggle (chronometrist-format-keybinds 'chronometrist-toggle-project t)))
|
|
|
|
(goto-char (point-max))
|
|
|
|
(-->
|
|
|
|
(chronometrist-total-time-one-day)
|
|
|
|
(chronometrist-format-time it)
|
|
|
|
(format "%s%- 26s%s" w "Total" it)
|
|
|
|
(insert it))
|
|
|
|
|
|
|
|
(insert "\n")
|
2018-09-28 13:53:44 +00:00
|
|
|
(insert w (format "% 17s" "Keys")
|
|
|
|
w (format "% 17s" "----"))
|
2018-09-28 07:03:52 +00:00
|
|
|
|
2018-09-28 13:53:44 +00:00
|
|
|
(chronometrist-print-keybind 'chronometrist-add-new-project)
|
2018-09-28 07:03:52 +00:00
|
|
|
(insert-text-button "start a new project"
|
|
|
|
'action #'chronometrist-add-new-project-button
|
|
|
|
'follow-link t)
|
|
|
|
|
|
|
|
(chronometrist-print-keybind 'chronometrist-toggle-project
|
|
|
|
"toggle project at point")
|
|
|
|
|
|
|
|
(chronometrist-print-keybind 'chronometrist-toggle-project-no-reason
|
|
|
|
"toggle without asking for reason")
|
|
|
|
|
2018-09-28 13:53:44 +00:00
|
|
|
(insert "\n " (format "%s %s - %s"
|
|
|
|
"<numeric argument N>"
|
|
|
|
keybind-toggle
|
|
|
|
"toggle <N>th project"))
|
2018-09-28 07:03:52 +00:00
|
|
|
|
|
|
|
(chronometrist-print-keybind 'chronometrist-report)
|
|
|
|
(insert-text-button "see weekly report"
|
|
|
|
'action #'chronometrist-report
|
|
|
|
'follow-link t)
|
|
|
|
|
2018-09-28 13:53:44 +00:00
|
|
|
(chronometrist-print-keybind 'chronometrist-open-timeclock-file)
|
2018-09-28 07:03:52 +00:00
|
|
|
(insert-text-button "open log file"
|
|
|
|
'action #'chronometrist-open-timeclock-file
|
2019-01-08 08:03:54 +00:00
|
|
|
'follow-link t)
|
|
|
|
(insert "\n"))))
|
2018-09-03 05:29:33 +00:00
|
|
|
|
2018-10-03 10:36:24 +00:00
|
|
|
(defun chronometrist-goto-nth-project (n)
|
2018-09-30 15:58:23 +00:00
|
|
|
"Move point to the beginning of the line containing the Nth
|
|
|
|
project in a `chronometrist' buffer. Return the project at point,
|
|
|
|
or nil if there is no corresponding project. N must be a positive
|
|
|
|
integer."
|
|
|
|
(goto-char (point-min))
|
|
|
|
(when (re-search-forward (format "^%d" n) nil t)
|
|
|
|
(beginning-of-line)
|
|
|
|
(chronometrist-project-at-point)))
|
2018-09-21 18:50:46 +00:00
|
|
|
|
2019-01-16 11:23:41 +00:00
|
|
|
(defun chronometrist-refresh (&optional ignore-auto noconfirm)
|
2019-04-08 15:30:56 +00:00
|
|
|
"The optional arguments IGNORE-AUTO and NOCONFIRM are ignored,
|
|
|
|
and are present solely for the sake of using this function as a
|
|
|
|
value of `revert-buffer-function'."
|
2018-09-28 18:27:01 +00:00
|
|
|
(let* ((w (get-buffer-window chronometrist-buffer-name t))
|
|
|
|
(p (window-point w)))
|
|
|
|
(with-current-buffer chronometrist-buffer-name
|
|
|
|
(tabulated-list-print t nil)
|
|
|
|
(chronometrist-print-non-tabular)
|
|
|
|
(chronometrist-maybe-start-timer)
|
|
|
|
(set-window-point w p))))
|
2018-09-24 05:00:31 +00:00
|
|
|
|
2019-04-08 15:30:56 +00:00
|
|
|
(defun chronometrist-refresh-file (fs-event)
|
|
|
|
(chronometrist-events-populate)
|
|
|
|
(chronometrist-events-clean)
|
|
|
|
(timeclock-reread-log)
|
|
|
|
(chronometrist-refresh))
|
|
|
|
|
2019-01-08 09:29:58 +00:00
|
|
|
;; FIXME - has some duplicate logic with `chronometrist-project-events-in-day'
|
|
|
|
(defun chronometrist-reason-list (project)
|
|
|
|
"Filters `timeclock-reason-list' to only return reasons for PROJECT."
|
|
|
|
(let (save-next results)
|
|
|
|
(maphash (lambda (date events)
|
|
|
|
(seq-do (lambda (event)
|
|
|
|
(cond ((and (equal "i" (chronometrist-vfirst event))
|
|
|
|
(equal project (chronometrist-vlast event)))
|
|
|
|
(setq save-next t))
|
|
|
|
(save-next
|
|
|
|
(->> (chronometrist-vlast event)
|
|
|
|
(list)
|
|
|
|
(append results)
|
|
|
|
(setq results))
|
|
|
|
(setq save-next nil))
|
|
|
|
(t nil)))
|
|
|
|
events))
|
|
|
|
chronometrist-events)
|
|
|
|
(->> results
|
2019-01-09 19:04:38 +00:00
|
|
|
;; the order of reverse and seq-uniq is important, so that
|
|
|
|
;; the most recent reasons come first in the history
|
|
|
|
(reverse)
|
2019-01-08 09:29:58 +00:00
|
|
|
(seq-uniq)
|
|
|
|
(seq-remove (lambda (elt)
|
2019-01-09 19:04:38 +00:00
|
|
|
(equal elt ""))))))
|
2019-01-08 09:29:58 +00:00
|
|
|
|
2019-01-07 17:14:08 +00:00
|
|
|
(defun chronometrist-ask-for-reason ()
|
2019-01-08 09:29:58 +00:00
|
|
|
"Replacement for `timeclock-ask-for-reason' which uses
|
|
|
|
`read-from-minibuffer' instead of `completing-read'. (see
|
|
|
|
`timeclock-get-reason-function')
|
|
|
|
|
|
|
|
Additionally, it uses `chronometrist-reason-list' to only suggest
|
|
|
|
reasons used for the relevant project, instead of all reasons as
|
|
|
|
in `timeclock-reason-list'."
|
|
|
|
(let ((reason-history (chronometrist-reason-list timeclock-last-project)))
|
2019-01-11 19:41:45 +00:00
|
|
|
(read-from-minibuffer "Reason for clocking out (optional): " nil nil nil
|
2019-01-08 09:29:58 +00:00
|
|
|
'reason-history)))
|
2019-01-07 17:14:08 +00:00
|
|
|
|
2018-09-27 22:16:55 +00:00
|
|
|
;; ## HOOKS ##
|
|
|
|
|
|
|
|
(defvar chronometrist-project-start-hook nil
|
|
|
|
"Hook run before a project is clocked in. Each function in this hook must accept a single argument, which is the project to be clocked-in.
|
|
|
|
|
|
|
|
The commands `chronometrist-toggle-project-button',
|
|
|
|
`chronometrist-add-new-project-button',
|
|
|
|
`chronometrist-toggle-project',
|
|
|
|
`chronometrist-add-new-project', and
|
|
|
|
`chronometrist-toggle-project-no-reason' will run this hook.")
|
|
|
|
|
|
|
|
(defvar chronometrist-project-stop-hook nil
|
|
|
|
"Hook run after a project is clocked out. Each function in this
|
|
|
|
hook must accept a single argument, which is the clocked-out
|
|
|
|
project.")
|
|
|
|
|
2018-09-25 21:18:52 +00:00
|
|
|
(defun chronometrist-run-project-start-hook (project)
|
2018-09-26 17:07:50 +00:00
|
|
|
(run-hook-with-args 'chronometrist-project-start-hook project))
|
2018-09-25 21:18:52 +00:00
|
|
|
|
|
|
|
(defun chronometrist-run-project-end-hook (project)
|
2018-09-27 22:16:55 +00:00
|
|
|
(run-hook-with-args 'chronometrist-project-stop-hook project))
|
2018-09-24 05:00:31 +00:00
|
|
|
|
2018-09-01 12:31:25 +00:00
|
|
|
;; ## MAJOR-MODE ##
|
2018-09-28 12:28:37 +00:00
|
|
|
(defvar chronometrist-mode-map
|
|
|
|
(let ((map (make-sparse-keymap)))
|
|
|
|
(define-key map (kbd "RET") #'chronometrist-toggle-project)
|
|
|
|
(define-key map (kbd "M-RET") #'chronometrist-toggle-project-no-reason)
|
|
|
|
(define-key map (kbd "l") #'chronometrist-open-timeclock-file)
|
|
|
|
(define-key map (kbd "r") #'chronometrist-report)
|
|
|
|
(define-key map [mouse-1] #'chronometrist-toggle-project)
|
|
|
|
(define-key map [mouse-3] #'chronometrist-toggle-project-no-reason)
|
|
|
|
(define-key map (kbd "a") #'chronometrist-add-new-project)
|
|
|
|
map)
|
|
|
|
"Keymap used by `chronometrist-mode'.")
|
|
|
|
|
2018-09-18 21:21:29 +00:00
|
|
|
(define-derived-mode chronometrist-mode tabulated-list-mode "Chronometrist"
|
|
|
|
"Major mode for `chronometrist'."
|
2018-09-11 12:39:38 +00:00
|
|
|
(timeclock-reread-log)
|
|
|
|
(make-local-variable 'tabulated-list-format)
|
2018-09-23 14:36:47 +00:00
|
|
|
(setq tabulated-list-format [("#" 3 t)
|
2018-09-02 10:55:25 +00:00
|
|
|
("Project" 25 t)
|
2018-09-23 14:36:47 +00:00
|
|
|
("Time" 10 t)
|
|
|
|
("Active" 3 t)])
|
2018-09-11 12:39:38 +00:00
|
|
|
(make-local-variable 'tabulated-list-entries)
|
2018-09-18 21:21:29 +00:00
|
|
|
(setq tabulated-list-entries 'chronometrist-entries)
|
2018-09-11 12:39:38 +00:00
|
|
|
(make-local-variable 'tabulated-list-sort-key)
|
|
|
|
(setq tabulated-list-sort-key '("Project" . nil))
|
2019-01-16 11:23:41 +00:00
|
|
|
(tabulated-list-init-header)
|
2019-01-18 05:42:46 +00:00
|
|
|
(setq revert-buffer-function #'chronometrist-refresh
|
|
|
|
timeclock-get-reason-function #'chronometrist-ask-for-reason))
|
2018-09-26 19:18:14 +00:00
|
|
|
|
2018-09-25 14:45:55 +00:00
|
|
|
;; ## BUTTONS ##
|
2018-09-01 12:31:25 +00:00
|
|
|
|
2019-01-08 08:03:12 +00:00
|
|
|
;; FIXME - there is duplication between this function and `chronometrist-toggle-project's logic
|
2018-09-23 14:36:47 +00:00
|
|
|
(defun chronometrist-toggle-project-button (button)
|
2018-09-26 04:52:30 +00:00
|
|
|
(let ((current (chronometrist-current-project))
|
2018-09-25 21:18:52 +00:00
|
|
|
(at-point (chronometrist-project-at-point)))
|
|
|
|
;; clocked in + point on current = clock out
|
|
|
|
;; clocked in + point on some other project = clock out, clock in to project
|
|
|
|
;; clocked out = clock in
|
|
|
|
(when current
|
2018-09-26 17:06:55 +00:00
|
|
|
(timeclock-out nil nil t)
|
|
|
|
(chronometrist-run-project-end-hook current))
|
2018-09-25 21:18:52 +00:00
|
|
|
(unless (equal at-point current)
|
|
|
|
(chronometrist-run-project-start-hook at-point)
|
|
|
|
(timeclock-in nil at-point nil))
|
2018-09-24 05:00:31 +00:00
|
|
|
(chronometrist-refresh)))
|
2018-09-23 14:36:47 +00:00
|
|
|
|
2018-09-25 14:45:55 +00:00
|
|
|
(defun chronometrist-add-new-project-button (button)
|
2018-09-25 21:18:52 +00:00
|
|
|
(let ((current (chronometrist-current-project)))
|
|
|
|
(when current
|
2018-09-26 17:06:55 +00:00
|
|
|
(timeclock-out nil nil t)
|
|
|
|
(chronometrist-run-project-end-hook current))
|
2018-09-28 10:12:09 +00:00
|
|
|
(let ((p (read-from-minibuffer "New project name: " nil nil nil nil nil t)))
|
|
|
|
(chronometrist-run-project-start-hook p)
|
|
|
|
(timeclock-in nil p nil))
|
2018-09-25 21:18:52 +00:00
|
|
|
(chronometrist-refresh)))
|
2018-09-25 14:45:55 +00:00
|
|
|
|
|
|
|
;; ## COMMANDS ##
|
|
|
|
|
2018-10-19 18:06:07 +00:00
|
|
|
;; TODO - if clocked in and point not on a project, just clock out
|
2019-08-04 19:27:22 +00:00
|
|
|
;; PROFILE
|
2018-09-25 14:45:55 +00:00
|
|
|
(defun chronometrist-toggle-project (&optional prefix no-prompt)
|
2018-09-21 18:50:46 +00:00
|
|
|
"In a `chronometrist' buffer, start or stop the project at
|
|
|
|
point. If there is no project at point, do nothing.
|
|
|
|
|
|
|
|
With a numeric prefix argument, toggle the Nth project. If there
|
|
|
|
is no corresponding project, do nothing."
|
2018-09-03 07:50:14 +00:00
|
|
|
(interactive "P")
|
2018-09-26 17:06:26 +00:00
|
|
|
(let* ((empty-file (chronometrist-common-file-empty-p timeclock-file))
|
2018-09-30 15:58:23 +00:00
|
|
|
(nth (when prefix (chronometrist-goto-nth-project prefix)))
|
2018-09-26 17:06:26 +00:00
|
|
|
(at-point (chronometrist-project-at-point))
|
|
|
|
(target (or nth at-point))
|
|
|
|
(current (chronometrist-current-project))
|
|
|
|
(ask (not no-prompt)))
|
|
|
|
(cond (empty-file (chronometrist-add-new-project)) ;; do not run hooks - chronometrist-add-new-project will do it
|
2018-09-21 18:50:46 +00:00
|
|
|
;; What should we do if the user provides an invalid argument? Currently - nothing.
|
2018-09-26 17:06:26 +00:00
|
|
|
((and prefix (not nth)))
|
|
|
|
(target ;; do nothing if there's no project at point
|
|
|
|
;; clocked in + target is current = clock out
|
|
|
|
;; clocked in + target is some other project = clock out, clock in to project
|
|
|
|
;; clocked out = clock in
|
|
|
|
(when current
|
|
|
|
(timeclock-out nil nil ask)
|
|
|
|
(chronometrist-run-project-end-hook current))
|
|
|
|
(unless (equal target current)
|
2018-09-28 14:18:33 +00:00
|
|
|
(chronometrist-run-project-start-hook target)
|
2018-09-26 17:06:26 +00:00
|
|
|
(timeclock-in nil target nil))))
|
2018-09-24 05:00:31 +00:00
|
|
|
(chronometrist-refresh)))
|
2018-09-11 12:27:34 +00:00
|
|
|
|
2018-09-25 14:45:55 +00:00
|
|
|
(defun chronometrist-toggle-project-no-reason (&optional prefix)
|
|
|
|
"Like `chronometrist-toggle-project', but do not ask for a
|
|
|
|
reason if clocking out."
|
|
|
|
(interactive "P")
|
|
|
|
(funcall-interactively #'chronometrist-toggle-project prefix t))
|
|
|
|
|
|
|
|
(defun chronometrist-add-new-project ()
|
|
|
|
(interactive)
|
|
|
|
(chronometrist-add-new-project-button nil))
|
2018-09-23 19:24:34 +00:00
|
|
|
|
2019-08-04 19:27:22 +00:00
|
|
|
;;;###autoload
|
2018-09-18 21:21:29 +00:00
|
|
|
(defun chronometrist (&optional arg)
|
2018-09-11 12:39:38 +00:00
|
|
|
"Displays a list of the user's timeclock.el projects and the
|
|
|
|
time spent on each today, based on their timelog file
|
|
|
|
`timeclock-file'. The user can hit RET to start/stop projects.
|
2018-11-02 08:28:54 +00:00
|
|
|
This is the 'listing command' for chronometrist-mode.
|
|
|
|
|
|
|
|
With numeric argument 1, run `chronometrist-report'.
|
|
|
|
With numeric argument 2, run `chronometrist-statistics'."
|
2018-09-01 16:19:47 +00:00
|
|
|
(interactive "P")
|
2018-10-03 10:36:24 +00:00
|
|
|
(let ((buffer (get-buffer-create chronometrist-buffer-name))
|
|
|
|
(w (get-buffer-window chronometrist-buffer-name t)))
|
|
|
|
(cond
|
2018-11-02 08:28:54 +00:00
|
|
|
(arg (case arg
|
|
|
|
(1 (chronometrist-report))
|
|
|
|
(2 (chronometrist-statistics))))
|
|
|
|
(w (with-current-buffer buffer
|
|
|
|
(setq chronometrist--point (point))
|
|
|
|
(kill-buffer chronometrist-buffer-name)))
|
|
|
|
(t (with-current-buffer buffer
|
|
|
|
(cond ((or (not (file-exists-p timeclock-file))
|
|
|
|
(chronometrist-common-file-empty-p timeclock-file))
|
|
|
|
;; first run
|
|
|
|
(chronometrist-common-create-timeclock-file)
|
|
|
|
(let ((inhibit-read-only t))
|
|
|
|
(chronometrist-common-clear-buffer buffer)
|
|
|
|
(insert "Welcome to Chronometrist! Hit RET to ")
|
|
|
|
(insert-text-button "start a new project."
|
|
|
|
'action #'chronometrist-add-new-project-button
|
|
|
|
'follow-link t)
|
|
|
|
(chronometrist-mode)
|
|
|
|
(switch-to-buffer buffer)))
|
|
|
|
(t (chronometrist-mode)
|
|
|
|
(when chronometrist-hide-cursor
|
|
|
|
(make-local-variable 'cursor-type)
|
|
|
|
(setq cursor-type nil)
|
|
|
|
(hl-line-mode))
|
|
|
|
(switch-to-buffer buffer)
|
|
|
|
(chronometrist-refresh)
|
|
|
|
(if chronometrist--point
|
|
|
|
(goto-char chronometrist--point)
|
2019-04-08 15:30:56 +00:00
|
|
|
(chronometrist-goto-last-project))))
|
|
|
|
(file-notify-add-watch timeclock-file '(change) #'chronometrist-refresh-file))))))
|
2018-09-11 12:27:34 +00:00
|
|
|
|
2018-09-09 07:35:20 +00:00
|
|
|
;; Local Variables:
|
2018-09-18 21:21:29 +00:00
|
|
|
;; nameless-current-name: "chronometrist"
|
2018-09-09 07:35:20 +00:00
|
|
|
;; End:
|
2019-07-29 10:59:17 +00:00
|
|
|
|
|
|
|
(provide 'chronometrist)
|
|
|
|
|
|
|
|
;;; chronometrist.el ends here
|