All tests pass after each commit on emacs 30.

All tests pass after final commit on emacs 28 and 29.

No new warnings during compilation or running the tests for all previously
mentioned runs.

Patches 1-6 are basically the same as previously submitted (with more concise
quote matching detection).

Patches 7-10 are new and exciting.  Now we complete all the things!
properties, tags, and even todo-keywords when it's appropriate.

Your suggestion to go read the part of the code where we parse the thing I'm
trying parse was a really helpful suggestion.  That's not sarcasm.  I'm an
idiot.  Rewriting those regex's also gave me a pretty good understanding of
what's going on.

All of the code changes I want to make are here but there are a couple more
documentation things that should be done.
- News entry
- Rename `org-tags-completion-function'.  What are we thinking?
  `org-tags-matcher-completion-table'?  `org-tags-matcher-completion-function'?

In the meantime feel free to apply any patches you feel good about.  We're
currently at 10 patches and I'll be adding at least 2 more.  I'm good with git
stuff so don't worry about me.


>From ef9b59775902989eb61b53a006e0d543f1b8480d Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Mon, 27 Oct 2025 00:09:46 -0400
Subject: [PATCH 01/10] 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 | 93 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 93 insertions(+)

diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index 90e506f50..eecaa140b 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -9962,6 +9962,99 @@ 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"))
+          (should (equal (minibuffer-contents) "test|uniq+test"))
+          (execute-kbd-macro (kbd "C-a C-f TAB"))
+          (execute-kbd-macro (kbd "C-a C-f C-f TAB"))
+          (execute-kbd-macro (kbd "C-a C-f C-f C-f TAB"))
+          (execute-kbd-macro (kbd "C-a C-f C-f C-f C-f TAB"))
+          (should (equal (minibuffer-contents) "test|uniq+test")))))))
+
 (provide 'test-org)
 
 ;;; test-org.el ends here

base-commit: 565459cf27470d8830fdf0e54bcf2a3b71ac513d
-- 
2.51.2

>From 4085d9f7fea3893abc486ee4c73de0178b4143f0 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Fri, 31 Oct 2025 12:29:01 -0400
Subject: [PATCH 02/10] 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 dcb1232c0..c5987a713 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -12276,7 +12276,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 eecaa140b..c25ae52ee 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -9977,7 +9977,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.2

>From 23a08007e52c38054a4b51a74e67932720de681b Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Fri, 31 Oct 2025 12:33:53 -0400
Subject: [PATCH 03/10] 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              | 3 +++
 testing/lisp/test-org.el | 3 ---
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/lisp/org.el b/lisp/org.el
index c5987a713..d8c20b94e 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -12267,6 +12267,9 @@ org-tags-completion-function
 	(confirm (lambda (x) (stringp (car x))))
 	(prefix "")
         begin)
