Hello Ihor,

Thank you very much for your example.  It really helped me understand a few
things better.

I have realized that I was not particularly detail oriented when last
submitting this patch series.  This time I have been quite thorough.  I've made
many new tests.

All the tests pass after every single commit on emacs 30.2 (TZ=UTC).  I also
tested the full series on emacs 28 and 29.

changes from last patch series:
- Add and change tests for clocktables from inline tasks
  - I completely misunderstood what was going before.  Basically the inline
    tasks are on the same level of their parent heading, not under the
    headings.  I've fixed this by re-writing `org-clock-get-table-data'.
- New tests for clocktable with :timestamp set
- New tests for org-columns with CLOCKSUM and CLOCKSUM_T properties
- New test `test-org-clock/org-clock-sum` which was derived from your example
  from the previous email
- Test how org-clock-sum interacts with source blocks
- Rewrite `org-clock-get-table-data' using org-element
- Put inline tasks in the right place in clocktables


changes to `org-clock-sum' implementation:
- Add 'inlinetask to the org-element-lineage-map call.  I didn't realize the
  WITH-SELF argument only works if the type of self is in TYPES.  This fixes
  the 1st problem from your previous email.
- Add the buffer position to open clocks in `org--clock-ranges' and then check
  them against `org-clock-marker' in `org-clock-sum'.  Previously I was only
  checking `org-clock-hd-marker' which didn't do much when multiple open clocks
  where under the `org-clock-hd-marker' heading which was the 2nd problem from
  your previous email.
- Don't widen, just check point-min.  This is how the function previously
  worked.  Widening would probably break some other things (org source
  blocks?).

>From 440a8c1dead83ff96951bd3a1977d8454f6d359b Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Wed, 17 Apr 2024 17:51:35 -0400
Subject: [PATCH 01/12] Testing: Test clock times without timestamps

* testing/lisp/test-org-clock.el (test-org-clock/clocktable/insert):
Add a clock time that does not include timestamps.
---
 testing/lisp/test-org-clock.el | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/testing/lisp/test-org-clock.el b/testing/lisp/test-org-clock.el
index 8a196ee96..62e4d7507 100644
--- a/testing/lisp/test-org-clock.el
+++ b/testing/lisp/test-org-clock.el
@@ -345,13 +345,12 @@ test-org-clock/clocktable/insert
    (equal
     "| Headline     | Time   |
 |--------------+--------|
-| *Total time* | *1:00* |
+| *Total time* | *2:00* |
 |--------------+--------|
-| H1           | 1:00   |"
+| H1           | 2:00   |"
     (org-test-with-temp-text "* H1\n<point>"
-      (insert (org-test-clock-create-clock ". 1:00" ". 2:00"))
-
-      (goto-line 2)
+      (insert (org-test-clock-create-clock ". 1:00" ". 2:00")
+              "CLOCK: => 1:00\n")
       (require 'org-clock)
       (org-dynamic-block-insert-dblock "clocktable")
 

base-commit: 7235abfaec242d03ef8aaa7d2bbb8c27b99614b5
-- 
2.51.0

>From 6e020ebcac6a96bccdd1e97886bc480c35a89d11 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Wed, 8 May 2024 10:36:07 -0400
Subject: [PATCH 02/12] Testing: New test test-org-clock/clocktable/open-clock

* testing/lisp/test-org-clock.el
(test-org-clock/clocktable/open-clock): New test.
---
 testing/lisp/test-org-clock.el | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/testing/lisp/test-org-clock.el b/testing/lisp/test-org-clock.el
index 62e4d7507..1ae91ebba 100644
--- a/testing/lisp/test-org-clock.el
+++ b/testing/lisp/test-org-clock.el
@@ -362,6 +362,39 @@ test-org-clock/clocktable/insert
 	     (point) (progn (search-forward "#+END:") (line-end-position 0))))
 	(delete-region (point) (search-forward "#+END:\n")))))))
 
+(ert-deftest test-org-clock/clocktable/open-clock ()
+  "Test open clocks.
+Open clocks should be ignored unless it is clocked in and
+`org-clock-report-include-clocking-task' is t."
+  (let ((time-reported "| Headline     | Time   |
+|--------------+--------|
+| *Total time* | *1:00* |
+|--------------+--------|
+| H1           | 1:00   |")
+        (time-not-reported "| Headline     | Time   |
+|--------------+--------|
+| *Total time* | *0:00* |"))
+    (dolist (org-clock-report-include-clocking-task '(nil t))
+      (dolist (actually-clock-in '(nil t))
+        ;; Without leading characters then `org-clock-hd-marker' doesn't
+        ;; get updated when clocktable is inserted and test fails.
+        (org-test-with-temp-text "\n*<point> H1\n"
+          (should
+           (equal
+            (if (and org-clock-report-include-clocking-task
+                     actually-clock-in)
+                time-reported
+              time-not-reported)
+            (progn
+              (if actually-clock-in
+                  (org-clock-in nil (- (float-time) (* 60 60)))
+                (goto-char (point-max))
+                (insert (org-test-clock-create-clock "-1h")))
+              ;; Unless tstart and tend are fully specified it doesn't work
+              (test-org-clock-clocktable-contents ":tstart \"<-2d>\" :tend \"<tomorrow>\""))))
+          (when actually-clock-in
+            (org-clock-cancel)))))))
+
 (ert-deftest test-org-clock/clocktable/ranges ()
   "Test ranges in Clock table."
   ;; Relative time: Previous two days.
-- 
2.51.0

>From e277723c2adce67de54f7ae0f5b3f2c60448d478 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Sat, 9 Aug 2025 14:04:07 -0400
Subject: [PATCH 03/12] Testing: Add tests for clocktables from inline tasks

* testing/lisp/test-org-clock.el
(test-org-clock/clocktable/inlinetask/insert)
(test-org-clock/clocktable/inlinetask/no-heading)
(test-org-clock/clocktable/inlinetask/open-clock): New tests
---
 testing/lisp/test-org-clock.el | 81 ++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)

