From 35fb702833c4d1d35ce1986165c13558b8614d67 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 13 Jan 2020 11:57:19 +0530 Subject: [PATCH] Use only a single marker function Multipe colors/expressions can instead be combined at definition time --- kitty/config.py | 57 ++++++++++++++++------------------ kitty/data-types.h | 6 ---- kitty/line.c | 30 +++++++++--------- kitty/lineops.h | 2 +- kitty/marks.py | 26 ++++++++++++++-- kitty/screen.c | 71 ++++++++++++------------------------------- kitty/screen.h | 5 +-- kitty/window.py | 23 ++++++++++---- kitty_tests/screen.py | 6 ++-- 9 files changed, 107 insertions(+), 119 deletions(-) diff --git a/kitty/config.py b/kitty/config.py index 7bf2759c5..19afa9dcc 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -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) diff --git a/kitty/data-types.h b/kitty/data-types.h index 1538c82f8..49f5aa325 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -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; diff --git a/kitty/line.c b/kitty/line.c index eac42f392..a2b4b01dd 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -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; } diff --git a/kitty/lineops.h b/kitty/lineops.h index 3c3b8ec59..d2f0671a5 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -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) { \ diff --git a/kitty/marks.py b/kitty/marks.py index aa6f6d43f..620d8c1d0 100644 --- a/kitty/marks.py +++ b/kitty/marks.py @@ -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) diff --git a/kitty/screen.c b/kitty/screen.c index 73850fa28..99b8d7ecc 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -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, ""}, diff --git a/kitty/screen.h b/kitty/screen.h index 524b92160..307eb47b8 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -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; diff --git a/kitty/window.py b/kitty/window.py index 94befd872..7e17e3e65 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -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 # }}} diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index 93d6ad3b7..9743e047e 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -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)])