Use only a single marker function

Multipe colors/expressions can instead be combined at definition time
This commit is contained in:
Kovid Goyal 2020-01-13 11:57:19 +05:30
parent 072cd29e3c
commit 35fb702833
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
9 changed files with 107 additions and 119 deletions

View File

@ -241,41 +241,36 @@ def disable_ligatures_in(func, rest):
return func, [where, strategy]
@func_with_args('add_marker')
def add_marker(func, rest):
parts = rest.split(maxsplit=2)
if len(parts) != 3:
@func_with_args('toggle_marker')
def toggle_marker(func, rest):
parts = rest.split(maxsplit=1)
if len(parts) != 2:
raise ValueError('{} if not a valid marker specification'.format(rest))
name, ftype, spec = parts
color = None
if ftype in ('text', 'itext', 'regex'):
flags = re.UNICODE
parts = spec.split(maxsplit=1)
if len(parts) != 2:
raise ValueError('No color specified in marker: {}'.format(spec))
try:
color = max(1, min(int(parts[0]), 3))
except Exception:
raise ValueError('color {} in marker specification is not an integer'.format(parts[0]))
spec = parts[1]
if ftype in ('text', 'itext'):
spec = re.escape(spec)
ftype, spec = parts
flags = re.UNICODE
if ftype in ('text', 'itext', 'regex', 'iregex'):
parts = spec.split()
if ftype.startswith('i'):
flags |= re.IGNORECASE
ftype = 'regex'
try:
spec = re.compile(spec, flags=flags)
except Exception:
raise ValueError('{} is not a valid regular expression'.format(spec))
if not parts or len(parts) % 2 != 0:
raise ValueError('No color specified in marker: {}'.format(spec))
ans = []
for i in range(0, len(parts), 2):
try:
color = max(1, min(int(parts[i]), 3))
except Exception:
raise ValueError('color {} in marker specification is not an integer'.format(parts[i]))
spec = parts[i + 1]
if 'regex' not in ftype:
spec = re.escape(spec)
ans.append((color, spec))
ftype = 'regex'
spec = tuple(ans)
elif ftype == 'function':
pass
else:
raise ValueError('Unknown marker type: {}'.format(ftype))
return func, [name, ftype, spec, color]
@func_with_args('remove_marker')
def remove_marker(func, rest):
return func, [rest]
return func, [ftype, spec, flags]
def parse_key_action(action):
@ -288,8 +283,8 @@ def parse_key_action(action):
if parser is not None:
try:
func, args = parser(func, rest)
except Exception:
log_error('Ignoring invalid key action: {}'.format(action))
except Exception as err:
log_error('Ignoring invalid key action: {} with err: {}'.format(action, err))
else:
return KeyAction(func, args)

View File