diff --git a/testing/lisp/test-org-clock.el b/testing/lisp/test-org-clock.el
index 1ae91ebba..af5c68eb8 100644
--- a/testing/lisp/test-org-clock.el
+++ b/testing/lisp/test-org-clock.el
@@ -1497,5 +1497,86 @@ test-org-clock/special-range
                    cases))))
     (should-not failed)))
 
+;;; Inline tasks clocktable
+
+(require 'org-inlinetask)
+
+(ert-deftest test-org-clock/clocktable/inlinetask/insert ()
+  "Test insert clocktable on an inline task."
+  (should
+   (equal
+    "| Headline     |   Time |
+|--------------+--------|
+| *Total time* | *2:00* |
+|--------------+--------|
+| H1           |   2:00 |
+| I            |   2:00 |"
+    (let ((org-inlinetask-min-level 5))
+      (org-test-with-temp-text "* H1
+***** I
+<point>
+***** END
+foo"
+      (insert (org-test-clock-create-clock ". 1:00" ". 2:00")
+              "CLOCK: => 1:00\n")
+      (test-org-clock-clocktable-contents ""))))))
+
+(ert-deftest test-org-clock/clocktable/inlinetask/no-heading ()
+  "Test insert clocktable on an inline task not under a heading."
+  ;; (wrong-type-argument number-or-marker-p nil)
+  :expected-result :failed
+  (should
+   (equal
+    "| Headline     | Time   |
+|--------------+--------|
+| *Total time* | *2:00* |
+|--------------+--------|
+| I            | 2:00   |"
+    (let ((org-inlinetask-min-level 5))
+      (org-test-with-temp-text "***** I
+<point>
+***** END
+foo"
+      (insert (org-test-clock-create-clock ". 1:00" ". 2:00")
+              "CLOCK: => 1:00\n")
+      (test-org-clock-clocktable-contents ""))))))
+
+(ert-deftest test-org-clock/clocktable/inlinetask/open-clock ()
+  "Test open clocks on an inline task.
+Open clocks should be ignored unless it is clocked in and
+`org-clock-report-include-clocking-task' is t."
+  (let ((time-reported "| Headline     |   Time |
+|--------------+--------|
+| *Total time* | *1:00* |
+|--------------+--------|
+| H1           |   1:00 |
+| I            |   1:00 |")
+        (time-not-reported "| Headline     | Time   |
+|--------------+--------|
+| *Total time* | *0:00* |")
+        (org-inlinetask-min-level 5))
+    (dolist (org-clock-report-include-clocking-task '(nil t))
+      (dolist (actually-clock-in '(nil t))
+        (org-test-with-temp-text
+         "* H1
+***** I
+<point>
+***** END
+foo"
+         (should
+          (equal
+           (if (and org-clock-report-include-clocking-task
+                    actually-clock-in)
+               time-reported
+             time-not-reported)
+           (progn
+             (if actually-clock-in
+                 (org-clock-in nil (- (float-time) (* 60 60)))
+               (insert (org-test-clock-create-clock "-1h")))
+             ;; Unless tstart and tend are fully specified it doesn't work
+             (test-org-clock-clocktable-contents ":tstart \"<-2d>\" :tend \"<tomorrow>\""))))
+         (when actually-clock-in
+           (org-clock-cancel)))))))
+
 (provide 'test-org-clock)
 ;;; test-org-clock.el end here
-- 
2.51.0

>From 8e42f4d6dccda4845c72fb858277adfefc2ca5c3 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Sat, 9 Aug 2025 14:09:20 -0400
Subject: [PATCH 04/12] Testing: test clocktable with malformed clock lines

* testing/lisp/test-org-clock.el
(test-org-clock/clocktable/malformed-clock-lines): New test
---
 testing/lisp/test-org-clock.el | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/testing/lisp/test-org-clock.el b/testing/lisp/test-org-clock.el
index af5c68eb8..7592c4e10 100644
--- a/testing/lisp/test-org-clock.el
+++ b/testing/lisp/test-org-clock.el
@@ -1316,6 +1316,21 @@ test-org-clock/clocktable/hidefiles
           (test-org-clock-clocktable-contents
            (format ":hidefiles t :scope (lambda () (list %S))" the-file))))))))
 
+(ert-deftest test-org-clock/clocktable/malformed-clock-lines ()
+  "Test clocktable with malformed clock lines."
+  (let (org-warning)
+    (cl-letf* (((symbol-function #'org-display-warning)
+                (lambda (message) (setq org-warning message))))
+      (should
+       (equal
+        "| Headline     | Time   |
+|--------------+--------|
+| *Total time* | *0:00* |"
+        (org-test-with-temp-text "* H1
+CLOCK: [2012-01-01 sun. 00rr:04]--[2012-01-01 sun. 00:05] =>  0:01"
+          (test-org-clock-clocktable-contents ""))))
+      (should (string-prefix-p "org-clock-sum: Ignoring invalid" org-warning)))))
+
 ;;; Mode line
 
 (ert-deftest test-org-clock/mode-line ()
-- 
2.51.0

>From 4bbd03211c9f3ec16614c828c24c29ae660e0ba5 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Sat, 30 Aug 2025 13:22:51 -0400
Subject: [PATCH 05/12] Testing: Add test for clocktable with :subtree scope

* testing/lisp/test-org-clock.el (test-org-clock-clocktable-contents):
Add and implement new argument to insert clocktable at point instead
of the beggining of the buffer.
(test-org-clock/clocktable/scope): Add a test for when :scope is
subtree.
---
 testing/lisp/test-org-clock.el | 25 ++++++++++++++++++++++---
 1 file changed, 22 insertions(+), 3 deletions(-)

diff --git a/testing/lisp/test-org-clock.el b/testing/lisp/test-org-clock.el
index 7592c4e10..f20c7631b 100644
--- a/testing/lisp/test-org-clock.el
+++ b/testing/lisp/test-org-clock.el
@@ -62,17 +62,21 @@ org-test-clock-create-clock
                               (/ (mod sec-diff 3600) 60))))
             "\n")))
 
-(defun test-org-clock-clocktable-contents (options &optional initial)
+(defun test-org-clock-clocktable-contents (options &optional initial no-move)
   "Return contents of a Clock table for current buffer
 
 OPTIONS is a string of Clock table options.  Optional argument
 INITIAL is a string specifying initial contents within the Clock
 table.
 
+When NO-MOVE is non-nil, then place the clocktable at point instead of
+the beginning of the buffer.
+
 Caption is ignored in contents.  The clocktable doesn't appear in
 the buffer."
   (declare (indent 2))
-  (goto-char (point-min))
+  (unless no-move
+    (goto-char (point-min)))
   (save-excursion
     (insert "#+BEGIN: clocktable " options "\n")
     (when initial (insert initial))
@@ -502,7 +506,22 @@ test-org-clock/clocktable/scope
       (let ((the-file (buffer-file-name)))
         (org-test-with-temp-text-in-file ""
           (test-org-clock-clocktable-contents
-           (format ":scope (lambda () (list %S))" the-file))))))))
+           (format ":scope (lambda () (list %S))" the-file)))))))
+  ;; Test "subtree" scope.
+  (should
+   (string-equal
+    "| Headline         | Time   |      |
+|------------------+--------+------|
+| *Total time*     | *1:00* |      |
+|------------------+--------+------|
+| \\_  subtree Test |        | 1:00 |"
+    (org-test-with-temp-text
+     "* Test
+CLOCK: [2012-03-29 Thu 8:00]--[2012-03-29 Thu 16:40] => 8:40
+** subtree Test
+<point>
+CLOCK: [2012-03-29 Thu 16:00]--[2012-03-29 Thu 17:00] =>  1:00"
+     (test-org-clock-clocktable-contents ":scope subtree" nil t)))))
 
 (ert-deftest test-org-clock/clocktable/maxlevel ()
   "Test \":maxlevel\" parameter in Clock table."
-- 
2.51.0

>From 56eddb4f98fbbbba648c239fde3cb09ae7aa57cf Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Fri, 24 Oct 2025 18:14:17 -0400
Subject: [PATCH 06/12] Testing: Add test for clocktable with :timestamp set

* testing/lisp/test-org-clock.el
(test-org-clock/clocktable/timestamp): New test.
---
 testing/lisp/test-org-clock.el | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/testing/lisp/test-org-clock.el b/testing/lisp/test-org-clock.el
index f20c7631b..e9f3584a4 100644
--- a/testing/lisp/test-org-clock.el
+++ b/testing/lisp/test-org-clock.el
@@ -479,6 +479,33 @@ test-org-clock/clocktable/tags
       (goto-line 4)
       (test-org-clock-clocktable-contents ":tags t :indent nil")))))
 
+(ert-deftest test-org-clock/clocktable/timestamp ()
+  "Test \":timestamp\" parameter in Clock table."
+  (should
+   (equal
+    "| Timestamp              | Headline           |   Time |
+|------------------------+--------------------+--------|
+|                        | *Total time*       | *4:00* |
+|------------------------+--------------------+--------|
+| <2025-10-24 Fri 10:00> | scheduled          |   1:00 |
+| <2025-10-24 Fri 10:00> | deadline           |   1:00 |
+| <2025-10-24 Fri 10:00> | timestamp          |   1:00 |
+| [2025-10-24 Fri 10:00] | inactive timestamp |   1:00 |"
+    (org-test-with-temp-text "* scheduled
+SCHEDULED: <2025-10-24 Fri 10:00>
+CLOCK: [2025-10-23 Thu 12:00]--[2025-10-23 Thu 13:00] =>  1:00
+* deadline
+DEADLINE: <2025-10-24 Fri 10:00>
+CLOCK: [2025-10-23 Thu 12:00]--[2025-10-23 Thu 13:00] =>  1:00
+* timestamp
+<2025-10-24 Fri 10:00>
+CLOCK: [2025-10-23 Thu 12:00]--[2025-10-23 Thu 13:00] =>  1:00
+* inactive timestamp
+[2025-10-24 Fri 10:00]
+CLOCK: [2025-10-23 Thu 12:00]--[2025-10-23 Thu 13:00] =>  1:00
+"
+      (test-org-clock-clocktable-contents ":timestamp t")))))
+
 (ert-deftest test-org-clock/clocktable/scope ()
   "Test \":scope\" parameter in Clock table."
   ;; Test `file-with-archives' scope.  In particular, preserve "TBLFM"
-- 
2.51.0

>From afe4a9bfe38d4989fa95d82946d478b8ba99f380 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Mon, 20 Oct 2025 16:33:47 -0400
Subject: [PATCH 07/12] Testing: Test org-columns with CLOCKSUM and CLOCKSUM_T
 properties

* testing/lisp/test-org-colview.el
(test-org-colview/column-property/clocksum)
(test-org-colview/column-property/clocksum_t): New tests.
---
 testing/lisp/test-org-colview.el | 41 ++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/testing/lisp/test-org-colview.el b/testing/lisp/test-org-colview.el
index 623ee6283..681f4fe48 100644
--- a/testing/lisp/test-org-colview.el
+++ b/testing/lisp/test-org-colview.el
@@ -1399,6 +1399,47 @@ test-org-colview/columns-next-allowed-value
 		(list (get-char-property (- (point) 1) 'org-columns-value)
 		      (get-char-property (point) 'org-columns-value))))))))
 
+(ert-deftest test-org-colview/column-property/clocksum ()
+  "Test `org-columns' display of the CLOCKSUM property."
+  (org-test-with-temp-text
+      "* H
+CLOCK: [2022-11-03 06:00]--[2022-11-03 06:03] =>  0:03
+** S1
+CLOCK: [2022-11-03 06:03]--[2022-11-03 06:05] =>  0:02
+** S2
+empty
+** S3
+CLOCK: [2022-11-03 06:05]--[2022-11-03 06:06] =>  0:01"
+    (let ((org-columns-default-format "%CLOCKSUM"))
+      (org-columns))
+    (should
+     (equal
+      '("0:06" "0:02" "" "0:01")
+      (org-map-entries
+       (lambda ()
+         (get-char-property (point) 'org-columns-value-modified)))))))
+
+(ert-deftest test-org-colview/column-property/clocksum_t ()
+  "Test `org-columns' display of the CLOCKSUM_T property."
+  (org-test-at-time "<2022-11-03>"
+    (org-test-with-temp-text
+        "* H
+CLOCK: [2022-11-02 12:00]--[2022-11-03 02:00] =>  14:00
+** S1
+CLOCK: [2022-11-03 23:50]--[2022-11-04 01:50] =>  2:00
+** S2
+empty
+** S3
+CLOCK: [2022-11-03 06:05]--[2022-11-03 06:06] =>  0:01
+"
+      (let ((org-columns-default-format "%CLOCKSUM_T"))
+        (org-columns))
+      (should
+       (equal
+        '("2:11" "0:10" "" "0:01")
+        (org-map-entries
+         (lambda ()
+           (get-char-property (point) 'org-columns-value-modified))))))))
 
 
 ;;; Dynamic block
-- 
2.51.0

>From 99163ab288bcfb4232726cf2b5d25e9b820db8e9 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Tue, 21 Oct 2025 13:15:19 -0400
Subject: [PATCH 08/12] Testing: Add test-org-clock/org-clock-sum

* testing/lisp/test-org-clock.el (test-org-clock/org-clock-sum): New
test.
---
 testing/lisp/test-org-clock.el | 54 ++++++++++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)

diff --git a/testing/lisp/test-org-clock.el b/testing/lisp/test-org-clock.el
index e9f3584a4..c1807335b 100644
--- a/testing/lisp/test-org-clock.el
+++ b/testing/lisp/test-org-clock.el
@@ -340,6 +340,60 @@ test-org-clock/clock-drawer-dwim
                      (org-ctrl-c-ctrl-c)
                      (buffer-string))))))
 
+
+;;; org-clock-sum
+
+(ert-deftest test-org-clock/org-clock-sum ()
+  "Test `org-clock-sum'."
+  (org-test-at-time "<2025-10-18 12:00>"
+    (cl-flet ((org-test-get-clock-minutes (text-property)
+                (org-map-entries
+                 (lambda ()
+                   (get-char-property (point) text-property)))))
+      (org-test-with-temp-text
+          "* This is test
+CLOCK: [2025-10-18 Sat 09:00]--[2025-10-18 Sat 10:00] =>  1:00
+*************** Here
+:LOGBOOK:
+CLOCK: [2025-10-18 Sat 13:00]
+CLOCK: [2025-10-18 Sat 10:00]--[2025-10-18 Sat 11:00] =>  1:00
+CLOCK: [2025-10-18 Sat 13:00]
+CLOCK: [2025-10-18 Sat 14:43]
+:END:
+The open clocks here are fake outs.
+*************** END"
+        (require 'org-inlinetask)
+        (org-clock-sum)
+        (should
+         (eq 120 org-clock-file-total-minutes))
+        (should
+         (equal
+          '(120 60)
+          (org-test-get-clock-minutes :org-clock-minutes)))
+        ;; Test including the current clocking task.  Requires tstart and
+        ;; tend to be set so just use `org-clock-sum-today'.
+        (let ((org-clock-report-include-clocking-task t))
+          (org-clock-in nil (time-subtract nil (* 2 60 60)))
+          (org-clock-sum-today)
+          (should
+           (eq 240 org-clock-file-total-minutes))
+          (should
+           (equal
+            '(240 60)
+            (org-test-get-clock-minutes :org-clock-minutes-today)))
+          (org-clock-cancel)
+          ;; Test open clock on inline task
+          (search-forward "Here")
+          (org-clock-in nil (time-subtract nil (* 3 60 60)))
+          (org-clock-sum-today)
+          (should
+           (eq 300 org-clock-file-total-minutes))
+          (should
+           (equal
+            '(300 240)
+            (org-test-get-clock-minutes :org-clock-minutes-today))))))))
+
+
 
 ;;; Clocktable
 
-- 
2.51.0

>From 7ffbcceb0c9f6a30579f30726babf4d43c168fcb Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Thu, 23 Oct 2025 15:31:17 -0400
Subject: [PATCH 09/12] Testing: Add test for `org-clock-sum' in source blocks

* testing/lisp/test-org-clock.el
(test-org-clock/org-clock-sum-source-block): New test
---
 testing/lisp/test-org-clock.el | 47 ++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/testing/lisp/test-org-clock.el b/testing/lisp/test-org-clock.el
index c1807335b..04e8f2fb6 100644
--- a/testing/lisp/test-org-clock.el
+++ b/testing/lisp/test-org-clock.el
@@ -393,6 +393,53 @@ test-org-clock/org-clock-sum
             '(300 240)
             (org-test-get-clock-minutes :org-clock-minutes-today))))))))
 
+(ert-deftest test-org-clock/org-clock-sum-source-block ()
+  "Test `org-clock-sum' with source blocks."
+  (org-test-at-time "<2025-10-18 15:00>"
+    (cl-flet ((org-test-get-clock-minutes (text-property)
+                (org-map-entries
+                 (lambda ()
+                   (get-char-property (point) text-property)))))
+      (org-test-with-temp-text
+          "* This is test
+CLOCK: [2025-10-18 Sat 09:00]--[2025-10-18 Sat 10:00] =>  1:00
+#+begin_src org
+<point>
+,* foo
+CLOCK: [2025-10-18 Sat 10:00]--[2025-10-18 Sat 11:00] =>  1:00
+,** subfoo
+CLOCK: [2025-10-18 Sat 11:00]--[2025-10-18 Sat 12:00] =>  1:00
+,* bar
+CLOCK: [2025-10-18 Sat 12:00]--[2025-10-18 Sat 13:00] =>  1:00
+,** subbar
+CLOCK: [2025-10-18 Sat 13:00]--[2025-10-18 Sat 14:00] =>  1:00
+#+end_src
+CLOCK: [2025-10-18 Sat 14:00]--[2025-10-18 Sat 15:00] =>  1:00
+"
+        (org-clock-sum)
+        (should
+         (eq 120 org-clock-file-total-minutes))
+        (should
+         (equal
+          '(120)
+          (org-test-get-clock-minutes :org-clock-minutes)))
+        ;; Test when editing source block
+        (org-edit-special)
+        (org-clock-sum)
+        (should
+         (eq 240 org-clock-file-total-minutes))
+        (should
+         (equal
+          '(120 60 120 60)
+          (org-test-get-clock-minutes :org-clock-minutes)))
+        (org-edit-src-exit)
+        ;; After exiting we still have the original results
+        (should
+         (eq 120 org-clock-file-total-minutes))
+        (should
+         (equal
+          '(120)
+          (org-test-get-clock-minutes :org-clock-minutes)))))))
 
 
 ;;; Clocktable
-- 
2.51.0

>From a17e34a91a30d5a3d9dbaff00978b5384958b511 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Thu, 11 Apr 2024 12:23:21 -0400
Subject: [PATCH 10/12] lisp/org-clock.el (org-clock-sum): Rewrite using
 element api

* lisp/org-clock.el (org-clock-sum): Rewrite using element api.
(org--clock-ranges): New function.
---
 lisp/org-clock.el | 220 +++++++++++++++++++++++++---------------------
 1 file changed, 119 insertions(+), 101 deletions(-)

diff --git a/lisp/org-clock.el b/lisp/org-clock.el
index 8b1752384..74aa3a162 100644
--- a/lisp/org-clock.el
+++ b/lisp/org-clock.el
@@ -33,15 +33,13 @@
 
 (require 'cl-lib)
 (require 'org)
+(require 'org-element)
 
 (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" ())
@@ -2069,105 +2067,68 @@ org-clock-sum
 HEADLINE-FILTER is a zero-arg function that, if specified, is called for
 each headline in the time range with point at the headline.  Headlines for
 which HEADLINE-FILTER returns nil are excluded from the clock summation.
-PROPNAME lets you set a custom text property instead of :org-clock-minutes."
+PROPNAME lets you set a custom text property instead of :org-clock-minutes.
+
+Clocking entries that are open (as in don't have an end time) that are
+not the current clocking entry will be ignored."
   (with-silent-modifications
-    (let* ((re (concat "^\\(\\*+\\)[ \t]\\|^[ \t]*"
-		       org-clock-string
-		       "[ \t]*\\(?:\\(\\[.*?\\]\\)-+\\(\\[.*?\\]\\)\\|=>[ \t]+\\([0-9]+\\):\\([0-9]+\\)\\)"))
-	   (lmax 30)
-	   (ltimes (make-vector lmax 0))
-	   (level 0)
-	   (tstart (cond ((stringp tstart) (org-time-string-to-seconds tstart))
-			 ((consp tstart) (float-time tstart))
-			 (t tstart)))
-	   (tend (cond ((stringp tend) (org-time-string-to-seconds tend))
-		       ((consp tend) (float-time tend))
-		       (t tend)))
-	   (t1 0)
-	   time)
-      (remove-text-properties (point-min) (point-max)
-			      `(,(or propname :org-clock-minutes) t
-				:org-clock-force-headline-inclusion t))
-      (save-excursion
-	(goto-char (point-max))
-	(while (re-search-backward re nil t)
-          (let* ((element (save-match-data (org-element-at-point)))
-                 (element-type (org-element-type element)))
-	    (cond
-	     ((and (eq element-type 'clock) (match-end 2))
-	      ;; Two time stamps.
-              (condition-case nil
-	          (let* ((timestamp (org-element-property :value element))
-		         (ts (float-time
-                              (org-encode-time
-                               (list 0
-                                     (org-element-property :minute-start timestamp)
-                                     (org-element-property :hour-start timestamp)
-                                     (org-element-property :day-start timestamp)
-                                     (org-element-property :month-start timestamp)
-                                     (org-element-property :year-start timestamp)
-                                     nil -1 nil))))
-		         (te (float-time
-                              (org-encode-time
-                               (list 0
-                                     (org-element-property :minute-end timestamp)
-                                     (org-element-property :hour-end timestamp)
-                                     (org-element-property :day-end timestamp)
-                                     (org-element-property :month-end timestamp)
-                                     (org-element-property :year-end timestamp)
-                                     nil -1 nil))))
-		         (dt (- (if tend (min te tend) te)
-			        (if tstart (max ts tstart) ts))))
-	            (when (> dt 0) (cl-incf t1 (floor dt 60))))
-                (error
-                 (org-display-warning (format "org-clock-sum: Ignoring invalid %s" (org-current-line-string))))))
-	     ((match-end 4)
-	      ;; A naked time.
-	      (setq t1 (+ t1 (string-to-number (match-string 5))
-			  (* 60 (string-to-number (match-string 4))))))
-	     ((memq element-type '(headline inlinetask)) ;A headline
-	      ;; Add the currently clocking item time to the total.
-	      (when (and org-clock-report-include-clocking-task
-		         (eq (org-clocking-buffer) (current-buffer))
-		         (eq (marker-position org-clock-hd-marker) (point))
-		         tstart
-		         tend
-		         (>= (float-time org-clock-start-time) tstart)
-		         (<= (float-time org-clock-start-time) tend))
-	        (let ((time (floor (org-time-convert-to-integer
-				    (time-since org-clock-start-time))
-				   60)))
-		  (setq t1 (+ t1 time))))
-	      (let* ((headline-forced
-		      (get-text-property (point)
-				         :org-clock-force-headline-inclusion))
-		     (headline-included
-		      (or (null headline-filter)
-			  (save-excursion
-			    (save-match-data (funcall headline-filter))))))
-	        (setq level (- (match-end 1) (match-beginning 1)))
-	        (when (>= level lmax)
-		  (setq ltimes (vconcat ltimes (make-vector lmax 0)) lmax (* 2 lmax)))
-	        (when (or (> t1 0) (> (aref ltimes level) 0))
-		  (when (or headline-included headline-forced)
-		    (if headline-included
-		        (cl-loop for l from 0 to level do
-			         (aset ltimes l (+ (aref ltimes l) t1))))
-		    (setq time (aref ltimes level))
-		    (goto-char (match-beginning 0))
-                    (put-text-property (point) (line-end-position)
-				       (or propname :org-clock-minutes) time)
-		    (when headline-filter
-		      (save-excursion
-		        (save-match-data
-			  (while (org-up-heading-safe)
-			    (put-text-property
-			     (point) (line-end-position)
-			     :org-clock-force-headline-inclusion t))))))
-		  (setq t1 0)
-		  (cl-loop for l from level to (1- lmax) do
-			   (aset ltimes l 0))))))))
-	(setq org-clock-file-total-minutes (aref ltimes 0))))))
+    (let ((tstart (cond ((stringp tstart) (org-time-string-to-seconds tstart))
+                        ((consp tstart) (float-time tstart))
+                        (t tstart)))
+          (tend (cond ((stringp tend) (org-time-string-to-seconds tend))
+                      ((consp tend) (float-time tend))
+                      (t tend)))
+          (propname (or propname :org-clock-minutes))
+          (t1 0)
+          (total 0)
+          time)
+      (remove-text-properties (point-min) (point-max) `(,propname t))
+      (org-element-cache-map
+       (lambda (headline-or-inlinetask)
+         (when (or (null headline-filter)
+                   (save-excursion
+                     (funcall headline-filter)))
+           (mapc
+            (lambda (range)
+              (setq time
+                    (pcase range
+                      (`(,_ . (open . ,buffer-position))
+                       (when (and org-clock-report-include-clocking-task
+                                  (eq (org-clocking-buffer) (current-buffer))
+                                  (eq (marker-position org-clock-marker)
+                                      buffer-position)
+                                  tstart
+                                  tend
+                                  (>= (float-time org-clock-start-time) tstart)
+                                  (<= (float-time org-clock-start-time) tend))
+                         (floor (org-time-convert-to-integer
+                                 (time-since org-clock-start-time))
+                                60)))
+                      ((pred floatp) range)
+                      (`(,time1 . ,time2)
+                       (let* ((ts (float-time time1))
+                              (te (float-time time2))
+                              (dt (- (if tend (min te tend) te)
+                                     (if tstart (max ts tstart) ts))))
+                         (floor dt 60)))))
+              (when (and time (> time 0)) (cl-incf t1 time)))
+            (org--clock-ranges headline-or-inlinetask))
+           (when (> t1 0)
+             (setq total (+ total t1))
+             (org-element-lineage-map headline-or-inlinetask
+                 (lambda (parent)
+                   (when (<= (point-min) (org-element-begin parent))
+                     (put-text-property
+                      (org-element-begin parent) (1- (org-element-contents-begin parent))
+                      propname
+                      (+ t1 (or (get-text-property
+                                 (org-element-begin parent)
+                                 propname)
+                                0)))))
+               '(headline inlinetask) t))
+           (setq t1 0)))
+       :narrow t)
+      (setq org-clock-file-total-minutes total))))
 
 (defun org-clock-sum-current-item (&optional tstart)
   "Return time, clocked on current item in total."
@@ -2182,6 +2143,63 @@ org-clock-sum-current-item
       (org-clock-sum tstart)
       org-clock-file-total-minutes)))
 
+(defun org--clock-ranges (headline)
+  "Return a list of clock ranges of HEADLINE.
+Does not recurse into subheadings.
+Ranges are in one of these formats:
+   (time . time)
+   (time . (\\='open . buffer-position)) The clock does not have an end time
+   float               The number of minutes as a float"
+  (unless (org-element-type-p headline '(headline inlinetask))
+    (error "Argument must be a headline"))
+  (and
+   (org-element-contents-begin headline) ;; empty headline
+   (or
+    (org-element-cache-get-key headline :clock-ranges)
+    (let ((clock-ranges
+           (org-element-cache-map
+            (lambda (elem)
+              (when (org-element-type-p elem 'clock)
+                (if-let* ((timestamp (org-element-property :value elem)))
+                    (progn
+                      (if
+                          (and
+                           (org-element-property :minute-start timestamp)
+                           (org-element-property :hour-start timestamp)
+                           (org-element-property :day-start timestamp)
+                           (org-element-property :month-start timestamp)
+                           (org-element-property :year-start timestamp)
+                           ;; In org-element, when the end doesn't exist, it is set to the start.
+                           ;; This means we can't check that the end is fully specified.
+                           ;; (org-element-property :minute-end timestamp)
+                           ;; (org-element-property :hour-end timestamp)
+                           ;; (org-element-property :day-end timestamp)
+                           ;; (org-element-property :month-end timestamp)
+                           ;; (org-element-property :year-end timestamp)
+                           )
+                          (cons (org-timestamp-to-time timestamp)
+                                (if (eq 'running (org-element-property :status elem))
+                                    (cons 'open (org-element-property :end timestamp))
+                                  (org-timestamp-to-time timestamp t)))
+                        (org-display-warning
+                         (format "org-clock-sum: Ignoring invalid timestamp: %s"
+                                 (org-element-property :raw-value timestamp)))))
+                  (when (org-element-property :duration elem)
+                    (org-duration-to-minutes (org-element-property :duration elem))))))
+            ;; XXX: using these arguments would be more intuitive
+            ;; but don't seem to work due to bugs in
+            ;; `org-element-cache-map'
+            ;; :restrict-elements '(clock)
+            ;; :after-element headline
+            :granularity 'element
+            :next-re org-element-clock-line-re
+            :from-pos (org-element-contents-begin headline)
+            :to-pos (save-excursion
+                      (goto-char (org-element-begin headline))
+                      (org-entry-end-position)))))
+      (org-element-cache-store-key headline :clock-ranges clock-ranges)
+      clock-ranges))))
+
 ;;;###autoload
 (defun org-clock-display (&optional arg)
   "Show subtree times in the entire buffer.
-- 
2.51.0

>From 0c2c255de15bd88b879927bd6c6d9485bb0e09c8 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Fri, 24 Oct 2025 14:44:59 -0400
Subject: [PATCH 11/12] Rewrite `org-clock-get-table-data' using org-element

* lisp/org-clock.el (org-clock-get-table-data): Rewrite using
org-element.
* testing/lisp/test-org-clock.el
(test-org-clock/clocktable/inlinetask/no-heading): Expect this test to
pass now.
---
 lisp/org-clock.el              | 168 +++++++++++++++++----------------
 testing/lisp/test-org-clock.el |   2 -
 2 files changed, 87 insertions(+), 83 deletions(-)

diff --git a/lisp/org-clock.el b/lisp/org-clock.el
index 74aa3a162..3da8c1ce1 100644
--- a/lisp/org-clock.el
+++ b/lisp/org-clock.el
@@ -3154,95 +3154,101 @@ org-clock-get-table-data
             entry, any of SCHEDULED, DEADLINE, NORMAL, or first inactive,
             in this sequence.
 TIME:       The sum of all time spend in this tree, in minutes.  This time
-            will of cause be restricted to the time block and tags match
+            will of course be restricted to the time block and tags match
             specified in PARAMS.
 PROPERTIES: The list properties specified in the `:properties' parameter
             along with their value, as an alist following the pattern
             (NAME . VALUE)."
   (let* ((maxlevel (or (plist-get params :maxlevel) 3))
-	 (timestamp (plist-get params :timestamp))
-	 (ts (plist-get params :tstart))
-	 (te (plist-get params :tend))
-	 (ws (plist-get params :wstart))
-	 (ms (plist-get params :mstart))
-	 (block (plist-get params :block))
-	 (link (plist-get params :link))
-	 (tags (plist-get params :tags))
-	 (match (plist-get params :match))
-	 (properties (plist-get params :properties))
-	 (inherit-property-p (plist-get params :inherit-props))
-	 (matcher (and match (cdr (org-make-tags-matcher match))))
-	 cc st p tbl)
+         (timestamp (plist-get params :timestamp))
+         (ts (plist-get params :tstart))
+         (te (plist-get params :tend))
+         (ws (plist-get params :wstart))
+         (ms (plist-get params :mstart))
+         (block (plist-get params :block))
+         (link (plist-get params :link))
+         (tags (plist-get params :tags))
+         (match (plist-get params :match))
+         (properties (plist-get params :properties))
+         (inherit-property-p (plist-get params :inherit-props))
+         (matcher (and match (cdr (org-make-tags-matcher match))))
+         cc)
 
-    (setq org-clock-file-total-minutes nil)
     (when block
       (setq cc (org-clock-special-range block nil t ws ms)
-	    ts (car cc)
-	    te (nth 1 cc)))
-    (when (integerp ts) (setq ts (calendar-gregorian-from-absolute ts)))
-    (when (integerp te) (setq te (calendar-gregorian-from-absolute te)))
-    (when (and ts (listp ts))
-      (setq ts (format "%4d-%02d-%02d" (nth 2 ts) (car ts) (nth 1 ts))))
-    (when (and te (listp te))
-      (setq te (format "%4d-%02d-%02d" (nth 2 te) (car te) (nth 1 te))))
-    ;; Now the times are strings we can parse.
-    (if ts (setq ts (org-matcher-time ts)))
-    (if te (setq te (org-matcher-time te)))
-    (save-excursion
-      (org-clock-sum ts te
-		     (when matcher
-		       (lambda ()
-			 (let* ((todo (org-get-todo-state))
-				(tags-list (org-get-tags))
-				(org-scanner-tags tags-list)
-				(org-trust-scanner-tags t)
-                                (level (org-current-level)))
-			   (funcall matcher todo tags-list level)))))
-      (goto-char (point-min))
-      (setq st t)
-      (while (or (and (bobp) (prog1 st (setq st nil))
-		      (get-text-property (point) :org-clock-minutes)
-		      (setq p (point-min)))
-		 (setq p (next-single-property-change
-			  (point) :org-clock-minutes)))
-	(goto-char p)
-	(let ((time (get-text-property p :org-clock-minutes)))
-	  (when (and time (> time 0) (org-at-heading-p))
-	    (let ((level (org-reduced-level (org-current-level))))
-	      (when (<= level maxlevel)
-		(let* ((headline (org-get-heading t t t t))
-		       (hdl
-			(if (not link) headline
-			  (let ((search
-				 (org-link-heading-search-string headline)))
-			    (org-link-make-string
-			     (if (not (buffer-file-name)) search
-			       (format "file:%s::%s" (buffer-file-name) search))
-			     ;; Prune statistics cookies.  Replace
-			     ;; links with their description, or
-			     ;; a plain link if there is none.
-			     (org-trim
-			      (org-link-display-format
-			       (replace-regexp-in-string
-				"\\[[0-9]*\\(?:%\\|/[0-9]*\\)\\]" ""
-				headline)))))))
-		       (tgs (and tags (org-get-tags)))
-		       (tsp
-			(and timestamp
-			     (cl-some (lambda (p) (org-entry-get (point) p))
-				      '("SCHEDULED" "DEADLINE" "TIMESTAMP"
-					"TIMESTAMP_IA"))))
-		       (props
-			(and properties
-			     (delq nil
-				   (mapcar
-				    (lambda (p)
-				      (let ((v (org-entry-get
-						(point) p inherit-property-p)))
-					(and v (cons p v))))
-				    properties)))))
-		  (push (list level hdl tgs tsp time props) tbl)))))))
-      (list file org-clock-file-total-minutes (nreverse tbl)))))
+            ts (car cc)
+            te (nth 1 cc)))
+    (when (and ts (or (integerp ts) (listp ts)))
+      (setq ts (org-time-from-absolute ts)))
+    (when (and te (or (integerp te) (listp te)))
+      (setq te (org-time-from-absolute te)))
+    (when (stringp ts) (setq ts (org-matcher-time ts)))
+    (when (stringp te) (setq te (org-matcher-time te)))
+    (org-clock-sum ts te
+                   (when matcher
+                     (lambda ()
+                       (let* ((todo (org-get-todo-state))
+                              (tags-list (org-get-tags))
+                              (org-scanner-tags tags-list)
+                              (org-trust-scanner-tags t)
+                              (level (org-current-level)))
+                         (funcall matcher todo tags-list level)))))
+    (list
+     file org-clock-file-total-minutes
+     (org-element-cache-map
+      (lambda (elm)
+        (when-let*
+            ((time
+              (let ((time (get-text-property (org-element-begin elm)
+                                             :org-clock-minutes)))
+                (when (and time (> time 0))
+                  time)))
+             (level
+              (let ((level
+                     (if (eq 'headline (org-element-type elm))
+                         (org-element-property :level elm)
+                       ;; inline task
+                       (or (org-element-lineage-map elm
+                               (lambda (elm)
+                                 (org-element-property :level elm))
+                             '(headline) nil t)
+                           1))))
+                (when (<= level maxlevel)
+                  level)))
+             (title
+              (let ((headline (org-element-property :title elm)))
+                (if (not link) headline
+                  (let ((search
+                         (org-link-heading-search-string headline)))
+                    (org-link-make-string
+                     (if (not (buffer-file-name)) search
+                       (format "file:%s::%s" (buffer-file-name) search))
+                     ;; Prune statistics cookies.  Replace
+                     ;; links with their description, or
+                     ;; a plain link if there is none.
+                     (org-trim
+                      (org-link-display-format
+                       (replace-regexp-in-string
+                        "\\[[0-9]*\\(?:%\\|/[0-9]*\\)\\]" ""
+                        headline)))))))))
+
+          (list level
+                title
+                (and tags (org-element-property :tags elm))
+                (and timestamp
+                     (cl-some
+                      (lambda (p) (org-entry-get (org-element-begin elm) p))
+                      '("SCHEDULED" "DEADLINE" "TIMESTAMP" "TIMESTAMP_IA")))
+                time
+                (and properties
+                     (delq nil
+                           (mapcar
+                            (lambda (p)
+                              (let ((v (org-entry-get
+                                        (point) p inherit-property-p)))
+                                (and v (cons p v))))
+                            properties))))))
+      :narrow t))))
 
 ;; Saving and loading the clock
 
diff --git a/testing/lisp/test-org-clock.el b/testing/lisp/test-org-clock.el
index 04e8f2fb6..37e72c940 100644
--- a/testing/lisp/test-org-clock.el
+++ b/testing/lisp/test-org-clock.el
@@ -1685,8 +1685,6 @@ test-org-clock/clocktable/inlinetask/insert
 
 (ert-deftest test-org-clock/clocktable/inlinetask/no-heading ()
   "Test insert clocktable on an inline task not under a heading."
-  ;; (wrong-type-argument number-or-marker-p nil)
-  :expected-result :failed
   (should
    (equal
     "| Headline     | Time   |
-- 
2.51.0

>From a6fb8112cbc7e3f5dd1380cc456028c688954a1f Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Fri, 24 Oct 2025 16:47:44 -0400
Subject: [PATCH 12/12] Clocktables: Indent inline tasks under their heading

* lisp/org-clock.el (lisp/org-clock.el): Inrease the level of inline
tasks by 1.
* testing/lisp/test-org-clock.el
(test-org-clock/clocktable/inlinetask/insert)
(test-org-clock/clocktable/inlinetask/open-clock): Adjust tests to new
behavior.
---
 lisp/org-clock.el              | 11 ++++++-----
 testing/lisp/test-org-clock.el | 15 ++++++++-------
 2 files changed, 14 insertions(+), 12 deletions(-)

diff --git a/lisp/org-clock.el b/lisp/org-clock.el
index 3da8c1ce1..487df9780 100644
--- a/lisp/org-clock.el
+++ b/lisp/org-clock.el
@@ -3208,11 +3208,12 @@ org-clock-get-table-data
                      (if (eq 'headline (org-element-type elm))
                          (org-element-property :level elm)
                        ;; inline task
-                       (or (org-element-lineage-map elm
-                               (lambda (elm)
-                                 (org-element-property :level elm))
-                             '(headline) nil t)
-                           1))))
+                       (1+
+                        (or (org-element-lineage-map elm
+                                (lambda (elm)
+                                  (org-element-property :level elm))
+                              '(headline) nil t)
+                            0)))))
                 (when (<= level maxlevel)
                   level)))
              (title
diff --git a/testing/lisp/test-org-clock.el b/testing/lisp/test-org-clock.el
index 37e72c940..4e392eeac 100644
--- a/testing/lisp/test-org-clock.el
+++ b/testing/lisp/test-org-clock.el
@@ -1672,7 +1672,7 @@ test-org-clock/clocktable/inlinetask/insert
 | *Total time* | *2:00* |
 |--------------+--------|
 | H1           |   2:00 |
-| I            |   2:00 |"
+| \\_  I        |   2:00 |"
     (let ((org-inlinetask-min-level 5))
       (org-test-with-temp-text "* H1
 ***** I
@@ -1705,12 +1705,13 @@ test-org-clock/clocktable/inlinetask/open-clock
   "Test open clocks on an inline task.
 Open clocks should be ignored unless it is clocked in and
 `org-clock-report-include-clocking-task' is t."
-  (let ((time-reported "| Headline     |   Time |
-|--------------+--------|
-| *Total time* | *1:00* |
-|--------------+--------|
-| H1           |   1:00 |
-| I            |   1:00 |")
+  (let ((time-reported
+         "| Headline     | Time   |      |
+|--------------+--------+------|
+| *Total time* | *1:00* |      |
+|--------------+--------+------|
+| H1           | 1:00   |      |
+| \\_  I        |        | 1:00 |")
         (time-not-reported "| Headline     | Time   |
 |--------------+--------|
 | *Total time* | *0:00* |")
-- 
2.51.0

Reply via email to