Proportional fonts and yapf vs black

I normally frown on time spent customizing coding environments. But since I’m switching to VS.Code I figured it was time to also consider trying proportional fonts for coding again. It’s ridiculous how we still write code as if our I/O devices were line printers and ttys (or worse, literal punched cards.) Proportional fonts are certainly idiosyncratic for coding but there’s a growing community of folks doing it. GoLang gave it a lot of legitimacy; Rob Pike has been using proportional fonts for decades and who’s going to say he is wrong?

Here’s where I’ve landed so far. To get there I had to set up VS.Code to deal nicely with proportional fonts, then adopt a Python code formatter that put out code that looked good in my setup: YAPF, not Black.

VS.Code setup

First the VS.Code setup. This was mostly easy.

    "editor.fontSize": 18,
    "editor.fontFamily": "Tenorite",
    "terminal.integrated.fontSize": 18,
    "terminal.integrated.fontFamily": "Consolas",
    "editor.letterSpacing": 0.6,
"editor.tabSize": 8,

    "editor.formatOnSave": true,
    "python.formatting.provider": "yapf",
    "python.formatting.yapfArgs": [
        "--style",
        "/home/nelson/.yapf"
    ],

The first chunk sets the font. Note you have to set the terminal font to a fixed width font: by default it will inherit the editor fontFamily and proportional fonts do not work at all in a terminal window. They never will work well, too much Unix console UI assumes fixed width.

The rest of the settings are for making proportional fonts look nice for my Python code. Read below for details.

Font Choice

The nice thing about proportional fonts is you have so many to choose from. Want to program in Zapf Chancery? Go right ahead. (No, don’t do that). One thing to look out for is a few characters special to programming. You want 0/O and l/1/| to be distinguishable. The { braces} and [ brackets ] are also important and often overlooked in non-coding font design. Another thing to think about is the overall width of characters; many programmers want a narrow font to get more on the screen. I don’t.

I started with Calibri;. I like how it looks, it’s calm and innocuous. It’s not designed as a coding font but it’s not bad for the purpose. I’m currently experimenting with the five new Microsoft candidate fonts that fell off the back of a truck. They all look pretty good. None are designed as coding fonts but they all seem to work OK. The screenshot above is Tenorite.

I’d still rather use a proportional font designed for coding. There are precious few of those. Input is the standout and it is a very thoughtful font, including proportional options. I just don’t like the way Input Sans looks. Purely a subjective opinion. The Go Fonts are another good option but again don’t like how it looks, it’s a very opinionated design.

(PS: font installation still sucks in every OS. After you install new TTF fonts in Windows 10, close and reopen the Settings program or else it will hang / crash randomly. You also have to restart VS.Code to make it aware of newly installed fonts.)

Update: since taking the screenshot I added the letterSpacing. It’s a kerning kludge, to add 0.6 pixels of space between all letters. Text was just too tight, particularly things like the empty string ”. A single fixed value isn’t ideal for this but it helps.

Tabs vs Spaces

You can just change the fonts in VS.Code and start coding, things will mostly work. But the indentation isn’t awesome. The space in a proportional font is so narrow that the typical 4 space indent ends up being too narrow (and 2 spaces is impossible).

So I switched to tabs. Yeah, really; this is my embarrassed face. I’ve spent nearly two decades expunging tabs from all my code (except Makefiles and other monstrous systems that require them.) I had clearly chosen sides in programming’s stupidest controversy. But in the back of my head I always knew tabs were “theoretically” better; tab is a semantic character, saying “this is a new level of indentation”, and the fact it’s 2 or 4 spaces shouldn’t matter at all. The tab character is the way to express that logical structure. It’s also the practical way to make Visual Studio work, because it lets you customize the look of the indentation without changing the bytes in the file.

So the second block of my VS.Code setup is setting the tab size to the size of 8 spaces. That “looks right” to me in Tenorite. It happens to also be the standard. For Calibri 12 looks better to me. 12 would look terrible in a fixed width font but you make sure your output only ever has tabs (not 12 actual spaces) and it’s fine. Wouldn’t it be nice if this setting were in terms of an em-space or en-space instead of the tiny space character? In real typesetting spaces are variable width.

Code formatting: YAPF, not Black

Automatic code formatting pairs nicely with proportional fonts. You’ve given up the ability (or requirement) to vertically align blocks of comments and code and stuff. Might as well use a tool to just format everything for you. Again the Go community is a guide here; gofmt is a standard tool. The same principle works for Python too!

I switched to using Black a few days ago and loved having a formatter make choices for me. However Black is very serious about making specific choices and they are very clear they won’t support tabs. Nor anything but 4 spaces. That’s Black’s whole shtick and I kind of like it, right until I don’t.

So I switched to YAPF, Google’s formatter. It’s a whole lot like Black but it’s more configurable. Out of the box there are some slight differences I prefer (fewer vertical lines for braces, etc.) but it basically does PEP8 more or less like Black does. But YAPF invites configuration. I tried to use a light hand here, I really wanted to not do anything too personalized.

[style]
based_on_style = pep8

indent_width = 8
use_tabs = True
continuation_align_style = valign-right

column_limit = 9999

arithmetic_precedence_indication = True
each_dict_entry_on_separate_line = False

The second chunk is all about using tabs instead of spaces for indents. You have to tell it to use tabs twice; for most things, and then specifically for continuations (with the cryptic value valign-right). I’m not positive that the indent_width setting even means anything.

The column limit is.. I don’t know what’s right. 80 columns is very narrow, one nice thing about a proportional font is it typesets a good deal narrower than fixed width. 132 would be a natural choice with precedent. But I’m going to go to (near) infinite. We’ll see how that works out. 999 might be a better choice to catch pathological cases.

The last two entries are preference things for me, idiosyncrasies. I figured hell I’m customizing YAPF already, why not go whole hog!

(PS: if you want to understand what YAPF styles are, look at the --style-help output. The code for making styles is also interesting.)

Drawbacks

As I write this I just set this up today; I haven’t lived with it. We’ll see how it goes.

The primary drawback is interacting with other programmers. Folks would right be mad at me if I submit a 2 line patch to some code when 800 other lines changed reformatting the code. It’s always more important to follow an existing style! But right now I have the luxury of mostly working alone on my own code so I can experiment a bit.

The other drawback is interop. Much of the Unix world (and my own environment) is designed around monospace fonts and 2 or 4 character indents. Things like my .bashrc look weird in this new setup, at least until I convert them to tabs. It’ll never look right in a terminal window which has to be fixed width for most things to work right. I think writing code in an editor is the important use case, so I’m OK optimizing for it, but we’ll see.

I’m also a bit uneasy that this setup is so Python specific, relying on the code formatter. I guess most languages have formatters now though.

Update (Jan 2022)

This setup has worked great for me. I’ve basically used it unchanged since I posted this and am much happier. My code looks nice! The only real problem I’ve run into is YAPF has some bugs where it seems to reformat my code slightly incorrectly sometimes, something about comments and multiply nested blocks. It’s purely cosmetic and I mostly just ignore it.

Update (Nov 2023)

Still working with proportional fonts, still like it. Thanks to this discussion I now know a new term for right-aligned comments that don’t work with my setup. “Hanging comments”. There’s a nice writeup of a solution for this called elastic tabstops; basically you make the editor capable of tabbing comments to a second left-aligned column. Unfortunately there’s no plugin to do that in VS.Code with a proportional font, and the discussion in the feature request implies it’s not easy to add as a plugin.