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:
parent
d313bdd855
commit
6a5ebab01f
2
Makefile
2
Makefile
@ -1,5 +1,5 @@
|
||||
CFLAGS = -Isaturn
|
||||
OPT = -Og
|
||||
OPT ?= -Og
|
||||
LIBGCC = $(shell $(CC) -print-file-name=libgcc.a)
|
||||
LIB = ./saturn
|
||||
|
||||
|
@ -10,12 +10,15 @@ namespace editor {
|
||||
template <int C>
|
||||
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 <int C, int R>
|
||||
struct buffer {
|
||||
line<C> row[R];
|
||||
line<C> * lines[R];
|
||||
int32_t length;
|
||||
struct {
|
||||
line<C> * 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<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 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<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();
|
||||
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_right();
|
||||
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)
|
||||
{
|
||||
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<C, R>::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<C> * buffer<C, R>::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<C, R>::deallocate(line<C> *& l)
|
||||
// does not touch alloc_ix
|
||||
fill<uint8_t>(l->buf, 0x7f, l->length);
|
||||
l->length = -1;
|
||||
l->refcount = 0;
|
||||
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>
|
||||
inline constexpr bool buffer<C, R>::put(const uint8_t c)
|
||||
{
|
||||
struct cursor& cur = this->cursor;
|
||||
line<C> *& l = this->lines[cur.row];
|
||||
if (l == nullptr) {
|
||||
l = this->allocate();
|
||||
}
|
||||
line<C> * l = mutref(this->lines[cur.row]);
|
||||
|
||||
// v
|
||||
// 0123
|
||||
@ -141,47 +236,36 @@ inline constexpr bool buffer<C, R>::put(const uint8_t c)
|
||||
template <int C, int R>
|
||||
inline constexpr bool buffer<C, R>::backspace()
|
||||
{
|
||||
if (this->mode == mode::mark) {
|
||||
return mark_delete();
|
||||
}
|
||||
|
||||
struct cursor& cur = this->cursor;
|
||||
|
||||
if (cur.col < 0 || cur.col > C)
|
||||
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.row == 0) return false;
|
||||
// combine this line with the previous line
|
||||
line<C> *& 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<C>*)) * 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<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);
|
||||
|
||||
@ -207,8 +291,7 @@ inline constexpr bool buffer<C, R>::cursor_left()
|
||||
scroll_up();
|
||||
|
||||
line<C> 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<C, R>::cursor_right()
|
||||
struct cursor& cur = this->cursor;
|
||||
|
||||
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.row >= (this->length - 1))
|
||||
return false;
|
||||
@ -254,8 +337,7 @@ inline constexpr bool buffer<C, R>::cursor_up()
|
||||
scroll_up();
|
||||
|
||||
line<C> 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<C, R>::cursor_down()
|
||||
scroll_down();
|
||||
|
||||
line<C> 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 <int C, int R>
|
||||
inline constexpr bool buffer<C, R>::cursor_home()
|
||||
{
|
||||
struct cursor& cur = this->cursor;
|
||||
|
||||
line<C> 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<C, R>::cursor_end()
|
||||
{
|
||||
struct cursor& cur = this->cursor;
|
||||
|
||||
line<C> 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<C, R>::enter()
|
||||
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
|
||||
line<C> * old_l = this->lines[cur.row];
|
||||
line<C> * new_l = allocate();
|
||||
line<C> * new_l = this->allocate();
|
||||
line<C> *& 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<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>
|
||||
inline constexpr void buffer<C, R>::set_mark()
|
||||
inline constexpr void buffer<C, R>::mark_set()
|
||||
{
|
||||
this->mark.row = this->cursor.row;
|
||||
this->mark.col = this->cursor.col;
|
||||
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>
|
||||
inline constexpr void buffer<C, R>::quit()
|
||||
{
|
||||
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>
|
||||
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)
|
||||
this->window.left = (this->cursor.col - (this->window.cell_width - 1));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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];
|
||||
|
||||
for (int col = 0; col < buffer.window.cell_width; col++) {
|
||||
uint8_t c;
|
||||
|
||||
c = ' ';
|
||||
if (l != nullptr && (buffer.window.left + col) < l->length)
|
||||
c = l->buf[buffer.window.left + col];
|
||||
|
||||
set_char(col, row, 0, 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);
|
||||
|
||||
editor::selection sel;
|
||||
|
||||
if (buffer.mode == editor::mode::mark)
|
||||
sel = buffer.mark_get();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C"
|
||||
|
@ -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<uint8_t*>(&b.lines[1]) -
|
||||
reinterpret_cast<uint8_t*>(&b.lines[0])) << '\n';
|
||||
std::cerr << " shadow.lines: " << (sizeof (b.shadow.lines)) << '\n';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user