Completion colours

,

In ‘Completion annotations’, I illustrated the interactive completion feature in Emacs with the screenshot you can see here, showing what happens if you type M-x set-cursor-color RET Dark TAB: you get a *Completions* buffer containing the colour names starting with ‘Dark’. Nick Barnes posted a comment pointing out a potential usability improvement:

It would be nice to have a bit more control still, so that (for instance) when completing on colour names one could see the colours.

If you read the section on Completion in the Emacs Lisp Reference Manual, then you’ll see that Emacs appears to have no support for implementing this feature. To display the colours, you need to modify the *Completions* buffer after the completion strings have been inserted and before it is displayed to the user, and Emacs provides no hook for adding behaviour at that point. But it turns out that you don’t need such a hook. Instead, just evaluate the following expression:

(dolist (c (defined-colors)) 
 (put-text-property 0 (length c) 'face `(:foreground ,c) c))

and then run any command that prompts you to pick a colour, for example read-color. See the screenshot for the results.

At this point, if you are used to the way strings work in most programming languages, you ought to be asking yourself, how does this work? Well, there are two parts to the explanation:

  1. Strings in Emacs are mutable data structures with the following representation (see lisp.h):

    struct GCALIGNED Lisp_String
      {
        ptrdiff_t size;
        ptrdiff_t size_byte;
        INTERVAL intervals; /* Text properties. */
        unsigned char *data;
      };
    

    Here intervals is a pointer to an interval tree, a data structure containing a set of intervals [start, end) that allows you to efficiently find all intervals overlapping with a given interval. See intervals.h. This data structure makes it possible to attach text properties—here, the face property—to any part of a string. Ordinary functions that operate on strings (length, string-equal, aref and so on) ignore the text properties, but when the string is inserted into a buffer, the text properties are copied into the buffer’s interval tree, and when the buffer is displayed in a window, Emacs’ display engine reads the buffer’s interval tree and updates the display accordingly.

  2. The function read-color calls defined-colors to get a list of completions. The latter returns a list of strings naming colours that are supported by the current frame, and on all windowing systems these strings are generated once and cached. For example, on OS X, Emacs calls NSColorList::availableColorLists once and stores the result in the x-colors variable. This means that if you edit these strings (for example, by adding text properties) then the edited strings are used on subsequent occasions when read-color is called.

So it’s a bit of a hack—if Emacs ever changed the caching behaviour of defined-colors then it would stop working. If that happened, then you’d have to resort to other techniques, such as advice.

But there’s a more immediate problem with this approach: on my white window background, colouring the names works well for dark colours, but not for light colours: LightCyan1 and LightGoldenrodYellow are effectively invisible on a white background. We’d like to use a light background for dark colour names and vice versa. Emacs comes with a handy (but undocumented) function color-distance:

(color-distance COLOR1 COLOR2 &optional FRAME)

Return an integer distance between COLOR1 and COLOR2 on FRAME. COLOR1 and COLOR2 may be either strings containing the color name, or lists of the form (RED GREEN BLUE). If FRAME is unspecified or nil, the current frame is used.

So given a foreground colour and a list of candidate background colours, we can pick the background colour that’s farthest from the foreground. Unlike in Python, where the sorting and maximization functions take a key function, Emacs Lisp doesn’t have any built-in function for picking the best item from a list according to a scoring function. The built-in max and the Common Lisp (loop ... maximize ...) only work if the item is its own score, so we need a helper function:

(require 'cl)

(cl-defun best (list &key (key #'identity) (predicate #'<))
  "Return the best element in LIST according to PREDICATE.
The function KEY gives the score of an element."
  (let* ((best-elt (first list))
         (best-key (funcall key best-elt)))
    (dolist (elt (rest list))
      (let ((k (funcall key elt)))
        (when (funcall predicate k best-key)
          (setq best-elt elt best-key k))))
    best-elt))

And then the choice of background colour is simple. (Well, simple-ish: I did find myself using apply-partially.) I’m not convinced that raw distance between colours is the perfect measure, but it’s good enough for this demonstration.

(require 'color)

(let ((backgrounds '("white" "black")))
  (dolist (c (defined-colors))
    (let* ((key (apply-partially #'color-distance c))
           (bg (best backgrounds :key key :predicate #'>))
           (face `(:foreground ,c :background ,bg)))
      (put-text-property 0 (length c) 'face face c)))