From 118eb071b554d25448d6e1f968354718f9c0f65f Mon Sep 17 00:00:00 2001
From: Daanturo <daanturo@gmail.com>
Date: Sat, 15 Mar 2025 12:46:45 +0000
Subject: [PATCH] org-cite-basic-complete-key-crm-separator

---
 lisp/oc-basic.el | 73 ++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 64 insertions(+), 9 deletions(-)

diff --git a/lisp/oc-basic.el b/lisp/oc-basic.el
index 32a99e987..f00df0b78 100644
--- a/lisp/oc-basic.el
+++ b/lisp/oc-basic.el
@@ -140,6 +140,25 @@
   :type 'face
   :safe #'facep)
 
+(defcustom org-cite-basic-complete-key-crm-separator nil
+  "When non-nil, use `completing-read-multiple' with this as the separator.
+When nil: use traditional consecutive prompts.  When set to a
+string (regexp), carefully set craft a regexp that doesn't appear in the
+completion candidates.  When set to '`dynamic', automatically use \";\",
+or \";;\", \";;;\", etc. as needed."
+  :group 'org-cite
+  :package-version '(Org . "9.8")
+  :type
+  '(choice
+    (const :tag "Use \";;\" as the separator." "[ \t]*;;[ \t]*")
+    (const
+     :tag
+     "Dynamically compute \";\"+ with the needed length."
+     dynamic)
+    (string :tag "Custom regexp for the separator.")
+    (const :tag "Prompt multiple times." nil))
+  :safe (lambda (obj) (or (string-or-null-p obj) (member obj '(dynamic)))))
+
 
 ;;; Internal variables
 (defvar org-cite-basic--bibliography-cache nil
@@ -884,6 +903,24 @@ Return nil if there are no bibliography files or no entries."
         (puthash entries t org-cite-basic--completion-cache)
         org-cite-basic--completion-cache)))))
 
+(defun org-cite-basic--complete-key-dynamic-crm-separator
+    (completion-hash-table separator)
+  "Return a repeated version of SEPARATOR as needed that doesn't appear in COMPLETION-HASH-TABLE."
+  (let* ((cands (hash-table-keys completion-hash-table))
+         ;; Handle regexp separator?
+         (dyn-sep separator)
+         (consec-sep-regexp (format "\\(%s\\)+" separator)))
+    (with-temp-buffer
+      (dolist (cand cands)
+        (when (stringp cand)
+          ;; Handle multi-line candidates?
+          (insert cand "\n")))
+      (goto-char (point-min))
+      (while (re-search-forward consec-sep-regexp nil t)
+        (when (<= (length dyn-sep) (length (match-string 0)))
+          (setq dyn-sep (concat dyn-sep separator)))))
+    (format "[ \t]*%s[ \t]*" dyn-sep)))
+
 (defun org-cite-basic--complete-key (&optional multiple)
   "Prompt for a reference key and return a citation reference string.
 
@@ -895,12 +932,29 @@ Raise an error when no bibliography is set in the buffer."
   (let* ((table
           (or (org-cite-basic--key-completion-table)
               (user-error "No bibliography set")))
-         (prompt
-          (lambda (text)
-            (completing-read text table nil t))))
-    (if (null multiple)
-        (let ((key (gethash (funcall prompt "Key: ") table)))
-          (org-string-nw-p key))
+         (prompt-single
+          (lambda (text) (completing-read text table nil t)))
+         (choice-to-citation
+          (lambda (choice)
+            (let ((key (gethash choice table)))
+              (org-string-nw-p key)))))
+    (cond
+     ((null multiple)
+      (funcall choice-to-citation
+               (completing-read "Key: " table nil t)))
+     (org-cite-basic-complete-key-crm-separator
+      (dlet ((crm-separator
+              (cond
+               ((stringp org-cite-basic-complete-key-crm-separator)
+                org-cite-basic-complete-key-crm-separator)
+               ((equal
+                 'dynamic org-cite-basic-complete-key-crm-separator)
+                (org-cite-basic--complete-key-dynamic-crm-separator
+                 table ";")))))
+        (seq-keep
+         choice-to-citation
+         (completing-read-multiple "Keys: " table nil t))))
+     (t
       (let* ((keys nil)
              (build-prompt
               (lambda ()
@@ -908,11 +962,12 @@ Raise an error when no bibliography is set in the buffer."
                     (format "Key (empty input exits) %s: "
                             (mapconcat #'identity (reverse keys) ";"))
                   "Key (empty input exits): "))))
-        (let ((key (funcall prompt (funcall build-prompt))))
+        (let ((key (funcall prompt-single (funcall build-prompt))))
           (while (org-string-nw-p key)
             (push (gethash key table) keys)
-            (setq key (funcall prompt (funcall build-prompt)))))
-        keys))))
+            (setq key
+                  (funcall prompt-single (funcall build-prompt)))))
+        keys)))))
 
 
 ;;; Register processor
-- 
2.48.1

