diff --git a/kitty/line-buf.c b/kitty/line-buf.c index 2dcecbeea..7e2233298 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -156,16 +156,12 @@ allocate_line_storage(Line *line, bool initialize) { return 1; } -static PyObject* -create_line_copy(LineBuf *self, PyObject *ynum) { -#define create_line_copy_doc "Create a new Line object that is a copy of the line at ynum. Note that this line has its own copy of the data and does not refer to the data in the LineBuf." +static inline PyObject* create_line_copy_inner(LineBuf* self, index_type y) { Line src, *line; - index_type y = (index_type)PyLong_AsUnsignedLong(ynum); - if (y >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } line = alloc_line(); if (line == NULL) return PyErr_NoMemory(); src.xnum = self->xnum; line->xnum = self->xnum; - if (!allocate_line_storage(line, 0)) { Py_DECREF(line); return NULL; } + if (!allocate_line_storage(line, 0)) { Py_CLEAR(line); return PyErr_NoMemory(); } line->ynum = y; line->continued = self->continued_map[y]; INIT_LINE(self, &src, self->line_map[y]); @@ -173,6 +169,14 @@ create_line_copy(LineBuf *self, PyObject *ynum) { return (PyObject*)line; } +static PyObject* +create_line_copy(LineBuf *self, PyObject *ynum) { +#define create_line_copy_doc "Create a new Line object that is a copy of the line at ynum. Note that this line has its own copy of the data and does not refer to the data in the LineBuf." + index_type y = (index_type)PyLong_AsUnsignedLong(ynum); + if (y >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } + return create_line_copy_inner(self, y); +} + static PyObject* copy_line_to(LineBuf *self, PyObject *args) { #define copy_line_to_doc "Copy the line at ynum to the provided line object." @@ -199,12 +203,7 @@ clear_line(LineBuf *self, PyObject *val) { Py_RETURN_NONE; } -static PyObject* -index(LineBuf *self, PyObject *args) { -#define index_doc "index(top, bottom) -> Scroll all lines in the range [top, bottom] by one upwards. After scrolling, bottom will be top." - unsigned int top, bottom; - if (!PyArg_ParseTuple(args, "II", &top, &bottom)) return NULL; - if (top >= self->ynum - 1 || bottom >= self->ynum || bottom <= top) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } +static inline void index_inner(LineBuf* self, index_type top, index_type bottom) { index_type old_top = self->line_map[top]; bool old_cont = self->continued_map[top]; for (index_type i = top; i < bottom; i++) { @@ -213,6 +212,15 @@ index(LineBuf *self, PyObject *args) { } self->line_map[bottom] = old_top; self->continued_map[bottom] = old_cont; +} + +static PyObject* +index(LineBuf *self, PyObject *args) { +#define index_doc "index(top, bottom) -> Scroll all lines in the range [top, bottom] by one upwards. After scrolling, bottom will be top." + unsigned int top, bottom; + if (!PyArg_ParseTuple(args, "II", &top, &bottom)) return NULL; + if (top >= self->ynum - 1 || bottom >= self->ynum || bottom <= top) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } + index_inner(self, top, bottom); Py_RETURN_NONE; } @@ -383,63 +391,43 @@ static inline void copy_range(Line *src, index_type src_at, Line* dest, index_ty memcpy(dest->combining_chars + dest_at, src->combining_chars + src_at, num * sizeof(combining_type)); } -typedef Line* (*nextlinefunc)(void *, bool *); -typedef void (*setcontfunc)(void *, bool val); - -static Py_ssize_t rewrap_inner(nextlinefunc src_next, void *src_data, nextlinefunc dest_next, void *dest_data, index_type src_col, index_type src_xnum, index_type dest_xnum, setcontfunc set_continued, bool *oom) { - Line *src, *dest; - Py_ssize_t src_x = src_col, dest_x = dest_xnum, num; - src = src_next(src_data, oom); dest = dest_next(dest_data, oom); - bool prev_line_was_continued = false; - while (src && dest) { - if (!prev_line_was_continued && src_x == src->xnum) { - // Trim trailing whitespace - while(src_x && (src->chars[src_x - 1] & CHAR_MASK) == 32) src_x--; - } - num = MIN(dest_x, src_x); - if (num > 0) { - dest_x -= num; src_x -= num; - copy_range(src, src_x, dest, dest_x, num); - } - if (src_x <= 0) { - prev_line_was_continued = src->continued; - src = src_next(src_data, oom); - src_x = src_xnum; - if (!prev_line_was_continued) { - // Hard break, finalize this line - if (dest_x > 0) { // Left align dest line - left_shift_line(dest, 0, dest_x); - CLEAR_LINE(dest, dest_xnum - dest_x, dest_x); - } - if (src) { // Only start a new line if there is more in src - if (set_continued != NULL) set_continued(dest_data, false); - else dest->continued = false; - dest = dest_next(dest_data, oom); - } - dest_x = dest_xnum; - } - } - if (dest_x <= 0) { - if (set_continued != NULL) set_continued(dest_data, true); - else dest->continued = true; - dest = dest_next(dest_data, oom); - dest_x = dest_xnum; - } - - } - return src_x; +#define next_dest_line(continued) {\ + if (dest_y >= dest->ynum - 1) { \ + index_inner(dest, 0, dest->ynum - 1); \ + PyObject *l = create_line_copy_inner(dest, dest_y); \ + if (l == NULL) return false; \ + if (PyList_Append(extra_lines, l) != 0) { Py_CLEAR(l); return false; } \ + } else dest_y++; \ + INIT_LINE(dest, dest->line, dest->line_map[dest_y]); \ + dest->continued_map[dest_y] = continued; \ + dest_x = 0; \ } -static Line* reverse_line_iter(LineBuf *lb, bool UNUSED *oom) { - if (lb->line->ynum <= 0) return NULL; - lb->line->ynum--; - INIT_LINE(lb, lb->line, lb->line->ynum); - lb->line->continued = lb->continued_map[lb->line->ynum]; - return lb->line; -} +static bool rewrap_inner(LineBuf *src, LineBuf *dest, const index_type src_limit, PyObject *extra_lines) { + bool src_line_is_continued = false; + index_type src_y = 0, src_x = 0, dest_x = 0, dest_y = 0, num = 0, src_x_limit = 0; + INIT_LINE(dest, dest->line, dest->line_map[dest_y]); -static void rewrap_set_continued(LineBuf *lb, bool val) { - lb->continued_map[lb->line->ynum] = val; + do { + INIT_LINE(src, src->line, src->line_map[src_y]); + src_line_is_continued = src_y < src->ynum - 1 ? src->continued_map[src_y + 1] : false; + src_x_limit = src->xnum; + if (!src_line_is_continued) { + // Trim trailing white-space since there is a hard line break at the end of this line + while(src_x_limit && (src->line->chars[src->line->xnum - 1] & CHAR_MASK) == 32) src_x_limit--; + + } + while (src_x < src_x_limit) { + if (dest_x >= dest->xnum) next_dest_line(true); + num = MIN(src->line->xnum - src_x, dest->xnum - dest_x); + copy_range(src->line, src_x, dest->line, dest_x, num); + src_x += num; dest_x += num; + } + src_y++; src_x = 0; + if (!src_line_is_continued && src_y < src_limit) next_dest_line(false); + } while (src_y < src_limit); + dest->line->ynum = dest_y; + return true; } static PyObject* @@ -447,8 +435,7 @@ rewrap(LineBuf *self, PyObject *val) { LineBuf* other; index_type first, i; int cursor_y = -1; - Py_ssize_t src_x, src_y, dest_y; - bool oom = false; + bool is_empty = true; if (!PyObject_TypeCheck(val, &LineBuf_Type)) { PyErr_SetString(PyExc_TypeError, "Not a LineBuf object"); return NULL; } other = (LineBuf*) val; @@ -468,7 +455,6 @@ rewrap(LineBuf *self, PyObject *val) { // Find the first line that contains some content Py_BEGIN_ALLOW_THREADS; for (first = self->ynum - 1; true; first--) { - bool is_empty = true; char_type *chars = self->chars + self->xnum * first; for(i = 0; i < self->xnum; i++) { if ((chars[i] & CHAR_MASK) != 32) { is_empty = false; break; } @@ -479,25 +465,13 @@ rewrap(LineBuf *self, PyObject *val) { if (first == 0) { cursor_y = 0; goto end; } // All lines are empty - // Initialize iterators - self->line->ynum = first + 1; self->line->xnum = self->xnum; - other->line->ynum = other->ynum; other->line->xnum = other->xnum; - Py_BEGIN_ALLOW_THREADS; - src_x = rewrap_inner((nextlinefunc)reverse_line_iter, self, (nextlinefunc)reverse_line_iter, other, self->xnum, self->xnum, other->xnum, (setcontfunc)rewrap_set_continued, &oom); - Py_END_ALLOW_THREADS; - if (oom) { Py_CLEAR(ret); return PyErr_NoMemory(); } - src_y = self->line->ynum; dest_y = other->line->ynum; - - if (dest_y > 0) { - // Shift lines up so that untouched lines are at the bottom - do_delete(other, dest_y, 0, other->ynum - 1); - cursor_y = MAX(0, other->ynum - (dest_y + 1)); - } else cursor_y = other->ynum - 1; - - if (src_y > 0 || (src_y == 0 && src_x > 0)) { - // TODO: return extra lines + if (!rewrap_inner(self, other, first + 1, ret)) { + Py_CLEAR(ret); + return PyErr_NoMemory(); } + cursor_y = other->line->ynum; + end: return Py_BuildValue("Ni", ret, cursor_y); } diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index 9b7978ff1..01383aff0 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -199,10 +199,6 @@ class TestDataTypes(BaseTest): lb.rewrap(lb2) for i in range(lb.ynum): self.ae(lb2.line(i), lb.line(i)) - lb2 = LineBuf(3, 5) - lb.rewrap(lb2) - for i in range(lb2.ynum): - self.ae(lb2.line(i), lb.line(i + 2)) lb2 = LineBuf(8, 5) cy = lb.rewrap(lb2)[1] self.ae(cy, 4) @@ -211,6 +207,11 @@ class TestDataTypes(BaseTest): empty = LineBuf(1, lb2.xnum) for i in range(lb.ynum, lb2.ynum): self.ae(str(lb2.line(i)), str(empty.line(0))) + lb2 = LineBuf(3, 5) + extra, cy = lb.rewrap(lb2) + self.ae(cy, 2) + for i in range(lb2.ynum): + self.ae(lb2.line(i), lb.line(i + 2)) def test_rewrap_wider(self): ' New buffer wider ' @@ -220,7 +221,8 @@ class TestDataTypes(BaseTest): lb.set_continued(1, True) lb2 = LineBuf(2, 6) lb.rewrap(lb2) - self.ae(str(lb2.line(1)), '456789') + self.ae(str(lb2.line(0)), '012345') + self.ae(str(lb2.line(1)), '6789 ') def test_utils(self): d = codecs.getincrementaldecoder('utf-8')('strict').decode