Monday, September 29, 2014

A few months ago I had read Evan Brook's post about using text editor colors for identifiers instead of keywords. I tried the two Emacs modes for this: rainbow-identifiers and color-identifiers-mode. I wrote up my thoughts back then, and that blog post led to some features being added to rainbow-identifiers. Pros and cons:

rainbow-identifierscolor-identifiers
colors are stable on reload/edit better than colors change on reload/edit
same name has the same color across buffers better than the same name has different colors across buffers
color assigned to every name worse than color assigned to important names
colors might be similar to each other worse than colors chosen to be distinguishable
universal configuration across modes better than separate configuration per mode

Neither is a clear win. Try both and see. I ended up using rainbow-identifiers. However, there were too many identifiers being colored, which meant there were too many colors, which meant I couldn't really distinguish things anymore. Here's what color-identifiers looks like:

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;
        }
    }
}

And here's what rainbow-identifiers looks like:

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;
        }
    }
}

You can see that color-identifiers picks out local variables and not all names. And rainbow-identifiers colors lots of things, but it misses the variable declaration, which is important to color. So I configured rainbow-identifiers to do this:

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;
        }
    }
}

It's in between the default configuration of the two packages. I highlight declarations in bold, and in color. I highlight local variables. I highlight local fields (this.x). But I don't highlight all names. Here's my configuration:

;; Customized filter: don't mark *all* identifiers
(defun amitp/rainbow-identifiers-filter (beg end)
  "Only highlight standalone words or those following 'this.' or 'self.'"
  (let ((curr-char (char-after beg))
        (prev-char (char-before beg))
        (prev-self (buffer-substring-no-properties
                    (max (point-min) (- beg 5)) beg)))
    (and (not (member curr-char 
                    '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ??)))
         (or (not (equal prev-char ?\.))
             (equal prev-self "self.")
             (equal prev-self "this.")))))

;; Filter: don't mark identifiers inside comments or strings
(setq rainbow-identifiers-faces-to-override
      '(font-lock-type-face
        font-lock-variable-name-face
        font-lock-function-name-face))

;; Set the filter
(add-hook 'rainbow-identifiers-filter-functions 'amitp/rainbow-identifiers-filter)

;; Use a wider set of colors
(setq rainbow-identifiers-choose-face-function
      'rainbow-identifiers-cie-l*a*b*-choose-face)
(setq rainbow-identifiers-cie-l*a*b*-lightness 45)
(setq rainbow-identifiers-cie-l*a*b*-saturation 45))

I also turned off colors for the font-lock faces, and made font-lock-variable-name-face bold. I still think it may be too many colors, so I may end up turning off rainbow-identifiers and trying some of the things Wilfred Hughes has suggested.

Labels: ,

2 comments:

Fanael wrote at Sunday, October 26, 2014 at 3:23:00 AM PDT

You know, that's why I don't have rainbow-identifiers turned on by default. As something you can toggle with a key combo? It's great. As something you have turned on at all times? Not a chance.

Artem Boytsov wrote at Friday, July 17, 2015 at 5:29:00 PM PDT

Amit, I made colors depend on identifier's hash in this pull request: https://github.com/ankurdave/color-identifiers-mode/pull/32

This is nice as colors are stable across buffers, as you and I would both like to see. I'm not sure about reload - but I looked at the code and at the very first glance, it seems like color-identifiers-mode generates colors deterministically.

I much prefer it to rainbow-identifiers, mostly because I'm doing my development in Clojure and it's smart enough to only count local identifiers in functions, which makes more sense to me.

Yours,
Artem.