Allow scrolling to marks

This commit is contained in:
Kovid Goyal 2020-01-15 07:11:34 +05:30
parent c68b302fa2
commit 936f2186cc
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 98 additions and 0 deletions

View File

@ -57,6 +57,26 @@ You can also use the facilities for :doc:`remote-control` to dynamically
add/remove markers. add/remove markers.
Scrolling to marks
--------------------
kitty has an action to scroll to the next line that contains a mark. You can
use it by mapping it to some shortcut in :file:`kitty.conf`::
map ctrl+p scroll_to_mark prev
map ctrl+n scroll_to_mark next
Then pressing :kbd:`ctrl+p` will scroll to the first line in the scrollback
buffer above the current top line that contains a mark. Pressing :kbd:`ctrl+n`
will scroll to show the first line below the current last line that contains
a mark. If you wish to jump to a mark of a specific type, you can add that to
the mapping::
map ctrl+1 scroll_to_mark prev 1
Which will scroll only to marks of type 1.
The full syntax for creating marks The full syntax for creating marks
------------------------------------- -------------------------------------

View File

@ -277,6 +277,22 @@ def toggle_marker(func, rest):
return func, list(parse_marker_spec(ftype, parts)) return func, list(parse_marker_spec(ftype, parts))
@func_with_args('scroll_to_mark')
def scroll_to_mark(func, rest):
parts = rest.split()
if not parts or not rest:
return func, [True, 0]
if len(parts) == 1:
q = parts[0].lower()
if q in ('prev', 'previous', 'next'):
return func, [q != 'next', 0]
try:
return func, [True, max(0, min(int(q), 3))]
except Exception:
raise ValueError('{} is not a valid scroll_to_mark destination'.format(rest))
return func, [parts[0] != 'next', max(0, min(int(parts[1]), 3))]
def parse_key_action(action): def parse_key_action(action):
parts = action.strip().split(maxsplit=1) parts = action.strip().split(maxsplit=1)
func = parts[0] func = parts[0]

View File

@ -665,6 +665,15 @@ __eq__(Line *a, Line *b) {
return a->xnum == b->xnum && memcmp(a->cpu_cells, b->cpu_cells, sizeof(CPUCell) * a->xnum) == 0 && memcmp(a->gpu_cells, b->gpu_cells, sizeof(GPUCell) * a->xnum) == 0; return a->xnum == b->xnum && memcmp(a->cpu_cells, b->cpu_cells, sizeof(CPUCell) * a->xnum) == 0 && memcmp(a->gpu_cells, b->gpu_cells, sizeof(GPUCell) * a->xnum) == 0;
} }
bool
line_has_mark(Line *line, attrs_type mark) {
for (index_type x = 0; x < line->xnum; x++) {
attrs_type m = (line->gpu_cells[x].attrs >> MARK_SHIFT) & MARK_MASK;
if (m && (!mark || mark == m)) return true;
}
return false;
}
static inline void static inline void
report_marker_error(PyObject *marker) { report_marker_error(PyObject *marker) {
if (!PyObject_HasAttrString(marker, "error_reported")) { if (!PyObject_HasAttrString(marker, "error_reported")) {

View File

@ -91,6 +91,7 @@ void historybuf_mark_line_dirty(HistoryBuf *self, index_type y);
void historybuf_refresh_sprite_positions(HistoryBuf *self); void historybuf_refresh_sprite_positions(HistoryBuf *self);
void historybuf_clear(HistoryBuf *self); void historybuf_clear(HistoryBuf *self);
void mark_text_in_line(PyObject *marker, Line *line); void mark_text_in_line(PyObject *marker, Line *line);
bool line_has_mark(Line *, attrs_type mark);
#define as_text_generic(args, container, get_line, lines, columns) { \ #define as_text_generic(args, container, get_line, lines, columns) { \

View File

@ -2293,6 +2293,40 @@ set_marker(Screen *self, PyObject *args) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyObject*
scroll_to_next_mark(Screen *self, PyObject *args) {
int backwards = 1;
unsigned int mark = 0;
if (!PyArg_ParseTuple(args, "|Ip", &mark, &backwards)) return NULL;
if (!screen_has_marker(self) || self->linebuf == self->alt_linebuf) Py_RETURN_FALSE;
if (backwards) {
for (unsigned int y = self->scrolled_by; y < self->historybuf->count; y++) {
historybuf_init_line(self->historybuf, y, self->historybuf->line);
if (line_has_mark(self->historybuf->line, mark)) {
screen_history_scroll(self, y - self->scrolled_by + 1, true);
Py_RETURN_TRUE;
}
}
} else {
Line *line;
for (unsigned int y = self->scrolled_by; y > 0; y--) {
if (y > self->lines) {
historybuf_init_line(self->historybuf, y - self->lines, self->historybuf->line);
line = self->historybuf->line;
} else {
linebuf_init_line(self->linebuf, self->lines - y);
line = self->linebuf->line;
}
if (line_has_mark(line, mark)) {
screen_history_scroll(self, self->scrolled_by - y + 1, false);
Py_RETURN_TRUE;
}
}
}
Py_RETURN_FALSE;
}
static PyObject* static PyObject*
marked_cells(Screen *self, PyObject *o UNUSED) { marked_cells(Screen *self, PyObject *o UNUSED) {
PyObject *ans = PyList_New(0); PyObject *ans = PyList_New(0);
@ -2414,6 +2448,7 @@ static PyMethodDef methods[] = {
MND(copy_colors_from, METH_O) MND(copy_colors_from, METH_O)
MND(set_marker, METH_VARARGS) MND(set_marker, METH_VARARGS)
MND(marked_cells, METH_NOARGS) MND(marked_cells, METH_NOARGS)
MND(scroll_to_next_mark, METH_VARARGS)
{"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""}, {"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""},
{NULL} /* Sentinel */ {NULL} /* Sentinel */

View File

@ -631,4 +631,7 @@ class Window:
if self.current_marker_spec is not None: if self.current_marker_spec is not None:
self.screen.set_marker() self.screen.set_marker()
self.current_marker_spec = None self.current_marker_spec = None
def scroll_to_mark(self, prev=True, mark=0):
self.screen.scroll_to_next_mark(mark, prev)
# }}} # }}}

View File

@ -474,3 +474,17 @@ class TestScreen(BaseTest):
s.set_marker(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)]) self.ae(s.marked_cells(), [(0, 1, 1), (2, 1, 2), (4, 1, 3)])
s = self.create_screen(lines=5, scrollback=10)
for i in range(15):
s.draw(str(i))
if i != 14:
s.carriage_return(), s.linefeed()
s.set_marker(marker_from_regex(r'\d+', 3))
for i in range(10):
self.assertTrue(s.scroll_to_next_mark())
self.ae(s.scrolled_by, i + 1)
self.ae(s.scrolled_by, 10)
for i in range(10):
self.assertTrue(s.scroll_to_next_mark(0, False))
self.ae(s.scrolled_by, 10 - i - 1)
self.ae(s.scrolled_by, 0)