How do I get autocomplete for multiple entries in an org-mode capture template?

0

2

I'm using Emacs org-mode with capture templates extensively, but one thing is still irking me: I can't get autocomplete for multiple entries in prompts, except for tags. Considering the following capture template:

("m" "meeting" entry
   (file+datetree "~/Dropbox/org/meetings.org")
   "* %^{Description} %^G
   Time: %T
   %?
   ** Meeting info
   Place: %^{Place|Headquarters|Customer}
   Participants: %^{Participants|me|Tony|bossman}
   "
   :clock-in t)

For the tags (%^G), autocomplete for multiple entries works like a charm, but for Participants, autocomplete only works for a single entry, regardless if I separate them by : or ,

I would like to autocomplete the participant for each entry I separate by , or :

Even better would be if it built a dynamic autocomplete list for participants, like for that, so that I don't have to specify all possible participants in the capture template.

Fredrik Forséll

Posted 2015-10-09T06:50:51.560

Reputation: 35

Answers

0

Org-mode's capture templates include a %-escape for executing arbitrary elisp, which we can use to do practically anything. Here's how to use it to get what you want:

First, here are two functions that build a dynamic completion table. The first one just finds all matches for a specified regexp. The second uses the first to specifically get the sort of items you're looking for.

(defun my/dynamic-completion-table-regexp (regexp &optional group buffer)
  "Return alist containing all matches for REGEXP in BUFFER.

The returned alist contains the matches as keys, and each key is
associated with a nil value.

If the optional parameter GROUP (an integer) is supplied, only
that part matching the corresponding parenthetical subexpression
is taken as the key.

If BUFFER is omitted, it defaults to the current buffer.  Note
that the entire buffer is scanned, regardless of narrowing."
  (let ((buffer (or buffer (current-buffer)))
        (group  (or group 0))
        table match)
    (with-current-buffer buffer
      (save-excursion
        (save-restriction
          (widen)
          (goto-char (point-min))
          (while (re-search-forward regexp nil :noerror)
            (setq match (substring-no-properties (match-string group)))
            (add-to-list 'table (list match))))))
    table))

(defun my/dynamic-completion-table-by-field (field &optional delims buffer)
  "Return alist containing all entries for FIELD in BUFFER.

Look in BUFFER for all lines of the form \"FIELD: TEXT\",
possibly with leading or trailing whitespace.  If DELIMS is
omitted, consider TEXT as a single item; otherwise, it should be
a regexp matching the delimiters between the items inside TEXT.
Empty items are discarded, and whitespace surrounding the
delimiters is removed.

The returned alist contains one key for each item found; all keys
are associated with the value nil.

If BUFFER is omitted, it defaults to the current buffer.  Note
that the entire buffer is scanned, regardless of narrowing."
  (require 'cl-lib)                     ; For `cl-mapcan'
  (let ((table (my/dynamic-completion-table-regexp
                (concat "^\\s-*"
                        (regexp-quote field)
                        ": \\(.+\\)\\s-*$")
                1 buffer)))
    (cl-mapcan (lambda (x)
                 (mapcar #'list
                         (split-string (car x) delims :omit-nulls "\\s-+")))
               table)))

Now we need a function that reads from the minibuffer, knows how to use these functions, and uses Org-mode's tag completion to get the sort of per-item completion you're looking for. It also needs to be aware of the capture process, so it can scan the target buffer instead of the capture buffer.

(defun my/org-capture-read-multiple (field completion &optional delims)
  "Read one or more items for FIELD in capture template.

The COMPLETION parameter can be any sort of completion
table or function valid for the second parameter of
`completing-read'.  It may also be the keyword :dynamic, which
indicates that `my/dynamic-completion-table-by-field' should be
used to generate a completion table dynamically.

The optional parameter DELIMS is ignored except with the :dynamic
option, in which case it is passed to
`my/dynamic-completion-table-by-field' as the parameter of the
same name.

If this function is invoked from within an Org-mode capture
process, the current buffer will be the target buffer for the
capture attempt, not the buffer used to fill the capture
template."
  (let* ((buffer (if (equal (buffer-name) "*Capture*")
                     (org-capture-get :buffer)
                   (current-buffer)))
         (completion (if (eq completion :dynamic)
                         (my/dynamic-completion-table-by-field
                          field delims buffer)
                       completion)))
    (with-current-buffer buffer
      (let ((org-last-tags-completion-table completion)
            org-completion-use-ido)
        (org-completing-read-no-i (format "%s: " field)
                                  #'org-tags-completion-function)))))

To use these, you'll need to modify your template. For example, you could replace the "Participants" line in your template above with

Participants: %(my/org-capture-read-multiple \"Participants\" :dynamic \"[,:]\")

Aaron Harris

Posted 2015-10-09T06:50:51.560

Reputation: 246

Took me a while to get around to testing this, but oh my god it's amazing! This is why I love Superuser, thanks a lot! It doesn't allow autocomplete if I enter participants with a blank after the delimiter though, but never mind. – Fredrik Forséll – 2015-11-16T09:27:54.963