From 6a5ebab01f626c85152aa99b990f34b4344341e9 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Sat, 10 Jun 2023 02:57:54 +0000 Subject: [PATCH] editor: add shadow-lines and region-deletes This implements the "copy" portion of copy-and-paste and the "delete" portion of cut-and-paste. 'backspace' was also partially refactored to share code with the new 'selection_delete'. I am excited in how expressiveness/readability improved in 'backspace' after selection_delete was implemented. --- Makefile | 2 +- editor/editor.hpp | 399 +++++++++++++++++++++++++++++++++++------ editor/main_saturn.cpp | 41 +++-- editor/test_editor.cpp | 292 +++++++++++++++++++++++++++++- 4 files changed, 653 insertions(+), 81 deletions(-) diff --git a/Makefile b/Makefile index a25c41d..407890d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CFLAGS = -Isaturn -OPT = -Og +OPT ?= -Og LIBGCC = $(shell $(CC) -print-file-name=libgcc.a) LIB = ./saturn diff --git a/editor/editor.hpp b/editor/editor.hpp index e6d4aa0..d4dfcc9 100644 --- a/editor/editor.hpp +++ b/editor/editor.hpp @@ -10,12 +10,15 @@ namespace editor { template struct line { uint8_t buf[C]; - int32_t length; + int16_t length; + int16_t refcount; + + static_assert(C < 32768); }; struct cursor { - int32_t row; int32_t col; + int32_t row; }; enum struct mode { @@ -23,11 +26,38 @@ enum struct mode { mark }; +struct selection { + cursor * min; + cursor * max; + + inline constexpr bool contains(int32_t col, int32_t row); +}; + +inline constexpr bool selection::contains(int32_t col, int32_t row) +{ + if (this->min != this->max) { + if (row == this->min->row && row == this->max->row) { + if (col >= this->min->col && col < this->max->col) + return true; + } else { + if ( (row == this->min->row && col >= this->min->col) + || (row == this->max->row && col < this->max->col) + || (row > this->min->row && row < this->max->row)) + return true; + } + } + return false; +} + template struct buffer { line row[R]; line * lines[R]; int32_t length; + struct { + line * lines[R]; + int32_t length; + } shadow; int32_t alloc_ix; struct { int32_t cell_width; @@ -46,7 +76,12 @@ struct buffer { inline constexpr buffer(int32_t width, int32_t height); inline constexpr line * allocate(); - inline constexpr void deallocate(line *& l); + inline static constexpr void deallocate(line *& l); + inline static constexpr void decref(line *& l); + inline constexpr line * dup(line const * const l); + inline constexpr line * mutref(line *& l); + inline static constexpr line * incref(line * l); + inline static constexpr int32_t line_length(line const * const l); inline constexpr bool put(uint8_t c); inline constexpr bool backspace(); inline constexpr bool cursor_left(); @@ -56,9 +91,21 @@ struct buffer { inline constexpr bool cursor_home(); inline constexpr bool cursor_end(); inline constexpr bool enter(); - inline constexpr void set_mark(); + inline constexpr void mark_set(); + inline constexpr selection mark_get(); + inline constexpr void delete_from_line(line *& l, + const int32_t col_start_ix, + const int32_t col_end_ix); + inline constexpr void selection_delete(const selection& sel); + inline constexpr bool mark_delete(); inline constexpr void quit(); -private: + inline constexpr void shadow_clear(); + inline constexpr void _shadow_cow(line * src, + const int32_t dst_row_ix, + const int32_t col_start_ix, + const int32_t col_end_ix); + inline constexpr bool shadow_copy(); + inline constexpr bool shadow_cut(); inline constexpr void scroll_left(); inline constexpr void scroll_right(); inline constexpr void scroll_up(); @@ -69,6 +116,7 @@ template inline constexpr buffer::buffer(int32_t width, int32_t height) { this->length = 1; + this->shadow.length = 1; this->alloc_ix = 0; this->window.top = 0; this->window.left = 0; @@ -80,6 +128,7 @@ inline constexpr buffer::buffer(int32_t width, int32_t height) for (int32_t i = 0; i < R; i++) { this->row[i].length = -1; this->lines[i] = nullptr; + this->shadow.lines[i] = nullptr; for (int32_t j = 0; j < C; j++) { this->row[i].buf[j] = 0x7f; @@ -97,6 +146,7 @@ inline constexpr line * buffer::allocate() this->alloc_ix = (this->alloc_ix + 1) & (R - 1); } l->length = 0; + l->refcount = 1; return l; } @@ -106,17 +156,62 @@ inline constexpr void buffer::deallocate(line *& l) // does not touch alloc_ix fill(l->buf, 0x7f, l->length); l->length = -1; + l->refcount = 0; l = nullptr; } +template +inline constexpr void buffer::decref(line *& l) +{ + if (l->refcount == 1) + buffer::deallocate(l); + else { + l->refcount--; + l = nullptr; + } +} + +template +inline constexpr line * buffer::dup(line const * const src) +{ + line * dst = this->allocate(); + copy(&dst->buf[0], &src->buf[0], src->length); + dst->length = src->length; + return dst; +} + +template +inline constexpr line * buffer::mutref(line *& l) +{ + if (l == nullptr) { + l = this->allocate(); + } else if (l->refcount > 1) { + // copy-on-write + l->refcount--; + l = this->dup(l); + } + return l; +} + +template +inline constexpr line * buffer::incref(line * l) +{ + if (l != nullptr) l->refcount++; + return l; +} + +template +inline constexpr int32_t buffer::line_length(line const * const l) +{ + if (l == nullptr) return 0; + else return l->length; +} + template inline constexpr bool buffer::put(const uint8_t c) { struct cursor& cur = this->cursor; - line *& l = this->lines[cur.row]; - if (l == nullptr) { - l = this->allocate(); - } + line * l = mutref(this->lines[cur.row]); // v // 0123 @@ -141,47 +236,36 @@ inline constexpr bool buffer::put(const uint8_t c) template inline constexpr bool buffer::backspace() { + if (this->mode == mode::mark) { + return mark_delete(); + } + struct cursor& cur = this->cursor; if (cur.col < 0 || cur.col > C) return false; - line *& l = this->lines[cur.row]; - if (l == nullptr) { - // is it possible to get in this state? - return false; - } - if (l->length < 0) { - // is it possible to get in this state? - return false; - } - if (cur.col == 0) { if (cur.row == 0) return false; - // combine this line with the previous line - line *& lp = this->lines[cur.row-1]; - if (lp == nullptr) lp = allocate(); - // blindly truncate overflowing lines - auto length = min(l->length - cur.col, C - lp->length); - if (length > 0) move(&lp->buf[lp->length], &l->buf[cur.col], length); + // make selection + struct cursor min { line_length(this->lines[cur.row-1]), cur.row-1 }; + struct cursor max { cur.col, cur.row }; + selection sel { &min, &max }; + selection_delete(sel); - cur.col = lp->length; - scroll_right(); - lp->length += length; - deallocate(l); - // 0 a - // 1 - // 2 _ (cur.row, deleted) - // 3 b - int32_t n_lines = this->length - (cur.row + 1); - move(&this->lines[cur.row], &this->lines[cur.row+1], (sizeof (line*)) * n_lines); - this->length--; - this->lines[this->length] = nullptr; cur.row--; scroll_up(); + if (min.col < cur.col) { + cur.col = min.col; + scroll_right(); + } else { + cur.col = min.col; + scroll_left(); + } } else { // 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); @@ -207,8 +291,7 @@ inline constexpr bool buffer::cursor_left() scroll_up(); line const * const l = this->lines[cur.row]; - int32_t length = (l == nullptr) ? 0 : l->length; - cur.col = length; + cur.col = line_length(l); scroll_right(); } else { cur.col--; @@ -224,7 +307,7 @@ inline constexpr bool buffer::cursor_right() struct cursor& cur = this->cursor; line const * const l = this->lines[cur.row]; - int32_t length = (l == nullptr) ? 0 : l->length; + int32_t length = line_length(l); if (cur.col >= length) { if (cur.row >= (this->length - 1)) return false; @@ -254,8 +337,7 @@ inline constexpr bool buffer::cursor_up() scroll_up(); line const * const l = this->lines[cur.row]; - int32_t length = (l == nullptr) ? 0 : l->length; - cur.col = min(cur.col, length); + cur.col = min(cur.col, line_length(l)); scroll_left(); return true; } @@ -272,8 +354,7 @@ inline constexpr bool buffer::cursor_down() scroll_down(); line const * const l = this->lines[cur.row]; - int32_t length = (l == nullptr) ? 0 : l->length; - cur.col = min(cur.col, length); + cur.col = min(cur.col, line_length(l)); scroll_left(); return true; } @@ -282,11 +363,6 @@ template inline constexpr bool buffer::cursor_home() { struct cursor& cur = this->cursor; - - line const * const l = this->lines[cur.row]; - if (l == nullptr) - return false; - cur.col = 0; scroll_left(); return true; @@ -297,11 +373,7 @@ inline constexpr bool buffer::cursor_end() { struct cursor& cur = this->cursor; - line const * const l = this->lines[cur.row]; - if (l == nullptr) - return false; - - cur.col = l->length; + cur.col = line_length(this->lines[cur.row]); scroll_right(); return true; } @@ -321,13 +393,14 @@ inline constexpr bool buffer::enter() move(&this->lines[cur.row+2], &this->lines[cur.row+1], (sizeof (line*)) * n_lines); } // column-wise copy of the cursor position to the newly-created line - line * old_l = this->lines[cur.row]; - line * new_l = allocate(); + line * new_l = this->allocate(); + line *& old_l = this->lines[cur.row]; // v // 01234 (5) if (old_l != nullptr) { new_l->length = old_l->length - cur.col; if (new_l->length > 0) { + old_l = mutref(old_l); old_l->length -= new_l->length; copy(&new_l->buf[0], &old_l->buf[cur.col], new_l->length); fill(&old_l->buf[cur.col], 0x7f, new_l->length); @@ -348,19 +421,230 @@ inline constexpr bool buffer::enter() } template -inline constexpr void buffer::set_mark() +inline constexpr void buffer::mark_set() { this->mark.row = this->cursor.row; this->mark.col = this->cursor.col; this->mode = mode::mark; } +template +inline constexpr selection buffer::mark_get() +{ + editor::cursor& cur = this->cursor; + editor::cursor& mark = this->mark; + selection sel; + + if (cur.row == mark.row) { + if (cur.col == mark.col) { + sel.min = sel.max = &cur; + } else if (cur.col < mark.col) { + sel.min = &cur; + sel.max = &mark; + } else { + sel.min = &mark; + sel.max = &cur; + } + } else if (cur.row < mark.row) { + sel.min = &cur; + sel.max = &mark; + } else { + sel.min = &mark; + sel.max = &cur; + } + return sel; +} + +template +inline constexpr void buffer::delete_from_line(line *& l, + const int32_t col_start_ix, + const int32_t col_end_ix) +{ + if (l == nullptr) return; + else if (col_start_ix == 0 && col_end_ix == l->length) + decref(l); + else if (col_end_ix == l->length) + mutref(l)->length = col_start_ix; + else { + // S E + // 0123456(7) + // 0156 + l = mutref(l); + int32_t length = l->length - col_end_ix; + move(&l->buf[col_start_ix], &l->buf[col_end_ix], length); + l->length = col_start_ix + length; + } +} + +template +inline constexpr void buffer::selection_delete(const selection& sel) +{ + if (sel.min == sel.max) { + return; + } else if (sel.min->row == sel.max->row) { + // delete from min.col to max.col (excluding max.col) + delete_from_line(this->lines[sel.min->row], sel.min->col, sel.max->col); + } else { + // decref all lines between min.row and max.row, exclusive + for (int32_t ix = (sel.min->row + 1); ix < (sel.max->row); ix++) { + decref(this->lines[ix]); + } + + if (this->lines[sel.max->row] != nullptr) { + // combine min/max; truncating overflow + line * lmin = mutref(this->lines[sel.min->row]); + const line * lmax = this->lines[sel.max->row]; + + // v + // 0123 + // v + // abcd(4) + + // 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); + lmin->length += move_length; + + // delete max + decref(this->lines[sel.max->row]); + } + + // shift rows up + int32_t n_lines = this->length - (sel.max->row + 1); + move(&this->lines[sel.min->row + 1], + &this->lines[sel.max->row + 1], + (sizeof (line*)) * n_lines); + + int32_t deleted_rows = (sel.max->row - sel.min->row); + + // don't decref here -- the references were just moved + for (int32_t ix = this->length - deleted_rows; ix < this->length; ix++) { + this->lines[ix] = nullptr; + } + + this->length -= deleted_rows; + } +} + +template +inline constexpr bool buffer::mark_delete() +{ + if (this->mode != mode::mark) + return false; + + const selection sel = mark_get(); + this->selection_delete(sel); + + // move cur to sel.min + editor::cursor& cur = this->cursor; + const editor::cursor& min = *sel.min; + if (min.row < cur.row) { cur.row = min.row; scroll_up(); } + else { cur.row = min.row; scroll_down(); } + + if (min.col < cur.col) { cur.col = min.col; scroll_right(); } + else { cur.col = min.col; scroll_left(); } + + this->mode = mode::normal; + + return true; +} + template inline constexpr void buffer::quit() { this->mode = mode::normal; } +template +inline constexpr void buffer::shadow_clear() +{ + for (int32_t i = 0; i < this->shadow.length; i++) + if (this->shadow.lines[i] != nullptr) + decref(this->shadow.lines[i]); + + this->shadow.length = 1; +} + +template +inline constexpr void buffer::_shadow_cow(line * src, + const int32_t dst_row_ix, + const int32_t col_start_ix, + const int32_t col_end_ix) +{ + if (src == nullptr || (col_start_ix == col_end_ix)) { + this->shadow.lines[dst_row_ix] = nullptr; + } else if (col_start_ix == 0 && col_end_ix == src->length) { + this->shadow.lines[dst_row_ix] = incref(src); + } else { + line * dst = this->allocate(); + dst->length = col_end_ix - col_start_ix; + copy(&dst->buf[0], &src->buf[col_start_ix], dst->length); + this->shadow.lines[dst_row_ix] = dst; + } +} + +template +inline constexpr bool buffer::shadow_copy() +{ + if (this->mode != mode::mark) + return false; + + this->shadow_clear(); + + selection sel = this->mark_get(); + + this->shadow.length = (sel.max->row - sel.min->row) + 1; + + if (sel.min == sel.max) { + this->shadow.lines[0] = nullptr; + return true; + } else if (sel.min->row == sel.max->row) { + // copy from min.col to max.col (excluding max.col) + _shadow_cow(this->lines[sel.min->row], + 0, // dst_row_ix + sel.min->col, // col_start_ix + sel.max->col); // col_end_ix + } else { + line * src = this->lines[sel.min->row]; + // copy from min.col to the end of the line (including min.col) + _shadow_cow(src, + 0, // dst_row_ix + sel.min->col, // col_start_ix + line_length(src)); // col_end_ix + + int32_t shadow_ix = 1; + for (int32_t ix = (sel.min->row + 1); ix < (sel.max->row); ix++) { + // cow all lines between min.row and max.row, exclusive + this->shadow.lines[shadow_ix++] = incref(this->lines[ix]); + } + + // copy from the beginning of the line to max.col (excluding max.col) + _shadow_cow(this->lines[sel.max->row], + shadow_ix, // row_ix + 0, // col_start_ix + sel.max->col); // col_end_ix + } + + return true; +} + +template +inline constexpr bool buffer::shadow_cut() +{ + if (this->mode != mode::mark) + return false; + + this->shadow_copy(); + + return true; +} + template inline constexpr void buffer::scroll_up() { @@ -402,5 +686,4 @@ inline constexpr void buffer::scroll_right() if (this->cursor.col >= this->window.left + this->window.cell_width) this->window.left = (this->cursor.col - (this->window.cell_width - 1)); } - } diff --git a/editor/main_saturn.cpp b/editor/main_saturn.cpp index aed897c..d410fd1 100644 --- a/editor/main_saturn.cpp +++ b/editor/main_saturn.cpp @@ -33,7 +33,7 @@ void palette_data() vdp2.cram.u16[1 + 16] = rgb15(31, 31, 31); vdp2.cram.u16[2 + 16] = rgb15( 0, 0, 0); - vdp2.cram.u16[1 + 32] = rgb15(15, 15, 15); + vdp2.cram.u16[1 + 32] = rgb15(10, 10, 10); vdp2.cram.u16[2 + 32] = rgb15(31, 31, 31); } @@ -102,7 +102,7 @@ inline void keyboard_regular_key(const enum keysym k) case keysym::ENTER : buffer.enter(); break; default: { - uint8_t c = keysym_to_char(k, false); + int32_t c = keysym_to_char(k, false); if (c != -1) buffer.put(c); } break; @@ -115,7 +115,7 @@ inline void keyboard_regular_key(const enum keysym k) switch (k) { default: { - uint8_t c = keysym_to_char(k, true); + int32_t c = keysym_to_char(k, true); if (c != -1) buffer.put(c); } break; @@ -126,7 +126,7 @@ inline void keyboard_regular_key(const enum keysym k) case MODIFIER_RIGHT_CTRL: [[fallthrough]]; case MODIFIER_BOTH_CTRL: switch (k) { - case keysym::SPACE : buffer.set_mark(); break; + case keysym::SPACE : buffer.mark_set(); break; case keysym::G : buffer.quit(); break; case keysym::B : buffer.cursor_left(); break; case keysym::F : buffer.cursor_right(); break; @@ -207,23 +207,32 @@ set_char(int32_t x, int32_t y, uint8_t palette, uint8_t c) void render() { - for (int row = 0; row < buffer.window.cell_height; row++) { - const buffer_type::line_type * l = buffer.lines[buffer.window.top + row]; + editor::cursor& cur = buffer.cursor; - for (int col = 0; col < buffer.window.cell_width; col++) { - uint8_t c; + editor::selection sel; - c = ' '; - if (l != nullptr && (buffer.window.left + col) < l->length) - c = l->buf[buffer.window.left + col]; + if (buffer.mode == editor::mode::mark) + sel = buffer.mark_get(); - set_char(col, row, 0, c); + for (int y = 0; y < buffer.window.cell_height; y++) { + const int32_t row = buffer.window.top + y; + const buffer_type::line_type * l = buffer.lines[row]; + + for (int x = 0; x < buffer.window.cell_width; x++) { + const int32_t col = buffer.window.left + x; + const uint8_t c = (l != nullptr && col < l->length) ? l->buf[col] : ' '; + uint8_t palette = 0; + + if (row == cur.row && col == cur.col) { + palette = 1; + } else if (buffer.mode == editor::mode::mark) { + if (sel.contains(col, row)) + palette = 2; + } + + set_char(x, y, palette, c); } } - editor::cursor& cur = buffer.cursor; - const buffer_type::line_type * l = buffer.lines[cur.row]; - uint8_t c = (l != nullptr && cur.col < l->length) ? l->buf[cur.col] : ' '; - set_char(cur.col - buffer.window.left, cur.row - buffer.window.top, 1, c); } extern "C" diff --git a/editor/test_editor.cpp b/editor/test_editor.cpp index dc291db..a1970ed 100644 --- a/editor/test_editor.cpp +++ b/editor/test_editor.cpp @@ -74,7 +74,6 @@ static void test_put() assert(l = &b.row[0]); assert(l->length == 1); assert(l->buf[0] == 'a'); - assert(l->buf[1] == 0x7f); assert(b.cursor.col == 1); assert(b.cursor.row == 0); assert(b.length == 1); @@ -84,7 +83,6 @@ static void test_put() assert(l->length == 2); assert(l->buf[0] == 'a'); assert(l->buf[1] == 'b'); - assert(l->buf[2] == 0x7f); assert(b.cursor.col == 2); assert(b.cursor.row == 0); assert(b.length == 1); @@ -96,7 +94,6 @@ static void test_put() assert(l->buf[0] == 'a'); assert(l->buf[1] == 'c'); assert(l->buf[2] == 'b'); - assert(l->buf[3] == 0x7f); assert(b.cursor.col == 2); assert(b.cursor.row == 0); assert(b.length == 1); @@ -114,7 +111,6 @@ void test_backspace() assert(b.backspace() == true); assert(l->length == 0); - assert(l->buf[0] == 0x7f); assert(b.backspace() == false); @@ -130,7 +126,6 @@ void test_backspace() assert(l->buf[0] == 'b'); assert(l->buf[1] == 'd'); assert(l->buf[2] == 'e'); - assert(l->buf[3] == 0x7f); assert(l->length == 3); } @@ -150,7 +145,6 @@ void test_enter() b.put('s'); b.put('d'); b.put('f'); - b.cursor.row = 1; b.cursor.col = 0; b.put('q'); @@ -295,6 +289,278 @@ void test_enter_backspace3() assert(b.lines[3] == nullptr); } +void test_copy() +{ + // 0123 + + // asDf + // qwer + // jKlo + + // asklo + // + // df + // qwer + // j + + buffer<8, 8> b {4, 2}; + + b.put('a'); + b.put('s'); + b.put('d'); + b.put('f'); + b.enter(); + b.put('q'); + b.put('w'); + b.put('e'); + b.put('r'); + b.enter(); + b.put('j'); + b.put('k'); + b.put('l'); + b.put('o'); + + b.cursor_up(); + b.cursor_up(); + b.cursor_left(); + b.cursor_left(); + b.mark_set(); + b.cursor_down(); + b.cursor_down(); + b.cursor_left(); + + assert(b.mark.row == 0); + assert(b.mark.col == 2); + assert(b.cursor.row == 2); + assert(b.cursor.col == 1); + + b.shadow_copy(); + assert(b.shadow.length == 3); + assert(b.shadow.lines[0] != nullptr); + assert(b.shadow.lines[1] != nullptr); + assert(b.shadow.lines[2] != nullptr); + assert(b.shadow.lines[3] == nullptr); + assert(b.shadow.lines[0]->length == 2); + assert(b.shadow.lines[1]->length == 4); + assert(b.shadow.lines[2]->length == 1); + assert(b.shadow.lines[0]->buf[0] == 'd'); + assert(b.shadow.lines[0]->buf[1] == 'f'); + assert(b.shadow.lines[1]->buf[0] == 'q'); + assert(b.shadow.lines[1]->buf[1] == 'w'); + assert(b.shadow.lines[1]->buf[2] == 'e'); + assert(b.shadow.lines[1]->buf[3] == 'r'); + assert(b.shadow.lines[2]->buf[0] == 'j'); + assert(b.shadow.lines[0] != b.lines[0]); + assert(b.shadow.lines[1] == b.lines[1]); + assert(b.shadow.lines[2] != b.lines[2]); +} + +void test_copy_same_line() +{ + buffer<8, 8> b {4, 2}; + + // v + // asdF + // + // sd + + b.put('a'); + b.put('s'); + b.put('d'); + b.put('f'); + b.cursor_left(); + b.mark_set(); + b.cursor_left(); + b.cursor_left(); + + // non-cow same line + b.shadow_copy(); + assert(b.shadow.length == 1); + assert(b.shadow.lines[0] != nullptr); + assert(b.shadow.lines[1] == nullptr); + assert(b.shadow.lines[0] != b.lines[0]); + assert(b.shadow.lines[0]->length == 2); + assert(b.shadow.lines[0]->buf[0] == 's'); + assert(b.shadow.lines[0]->buf[1] == 'd'); + + // cow same line + b.cursor_home(); + b.mark_set(); + b.cursor_end(); + b.shadow_copy(); + assert(b.shadow.length == 1); + assert(b.shadow.lines[0] == b.lines[0]); +} + +void test_copy_multi_line_cow() +{ + buffer<8, 8> b {4, 2}; + + // v + // asdF + // + // sd + + b.put('a'); + b.put('s'); + b.put('d'); + b.put('f'); + b.enter(); + b.put('q'); + b.put('w'); + b.put('e'); + b.put('r'); + b.mark_set(); + b.cursor_up(); + b.cursor_home(); + b.shadow_copy(); + + assert(b.shadow.length == 2); + assert(b.shadow.lines[0] == b.lines[0]); + assert(b.shadow.lines[1] == b.lines[1]); +} + +void test_copy_multi_line_offset() +{ + buffer<8, 8> b {4, 2}; + + b.put('a'); + b.put('s'); + b.put('d'); + b.put('f'); + b.enter(); + b.put('q'); + b.put('w'); + b.put('e'); + b.put('r'); + b.enter(); + b.put('s'); + b.put('h'); + b.put('a'); + b.put('d'); + b.enter(); + b.put('o'); + b.put('w'); + + // qwEr + // sHad + + // er + // s + b.cursor_up(); + b.cursor_home(); + b.cursor_right(); + b.mark_set(); + b.cursor_up(); + b.cursor_right(); + b.shadow_copy(); + + assert(b.shadow.length == 2); + assert(b.shadow.lines[0]->length == 2); + assert(b.shadow.lines[1]->length == 1); + assert(b.shadow.lines[0]->buf[0] == 'e'); + assert(b.shadow.lines[0]->buf[1] == 'r'); + assert(b.shadow.lines[1]->buf[0] == 's'); + + b.cursor_home(); + b.mark_set(); + b.cursor_down(); + b.cursor_down(); + b.cursor_end(); + b.shadow_copy(); + + assert(b.shadow.length == 3); + assert(b.shadow.lines[0] == b.lines[1]); + assert(b.shadow.lines[1] == b.lines[2]); + assert(b.shadow.lines[2] == b.lines[3]); +} + +void test_delete_from_line() +{ + buffer<8, 8> b {4, 2}; + + b.put('a'); + b.put('s'); + b.put('d'); + b.put('f'); + + // S E + // asdf + // af + + b.delete_from_line(b.lines[0], 1, 3); + assert(b.lines[0]->length == 2); + assert(b.lines[0]->buf[0] == 'a'); + assert(b.lines[0]->buf[1] == 'f'); + + b.delete_from_line(b.lines[0], 0, 2); + assert(b.lines[0] == nullptr); +} + +void test_selection_delete() +{ + buffer<8, 8> b {4, 2}; + + b.put('s'); + b.put('p'); + b.put('a'); + b.put('m'); + b.enter(); + b.put('a'); + b.put('s'); + b.put('d'); + b.put('f'); + b.enter(); + b.put('0'); + b.put('1'); + b.put('2'); + b.put('3'); + b.enter(); + b.put('j'); + b.put('k'); + b.put('l'); + b.put('p'); + b.enter(); + b.put('q'); + b.put('w'); + b.put('e'); + b.put('r'); + + // spam (0) + // asDf < (1) + // 0123 -- (2) (del) + // jKlp < (3) (del) + // qwer (4) + // (5) + + // spam (0) + // asklp (1) + // qwer (2) + + cursor min { 2, 1 }; + cursor max { 1, 3 }; + selection sel { &min, &max }; + b.selection_delete(sel); + + assert(b.length == 3); + assert(b.lines[0]->length == 4); + assert(b.lines[0]->buf[0] == 's'); + assert(b.lines[0]->buf[1] == 'p'); + assert(b.lines[0]->buf[2] == 'a'); + assert(b.lines[0]->buf[3] == 'm'); + assert(b.lines[1]->length == 5); + assert(b.lines[1]->buf[0] == 'a'); + assert(b.lines[1]->buf[1] == 's'); + assert(b.lines[1]->buf[2] == 'k'); + assert(b.lines[1]->buf[3] == 'l'); + assert(b.lines[1]->buf[4] == 'p'); + assert(b.lines[2]->length == 4); + assert(b.lines[2]->buf[0] == 'q'); + assert(b.lines[2]->buf[1] == 'w'); + assert(b.lines[2]->buf[2] == 'e'); + assert(b.lines[2]->buf[3] == 'r'); +} + int main() { test_allocate(); @@ -305,6 +571,20 @@ int main() test_enter_backspace3(); test_enter_scroll(); test_first_enter(); + test_copy(); + test_copy_same_line(); + test_copy_multi_line_cow(); + 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'; return 0; }