Adding support for open-color in rainbow-mode

13 Jan 2024

Like many fellow software engineers, I'm bad at picking colors, so I was quite excited when I discovered open-color, less thinking to do ! Moreover, I use rainbow-mode once in a while, which allows to set the background of a string representing a color in a buffer to the corresponding color (i.e. "#ff0000" is displayed with a red background).

When I saw open-color had in its TODO list an item to add support for rainbow-mode, I wanted to give it a try.

Implementation

My original plan was to add support directly into rainbow-mode. But then I saw the package was offering a hook rainbow-keywords-hook for adding new keywords, my original plan changed to something less intrusive.

The work can be broken into 3 chunks.

The hook is fairly straightforward. To check if rainbow-mode is enabled, we can simply check with (if rainbow-mode ...), such that it boils down to

(defun add-open-color-hook()
   (if rainbow-mode
         (font-lock-add-keywords nil open-color-rainbow-font-lock-keywords 'end)
     (font-lock-remove-keywords nil open-color-rainbow-font-lock-keywords)
     )
   )

The only gotcha here is to put our keywords at the end, otherwise when coloring something like open-color-red-1, the sub expression red would be colored in red, which is not what we want.

Each element in the list of keywords open-color-rainbow-font-lock-keywords can have one of 6 forms, as described in font-lock-keywords documentation. I took direct inspiration from rainbow-html-rgb-colors-font-lock-keywords to come with the following function

(defvar open-color-rainbow-font-lock-keywords
       '(
         ("\\<\\(open-color-[a-z]*-[0-9]\\)\\>" 1 (rainbow-colorize-open-color) )
         ("\\<\\(open-color-[a-z]*\\)\\>" 1 (rainbow-colorize-open-color) ) ;; handle open-color-black
         )
       "Font-lock keywords to add for open-color colors.")

In broad terms, if something matches the regex, then first capture group is getting colorized by rainbow-colorize-open-color (which we have to define).

The hardest part (in my opinion) is that last function. its goal to retrieve the string that was matched by font-lock and return a face for it.

By looking on google, I found this StackOverflow question, where I learn about intern. That function returns the canonical symbol whose name is given, or creating one if any. From there, we can use symbol-value to read the content of the symbol (i.e. the actual value as defined by the open-color package). All that is left is to colorize the string using ainbow-colorize-match .

At the end, the colorization function looks like this

(defun rainbow-colorize-open-color()
  (let* (
         (color-name (match-string-no-properties 1))
         (color-object (intern color-name))
         )
    (if (boundp color-object)
        (let ((color-value (symbol-value color-object)))
          (rainbow-colorize-match color-value)
          )
      ))
  )

For some reasons, the regex doesn't always match the number at the end (such that color-name can be open-color-red), so we have to check if if the object is actually bound. If someone knows why, drop me a mail.

The result

two columns of texts, whose background is highlighted according to their name

The whole demo has been proposed in open-color README, but there has not been any activity on the PR since.

During the development, I found very useful to focus on quick (visual) feedback, and build gradually:



RSS Feed