Wednesday, April 23, 2014

After reading Evan Brook's post about using text editor colors for identifiers instead of keywords, I decided to try the Emacs rainbow-identifiers and color-identifiers-mode. I find rainbow-identifiers easier to configure (it's language agnostic), and the color choices are stable while editing. I find color-identifiers to generate better output when reading code. It highlights only the local identifiers instead of all words, so the colors are more meaningful. Try both and see which works best for you.

Before, using colors for keywords:

var edges = this.graph.edges_from(this.curr);
for (var i = 0; i < edges.length; i++) {
    var next = edges[i];
    var edge_weight = this.graph.edge_weight(this.curr, next);
    if (edge_weight != Infinity) {
        this.neighbors.push(next);
        mark_changed(next);
        if (!this.visited[next]) {
            this.g[next] = this.g[this.curr] + edge_weight;
            this.open.push(next);
            this.parent[next] = this.curr;
            this.visited[next] = true;
        }
    }
}

After, using rainbow-colors mode:

var edges = this.graph.edges_from(this.curr);
for (var i = 0; i < edges.length; i++) {
    var next = edges[i];
    var edge_weight = this.graph.edge_weight(this.curr, next);
    if (edge_weight != Infinity) {
        this.neighbors.push(next);
        mark_changed(next);
        if (!this.visited[next]) {
            this.g[next] = this.g[this.curr] + edge_weight;
            this.open.push(next);
            this.parent[next] = this.curr;
            this.visited[next] = true;
        }
    }
}

After, using color-identifiers mode (notice how it uses colors for fewer identifiers, so they stand out better):

var edges = this.graph.edges_from(this.curr);
for (var i = 0; i < edges.length; i++) {
    var next = edges[i];
    var edge_weight = this.graph.edge_weight(this.curr, next);
    if (edge_weight != Infinity) {
        this.neighbors.push(next);
        mark_changed(next);
        if (!this.visited[next]) {
            this.g[next] = this.g[this.curr] + edge_weight;
            this.open.push(next);
            this.parent[next] = this.curr;
            this.visited[next] = true;
        }
    }
}

Here's how — I first turned off font-lock colors:

