branch: externals/org
commit 7eafc194d2c3a42b84a319847f66ec6bdfea183c
Author: Rohit Patnaik <quanti...@gmail.com>
Commit: Ihor Radchenko <yanta...@posteo.net>

    org-clock: Make headline truncation behave better
    
    * lisp/org-clock.el (org-clock-get-clock-string): Move the headline 
truncation
    logic into `org-clock-get-clock-string', which enables much nicer truncation
    behavior.  Now, `org-clock-get-clock-string' accepts an optional 
`max-length'
    parameter.  If the length of the combined time string and headline exceeds
    `max-length', the function truncates the headline, adds an ellipsis and
    preserves the closing parenthesis.  If `max-length' is so small that even a
    single character of the headline cannot be displayed, the function returns a
    (possibly truncated) time string
    
    * lisp/org-clock.el (org-clock-update-mode-line): Removed truncation code, 
as it
    is now redundant with the truncation code in `org-clock-get-clock-string`.
    Instead, the function now passes `org-clock-string-limit' to
    `org-clock-get-clock-string' as the `max-length' argument.
    
    * testing/lisp/test-org-clock.el (test-org-clock/mode-line): Added a few 
tests
    to ensure that the new truncation logic is behaving correctly.
    
    * etc/ORG-NEWS: (~org-clock-get-clock-string~ now takes an optional 
~max-length~
    argument): Document the change.
