Add tests for hyperlink storage

This commit is contained in:
Kovid Goyal 2020-08-27 12:32:47 +05:30
parent 33beecddda
commit 78dc93721d
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 133 additions and 7 deletions

View File

@ -10,6 +10,8 @@
#include <string.h>
#define MAX_KEY_LEN 2048
#define MAX_ID_LEN 256
#define MAX_ADDS_BEFORE_GC 256
#undef uthash_fatal
#define uthash_fatal(msg) fatal(msg)
@ -69,8 +71,8 @@ free_hyperlink_pool(HYPERLINK_POOL_HANDLE h) {
}
static void
garbage_collect_pool(Screen *screen) {
void
screen_garbage_collect_hyperlink_pool(Screen *screen) {
HyperLinkPool *pool = (HyperLinkPool*)screen->hyperlink_pool;
pool->num_of_adds_since_garbage_collection = 0;
if (!pool->max_link_id) return;
@ -99,14 +101,14 @@ get_id_for_hyperlink(Screen *screen, const char *id, const char *url) {
if (!url) return 0;
HyperLinkPool *pool = (HyperLinkPool*)screen->hyperlink_pool;
static char key[MAX_KEY_LEN] = {0};
size_t keylen = snprintf(key, MAX_KEY_LEN-1, "%s:%s", id ? id : "", url);
size_t keylen = snprintf(key, MAX_KEY_LEN-1, "%.*s:%s", MAX_ID_LEN, id ? id : "", url);
HyperLinkEntry *s = NULL;
if (pool->hyperlinks) {
HASH_FIND_STR(pool->hyperlinks, key, s);
if (s) return s->id;
}
hyperlink_id_type new_id = 0;
if (pool->num_of_adds_since_garbage_collection >= 256) garbage_collect_pool(screen);
if (pool->num_of_adds_since_garbage_collection >= MAX_ADDS_BEFORE_GC) screen_garbage_collect_hyperlink_pool(screen);
if (pool->max_link_id >= HYPERLINK_MAX_NUMBER) {
log_error("Too many hyperlinks, discarding oldest, this means some hyperlinks might be incorrect");
new_id = pool->hyperlinks->id;
@ -124,3 +126,27 @@ get_id_for_hyperlink(Screen *screen, const char *id, const char *url) {
pool->num_of_adds_since_garbage_collection++;
return s->id;
}
const char*
get_hyperlink_for_id(Screen *screen, hyperlink_id_type id) {
HyperLinkPool *pool = (HyperLinkPool*)screen->hyperlink_pool;
HyperLinkEntry *s, *tmp;
HASH_ITER(hh, pool->hyperlinks, s, tmp) {
if (s->id == id) return strstr(s->key, ":") + 1;
}
return NULL;
}
PyObject*
screen_hyperlinks_as_list(Screen *screen) {
HyperLinkPool *pool = (HyperLinkPool*)screen->hyperlink_pool;
PyObject *ans = PyList_New(0);
HyperLinkEntry *s, *tmp;
HASH_ITER(hh, pool->hyperlinks, s, tmp) {
PyObject *e = Py_BuildValue("sH", s->key, s->id);
PyList_Append(ans, e);
Py_DECREF(e);
}
return ans;
}

View File

@ -13,3 +13,6 @@ void free_hyperlink_pool(HYPERLINK_POOL_HANDLE);
void clear_hyperlink_pool(HYPERLINK_POOL_HANDLE);
hyperlink_id_type get_id_for_hyperlink(Screen*, const char*, const char*);
hyperlink_id_type remap_hyperlink_ids(Screen *self, hyperlink_id_type *map);
PyObject* screen_hyperlinks_as_list(Screen *screen);
void screen_garbage_collect_hyperlink_pool(Screen *screen);
const char* get_hyperlink_for_id(Screen *screen, hyperlink_id_type id);

View File

@ -817,12 +817,22 @@ end:
Py_RETURN_NONE;
}
// Boilerplate {{{
static PyObject*
copy_char(Line* self, PyObject *args);
#define copy_char_doc "copy_char(src, to, dest) -> Copy the character at src to to the character dest in the line `to`"
#define hyperlink_ids_doc "hyperlink_ids() -> Tuple of hyper link ids at every cell"
static PyObject*
hyperlink_ids(Line *self, PyObject *args UNUSED) {
PyObject *ans = PyTuple_New(self->xnum);
for (index_type x = 0; x < self->xnum; x++) {
PyTuple_SET_ITEM(ans, x, PyLong_FromUnsignedLong(self->cpu_cells[x].hyperlink_id));
}
return ans;
}
static PyObject *
richcmp(PyObject *obj1, PyObject *obj2, int op);
@ -845,6 +855,7 @@ static PyMethodDef methods[] = {
METHOD(set_attribute, METH_VARARGS)
METHOD(as_ansi, METH_NOARGS)
METHOD(is_continued, METH_NOARGS)
METHOD(hyperlink_ids, METH_NOARGS)
METHOD(width, METH_O)
METHOD(url_start_at, METH_O)
METHOD(url_end_at, METH_VARARGS)

View File

@ -1911,6 +1911,20 @@ deactivate_overlay_line(Screen *self) {
#define WRAP2(name, defval1, defval2) static PyObject* name(Screen *self, PyObject *args) { unsigned int a=defval1, b=defval2; if(!PyArg_ParseTuple(args, "|II", &a, &b)) return NULL; screen_##name(self, a, b); Py_RETURN_NONE; }
#define WRAP2B(name) static PyObject* name(Screen *self, PyObject *args) { unsigned int a, b; int p; if(!PyArg_ParseTuple(args, "IIp", &a, &b, &p)) return NULL; screen_##name(self, a, b, (bool)p); Py_RETURN_NONE; }
WRAP0(garbage_collect_hyperlink_pool)
static PyObject*
hyperlinks_as_list(Screen *self, PyObject *args UNUSED) {
return screen_hyperlinks_as_list(self);
}
static PyObject*
hyperlink_for_id(Screen *self, PyObject *val) {
unsigned long id = PyLong_AsUnsignedLong(val);
if (id > HYPERLINK_MAX_NUMBER) { PyErr_SetString(PyExc_IndexError, "Out of bounds"); return NULL; }
return Py_BuildValue("s", get_hyperlink_for_id(self, id));
}
static PyObject*
set_pending_timeout(Screen *self, PyObject *val) {
if (!PyFloat_Check(val)) { PyErr_SetString(PyExc_TypeError, "timeout must be a float"); return NULL; }
@ -2638,6 +2652,9 @@ static PyMethodDef methods[] = {
MND(erase_in_line, METH_VARARGS)
MND(erase_in_display, METH_VARARGS)
MND(scroll_until_cursor, METH_NOARGS)
MND(hyperlinks_as_list, METH_NOARGS)
MND(garbage_collect_hyperlink_pool, METH_NOARGS)
MND(hyperlink_for_id, METH_O)
METHOD(current_char_width, METH_NOARGS)
MND(insert_lines, METH_VARARGS)
MND(delete_lines, METH_VARARGS)

View File

@ -2,9 +2,12 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from kitty.fast_data_types import (
DECAWM, DECCOLM, DECOM, IRM, Cursor, parse_bytes
)
from kitty.marks import marker_from_function, marker_from_regex
from . import BaseTest
from kitty.fast_data_types import DECAWM, IRM, Cursor, DECCOLM, DECOM
from kitty.marks import marker_from_regex, marker_from_function
class TestScreen(BaseTest):
@ -516,3 +519,69 @@ class TestScreen(BaseTest):
self.ae(s.marked_cells(), cells(8))
s.set_marker(marker_from_regex('\t', 3))
self.ae(s.marked_cells(), cells(*range(8)))
def test_hyperlinks(self):
s = self.create_screen()
self.ae(s.line(0).hyperlink_ids(), tuple(0 for x in range(s.columns)))
def set_link(url=None, id=None):
parse_bytes(s, '\x1b]8;id={};{}\x1b\\'.format(id or '', url or '').encode('utf-8'))
set_link('url-a', 'a')
self.ae(s.line(0).hyperlink_ids(), tuple(0 for x in range(s.columns)))
s.draw('a')
self.ae(s.line(0).hyperlink_ids(), (1,) + tuple(0 for x in range(s.columns - 1)))
s.draw('bc')
self.ae(s.line(0).hyperlink_ids(), (1, 1, 1, 0, 0))
set_link()
s.draw('d')
self.ae(s.line(0).hyperlink_ids(), (1, 1, 1, 0, 0))
set_link('url-a', 'a')
s.draw('efg')
self.ae(s.line(0).hyperlink_ids(), (1, 1, 1, 0, 1))
self.ae(s.line(1).hyperlink_ids(), (1, 1, 0, 0, 0))
set_link('url-b')
s.draw('hij')
self.ae(s.line(1).hyperlink_ids(), (1, 1, 2, 2, 2))
set_link()
self.ae([('a:url-a', 1), (':url-b', 2)], s.hyperlinks_as_list())
s.garbage_collect_hyperlink_pool()
self.ae([('a:url-a', 1), (':url-b', 2)], s.hyperlinks_as_list())
for i in range(s.lines + 2):
s.linefeed()
s.garbage_collect_hyperlink_pool()
self.ae([('a:url-a', 1), (':url-b', 2)], s.hyperlinks_as_list())
for i in range(s.lines * 2):
s.linefeed()
s.garbage_collect_hyperlink_pool()
self.assertFalse(s.hyperlinks_as_list())
set_link('url-a', 'x')
s.draw('a')
set_link('url-a', 'y')
s.draw('a')
set_link()
self.ae([('x:url-a', 1), ('y:url-a', 2)], s.hyperlinks_as_list())
s = self.create_screen()
set_link('u' * 2048)
s.draw('a')
self.ae([(':' + 'u' * 2045, 1)], s.hyperlinks_as_list())
s = self.create_screen()
set_link('u' * 2048, 'i' * 300)
s.draw('a')
self.ae([('i'*256 + ':' + 'u' * (2045 - 256), 1)], s.hyperlinks_as_list())
s = self.create_screen()
set_link('1'), s.draw('1')
set_link('2'), s.draw('2')
set_link('3'), s.draw('3')
s.cursor.x = 1
set_link(), s.draw('X')
self.ae(s.line(0).hyperlink_ids(), (1, 0, 3, 0, 0))
self.ae([(':1', 1), (':2', 2), (':3', 3)], s.hyperlinks_as_list())
s.garbage_collect_hyperlink_pool()
self.ae([(':1', 1), (':3', 2)], s.hyperlinks_as_list())
set_link('3'), s.draw('3')
self.ae([(':1', 1), (':3', 2)], s.hyperlinks_as_list())
set_link('4'), s.draw('4')
self.ae([(':1', 1), (':3', 2), (':4', 3)], s.hyperlinks_as_list())