(set-face-attribute 'font-lock-comment-face nil :foreground nil :slant 'italic)
(set-face-attribute 'font-lock-comment-delimiter-face nil :slant 'italic)
(set-face-attribute 'font-lock-constant-face nil :foreground nil :underline t)
(set-face-attribute 'font-lock-type-face nil :foreground nil)
(set-face-attribute 'font-lock-function-name-face nil :foreground nil :weight 'bold)
(set-face-attribute 'font-lock-variable-name-face nil :foreground nil)
(set-face-attribute 'font-lock-keyword-face nil :foreground nil :slant 'italic)
(set-face-attribute 'font-lock-string-face nil :background "#e0e0e0")
(set-face-attribute 'font-lock-builtin-face nil :foreground nil :slant 'italic)

Second, I want colors chosen from L*a*b* color space because it makes the perceived brightness consistent across colors. It turns out color-identifiers mode already uses L*a*b* colors. In rainbow-identifiers mode, I configured it like this:

(loop for i from 1 to 15 do
      (let* ((lightness 45)
             (saturation 40)
             (angle (* 2 pi (/ i 15.0)))
             (a (* saturation (cos angle)))
             (b (* saturation (sin angle))))
        (set-face-attribute 
             (intern (format "rainbow-identifiers-identifier-%s" i))
             nil
             :foreground (apply 'color-rgb-to-hex
                             (color-lab-to-srgb lightness a b)))))

This generates these colors:

rainbow-identifiers-identifier-1
rainbow-identifiers-identifier-2
rainbow-identifiers-identifier-3
rainbow-identifiers-identifier-4
rainbow-identifiers-identifier-5
rainbow-identifiers-identifier-6
rainbow-identifiers-identifier-7
rainbow-identifiers-identifier-8
rainbow-identifiers-identifier-9
rainbow-identifiers-identifier-10
rainbow-identifiers-identifier-11
rainbow-identifiers-identifier-12
rainbow-identifiers-identifier-13
rainbow-identifiers-identifier-14
rainbow-identifiers-identifier-15

Pretty! You can change the color selection by changing the lightness and saturation constants. I'll try this for a while. Update: [2014-05-15] rainbow-identifiers 0.1.3 now has an option for using L*a*b* colors without needing the above code. Use (setq rainbow-identifiers-choose-face-function 'rainbow-identifiers-cie-l*a*b*-choose-face)

Edit: [2014-05-18] The original post was about rainbow-identifiers mode; I updated it to also show color-identifiers mode.

Labels:

9 comments:

snarfed.org wrote at Wednesday, April 23, 2014 at 4:48:00 PM PDT

oh man. i'm already a huge fan of color as meaningful UX, so i can't wait to try this. thanks for the post!

Nelson wrote at Sunday, May 4, 2014 at 8:15:00 AM PDT

Here's a forum thread about doing this in Sublime. The Colorcoder addon does this, although the sample is alarmingly rainbow colored and my brief attempt at using it was discouraging.

Evan wrote at Sunday, May 4, 2014 at 12:58:00 PM PDT

This is great, nice use of L*a*b*. Also into the italics, I'm working on a codemirror theme that uses uses italics as well (Consolas has such nice italics). What does the underline represent there?

One other thought: To make the color more distinct, you could try what d3.js does and use multiple shades of each hue. See demo here: http://jsfiddle.net/LqHst/5/

Amit wrote at Sunday, May 4, 2014 at 1:42:00 PM PDT

Nelson: interesting. It's unclear to me whether the number of distinct hues is going to be enough to cover the number of distinct identifiers. Evan's suggestion of multiple shades per hue is interesting but I haven't tried it yet. I had also seen a suggestion of using a space-filling curve through color space.

Evan: the underline is just a "constant" in some (but not all) emacs language modes. I sort of picked underline, bold, italic randomly and planned to go back and update once I had used this setup more. I like the d3 color schemes; I should try it.

Alex Schroeder wrote at Monday, May 5, 2014 at 5:55:00 AM PDT

rcirc colored nicks is another library that generates lots of colors for nicks in IRC. A while ago, I found a blog post on random foreground color generation with reference to the Web Content Accessibility Guidelines' contrast ratio ensurance procedure. Maybe it applies?

Amit wrote at Monday, May 5, 2014 at 8:53:00 AM PDT

Thanks Alex — yes, I installed rcirc colored nicks a long time ago and found it quite valuable. I think the foreground color generation is much simpler these days with the color.el package that's part of emacs. I haven't checked but I believe the luminance calculation from L*a*b* colors will work just as well as the one in that post.

Fanael wrote at Wednesday, May 14, 2014 at 12:19:00 PM PDT

Hi, I'm the author of rainbow-identifiers.

Can I include your color generation code as an option in rainbow-identifiers? It could be useful if somebody (e.g. me) wants to use as many colors as possible without creating too many faces, as Emacs seems to have some kind of performance problem when there are tons of faces defined.

Amit wrote at Wednesday, May 14, 2014 at 4:43:00 PM PDT

Hi Fanael, yes, please free to use this code for any purpose. And thank you for rainbow-identifiers!

Fanael wrote at Friday, May 16, 2014 at 9:51:00 AM PDT

And it's upstream.

Replicating the configuration in the post should now be a matter of setting a few variables:

(setq rainbow-identifiers-choose-face-function 'rainbow-identifiers-cie-l*a*b*-choose-face
rainbow-identifiers-cie-l*a*b*-lightness 45
rainbow-identifiers-cie-l*a*b*-saturation 40
rainbow-identifiers-cie-l*a*b*-color-count 15)