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'.
This commit is contained in:
Zack Buhman 2023-06-10 23:01:13 +00:00
parent 6a5ebab01f
commit a11dadcde8
3 changed files with 312 additions and 23 deletions

View File

@ -10,8 +10,8 @@ namespace editor {
template <int C>
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<C> *& dst,
const int32_t dst_col,
line<C> * 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 <int C, int R>
inline constexpr bool buffer<C, R>::backspace()
{
if (this->mode == mode::mark) {
this->mode = mode::normal;
return mark_delete();
}
@ -266,8 +272,8 @@ inline constexpr bool buffer<C, R>::backspace()
// c
// 01234
line<C> * 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<C, R>::selection_delete(const selection& sel)
// 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);
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<C, R>::selection_delete(const selection& sel)
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);
@ -550,8 +551,6 @@ inline constexpr bool buffer<C, R>::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<C, R>::shadow_cut()
this->shadow_copy();
this->mark_delete();
this->mode = mode::normal;
return true;
}
template <int C, int R>
inline constexpr void buffer<C, R>::overwrite_line(line<C> *& dst,
const int32_t dst_col,
line<C> * 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<C> * 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 <int C, int R>
inline constexpr bool buffer<C, R>::shadow_paste()
{
if (this->mode == mode::mark)
this->mode = mode::normal;
editor::cursor& cur = this->cursor;
if (this->shadow.length == 1) {
line<C> * ls = this->shadow.lines[0];
line<C> *& 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<C> *& first_line_dst = this->lines[cur.row];
line<C> * first_line_src = this->shadow.lines[0];
line<C> *& last_line_dst = this->lines[cur.row + last_line_offset];
line<C> * 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<C>*)) * 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;
}

View File

@ -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;

View File

@ -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<uint8_t*>(&b.lines[1]) -
reinterpret_cast<uint8_t*>(&b.lines[0])) << '\n';
std::cerr << " shadow.lines: " << (sizeof (b.shadow.lines)) << '\n';
test_shadow_paste_oneline();
test_shadow_paste_multiline();
return 0;
}