Implement round tripping of SGR blink (5/25)

This commit is contained in:
Kovid Goyal
2025-08-25 12:34:38 +05:30
parent 5a3e9a5567
commit 24049a1a5a
9 changed files with 31 additions and 8 deletions

View File

@@ -7,6 +7,7 @@
#define REVERSE_SHIFT {REVERSE_SHIFT}
#define STRIKE_SHIFT {STRIKE_SHIFT}
#define DIM_SHIFT {DIM_SHIFT}
#define BLINK_SHIFT {BLINK_SHIFT}
#define MARK_SHIFT {MARK_SHIFT}
#define MARK_MASK {MARK_MASK}
#define USE_SELECTION_FG

View File

@@ -34,9 +34,9 @@ static const char* cursor_names[NUM_OF_CURSOR_SHAPES] = { "NO_SHAPE", "BLOCK", "
static PyObject *
repr(Cursor *self) {
return PyUnicode_FromFormat(
"Cursor(x=%u, y=%u, shape=%s, blink=%R, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, dim=%R, decoration=%d, decoration_fg=#%08x)",
"Cursor(x=%u, y=%u, shape=%s, blink=%R, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, dim=%R, decoration=%d, decoration_fg=#%08x, text_blink=%R)",
self->x, self->y, (self->shape < NUM_OF_CURSOR_SHAPES ? cursor_names[self->shape] : "INVALID"),
BOOL(!self->non_blinking), self->sgr.fg, self->sgr.bg, BOOL(self->sgr.bold), BOOL(self->sgr.italic), BOOL(self->sgr.reverse), BOOL(self->sgr.strikethrough), BOOL(self->sgr.dim), self->sgr.decoration, self->sgr.decoration_fg
BOOL(!self->non_blinking), self->sgr.fg, self->sgr.bg, BOOL(self->sgr.bold), BOOL(self->sgr.italic), BOOL(self->sgr.reverse), BOOL(self->sgr.strikethrough), BOOL(self->sgr.dim), self->sgr.decoration, self->sgr.decoration_fg, BOOL(self->sgr.blink)
);
}
@@ -93,6 +93,8 @@ START_ALLOW_CASE_RANGE
if (is_group && i < count) { self->sgr.decoration = MIN(5, params[i]); i++; }
else self->sgr.decoration = 1;
break;
case 5:
self->sgr.blink = true; break;
case 7:
self->sgr.reverse = true; break;
case 9:
@@ -109,6 +111,8 @@ START_ALLOW_CASE_RANGE
self->sgr.italic = false; break;
case 24:
self->sgr.decoration = 0; break;
case 25:
self->sgr.blink = false; break;
case 27:
self->sgr.reverse = false; break;
case 29:
@@ -169,6 +173,8 @@ apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, un
if (is_group && i < count) { val = MIN(5, params[i]); i++; }
S(decoration, val);
}
case 5:
S(blink, true);
case 7:
S(reverse, true);
case 9:
@@ -185,6 +191,8 @@ apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, un
S(italic, false);
case 24:
S(decoration, 0);
case 25:
S(blink, false);
case 27:
S(reverse, false);
case 29:
@@ -272,6 +280,11 @@ static PyObject* blink_get(Cursor *self, void UNUSED *closure) { PyObject *ans =
static int blink_set(Cursor *self, PyObject *value, void UNUSED *closure) { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } self->non_blinking = PyObject_IsTrue(value) ? false : true; return 0; }
static PyObject* text_blink_get(Cursor *self, void UNUSED *closure) { return Py_NewRef(self->sgr.blink ? Py_True : Py_False); }
static int text_blink_set(Cursor *self, PyObject *value, void UNUSED *closure) { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } self->sgr.blink = PyObject_IsTrue(value) ? false : true; return 0; }
#define SGR_UINT_GETSET(x) \
static PyObject* x##_get(Cursor *self, void UNUSED *closure) { return PyLong_FromUnsignedLong((unsigned long)self->sgr.x); } \
static int x##_set(Cursor *self, PyObject *value, void UNUSED *closure) { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } if (!PyLong_Check(value)) { PyErr_SetString(PyExc_TypeError, "value must be an int"); return -1; } self->sgr.x = PyLong_AsUnsignedLong(value); return 0; }
@@ -295,6 +308,7 @@ static PyGetSetDef getseters[] = {
GETSET(strikethrough)
GETSET(dim)
GETSET(blink)
GETSET(text_blink)
GETSET(decoration)
GETSET(fg)
GETSET(bg)

View File

@@ -836,7 +836,7 @@ PyInit_fast_data_types(void) {
CellAttrs a;
#define s(name, attr) { a.val = 0; a.attr = 1; PyModule_AddIntConstant(m, #name, shift_to_first_set_bit(a)); }
s(BOLD, bold); s(ITALIC, italic); s(REVERSE, reverse); s(MARK, mark);
s(STRIKETHROUGH, strike); s(DIM, dim); s(DECORATION, decoration);
s(STRIKETHROUGH, strike); s(DIM, dim); s(DECORATION, decoration); s(BLINK, blink);
#undef s
PyModule_AddIntConstant(m, "MARK_MASK", MARK_MASK);
PyModule_AddIntConstant(m, "DECORATION_MASK", DECORATION_MASK);

View File

@@ -217,7 +217,7 @@ typedef struct {
CursorShape shape;
struct {
bool bold, italic, reverse, strikethrough, dim;
bool bold, italic, reverse, strikethrough, dim, blink;
uint8_t decoration;
color_type fg, bg, decoration_fg;
} sgr;

View File

@@ -277,6 +277,7 @@ CELL_BG_PROGRAM: int
BLIT_PROGRAM: int
ROUNDED_RECT_PROGRAM: int
DECORATION: int
BLINK: int
DIM: int
GRAPHICS_ALPHA_MASK_PROGRAM: int
GRAPHICS_PROGRAM: int
@@ -1195,6 +1196,7 @@ class Cursor:
bold: bool
italic: bool
blink: bool
text_blink: bool
shape: int

View File

@@ -920,6 +920,7 @@ cell_as_sgr(const GPUCell *cell, const GPUCell *prev) {
if (CA.italic != PA.italic) P(CA.italic ? "3;" : "23;");
if (CA.reverse != PA.reverse) P(CA.reverse ? "7;" : "27;");
if (CA.strike != PA.strike) P(CA.strike ? "9;" : "29;");
if (CA.blink != PA.blink) P(CA.blink ? "5;" : "25;");
if (cell->fg != prev->fg) p += color_as_sgr(p, SZ, cell->fg, 30, 90, 38);
if (cell->bg != prev->bg) p += color_as_sgr(p, SZ, cell->bg, 40, 100, 48);
if (cell->decoration_fg != prev->decoration_fg) p += color_as_sgr(p, SZ, cell->decoration_fg, 0, 0, DECORATION_FG_CODE);

View File

@@ -17,8 +17,9 @@ typedef union CellAttrs {
uint16_t reverse : 1;
uint16_t strike : 1;
uint16_t dim : 1;
uint16_t blink: 1;
uint16_t mark : 2;
uint32_t : 22;
uint32_t : 21;
};
uint32_t val;
} CellAttrs;
@@ -180,7 +181,7 @@ static inline CellAttrs
cursor_to_attrs(const Cursor *c) {
CellAttrs ans = {
.decoration=c->sgr.decoration, .bold=c->sgr.bold, .italic=c->sgr.italic, .reverse=c->sgr.reverse,
.strike=c->sgr.strikethrough, .dim=c->sgr.dim};
.strike=c->sgr.strikethrough, .dim=c->sgr.dim, .blink=c->sgr.blink};
return ans;
}
@@ -188,6 +189,7 @@ static inline void
attrs_to_cursor(const CellAttrs attrs, Cursor *c) {
c->sgr.decoration = attrs.decoration; c->sgr.bold = attrs.bold; c->sgr.italic = attrs.italic;
c->sgr.reverse = attrs.reverse; c->sgr.strikethrough = attrs.strike; c->sgr.dim = attrs.dim;
c->sgr.blink = attrs.blink;
}
#define cursor_as_gpu_cell(cursor) {.attrs=cursor_to_attrs(cursor), .fg=(cursor->sgr.fg & COL_MASK), .bg=(cursor->sgr.bg & COL_MASK), .decoration_fg=cursor->sgr.decoration_fg & COL_MASK}

View File

@@ -10,6 +10,7 @@ from typing import Any, Literal, NamedTuple, Optional
from .constants import read_kitty_resource
from .fast_data_types import (
BGIMAGE_PROGRAM,
BLINK,
BLIT_PROGRAM,
CELL_BG_PROGRAM,
CELL_FG_PROGRAM,
@@ -172,6 +173,7 @@ class LoadShaderPrograms:
REVERSE_SHIFT=REVERSE,
STRIKE_SHIFT=STRIKETHROUGH,
DIM_SHIFT=DIM,
BLINK_SHIFT=BLINK,
DECORATION_SHIFT=DECORATION,
MARK_SHIFT=MARK,
MARK_MASK=MARK_MASK,

View File

@@ -112,7 +112,6 @@ class TestScreen(BaseTest):
ln = s.line(0)
self.ae(txt, ln.as_ansi())
def test_rep(self):
s = self.create_screen()
s.draw('a')
@@ -618,11 +617,13 @@ class TestScreen(BaseTest):
def test_sgr(self):
s = self.create_screen()
s.select_graphic_rendition(0, 1, 37, 42)
s.select_graphic_rendition(0, 1, 5, 37, 42)
s.draw('a')
c = s.line(0).cursor_from(0)
self.assertTrue(c.bold)
self.assertTrue(c.blink)
self.ae(c.bg, (2 << 8) | 1)
self.ae('\x1b[22;1;5;37;42ma', s.line(0).as_ansi())
s.cursor_position(2, 1)
s.select_graphic_rendition(0, 35)
s.draw('b')