"Rohit Patnaik" <quanti...@quanticle.net> writes: >> Rather than hard-coding number 5, I'd rather >> 1. Let-bind "%s (%s) " value >> 2. Compute spaces-and-parenth-length by formatting that value with %s >> placeholders being empty. > > This is the approach I took. I let-bound the format strings into variables and > formatted them with empty strings in order to get the length of the > "overhead", > and used that to compute the value of spaces-and-paren-length. > > Thanks for the feedback; the code looks much better now.
Thanks for the update! I cleaned the patch a bit, fixing indentation, some trailing spaces, and function arguments in docstring (they should be formatted as ARGUMENT, as per Elisp conventions). See the attached. > - (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))) In your patch, you lost 'org-mode-line-clock-overrun face application. Please, fix it.
>From 219894424300f24c064a418c504c5d4459a2074c Mon Sep 17 00:00:00 2001 Message-ID: <219894424300f24c064a418c504c5d4459a2074c.1747155198.git.yanta...@posteo.net> From: Rohit Patnaik <quanti...@gmail.com> Date: Tue, 18 Mar 2025 04:45:06 -0500 Subject: [PATCH v2] 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 | 82 +++++++++++++++++++++++----------- testing/lisp/test-org-clock.el | 62 +++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 25 deletions(-) diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 4e9440b51..62502a678 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 a4b37ce59..5d2c5c7df 100644 --- a/lisp/org-clock.el +++ b/lisp/org-clock.el @@ -741,27 +741,64 @@ (defvar org-clock-task-overrun nil (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)))) +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 "[%s]") + (clock-format-effort-str "[%s/%s]") + (mode-line-str-with-headline "%s (%s) ") + (mode-line-str-without-headline "%s ") + (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 + clock-str + 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 (propertize mode-line-str-with-headline 'face 'org-mode-line-clock) + time-str org-clock-heading)) + ((or (<= max-string-length 0) + (<= max-string-length (+ spaces-and-parens-length (length time-str)))) + (format (propertize mode-line-str-without-headline 'face 'org-mode-line-clock) + (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 (propertize mode-line-str-with-headline 'face 'org-mode-line-clock) + 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 +818,10 @@ (defun org-clock-update-mode-line (&optional refresh) (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 17f71d492..db1f03673 100644 --- a/testing/lisp/test-org-clock.el +++ b/testing/lisp/test-org-clock.el @@ -1317,6 +1317,68 @@ (ert-deftest test-org-clock/mode-line () (prog1 (concat "<before> " (org-clock-get-clock-string) "<after> ") + (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)))))) ;;; Helpers -- 2.49.0
-- Ihor Radchenko // yantar92, Org mode maintainer, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92>