+    ;; Abort if the string has unbalanced quotes
+    (unless (eq 0 (% (seq-count (lambda (char) (eq char ?\")) string) 2))
+      (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 c25ae52ee..fc9ca3ade 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -9977,9 +9977,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.2

>From 67f44a36cc8466a2375852735bdc8dc9d512c877 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Fri, 31 Oct 2025 12:34:25 -0400
Subject: [PATCH 04/10] 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 d8c20b94e..8ca5ecf6d 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -12260,9 +12260,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.2

>From 2dd7b513a7a782c92cbefad0a435bf7073a02ef0 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Fri, 31 Oct 2025 13:25:13 -0400
Subject: [PATCH 05/10] 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 0444d0d81..eb892f492 100644
--- a/lisp/org-agenda.el
+++ b/lisp/org-agenda.el
@@ -8235,7 +8235,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.2

>From 013a869c1d6fa4a8a0c32ac31bf86362e766ee25 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Fri, 31 Oct 2025 13:32:12 -0400
Subject: [PATCH 06/10] 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 eb892f492..74f1af514 100644
--- a/lisp/org-agenda.el
+++ b/lisp/org-agenda.el
@@ -8204,6 +8204,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.2

>From c2eade05d6f225263a5e4878b711c9702e300f73 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Fri, 7 Nov 2025 17:22:38 -0500
Subject: [PATCH 07/10] org-make-tags-matcher: Rewrite parsing regex using rx

* lisp/org.el (org-make-tags-matcher): Rewrite parsing regex using rx.
---
 lisp/org.el | 62 ++++++++++++++++++++++++++++-------------------------
 1 file changed, 33 insertions(+), 29 deletions(-)

diff --git a/lisp/org.el b/lisp/org.el
index 8ca5ecf6d..862285c5c 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -11720,38 +11720,42 @@ org-make-tags-matcher
 	     'org-tags-completion-function nil nil nil 'org-tags-history))))
 
   (let* ((match0 match)
-         (opre "[<=>]=?\\|[!/]=\\|<>")
-         (re (concat
-              "^"
+         (opre (rx (or (and (char "<=>") (? "=")) "!=" "/=" "<>")))
+         (re (rx
+              string-start
               ;; implicit AND operator (OR is done by global splitting)
-              "&?"
+              (? "&")
               ;; exclusion and inclusion (the latter being implicit)
-              "\\(?1:[-+:]\\)?"
+              (? (group-n 1 (char "-+:")))
               ;; query term
-              "\\(?2:"
-                  ;; tag regexp match
-                  "{[^}]+}\\|"
-                  ;; property match.  Try to keep this subre generic
-                  ;; and rather handle special properties like LEVEL
-                  ;; and CATEGORY further below.  This ensures that
-                  ;; the same quoting mechanics can be used for all
-                  ;; property names.
-                  "\\(?:"
-                      ;; property name [1]
-                      "\\(?5:\\(?:[[:alnum:]_]+\\|\\\\[^[:space:]]\\)+\\)"
-                      ;; operator, optionally starred
-                      "\\(?6:" opre "\\)\\(?7:\\*\\)?"
-                      ;; operand (regexp, double-quoted string,
-                      ;; number)
-                      "\\(?8:"
-                          "{[^}]+}\\|"
-                          "\"[^\"]*\"\\|"
-                          "-?[.0-9]+\\(?:[eE][-+]?[0-9]+\\)?"
-                      "\\)"
-                  "\\)\\|"
-                  ;; exact tag match
-                  org-tag-re
-              "\\)"))
+              (group-n 2
+                (or
+                 ;; tag regexp match
+                 (and "{" (+ (not "}")) "}")
+                 ;; property match.  Try to keep this subre generic
+                 ;; and rather handle special properties like LEVEL
+                 ;; and CATEGORY further below.  This ensures that
+                 ;; the same quoting mechanics can be used for all
+                 ;; property names.
+                 (and
+                  ;; property name [1]
+                  (group-n 5
+                    (+ (or alnum "_" (and "\\" (not space)))))
+                  ;; operator, optionally starred
+                  (group-n 6 (regexp opre))
+                  (? (group-n 7 "*"))
+                  ;; operand (regexp, double-quoted string,
+                  ;; number)
+                  (group-n 8
+                    (or
+                     (and "{" (+ (not "}")) "}")
+                     (and "\"" (* (not "\"")) "\"")
+
+                     (and (? "-") (+ (or "." digit))
+                          (? (char "eE") (? (char "-+"))
+                             (+ digit))))))
+                 ;; exact tag match
+                 (regexp org-tag-re)))))
          (start 0)
          tagsmatch todomatch tagsmatcher todomatcher)
 
-- 
2.51.2

>From e681a284df9fb7abdd031b40357f429f7124662b Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Fri, 7 Nov 2025 18:12:50 -0500
Subject: [PATCH 08/10] org-make-tags-matcher: Simplify parsing logic

* lisp/org.el (org-make-tags-matcher): Find the TODO section of the
tags matcher using just regex instead of a while loop with regex.
---
 lisp/org.el | 23 +++++++++--------------
 1 file changed, 9 insertions(+), 14 deletions(-)

diff --git a/lisp/org.el b/lisp/org.el
index 862285c5c..8fc493b49 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -11756,7 +11756,6 @@ org-make-tags-matcher
                              (+ digit))))))
                  ;; exact tag match
                  (regexp org-tag-re)))))
-         (start 0)
          tagsmatch todomatch tagsmatcher todomatcher)
 
     ;; [1] The history of this particular subre:
@@ -11779,20 +11778,16 @@ org-make-tags-matcher
     ;; Check if there is a TODO part of this match, which would be the
     ;; part after a "/".  To make sure that this slash is not part of
     ;; a property value to be matched against, we also check that
