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.
This commit is contained in:
Zack Buhman 2023-06-10 02:57:54 +00:00
parent d313bdd855
commit 6a5ebab01f
4 changed files with 653 additions and 81 deletions

View File

@ -1,5 +1,5 @@
CFLAGS = -Isaturn CFLAGS = -Isaturn
OPT = -Og OPT ?= -Og
LIBGCC = $(shell $(CC) -print-file-name=libgcc.a) LIBGCC = $(shell $(CC) -print-file-name=libgcc.a)
LIB = ./saturn LIB = ./saturn

View File

@ -10,12 +10,15 @@ namespace editor {
template <int C> template <int C>
struct line { struct line {
uint8_t buf[C]; uint8_t buf[C];
int32_t length; int16_t length;
int16_t refcount;
static_assert(C < 32768);
}; };
struct cursor { struct cursor {
int32_t row;
int32_t col; int32_t col;
int32_t row;
}; };
enum struct mode { enum struct mode {
@ -23,11 +26,38 @@ enum struct mode {
mark 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 <int C, int R> template <int C, int R>
struct buffer { struct buffer {
line<C> row[R]; line<C> row[R];
line<C> * lines[R]; line<C> * lines[R];
int32_t length; int32_t length;
struct {
line<C> * lines[R];
int32_t length;
} shadow;
int32_t alloc_ix; int32_t alloc_ix;
struct { struct {
int32_t cell_width; int32_t cell_width;
@ -46,7 +76,12 @@ struct buffer {
inline constexpr buffer(int32_t width, int32_t height); inline constexpr buffer(int32_t width, int32_t height);
inline constexpr line<C> * allocate(); inline constexpr line<C> * allocate();
inline constexpr void deallocate(line<C> *& l); inline static constexpr void deallocate(line<C> *& l);
inline static constexpr void decref(line<C> *& l);
inline constexpr line<C> * dup(line<C> const * const l);
inline constexpr line<C> * mutref(line<C> *& l);
inline static constexpr line<C> * incref(line<C> * l);
inline static constexpr int32_t line_length(line<C> const * const l);
inline constexpr bool put(uint8_t c); inline constexpr bool put(uint8_t c);
inline constexpr bool backspace(); inline constexpr bool backspace();
inline constexpr bool cursor_left(); inline constexpr bool cursor_left();
@ -56,9 +91,21 @@ struct buffer {
inline constexpr bool cursor_home(); inline constexpr bool cursor_home();
inline constexpr bool cursor_end(); inline constexpr bool cursor_end();
inline constexpr bool enter(); 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<C> *& 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(); inline constexpr void quit();
private: inline constexpr void shadow_clear();
inline constexpr void _shadow_cow(line<C> * 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_left();
inline constexpr void scroll_right(); inline constexpr void scroll_right();
inline constexpr void scroll_up(); inline constexpr void scroll_up();
@ -69,6 +116,7 @@ template <int C, int R>
inline constexpr buffer<C, R>::buffer(int32_t width, int32_t height) inline constexpr buffer<C, R>::buffer(int32_t width, int32_t height)
{ {
this->length = 1; this->length = 1;
this->shadow.length = 1;
this->alloc_ix = 0; this->alloc_ix = 0;
this->window.top = 0; this->window.top = 0;
this->window.left = 0; this->window.left = 0;
@ -80,6 +128,7 @@ inline constexpr buffer<C, R>::buffer(int32_t width, int32_t height)
for (int32_t i = 0; i < R; i++) { for (int32_t i = 0; i < R; i++) {
this->row[i].length = -1; this->row[i].length = -1;
this->lines[i] = nullptr; this->lines[i] = nullptr;
this->shadow.lines[i] = nullptr;
for (int32_t j = 0; j < C; j++) { for (int32_t j = 0; j < C; j++) {
this->row[i].buf[j] = 0x7f; this->row[i].buf[j] = 0x7f;
@ -97,6 +146,7 @@ inline constexpr line<C> * buffer<C, R>::allocate()
this->alloc_ix = (this->alloc_ix + 1) & (R - 1); this->alloc_ix = (this->alloc_ix + 1) & (R - 1);
} }
l->length = 0; l->length = 0;
l->refcount = 1;
return l; return l;
} }
@ -106,17 +156,62 @@ inline constexpr void buffer<C, R>::deallocate(line<C> *& l)
// does not touch alloc_ix // does not touch alloc_ix
fill<uint8_t>(l->buf, 0x7f, l->length); fill<uint8_t>(l->buf, 0x7f, l->length);
l->length = -1; l->length = -1;
l->refcount = 0;
l = nullptr; l = nullptr;
} }
template <int C, int R>
inline constexpr void buffer<C, R>::decref(line<C> *& l)
{
if (l->refcount == 1)
buffer<C, R>::deallocate(l);
else {
l->refcount--;
l = nullptr;
}
}
template <int C, int R>
inline constexpr line<C> * buffer<C, R>::dup(line<C> const * const src)
{
line<C> * dst = this->allocate();
copy(&dst->buf[0], &src->buf[0], src->length);
dst->length = src->length;
return dst;
}
template <int C, int R>
inline constexpr line<C> * buffer<C, R>::mutref(line<C> *& l)
{
if (l == nullptr) {
l = this->allocate();
} else if (l->refcount > 1) {
// copy-on-write
l->refcount--;
l = this->dup(l);
}
return l;
}
template <int C, int R>
inline constexpr line<C> * buffer<C, R>::incref(line<C> * l)
{
if (l != nullptr) l->refcount++;
return l;
}
template <int C, int R>
inline constexpr int32_t buffer<C, R>::line_length(line<C> const * const l)
{
if (l == nullptr) return 0;
else return l->length;
}
template <int C, int R> template <int C, int R>
inline constexpr bool buffer<C, R>::put(const uint8_t c) inline constexpr bool buffer<C, R>::put(const uint8_t c)
{ {
struct cursor& cur = this->cursor; struct cursor& cur = this->cursor;
line<C> *& l = this->lines[cur.row]; line<C> * l = mutref(this->lines[cur.row]);
if (l == nullptr) {
l = this->allocate();
}
// v // v
// 0123 // 0123
@ -141,47 +236,36 @@ inline constexpr bool buffer<C, R>::put(const uint8_t c)
template <int C, int R> template <int C, int R>
inline constexpr bool buffer<C, R>::backspace() inline constexpr bool buffer<C, R>::backspace()
{ {
if (this->mode == mode::mark) {
return mark_delete();
}
struct cursor& cur = this->cursor; struct cursor& cur = this->cursor;
if (cur.col < 0 || cur.col > C) if (cur.col < 0 || cur.col > C)
return false; return false;
line<C> *& 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.col == 0) {
if (cur.row == 0) return false; if (cur.row == 0) return false;
// combine this line with the previous line // make selection
line<C> *& lp = this->lines[cur.row-1]; struct cursor min { line_length(this->lines[cur.row-1]), cur.row-1 };
if (lp == nullptr) lp = allocate(); struct cursor max { cur.col, cur.row };
// blindly truncate overflowing lines selection sel { &min, &max };
auto length = min(l->length - cur.col, C - lp->length); selection_delete(sel);
if (length > 0) move(&lp->buf[lp->length], &l->buf[cur.col], length);
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<C>*)) * n_lines);
this->length--;
this->lines[this->length] = nullptr;
cur.row--; cur.row--;
scroll_up(); scroll_up();
if (min.col < cur.col) {
cur.col = min.col;
scroll_right();
} else {
cur.col = min.col;
scroll_left();
}
} else { } else {
// c // c
// 01234 // 01234
line<C> * l = mutref(this->lines[cur.row]);
auto length = l->length - cur.col; auto length = l->length - cur.col;
if (length > 0) move(&l->buf[cur.col-1], &l->buf[cur.col], length); if (length > 0) move(&l->buf[cur.col-1], &l->buf[cur.col], length);
@ -207,8 +291,7 @@ inline constexpr bool buffer<C, R>::cursor_left()
scroll_up(); scroll_up();
line<C> const * const l = this->lines[cur.row]; line<C> const * const l = this->lines[cur.row];
int32_t length = (l == nullptr) ? 0 : l->length; cur.col = line_length(l);
cur.col = length;
scroll_right(); scroll_right();
} else { } else {
cur.col--; cur.col--;
@ -224,7 +307,7 @@ inline constexpr bool buffer<C, R>::cursor_right()
struct cursor& cur = this->cursor; struct cursor& cur = this->cursor;
line<C> const * const l = this->lines[cur.row]; line<C> 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.col >= length) {
if (cur.row >= (this->length - 1)) if (cur.row >= (this->length - 1))
return false; return false;
@ -254,8 +337,7 @@ inline constexpr bool buffer<C, R>::cursor_up()
scroll_up(); scroll_up();
line<C> const * const l = this->lines[cur.row]; line<C> const * const l = this->lines[cur.row];
int32_t length = (l == nullptr) ? 0 : l->length; cur.col = min(cur.col, line_length(l));
cur.col = min(cur.col, length);
scroll_left(); scroll_left();
return true; return true;
} }
@ -272,8 +354,7 @@ inline constexpr bool buffer<C, R>::cursor_down()
scroll_down(); scroll_down();
line<C> const * const l = this->lines[cur.row]; line<C> const * const l = this->lines[cur.row];
int32_t length = (l == nullptr) ? 0 : l->length; cur.col = min(cur.col, line_length(l));
cur.col = min(cur.col, length);
scroll_left(); scroll_left();
return true; return true;
} }
@ -282,11 +363,6 @@ template <int C, int R>
inline constexpr bool buffer<C, R>::cursor_home() inline constexpr bool buffer<C, R>::cursor_home()
{ {
struct cursor& cur = this->cursor; struct cursor& cur = this->cursor;
line<C> const * const l = this->lines[cur.row];
if (l == nullptr)
return false;
cur.col = 0; cur.col = 0;
scroll_left(); scroll_left();
return true; return true;
@ -297,11 +373,7 @@ inline constexpr bool buffer<C, R>::cursor_end()
{ {
struct cursor& cur = this->cursor; struct cursor& cur = this->cursor;
line<C> const * const l = this->lines[cur.row]; cur.col = line_length(this->lines[cur.row]);
if (l == nullptr)
return false;
cur.col = l->length;
scroll_right(); scroll_right();
return true; return true;
} }
@ -321,13 +393,14 @@ inline constexpr bool buffer<C, R>::enter()
move(&this->lines[cur.row+2], &this->lines[cur.row+1], (sizeof (line<C>*)) * n_lines); move(&this->lines[cur.row+2], &this->lines[cur.row+1], (sizeof (line<C>*)) * n_lines);
} }
// column-wise copy of the cursor position to the newly-created line // column-wise copy of the cursor position to the newly-created line
line<C> * old_l = this->lines[cur.row]; line<C> * new_l = this->allocate();
line<C> * new_l = allocate(); line<C> *& old_l = this->lines[cur.row];
// v // v
// 01234 (5) // 01234 (5)
if (old_l != nullptr) { if (old_l != nullptr) {
new_l->length = old_l->length - cur.col; new_l->length = old_l->length - cur.col;
if (new_l->length > 0) { if (new_l->length > 0) {
old_l = mutref(old_l);
old_l->length -= new_l->length; old_l->length -= new_l->length;
copy(&new_l->buf[0], &old_l->buf[cur.col], new_l->length); copy(&new_l->buf[0], &old_l->buf[cur.col], new_l->length);
fill<uint8_t>(&old_l->buf[cur.col], 0x7f, new_l->length); fill<uint8_t>(&old_l->buf[cur.col], 0x7f, new_l->length);
@ -348,19 +421,230 @@ inline constexpr bool buffer<C, R>::enter()
} }
template <int C, int R> template <int C, int R>
inline constexpr void buffer<C, R>::set_mark() inline constexpr void buffer<C, R>::mark_set()
{ {
this->mark.row = this->cursor.row; this->mark.row = this->cursor.row;
this->mark.col = this->cursor.col; this->mark.col = this->cursor.col;
this->mode = mode::mark; this->mode = mode::mark;
} }
template <int C, int R>
inline constexpr selection buffer<C, R>::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 <int C, int R>
inline constexpr void buffer<C, R>::delete_from_line(line<C> *& 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 <int C, int R>
inline constexpr void buffer<C, R>::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<C> * lmin = mutref(this->lines[sel.min->row]);
const line<C> * 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<int32_t>(lmax->length - sel.max->col),
static_cast<int32_t>(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<C>*)) * 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 <int C, int R>
inline constexpr bool buffer<C, R>::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 <int C, int R> template <int C, int R>
inline constexpr void buffer<C, R>::quit() inline constexpr void buffer<C, R>::quit()
{ {
this->mode = mode::normal; this->mode = mode::normal;
} }
template <int C, int R>
inline constexpr void buffer<C, R>::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 <int C, int R>
inline constexpr void buffer<C, R>::_shadow_cow(line<C> * 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<C> * 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 <int C, int R>
inline constexpr bool buffer<C, R>::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<C> * 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 <int C, int R>
inline constexpr bool buffer<C, R>::shadow_cut()
{
if (this->mode != mode::mark)
return false;
this->shadow_copy();
return true;
}
template <int C, int R> template <int C, int R>
inline constexpr void buffer<C, R>::scroll_up() inline constexpr void buffer<C, R>::scroll_up()
{ {
@ -402,5 +686,4 @@ inline constexpr void buffer<C, R>::scroll_right()
if (this->cursor.col >= this->window.left + this->window.cell_width) if (this->cursor.col >= this->window.left + this->window.cell_width)
this->window.left = (this->cursor.col - (this->window.cell_width - 1)); this->window.left = (this->cursor.col - (this->window.cell_width - 1));
} }
} }

View File

@ -33,7 +33,7 @@ void palette_data()
vdp2.cram.u16[1 + 16] = rgb15(31, 31, 31); vdp2.cram.u16[1 + 16] = rgb15(31, 31, 31);
vdp2.cram.u16[2 + 16] = rgb15( 0, 0, 0); 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); 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; case keysym::ENTER : buffer.enter(); break;
default: default:
{ {
uint8_t c = keysym_to_char(k, false); int32_t c = keysym_to_char(k, false);
if (c != -1) buffer.put(c); if (c != -1) buffer.put(c);
} }
break; break;
@ -115,7 +115,7 @@ inline void keyboard_regular_key(const enum keysym k)
switch (k) { switch (k) {
default: default:
{ {
uint8_t c = keysym_to_char(k, true); int32_t c = keysym_to_char(k, true);
if (c != -1) buffer.put(c); if (c != -1) buffer.put(c);
} }
break; break;
@ -126,7 +126,7 @@ inline void keyboard_regular_key(const enum keysym k)
case MODIFIER_RIGHT_CTRL: [[fallthrough]]; case MODIFIER_RIGHT_CTRL: [[fallthrough]];
case MODIFIER_BOTH_CTRL: case MODIFIER_BOTH_CTRL:
switch (k) { switch (k) {
case keysym::SPACE : buffer.set_mark(); break; case keysym::SPACE : buffer.mark_set(); break;
case keysym::G : buffer.quit(); break; case keysym::G : buffer.quit(); break;
case keysym::B : buffer.cursor_left(); break; case keysym::B : buffer.cursor_left(); break;
case keysym::F : buffer.cursor_right(); 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() void render()
{ {
for (int row = 0; row < buffer.window.cell_height; row++) { editor::cursor& cur = buffer.cursor;
const buffer_type::line_type * l = buffer.lines[buffer.window.top + row];
for (int col = 0; col < buffer.window.cell_width; col++) { editor::selection sel;
uint8_t c;
c = ' '; if (buffer.mode == editor::mode::mark)
if (l != nullptr && (buffer.window.left + col) < l->length) sel = buffer.mark_get();
c = l->buf[buffer.window.left + col];
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" extern "C"

View File

@ -74,7 +74,6 @@ static void test_put()
assert(l = &b.row[0]); assert(l = &b.row[0]);
assert(l->length == 1); assert(l->length == 1);
assert(l->buf[0] == 'a'); assert(l->buf[0] == 'a');
assert(l->buf[1] == 0x7f);
assert(b.cursor.col == 1); assert(b.cursor.col == 1);
assert(b.cursor.row == 0); assert(b.cursor.row == 0);
assert(b.length == 1); assert(b.length == 1);
@ -84,7 +83,6 @@ static void test_put()
assert(l->length == 2); assert(l->length == 2);
assert(l->buf[0] == 'a'); assert(l->buf[0] == 'a');
assert(l->buf[1] == 'b'); assert(l->buf[1] == 'b');
assert(l->buf[2] == 0x7f);
assert(b.cursor.col == 2); assert(b.cursor.col == 2);
assert(b.cursor.row == 0); assert(b.cursor.row == 0);
assert(b.length == 1); assert(b.length == 1);
@ -96,7 +94,6 @@ static void test_put()
assert(l->buf[0] == 'a'); assert(l->buf[0] == 'a');
assert(l->buf[1] == 'c'); assert(l->buf[1] == 'c');
assert(l->buf[2] == 'b'); assert(l->buf[2] == 'b');
assert(l->buf[3] == 0x7f);
assert(b.cursor.col == 2); assert(b.cursor.col == 2);
assert(b.cursor.row == 0); assert(b.cursor.row == 0);
assert(b.length == 1); assert(b.length == 1);
@ -114,7 +111,6 @@ void test_backspace()
assert(b.backspace() == true); assert(b.backspace() == true);
assert(l->length == 0); assert(l->length == 0);
assert(l->buf[0] == 0x7f);
assert(b.backspace() == false); assert(b.backspace() == false);
@ -130,7 +126,6 @@ void test_backspace()
assert(l->buf[0] == 'b'); assert(l->buf[0] == 'b');
assert(l->buf[1] == 'd'); assert(l->buf[1] == 'd');
assert(l->buf[2] == 'e'); assert(l->buf[2] == 'e');
assert(l->buf[3] == 0x7f);
assert(l->length == 3); assert(l->length == 3);
} }
@ -150,7 +145,6 @@ void test_enter()
b.put('s'); b.put('s');
b.put('d'); b.put('d');
b.put('f'); b.put('f');
b.cursor.row = 1; b.cursor.row = 1;
b.cursor.col = 0; b.cursor.col = 0;
b.put('q'); b.put('q');
@ -295,6 +289,278 @@ void test_enter_backspace3()
assert(b.lines[3] == nullptr); 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() int main()
{ {
test_allocate(); test_allocate();
@ -305,6 +571,20 @@ int main()
test_enter_backspace3(); test_enter_backspace3();
test_enter_scroll(); test_enter_scroll();
test_first_enter(); 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<uint8_t*>(&b.lines[1]) -
reinterpret_cast<uint8_t*>(&b.lines[0])) << '\n';
std::cerr << " shadow.lines: " << (sizeof (b.shadow.lines)) << '\n';
return 0; return 0;
} }