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.
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.
rainbow-mode
: Its job is to add / remove keywords with font-lock-{add-keywords/remove-keywords}
when the mode is enabled / disabled.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.
open-color-red-1
!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
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:
open-color-rainbow-font-lock-keywords
was highlighting the keyword "TEST" with with font-lock-warning-face
.