-
Notifications
You must be signed in to change notification settings - Fork 0
/
org-clock.el
3336 lines (3101 loc) · 125 KB
/
org-clock.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; org-clock.el --- The time clocking code for Org mode -*- lexical-binding: t; -*-
;; Copyright (C) 2004-2025 Free Software Foundation, Inc.
;; Author: Carsten Dominik <[email protected]>
;; Keywords: outlines, hypermedia, calendar, text
;; URL: https://orgmode.org
;;
;; This file is part of GNU Emacs.
;;
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Commentary:
;; This file contains the time clocking code for Org mode
;;; Code:
(require 'org-macs)
(org-assert-version)
(require 'cl-lib)
(require 'org)
(declare-function calendar-iso-to-absolute "cal-iso" (date))
(declare-function notifications-notify "notifications" (&rest params))
(declare-function org-element-property "org-element-ast" (property node))
(declare-function org-element-contents-end "org-element" (node))
(declare-function org-element-end "org-element" (node))
(declare-function org-element-type "org-element-ast" (node &optional anonymous))
(declare-function org-element-type-p "org-element-ast" (node types))
(defvar org-element-use-cache)
(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))
(declare-function dbus-list-activatable-names "dbus" (&optional bus))
(declare-function dbus-call-method "dbus" (bus service path interface method &rest args))
(declare-function dbus-get-property "dbus" (bus service path interface property))
(declare-function haiku-notifications-notify "haikuselect.c")
(declare-function android-notifications-notify "androidselect.c")
(defvar org-frame-title-format-backup nil)
(defvar org-state)
(defvar org-link-bracket-re)
(defgroup org-clock nil
"Options concerning clocking working time in Org mode."
:tag "Org Clock"
:group 'org-progress)
(defcustom org-clock-into-drawer t
"Non-nil when clocking info should be wrapped into a drawer.
When non-nil, clocking info will be inserted into the same drawer
as log notes (see variable `org-log-into-drawer'), if it exists,
or \"LOGBOOK\" otherwise. If necessary, the drawer will be
created.
When an integer, the drawer is created only when the number of
clocking entries in an item reaches or exceeds this value.
When a string, it becomes the name of the drawer, ignoring the
log notes drawer altogether.
Do not check directly this variable in a Lisp program. Call
function `org-clock-into-drawer' instead."
:group 'org-todo
:group 'org-clock
:version "26.1"
:package-version '(Org . "8.3")
:type '(choice
(const :tag "Always" t)
(const :tag "Only when drawer exists" nil)
(integer :tag "When at least N clock entries")
(const :tag "Into LOGBOOK drawer" "LOGBOOK")
(string :tag "Into Drawer named...")))
(defun org-clock-into-drawer ()
"Value of `org-clock-into-drawer', but let properties overrule.
If the current entry has or inherits a CLOCK_INTO_DRAWER
property, it will be used instead of the default value.
Return value is either a string, an integer, or nil."
(let ((p (org-entry-get nil "CLOCK_INTO_DRAWER" 'inherit t)))
(cond ((equal p "nil") nil)
((equal p "t") (or (org-log-into-drawer) "LOGBOOK"))
((org-string-nw-p p)
(if (string-match-p "\\`[0-9]+\\'" p) (string-to-number p) p))
((org-string-nw-p org-clock-into-drawer))
((integerp org-clock-into-drawer) org-clock-into-drawer)
((not org-clock-into-drawer) nil)
((org-log-into-drawer))
(t "LOGBOOK"))))
(defcustom org-clock-out-when-done t
"When non-nil, clock will be stopped when the clocked entry is marked DONE.
\\<org-mode-map>\
DONE here means any DONE-like state.
A nil value means clock will keep running until stopped explicitly with
`\\[org-clock-out]', or until the clock is started in a different item.
Instead of t, this can also be a list of TODO states that should trigger
clocking out."
:group 'org-clock
:type '(choice
(const :tag "No" nil)
(const :tag "Yes, when done" t)
(repeat :tag "State list"
(string :tag "TODO keyword"))))
(defcustom org-clock-rounding-minutes 0
"Rounding minutes when clocking in or out.
The default value is 0 so that no rounding is done.
When set to a non-integer value, use the car of
`org-timestamp-rounding-minutes', like for setting a timestamp.
E.g. if `org-clock-rounding-minutes' is set to 5, time is 14:47
and you clock in: then the clock starts at 14:45. If you clock
out within the next 5 minutes, the clock line will be removed;
if you clock out 8 minutes after your clocked in, the clock
out time will be 14:50."
:group 'org-clock
:version "24.4"
:package-version '(Org . "8.0")
:type '(choice
(integer :tag "Minutes (0 for no rounding)")
(symbol :tag "Use `org-time-stamp-rounding-minutes'" 'same-as-time-stamp)))
(defcustom org-clock-out-remove-zero-time-clocks nil
"Non-nil means remove the clock line when the resulting time is zero."
:group 'org-clock
:type 'boolean)
(defcustom org-clock-in-switch-to-state nil
"Set task to a special todo state while clocking it.
The value should be the state to which the entry should be
switched. If the value is a function, it must take one
parameter (the current TODO state of the item) and return the
state to switch it to."
:group 'org-clock
:group 'org-todo
:type '(choice
(const :tag "Don't force a state" nil)
(string :tag "State")
(symbol :tag "Function")))
(defcustom org-clock-out-switch-to-state nil
"Set task to a special todo state after clocking out.
The value should be the state to which the entry should be
switched. If the value is a function, it must take one
parameter (the current TODO state of the item) and return the
state to switch it to."
:group 'org-clock
:group 'org-todo
:type '(choice
(const :tag "Don't force a state" nil)
(string :tag "State")
(symbol :tag "Function")))
(defcustom org-clock-history-length 5
"Number of clock tasks to remember in history.
Clocking in using history works best if this is at most 35, in
which case all digits and capital letters are used up by the
*Clock Task Select* buffer."
:group 'org-clock
:type 'integer)
(defcustom org-clock-goto-may-find-recent-task t
"Non-nil means `org-clock-goto' can go to recent task if no active clock."
:group 'org-clock
:type 'boolean)
(defcustom org-clock-heading-function nil
"When non-nil, should be a function to create `org-clock-heading'.
This is the string shown in the mode line when a clock is running.
The function is called with point at the beginning of the headline."
:group 'org-clock
:type '(choice (const nil) (function)))
(defcustom org-clock-string-limit 0
"Maximum length of clock strings in the mode line. 0 means no limit."
:group 'org-clock
:type 'integer)
(defcustom org-clock-in-resume nil
"If non-nil, resume clock when clocking into task with open clock.
When clocking into a task with a clock entry which has not been closed,
the clock can be resumed from that point."
:group 'org-clock
:type 'boolean)
(defcustom org-clock-persist nil
"When non-nil, save the running clock when Emacs is closed.
The clock is resumed when Emacs restarts.
When this is t, both the running clock, and the entire clock
history are saved. When this is the symbol `clock', only the
running clock is saved. When this is the symbol `history', only
the clock history is saved.
When Emacs restarts with saved clock information, the file containing
the running clock as well as all files mentioned in the clock history
will be visited.
All this depends on running `org-clock-persistence-insinuate' in your
Emacs initialization file."
:group 'org-clock
:type '(choice
(const :tag "Just the running clock" clock)
(const :tag "Just the history" history)
(const :tag "Clock and history" t)
(const :tag "No persistence" nil)))
(defcustom org-clock-persist-file (locate-user-emacs-file "org-clock-save.el")
"File to save clock data to."
:group 'org-clock
:type 'string)
(defcustom org-clock-persist-query-save nil
"When non-nil, ask before saving the current clock on exit."
:group 'org-clock
:type 'boolean)
(defcustom org-clock-persist-query-resume t
"When non-nil, ask before resuming any stored clock during load."
:group 'org-clock
:type 'boolean)
(defcustom org-clock-sound nil
"Sound to use for notifications.
Possible values are:
nil No sound played
t Standard Emacs beep
file name Play this sound file, fall back to beep"
:group 'org-clock
:type '(choice
(const :tag "No sound" nil)
(const :tag "Standard beep" t)
(file :tag "Play sound file")))
(defcustom org-clock-mode-line-total 'auto
"Default setting for the time included for the mode line clock.
This can be overruled locally using the CLOCK_MODELINE_TOTAL property.
Allowed values are:
current Only the time in the current instance of the clock
today All time clocked into this task today
repeat All time clocked into this task since last repeat
all All time ever recorded for this task
auto Automatically, either `all', or `repeat' for repeating tasks"
:group 'org-clock
:type '(choice
(const :tag "Current clock" current)
(const :tag "Today's task time" today)
(const :tag "Since last repeat" repeat)
(const :tag "All task time" all)
(const :tag "Automatically, `all' or since `repeat'" auto)))
(defvaralias 'org-task-overrun-text 'org-clock-task-overrun-text)
(defcustom org-clock-task-overrun-text nil
"Extra mode line text to indicate that the clock is overrun.
The can be nil to indicate that instead of adding text, the clock time
should get a different face (`org-mode-line-clock-overrun').
When this is a string, it is prepended to the clock string as an indication,
also using the face `org-mode-line-clock-overrun'."
:group 'org-clock
:version "24.1"
:type '(choice
(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
string as argument."
:group 'org-clock
:type '(choice
(const nil)
(string :tag "Program")
(function :tag "Function")))
(defgroup org-clocktable nil
"Options concerning the clock table in Org mode."
:tag "Org Clock Table"
:group 'org-clock)
(defcustom org-clocktable-defaults
(list
:maxlevel 2
:lang (or (bound-and-true-p org-export-default-language) "en")
:scope 'file
:block nil
:wstart 1
:mstart 1
:tstart nil
:tend nil
:step nil
:stepskip0 nil
:fileskip0 nil
:tags nil
:match nil
:emphasize nil
:link nil
:narrow '40!
:indent t
:filetitle nil
:hidefiles nil
:formula nil
:timestamp nil
:level nil
:tcolumns nil
:formatter nil)
"Default properties for clock tables."
:group 'org-clock
:package-version '(Org . "9.6")
:type 'plist)
(defcustom org-clock-clocktable-formatter 'org-clocktable-write-default
"Function to turn clocking data into a table.
For more information, see `org-clocktable-write-default'."
:group 'org-clocktable
:version "24.1"
:type 'function)
(defcustom org-clock-clocktable-language-setup
'(("en" "File" "L" "Timestamp" "Headline" "Time" "ALL" "Total time" "File time" "Clock summary at")
("de" "Datei" "E" "Zeitstempel" "Kopfzeile" "Dauer" "GESAMT" "Gesamtdauer" "Dateizeit" "Erstellt am")
("es" "Archivo" "N" "Fecha y hora" "Tarea" "Duración" "TODO" "Duración total" "Tiempo archivo" "Generado el")
("fr" "Fichier" "N" "Horodatage" "En-tête" "Durée" "TOUT" "Durée totale" "Durée fichier" "Horodatage sommaire à")
("nl" "Bestand" "N" "Tijdstip" "Rubriek" "Duur" "ALLES" "Totale duur" "Bestandstijd" "Klok overzicht op")
("nn" "Fil" "N" "Tidspunkt" "Overskrift" "Tid" "ALLE" "Total tid" "Filtid" "Tidsoversyn")
("pl" "Plik" "P" "Data i godzina" "Nagłówek" "Czas" "WSZYSTKO" "Czas całkowity" "Czas pliku" "Poddumowanie zegara na")
("pt-BR" "Arquivo" "N" "Data e hora" "Título" "Hora" "TODOS" "Hora total" "Hora do arquivo" "Resumo das horas em")
("sk" "Súbor" "L" "Časová značka" "Záhlavie" "Čas" "VŠETKO" "Celkový čas" "Čas súboru" "Časový súhrn pre"))
"Terms used in clocktable, translated to different languages."
:group 'org-clocktable
:version "24.1"
:type 'alist)
(defcustom org-clock-clocktable-default-properties '(:maxlevel 2)
"Default properties for new clocktables.
These will be inserted into the BEGIN line, to make it easy for users to
play with them."
:group 'org-clocktable
:package-version '(Org . "9.2")
:type 'plist)
(defcustom org-clock-idle-time nil
"When non-nil, resolve open clocks if the user is idle more than X minutes."
:group 'org-clock
:type '(choice
(const :tag "Never" nil)
(integer :tag "After N minutes")))
(defcustom org-clock-auto-clock-resolution 'when-no-clock-is-running
"When to automatically resolve open clocks found in Org buffers."
:group 'org-clock
:type '(choice
(const :tag "Never" nil)
(const :tag "Always" t)
(const :tag "When no clock is running" when-no-clock-is-running)))
(defcustom org-clock-report-include-clocking-task nil
"When non-nil, include the current clocking task time in clock reports."
:group 'org-clock
:version "24.1"
:type 'boolean)
(defcustom org-clock-resolve-expert nil
"Non-nil means do not show the splash buffer with the clock resolver."
:group 'org-clock
:version "24.1"
:type 'boolean)
(defcustom org-clock-continuously nil
"Non-nil means to start clocking from the last clock-out time, if any."
:type 'boolean
:version "24.1"
:group 'org-clock)
(defcustom org-clock-total-time-cell-format "*%s*"
"Format string for the total time cells."
:group 'org-clock
:version "24.1"
:type 'string)
(defcustom org-clock-file-time-cell-format "*%s*"
"Format string for the file time cells."
:group 'org-clock
:version "24.1"
:type 'string)
(defcustom org-clock-clocked-in-display 'mode-line
"Where to display clocked in task and accumulated time when clocked in.
Allowed values are:
both displays in both mode line and frame title
mode-line displays only in mode line (default)
frame-title displays only in frame title
nil current clock is not displayed"
:group 'org-clock
:type '(choice
(const :tag "Mode line" mode-line)
(const :tag "Frame title" frame-title)
(const :tag "Both" both)
(const :tag "None" nil)))
(defcustom org-clock-frame-title-format '(t org-mode-line-string)
"The value for `frame-title-format' when clocking in.
When `org-clock-clocked-in-display' is set to `frame-title'
or `both', clocking in will replace `frame-title-format' with
this value. Clocking out will restore `frame-title-format'.
This uses the same format as `frame-title-format', which see."
:version "24.1"
:group 'org-clock
:type 'sexp)
(defcustom org-clock-x11idle-program-name
(if (executable-find "xprintidle")
"xprintidle" "x11idle")
"Name of the program which prints X11 idle time in milliseconds.
you can do \"~$ sudo apt-get install xprintidle\" if you are using
a Debian-based distribution.
Alternatively, can find x11idle.c in
https://orgmode.org/worg/code/scripts/x11idle.c"
:group 'org-clock
:package-version '(Org . "9.7")
:type 'string)
(defcustom org-clock-goto-before-context 2
"Number of lines of context to display before currently clocked-in entry.
This applies when using `org-clock-goto'."
:group 'org-clock
:type 'integer)
(defcustom org-clock-display-default-range 'thisyear
"Default range when displaying clocks with `org-clock-display'.
Valid values are: `today', `yesterday', `thisweek', `lastweek',
`thismonth', `lastmonth', `thisyear', `lastyear' and `untilnow'."
:group 'org-clock
:type '(choice (const today)
(const yesterday)
(const thisweek)
(const lastweek)
(const thismonth)
(const lastmonth)
(const thisyear)
(const lastyear)
(const untilnow)
(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)))
(defcustom org-clock-ask-before-exiting t
"If non-nil, ask if the user wants to clock out before exiting Emacs.
This variable only has effect if set with \\[customize]."
:set (lambda (symbol value)
(if value
(add-hook 'kill-emacs-query-functions #'org-clock-kill-emacs-query)
(remove-hook 'kill-emacs-query-functions #'org-clock-kill-emacs-query))
(set-default-toplevel-value symbol value))
:type 'boolean
:package-version '(Org . "9.5"))
(defvar org-clock-in-prepare-hook nil
"Hook run when preparing the clock.
This hook is run before anything happens to the task that
you want to clock in. For example, you can use this hook
to add an effort property.")
(defvar org-clock-in-hook nil
"Hook run when starting the clock.")
(defvar org-clock-out-hook nil
"Hook run when stopping the current clock.
The point is at the current clock line when the hook is executed.
The hook functions can access `org-clock-out-removed-last-clock' to
check whether the latest CLOCK line has been cleared.")
(defvar org-clock-cancel-hook nil
"Hook run when canceling the current clock.")
(defvar org-clock-goto-hook nil
"Hook run when selecting the currently clocked-in entry.")
(defvar org-clock-has-been-used nil
"Has the clock been used during the current Emacs session?")
(defvar org-clock-stored-history nil
"Clock history, populated by `org-clock-load'.")
(defvar org-clock-stored-resume-clock nil
"Clock to resume, saved by `org-clock-load'.")
;;; The clock for measuring work time.
(defvar org-mode-line-string "")
(put 'org-mode-line-string 'risky-local-variable t)
(defvar org-clock-mode-line-timer nil)
(defvar org-clock-idle-timer nil)
(defvar org-clock-heading) ; defined in org.el
(defvar org-clock-start-time "")
(defvar org-clock-leftover-time nil
"If non-nil, user canceled a clock; this is when leftover time started.")
(defvar org-clock-effort ""
"Effort estimate of the currently clocking task.")
(defvar org-clock-total-time nil
"Holds total time, spent previously on currently clocked item.
This does not include the time in the currently running clock.")
(defvar org-clock-history nil
"List of marker pointing to recent clocked tasks.")
(defvar org-clock-default-task (make-marker)
"Marker pointing to the default task that should clock time.
The clock can be made to switch to this task after clocking out
of a different task.")
(defvar org-clock-interrupted-task (make-marker)
"Marker pointing to the task that has been interrupted by the current clock.")
(defvar org-clock-mode-line-map (make-sparse-keymap))
(define-key org-clock-mode-line-map [mode-line mouse-2] #'org-clock-goto)
(define-key org-clock-mode-line-map [mode-line mouse-1] #'org-clock-menu)
(defun org-clock--translate (s language)
"Translate string S into using string LANGUAGE.
Assume S in the English term to translate. Return S as-is if it
cannot be translated."
(or (nth (pcase s
;; "L" stands for "Level"
;; "ALL" stands for a line summarizing clock data across
;; all the files, when the clocktable includes multiple
;; files.
("File" 1) ("L" 2) ("Timestamp" 3) ("Headline" 4) ("Time" 5)
("ALL" 6) ("Total time" 7) ("File time" 8) ("Clock summary at" 9))
(assoc-string language org-clock-clocktable-language-setup t))
s))
(defun org-clock--mode-line-heading ()
"Return currently clocked heading, formatted for mode line."
(cond ((functionp org-clock-heading-function)
(funcall org-clock-heading-function))
((org-before-first-heading-p) "???")
(t (org-link-display-format
(org-no-properties (org-get-heading t t t t))))))
(defun org-clock-menu ()
"Pop up org-clock menu."
(interactive)
(popup-menu
'("Clock"
["Clock out" org-clock-out t]
["Change effort estimate" org-clock-modify-effort-estimate t]
["Go to clock entry" org-clock-goto t]
["Switch task" (lambda () (interactive) (org-clock-in '(4))) :active t :keys "C-u C-c C-x C-i"])))
(defun org-clock-history-push (&optional pos buffer)
"Push point marker to the clock history.
When POS is provided, use it as marker point.
When BUFFER and POS are provided, use marker at POS in base buffer of
BUFFER."
;; When buffer is provided, POS must be provided.
(cl-assert (or (not buffer) pos))
(setq org-clock-history-length (max 1 org-clock-history-length))
(let ((m (move-marker (make-marker)
(or pos (point)) (org-base-buffer
(or buffer (current-buffer)))))
n l)
(while (setq n (member m org-clock-history))
(move-marker (car n) nil))
(setq org-clock-history
(delq nil
(mapcar (lambda (x) (if (marker-buffer x) x nil))
org-clock-history)))
(when (>= (setq l (length org-clock-history)) org-clock-history-length)
(setq org-clock-history
(nreverse
(nthcdr (- l org-clock-history-length -1)
(nreverse org-clock-history)))))
(push m org-clock-history)))
(defun org-clock-save-markers-for-cut-and-paste (beg end)
"Save relative positions of markers in region BEG..END.
Save `org-clock-marker', `org-clock-hd-marker',
`org-clock-default-task', `org-clock-interrupted-task', and the
markers in `org-clock-history'."
(org-check-and-save-marker org-clock-marker beg end)
(org-check-and-save-marker org-clock-hd-marker beg end)
(org-check-and-save-marker org-clock-default-task beg end)
(org-check-and-save-marker org-clock-interrupted-task beg end)
(dolist (m org-clock-history)
(org-check-and-save-marker m beg end)))
(defun org-clock-drawer-name ()
"Return clock drawer's name for current entry, or nil."
(let ((drawer (org-clock-into-drawer)))
(cond ((integerp drawer)
(let ((log-drawer (org-log-into-drawer)))
(if (stringp log-drawer) log-drawer "LOGBOOK")))
((stringp drawer) drawer)
(t nil))))
(defun org-clocking-p ()
"Return t when clocking a task."
(not (equal (org-clocking-buffer) nil)))
(defvar org-clock-before-select-task-hook nil
"Hook called in task selection just before prompting the user.")
(defun org-clock-select-task (&optional prompt)
"Select a task that was recently associated with clocking.
PROMPT is the prompt text to be used, as a string.
Return marker position of the selected task. Raise an error if
there is no recent clock to choose from."
(let (och chl sel-list rpl (i 0) s)
;; Remove successive dups from the clock history to consider
(dolist (c org-clock-history)
(unless (equal c (car och)) (push c och)))
(setq och (reverse och) chl (length och))
(if (zerop chl)
(user-error "No recent clock")
(save-window-excursion
(switch-to-buffer-other-window
(get-buffer-create "*Clock Task Select*"))
(erase-buffer)
(when (marker-buffer org-clock-default-task)
(insert (org-add-props "Default Task\n" nil 'face 'bold))
(setq s (org-clock-insert-selection-line ?d org-clock-default-task))
(push s sel-list))
(when (marker-buffer org-clock-interrupted-task)
(insert (org-add-props "The task interrupted by starting the last one\n" nil 'face 'bold))
(setq s (org-clock-insert-selection-line ?i org-clock-interrupted-task))
(push s sel-list))
(when (org-clocking-p)
(insert (org-add-props "Current Clocking Task\n" nil 'face 'bold))
(setq s (org-clock-insert-selection-line ?c org-clock-marker))
(push s sel-list))
(insert (org-add-props "Recent Tasks\n" nil 'face 'bold))
(dolist (m och)
(when (marker-buffer m)
(setq i (1+ i)
s (org-clock-insert-selection-line
(if (< i 10)
(+ i ?0)
(+ i (- ?A 10))) m))
(push s sel-list)))
(run-hooks 'org-clock-before-select-task-hook)
(goto-char (point-min))
;; Set min-height relatively to circumvent a possible but in
;; `fit-window-to-buffer'
(fit-window-to-buffer nil nil (if (< chl 10) chl (+ 5 chl)))
(message (or prompt "Select task for clocking:"))
(unwind-protect (setq cursor-type nil rpl (read-char-exclusive))
(when-let* ((window (get-buffer-window "*Clock Task Select*" t)))
(quit-window 'kill window))
(when (get-buffer "*Clock Task Select*")
(kill-buffer "*Clock Task Select*")))
(cond
((eq rpl ?q) nil)
((eq rpl ?x) nil)
((assoc rpl sel-list) (cdr (assoc rpl sel-list)))
(t (user-error "Invalid task choice %c" rpl)))))))
(defun org-clock-insert-selection-line (i marker)
"Insert a line for the clock selection menu.
And return a cons cell with the selection character integer and the marker
pointing to it."
(when (marker-buffer marker)
(let (cat task heading prefix)
(with-current-buffer (org-base-buffer (marker-buffer marker))
(org-with-wide-buffer
(ignore-errors
(goto-char marker)
(setq cat (org-get-category)
heading (org-get-heading 'notags)
prefix (save-excursion
(org-back-to-heading t)
(looking-at org-outline-regexp)
(match-string 0))
task (substring
(org-fontify-like-in-org-mode
(concat prefix heading)
org-odd-levels-only)
(length prefix))))))
(when (and cat task)
(if (string-match-p "[[:print:]]" (make-string 1 i))
(insert (format "[%c] %-12s %s\n" i cat task))
;; Avoid non-printable characters.
(insert (format "[N/A] %-12s %s\n" cat task)))
(cons i marker)))))
(defvar org-clock-task-overrun nil
"Internal flag indicating if the clock has overrun the planned time.")
(defvar org-clock-update-period 60
"Number of seconds between mode line clock string updates.")
(defun org-clock-get-clock-string ()
"Form a clock-string, that will be shown in the mode line.
If an effort estimate was defined for the current item, use
01:30/01:50 format (clocked/estimated).
If not, show simply the clocked time like 01:50."
(let ((clocked-time (org-clock-get-clocked-time)))
(if org-clock-effort
(let* ((effort-in-minutes (org-duration-to-minutes org-clock-effort))
(work-done-str
(propertize (org-duration-from-minutes clocked-time)
'face
(if (and org-clock-task-overrun
(not org-clock-task-overrun-text))
'org-mode-line-clock-overrun
'org-mode-line-clock)))
(effort-str (org-duration-from-minutes effort-in-minutes)))
(format (propertize "[%s/%s] (%s) " 'face 'org-mode-line-clock)
work-done-str effort-str org-clock-heading))
(format (propertize "[%s] (%s) " 'face 'org-mode-line-clock)
(org-duration-from-minutes clocked-time)
org-clock-heading))))
(defun org-clock-get-last-clock-out-time ()
"Get the last clock-out time for the current subtree."
(save-excursion
(let ((end (save-excursion (org-end-of-subtree))))
(when (re-search-forward (concat org-clock-string
".*\\]--\\(\\[[^]]+\\]\\)")
end t)
(org-time-string-to-time (match-string 1))))))
(defun org-clock-update-mode-line (&optional refresh)
"Update mode line with clock information.
When optional argument is non-nil, refresh cached heading."
(if org-clock-effort
(org-clock-notify-once-if-expired)
(setq org-clock-task-overrun nil))
(when refresh (setq org-clock-heading (org-clock--mode-line-heading)))
(setq org-mode-line-string
(propertize
(let ((clock-string (org-clock-get-clock-string))
(help-text "Org mode clock is running.\nmouse-1 shows a \
menu\nmouse-2 will jump to task"))
(if (and (> org-clock-string-limit 0)
(> (length clock-string) org-clock-string-limit))
(propertize
(substring clock-string 0 org-clock-string-limit)
'help-echo (concat help-text ": " org-clock-heading))
(propertize clock-string 'help-echo help-text)))
'local-map org-clock-mode-line-map
'mouse-face 'mode-line-highlight))
(if (and org-clock-task-overrun org-clock-task-overrun-text)
(setq org-mode-line-string
(concat (propertize
org-clock-task-overrun-text
'face 'org-mode-line-clock-overrun)
org-mode-line-string)))
(force-mode-line-update))
(defun org-clock-get-clocked-time ()
"Get the clocked time for the current item in minutes.
The time returned includes the time spent on this task in
previous clocking intervals."
(let ((currently-clocked-time
(floor (org-time-convert-to-integer
(time-since org-clock-start-time))
60)))
(+ currently-clocked-time (or org-clock-total-time 0))))
;;;###autoload
(defun org-clock-modify-effort-estimate (&optional value)
"Add to or set the effort estimate of the item currently being clocked.
VALUE can be a number of minutes, or a string with format hh:mm or mm.
When the string starts with a + or a - sign, the current value of the effort
property will be changed by that amount. If the effort value is expressed
as an unit defined in `org-duration-units' (e.g. \"3h\"), the modified
value will be converted to a hh:mm duration.
This command will update the \"Effort\" property of the currently
clocked item, and the value displayed in the mode line."
(interactive)
(if (org-clock-is-active)
(let ((current org-clock-effort) sign)
(unless value
;; Prompt user for a value or a change
(setq value
(read-string
(format "Set effort (hh:mm or mm%s): "
(if current
(format ", prefix + to add to %s" org-clock-effort)
"")))))
(when (stringp value)
;; A string. See if it is a delta
(setq sign (string-to-char value))
(if (member sign '(?- ?+))
(setq current (org-duration-to-minutes current)
value (substring value 1))
(setq current 0))
(setq value (org-duration-to-minutes value))
(if (equal ?- sign)
(setq value (- current value))
(if (equal ?+ sign) (setq value (+ current value)))))
(setq value (max 0 value)
org-clock-effort (org-duration-from-minutes value))
(org-entry-put org-clock-marker "Effort" org-clock-effort)
(org-clock-update-mode-line)
(message "Effort is now %s" org-clock-effort))
(message "Clock is not currently active")))
(defvar org-clock-notification-was-shown nil
"Shows if we have shown notification already.")
(defun org-clock-notify-once-if-expired ()
"Show notification if we spent more time than we estimated before.
Notification is shown only once."
(when (org-clocking-p)
(let ((effort-in-minutes (org-duration-to-minutes org-clock-effort))
(clocked-time (org-clock-get-clocked-time)))
(if (setq org-clock-task-overrun
(if (or (null effort-in-minutes) (zerop effort-in-minutes))
nil
(>= clocked-time effort-in-minutes)))
(unless org-clock-notification-was-shown
(setq org-clock-notification-was-shown t)
(org-notify
(format-message "Task `%s' should be finished by now. (%s)"
org-clock-heading org-clock-effort)
org-clock-sound))
(setq org-clock-notification-was-shown nil)))))
(defun org-notify (notification &optional play-sound)
"Send a NOTIFICATION and maybe PLAY-SOUND.
If PLAY-SOUND is non-nil, it overrides `org-clock-sound'."
(org-show-notification notification)
(if play-sound (org-clock-play-sound play-sound)))
(defun org-show-notification (notification)
"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 'haiku-notifications-notify)
;; N.B. timeouts are not available under Haiku.
(haiku-notifications-notify :title "Org mode message"
:body notification
:urgency 'low))
((fboundp 'android-notifications-notify)
;; N.B. timeouts are not available under Haiku or Android.
(android-notifications-notify :title "Org mode message"
:body notification
;; Low urgency notifications
;; are by default hidden.
:urgency 'normal))
((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 'ns-do-applescript)
(ns-do-applescript
(format "display notification \"%s\" with title \"Org mode notification\""
(replace-regexp-in-string "\"" "#" notification))))
((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))
((executable-find "notify-send")
(start-process "emacs-timer-notification" nil
"notify-send" notification))
;; Maybe the handler will send a message, so only use message as
;; a fall back option
(t (message "%s" notification))))
(defun org-clock-play-sound (&optional clock-sound)
"Play sound as configured by `org-clock-sound'.
Use alsa's aplay tool if available.
If CLOCK-SOUND is non-nil, it overrides `org-clock-sound'."
(let ((org-clock-sound (or clock-sound org-clock-sound)))
(cond
((not org-clock-sound))
((eq org-clock-sound t) (beep t) (beep t))
((stringp org-clock-sound)
(let ((file (expand-file-name org-clock-sound)))
(if (file-exists-p file)
(if (executable-find "aplay")
(start-process "org-clock-play-notification" nil
"aplay" file)
(condition-case-unless-debug nil
(play-sound-file file)
(error (beep t) (beep t))))))))))
(defvar org-clock-mode-line-entry nil
"Information for the mode line about the running clock.")
(defun org-find-open-clocks (file)
"Search through the given file and find all open clocks."
(let ((buf (or (get-file-buffer file)
(find-file-noselect file)))
(org-clock-re (concat org-clock-string " \\(\\[.*?\\]\\)$"))
clocks)
(with-current-buffer buf
(save-excursion
(goto-char (point-min))
(while (re-search-forward org-clock-re nil t)
(when (save-match-data
(org-element-type-p (org-element-at-point) 'clock))
(push (cons (copy-marker (match-end 1) t)
(org-time-string-to-time (match-string 1)))
clocks)))))
clocks))
(defsubst org-is-active-clock (clock)
"Return t if CLOCK is the currently active clock."
(and (org-clock-is-active)
(= org-clock-marker (car clock))))
(defmacro org-with-clock-position (clock &rest forms)
"Evaluate FORMS with CLOCK as the current active clock."
(declare (indent 1) (debug t))
`(with-current-buffer (marker-buffer (car ,clock))
(org-with-wide-buffer
(goto-char (car ,clock))
(forward-line 0)
,@forms)))
(defmacro org-with-clock (clock &rest forms)
"Evaluate FORMS with CLOCK as the current active clock.
This macro also protects the current active clock from being altered."
(declare (indent 1) (debug t))
`(org-with-clock-position ,clock
(let ((org-clock-start-time (cdr ,clock))
(org-clock-total-time)
(org-clock-history)
(org-clock-effort)
(org-clock-marker (car ,clock))
(org-clock-hd-marker (save-excursion
(org-back-to-heading t)
(point-marker))))
,@forms)))
(defsubst org-clock-clock-in (clock &optional resume start-time)
"Clock in to the clock located by CLOCK.
If necessary, clock-out of the currently active clock."
(org-with-clock-position clock