I write a lot of things using Org-Mode. One function I have wanted is auto-completion of links that already exist in the current Org-Mode buffer. I have created custom links for abbreviations, epigraphs, dates, and glossary of terms.
I spent a bit of time writing that function. I remembered Org-Roam’s completion functions, so I started there for inspiration.
Writing Some Emacs Lisp
I looked to org-roam-complete-link-at-point for inspiration. I need a function that returns the text of the links. Along with the text, I would need the
jf/org-links-with-text, the function I wrote.
Here’s the link to jf/org-links-with-text.
(defun jf/org-links-with-text (&optional given-link) "Return the `distinct-' `org-mode' links in the `current-buffer'. Each element of the list will be a `propertize' string where the string value is the text of the link and the \"link\" property will be the :raw-link. When provided a GIVEN-LINK stop processing when we encounter the first matching link." (let ((links (org-element-map (org-element-parse-buffer) 'link (lambda (link) (when-let* ((left (org-element-property :contents-begin link)) (right (org-element-property :contents-end link))) (let ((returning (propertize (buffer-substring-no-properties left right) 'link (org-element-property :raw-link link)))) (if given-link (when (string= given-link returning) returning) returning)))) nil given-link))) ;; Ensure that we have a distinct list. (if (listp links) (-distinct links) (list links))))
The above loops through all
link elements. Assembling a propertized string with each link it encounters. When provided a
given-link it halts processing on the first match. And then returns a list of the matches.
I reference Org Element API when writing the function.
Here are some examples of the propertized string section of the code:
[[https://orgmode.org][Org-Mode]]then return the string
[[denote:20230506T202945][Title of Note]]then return the string
Title of Notewith a
In other words, the
CAPF function I’m developing will handle all Org-Mode style links.
With that function, I turned to the inspiration of the org-roam-complete-link-at-point. Below is the function I wrote. Here’s the link to jf/org-capf-links.
;; Cribbed from `org-roam' org-roam-complete-link-at-point (defun jf/org-capf-links () "Complete links." (when (and (thing-at-point 'symbol) (not (org-in-src-block-p)) (not (save-match-data (org-in-regexp org-link-any-re)))) ;; We want the symbol so that links such performing completion on ;; "org-mode" will look for links with the text of org-mode and ;; then replace the text "org-mode" with the returned link. (let ((bounds (bounds-of-thing-at-point 'symbol))) (list (car bounds) (cdr bounds) ;; Call without parameters, getting a links (filtered by CAPF ;; magic) (jf/org-links-with-text) :exit-function (lambda (text _status) ;; We want the properties of that link. In the case of one ;; match, the provided text will have the 'link property. ;; However if the (let ((link (car (jf/org-links-with-text text)))) (delete-char (- (length text))) (insert "[[" (get-text-property 0 'link link) "]" "[" text "]]"))) ;; Proceed with the next completion function if the returned ;; titles do not match. This allows the default Org capfs or ;; custom capfs of lower priority to run. :exclusive 'no))))
jf/org-capf-links function has three significant parts:
(when (and… section guards running in a context where things might get confusing.
(bounds-of-thing-at-point 'symbol) section checks the current item; I could use either
'symbol means I can complete or links that have dashes.
:exit-function, and this is where I spent significant time. In my first round of testing, I had a simple Org-Mode buffer that had one link. When I called the Completion at Point Function (CaPF 📖) function (via TAB) the
text parameter would have the propertized value.
However, when I had multiple candidates, and selected one, the
text parameter would not have the propertized value. Hence, I had to go and find again the property.
Last, I wire this into my Org-Mode. To test the functions prior, I had already done this. I use the Corfu and Cape packages. Below is the configuration for my Org-Mode CaPFs:
(defun jf/org-capf () "The `completion-at-point-functions' I envision using for `org-mode'." (setq-local completion-at-point-functions (list (cape-super-capf #'jf/org-capf-links #'tempel-expand #'cape-file)))) (add-hook ‘org-mode-hook #’jf/org-capf)
Given that I write between one thousand and four thousand words per day in Org-Mode and I do a lot of linking to code, glossaries, and external sites, I felt it worth the time and energy to write up a CaPF that could help reduce context shifting.
Now, when I write, I can use my TAB completion to provide link candidates to insert.