From a11dadcde8096f9efdcdd7a8aaef77e2fc66299a Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Sat, 10 Jun 2023 23:01:13 +0000 Subject: [PATCH] editor: finish implementing shadow_paste This adds a new overwrite_line is very useful for dealing with cow-related boilerplate. There is some refactoring possible (and perhaps accidental correctness improvement) if more functions used 'overwrite_line'. --- editor/editor.hpp | 149 ++++++++++++++++++++++++++++++---- editor/main_saturn.cpp | 6 ++ editor/test_editor.cpp | 180 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 312 insertions(+), 23 deletions(-) diff --git a/editor/editor.hpp b/editor/editor.hpp index d4dfcc9..f8abb14 100644 --- a/editor/editor.hpp +++ b/editor/editor.hpp @@ -10,8 +10,8 @@ namespace editor { template struct line { uint8_t buf[C]; - int16_t length; - int16_t refcount; + int32_t length; + int32_t refcount; static_assert(C < 32768); }; @@ -106,6 +106,11 @@ struct buffer { const int32_t col_end_ix); inline constexpr bool shadow_copy(); inline constexpr bool shadow_cut(); + inline constexpr void overwrite_line(line *& dst, + const int32_t dst_col, + line * src, + const int32_t src_col); + inline constexpr bool shadow_paste(); inline constexpr void scroll_left(); inline constexpr void scroll_right(); inline constexpr void scroll_up(); @@ -237,6 +242,7 @@ template inline constexpr bool buffer::backspace() { if (this->mode == mode::mark) { + this->mode = mode::normal; return mark_delete(); } @@ -266,8 +272,8 @@ inline constexpr bool buffer::backspace() // c // 01234 line * l = mutref(this->lines[cur.row]); - auto length = l->length - cur.col; - if (length > 0) move(&l->buf[cur.col-1], &l->buf[cur.col], length); + int32_t length = l->length - cur.col; + move(&l->buf[cur.col-1], &l->buf[cur.col], length); cur.col--; scroll_left(); @@ -503,12 +509,10 @@ inline constexpr void buffer::selection_delete(const selection& sel) // 0cd lmin->length -= (lmin->length - sel.min->col); - int32_t move_length = min(static_cast(lmax->length - sel.max->col), - static_cast(C - lmin->length)); - if (move_length > 0) - move(&lmin->buf[sel.min->col], - &lmax->buf[sel.max->col], - move_length); + int32_t move_length = min(lmax->length - sel.max->col, C - lmin->length); + move(&lmin->buf[sel.min->col], + &lmax->buf[sel.max->col], + move_length); lmin->length += move_length; // delete max @@ -535,9 +539,6 @@ inline constexpr void buffer::selection_delete(const selection& sel) template inline constexpr bool buffer::mark_delete() { - if (this->mode != mode::mark) - return false; - const selection sel = mark_get(); this->selection_delete(sel); @@ -550,8 +551,6 @@ inline constexpr bool buffer::mark_delete() if (min.col < cur.col) { cur.col = min.col; scroll_right(); } else { cur.col = min.col; scroll_left(); } - this->mode = mode::normal; - return true; } @@ -642,6 +641,126 @@ inline constexpr bool buffer::shadow_cut() this->shadow_copy(); + this->mark_delete(); + + this->mode = mode::normal; + + return true; +} + +template +inline constexpr void buffer::overwrite_line(line *& dst, + const int32_t dst_col, + line * src, + const int32_t src_col) +{ + if (src == nullptr) { + if (dst_col == 0) dst = nullptr; + //else do nothing + } else if (dst_col == 0 && src_col == 0 && + line_length(src) >= line_length(dst)) { + if (dst != nullptr) decref(dst); + dst = incref(src); + } else { + line * dstmut = mutref(dst); + int32_t copy_length = min(src->length - src_col, C - dst_col); + move(&dstmut->buf[dst_col], + &src->buf[src_col], + copy_length); + + // v + // 012345 + // 2 + 4 = 6 + dstmut->length = max(dstmut->length, dst_col + copy_length); + } +} + +template +inline constexpr bool buffer::shadow_paste() +{ + if (this->mode == mode::mark) + this->mode = mode::normal; + + editor::cursor& cur = this->cursor; + + if (this->shadow.length == 1) { + + line * ls = this->shadow.lines[0]; + line *& lc = this->lines[cur.row]; + // dst, dst_col + overwrite_line(lc, cur.col + ls->length, // (dst_col = 2 + 3 = 5) + // src, src_col + lc, cur.col); // (length = 5 - 2 = 3) + // now copy shadow to lc + overwrite_line(lc, cur.col, + ls, 0); + + cur.col += ls->length; + scroll_right(); + } else { + // (qw + // er + // gh) + // + // aBcd + // zyx (1) + // + // aqw + // er (cow) + // ghBcd + // zyx (3) + + int32_t last_line_offset = (this->shadow.length - 1); // (2) + + line *& first_line_dst = this->lines[cur.row]; + line * first_line_src = this->shadow.lines[0]; + line *& last_line_dst = this->lines[cur.row + last_line_offset]; + line * last_line_src = this->shadow.lines[last_line_offset]; + + int32_t shift_first_ix = cur.row + 1; + int32_t shift_lines = this->length - shift_first_ix; // (2 - 1) + + move(&this->lines[cur.row + this->shadow.length], // 3 + &this->lines[shift_first_ix], // 1 + (sizeof (line*)) * shift_lines); + + // cow all lines other than the first and last + for (int ix = 1; ix < last_line_offset; ix++) { // ix < 2, max(ix) == 1 + this->lines[cur.row + ix] = incref(this->shadow.lines[ix]); + } + + // copy 'bcd' to last_line_dst + // dst, dst_col + overwrite_line(last_line_dst, line_length(last_line_src), + // src, src_col + first_line_dst, cur.col); + + // shrink first_line_dst to '1' + if (first_line_dst != nullptr) first_line_dst->length = cur.col; + + // copy 'qw' to first_line_dst + overwrite_line(first_line_dst, cur.col, + first_line_src, 0); + + // copy 'gh' to last_line_dst + overwrite_line(last_line_dst, 0, + last_line_src, 0); + + this->length += last_line_offset; + + cur.row += last_line_offset; + scroll_down(); + + int32_t new_col = line_length(last_line_src); + if (new_col < cur.col) { + cur.col = new_col; + scroll_right(); + } else { + cur.col = new_col; + scroll_left(); + } + } + return true; } diff --git a/editor/main_saturn.cpp b/editor/main_saturn.cpp index d410fd1..1f11694 100644 --- a/editor/main_saturn.cpp +++ b/editor/main_saturn.cpp @@ -134,6 +134,8 @@ inline void keyboard_regular_key(const enum keysym k) case keysym::N : buffer.cursor_down(); break; case keysym::A : buffer.cursor_home(); break; case keysym::E : buffer.cursor_end(); break; + case keysym::Y : buffer.shadow_paste(); break; + case keysym::W : buffer.shadow_cut(); break; default: break; } break; @@ -141,6 +143,10 @@ inline void keyboard_regular_key(const enum keysym k) case MODIFIER_LEFT_ALT: [[fallthrough]]; case MODIFIER_RIGHT_ALT: [[fallthrough]]; case MODIFIER_BOTH_ALT: + switch (k) { + case keysym::W : buffer.shadow_copy(); break; + default: break; + } break; default: break; diff --git a/editor/test_editor.cpp b/editor/test_editor.cpp index a1970ed..2d762a6 100644 --- a/editor/test_editor.cpp +++ b/editor/test_editor.cpp @@ -561,6 +561,176 @@ void test_selection_delete() assert(b.lines[2]->buf[3] == 'r'); } +void test_shadow_paste_oneline() +{ + buffer<8, 8> b {4, 2}; + + b.put('q'); + b.put('w'); + b.put('r'); + b.mark_set(); + b.cursor_home(); + b.shadow_copy(); + b.mark_delete(); + assert(decltype(b)::line_length(b.lines[0]) == 0); + assert(b.cursor.col == 0); + + b.put('a'); + b.put('b'); + b.put('c'); + b.put('d'); + b.put('e'); + + b.cursor_left(); + b.cursor_left(); + b.cursor_left(); + + // (qwr) + // 01234567 + // abCde + // abqwrCde + + assert(decltype(b)::line_length(b.lines[0]) == 5); + b.shadow_paste(); + assert(decltype(b)::line_length(b.lines[0]) == 8); + assert(b.lines[0]->buf[0] == 'a'); + assert(b.lines[0]->buf[1] == 'b'); + assert(b.lines[0]->buf[2] == 'q'); + assert(b.lines[0]->buf[3] == 'w'); + assert(b.lines[0]->buf[4] == 'r'); + assert(b.lines[0]->buf[5] == 'c'); + assert(b.lines[0]->buf[6] == 'd'); + assert(b.lines[0]->buf[7] == 'e'); + + // also handles these other cases: + // abc| + // abcqwr + + // | + // qwr (cow) + b.decref(b.lines[0]); + b.cursor_home(); + assert(b.shadow.lines[0]->buf[0] == 'q'); + assert(b.shadow.lines[0]->buf[1] == 'w'); + assert(b.shadow.lines[0]->buf[2] == 'r'); + + b.put('a'); + b.put('b'); + b.put('c'); + assert(decltype(b)::line_length(b.lines[0]) == 3); + b.shadow_paste(); + assert(decltype(b)::line_length(b.lines[0]) == 6); + assert(b.lines[0]->buf[0] == 'a'); + assert(b.lines[0]->buf[1] == 'b'); + assert(b.lines[0]->buf[2] == 'c'); + assert(b.lines[0]->buf[3] == 'q'); + assert(b.lines[0]->buf[4] == 'w'); + assert(b.lines[0]->buf[5] == 'r'); + + b.decref(b.lines[0]); + b.cursor_home(); + + b.shadow_paste(); + assert(b.lines[0] == b.shadow.lines[0]); +} + +void test_shadow_paste_multiline() +{ + buffer<8, 8> b {4, 2}; + + // (qw + // er + // gh) + // + // jklopr + // aBcd + // zyx (2) + // + // jklopr + // aqw + // er (cow) + // ghBcd + // zyx (4) + + b.put('q'); + b.put('w'); + b.enter(); + b.put('e'); + b.put('r'); + b.enter(); + b.put('g'); + b.put('h'); + b.mark_set(); + b.cursor_home(); + b.cursor_up(); + b.cursor_up(); + assert(b.cursor.row == 0); + assert(b.cursor.col == 0); + b.shadow_copy(); + b.mark_delete(); + assert(b.shadow.length == 3); + assert(decltype(b)::line_length(b.shadow.lines[0]) == 2); + assert(decltype(b)::line_length(b.shadow.lines[1]) == 2); + assert(decltype(b)::line_length(b.shadow.lines[2]) == 2); + assert(b.length == 1); + assert(decltype(b)::line_length(b.lines[0]) == 0); + + // jkl + // aBcd + // zyx + + b.put('j'); + b.put('k'); + b.put('l'); + b.put('o'); + b.put('p'); + b.put('r'); + b.enter(); + b.put('a'); + b.put('b'); + b.put('c'); + b.put('d'); + b.enter(); + b.put('z'); + b.put('y'); + b.put('x'); + b.cursor_home(); + b.cursor_up(); + b.cursor_right(); + assert(b.cursor.row == 1); + assert(b.cursor.col == 1); + + b.shadow_paste(); + + // jklopr + // aqw + // er (cow) + // ghBcd + // zyx (4) + + assert(b.length == 5); + assert(b.lines[0]->length == 6); + assert(b.lines[1]->length == 3); + assert(b.lines[2]->length == 2); + assert(b.lines[3]->length == 5); + assert(b.lines[4]->length == 3); + + assert(b.lines[1]->buf[0] == 'a'); + assert(b.lines[1]->buf[1] == 'q'); + assert(b.lines[1]->buf[2] == 'w'); + assert(b.lines[2] == b.shadow.lines[1]); + assert(b.lines[2]->buf[0] == 'e'); + assert(b.lines[2]->buf[1] == 'r'); + assert(b.lines[3]->buf[0] == 'g'); + assert(b.lines[3]->buf[1] == 'h'); + assert(b.lines[3]->buf[2] == 'b'); + assert(b.lines[3]->buf[3] == 'c'); + assert(b.lines[3]->buf[4] == 'd'); + assert(b.lines[4]->buf[0] == 'z'); + assert(b.lines[4]->buf[1] == 'y'); + assert(b.lines[4]->buf[2] == 'x'); +} + int main() { test_allocate(); @@ -577,14 +747,8 @@ int main() test_copy_multi_line_offset(); test_delete_from_line(); test_selection_delete(); - - editor::buffer<64, 12 * 1024> b {0, 0}; - std::cerr << "size: " << (sizeof (b)) << '\n'; - std::cerr << " row: " << (sizeof (b.row)) << '\n'; - std::cerr << " lines: " << (sizeof (b.lines)) << '\n'; - std::cerr << " offsetof lines[1]: " << (reinterpret_cast(&b.lines[1]) - - reinterpret_cast(&b.lines[0])) << '\n'; - std::cerr << " shadow.lines: " << (sizeof (b.shadow.lines)) << '\n'; + test_shadow_paste_oneline(); + test_shadow_paste_multiline(); return 0; }