diff --git a/kitty/data-types.h b/kitty/data-types.h index d3a3d055c..bb90483e1 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -199,9 +199,12 @@ typedef struct { bool rewrap_needed; } PagerHistoryBuf; +typedef struct {int x;} *HYPERLINK_POOL_HANDLE; typedef struct { Py_UCS4 *buf; size_t len, capacity; + HYPERLINK_POOL_HANDLE hyperlink_pool; + hyperlink_id_type active_hyperlink_id; } ANSIBuf; typedef struct { @@ -254,7 +257,6 @@ typedef struct { typedef struct {int x;} *SPRITE_MAP_HANDLE; #define FONTS_DATA_HEAD SPRITE_MAP_HANDLE sprite_map; double logical_dpi_x, logical_dpi_y, font_sz_in_pts; unsigned int cell_width, cell_height; typedef struct {FONTS_DATA_HEAD} *FONTS_DATA_HANDLE; -typedef struct {int x;} *HYPERLINK_POOL_HANDLE; #define PARSER_BUF_SZ (8 * 1024) #define READ_BUF_SZ (1024*1024) diff --git a/kitty/line.c b/kitty/line.c index 932f967f4..86858c98b 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -277,11 +277,36 @@ write_sgr(const char *val, ANSIBuf *output) { #undef W } +static inline void +write_hyperlink(hyperlink_id_type hid, ANSIBuf *output) { +#define W(c) output->buf[output->len++] = c + const char *key = hid ? get_hyperlink_for_id(output->hyperlink_pool, hid, false) : NULL; + if (!key) hid = 0; + output->active_hyperlink_id = hid; + W(0x1b); W(']'); W('8'); + if (!hid) { + W(';'); W(';'); + } else { + const char* partition = strstr(key, ":"); + W(';'); + if (partition != key) { + W('i'); W('d'); W('='); + while (key != partition) W(*(key++)); + } + W(';'); + while(*(++partition)) W(*partition); + } + W(0x1b); W('\\'); +#undef W +} + + void line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell) { #define ENSURE_SPACE(extra) ensure_space_for(output, buf, Py_UCS4, output->len + extra, capacity, 2048, false); #define WRITE_SGR(val) { ENSURE_SPACE(128); write_sgr(val, output); } #define WRITE_CH(val) { ENSURE_SPACE(1); output->buf[output->len++] = val; } +#define WRITE_HYPERLINK(val) { ENSURE_SPACE(2256); write_hyperlink(val, output); } output->len = 0; index_type limit = xlimit_for_line(self); if (limit == 0) return; @@ -297,6 +322,12 @@ line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell) { if (previous_width == 2) { previous_width = 0; continue; } ch = ' '; } + if (output->hyperlink_pool) { + hyperlink_id_type hid = self->cpu_cells[pos].hyperlink_id; + if (hid != output->active_hyperlink_id) { + WRITE_HYPERLINK(hid); + } + } cell = &self->gpu_cells[pos]; @@ -325,6 +356,7 @@ line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell) { #undef WRITE_SGR #undef WRITE_CH #undef ENSURE_SPACE +#undef WRITE_HYPERLINK } static PyObject* @@ -770,8 +802,9 @@ as_text_generic(PyObject *args, void *container, get_line_func get_line, index_t PyObject *nl = PyUnicode_FromString("\n"); PyObject *cr = PyUnicode_FromString("\r"); PyObject *sgr_reset = PyUnicode_FromString("\x1b[m"); + if (nl == NULL || cr == NULL || sgr_reset == NULL) goto end; const GPUCell *prev_cell = NULL; - if (nl == NULL || cr == NULL) goto end; + ansibuf->active_hyperlink_id = 0; for (index_type y = 0; y < lines; y++) { Line *line = get_line(container, y); if (!line->continued && y > 0) { @@ -805,6 +838,15 @@ as_text_generic(PyObject *args, void *container, get_line_func get_line, index_t Py_CLEAR(ret); } } + if (ansibuf->active_hyperlink_id) { + ansibuf->active_hyperlink_id = 0; + t = PyUnicode_FromString("\x1b]8;;\x1b\\"); + if (t) { + ret = PyObject_CallFunctionObjArgs(callback, t, NULL); + Py_CLEAR(t); + Py_CLEAR(ret); + } + } end: Py_CLEAR(nl); Py_CLEAR(cr); Py_CLEAR(sgr_reset); if (PyErr_Occurred()) return NULL; diff --git a/kitty/screen.c b/kitty/screen.c index a7fa9888f..df8728e5b 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -137,6 +137,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { if (!init_overlay_line(self, self->columns)) { Py_CLEAR(self); return NULL; } self->hyperlink_pool = alloc_hyperlink_pool(); if (!self->hyperlink_pool) { Py_CLEAR(self); return PyErr_NoMemory(); } + self->as_ansi_buf.hyperlink_pool = self->hyperlink_pool; } return (PyObject*) self; } diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index bf022ae2c..cab7388c7 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -467,6 +467,18 @@ class TestScreen(BaseTest): s.draw(f'{i}' * s.columns) self.ae(as_text(s, True, True), '\x1b[m\x1b[31m11\x1b[m\x1b[32m22\x1b[m\x1b[33m33\x1b[m\x1b[34m44\x1b[m\x1b[m\x1b[35m55\x1b[m\x1b[36m66') + def set_link(url=None, id=None): + parse_bytes(s, '\x1b]8;id={};{}\x1b\\'.format(id or '', url or '').encode('utf-8')) + + s = self.create_screen() + s.draw('a') + set_link('moo', 'foo') + s.draw('bcdef') + self.ae(as_text(s, True), '\x1b[ma\x1b]8;id=foo;moo\x1b\\bcde\x1b[mf\n\n\n\x1b]8;;\x1b\\') + set_link() + s.draw('gh') + self.ae(as_text(s, True), '\x1b[ma\x1b]8;id=foo;moo\x1b\\bcde\x1b[mf\x1b]8;;\x1b\\gh\n\n\n') + def test_pagerhist(self): hsz = 8 s = self.create_screen(cols=2, lines=2, scrollback=2, options={'scrollback_pager_history_size': hsz})