---
 etc/ORG-NEWS                   |   9 ++++
 lisp/org-clock.el              | 100 ++++++++++++++++++++++++++++++-----------
 testing/lisp/test-org-clock.el |  96 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 178 insertions(+), 27 deletions(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 4e9440b519..62502a6781 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -453,6 +453,15 @@ When CHILDREN contains ~nil~ elements, they are skipped.  
This way,
 
 will yield expected results rather than assigning literal ~nil~ as a child.
 
+*** ~org-clock-get-clock-string~ now takes an optional ~max-length~ argument
+
+When a ~max-length~ is passed to ~org-clock-get-clock-string~, it will first
+attempt to truncate the headline and add an ellipsis in order to make the 
entire
+clock string fit under the length limit. If the length limit is too small to
+accommodate even a single character of the headline, after accounting for 
spaces
+and the surrounding parentheses, it will omit the headline entirely and just
+show as much of the clock as fits under the limit.
+
 ** Removed or renamed functions and variables
 
 *** ~org-cycle-display-inline-images~ is renamed to 
~org-cycle-display-link-previews~
diff --git a/lisp/org-clock.el b/lisp/org-clock.el
index a4b37ce59e..efac5212a2 100644
--- a/lisp/org-clock.el
+++ b/lisp/org-clock.el
@@ -741,27 +741,80 @@ pointing to it."
 (defvar org-clock-update-period 60
   "Number of seconds between mode line clock string updates.")
 
-(defun org-clock-get-clock-string ()
+(defun org-clock-get-clock-string (&optional max-length)
   "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))))
+01:30/01:50 format (clocked/estimated).  If not, show simply
+the clocked time like 01:50.
+
+When the optional MAX-LENGTH argument is given, this function
+will preferentially truncate the headline in order to ensure
+that the entire clock string's length remains under the
+limit."
+  (let* ((max-string-length (or max-length 0))
+         (clocked-time (org-clock-get-clocked-time))
+         (clock-str (org-duration-from-minutes clocked-time))
+         (clock-format-str (propertize "[%s]" 'face 'org-mode-line-clock))
+         (clock-format-effort-str (propertize "[%s/%s]"
+                                              'face
+                                              'org-mode-line-clock))
+         (mode-line-str-with-headline (propertize "%s (%s) "
+                                                  'face
+                                                  'org-mode-line-clock))
+         (mode-line-str-without-headline (propertize "%s "
+                                                     'face
+                                                     'org-mode-line-clock))
+         (effort-estimate-str (if org-clock-effort
+                         (org-duration-from-minutes
+                          (org-duration-to-minutes
+                           org-clock-effort))
+                       nil))
+         (time-str (if (not org-clock-effort)
+                       (format clock-format-str clock-str)
+                     (format clock-format-effort-str
+                             (propertize clock-str
+                                         'face
+                                         (if (and org-clock-task-overrun
+                                                  (not
+                                                   
org-clock-task-overrun-text))
+                                             'org-mode-line-clock-overrun
+                                           'org-mode-line-clock))
+                             effort-estimate-str)))
+         (spaces-and-parens-length (1+ (length
+                                        (format
+                                         mode-line-str-with-headline "" ""))))
+         (untruncated-length (+ spaces-and-parens-length (length time-str)
+                                (length org-clock-heading))))
+    ;; There are three cases for displaying the mode-line clock string.
+    ;; 1. MAX-STRING-LENGTH is zero or greater than UNTRUNCATED-LENGTH
+    ;;      - We can display the clock and the headline without truncation
+    ;; 2. MAX-STRING-LENGTH is above zero and less than or equal to
+    ;;    (+ SPACES-AND-PARENS-LENGTH (LENGTH TIME-STR))
+    ;;      - There isn't enough room to display any of the headline so just
+    ;;        display a (truncated) time string
+    ;; 3. ORG-CLOCK-STRING-LIMIT is greater than
+    ;;    (+ SPACES-AND-PARENS-LENGTH (LENGTH TIME-STR)) but less than
+    ;;    UNTRUNCATED-LENGTH
+    ;;      - Intelligently truncate the headline such that the total length of
+    ;;        the mode line string is less than ORG-CLOCK-STRING-LIMIT
+    (cond ((or (<= max-string-length 0)
+               (>= max-string-length untruncated-length))
+           (format mode-line-str-with-headline time-str org-clock-heading))
+          ((or (<= max-string-length 0)
+               (<= max-string-length (+ spaces-and-parens-length
+                                        (length time-str))))
+           (format mode-line-str-without-headline
+                   (substring time-str 0 (min (length time-str)
+                                              max-string-length))))
+          (t
+           (let ((heading-length (- max-string-length
+                                    (+ spaces-and-parens-length
+                                       (length time-str)))))
+             (format mode-line-str-with-headline
+                     time-str
+                     (string-join `(,(substring org-clock-heading
+                                                0 heading-length)
+                                    "…"))))))))
 
 (defun org-clock-get-last-clock-out-time ()
   "Get the last clock-out time for the current subtree."
@@ -781,15 +834,10 @@ When optional argument is non-nil, refresh cached 
heading."
   (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))
+        (let ((clock-string (org-clock-get-clock-string 
org-clock-string-limit))
               (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)))
+          (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)
diff --git a/testing/lisp/test-org-clock.el b/testing/lisp/test-org-clock.el
index 17f71d4927..8a196ee963 100644
--- a/testing/lisp/test-org-clock.el
+++ b/testing/lisp/test-org-clock.el
@@ -1317,7 +1317,101 @@ Variables'."
       (prog1 (concat "<before> "
                      (org-clock-get-clock-string)
                      "<after> ")
-        (org-clock-out))))))
+        (org-clock-out)))))
+  ;; Verify that long headlines are truncated correctly
+  (should
+   (equal
+    "<before> [0:00] (This is a…) <after> "
+    (org-test-with-temp-text
+     "* This is a long headline blah blah blah"
+     (org-clock-in)
+     (prog1 (concat "<before> "
+                    (org-clock-get-clock-string 20)
+                    "<after> ")
+       (org-clock-out)))))
+  ;; Verify that long headlines with effort are truncated correctly
+  (should
+   (equal
+    "<before> [0:00/1:00] (This…) <after> "
+    (org-test-with-temp-text
+     "* This is a long headline blah blah blah
+:PROPERTIES:
+:EFFORT: 1h
+:END:"
+     (org-clock-in)
+     (prog1 (concat "<before> "
+                    (org-clock-get-clock-string 20)
+                    "<after> ")
+       (org-clock-out)))))
+
+  ;; Check the limit case where there's just one character of the headline
+  ;; displayed
+  (should
+   (equal
+    "<before> [0:00] (T…) <after> "
+    (org-test-with-temp-text
+     "* This is a long headline blah blah blah"
+     (org-clock-in)
+     (prog1 (concat "<before> "
+                    (org-clock-get-clock-string 12)
+                    "<after> ")
+       (org-clock-out)))))
+
+  ;; Check the limit case where the headline can't be displayed at all
+  (should
+   (equal
+    "<before> [0:00] <after> "
+    (org-test-with-temp-text
+     "* This is a long headline blah blah blah"
+     (org-clock-in)
+     (prog1 (concat "<before> "
+                    (org-clock-get-clock-string 10)
+                    "<after> ")
+       (org-clock-out)))))
+
+  ;; Check the limit case where even the time string is truncated
+  (should
+   (equal
+    "<before> [0: <after> "
+    (org-test-with-temp-text
+     "* This is a long headline blah blah blah"
+     (org-clock-in)
+     (prog1 (concat "<before> "
+                    (org-clock-get-clock-string 3)
+                    "<after> ")
+       (org-clock-out)))))
+
+  ;; Verify that 'org-mode-line-clock-overrun face is applied if the task has
+  ;; overrun is alloted time and there is no overrun text defined
+  (should
+   (equal 'org-mode-line-clock-overrun
+          (org-test-with-temp-text
+              "* Heading
+:PROPERTIES:
+:EFFORT: 1h
+:END:"
+            (org-clock-in)
+            (prog1
+                (let ((org-clock-task-overrun t)
+                      (org-clock-task-overrun-text nil))
+                  (get-text-property 1 'face (org-clock-get-clock-string)))
+              (org-clock-out)))))
+
+  ;; Verify that the 'org-mode-line-clock face is applied if the task has not
+  ;; overrun its alloted time
+  (should
+   (equal 'org-mode-line-clock
+          (org-test-with-temp-text
+              "* Heading
+:PROPERTIES:
+:EFFORT: 1h
+:END:"
+            (org-clock-in)
+            (prog1
+                (get-text-property 1 'face (org-clock-get-clock-string))
+              (org-clock-out))))))
+
+
 
 ;;; Helpers
 

Reply via email to