@ -144,12 +144,6 @@ typedef enum { NONE, MENUBAR, WINDOW, ALL } WindowTitleIn;
#define END_ALLOW_UNUSED_RESULT _Pragma("GCC diagnostic pop")
#endif
typedef struct {
PyObject *callback;
const char *name;
bool error_reported;
} Marker;
typedef struct {
uint32_t left, top, right, bottom;

View File

@ -666,21 +666,22 @@ __eq__(Line *a, Line *b) {
}
static inline void
apply_marker(Marker *marker, Line *line, const PyObject *text) {
report_marker_error(PyObject *marker) {
if (!PyObject_HasAttrString(marker, "error_reported")) {
PyErr_Print();
if (PyObject_SetAttrString(marker, "error_reported", Py_True) != 0) PyErr_Clear();
} else PyErr_Clear();
}
static inline void
apply_marker(PyObject *marker, Line *line, const PyObject *text) {
unsigned int l=0, r=0, col=0, match_pos=0;
PyObject *pl = PyLong_FromVoidPtr(&l), *pr = PyLong_FromVoidPtr(&r), *pcol = PyLong_FromVoidPtr(&col);
if (!pl || !pr || !pcol) { PyErr_Clear(); return; }
PyObject *iter = PyObject_CallFunctionObjArgs(marker->callback, text, pl, pr, pcol, NULL);
PyObject *iter = PyObject_CallFunctionObjArgs(marker, text, pl, pr, pcol, NULL);
Py_DECREF(pl); Py_DECREF(pr); Py_DECREF(pcol);
if (iter == NULL) {
if (!marker->error_reported) {
PyErr_Print();
marker->error_reported = true;
}
else PyErr_Clear();
return;
}
if (iter == NULL) { report_marker_error(marker); return; }
PyObject *match;
index_type x = 0;
#define INCREMENT_MATCH_POS { \
@ -708,20 +709,19 @@ apply_marker(Marker *marker, Line *line, const PyObject *text) {
}
while(x < line->xnum) line->gpu_cells[x++].attrs &= ATTRS_MASK_WITHOUT_MARK;
Py_DECREF(iter);
if (PyErr_Occurred()) report_marker_error(marker);
#undef INCREMENT_MATCH_POS
}
void
mark_text_in_line(Marker *markers, size_t markers_count, Line *line) {
if (!markers_count) {
mark_text_in_line(PyObject *marker, Line *line) {
if (!marker) {
for (index_type i = 0; i < line->xnum; i++) line->gpu_cells[i].attrs &= ATTRS_MASK_WITHOUT_MARK;
return;
}
PyObject *text = line_as_unicode(line);
if (PyUnicode_GET_LENGTH(text) > 0) {
for (size_t i = 0; i < markers_count; i++) {
apply_marker(markers + i, line, text);
}
apply_marker(marker, line, text);
} else {
for (index_type i = 0; i < line->xnum; i++) line->gpu_cells[i].attrs &= ATTRS_MASK_WITHOUT_MARK;
}

View File

@ -90,7 +90,7 @@ void historybuf_mark_line_clean(HistoryBuf *self, index_type y);
void historybuf_mark_line_dirty(HistoryBuf *self, index_type y);
void historybuf_refresh_sprite_positions(HistoryBuf *self);
void historybuf_clear(HistoryBuf *self);
void mark_text_in_line(Marker *markers, size_t markers_count, Line *line);
void mark_text_in_line(PyObject *marker, Line *line);
#define as_text_generic(args, container, get_line, lines, columns) { \

View File

@ -21,10 +21,10 @@ def get_output_variables(left_address, right_address, color_address):
)
def marker_from_regex(expression, color):
def marker_from_regex(expression, color, flags=re.UNICODE):
color = max(1, min(color, 3))
if isinstance(expression, str):
pat = re.compile(expression)
pat = re.compile(expression, flags=flags)
else:
pat = expression
@ -39,6 +39,28 @@ def marker_from_regex(expression, color):
return marker
def marker_from_multiple_regex(regexes, flags=re.UNICODE):
expr = ''
color_map = {}
for i, (color, spec) in enumerate(regexes):
grp = 'mcg{}'.format(i)
expr += '|(?P<{}>{})'.format(grp, spec)
color_map[grp] = color
expr = expr[1:]
pat = re.compile(expr, flags=flags)
def marker(text, left_address, right_address, color_address):
left, right, color = get_output_variables(left_address, right_address, color_address)
for match in pat.finditer(text):
left.value = match.start()
right.value = match.end() - 1
grp = next(k for k, v in match.groupdict().items() if v is not None)
color.value = color_map[grp]
yield
return marker
def marker_from_text(expression, color):
return marker_from_regex(re.escape(expression), color)

View File

@ -268,12 +268,6 @@ reset_callbacks(Screen *self, PyObject *a UNUSED) {
Py_RETURN_NONE;
}
static void
free_marker(Marker *marker) {
Py_CLEAR(marker->callback);
free((void*)marker->name);
}
static void
dealloc(Screen* self) {
pthread_mutex_destroy(&self->read_buf_lock);
@ -288,10 +282,7 @@ dealloc(Screen* self) {
Py_CLEAR(self->alt_linebuf);
Py_CLEAR(self->historybuf);
Py_CLEAR(self->color_profile);
if (self->markers.items) {
for (size_t i = 0; i < self->markers.count; i++) free_marker(self->markers.items + i);
free(self->markers.items);
}
Py_CLEAR(self->marker);
PyMem_Free(self->overlay_line.cpu_cells);
PyMem_Free(self->overlay_line.gpu_cells);
PyMem_Free(self->main_tabstops);
@ -1518,8 +1509,8 @@ screen_reset_dirty(Screen *self) {
}
static inline bool
screen_has_markers(Screen *self) {
return self->markers.count > 0;
screen_has_marker(Screen *self) {
return self->marker != NULL;
}
@ -1536,7 +1527,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
historybuf_init_line(self->historybuf, lnum, self->historybuf->line);
if (self->historybuf->line->has_dirty_text) {
render_line(fonts_data, self->historybuf->line, lnum, self->cursor, self->disable_ligatures);
if (screen_has_markers(self)) mark_text_in_line(self->markers.items, self->markers.count, self->historybuf->line);
if (screen_has_marker(self)) mark_text_in_line(self->marker, self->historybuf->line);
historybuf_mark_line_clean(self->historybuf, lnum);
}
update_line_data(self->historybuf->line, y, address);
@ -1547,7 +1538,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
if (self->linebuf->line->has_dirty_text ||
(cursor_has_moved && (self->cursor->y == lnum || self->last_rendered_cursor_y == lnum))) {
render_line(fonts_data, self->linebuf->line, lnum, self->cursor, self->disable_ligatures);
if (self->linebuf->line->has_dirty_text && screen_has_markers(self)) mark_text_in_line(self->markers.items, self->markers.count, self->linebuf->line);
if (self->linebuf->line->has_dirty_text && screen_has_marker(self)) mark_text_in_line(self->marker, self->linebuf->line);
linebuf_mark_line_clean(self->linebuf, lnum);
}
@ -2268,61 +2259,40 @@ static inline void
screen_mark_all(Screen *self) {
for (index_type y = 0; y < self->main_linebuf->ynum; y++) {
linebuf_init_line(self->main_linebuf, y);
mark_text_in_line(self->markers.items, self->markers.count, self->main_linebuf->line);
mark_text_in_line(self->marker, self->main_linebuf->line);
}
for (index_type y = 0; y < self->alt_linebuf->ynum; y++) {
linebuf_init_line(self->alt_linebuf, y);
mark_text_in_line(self->markers.items, self->markers.count, self->alt_linebuf->line);
mark_text_in_line(self->marker, self->alt_linebuf->line);
}
for (index_type y = 0; y < self->historybuf->count; y++) {
historybuf_init_line(self->historybuf, y, self->historybuf->line);
mark_text_in_line(self->markers.items, self->markers.count, self->historybuf->line);
mark_text_in_line(self->marker, self->historybuf->line);
}
self->is_dirty = true;
}
static PyObject*
add_marker(Screen *self, PyObject *args) {
const char *name;
PyObject *marker;
if (!PyArg_ParseTuple(args, "sO", &name, &marker)) return NULL;
set_marker(Screen *self, PyObject *args) {
PyObject *marker = NULL;
if (!PyArg_ParseTuple(args, "|O", &marker)) return NULL;
if (!marker) {
if (self->marker) {
Py_CLEAR(self->marker);
screen_mark_all(self);
}
Py_RETURN_NONE;
}
if (!PyCallable_Check(marker)) {
PyErr_SetString(PyExc_TypeError, "marker must be a callable");
return NULL;
}
for (size_t i = 0; i < self->markers.count; i++) {
if (strcmp(self->markers.items[i].name, name) == 0) {
if (self->markers.items[i].callback == marker) Py_RETURN_NONE;
Py_DECREF(self->markers.items[i].callback);
self->markers.items[i].callback = marker;
Py_INCREF(marker);
screen_mark_all(self);
Py_RETURN_NONE;
}
}
ensure_space_for(&self->markers, items, Marker, 1, capacity, 8, true);
self->markers.items[self->markers.count].name = strdup(name);
self->markers.items[self->markers.count++].callback = marker;
self->marker = marker;
Py_INCREF(marker);
screen_mark_all(self);
Py_RETURN_NONE;
}
static PyObject*
remove_marker(Screen *self, PyObject *args) {
const char *name;
if (!PyArg_ParseTuple(args, "s", &name)) return NULL;
for (size_t i = 0; i < self->markers.count; i++) {
if (strcmp(self->markers.items[i].name, name) == 0) {
free_marker(self->markers.items + i);
remove_i_from_array(self->markers.items, i, self->markers.count);
screen_mark_all(self);
Py_RETURN_TRUE;
}
}
Py_RETURN_FALSE;
}
static PyObject*
marked_cells(Screen *self, PyObject *o UNUSED) {
PyObject *ans = PyList_New(0);
@ -2442,8 +2412,7 @@ static PyMethodDef methods[] = {
MND(paste, METH_O)
MND(paste_bytes, METH_O)
MND(copy_colors_from, METH_O)
MND(add_marker, METH_VARARGS)
MND(remove_marker, METH_VARARGS)
MND(set_marker, METH_VARARGS)
MND(marked_cells, METH_NOARGS)
{"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""},

View File

@ -107,10 +107,7 @@ typedef struct {
uint8_t stop_buf[32];
} pending_mode;
DisableLigature disable_ligatures;
struct {
Marker *items;
size_t count, capacity;
} markers;
PyObject *marker;
} Screen;

View File

@ -136,6 +136,7 @@ class Window:
def __init__(self, tab, child, opts, args, override_title=None, copy_colors_from=None):
self.action_on_close = self.action_on_removal = None
self.layout_data = None
self.current_marker_spec = None
self.pty_resized_once = False
self.needs_attention = False
self.override_title = override_title
@ -606,10 +607,17 @@ class Window:
if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_FULL, False)
def add_marker(self, name, ftype, spec, color):
from .marks import marker_from_regex, marker_from_function
def toggle_marker(self, ftype, spec, flags):
from .marks import marker_from_regex, marker_from_function, marker_from_multiple_regex
key = ftype, spec
if key == self.current_marker_spec:
self.remove_marker()
return
if ftype == 'regex':
marker = marker_from_regex(spec, color)
if len(spec) == 1:
marker = marker_from_regex(spec[0][1], spec[0][0], flags=flags)
else:
marker = marker_from_multiple_regex(spec, flags=flags)
elif ftype == 'function':
import runpy
path = spec
@ -618,8 +626,11 @@ class Window:
marker = marker_from_function(runpy.run_path(path, run_name='__marker__').marker)
else:
raise ValueError('Unknown marker type: {}'.format(ftype))
self.screen.add_marker(name, marker)
self.screen.set_marker(marker)
self.current_marker_spec = key
def remove_marker(self, name):
self.screen.remove_marker(name)
def remove_marker(self):
if self.current_marker_spec is not None:
self.screen.set_marker()
self.current_marker_spec = None
# }}}

View File

@ -460,9 +460,9 @@ class TestScreen(BaseTest):
s.draw('abaa')
s.carriage_return(), s.linefeed()
s.draw('xyxyx')
s.add_marker('a', marker_from_regex('a', 3))
s.set_marker(marker_from_regex('a', 3))
self.ae(s.marked_cells(), [(0, 0, 3), (2, 0, 3), (3, 0, 3)])
s.remove_marker('a')
s.set_marker()
self.ae(s.marked_cells(), [])
def mark_x(text):
@ -472,5 +472,5 @@ class TestScreen(BaseTest):
col += 1
yield i, i, col
s.add_marker('x', marker_from_function(mark_x))
s.set_marker(marker_from_function(mark_x))
self.ae(s.marked_cells(), [(0, 1, 1), (2, 1, 2), (4, 1, 3)])