Endless Parentheses

Ramblings on productivity and technical subjects.

profile for Malabarba on Stack Exchange

Using prettify-symbols in Clojure and Elisp without breaking indentation

prettify-symbols-mode is a very nice minor-mode that is a little too modest for its own good. You can turn it on right now if you’re using a recent Emacs, but it’ll do nothing more than turn lambda into λ in emacs-lisp-mode. Still, it’s powerful and versatile and deserves that you give it a try. To extend its feature-set you can install packages or customize it yourself, and that’s what we’re here to do today, specifically in clojure-mode.

First of all, let’s make sure it’s turned on.

(global-prettify-symbols-mode 1)
;; We're going to play with this below.
(defvar endless/clojure-prettify-alist '())

If you read the docstring for this mode, it’ll explain that any symbol can be displayed as any character, so the first thing that comes to my mind is displaying <= or >= as or . But that comes with a drawback. Suddenly that symbol is 1 character shorter, so Emacs is going to indent sexps accordingly, and people reading your code will see bad indentation.

The solution is to configure prettify-symbols-mode to compose these symbols in a special way. This feature is somewhat accidental, and wasn’t even documented in the last release. The composition rules are slightly complicated to write, and for that I’ll point to the docstrings of compose-region (see the third argument) and reference-point-alist. Below are several examples you can toy around with.

One way to fix the width, is to join two spaces together, and then stick the inequality on top of them.

(add-to-list 'endless/clojure-prettify-alist
             '(">=" . (?\s (Br . Bl) ?\s (Bc . Bc) ?)))
(add-to-list 'endless/clojure-prettify-alist
             '("<=" . (?\s (Br . Bl) ?\s (Bc . Bc) ?)))

However, I find this looks a little “too spacey”.
prettify-inequalities-2.png

The option I prefer is to just add a small dot before the symbol. This makes it clear that the symbol occupies the space of two characters, while still looking nicer than a plain <=.

(add-to-list 'endless/clojure-prettify-alist
             '("<=" . (?· (Br . Bl) ?)))
(add-to-list 'endless/clojure-prettify-alist
             '(">=" . (?· (Br . Bl) ?)))

Which will look like this:
prettify-inequalities-1.png

Then there are the -> and ->> macros, which are in dire need of a makeover if you ask me. The solution I currently use is a spaced-out version of 🠊 (you could also use , 🡒, or ).

(add-to-list 'endless/clojure-prettify-alist
             '("->" . (?\s (Br . Bl) ?\s (Bc . Bc) ?🠊)))
(add-to-list 'endless/clojure-prettify-alist
             '("->>" . (?\s (Br . Bl) ?\s (Br . Bl) ?\s
                            (Bc . Br) ?🠊 (Bc . Bl) ?🠊)))

Because the 🠊 character is wider than a regular character (at least on my font), this turns out look quite nice.
prettify-threading-2.png

If you don’t like that, there’s also the option of adding one or two dashes inside the symbols to make our fake arrow prettier.

(add-to-list 'endless/clojure-prettify-alist
             '("->" . (?- (Br . Bc) ?- (Br . Bc) ?>)))
(add-to-list 'endless/clojure-prettify-alist
             '("->>" .  (?\s (Br . Bl) ?\s (Br . Bl) ?\s
                             (Bl . Bl) ?- (Bc . Br) ?- (Bc . Bc) ?>
                             (Bc . Bl) ?- (Br . Br) ?>)))

Here’s what they look like with this, compared to what they usually look like.
prettify-threading-1.png

And finally, none of this will work if we don’t set it up. Note that clojure-mode already defines fn to display as λ, so we don’t need to configure this one.

(eval-after-load 'clojure-mode
  '(setq clojure--prettify-symbols-alist
         (append endless/clojure-prettify-alist
                 clojure--prettify-symbols-alist)))
(eval-after-load 'lisp-mode
  '(setq lisp--prettify-symbols-alist
         (append endless/clojure-prettify-alist
                 lisp--prettify-symbols-alist)))
comments powered by Disqus