Thanks for this bug report! It was very well written and led me to the problem very quickly. Actually fixing the problem took a while and involved a lot of learning for me!
I've attached many patches which should fix everything and tests a couple things. Tests pass after each commit on Emacs 30. Test pass on the final commit on emacs 28, and 29. martin <[email protected]> writes: > 4. Enter some query that contains one or more hyphens, for example > TIMESTAMP_IA="<2025-09-18>" > > 5. Press C-a or otherwise place the cursor earlier in the minibuffer > than a hyphen, and the error message appears. I was able to reproduce this issue and I've added this scenario to a test. > So I looked into the code, and it appears that both > `org-tags-completion-function` and > `org-agenda-filter-completion-function` have this issue. Fixed in both places. > In each of the respective functions, the problem *seems* to be corrected > if I replace this line: > > (match-string 0 suffix) > > with this: > > (match-end 0) > > However, I'm not confident that I know enough about what's going on to > submit that change as a patch. You where very close! We actually want `match-beginning'. The boundry tells emacs that the item we are completing is within the boundary. So when completing something like: "tag1+tag2" with point at the start, we want to boundry to include "tag1", not "tag1+".
>From 6e76fc3d0f2bced13615b34add2425a421c15535 Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Mon, 27 Oct 2025 00:09:46 -0400 Subject: [PATCH 1/6] Testing: New test `test-org/org-tags-completion-function' * testing/lisp/test-org.el (org-test-with-minibuffer-setup): New macro copied from Emacs minibuffer tests. (test-org/org-tags-completion-function): New test. --- testing/lisp/test-org.el | 96 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el index 88c083def..ccfef01a5 100644 --- a/testing/lisp/test-org.el +++ b/testing/lisp/test-org.el @@ -9926,6 +9926,102 @@ test-org/create-math-formula (test-org/extract-mathml-math (org-create-math-formula "quote\" ; |")))))) +;; Copied from Emacs source code and prepended name with "org-test-" +;; test/lisp/minibuffer-tests.el +(defmacro org-test-with-minibuffer-setup (completing-read &rest body) + (declare (indent 1) (debug t)) + `(catch 'result + (minibuffer-with-setup-hook + (lambda () + (let ((redisplay-skip-initial-frame nil) + (executing-kbd-macro nil)) ; Don't skip redisplay + (throw 'result (progn . ,body)))) + (let ((executing-kbd-macro t)) ; Force the real minibuffer + ,completing-read)))) + +(ert-deftest test-org/org-tags-completion-function () + "Test completion with `org-tags-completion-function'." + ;; (wrong-type-argument number-or-marker-p "-") + :expected-result :failed + ;; To aid in debbugging try the following: + ;; (add-function :before (symbol-function 'kbd) #'message) + (let (messages + (dings 0)) + (cl-letf* (((symbol-function 'minibuffer-message) + (lambda (message &rest args) + (push (apply #'format-message message args) messages))) + ;; dinging cancels keyboard macros which is not helpful for these tests + ((symbol-function 'ding) + (lambda (&optional _arg) + (setq dings (+ 1 dings)))) + ((symbol-function 'test-messages) + (lambda (expected) + (should (equal messages expected)) + (setq messages nil)))) + (org-test-with-minibuffer-setup + (let ((org-last-tags-completion-table '(("test"))) org-tags-history) + (completing-read + "Match: " + 'org-tags-completion-function nil nil nil 'org-tags-history)) + (progn + (execute-kbd-macro (kbd "TIME TAB")) + (test-messages '("No match")) + (execute-kbd-macro (kbd "STAMP_IA=\"<2025- TAB")) + (should (equal (minibuffer-contents) "TIMESTAMP_IA=\"<2025-")) + (test-messages '("No match")) + (execute-kbd-macro (kbd "09-18>\" TAB")) + (test-messages '("No match")) + (execute-kbd-macro (kbd "C-a C-f TAB")) + (should (equal (minibuffer-contents) "TIMESTAMP_IA=\"<2025-09-18>\"")) + (test-messages '("No match")))) + (org-test-with-minibuffer-setup + (let ((org-last-tags-completion-table + '(("test") ("test2") ("uniq"))) + org-tags-history) + (completing-read + "Match: " + 'org-tags-completion-function nil nil nil 'org-tags-history)) + (progn + (setq messages nil) + (execute-kbd-macro (kbd "un TAB")) + (test-messages nil) + (should (equal (minibuffer-contents) "uniq")) + (execute-kbd-macro (kbd "TAB")) + (test-messages '("Sole completion")) + (execute-kbd-macro (kbd "+tes TAB")) + (test-messages nil) + (should (equal (minibuffer-contents) "uniq+test")) + (execute-kbd-macro (kbd "TAB")) + (test-messages '("Complete, but not unique")) + (should (equal (minibuffer-contents) "uniq+test")) + ;; Test the boundaries thoroughly. Ensure that completion + ;; acts the same regardless of point position within the + ;; boundary + (execute-kbd-macro (kbd "C-a TAB")) + (test-messages '("Sole completion")) + (execute-kbd-macro (kbd "C-a C-f TAB")) + (test-messages '("Sole completion")) + (execute-kbd-macro (kbd "C-a C-f C-f TAB")) + (test-messages '("Sole completion")) + (execute-kbd-macro (kbd "C-a C-f C-f C-f TAB")) + (test-messages '("Sole completion")) + (execute-kbd-macro (kbd "C-a C-f C-f C-f C-f TAB")) + (test-messages '("Sole completion")) + (should (equal (minibuffer-contents) "uniq+test")) + (execute-kbd-macro (kbd "C-a t| C-a TAB")) + (should (equal (minibuffer-contents) "test|uniq+test")) + (test-messages nil) + (execute-kbd-macro (kbd "C-a TAB")) + (test-messages '("Complete, but not unique")) + (execute-kbd-macro (kbd "C-a C-f TAB")) + (test-messages '("Complete, but not unique")) + (execute-kbd-macro (kbd "C-a C-f C-f TAB")) + (test-messages '("Complete, but not unique")) + (execute-kbd-macro (kbd "C-a C-f C-f C-f TAB")) + (test-messages '("Complete, but not unique")) + (execute-kbd-macro (kbd "C-a C-f C-f C-f C-f TAB")) + (test-messages '("Complete, but not unique"))))))) + (provide 'test-org) ;;; test-org.el ends here base-commit: df5628041dd2317f458e2903bc61fb985849c328 -- 2.51.0
>From 01ee76e609b8164e4b90fe7b98388091450d305f Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Fri, 31 Oct 2025 12:29:01 -0400 Subject: [PATCH 2/6] org-tags-completion-function: Return correct boundary * lisp/org.el (org-tags-completion-function): Previously a string was returned when the boundry should be a number. Now it is a number. * testing/lisp/test-org.el (org-test-with-minibuffer-setup): Update comment as test now fails for a different reason. Reported-by: "martin" <[email protected]> Link: https://list.orgmode.org/caofdpfwvy6ouzrz3qkn7aqlyvpgs3exx6qa-ftgfj1c7oak...@mail.gmail.com/ --- lisp/org.el | 2 +- testing/lisp/test-org.el | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lisp/org.el b/lisp/org.el index 6b8d02b87..16cdef14a 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -12284,7 +12284,7 @@ org-tags-completion-function (`lambda (assoc string org-last-tags-completion-table)) ;exact match? (`(boundaries . ,suffix) (let ((end (if (string-match "[-+:&,|]" suffix) - (match-string 0 suffix) + (match-beginning 0) (length suffix)))) `(boundaries ,(or begin 0) . ,end))) (`nil diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el index ccfef01a5..869c6584a 100644 --- a/testing/lisp/test-org.el +++ b/testing/lisp/test-org.el @@ -9941,7 +9941,8 @@ org-test-with-minibuffer-setup (ert-deftest test-org/org-tags-completion-function () "Test completion with `org-tags-completion-function'." - ;; (wrong-type-argument number-or-marker-p "-") + ;; Completes in unbalanced parenthesis. + ;; TIMESTAMP_IA="<2025- TAB adds a tag completion. :expected-result :failed ;; To aid in debbugging try the following: ;; (add-function :before (symbol-function 'kbd) #'message) -- 2.51.0
>From 9f294eb9e8e76aa73b0e7d46c6cc9d0c7c4f3811 Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Fri, 31 Oct 2025 12:33:53 -0400 Subject: [PATCH 3/6] org-tags-completion-function: Abort on unbalanced quotes * lisp/org.el (org-tags-completion-function): Abort on unbalanced quotes. * testing/lisp/test-org.el (org-test-with-minibuffer-setup): Test now passes. --- lisp/org.el | 10 ++++++++++ testing/lisp/test-org.el | 3 --- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lisp/org.el b/lisp/org.el index 16cdef14a..78d330aa7 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -12275,6 +12275,16 @@ org-tags-completion-function (confirm (lambda (x) (stringp (car x)))) (prefix "") begin) + ;; Abort if the string has unbalanced quotes + (let ((quotes 0)) + (mapc + (lambda (char) + (when (eq char ?\") + (setq quotes (1+ quotes)))) + string) + (when (and (< 0 quotes) + (not (eq (% quotes 2) 0))) + (setq flag 'invalid))) (when (string-match "^\\(.*[-+:&,|]\\)\\([^-+:&,|]*\\)$" string) (setq prefix (match-string 1 string)) (setq begin (match-beginning 2)) diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el index 869c6584a..33140d8a8 100644 --- a/testing/lisp/test-org.el +++ b/testing/lisp/test-org.el @@ -9941,9 +9941,6 @@ org-test-with-minibuffer-setup (ert-deftest test-org/org-tags-completion-function () "Test completion with `org-tags-completion-function'." - ;; Completes in unbalanced parenthesis. - ;; TIMESTAMP_IA="<2025- TAB adds a tag completion. - :expected-result :failed ;; To aid in debbugging try the following: ;; (add-function :before (symbol-function 'kbd) #'message) (let (messages -- 2.51.0
>From 8f8d5381f6619360672ac88997600565641b0350 Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Fri, 31 Oct 2025 12:34:25 -0400 Subject: [PATCH 4/6] org-tags-completion-function: Link to info page in docstring * lisp/org.el (org-tags-completion-function): Add link to info page. Also add FIXME to add property support. --- lisp/org.el | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lisp/org.el b/lisp/org.el index 78d330aa7..2cc137055 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -12268,9 +12268,15 @@ org-change-tag-in-region (defun org-tags-completion-function (string _predicate &optional flag) "Complete tag STRING. + +The format for tag string is described in the +Info node `(org) Matching tags and properties'. + FLAG specifies the type of completion operation to perform. This function is passed as a collection function to `completing-read', which see." + ;; FIXME: This function is used to complete a tag string which can + ;; include properties but does not know anything about properties (let ((completion-ignore-case nil) ;tags are case-sensitive (confirm (lambda (x) (stringp (car x)))) (prefix "") -- 2.51.0
>From 85f92d3318101fb9a1297bb62f8c05aae8895c86 Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Fri, 31 Oct 2025 13:25:13 -0400 Subject: [PATCH 5/6] org-agenda-filter-completion-function: Return correct boundary * lisp/org-agenda.el (org-agenda-filter-completion-function): Previously a string was returned when the boundry should be a number. Now it is a number. Reported-by: "martin" <[email protected]> Link: https://list.orgmode.org/caofdpfwvy6ouzrz3qkn7aqlyvpgs3exx6qa-ftgfj1c7oak...@mail.gmail.com/ --- lisp/org-agenda.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el index 3497a9763..c7b23b8c7 100644 --- a/lisp/org-agenda.el +++ b/lisp/org-agenda.el @@ -8237,7 +8237,7 @@ org-agenda-filter-completion-function (`lambda (assoc string table)) ;exact match? (`(boundaries . ,suffix) (let ((end (if (string-match "[-+<>=]" suffix) - (match-string 0 suffix) + (match-beginning 0) (length suffix)))) `(boundaries ,(or begin 0) . ,end))) (`nil -- 2.51.0
>From 5424694435e61273edb55a5d5830c0c0aca62f06 Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Fri, 31 Oct 2025 13:32:12 -0400 Subject: [PATCH 6/6] org-agenda-filter-completion-function: Add info page to docstring * lisp/org-agenda.el (org-agenda-filter-completion-function): Add link to info page. --- lisp/org-agenda.el | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el index c7b23b8c7..db3614e98 100644 --- a/lisp/org-agenda.el +++ b/lisp/org-agenda.el @@ -8206,6 +8206,9 @@ org-agenda-filter (defun org-agenda-filter-completion-function (string _predicate &optional flag) "Complete a complex filter string. + +See the Info Node `(org) Filtering/limiting agenda items'. + FLAG specifies the type of completion operation to perform. This function is passed as a collection function to `completing-read', which see." -- 2.51.0
