(or emacs irrelevant)

Extending completion-at-point for Org-mode

Intro

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)))))
  1. 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, return nil and let the other completion sources have a go.

  2. Next, it looks through the current buffer for each =foo= and =bar=, accumulating them into a list.

  3. 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.