> Resending because I spelled contiguous incorrectly like 10 times. > Thanks for the quick update!
> + ;; Regular text search of headlines in Org mode buffers > + (org-link--search-headlines words)) > + (forward-line 0) > + (setq type 'dedicated)) > + ;; Second attempt of regular text search of headlines in Org mode buffers > + ;; This time we remove pipes from headlines > + ((and (derived-mode-p 'org-mode) > + (org-link--search-headlines (split-string (org-link-unescape (if starred (substring s 1) s))) 't)) > Could you please elaborate why you need org-link-unescape here? > Also, nitpick: 't is not needed and not recommended as per Elisp style. t is self-quoting. Fixed the t. I also replaced "(split-string (org-link-unescape (if starred (substring s 1) s)))" with "words". That code was not needed. That was left over from not me not understanding how to handle the creation of links to headlines that included links themselves.
From cd8320da489c77f30fbf3d3c3c9edb4630eda508 Mon Sep 17 00:00:00 2001 From: ApollonDeParnasse <[email protected]> Date: Wed, 6 May 2026 14:20:02 -0500 Subject: [PATCH] org-clock: Fix pipe char (|) in headings breaks clockreport * lisp/ol.el (org-link-normalize-string): Optionally remove statistic cookies, search syntax and pipe chars from a given string. (org-link--normalize-string): Call `org-link--normalize-string' in order to remove statistic cookies and/or search syntax from a string. (org-link--remove-statistics-cookies): Remove statistics cookies from a string. (org-link--remove-search-syntax): Remove search syntax from a string. (org-link--remove-pipe-chars): Remove pipe chars from a string. (org-link--remove-contiguous-spaces): Remove contiguous spaces from a string. (org-link-heading-search-string): New arg REMOVE-PIPE-CHARS. All callers changed. (org-link--search-headlines): Search for a headline that matches a given string on behalf of `org-link-search'. Optionally will ignore pipe chars in possible matches. (org-link-search): Use `org-link--search-headlines' in order to search headlines for a string match. In order to avoid false positives, `org-link--search-headlines' is called twice. For the first search, we try to find an exact match. For the second search, we remove pipe chars from each possible headline match before we perform the final comparison. (org-link--string-normalizers): New constant variable containing the functions `org-link-normalize-string' will use to normalize a string. * testing/lisp/test-ol.el (test-org-link-search-asserter): New test case asserter for `org-link-search' tests. Asserts `org-link-search' can perform exact matches and fuzzy matches. (test-org-link/exact-headline-match): New test for `org-link-search'. Assert `org-link-search' can find exact headline matches. (test-org-link/fuzzy-headline-match): New test for `org-link-search'. Assert `org-link-search' can find a headline with pipe chars even when the search string does not contain any. * lisp/org-clock.el (org-clock-get-table-data): Use `org-link-normalize-string' in order to create clean headlines for clockreports. Use `org-link-create-headline-link-for-table' in order to create clean headline links for clockreports. * testing/lisp/test-org-clock.el (test-org-clock/clocktable/link): New test cases for headlines with links and pipe chars. (test-org-clock/clocktable/remove-pipe-chars): New test for `org-clock-report'. Assert pipe chars are always removed from headlines. Reported-by: Alexey Lebedeff <[email protected]> Link: https://list.orgmode.org/4kf7FncTOFolP8GCcXAtYILaRmeCvaXxPyVzZVYjdzIfR_VvZgIToSJZH3d2JakQxtTLs2f_hURIT97_fYrBEnFN2sSzW9iN6o96xnLE7tA=@binarin.Ru/ squash --- lisp/ol.el | 147 ++++++++++++++++++++++++--------- lisp/org-clock.el | 23 ++---- testing/lisp/test-ol.el | 46 +++++++++++ testing/lisp/test-org-clock.el | 109 ++++++++++++++++++++---- 4 files changed, 255 insertions(+), 70 deletions(-) diff --git a/lisp/ol.el b/lisp/ol.el index 73645fb97..73f3c84b3 100644 --- a/lisp/ol.el +++ b/lisp/ol.el @@ -35,6 +35,8 @@ (require 'org-fold) (require 'calendar) +(require 'map) +(require 'seq) (defvar clean-buffer-list-kill-buffer-names) (defvar org-agenda-buffer-name) @@ -743,6 +745,17 @@ exact and fuzzy text search.") (defconst org-link--forbidden-chars "]\t\n\r<>" "Characters forbidden within a link, as a string.") +(defconst org-link--string-normalizers + (list (cons 'statistics-cookies #'org-link--remove-statistics-cookies) + (cons 'search-syntax #'org-link--remove-search-syntax) + (cons 'pipe-chars #'org-link--remove-pipe-chars) + (cons 'contiguous-spaces #'org-link--remove-contiguous-spaces) + (cons 'leading-and-trailing-spaces #'org-trim)) + "Alist of functions used by `org-link-normalize-string'. +Each symbol corresponds to a value that will be +removed from a string. Each value corresponds to +a function that will remove said value.") + (defvar org-link--history nil "History for inserted links.") @@ -907,30 +920,57 @@ White spaces are not significant." "\n")))) context))) +(defsubst org-link--remove-statistics-cookies (string) + "Remove statistics cookies from STRING." + (replace-regexp-in-string + ;; Statistics cookie regexp. + (rx (seq "[" (0+ digit) (or "%" (seq "/" (0+ digit))) "]")) + " " string)) + +(defsubst org-link--remove-pipe-chars (string) + "Remove pipe chars from STRING." + (replace-regexp-in-string "|" " " string)) + +(defsubst org-link--remove-search-syntax (string) + "Remove search syntax from STRING." + (while (cond ((and (string-prefix-p "(" string) + (string-suffix-p ")" string)) + (setq string (org-trim (substring string 1 -1)))) + ((string-match "\\`[#*]+[ \t]*" string) + (setq string (substring string (match-end 0)))) + (t nil))) + string) + +(defsubst org-link--remove-contiguous-spaces (string) + "Remove contiguous spaces from STRING." + (replace-regexp-in-string + (rx (one-or-more (any " \t"))) + " " + string)) + (defun org-link--normalize-string (string &optional context) "Remove ignored contents from STRING string and return it. This function removes contiguous white spaces and statistics cookies. When optional argument CONTEXT is non-nil, it assumes STRING is a context string, and also removes special search syntax around the string." - (let ((string - (org-trim - (replace-regexp-in-string - (rx (one-or-more (any " \t"))) - " " - (replace-regexp-in-string - ;; Statistics cookie regexp. - (rx (seq "[" (0+ digit) (or "%" (seq "/" (0+ digit))) "]")) - " " - string))))) - (when context - (while (cond ((and (string-prefix-p "(" string) - (string-suffix-p ")" string)) - (setq string (org-trim (substring string 1 -1)))) - ((string-match "\\`[#*]+[ \t]*" string) - (setq string (substring string (match-end 0)))) - (t nil)))) - string)) + (org-link-normalize-string string + (if context + '(statistics-cookies search-syntax) + '(statistics-cookies)))) + +(cl-defun org-link-normalize-string (string &optional (ignored-contents '(statistics-cookies search-syntax))) + "Remove contiguous white spaces and IGNORED-CONTENTS from STRING. +IGNORED-CONTENTS is a list of extra things to remove. It can be any +subset of (statistics-cookies search-syntax pipe-chars): +- statistics-cookies are statistics cookie strings +- search-syntax is # in #heading and enclosing () in (ref) +- pipe-chars are | characters that may clash with table syntax" + (let* ((valid-symbols (seq-intersection ignored-contents (list 'statistics-cookies 'search-syntax 'pipe-chars))) + (values-to-remove (append valid-symbols (list 'contiguous-spaces 'leading-and-trailing-spaces)))) + (seq-reduce (lambda (str func) (funcall (map-elt org-link--string-normalizers func) str)) + values-to-remove + string))) (defun org-link--reveal-maybe (region _) "Reveal folded link in REGION when needed. @@ -1733,6 +1773,28 @@ Optional argument ARG is passed to `org-open-file' when S is a s (substring s (1- (org-element-end link))))) (link (org-link-open link arg)))) +(defun org-link--search-headlines (words &optional ignore-pipes) + "Search headlines in Org mode buffers. +WORDS is a list of strings. Ignore COMMENT +keyword, TODO keywords, priority cookies, +statistics cookies and tags. When IGNORE-PIPES +is non-nil, also ignore pipe characters." + (let ((title-re + (format "%s.*\\(?:%s[ \t]\\)?.*%s" + org-outline-regexp-bol + org-comment-string + (regexp-opt words))) + (case-fold-search t)) + (goto-char (point-min)) + (catch :found + (while (re-search-forward title-re nil t) + (when-let* ((heading-content (org-get-heading t t t t)) + (normalize-string-args (if ignore-pipes (list 'statistics-cookies 'pipe-chars) (list 'statistics-cookies))) + (heading-parts (split-string (org-link-normalize-string heading-content normalize-string-args))) + (match-found (equal words heading-parts))) + (throw :found t))) + nil))) + (defun org-link-search (s &optional avoid-pos stealth new-heading-container) "Search for a search string S in the accessible part of the buffer. @@ -1832,25 +1894,15 @@ respects buffer narrowing." (forward-line 0) (throw :name-match t)))) nil)))) - ;; Regular text search. Prefer headlines in Org mode buffers. - ;; Ignore COMMENT keyword, TODO keywords, priority cookies, - ;; statistics cookies and tags. + ;; Regular text search of headlines in Org mode buffers ((and (derived-mode-p 'org-mode) - (let ((title-re - (format "%s.*\\(?:%s[ \t]\\)?.*%s" - org-outline-regexp-bol - org-comment-string - (mapconcat #'regexp-quote words ".+")))) - (goto-char (point-min)) - (catch :found - (while (re-search-forward title-re nil t) - (when (equal (mapcar #'upcase words) - (mapcar #'upcase - (split-string - (org-link--normalize-string - (org-get-heading t t t t))))) - (throw :found t))) - nil))) + (org-link--search-headlines words)) + (forward-line 0) + (setq type 'dedicated)) + ;; Second attempt of regular text search of headlines in Org mode buffers + ;; This time we remove pipes from headlines + ((and (derived-mode-p 'org-mode) + (org-link--search-headlines words t)) (forward-line 0) (setq type 'dedicated)) ;; Offer to create non-existent headline depending on @@ -1911,7 +1963,7 @@ respects buffer narrowing." (org-fold-show-context 'link-search)) type)) -(defun org-link-heading-search-string (&optional string) +(defun org-link-heading-search-string (&optional string remove-pipe-chars) "Make search string for the current headline or STRING. Search string starts with an asterisk. COMMENT keyword and @@ -1920,10 +1972,25 @@ into a single one. When optional argument STRING is non-nil, assume it a headline, without any asterisk, TODO or COMMENT keyword, and without any -priority cookie or tag." +priority cookie or tag. + +When optional argument REMOVE-PIPE-CHARS is non-nil, +remove pipe chars from string." + (let ((normalize-string-args (if remove-pipe-chars (list 'statistics-cookies 'pipe-chars) (list 'statistics-cookies)))) (concat "*" - (org-link--normalize-string - (or string (org-get-heading t t t t))))) + (org-link-normalize-string + (or string (org-get-heading t t t t)) normalize-string-args)))) + +(defun org-link-create-headline-link-for-table (headline) + "Convert HEADLINE into a link for a clocktable. +The link and the description will not contain contiguous +white spaces, statistics cookies or pipe chars." + (let* ((file-name (buffer-file-name)) + (description (org-link-normalize-string headline (list 'statistics-cookies 'pipe-chars))) + (link (if file-name + (format "file:%s::%s" file-name (org-link-heading-search-string headline t)) + (org-link-heading-search-string headline t)))) + (org-link-make-string link description))) (defun org-link-precise-link-target () "Determine search string and description for storing a link. diff --git a/lisp/org-clock.el b/lisp/org-clock.el index 53d326e58..384b593c4 100644 --- a/lisp/org-clock.el +++ b/lisp/org-clock.el @@ -47,7 +47,8 @@ (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-normalize-string (string &optional (ignored-contents '(statistics-cookie search-syntax)))) +(declare-function org-link-create-headline-link-for-table "ol" (headline)) (declare-function org-link-make-string "ol" (link &optional description)) (declare-function org-table-goto-line "org-table" (n)) (declare-function w32-notification-notify "w32fns.c" (&rest params)) @@ -62,6 +63,7 @@ (defvar org-state) (defvar org-link-bracket-re) + (defgroup org-clock nil "Options concerning clocking working time in Org mode." :tag "Org Clock" @@ -3120,6 +3122,8 @@ a number of clock tables." (setq start next)) (end-of-line 0)))) + + (defun org-clock-get-table-data (file params) "Get the clocktable data for file FILE, with parameters PARAMS. FILE is only for identification - this function assumes that @@ -3206,20 +3210,9 @@ PROPERTIES: The list properties specified in the `:properties' parameter (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))))))) + (if (not link) + (org-link-normalize-string headline (list 'statistics-cookies 'pipe-chars)) + (org-link-create-headline-link-for-table headline))) (tgs (and tags (org-get-tags))) (tsp (and timestamp diff --git a/testing/lisp/test-ol.el b/testing/lisp/test-ol.el index c8ec09616..b815f8ee6 100644 --- a/testing/lisp/test-ol.el +++ b/testing/lisp/test-ol.el @@ -888,5 +888,51 @@ API in `org-link-parameters'. Used in test (org-insert-link nil nil "altered description")) (should (equal (buffer-string) "[[file:file.org][altered description]]")))) +(defmacro test-org-link-search-asserter (remove-pipe-chars buffer-text heading-to-find) + "Asserter for `org-link-search' tests. +Create an org buffer with BUFFER-TEXT +and then converts HEADING-TO-FIND into +a search string with `org-link-heading-search-string'. +If REMOVE-PIPE-CHARS is non-nil, the search string +returned by `org-link-heading-search-string' +will not contain any pipe chars." + (let ((org-link-search-must-match-exact-headline nil) + (org-todo-regexp "TODO")) + `(org-test-with-temp-text ,buffer-text + (org-link-search (org-test-with-temp-text ,heading-to-find (if ,remove-pipe-chars (org-link-heading-search-string nil t) (org-link-heading-search-string)))) + (should (string-equal (buffer-substring-no-properties (point) (line-end-position)) ,heading-to-find))))) + +(ert-deftest test-org-link/exact-headline-match () + "First test for `org-link-search'. +Confirm that we can find an exact match for +a given heading search string." + (cl-flet ((test-org-link-search-basic (buffer-text heading-to-find) (test-org-link-search-asserter 'nil buffer-text heading-to-find))) + (test-org-link-search-basic "* Head1\n* Head2\n* Head3\n* [[Head2]]" "* Head2") + (test-org-link-search-basic "* Test 1 2 3\n** Test 1 2\n* [[*Test 1 2]]" "* [[*Test 1 2]]") + (let ((first-line + "*** TODO [#A] [/] Test [1/2] [33%] 1 \t 2 [%] :work:urgent: ")) + (test-org-link-search-basic + (concat "* Foo Bar\n** [[*Test 1 2]]\n" first-line) first-line) + (test-org-link-search-basic + (concat "* Foo Bar\n** [[*Test 1 2]]\n" first-line) "** [[*Test 1 2]]")))) + +(ert-deftest test-org-link/fuzzy-headline-match () + "Second test for `org-link-search'. +Confirm that we can find a match for +a heading when the heading search string does +not contain pipe chars even though +the original heading does." + (cl-flet ((test-org-link-search-replace-pipe-chars (buffer-text heading-to-find) (test-org-link-search-asserter t buffer-text heading-to-find))) + (test-org-link-search-replace-pipe-chars "* Head1\n* Head2\n* | Head3\n* [[Head2]]" "* | Head3") + (test-org-link-search-replace-pipe-chars "* Test 1 2 3\n** Test 1 | 2 |\n* [[*Test 1 2]]" "** Test 1 | 2 |") + (test-org-link-search-replace-pipe-chars "* DONE task \n* WAITING another task \n* [[file:/home/binarin/test.org::*A|B][A|B]]" "* [[file:/home/binarin/test.org::*A|B][A|B]]") + (let ((first-line + "*** TODO [#A] [/] Test [1/2] [33%] 1 | 2 [%] :work:urgent: ")) + (test-org-link-search-replace-pipe-chars + (concat "* Foo Bar\n** [[*Test 1 |2]]\n" first-line) first-line) + (test-org-link-search-replace-pipe-chars + (concat "* Foo Bar\n** [[*Test 1 2 |]]\n" first-line) "** [[*Test 1 2 |]]")))) + + (provide 'test-ol) ;;; test-ol.el ends here diff --git a/testing/lisp/test-org-clock.el b/testing/lisp/test-org-clock.el index befd0e3e3..4d218dc31 100644 --- a/testing/lisp/test-org-clock.el +++ b/testing/lisp/test-org-clock.el @@ -863,10 +863,26 @@ CLOCK: [2016-12-28 Wed 13:09]--[2016-12-28 Wed 15:09] => 2:00" CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" (test-org-clock-clocktable-contents ":maxlevel 1 :lang foo"))))) +(ert-deftest test-org-clock/clocktable/remove-pipe-chars () + "Confirm pipe chars are removed from headings before they are added to the Clock Table." + (should + (string-match-p "| Foo Bar +| 26:00 +|" + (org-test-with-temp-text + "* Foo | Bar +CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" + (test-org-clock-clocktable-contents ":block untilnow :indent nil")))) + (should + (string-match-p "| Foo Bar Baz +| 26:00 +|" + (org-test-with-temp-text + "* Foo | Bar | Baz +CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" + (test-org-clock-clocktable-contents ":block untilnow :indent nil"))))) + (ert-deftest test-org-clock/clocktable/link () "Test \":link\" parameter in Clock table." ;; If there is no file attached to the document, link directly to ;; the headline. + (should (string-match-p "| +\\[\\[\\*Foo]\\[Foo]] +| 26:00 +|" (org-test-with-temp-text @@ -878,13 +894,13 @@ CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" (string-match-p "| \\[\\[file:filename::\\*Foo]\\[Foo]] +| 26:00 +|" (org-test-with-temp-text - (org-test-with-temp-text-in-file - "* Foo + (org-test-with-temp-text-in-file + "* Foo CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" - (let ((file (buffer-file-name))) + (let ((file (buffer-file-name))) (replace-regexp-in-string (regexp-quote file) "filename" - (test-org-clock-clocktable-contents ":link t :lang en")))) + (test-org-clock-clocktable-contents ":link t :lang en")))) (org-table-align) (buffer-substring-no-properties (point-min) (point-max))))) ;; Ignore TODO keyword, priority cookie, COMMENT and tags in @@ -893,28 +909,28 @@ CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" (string-match-p "| \\[\\[\\*Foo]\\[Foo]] +| 26:00 +|" (org-test-with-temp-text - "* TODO Foo + "* TODO Foo CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" (test-org-clock-clocktable-contents ":link t :lang en")))) (should (string-match-p "| \\[\\[\\*Foo]\\[Foo]] +| 26:00 +|" (org-test-with-temp-text - "* [#A] Foo + "* [#A] Foo CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" (test-org-clock-clocktable-contents ":link t :lang en")))) (should (string-match-p "| \\[\\[\\*Foo]\\[Foo]] +| 26:00 +|" (org-test-with-temp-text - "* COMMENT Foo + "* COMMENT Foo CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" (test-org-clock-clocktable-contents ":link t")))) (should (string-match-p "| \\[\\[\\*Foo]\\[Foo]] +| 26:00 +|" (org-test-with-temp-text - "* Foo :tag: + "* Foo :tag: CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" (test-org-clock-clocktable-contents ":link t :lang en")))) ;; Remove statistics cookie from headline description. @@ -922,32 +938,95 @@ CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" (string-match-p "| \\[\\[\\*Foo]\\[Foo]] +| 26:00 +|" (org-test-with-temp-text - "* Foo [50%] + "* Foo [50%] CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" (test-org-clock-clocktable-contents ":link t :lang en")))) (should (string-match-p "| \\[\\[\\*Foo]\\[Foo]] +| 26:00 +|" (org-test-with-temp-text - "* Foo [1/2] + "* Foo [1/2] CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" (test-org-clock-clocktable-contents ":link t :lang en")))) ;; Replace links with their description, or turn them into plain ;; links if there is no description. (should (string-match-p - "| \\[\\[\\*Foo \\\\\\[\\\\\\[https://orgmode\\.org\\\\]\\\\\\[Org mode\\\\]\\\\]]\\[Foo Org mode]] +| 26:00 +|" + "| \\[\\[\\*Foo \\\\\\[\\\\\\[https://orgmode\\.org\\\\]\\\\\\[Org mode\\\\]\\\\]]\\[Foo \\[\\[https://orgmode\\.org]\\[Org mode]]]] | 26:00 +|" + (org-test-with-temp-text + "* Foo [[https://orgmode.org][Org mode]] +CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" + (test-org-clock-clocktable-contents ":link t :lang en")))) + (should + (string-match-p + "| \\[\\[\\*Foo \\\\\\[\\\\\\[https://orgmode\\.org\\\\]\\\\]]\\[Foo \\[\\[https://orgmode\\.org]]]] | 26:00 +|" (org-test-with-temp-text - "* Foo [[https://orgmode.org][Org mode]] + "* Foo [[https://orgmode.org]] CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" (test-org-clock-clocktable-contents ":link t :lang en")))) + ;; remove pipe characters before creating links (should (string-match-p - "| \\[\\[\\*Foo \\\\\\[\\\\\\[https://orgmode\\.org\\\\]\\\\]]\\[Foo https://orgmode\\.org]] +| 26:00 +|" + "| \\[\\[\\*Foo Bar]\\[Foo Bar]] +| 26:00 +|" (org-test-with-temp-text - "* Foo [[https://orgmode.org]] + "* Foo | Bar CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" - (test-org-clock-clocktable-contents ":link t :lang en"))))) + (test-org-clock-clocktable-contents ":link t :lang en")))) + (should + (string-match-p + "| \\[\\[\\*Foo <file:foo\\.org::\\*Heading with inside>]\\[Foo <file:foo\\.org::\\*Heading with inside>]] | 26:00 +|" + (org-test-with-temp-text + "* Foo <file:foo.org::*Heading with | inside> +CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" + (test-org-clock-clocktable-contents ":link t :lang en")))) + (should + (string-match-p + "| \\[\\[\\*\\\\\\[\\\\\\[file:/home/binarin/test\\.org::\\*A B\\\\]\\\\\\[A B\\\\]\\\\]]\\[\\[\\[file:/home/binarin/test\\.org::\\*A\\.\\.\\.]] | 26:00 +|" + (org-test-with-temp-text + "* [[file:/home/binarin/test.org::*A | B][A | B]] +CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" + (test-org-clock-clocktable-contents ":link t :lang en")))) + ;; Works in files as as well. + (should + (string-match-p + "| \\[\\[file:filename::\\*Foo Bar]\\[Foo Bar]] +| 26:00 +|" + (org-test-with-temp-text + (org-test-with-temp-text-in-file + "* Foo | Bar +CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" + (let ((file (buffer-file-name))) + (replace-regexp-in-string + (regexp-quote file) "filename" + (test-org-clock-clocktable-contents ":link t :lang en")))) + (org-table-align) + (buffer-substring-no-properties (point-min) (point-max))))) + (should + (string-match-p + "| \\[\\[file:filename::\\*Foo <file:foo\\.org::\\*Heading with inside>]\\[Foo <file:foo\\.org::\\*Heading with inside>]] | 26:00 +|" + (org-test-with-temp-text + (org-test-with-temp-text-in-file + "* Foo <file:foo.org::*Heading with | inside> +CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" + (let ((file (buffer-file-name))) + (replace-regexp-in-string + (regexp-quote file) "filename" + (test-org-clock-clocktable-contents ":link t :lang en")))) + (org-table-align) + (buffer-substring-no-properties (point-min) (point-max))))) + (should + (string-match-p + "| \\[\\[file:filename::\\*\\\\\\[\\\\\\[file:/home/binarin/test\\.org::\\*A B\\\\]\\\\\\[A B\\\\]\\\\]]\\[\\[\\[file:/home/binarin/test\\.org::\\*A\\.\\.\\.]] | 26:00 +|" + (org-test-with-temp-text + (org-test-with-temp-text-in-file + "* [[file:/home/binarin/test.org::*A | B][A | B]] +CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00" + (let ((file (buffer-file-name))) + (replace-regexp-in-string + (regexp-quote file) "filename" + (test-org-clock-clocktable-contents ":link t :lang en")))) + (org-table-align) + (buffer-substring-no-properties (point-min) (point-max)))))) + (ert-deftest test-org-clock/clocktable/compact () "Test \":compact\" parameter in Clock table." -- 2.54.0
