mirror of
https://github.com/kovidgoyal/kitty.git
synced 2024-11-13 12:09:35 +03:00
Use only a single marker function
Multipe colors/expressions can instead be combined at definition time
This commit is contained in:
parent
072cd29e3c
commit
35fb702833
@ -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)
|
||||
|
||||
|
@ -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;
|
||||
|
30
kitty/line.c
30
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;
|
||||
}
|
||||
|
@ -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) { \
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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, ""},
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
@ -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
|
||||
# }}}
|
||||
|
@ -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)])
|
||||
|
Loading…
Reference in New Issue
Block a user