Colored and styled underlines

For most of the history of the terminal, the underline has been the least interesting attribute on the screen. It had one style, it was the same color as the text it sat under, and it mostly showed up in man pages and on the occasional link. Bold got the headlines. Reverse video got the cursor. The underline got the leftovers.

Then editors moved into the terminal for good, and the underline suddenly had a job. A language server wants to tell you that a variable is undefined, that a parameter is unused, and that a line could be simplified, all at once, and it wants each of those to look different. A spell checker wants to flag a word without changing its color. A diff tool wants to mark a changed span inside a line. Every graphical editor has done this for twenty years with a colored squiggle. The terminal had no way to draw one.

Kovid Goyal fixed that in kitty, and wrote it down so everyone else could copy it. His page, Colored and styled underlines, defines a small extension to the standard SGR sequence: a style sub-parameter on the underline attribute, and a separate underline color that works exactly like foreground and background colors do. It has since been adopted by most of the terminals people actually use, and by the editors that run inside them.

AbsoluteTelnet/SSH now supports it in full. This article covers what the extension is, what it looks like, and how to get your editor to use it.

The extension in one screen

That is the whole feature. Five underline styles, and any color you like under any of them, independent of the text color.

What the sequences look like

The classic underline is ESC [ 4 m. The kitty extension adds a colon-separated sub-parameter that picks a style:

ESC [ 4:0 m    no underline
ESC [ 4:1 m    straight
ESC [ 4:2 m    double
ESC [ 4:3 m    curly
ESC [ 4:4 m    dotted
ESC [ 4:5 m    dashed

Plain ESC [ 4 m still means straight and ESC [ 24 m still turns the underline off, so nothing that worked before stops working. A terminal that does not understand the sub-parameter is supposed to ignore the whole thing, which is why this was safe to roll out.

Underline color is its own attribute, number 58, and it takes the same arguments as the 38 and 48 codes for foreground and background:

ESC [ 58:2::R:G:B m    24-bit underline color
ESC [ 58:5:N m         256-color palette entry
ESC [ 59 m             back to the default (the text color)

The two halves are independent. You can set a color and then change the style, or set a style and then change the color, and the other half stays put. ESC [ 59 m clears the color but leaves the style. ESC [ 0 m clears everything, as it always has.

What AbsoluteTelnet does with them

All of it. Specifically:

  • All five styles, plus the explicit off. Curly is drawn as a proper wave, not a row of tildes.
  • 24-bit and 256-palette underline colors, in both the colon form Kovid’s page describes and the older semicolon form (ESC [ 58;2;R;G;B m) that some programs still emit.
  • The color is genuinely separate from the foreground. A red squiggle under white text stays red when the text turns bold, and a dotted line under a green string stays the color you asked for.
  • Styled underlines survive scrollback, selection, and copy the same as every other attribute.
  • The terminal answers XTGETTCAP queries for Su, Smulx, and Setulc, so a program that asks the terminal directly what it can do gets a truthful answer.

Getting your editor to use it

This is the part that trips people up, and it is not the terminal’s fault. Most editors decide whether to draw a curly underline by looking at the terminfo entry for your TERM, not by asking the terminal. The stock xterm-256color entry on a Linux box does not advertise the feature, so the editor plays it safe and draws a straight line.

The fix is a one-line terminfo addition on the server you are connecting to. It adds the Su capability that Kovid’s page describes and leaves everything else alone:

printf 'xterm-256color|xterm with styled underlines,\n\tSu,\n\tuse=xterm-256color,\n' | tic -x -

This writes a user-level entry under ~/.terminfo that overrides the system one for your account only. Make sure AbsoluteTelnet is set to send xterm-256color as its terminal type. That is the default for new connections.

Neovim needs nothing further. You can now turn on spell checking with “:set spell”!!! Once terminfo says Su, its diagnostics use curly underlines and the color from your colorscheme. Neovim does not query the terminal with XTGETTCAP for this, so the terminfo step is the one that matters.

Vim does not read Su and wants to be told explicitly. Add this to your .vimrc:

let &t_Cs = "\e[4:3m"
let &t_Ce = "\e[4:0m"
let &t_8u = "\e[58:2::%lu:%lu:%lum"

tmux passes styled underlines through to the outer terminal, but only if it knows the outer terminal supports them. Add these two lines to .tmux.conf:

set -as terminal-overrides ',*:Smulx=\E[4::%p1%dm'
set -as terminal-overrides ',*:Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m'

Helix, fish, and anything built on a recent notcurses or crossterm emit the sequences on their own and need no configuration.

Why we bothered

Underlines are a small feature. Nobody buys an SSH client for them. But the people who live in Neovim over SSH all day notice the difference between a squiggle that says “this is an error” and a flat line that says “something, somewhere, is being emphasized.” The whole point of running a real editor in a terminal is that it can show you what it knows. This is one more thing it can show you.

If you want to see the feature without setting up an editor, the script that produced the screenshot above is a single Python file that draws every style and color combination. Run it on any machine you are connected to and compare it with what your current terminal draws.

AbsoluteTelnet/SSH 14.15 is available now. Download it here. Existing customers with a current maintenance subscription get it as a free update.

Leave a Comment

Your email address will not be published. Required fields are marked *