feat(terminal): support SGR dim, overline attributes #37997

Problem:
libvterm doesn't support parsing the dim and overline attributes, so when a program running in the embedded terminal emits one of these escape codes, we ignore it and don't surface it to the outer terminal.

Solution: tweak libvterm to add support for both attributes.
This commit is contained in:
Riccardo Mazzarini
2026-02-27 12:46:22 +01:00
committed by GitHub
parent 5cbb9d613b
commit 2368a9edbd
8 changed files with 123 additions and 3 deletions

View File

@@ -1405,8 +1405,10 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int *te
bool bg_set = vt_bg_idx && vt_bg_idx <= 16 && term->color_set[vt_bg_idx - 1];
int hl_attrs = (cell.attrs.bold ? HL_BOLD : 0)
| (cell.attrs.dim ? HL_DIM : 0)
| (cell.attrs.blink ? HL_BLINK : 0)
| (cell.attrs.conceal ? HL_CONCEALED : 0)
| (cell.attrs.overline ? HL_OVERLINE : 0)
| (cell.attrs.italic ? HL_ITALIC : 0)
| (cell.attrs.reverse ? HL_INVERSE : 0)
| get_underline_hl_flag(cell.attrs)

View File

@@ -182,6 +182,8 @@ void vterm_state_resetpen(VTermState *state)
state->pen.font = 0; setpenattr_int(state, VTERM_ATTR_FONT, 0);
state->pen.small = 0; setpenattr_bool(state, VTERM_ATTR_SMALL, 0);
state->pen.baseline = 0; setpenattr_int(state, VTERM_ATTR_BASELINE, 0);
state->pen.dim = 0; setpenattr_bool(state, VTERM_ATTR_DIM, 0);
state->pen.overline = 0; setpenattr_bool(state, VTERM_ATTR_OVERLINE, 0);
state->pen.fg = state->default_fg;
setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg);
@@ -208,6 +210,8 @@ void vterm_state_savepen(VTermState *state, int save)
setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font);
setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small);
setpenattr_int(state, VTERM_ATTR_BASELINE, state->pen.baseline);
setpenattr_bool(state, VTERM_ATTR_DIM, state->pen.dim);
setpenattr_bool(state, VTERM_ATTR_OVERLINE, state->pen.overline);
setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
@@ -285,6 +289,11 @@ void vterm_state_setpen(VTermState *state, const long args[], int argcount)
break;
}
case 2: // Dim/faint on
state->pen.dim = 1;
setpenattr_bool(state, VTERM_ATTR_DIM, 1);
break;
case 3: // Italic on
state->pen.italic = 1;
setpenattr_bool(state, VTERM_ATTR_ITALIC, 1);
@@ -351,9 +360,11 @@ void vterm_state_setpen(VTermState *state, const long args[], int argcount)
setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);
break;
case 22: // Bold off
case 22: // Normal intensity (bold and dim off)
state->pen.bold = 0;
setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
state->pen.dim = 0;
setpenattr_bool(state, VTERM_ATTR_DIM, 0);
break;
case 23: // Italic and Gothic (currently unsupported) off
@@ -441,6 +452,16 @@ void vterm_state_setpen(VTermState *state, const long args[], int argcount)
setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
break;
case 53: // Overline on
state->pen.overline = 1;
setpenattr_bool(state, VTERM_ATTR_OVERLINE, 1);
break;
case 55: // Overline off
state->pen.overline = 0;
setpenattr_bool(state, VTERM_ATTR_OVERLINE, 0);
break;
case 73: // Superscript
case 74: // Subscript
case 75: // Superscript/subscript off
@@ -528,6 +549,10 @@ int vterm_state_getpen(VTermState *state, long args[], int argcount)
args[argi++] = 1;
}
if (state->pen.dim) {
args[argi++] = 2;
}
if (state->pen.italic) {
args[argi++] = 3;
}
@@ -567,6 +592,10 @@ int vterm_state_getpen(VTermState *state, long args[], int argcount)
argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false);
if (state->pen.overline) {
args[argi++] = 53;
}
if (state->pen.small) {
if (state->pen.baseline == VTERM_BASELINE_RAISE) {
args[argi++] = 73;
@@ -630,6 +659,12 @@ int vterm_state_set_penattr(VTermState *state, VTermAttr attr, VTermValueType ty
case VTERM_ATTR_URI:
state->pen.uri = val->number;
break;
case VTERM_ATTR_DIM:
state->pen.dim = (unsigned)val->boolean;
break;
case VTERM_ATTR_OVERLINE:
state->pen.overline = (unsigned)val->boolean;
break;
default:
return 0;
}

View File

@@ -399,6 +399,12 @@ static int setpenattr(VTermAttr attr, VTermValue *val, void *user)
case VTERM_ATTR_URI:
screen->pen.uri = val->number;
return 1;
case VTERM_ATTR_DIM:
screen->pen.dim = (unsigned)val->boolean;
return 1;
case VTERM_ATTR_OVERLINE:
screen->pen.overline = (unsigned)val->boolean;
return 1;
case VTERM_N_ATTRS:
return 0;
@@ -670,6 +676,8 @@ static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new
dst->pen.font = src->attrs.font;
dst->pen.small = src->attrs.small;
dst->pen.baseline = src->attrs.baseline;
dst->pen.dim = src->attrs.dim;
dst->pen.overline = src->attrs.overline;
dst->pen.fg = src->fg;
dst->pen.bg = src->bg;
@@ -931,6 +939,8 @@ int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCe
cell->attrs.font = intcell->pen.font;
cell->attrs.small = intcell->pen.small;
cell->attrs.baseline = intcell->pen.baseline;
cell->attrs.dim = intcell->pen.dim;
cell->attrs.overline = intcell->pen.overline;
cell->attrs.dwl = intcell->pen.dwl;
cell->attrs.dhl = intcell->pen.dhl;

View File

@@ -260,6 +260,10 @@ VTermValueType vterm_get_attr_type(VTermAttr attr)
return VTERM_VALUETYPE_INT;
case VTERM_ATTR_URI:
return VTERM_VALUETYPE_INT;
case VTERM_ATTR_DIM:
return VTERM_VALUETYPE_BOOL;
case VTERM_ATTR_OVERLINE:
return VTERM_VALUETYPE_BOOL;
case VTERM_N_ATTRS:
return 0;

View File

@@ -65,6 +65,8 @@ typedef struct {
unsigned dhl : 2; // On a DECDHL line (1=top 2=bottom)
unsigned small : 1;
unsigned baseline : 2;
unsigned dim : 1;
unsigned overline : 1;
} VTermScreenCellAttrs;
typedef struct {
@@ -158,8 +160,10 @@ typedef enum {
VTERM_ATTR_SMALL_MASK = 1 << 10,
VTERM_ATTR_BASELINE_MASK = 1 << 11,
VTERM_ATTR_URI_MASK = 1 << 12,
VTERM_ATTR_DIM_MASK = 1 << 13,
VTERM_ATTR_OVERLINE_MASK = 1 << 14,
VTERM_ALL_ATTRS_MASK = (1 << 13) - 1,
VTERM_ALL_ATTRS_MASK = (1 << 15) - 1,
} VTermAttrMask;
typedef enum {
@@ -187,6 +191,8 @@ typedef enum {
VTERM_ATTR_SMALL, // bool: 73, 74, 75
VTERM_ATTR_BASELINE, // number: 73, 74, 75
VTERM_ATTR_URI, // number
VTERM_ATTR_DIM, // bool: 2, 22
VTERM_ATTR_OVERLINE, // bool: 53, 55
VTERM_N_ATTRS,
} VTermAttr;
@@ -314,6 +320,8 @@ typedef struct {
unsigned font : 4; // 0 to 9
unsigned small : 1;
unsigned baseline : 2;
unsigned dim : 1;
unsigned overline : 1;
// Extra state storage that isn't strictly pen-related
unsigned protected_cell : 1;

View File

@@ -51,6 +51,8 @@ struct VTermPen {
unsigned font:4; // To store 0-9
unsigned small:1;
unsigned baseline:2;
unsigned dim:1;
unsigned overline:1;
};
// https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement

View File

@@ -423,6 +423,8 @@ struct {
int font;
int small;
int baseline;
int dim;
int overline;
VTermColor foreground;
VTermColor background;
} state_pen;
@@ -460,6 +462,12 @@ int state_setpenattr(VTermAttr attr, VTermValue *val, void *user)
case VTERM_ATTR_BASELINE:
state_pen.baseline = val->number;
break;
case VTERM_ATTR_DIM:
state_pen.dim = val->boolean;
break;
case VTERM_ATTR_OVERLINE:
state_pen.overline = val->boolean;
break;
case VTERM_ATTR_FOREGROUND:
state_pen.foreground = val->color;
break;
@@ -615,6 +623,14 @@ int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue
val->number = state->pen.uri;
return 1;
case VTERM_ATTR_DIM:
val->boolean = state->pen.dim;
return 1;
case VTERM_ATTR_OVERLINE:
val->boolean = state->pen.overline;
return 1;
case VTERM_N_ATTRS:
return 0;
}
@@ -663,6 +679,12 @@ static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b)
if ((attrs & VTERM_ATTR_URI_MASK) && (a->pen.uri != b->pen.uri)) {
return 1;
}
if ((attrs & VTERM_ATTR_DIM_MASK) && (a->pen.dim != b->pen.dim)) {
return 1;
}
if ((attrs & VTERM_ATTR_OVERLINE_MASK) && (a->pen.overline != b->pen.overline)) {
return 1;
}
return 0;
}

View File

@@ -285,7 +285,8 @@ local function lineinfo(row, expected, state)
end
local function pen(attribute, expected, state)
local is_bool = { bold = true, italic = true, blink = true, reverse = true }
local is_bool =
{ bold = true, italic = true, blink = true, reverse = true, dim = true, overline = true }
local vterm_attribute = {
bold = vterm.VTERM_ATTR_BOLD,
underline = vterm.VTERM_ATTR_UNDERLINE,
@@ -293,6 +294,8 @@ local function pen(attribute, expected, state)
blink = vterm.VTERM_ATTR_BLINK,
reverse = vterm.VTERM_ATTR_REVERSE,
font = vterm.VTERM_ATTR_FONT,
dim = vterm.VTERM_ATTR_DIM,
overline = vterm.VTERM_ATTR_OVERLINE,
}
local val = t.ffi.new('VTermValue') --- @type {boolean: integer}
@@ -386,11 +389,13 @@ local function screen_cell(row, col, expected, screen)
end
actual = string.format('%s} width=%d attrs={', actual, cell['width'])
actual = actual .. (cell['attrs'].bold ~= 0 and 'B' or '')
actual = actual .. (cell['attrs'].dim ~= 0 and 'D' or '')
actual = actual
.. (cell['attrs'].underline ~= 0 and string.format('U%d', cell['attrs'].underline) or '')
actual = actual .. (cell['attrs'].italic ~= 0 and 'I' or '')
actual = actual .. (cell['attrs'].blink ~= 0 and 'K' or '')
actual = actual .. (cell['attrs'].reverse ~= 0 and 'R' or '')
actual = actual .. (cell['attrs'].overline ~= 0 and 'O' or '')
actual = actual .. (cell['attrs'].font ~= 0 and string.format('F%d', cell['attrs'].font) or '')
actual = actual .. (cell['attrs'].small ~= 0 and 'S' or '')
if cell['attrs'].baseline ~= 0 then
@@ -2950,6 +2955,30 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]])
push('\x1b[11m\x1b[m', vt)
pen('font', 0, state)
-- Dim
push('\x1b[2m', vt)
pen('dim', true, state)
push('\x1b[22m', vt)
pen('dim', false, state)
push('\x1b[2m\x1b[m', vt)
pen('dim', false, state)
-- Bold and Dim interaction (SGR 22 turns off both)
push('\x1b[1;2m', vt)
pen('bold', true, state)
pen('dim', true, state)
push('\x1b[22m', vt)
pen('bold', false, state)
pen('dim', false, state)
-- Overline
push('\x1b[53m', vt)
pen('overline', true, state)
push('\x1b[55m', vt)
pen('overline', false, state)
push('\x1b[53m\x1b[m', vt)
pen('overline', false, state)
-- TODO(dundargoc): fix
-- Foreground
-- push "\x1b[31m"
@@ -3485,6 +3514,14 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]])
screen_cell(0, 9, '{30} width=1 attrs={S_} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
screen_cell(0, 10, '{32} width=1 attrs={S^} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- Dim
push('\x1b[2mI\x1b[m', vt)
screen_cell(0, 11, '{49} width=1 attrs={D} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- Overline
push('\x1b[53mJ\x1b[m', vt)
screen_cell(0, 12, '{4a} width=1 attrs={O} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- EL sets only colours to end of line, not other attrs
push('\x1b[H\x1b[7;33;44m\x1b[K', vt)
screen_cell(0, 0, '{} width=1 attrs={} fg=rgb(224,224,0) bg=rgb(0,0,224)', screen)