diff --git a/README.org b/README.org index 2845805d..07156802 100644 --- a/README.org +++ b/README.org @@ -560,6 +560,7 @@ Simple links may also be written manually in either sexp or non-sexp form, like: + Command ~org-ql-sparse-tree~ accepts both string and sexp queries. (Thanks to [[https://github.com/akirak][Akira Komamura]].) *Fixed* ++ Predicate ~link~ matches links whose descriptions contain escaped brackets (changed in Org 9.3). (Thanks to [[https://github.com/exot][Daniel Borchmann]] for reporting.) + Predicate ~src~'s matching of begin/end block lines, normalization of arguments, and handling in non-sexp queries. (Thanks to [[https://github.com/akirak][Akira Komamura]] for reporting.) + Predicate ~src~'s behavior with various arguments. + Various compilation warnings. diff --git a/org-ql.el b/org-ql.el index acfe8c0c..a471f4e8 100644 --- a/org-ql.el +++ b/org-ql.el @@ -794,38 +794,43 @@ DESCRIPTION-OR-TARGET, match it in either description or target. If DESCRIPTION, match it in the description. If TARGET, match it in the target. If both DESCRIPTION and TARGET, match both, respectively." - (cl-labels - ((no-desc - (match) (rx-to-string `(seq (or bol (1+ blank)) - "[[" (0+ (not (any "]"))) (regexp ,match) (0+ (not (any "]"))) - "]]"))) - (match-both - (description target) - (rx-to-string `(seq (or bol (1+ blank)) - "[[" (0+ (not (any "]"))) (regexp ,target) (0+ (not (any "]"))) - "][" (0+ (not (any "]"))) (regexp ,description) (0+ (not (any "]"))) - "]]"))) - ;; Note that these actually allow empty descriptions - ;; or targets, depending on what they are matching. - (match-desc - (match) (rx-to-string `(seq (or bol (1+ blank)) - "[[" (0+ (not (any "]"))) - "][" (0+ (not (any "]"))) (regexp ,match) (0+ (not (any "]"))) - "]]"))) - (match-target - (match) (rx-to-string `(seq (or bol (1+ blank)) - "[[" (0+ (not (any "]"))) (regexp ,match) (0+ (not (any "]"))) - "][" (0+ (not (any "]"))) - "]]")))) - (cond (description-or-target - (rx-to-string `(or (regexp ,(no-desc description-or-target)) - (regexp ,(match-desc description-or-target)) - (regexp ,(match-target description-or-target))))) - ((and description target) - (match-both description target)) - (description (match-desc description)) - (target (rx-to-string `(or (regexp ,(no-desc target)) - (regexp ,(match-target target)))))))) + ;; This `rx' part is borrowed from `org-make-link-regexps'. It matches the interior of an + ;; Org link target (i.e. the parts between the brackets, including any escaped brackets). + (let ((link-target-part '(0+ (or (not (any "[]\\")) + (and "\\" (0+ "\\\\") (any "[]")) + (and (1+ "\\") (not (any "[]"))))))) + (cl-labels + ((no-desc + (match) (rx-to-string `(seq (or bol (1+ blank)) + "[[" ,link-target-part (regexp ,match) ,link-target-part + "]]"))) + (match-both + (description target) + (rx-to-string `(seq (or bol (1+ blank)) + "[[" ,link-target-part (regexp ,target) ,link-target-part + "][" (*? anything) (regexp ,description) (*? anything) + "]]"))) + ;; Note that these actually allow empty descriptions + ;; or targets, depending on what they are matching. + (match-desc + (match) (rx-to-string `(seq (or bol (1+ blank)) + "[[" ,link-target-part + "][" (*? anything) (regexp ,match) (*? anything) + "]]"))) + (match-target + (match) (rx-to-string `(seq (or bol (1+ blank)) + "[[" ,link-target-part (regexp ,match) ,link-target-part + "][" (*? anything) + "]]")))) + (cond (description-or-target + (rx-to-string `(or (regexp ,(no-desc description-or-target)) + (regexp ,(match-desc description-or-target)) + (regexp ,(match-target description-or-target))))) + ((and description target) + (match-both description target)) + (description (match-desc description)) + (target (rx-to-string `(or (regexp ,(no-desc target)) + (regexp ,(match-target target))))))))) (defun org-ql--format-src-block-regexp (&optional lang) "Return regexp equivalent to `org-babel-src-block-regexp' with LANG filled in." @@ -1563,12 +1568,8 @@ any link is found." ;; enabled nearly all of the time, in which case this function won't be called anyway, it's ;; probably not worth rewriting code all over the place to fix this. :preambles ((`(,predicate-names) - (list :regexp ;; Match a link with a target and optionally a description. - (rx (or bol (1+ blank)) - "[[" (1+ (not (any "]"))) "]" - (optional (seq "[" (0+ (not (any "]"))) "]")) - "]" - (or eol blank)))) + ;; Match a link with a target and optionally a description. + (list :regexp (org-ql--link-regexp :target ".*"))) (`(,predicate-names ,(and description-or-target (guard (not (keywordp description-or-target)))) . ,plist) diff --git a/org-ql.info b/org-ql.info index 08900a28..ce8d9817 100644 --- a/org-ql.info +++ b/org-ql.info @@ -1055,6 +1055,9 @@ File: README.info, Node: 07-pre, Next: 063, Up: Changelog (Thanks to Akira Komamura (https://github.com/akirak).) *Fixed* + • Predicate ‘link’ matches links whose descriptions contain escaped + brackets (changed in Org 9.3). (Thanks to Daniel Borchmann + (https://github.com/exot) for reporting.) • Predicate ‘src’’s matching of begin/end block lines, normalization of arguments, and handling in non-sexp queries. (Thanks to Akira Komamura (https://github.com/akirak) for reporting.) @@ -1735,35 +1738,35 @@ Node: Links37255 Node: Tips37942 Node: Changelog38266 Node: 07-pre39049 -Node: 06341784 -Node: 06242319 -Node: 06142624 -Node: 0643192 -Node: 05246246 -Node: 05146546 -Node: 0546969 -Node: 04948498 -Node: 04848778 -Node: 04749127 -Node: 04649536 -Node: 04549944 -Node: 04450305 -Node: 04350664 -Node: 04250867 -Node: 04151028 -Node: 0451275 -Node: 03255376 -Node: 03155779 -Node: 0355976 -Node: 02359276 -Node: 02259510 -Node: 02159790 -Node: 0259995 -Node: 0164073 -Node: Notes64174 -Node: Comparison with Org Agenda searches64336 -Node: org-sidebar65225 -Node: License65504 +Node: 06341973 +Node: 06242508 +Node: 06142813 +Node: 0643381 +Node: 05246435 +Node: 05146735 +Node: 0547158 +Node: 04948687 +Node: 04848967 +Node: 04749316 +Node: 04649725 +Node: 04550133 +Node: 04450494 +Node: 04350853 +Node: 04251056 +Node: 04151217 +Node: 0451464 +Node: 03255565 +Node: 03155968 +Node: 0356165 +Node: 02359465 +Node: 02259699 +Node: 02159979 +Node: 0260184 +Node: 0164262 +Node: Notes64363 +Node: Comparison with Org Agenda searches64525 +Node: org-sidebar65414 +Node: License65693  End Tag Table diff --git a/tests/data-links.org b/tests/data-links.org new file mode 100644 index 00000000..b1589518 --- /dev/null +++ b/tests/data-links.org @@ -0,0 +1,10 @@ +* Alpha + +Let us link to: [[id:74d357ac-fb9c-40d1-a63f-eca8a227321d][Bravo [a phrase in brackets]​]]. + +* Bravo [a phrase in brackets] +:PROPERTIES: +:ID: 74d357ac-fb9c-40d1-a63f-eca8a227321d +:END: + +* Charlie diff --git a/tests/test-org-ql.el b/tests/test-org-ql.el index e6fc9092..4e78f3a6 100644 --- a/tests/test-org-ql.el +++ b/tests/test-org-ql.el @@ -1141,7 +1141,39 @@ with keyword arg NOW in PLIST." '("/r/emacs"))) (org-ql-it "with :description and :target regexp" (org-ql-expect ('(link :description "em.cs" :target "em.cs" :regexp-p t)) - '("/r/emacs")))) + '("/r/emacs"))) + + (describe "matches links whose descriptions contain brackets" + (before-each + (setq org-ql-test-buffer (org-ql-test-data-buffer "data-links.org"))) + + (org-ql-it "without arguments" + (org-ql-expect ('(link)) + '("Alpha"))) + (org-ql-it "with description-or-target" + (org-ql-expect ('(link "phrase")) + '("Alpha"))) + (org-ql-it "with :description" + (org-ql-expect ('(link :description "phrase")) + '("Alpha"))) + (org-ql-it "with :target" + (org-ql-expect ('(link :target "id:")) + '("Alpha"))) + (org-ql-it "with :description and :target" + (org-ql-expect ('(link :description "phrase" :target "id")) + '("Alpha"))) + (org-ql-it "with description-or-target regexp" + (org-ql-expect ('(link "id:.*" :regexp-p t)) + '("Alpha"))) + (org-ql-it "with :description regexp" + (org-ql-expect ('(link :description "phr.se" :regexp-p t)) + '("Alpha"))) + (org-ql-it "with :target regexp" + (org-ql-expect ('(link :target "id:.*" :regexp-p t)) + '("Alpha"))) + (org-ql-it "with :description and :target regexp" + (org-ql-expect ('(link :description "phr.se" :target "id:.*" :regexp-p t)) + '("Alpha"))))) (describe "(outline-path)" (org-ql-it "with one argument"