Extending completion-at-point for Org-mode
04 Oct 2017Intro
When creating documents, context aware completion is a powerful mechanism that can help you improve the speed, correctness and discoverability.
Emacs provides context aware completion via the complete-symbol
command, bound to C-M-i by default. In order for it to do
something useful, completion-at-point-functions
has to be set up.
Documentation:
Special hook to find the completion table for the thing at point.
Each function on this hook is called in turn without any argument and should
return either nil to mean that it is not applicable at point,
or a list of the form (START END COLLECTION) where
START and END delimit the entity to complete and should include
point, COLLECTION is the completion table to use to complete it.
For each major-mode
, a different value of
completion-at-point-functions
can (and probably should) apply. One
of the modes that's set up nicely by default is emacs-lisp-mode
:
press C-M-i to get completion for Elisp variable and
function names. Org-mode, on the other hand, is quite lacking in this
regard: nothing useful happens with C-M-i.
Here's my current setting for Org-mode:
(setq completion-at-point-functions
'(org-completion-symbols
ora-cap-filesystem
org-completion-refs))
org-completion-symbols
When I write about code in Org-mode, I quote items like this:
=/home/oleh/=, =HammerFactoryFactory=, etc.
Quoting has several advantages:
- It looks nice, since it's in a different face,
flyspell
doesn't need to check it, which makes sense since it would fail on most variable and class names,- Prevents Org from confusing directory names for italics mark up.
Completion has one more advantage on top of that: if I refer to a symbol name multiple times within a document, completion helps me to enter it quickly and correctly. Here's the corresponding completion source:
(defun org-completion-symbols ()
(when (looking-back "=[a-zA-Z]+")
(let (cands)
(save-match-data
(save-excursion
(goto-char (point-min))
(while (re-search-forward "=\\([a-zA-Z]+\\)=" nil t)
(cl-pushnew
(match-string-no-properties 0) cands :test 'equal))
cands))
(when cands
(list (match-beginning 0) (match-end 0) cands)))))
First of all, it checks if the point is e.g. after
=A
, i.e. we are in fact entering a new quoted symbol. If that's not the case, returnnil
and let the other completion sources have a go.Next, it looks through the current buffer for each
=foo=
and=bar=
, accumulating them into a list.Finally, it returns the bounds of what we've got so far, plus the found candidates. It's important that the bounds are passed to the completion engine, so that it can delete everything inside the bounds before inserting the whole selected symbol.
org-cap-filesystem
This source is for completing file names:
(defun ora-cap-filesystem ()
(let (path)
(when (setq path (ffap-string-at-point))
(when (string-match "\\`file:\\(.*\\)\\'" path)
(setq path (match-string 1 path)))
(let ((compl (all-completions path #'read-file-name-internal)))
(when compl
(let* ((str (car compl))
(offset
(let ((i 0)
(len (length str)))
(while (and (< i len)
(equal (get-text-property i 'face str)
'completions-common-part))
(cl-incf i))
i)))
(list (- (point) offset) (point) compl)))))))
I usually enter ~
, so that ffap-string-at-point
recognizes it as a
path. Then complete each part of the path with C-M-i. It's
very similar to counsel-find-file
. In fact, I could just use
counsel-find-file
for this, with M-o i to insert the file
name instead of opening the selected file.
org-completion-refs
org-completion-refs
is very similar to org-completion-symbols
: it
will collect all instances of e.g. \label{foo}
, and offer them for
completion when you enter \ref{
. If you want to look at the code,
it's available in my config.
Outro
I hope I convinced you about the usefulness of completion at
point. It's especially cool since it's a universal interface for
major-mode
-specific completion. So any IDE-like package for any
language could provide its own completion using the familiar
interface. That could go a long way towards providing a "just works"
experience, particularly when dealing with a new language.