Using Vim's Conceal Feature to Make Code More Readable (For You)

Whenever someone sees my editor, this is always the first thing they ask about.

Vim (and Emacs) have features that let you conceal text with other text. The actual source text is not modified. If you put your cursor on the concealed line, the conceal goes away.

This is best illustrated by example. Here’s what the Python code normally looks like:

identity = lambda x: x

But here’s what I see:

identity = λ x: x

It’s really easy to abuse since no one has to view it but you, unlike code formatting. I ended up creating an entire mini-language, consistent across multiple programming languages.

The running examples throughout this blog post will be Python and Rust, because I’ve used those recently. Haskell is another language that benefits a lot from this.


By relieving the brain of all unnecessary work, a good notation sets it free to concentrate on more advanced problems, and in effect increases the mental power of the race.

So we start by unifying the building blocks of every language with prettier Unicode versions.

==          | ≝
!=          | ≠
<=          | ≤
>=          | ≥

and, &&     | ∧
or, ||      | ∨
not, !      | ¬

None        | ∅
true, false | ⊤, ⊥ (top and bottom from logic)

for         | ∀
in          | ∈
not in      | ∉

=           | ← (to remind me that equality is really assignment)
->          | → (that's an arrow replaced by a better arrow)

assert      | ‼

Types are concealed with their math symbols

bool        | 𝔹
char        | ∁
string, str | 𝐒

unsigned    | ℕ
int         | ℤ
float       | ℝ
complex     | ℂ

vector      | V
tensor      | 𝕋

This is nice because it makes reading code uniform across languages. Basic constructs like for .. in .. read like math.


Most of this category is for making ugly syntax bearable.

I read a lot of scientific code and Greek letters as variable names abound.

So I concealed the entire Greek alphabet, lower and uppercase.

I also don’t like looking at :: in C++ and Rust. Conceptually, it’s like a dot operator for the purposes of reading code, so I conceal it with ..

I also don’t like looking at semicolons, and my linter will tell me if I missed them, so they get concealed with 𐤟 (which is apparently a Phoenician word separator. It should look like a very faint dot, but may not render correctly in the browser. You can see I’ve scoured a lot of glyphs for this).

I hate the use of self in Python, so I concealed it with , the female sign emoji. It looks like a little person (a little self), and is a single character.

If I’m going to see a word a lot in a lot of contexts, it’s probably going to end up concealed.

Grammar of control flow

I have a system for conditionals and looping.

loop              | ∞ (`loop` is just `while True`)
while             | ⥁
continue          | ↻

match, if         | ▸
elif              | ▹
else              | ▪

break             | ◁
return            | ◀

def, fn, function | ※ (Japanese typography reference marker)
class             | §

The infinity and ouroboros symbols for loop and while feel intuitive.

The rest, not so much. I worked up this system late at night.

continue is a loop because it doesn’t break out of the loop (that’s why it loops clockwise since I always imagine looping going clockwise). It’s a broken ouroboros because it cuts off the current iteration.

The conditionals always start with an if, which is filled in, and just gets a pointer to indicate, “hey look at this condition”. Conditions have to end with an if or an else, but not an elif, so elif gets an empty pointer. else is indicated by the mathematical “end of proof” symbol, since the else ends the conditional. In this way, a conditional is only valid if it begins and ends with solid markers.

return takes you out of a function’s scope, and I think of going in scope as going rightwards, so return points left. Plus the right pointer was taken by if. break is similar but may not take you out of all scopes, just the innermost loop enclosing it.

I think the function conceal is pretty clear. The class conceal is because I think of classes like I think of sections in an essay, so they get a section marker.

Really Idiosyncratic

AKA mathy.

range  | ι
unsafe | ☡

struct | ∏
enum   | ∐

The iota is because that’s the one bit of APL I know.

unsafe comes from the “dangerous bend” symbol that Bourbaki used to indicate a tricky section in their books. That concession to user-friendliness ironically concealed a dangerous bend. Which was the fact that Bourbaki books don’t care about anyone being able to read them.

struct and enum are product and sum (AKA coproduct) types, so they get the corresponding symbols from category theory.


It lets me feel my code rather than read it. It makes seeing the shape of code easier at first pass, and I can see the forest for the trees somewhat better. But then I have to hunker down and edit it.


function! ToggleConcealLevel()
    if &conceallevel == 0
        setlocal conceallevel=2
        setlocal conceallevel=0

nnoremap <silent> <C-c><C-y> :call ToggleConcealLevel()<CR>

That toggles the conceal. Now other people can read over your shoulder. It’ll also spare you explanations of how you got the for to look like it does in math.


(Since so many of you asked)

Related Posts

I finally have an answer to "who's your favorite singer?"

My Top Tip for Helping People Get Started Programming


Random paper on angles

An Image is Worth 16x16 Words

Random stuff

Lossless Data Compression with Neural Networks by Fabrice Bellard

Downscaling Numerical Weather Models With GANs (My CI 2019 Paper)

Learning Differential Forms and Questions

PyTorch Lightning is worth using