Skip to Content

Emulating ligatures with concealing in vim

Ligatures are typographic eye-candy where a special glyph replaces a combination of two or more letters or signs.
Some ligatures have been used for a so long time they’ve entered the realm of symbols, for example the ampersand & is a ligature of the two letters in the Latin et (and).

It’s traditionally been used for natural language to mimic handwriting and facilitate the reading flow, but recently a different kind of ligatures has appeared.

For years, programming language designers have introduced makeshift mathematical notations that could be typed with the standard ASCII.

for example,
>= instead of
!= instead of

Some fontmakers have decided to create programming ligatures, ligatures that are designed to make such makeshift symbols pretty again.

However, ligature fonts don’t work everywhere, especially not in most of the terminals emulators (rxvt-unicode comes to mind), so here’s a way to emulate ligatures in terminal Vim (granted your terminal supports Unicode, but that is more frequent than support for ligature fonts).

Vim includes a conceal feature which allows you and plugin makers to use special markings in text while hiding them. A typical examples of use of concealing is to hide markdown markup and just show italicized or bold text.

Here’s how you can define your own conceals:

syntax match <conceal_name><text_to_replace>” conceal cchar=<replacement_char>

And here is a list of conceals for frequent mathematical symbols:

syntax match div "//" conceal cchar=÷
syntax match mul "*" conceal cchar=×
syntax match eq "==" conceal cchar=≣
syntax match neq "!=" conceal cchar=≠
syntax match gteq ">=" conceal cchar=≥
syntax match lteq "<=" conceal cchar=≤

You can put this in your vimrc if you want these to be used globally, but I’d recommend defining ligatures in filetype syntax files (eg: ~/.config/nvim/after/syntax/elm.vim)

Here’s that exact file for example, with elm-specific ligatures:

syntax match arrow "->" conceal cchar=→
syntax match rpipe "|>" conceal cchar=⊳
syntax match lpipe "<|" conceal cchar=⊲
syntax match rcomp ">>" conceal cchar=»
syntax match lcom "<<" conceal cchar=«
syntax match lambda "\\" conceal cchar=λ
syntax match cons "::" conceal cchar=∷
syntax match parse1 "|=" conceal cchar=⊧
syntax match parse2 "|." conceal cchar=⊦
syntax match neq "/=" conceal cchar=≠

You also need to enable the conceal feature and options, and the best place to do it is alongside the conceal definitions in your syntax file.
To enable, setlocal conceallevel= with a level ≥ 1. This will enable the concealing on every line that is not the current cursor line.

You can choose to also conceal on the cursor line, and do so with setlocal concealcursor= with the list of modes in which you want to conceal on the cursor line. Available modes are normal, visual, insert, and command.
For example, setlocal concealcursor=nvic will always conceal, whatever the mode. This is not really recommended as it makes editing the text more cryptic.

Pros and cons

This method has the advantage of working everywhere Unicode is supported, and you can define any “ligature” you want, your imagination is the limit! You also don’t need any special font with special features, it works with any so you can keep on using your favorite font.
…and that’s about it for the pros.

This method has many problems.
First, if you can conceal any number of characters, you can replace with only one.
This in turn highlights another problem: concealing messes with character alignment, as you are replacing multiple characters with one. There are ways to go around this limitation but this is a bit more involved and I won’t talk about it right now.

Another problem is readability. Those Unicode glyphs tend to be very tiny, it often becomes eye-straining and cancels the advantages you’d expect from having typographically correct characters. In fact, the Unicode consortium has stopped adding ligature glyphs to the Unicode standard, favoring their replacement with real typographic ligatures.

In conclusion, if you don’t have access to a ligatures-supporting terminal and if you use this method with parsimony, this can be a nice workaround as long as you chose your replacement glyphs carefully.
If you can use proper ligatures, and don’t mind switching font to do so, I encourage you to use proper ligatures.