-    ;; there is no / after that slash.  First, find the last slash.
-    (let ((s 0))
-      (while (string-match "/+" match s)
-	(setq start (match-beginning 0))
-	(setq s (match-end 0))))
-    (if (and (string-match "/+" match start)
-	     (not (string-match-p "\"" match start)))
-	;; Match contains also a TODO-matching request.
+    ;; there is no / after that slash.
+    (if (string-match (rx (group-n 1 (+ "/")) (? (group-n 2 "!"))
+                          (* (not (char "/\""))) string-end)
+                      match)
+        ;; Match contains also a TODO-matching request.
 	(progn
-	  (setq tagsmatch (substring match 0 (match-beginning 0)))
-	  (setq todomatch (substring match (match-end 0)))
-	  (when (string-prefix-p "!" todomatch)
-	    (setq org--matcher-tags-todo-only t)
-	    (setq todomatch (substring todomatch 1)))
+	  (setq tagsmatch (substring match 0 (match-beginning 1)))
+	  (setq todomatch (substring match (or (match-end 2) (match-end 1))))
+	  (when (match-end 2) ;; Matched "!"
+	    (setq org--matcher-tags-todo-only t))
 	  (when (string-match "\\`\\s-*\\'" todomatch)
 	    (setq todomatch nil)))
       ;; Only matching tags.
-- 
2.51.2

>From 9e3e984527672386497424e81c5c7763f8606df3 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Sat, 8 Nov 2025 11:38:22 -0500
Subject: [PATCH 09/10] org-tags-completion-function: Add more completions

* lisp/org.el (org-tags-completion-function): Add support for
completing properties and todo-keywords.
* testing/lisp/test-org.el (test-org/org-tags-completion-function):
Adjust test.
---
 lisp/org.el              | 47 ++++++++++++++++++++++++----------------
 testing/lisp/test-org.el |  7 +++---
 2 files changed, 32 insertions(+), 22 deletions(-)

diff --git a/lisp/org.el b/lisp/org.el
index 8fc493b49..ab8a525ab 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -12266,37 +12266,46 @@ org-tags-completion-function
 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 "")
-        begin)
+  (defvar org-special-properties)
+  (let* ((completion-ignore-case nil)   ;tags are case-sensitive
+         (confirm (lambda (x) (stringp (car x))))
+         (prefix "")
+         (tags org-last-tags-completion-table)
+         (properties (mapcar #'list (cons "LEVEL" org-special-properties)))
+         (todo-keywords (mapcar #'list org-todo-keywords-for-agenda))
+         all-completions begin)
     ;; Abort if the string has unbalanced quotes
     (unless (eq 0 (% (seq-count (lambda (char) (eq char ?\")) string) 2))
       (setq flag 'invalid))
-    (when (string-match "^\\(.*[-+:&,|]\\)\\([^-+:&,|]*\\)$" string)
+    ;; If we are after an unquoted, unescaped "/" then we want to
+    ;; complete todo-keywords
+    (if (string-match (rx (or string-start (not "\\"))
+                          "/" (* (not "\"")) string-end)
+                      string)
+        (setq all-completions todo-keywords)
+      (setq all-completions (append tags properties)))
+    (when (string-match "^\\(.*[-+:&,|/]\\)\\([^-+:&,|/]*\\)$" string)
       (setq prefix (match-string 1 string))
       (setq begin (match-beginning 2))
       (setq string (match-string 2 string)))
     (pcase flag
-      (`t (all-completions string org-last-tags-completion-table confirm))
-      (`lambda (assoc string org-last-tags-completion-table)) ;exact match?
+      (`t (all-completions string all-completions confirm))
+      (`lambda (assoc string all-completions)) ;exact match?
       (`(boundaries . ,suffix)
-       (let ((end (if (string-match "[-+:&,|]" suffix)
+       (let ((end (if (string-match "[-+:&,|/]" suffix)
                       (match-beginning 0)
                     (length suffix))))
          `(boundaries ,(or begin 0) . ,end)))
       (`nil
-       (pcase (try-completion string org-last-tags-completion-table confirm)
-	 ((and completion (pred stringp))
-	  (concat prefix
-		  completion
-		  (if (and org-add-colon-after-tag-completion
-			   (assoc completion org-last-tags-completion-table))
-		      ":"
-		    "")))
-	 (completion completion)))
+       (pcase (try-completion string all-completions confirm)
+         ((and completion (pred stringp))
+          (concat prefix
+                  completion
+                  (if (and org-add-colon-after-tag-completion
+                           (assoc completion tags))
+                      ":"
+                    "")))
+         (completion completion)))
       (_ nil))))
 
 (defun org-fast-tag-insert (kwd tags face &optional end)
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index fc9ca3ade..4e517caf7 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -9999,15 +9999,16 @@ test-org/org-tags-completion-function
              '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"))
+          (test-messages nil)
+          (should (equal (minibuffer-contents) "TIMESTAMP"))
+          (execute-kbd-macro (kbd "_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"))))
+          (test-messages nil)))
       (org-test-with-minibuffer-setup
           (let ((org-last-tags-completion-table
                  '(("test") ("test2") ("uniq")))
-- 
2.51.2

>From 97bcfb7d2ec41f0da9865673e2b5873cf36f3fe4 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Sat, 8 Nov 2025 11:38:51 -0500
Subject: [PATCH 10/10] org-tags-completion-function: Group completions by type

* lisp/org.el (org-tags-completion-function): Add a
"completions-group" function.
---
 lisp/org.el | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/lisp/org.el b/lisp/org.el
index ab8a525ab..2b0ceb776 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -12296,6 +12296,17 @@ org-tags-completion-function
                       (match-beginning 0)
                     (length suffix))))
          `(boundaries ,(or begin 0) . ,end)))
+      (`metadata
+       `(metadata . ((group-function
+                      .
+                      ,(lambda (completion transform)
+                         (if transform completion
+                           (cond ((assoc completion tags)
+                                  "Tag")
+                                 ((assoc completion properties)
+                                  "Property")
+                                 ((assoc completion todo-keywords)
+                                  "TODO Keywords"))))))))
       (`nil
        (pcase (try-completion string all-completions confirm)
          ((and completion (pred stringp))
-- 
2.51.2

Reply via email to