Allow scrolling to marks
This commit is contained in:
parent
c68b302fa2
commit
936f2186cc
@ -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
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
|
|||||||
@ -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]
|
||||||
|
|||||||
@ -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")) {
|
||||||
|
|||||||
@ -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) { \
|
||||||
|
|||||||
@ -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 */
|
||||||
|
|||||||
@ -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)
|
||||||
# }}}
|
# }}}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user