Skip to Content

Tricking ligatures with conceal in vim

Last time I showed you how to emulate ligatures using unicode glyphs, then concluded by telling you against using this method! What was the point then?

Well, first of all, I did use this method for a couple weeks, because I used rxvt-unicode and didn’t want to switch to a graphical terminal emulator with menus, feature overload, etc.
Also, I used a font which I really much liked, but it’s one that does not support ligatures. (for the curious)

Sometime before that, I became aware of kitty, a (don’t laugh) GPU accelerated terminal emulator. It actually makes sense when you spend a lot of time in the terminal to have it use the resources available to be faster and more comfortable.
Anyway, some features were cool (integrated unicode input for one, scrollback buffer is nice too), it kept being quite minimalistic in its interface, and it’s blazing fast.
But it didn’t play well with italic and bold versions of my favorite font, and I’d have to port all my urxvt configuration. So I continued using urxvt.

But then, while writing that previous post, I noticed on the Fira Code repo that kitty was supported! (and yes, of course it is written in the very first paragraphs of kitty’s homepage, but it didn’t really register the first time I saw it).

So I gave kitty a second chance. The font rendering with Fira Code worked like a charm (some fonts ship with multiple versions, some don’t and their italic and bold versions have to be emulated by the terminal. It is to be believed that urxvt does it better than kitty, and the former still has many more font customization options than the latter).
I spent some time adapting the rest of my configuration, colors, etc., and voilà, a ligature supporting terminal!
Only con, I needed to get rid of my favorite font. There’s this tool called Ligaturizer that can add ligatures to any font. I gave it a quick try but didn’t manage to make it work right away, and decided that Fira Code was decent enough and that it wasn’t worth the time… maybe later.

Time to ditch all those conceals then, right?

Well… not exactly.

There are some single character conceals that maybe you’d like to keep. For example, I chose to keep the multiplication × and the integer division ÷. So only some cleanup of the conceals that are dealth with with the ligatures. I also added another one, transforming %in% into for R, as the Unicode glyph stays quite legible.

Then, what if you wanted to assign ligatures where they’re not supposed to be?
See, I used to conceal the R pipe %>% and the Elm pipe |> the same way, with an , and I’d like to continue doing so but now with the proper ligature .
Problem, the ligature glyph is not a single character, it’s a feature from the font, so you can’t use a simple conceal.
But there’s a solution! You can conceal the pipe in two parts: % is replaced by |, and >% by >. Add some restrictions so that % won’t be replaced by | everywhere, and you’re done.

syntax match pipe1 contained "%" conceal cchar=| containedin=pipe
syntax match pipe2 contained ">%" conceal cchar=> containedin=pipe
syntax match pipe "%>%" contains=pipe1,pipe2

Or maybe there’s a ligature you want replaced by another. For example, in Fira Code there are different ligatures for != () and for /= (). However, Elm uses /= as the operator for non equality, and again I’d like some consistency in the way my code appears. Here only one character needs to be replaced, but still you don’t want every / to be replace by !, so you can use the same trick as before. No need to conceal the = with itself, not all elements of the conceal need to be named.

syntax match neq1 contained "/" conceal cchar=! containedin=neq
syntax match neq "/=" contains=neq1

You can even disable some ligatures by replacing one of the characters with a similarly looking glyph that won’t trigger the ligature. For example the arrows can be disabled by replacing the - (Hyphen-minus) with a (Minus sign).

With this technique you can transform any sequence of characters into another, with one limitation: the replacement can’t to be longer than the initial sequence.

Short of patching the font, this is the best way I’ve found to control ligature rendering, and it has the benefit of allowing per-filetype customizations.