diff options
Diffstat (limited to 'lisp/org/org-clock.el')
-rw-r--r-- | lisp/org/org-clock.el | 141 |
1 files changed, 112 insertions, 29 deletions
diff --git a/lisp/org/org-clock.el b/lisp/org/org-clock.el index 06df2d49719..9efd99be826 100644 --- a/lisp/org/org-clock.el +++ b/lisp/org/org-clock.el @@ -35,11 +35,17 @@ (declare-function notifications-notify "notifications" (&rest params)) (declare-function org-element-property "org-element" (property element)) (declare-function org-element-type "org-element" (element)) +(declare-function org-inlinetask-at-task-p "org-inlinetask" ()) +(declare-function org-inlinetask-goto-beginning "org-inlinetask" ()) +(declare-function org-inlinetask-goto-end "org-inlinetask" ()) +(declare-function org-inlinetask-in-task-p "org-inlinetask" ()) (declare-function org-link-display-format "ol" (s)) (declare-function org-link-heading-search-string "ol" (&optional string)) (declare-function org-link-make-string "ol" (link &optional description)) (declare-function org-table-goto-line "org-table" (n)) (declare-function org-dynamic-block-define "org" (type func)) +(declare-function w32-notification-notify "w32fns.c" (&rest params)) +(declare-function w32-notification-close "w32fns.c" (&rest params)) (defvar org-frame-title-format-backup nil) (defvar org-state) @@ -273,6 +279,15 @@ also using the face `org-mode-line-clock-overrun'." (const :tag "Just mark the time string" nil) (string :tag "Text to prepend"))) +(defcustom org-show-notification-timeout 3 + "Number of seconds to wait before closing Org notifications. +This is applied to notifications sent with `notifications-notify' +and `w32-notification-notify' only, not other mechanisms possibly +set through `org-show-notification-handler'." + :group 'org-clock + :package-version '(Org . "9.4") + :type 'integer) + (defcustom org-show-notification-handler nil "Function or program to send notification with. The function or program will be called with the notification @@ -457,6 +472,19 @@ Valid values are: `today', `yesterday', `thisweek', `lastweek', (const :tag "Select range interactively" interactive)) :safe #'symbolp) +(defcustom org-clock-auto-clockout-timer nil + "Timer for auto clocking out when Emacs is idle. +When set to a number, auto clock out the currently clocked in +task after this number of seconds of idle time. + +This is only effective when `org-clock-auto-clockout-insinuate' +is added to the user configuration." + :group 'org-clock + :package-version '(Org . "9.4") + :type '(choice + (integer :tag "Clock out after Emacs is idle for X seconds") + (const :tag "Never auto clock out" nil))) + (defvar org-clock-in-prepare-hook nil "Hook run when preparing the clock. This hook is run before anything happens to the task that @@ -698,7 +726,8 @@ If not, show simply the clocked time like 01:50." (save-excursion (let ((end (save-excursion (org-end-of-subtree)))) (when (re-search-forward (concat org-clock-string - ".*\\]--\\(\\[[^]]+\\]\\)") end t) + ".*\\]--\\(\\[[^]]+\\]\\)") + end t) (org-time-string-to-time (match-string 1)))))) (defun org-clock-update-mode-line (&optional refresh) @@ -725,7 +754,8 @@ menu\nmouse-2 will jump to task")) (setq org-mode-line-string (concat (propertize org-clock-task-overrun-text - 'face 'org-mode-line-clock-overrun) org-mode-line-string))) + 'face 'org-mode-line-clock-overrun) + org-mode-line-string))) (force-mode-line-update)) (defun org-clock-get-clocked-time () @@ -808,15 +838,26 @@ If PLAY-SOUND is non-nil, it overrides `org-clock-sound'." "Show notification. Use `org-show-notification-handler' if defined, use libnotify if available, or fall back on a message." + (ignore-errors (require 'notifications)) (cond ((functionp org-show-notification-handler) (funcall org-show-notification-handler notification)) ((stringp org-show-notification-handler) (start-process "emacs-timer-notification" nil org-show-notification-handler notification)) + ((fboundp 'w32-notification-notify) + (let ((id (w32-notification-notify + :title "Org mode message" + :body notification + :urgency 'low))) + (run-with-timer + org-show-notification-timeout + nil + (lambda () (w32-notification-close id))))) ((fboundp 'notifications-notify) (notifications-notify :title "Org mode message" :body notification + :timeout (* org-show-notification-timeout 1000) ;; FIXME how to link to the Org icon? ;; :app-icon "~/.emacs.d/icons/mail.png" :urgency 'low)) @@ -859,7 +900,8 @@ If CLOCK-SOUND is non-nil, it overrides `org-clock-sound'." (goto-char (point-min)) (while (re-search-forward org-clock-re nil t) (push (cons (copy-marker (match-end 1) t) - (org-time-string-to-time (match-string 1))) clocks)))) + (org-time-string-to-time (match-string 1))) + clocks)))) clocks)) (defsubst org-is-active-clock (clock) @@ -983,7 +1025,7 @@ CLOCK is a cons cell of the form (MARKER START-TIME)." (let ((element (org-element-at-point))) (when (eq (org-element-type element) 'drawer) (when (> (org-element-property :end element) (car clock)) - (org-flag-drawer nil element)) + (org-hide-drawer-toggle 'off nil element)) (throw 'exit nil))))))))))) (defun org-clock-resolve (clock &optional prompt-fn last-valid fail-quietly) @@ -1022,6 +1064,9 @@ k/K Keep X minutes of the idle time (default is all). If this that many minutes after the time that idling began, and then clocked back in at the present time. +t/T Like `k', but will ask you to specify a time (when you got + distracted away), instead of a number of minutes. + g/G Indicate that you \"got back\" X minutes ago. This is quite different from `k': it clocks you out from the beginning of the idle period and clock you back in X minutes ago. @@ -1041,19 +1086,24 @@ to be CLOCKED OUT.")))) (while (or (null char-pressed) (and (not (memq char-pressed '(?k ?K ?g ?G ?s ?S ?C - ?j ?J ?i ?q))) + ?j ?J ?i ?q ?t ?T))) (or (ding) t))) (setq char-pressed (read-char (concat (funcall prompt-fn clock) - " [jkKgGSscCiq]? ") + " [jkKtTgGSscCiq]? ") nil 45))) (and (not (memq char-pressed '(?i ?q))) char-pressed))))) (default (floor (org-time-convert-to-integer (org-time-since last-valid)) 60)) (keep - (and (memq ch '(?k ?K)) - (read-number "Keep how many minutes? " default))) + (or (and (memq ch '(?k ?K)) + (read-number "Keep how many minutes? " default)) + (and (memq ch '(?t ?T)) + (floor + (/ (float-time + (org-time-subtract (org-read-date t t) last-valid)) + 60))))) (gotback (and (memq ch '(?g ?G)) (read-number "Got back how many minutes ago? " default))) @@ -1068,7 +1118,7 @@ to be CLOCKED OUT.")))) (org-clock-resolve-clock clock 'now nil t nil fail-quietly)) (org-clock-jump-to-current-clock clock)) ((or (null ch) - (not (memq ch '(?k ?K ?g ?G ?s ?S ?C)))) + (not (memq ch '(?k ?K ?g ?G ?s ?S ?C ?t ?T)))) (message "")) (t (org-clock-resolve-clock @@ -1092,7 +1142,7 @@ to be CLOCKED OUT.")))) (t (error "Unexpected, please report this as a bug"))) (and gotback last-valid) - (memq ch '(?K ?G ?S)) + (memq ch '(?K ?G ?S ?T)) (and start-over (not (memq ch '(?K ?G ?S ?C)))) fail-quietly))))) @@ -1315,7 +1365,6 @@ the default behavior." (t (insert-before-markers "\n") (backward-char 1) - (org-indent-line) (when (and (save-excursion (end-of-line 0) (org-in-item-p))) @@ -1340,7 +1389,8 @@ the default behavior." start-time (org-current-time org-clock-rounding-minutes t))) (setq ts (org-insert-time-stamp org-clock-start-time - 'with-hm 'inactive)))) + 'with-hm 'inactive)) + (org-indent-line))) (move-marker org-clock-marker (point) (buffer-base-buffer)) (move-marker org-clock-hd-marker (save-excursion (org-back-to-heading t) (point)) @@ -1375,6 +1425,26 @@ the default behavior." (message "Clock starts at %s - %s" ts org--msg-extra) (run-hooks 'org-clock-in-hook)))))) +(defun org-clock-auto-clockout () + "Clock out the currently clocked in task if Emacs is idle. +See `org-clock-auto-clockout-timer' to set the idle time span. + +This is only effective when `org-clock-auto-clockout-insinuate' +is present in the user configuration." + (when (and (numberp org-clock-auto-clockout-timer) + org-clock-current-task) + (run-with-idle-timer + org-clock-auto-clockout-timer nil #'org-clock-out))) + +;;;###autoload +(defun org-clock-toggle-auto-clockout () + (interactive) + (if (memq 'org-clock-auto-clockout org-clock-in-hook) + (progn (remove-hook 'org-clock-in-hook #'org-clock-auto-clockout) + (message "Auto clock-out after idle time turned off")) + (add-hook 'org-clock-in-hook #'org-clock-auto-clockout t) + (message "Auto clock-out after idle time turned on"))) + ;;;###autoload (defun org-clock-in-last (&optional arg) "Clock in the last closed clocked item. @@ -1512,7 +1582,7 @@ line and position cursor in that line." (insert ":" drawer ":\n:END:\n") (org-indent-region beg (point)) (org-flag-region - (line-end-position -1) (1- (point)) t 'org-hide-drawer) + (line-end-position -1) (1- (point)) t 'outline) (forward-line -1)))) ;; When a clock drawer needs to be created because of the ;; number of clock items or simply if it is missing, collect @@ -1537,7 +1607,7 @@ line and position cursor in that line." (let ((end (point-marker))) (goto-char beg) (save-excursion (insert ":" drawer ":\n")) - (org-flag-region (line-end-position) (1- end) t 'org-hide-drawer) + (org-flag-region (line-end-position) (1- end) t 'outline) (org-indent-region (point) end) (forward-line) (unless org-log-states-order-reversed @@ -1579,7 +1649,7 @@ to, overriding the existing value of `org-clock-out-switch-to-state'." org-clock-out-switch-to-state)) (now (org-current-time org-clock-rounding-minutes)) ts te s h m remove) - (setq org-clock-out-time now) + (setq org-clock-out-time (or at-time now)) (save-excursion ; Do not replace this with `with-current-buffer'. (with-no-warnings (set-buffer (org-clocking-buffer))) (save-restriction @@ -1724,7 +1794,7 @@ Optional argument N tells to change by that many units." (delq 'org-mode-line-string global-mode-string)) (org-clock-restore-frame-title-format) (force-mode-line-update) - (error "No active clock")) + (user-error "No active clock")) (save-excursion ; Do not replace this with `with-current-buffer'. (with-no-warnings (set-buffer (org-clocking-buffer))) (goto-char org-clock-marker) @@ -1753,14 +1823,14 @@ With prefix arg SELECT, offer recently clocked tasks for selection." (m (cond (select (or (org-clock-select-task "Select task to go to: ") - (error "No task selected"))) + (user-error "No task selected"))) ((org-clocking-p) org-clock-marker) ((and org-clock-goto-may-find-recent-task (car org-clock-history) (marker-buffer (car org-clock-history))) (setq recent t) (car org-clock-history)) - (t (error "No active or recent clock task"))))) + (t (user-error "No active or recent clock task"))))) (pop-to-buffer-same-window (marker-buffer m)) (if (or (< m (point-min)) (> m (point-max))) (widen)) (goto-char m) @@ -1890,7 +1960,12 @@ PROPNAME lets you set a custom text property instead of :org-clock-minutes." "Return time, clocked on current item in total." (save-excursion (save-restriction - (org-narrow-to-subtree) + (if (and (featurep 'org-inlinetask) + (or (org-inlinetask-at-task-p) + (org-inlinetask-in-task-p))) + (narrow-to-region (save-excursion (org-inlinetask-goto-beginning) (point)) + (save-excursion (org-inlinetask-goto-end) (point))) + (org-narrow-to-subtree)) (org-clock-sum tstart) org-clock-file-total-minutes))) @@ -2067,7 +2142,10 @@ in the buffer and update it." (start (goto-char start))) (org-update-dblock)) -(org-dynamic-block-define "clocktable" #'org-clock-report) +;;;###autoload +(eval-after-load 'org + '(progn + (org-dynamic-block-define "clocktable" #'org-clock-report))) (defun org-day-of-week (day month year) "Return the day of the week as an integer." @@ -2310,7 +2388,7 @@ the currently selected interval size." (save-excursion (goto-char (point-at-bol)) (if (not (looking-at "^[ \t]*#\\+BEGIN:[ \t]+clocktable\\>.*?:block[ \t]+\\(\\S-+\\)")) - (error "Line needs a :block definition before this command works") + (user-error "Line needs a :block definition before this command works") (let* ((b (match-beginning 1)) (e (match-end 1)) (s (match-string 1)) block shift ins y mw d date wp m) @@ -2369,7 +2447,7 @@ the currently selected interval size." (encode-time 0 0 0 1 (+ mw n) y)))) (y (setq ins (number-to-string (+ y n)))))) - (t (error "Cannot shift clocktable block"))) + (t (user-error "Cannot shift clocktable block"))) (when ins (goto-char b) (insert ins) @@ -2384,20 +2462,21 @@ the currently selected interval size." (setq params (org-combine-plists org-clocktable-defaults params)) (catch 'exit (let* ((scope (plist-get params :scope)) + (base-buffer (org-base-buffer (current-buffer))) (files (pcase scope (`agenda (org-agenda-files t)) (`agenda-with-archives (org-add-archive-files (org-agenda-files t))) (`file-with-archives - (and buffer-file-name - (org-add-archive-files (list buffer-file-name)))) + (let ((base-file (buffer-file-name base-buffer))) + (and base-file + (org-add-archive-files (list base-file))))) ((or `nil `file `subtree `tree (and (pred symbolp) (guard (string-match "\\`tree\\([0-9]+\\)\\'" (symbol-name scope))))) - (or (buffer-file-name (buffer-base-buffer)) - (current-buffer))) + base-buffer) ((pred functionp) (funcall scope)) ((pred consp) scope) (_ (user-error "Unknown scope: %S" scope)))) @@ -2421,7 +2500,7 @@ the currently selected interval size." (when step ;; Write many tables, in steps (unless (or block (and ts te)) - (error "Clocktable `:step' can only be used with `:block' or `:tstart,:end'")) + (user-error "Clocktable `:step' can only be used with `:block' or `:tstart, :end'")) (org-clocktable-steps params) (throw 'exit nil)) @@ -2527,7 +2606,7 @@ from the dynamic block definition." (guard (string-match-p "\\`[0-9]+!\\'" (symbol-name narrow)))) (setq narrow-cut-p t) (setq narrow (string-to-number (symbol-name narrow)))) - (_ (error "Invalid value %s of :narrow property in clock table" narrow))) + (_ (user-error "Invalid value %s of :narrow property in clock table" narrow))) ;; Now we need to output this table stuff. (goto-char ipos) @@ -2718,6 +2797,7 @@ a number of clock tables." (pcase step (`day "Daily report: ") (`week "Weekly report starting on: ") + (`semimonth "Semimonthly report starting on: ") (`month "Monthly report starting on: ") (`year "Annual report starting on: ") (_ (user-error "Unknown `:step' specification: %S" step)))) @@ -2767,6 +2847,9 @@ a number of clock tables." (let ((offset (if (= dow week-start) 7 (mod (- week-start dow) 7)))) (list 0 0 org-extend-today-until (+ d offset) m y))) + (`semimonth (list 0 0 0 + (if (< d 16) 16 1) + (if (< d 16) m (1+ m)) y)) (`month (list 0 0 0 month-start (1+ m) y)) (`year (list 0 0 org-extend-today-until 1 1 (1+ y))))))) (table-begin (line-beginning-position 0)) @@ -2883,7 +2966,7 @@ PROPERTIES: The list properties specified in the `:properties' parameter (org-trim (org-link-display-format (replace-regexp-in-string - "\\[[0-9]+%\\]\\|\\[[0-9]+/[0-9]+\\]" "" + "\\[[0-9]*\\(?:%\\|/[0-9]*\\)\\]" "" headline))))))) (tgs (and tags (org-get-tags))) (tsp |