diff --git a/kitty/boss.py b/kitty/boss.py index 4380c88f5..2abc1a7f6 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -263,7 +263,7 @@ class Boss: passthrough = f() if not passthrough: return - if window.char_grid.scrolled_by and key not in MODIFIER_KEYS and action == GLFW_PRESS: + if window.screen.scrolled_by and key not in MODIFIER_KEYS and action == GLFW_PRESS: window.scroll_end() data = get_sent_data( self.opts.send_text_map, key, scancode, mods, window, action diff --git a/kitty/char_grid.py b/kitty/char_grid.py index 4969537c5..f214bea84 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -126,10 +126,8 @@ class CharGrid: self.vao_id = None self.current_selection = Selection() self.screen_reversed = False - self.scroll_changed = False self.last_rendered_selection = None self.render_data = None - self.scrolled_by = 0 self.data_buffer_size = None self.screen = screen self.opts = opts @@ -174,23 +172,13 @@ class CharGrid: if dirtied: self.screen.mark_as_dirty() - def scroll(self, amt, upwards=True): - if not isinstance(amt, int): - amt = {'line': 1, 'page': self.screen.lines - 1, 'full': self.screen.historybuf.count}[amt] - if not upwards: - amt *= -1 - y = max(0, min(self.scrolled_by + amt, self.screen.historybuf.count)) - if y != self.scrolled_by: - self.scrolled_by = y - self.scroll_changed = True - def update_cell_data(self, cell_program): if self.data_buffer_size is None: return clear_selection = self.screen.is_dirty with cell_program.mapped_vertex_data(self.vao_id, self.data_buffer_size) as address: - cursor_changed, self.scrolled_by, self.screen_reversed = self.screen.update_cell_data( - address, self.scrolled_by, False) + cursor_changed, self.screen_reversed = self.screen.update_cell_data( + address, False) if clear_selection: self.current_selection.clear() @@ -214,12 +202,12 @@ class CharGrid: if is_press: self.current_selection.start_x = self.current_selection.end_x = x self.current_selection.start_y = self.current_selection.end_y = y - self.current_selection.start_scrolled_by = self.current_selection.end_scrolled_by = self.scrolled_by + self.current_selection.start_scrolled_by = self.current_selection.end_scrolled_by = self.screen.scrolled_by self.current_selection.in_progress = True elif self.current_selection.in_progress: self.current_selection.end_x = x self.current_selection.end_y = y - self.current_selection.end_scrolled_by = self.scrolled_by + self.current_selection.end_scrolled_by = self.screen.scrolled_by if is_press is False: self.current_selection.in_progress = False ps = self.text_for_selection() @@ -229,7 +217,7 @@ class CharGrid: def has_url_at(self, x, y): x, y = self.cell_for_pos(x, y) if x is not None: - l = self.screen_line(y) + l = self.screen.visual_line(y) if l is not None: text = str(l) for m in self.url_pat.finditer(text): @@ -240,7 +228,7 @@ class CharGrid: def click_url(self, x, y): x, y = self.cell_for_pos(x, y) if x is not None: - l = self.screen_line(y) + l = self.screen.visual_line(y) if l is not None: text = str(l) for m in self.url_pat.finditer(text): @@ -258,24 +246,20 @@ class CharGrid: if url: open_url(url, self.opts.open_url_with) - def screen_line(self, y): - ' Return the Line object corresponding to the yth line on the rendered screen ' - return self.screen.visual_line(y, self.scrolled_by) - def multi_click(self, count, x, y): x, y = self.cell_for_pos(x, y) if x is not None: - line = self.screen_line(y) + line = self.screen.visual_line(y) if line is not None and count in (2, 3): s = self.current_selection s.start_scrolled_by = s.end_scrolled_by = self.scrolled_by s.start_y = s.end_y = y s.in_progress = False if count == 2: - s.start_x, xlimit = self.screen.selection_range_for_word(x, y, self.scrolled_by, self.opts.select_by_word_characters) + s.start_x, xlimit = self.screen.selection_range_for_word(x, y, self.opts.select_by_word_characters) s.end_x = max(s.start_x, xlimit - 1) elif count == 3: - s.start_x, xlimit = self.screen.selection_range_for_line(y, self.scrolled_by) + s.start_x, xlimit = self.screen.selection_range_for_line(y) s.end_x = max(s.start_x, xlimit - 1) ps = self.text_for_selection() if ps: @@ -289,22 +273,21 @@ class CharGrid: def text_for_selection(self, sel=None): s = sel or self.current_selection - start, end = s.limits(self.scrolled_by, self.screen.lines, self.screen.columns) - return ''.join(self.screen.text_for_selection(self.scrolled_by, *start, *end)) + start, end = s.limits(self.screen.scrolled_by, self.screen.lines, self.screen.columns) + return ''.join(self.screen.text_for_selection(*start, *end)) def prepare_for_render(self, cell_program): if self.vao_id is None: self.vao_id = cell_program.create_sprite_map() - if self.scroll_changed or self.screen.is_dirty: + if self.screen.scroll_changed or self.screen.is_dirty: self.update_cell_data(cell_program) - self.scroll_changed = False sg = self.render_data - start, end = self.current_selection.limits(self.scrolled_by, self.screen.lines, self.screen.columns) - sel = start, end, self.scrolled_by + start, end = self.current_selection.limits(self.screen.scrolled_by, self.screen.lines, self.screen.columns) + sel = start, end, self.screen.scrolled_by selection_changed = sel != self.last_rendered_selection if selection_changed: with cell_program.mapped_vertex_data(self.vao_id, self.selection_buffer_size, bufnum=1) as address: - self.screen.apply_selection(address, self.selection_buffer_size, self.scrolled_by, *start, *end) + self.screen.apply_selection(address, self.selection_buffer_size, *start, *end) self.last_rendered_selection = sel return sg @@ -313,7 +296,7 @@ class CharGrid: def render_cursor(self, sg, cursor_program, is_focused): cursor = self.current_cursor - if not self.screen.cursor_visible or self.scrolled_by: + if not self.screen.cursor_visible or self.screen.scrolled_by: return def width(w=2, vert=True): diff --git a/kitty/data-types.h b/kitty/data-types.h index fa1f0620b..8557d03cc 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -223,12 +223,12 @@ typedef struct { typedef struct { PyObject_HEAD - unsigned int columns, lines, margin_top, margin_bottom, charset; + unsigned int columns, lines, margin_top, margin_bottom, charset, scrolled_by; uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset; bool use_latin1; Cursor *cursor; SavepointBuffer main_savepoints, alt_savepoints; - PyObject *callbacks, *is_dirty, *cursor_changed; + PyObject *callbacks, *is_dirty, *cursor_changed, *scroll_changed; LineBuf *linebuf, *main_linebuf, *alt_linebuf; HistoryBuf *historybuf; unsigned int history_line_added_count; diff --git a/kitty/screen.c b/kitty/screen.c index 695fb9a99..69a5d7dce 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -5,6 +5,12 @@ * Distributed under terms of the GPL3 license. */ +#define SCROLL_LINE -1 +#define SCROLL_PAGE -2 +#define SCROLL_FULL -3 + +#define EXTRA_INIT PyModule_AddIntMacro(module, SCROLL_LINE); PyModule_AddIntMacro(module, SCROLL_PAGE); PyModule_AddIntMacro(module, SCROLL_FULL); + #include "data-types.h" #include "lineops.h" #include "screen.h" @@ -61,6 +67,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { self->write_buf = NULL; self->modes = empty_modes; self->cursor_changed = Py_True; self->is_dirty = Py_True; + self->scroll_changed = Py_False; self->margin_top = 0; self->margin_bottom = self->lines - 1; self->history_line_added_count = 0; RESET_CHARSETS; @@ -1079,13 +1086,13 @@ line(Screen *self, PyObject *val) { } static inline Line* -visual_line_(Screen *self, index_type y, index_type scrolled_by) { - if (scrolled_by) { - if (y < scrolled_by) { - historybuf_init_line(self->historybuf, scrolled_by - 1 - y, self->historybuf->line); +visual_line_(Screen *self, index_type y) { + if (self->scrolled_by) { + if (y < self->scrolled_by) { + historybuf_init_line(self->historybuf, self->scrolled_by - 1 - y, self->historybuf->line); return self->historybuf->line; } - y -= scrolled_by; + y -= self->scrolled_by; } linebuf_init_line(self->linebuf, y); return self->linebuf->line; @@ -1094,10 +1101,10 @@ visual_line_(Screen *self, index_type y, index_type scrolled_by) { static PyObject* visual_line(Screen *self, PyObject *args) { // The line corresponding to the yth visual line, taking into account scrolling - unsigned int y, scrolled_by; - if (!PyArg_ParseTuple(args, "II", &y, &scrolled_by)) return NULL; + unsigned int y; + if (!PyArg_ParseTuple(args, "I", &y)) return NULL; if (y >= self->lines) { Py_RETURN_NONE; } - return Py_BuildValue("O", visual_line_(self, y, scrolled_by)); + return Py_BuildValue("O", visual_line_(self, y)); } static PyObject* @@ -1214,22 +1221,22 @@ update_cell_data(Screen *self, PyObject *args) { PyObject *dp; uint8_t *data; int force_screen_refresh; - unsigned int scrolled_by; unsigned int history_line_added_count = self->history_line_added_count; - if (!PyArg_ParseTuple(args, "O!Ip", &PyLong_Type, &dp, &scrolled_by, &force_screen_refresh)) return NULL; + if (!PyArg_ParseTuple(args, "O!p", &PyLong_Type, &dp, &force_screen_refresh)) return NULL; data = PyLong_AsVoidPtr(dp); PyObject *cursor_changed = self->cursor_changed; - if (scrolled_by) scrolled_by = MIN(scrolled_by + history_line_added_count, self->historybuf->count); + if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count); reset_dirty(self); - for (index_type y = 0; y < MIN(self->lines, scrolled_by); y++) { - historybuf_init_line(self->historybuf, scrolled_by - 1 - y, self->historybuf->line); + self->scroll_changed = Py_False; + for (index_type y = 0; y < MIN(self->lines, self->scrolled_by); y++) { + historybuf_init_line(self->historybuf, self->scrolled_by - 1 - y, self->historybuf->line); update_line_data(self->historybuf->line, y, data); } - for (index_type y = scrolled_by; y < self->lines; y++) { - linebuf_init_line(self->linebuf, y - scrolled_by); + for (index_type y = self->scrolled_by; y < self->lines; y++) { + linebuf_init_line(self->linebuf, y - self->scrolled_by); update_line_data(self->linebuf->line, y, data); } - return Py_BuildValue("OIO", cursor_changed, scrolled_by, self->modes.mDECSCNM ? Py_True : Py_False); + return Py_BuildValue("OO", cursor_changed, self->modes.mDECSCNM ? Py_True : Py_False); } static inline bool @@ -1239,14 +1246,14 @@ is_selection_empty(Screen *self, unsigned int start_x, unsigned int start_y, uns static PyObject* apply_selection(Screen *self, PyObject *args) { - unsigned int size, start_x, end_x, start_y, end_y, scrolled_by; + unsigned int size, start_x, end_x, start_y, end_y; PyObject *l; - if (!PyArg_ParseTuple(args, "O!IIIIII", &PyLong_Type, &l, &size, &scrolled_by, &start_x, &start_y, &end_x, &end_y)) return NULL; + if (!PyArg_ParseTuple(args, "O!IIIII", &PyLong_Type, &l, &size, &start_x, &start_y, &end_x, &end_y)) return NULL; float *data = PyLong_AsVoidPtr(l); memset(data, 0, size); if (is_selection_empty(self, start_x, start_y, end_x, end_y)) { Py_RETURN_NONE; } for (index_type y = start_y; y <= end_y; y++) { - Line *line = visual_line_(self, y, scrolled_by); + Line *line = visual_line_(self, y); index_type xlimit = xlimit_for_line(line); if (y == end_y) xlimit = MIN(end_x + 1, xlimit); float *line_start = data + self->columns * y; @@ -1257,14 +1264,14 @@ apply_selection(Screen *self, PyObject *args) { static PyObject* text_for_selection(Screen *self, PyObject *args) { - unsigned int start_x, end_x, start_y, end_y, scrolled_by; - if (!PyArg_ParseTuple(args, "IIIII", &scrolled_by, &start_x, &start_y, &end_x, &end_y)) return NULL; + unsigned int start_x, end_x, start_y, end_y; + if (!PyArg_ParseTuple(args, "IIII", &start_x, &start_y, &end_x, &end_y)) return NULL; if (is_selection_empty(self, start_x, start_y, end_x, end_y)) return PyTuple_New(0); Py_ssize_t i = 0, num_of_lines = end_y - start_y + 1; PyObject *ans = PyTuple_New(num_of_lines); if (ans == NULL) return PyErr_NoMemory(); for (index_type y = start_y; y <= end_y; y++, i++) { - Line *line = visual_line_(self, y, scrolled_by); + Line *line = visual_line_(self, y); index_type xlimit = xlimit_for_line(line); if (y == end_y) xlimit = MIN(end_x + 1, xlimit); index_type xstart = (y == start_y ? start_x : 0); @@ -1278,10 +1285,10 @@ text_for_selection(Screen *self, PyObject *args) { static PyObject* selection_range_for_line(Screen *self, PyObject *args) { - unsigned int y, scrolled_by; - if (!PyArg_ParseTuple(args, "II", &y, &scrolled_by)) return NULL; + unsigned int y; + if (!PyArg_ParseTuple(args, "I", &y)) return NULL; if (y >= self->lines) { PyErr_SetString(PyExc_ValueError, "y larger than lines"); return NULL; } - Line *line = visual_line_(self, y, scrolled_by); + Line *line = visual_line_(self, y); index_type xlimit = line->xnum, xstart = 0; while (xlimit > 0 && CHAR_IS_BLANK(line->cells[xlimit - 1].ch)) xlimit--; while (xstart < xlimit && CHAR_IS_BLANK(line->cells[xstart].ch)) xstart++; @@ -1298,13 +1305,13 @@ has_char(int kind, void *data, Py_ssize_t sz, char_type ch) { static PyObject* selection_range_for_word(Screen *self, PyObject *args) { - unsigned int x, y, scrolled_by; + unsigned int x, y; PyObject *extra_chars; - if (!PyArg_ParseTuple(args, "IIIU", &x, &y, &scrolled_by, &extra_chars)) return NULL; + if (!PyArg_ParseTuple(args, "IIU", &x, &y, &extra_chars)) return NULL; if (PyUnicode_READY(extra_chars) != 0) return NULL; if (y >= self->lines) { PyErr_SetString(PyExc_ValueError, "y larger than lines"); return NULL; } if (x >= self->columns) { PyErr_SetString(PyExc_ValueError, "x larger than columns"); return NULL; } - Line *line = visual_line_(self, y, scrolled_by); + Line *line = visual_line_(self, y); #define is_ok(x) (is_word_char((line->cells[x].ch) & CHAR_MASK) || has_char(PyUnicode_KIND(extra_chars), PyUnicode_DATA(extra_chars), PyUnicode_GET_LENGTH(extra_chars), (line->cells[x].ch) & CHAR_MASK)) if (!is_ok(x)) Py_BuildValue("II", x, x + 1); unsigned int start = x, end = x; @@ -1314,6 +1321,38 @@ selection_range_for_word(Screen *self, PyObject *args) { #undef is_ok } +static PyObject* +scroll(Screen *self, PyObject *args) { + int amt, upwards; + if (!PyArg_ParseTuple(args, "ip", &amt, &upwards)) return NULL; + switch(amt) { + case SCROLL_LINE: + amt = 1; + break; + case SCROLL_PAGE: + amt = self->lines - 1; + break; + case SCROLL_FULL: + amt = self->historybuf->count; + break; + default: + amt = MAX(0, amt); + break; + } + if (!upwards) { + amt = MIN((unsigned int)amt, self->scrolled_by); + amt *= -1; + } + if (amt != 0) { + unsigned int new_scroll = MIN(self->scrolled_by + amt, self->historybuf->count); + if (new_scroll != self->scrolled_by) { + self->scrolled_by = new_scroll; + self->scroll_changed = Py_True; + Py_RETURN_TRUE; + } + } + Py_RETURN_FALSE; +} static PyObject* mark_as_dirty(Screen *self) { @@ -1403,6 +1442,7 @@ static PyMethodDef methods[] = { MND(selection_range_for_line, METH_VARARGS) MND(selection_range_for_word, METH_VARARGS) MND(text_for_selection, METH_VARARGS) + MND(scroll, METH_VARARGS) MND(toggle_alt_screen, METH_NOARGS) MND(reset_callbacks, METH_NOARGS) {"update_cell_data", (PyCFunction)update_cell_data, METH_VARARGS, ""}, @@ -1432,11 +1472,13 @@ static PyGetSetDef getsetters[] = { static PyMemberDef members[] = { {"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"}, {"cursor_changed", T_OBJECT_EX, offsetof(Screen, cursor_changed), 0, "cursor_changed"}, + {"scroll_changed", T_OBJECT, offsetof(Screen, scroll_changed), 0, "scroll_changed"}, {"is_dirty", T_OBJECT_EX, offsetof(Screen, is_dirty), 0, "is_dirty"}, {"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"}, {"color_profile", T_OBJECT_EX, offsetof(Screen, color_profile), READONLY, "color_profile"}, {"linebuf", T_OBJECT_EX, offsetof(Screen, linebuf), READONLY, "linebuf"}, {"historybuf", T_OBJECT_EX, offsetof(Screen, historybuf), READONLY, "historybuf"}, + {"scrolled_by", T_UINT, offsetof(Screen, scrolled_by), READONLY, "scrolled_by"}, {"lines", T_UINT, offsetof(Screen, lines), READONLY, "lines"}, {"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"}, {"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"}, diff --git a/kitty/window.py b/kitty/window.py index 83256f209..c8fe80f25 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -17,8 +17,8 @@ from .fast_data_types import ( ANY_MODE, BRACKETED_PASTE_END, BRACKETED_PASTE_START, GLFW_KEY_DOWN, GLFW_KEY_LEFT_SHIFT, GLFW_KEY_RIGHT_SHIFT, GLFW_KEY_UP, GLFW_MOD_SHIFT, GLFW_MOUSE_BUTTON_1, GLFW_MOUSE_BUTTON_4, GLFW_MOUSE_BUTTON_5, - GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE, MOTION_MODE, Screen, - glfw_post_empty_event + GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE, MOTION_MODE, + SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, Screen, glfw_post_empty_event ) from .keys import get_key_map from .mouse import DRAG, MOVE, PRESS, RELEASE, encode_mouse_event @@ -263,7 +263,7 @@ class Window: return upwards = s > 0 if self.screen.is_main_linebuf(): - self.char_grid.scroll(abs(s), upwards) + self.screen.scroll(abs(s), upwards) glfw_post_empty_event() else: mode = self.screen.mouse_tracking_mode() @@ -281,7 +281,7 @@ class Window: self.write_to_child(k * abs(s)) def buf_toggled(self, is_main_linebuf): - self.char_grid.scroll('full', False) + self.screen.scroll(SCROLL_FULL, False) def render_cells(self, render_data, program, sprites): invert_colors = False @@ -319,31 +319,31 @@ class Window: def scroll_line_up(self): if self.screen.is_main_linebuf(): - self.char_grid.scroll('line', True) + self.screen.scroll(SCROLL_LINE, True) glfw_post_empty_event() def scroll_line_down(self): if self.screen.is_main_linebuf(): - self.char_grid.scroll('line', False) + self.screen.scroll(SCROLL_LINE, False) glfw_post_empty_event() def scroll_page_up(self): if self.screen.is_main_linebuf(): - self.char_grid.scroll('page', True) + self.screen.scroll(SCROLL_PAGE, True) glfw_post_empty_event() def scroll_page_down(self): if self.screen.is_main_linebuf(): - self.char_grid.scroll('page', False) + self.screen.scroll(SCROLL_PAGE, False) glfw_post_empty_event() def scroll_home(self): if self.screen.is_main_linebuf(): - self.char_grid.scroll('full', True) + self.screen.scroll(SCROLL_FULL, True) glfw_post_empty_event() def scroll_end(self): if self.screen.is_main_linebuf(): - self.char_grid.scroll('full', False) + self.screen.scroll(SCROLL_FULL, False) glfw_post_empty_event() # }}}