599 lines
15 KiB
C++
599 lines
15 KiB
C++
#include "PieceTable.h"
|
|
|
|
PieceTable::PieceTable()
|
|
{
|
|
m_piece_index = 0u;
|
|
start_piece = add_piece();
|
|
end_piece = add_piece();
|
|
start_piece->length = 0u;
|
|
end_piece->length = 0u;
|
|
start_piece->next = end_piece;
|
|
end_piece->prev = start_piece;
|
|
tabstop = 4u;
|
|
m_append_buffer_index = 0u;
|
|
m_changes_index = 0u;
|
|
}
|
|
|
|
void PieceTable::append_initial_line_piece(uint8_t * start, uint32_t length, bool eol)
|
|
{
|
|
Piece * piece = add_piece();
|
|
piece->prev = end_piece->prev;
|
|
piece->next = end_piece;
|
|
piece->start = start;
|
|
piece->length = length;
|
|
end_piece->prev->next = piece;
|
|
end_piece->prev = piece;
|
|
m_num_lines++;
|
|
}
|
|
|
|
std::shared_ptr<PieceTable::Cursor> PieceTable::add_cursor()
|
|
{
|
|
auto cursor = std::make_shared<Cursor>(this);
|
|
|
|
m_cursors.push_back(cursor);
|
|
|
|
return cursor;
|
|
}
|
|
|
|
PieceTable::Iterator::Iterator(PieceTable * pt)
|
|
{
|
|
piece_table = pt;
|
|
piece = pt->start_piece->next;
|
|
while ((piece->length == 0u) && (piece != piece_table->end_piece))
|
|
{
|
|
piece = piece->next;
|
|
}
|
|
offset = 0u;
|
|
}
|
|
|
|
uint8_t PieceTable::Iterator::num_bytes_in_code_point() const
|
|
{
|
|
if (offset >= piece->length)
|
|
{
|
|
return 0u;
|
|
}
|
|
else
|
|
{
|
|
return Encoding::num_bytes_in_code_point(piece_table->encoding, &piece->start[offset]);
|
|
}
|
|
}
|
|
|
|
void PieceTable::Iterator::go_next_position()
|
|
{
|
|
if (valid())
|
|
{
|
|
uint8_t code_point_size = num_bytes_in_code_point();
|
|
if ((offset + code_point_size) >= piece->length)
|
|
{
|
|
do
|
|
{
|
|
piece = piece->next;
|
|
} while ((piece != piece_table->end_piece) &&
|
|
(piece->length == 0u));
|
|
offset = 0u;
|
|
return;
|
|
}
|
|
offset += code_point_size;
|
|
}
|
|
}
|
|
|
|
void PieceTable::Iterator::go_prev_position()
|
|
{
|
|
if (piece != piece_table->start_piece)
|
|
{
|
|
if (offset > 0)
|
|
{
|
|
offset = Encoding::beginning_of_code_point(piece_table->encoding, &piece->start[offset - 1u]) - piece->start;
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
piece = piece->prev;
|
|
} while ((piece != piece_table->start_piece) &&
|
|
(piece->length == 0u));
|
|
if (piece->length > 0u)
|
|
{
|
|
offset = Encoding::beginning_of_code_point(piece_table->encoding, &piece->start[piece->length - 1u]) - piece->start;
|
|
}
|
|
else
|
|
{
|
|
offset = 0u;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PieceTable::Iterator::go_end_of_piece()
|
|
{
|
|
if (piece->length > 0u)
|
|
{
|
|
offset = Encoding::beginning_of_code_point(piece_table->encoding, &piece->start[piece->length - 1u]) - piece->start;
|
|
}
|
|
else
|
|
{
|
|
offset = 0u;
|
|
}
|
|
}
|
|
|
|
void PieceTable::Iterator::go_next_piece()
|
|
{
|
|
if (piece != piece_table->end_piece)
|
|
{
|
|
do
|
|
{
|
|
piece = piece->next;
|
|
} while ((piece != piece_table->end_piece) &&
|
|
(piece->length == 0u));
|
|
offset = 0u;
|
|
}
|
|
}
|
|
|
|
void PieceTable::Iterator::go_prev_piece()
|
|
{
|
|
if (piece != piece_table->start_piece)
|
|
{
|
|
do
|
|
{
|
|
piece = piece->prev;
|
|
} while ((piece != piece_table->start_piece) &&
|
|
(piece->length == 0u));
|
|
offset = 0u;
|
|
}
|
|
}
|
|
|
|
PieceTable::Cursor::Cursor(PieceTable * pt) : iterator(pt)
|
|
{
|
|
line = 0u;
|
|
init_column();
|
|
}
|
|
|
|
void PieceTable::Cursor::go_start_of_line()
|
|
{
|
|
Iterator last_iterator = iterator;
|
|
for (;;)
|
|
{
|
|
iterator.go_prev_piece();
|
|
if (iterator.piece_eol() || !iterator.valid())
|
|
{
|
|
break;
|
|
}
|
|
last_iterator = iterator;
|
|
}
|
|
iterator = last_iterator;
|
|
iterator.offset = 0u;
|
|
init_column();
|
|
}
|
|
|
|
bool PieceTable::Cursor::check_go_start_of_line()
|
|
{
|
|
uint32_t current_line = line;
|
|
uint32_t current_column = column;
|
|
go_start_of_line();
|
|
return (current_line != line) || (current_column != column);
|
|
}
|
|
|
|
void PieceTable::Cursor::go_end_of_line(bool allow_on_eol)
|
|
{
|
|
if (!iterator.valid())
|
|
{
|
|
return;
|
|
}
|
|
for (;;)
|
|
{
|
|
if (iterator.piece_eol())
|
|
{
|
|
break;
|
|
}
|
|
Iterator move_iterator = iterator;
|
|
move_iterator.go_next_piece();
|
|
if (move_iterator.valid())
|
|
{
|
|
iterator = move_iterator;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
iterator.go_end_of_piece();
|
|
calculate_column();
|
|
if (!allow_on_eol)
|
|
{
|
|
go_left(1);
|
|
}
|
|
}
|
|
|
|
bool PieceTable::Cursor::check_go_end_of_line(bool allow_on_eol)
|
|
{
|
|
uint32_t current_line = line;
|
|
uint32_t current_column = column;
|
|
go_end_of_line(allow_on_eol);
|
|
return (current_line != line) || (current_column != column);
|
|
}
|
|
|
|
void PieceTable::Cursor::go_up(int n, uint32_t desired_column)
|
|
{
|
|
if (!iterator.valid())
|
|
return;
|
|
Piece * p = prev_line();
|
|
if (p == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
iterator.piece = p;
|
|
iterator.offset = 0u;
|
|
line--;
|
|
init_column();
|
|
forward_to_column(desired_column);
|
|
}
|
|
|
|
bool PieceTable::Cursor::check_go_up(int n, uint32_t desired_column)
|
|
{
|
|
uint32_t current_line = line;
|
|
uint32_t current_column = column;
|
|
go_up(n, desired_column);
|
|
return (current_line != line) || (current_column != column);
|
|
}
|
|
|
|
void PieceTable::Cursor::go_down(int n, uint32_t desired_column)
|
|
{
|
|
if (!iterator.valid())
|
|
return;
|
|
Piece * p = next_line();
|
|
if (p == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
iterator.piece = p;
|
|
iterator.offset = 0u;
|
|
line++;
|
|
init_column();
|
|
forward_to_column(desired_column);
|
|
}
|
|
|
|
bool PieceTable::Cursor::check_go_down(int n, uint32_t desired_column)
|
|
{
|
|
uint32_t current_line = line;
|
|
uint32_t current_column = column;
|
|
go_down(n, desired_column);
|
|
return (current_line != line) || (current_column != column);
|
|
}
|
|
|
|
void PieceTable::Cursor::go_left(int n)
|
|
{
|
|
if (!iterator.valid())
|
|
return;
|
|
if (is_start_of_line())
|
|
{
|
|
return;
|
|
}
|
|
uint32_t chr = *iterator;
|
|
iterator.go_prev_position();
|
|
if (chr == '\t')
|
|
{
|
|
calculate_column();
|
|
}
|
|
else
|
|
{
|
|
/* TODO: handle multi-column characters */
|
|
column--;
|
|
}
|
|
}
|
|
|
|
bool PieceTable::Cursor::check_go_left(int n)
|
|
{
|
|
uint32_t current_line = line;
|
|
uint32_t current_column = column;
|
|
go_left(n);
|
|
return (current_line != line) || (current_column != column);
|
|
}
|
|
|
|
void PieceTable::Cursor::go_right(int n, bool allow_on_eol)
|
|
{
|
|
if (!iterator.valid())
|
|
return;
|
|
if (is_end_of_line())
|
|
{
|
|
return;
|
|
}
|
|
Iterator iterator_2 = iterator;
|
|
iterator.go_next_position();
|
|
if ((!allow_on_eol) && is_end_of_line())
|
|
{
|
|
iterator = iterator_2;
|
|
return;
|
|
}
|
|
uint32_t chr = *iterator;
|
|
if (chr == '\t')
|
|
{
|
|
uint8_t tabstop = iterator.piece_table->tabstop;
|
|
column += tabstop - (column + 1u) % tabstop;
|
|
}
|
|
else
|
|
{
|
|
/* TODO: handle multi-column characters */
|
|
column++;
|
|
}
|
|
}
|
|
|
|
bool PieceTable::Cursor::check_go_right(int n, bool allow_on_eol)
|
|
{
|
|
uint32_t current_line = line;
|
|
uint32_t current_column = column;
|
|
go_right(n, allow_on_eol);
|
|
return (current_line != line) || (current_column != column);
|
|
}
|
|
|
|
void PieceTable::Cursor::warp_to_inserted_piece(Piece * piece)
|
|
{
|
|
iterator.piece = piece;
|
|
iterator.offset = 0u;
|
|
iterator.go_end_of_piece();
|
|
calculate_column();
|
|
}
|
|
|
|
bool PieceTable::Cursor::is_start_of_line()
|
|
{
|
|
return iterator.valid() &&
|
|
(iterator.offset == 0u) &&
|
|
((iterator.piece->prev == iterator.piece_table->start_piece) ||
|
|
iterator.piece->prev->eol());
|
|
}
|
|
|
|
bool PieceTable::Cursor::is_end_of_line()
|
|
{
|
|
return iterator.valid() &&
|
|
((iterator.offset + iterator.num_bytes_in_code_point()) >= iterator.piece->length) &&
|
|
(iterator.piece->eol() || (iterator.piece->next == iterator.piece_table->end_piece));
|
|
}
|
|
|
|
PieceTable::Piece * PieceTable::Cursor::prev_line() const
|
|
{
|
|
Piece * p = iterator.piece;
|
|
while ((!p->prev->eol()) && (p->prev != iterator.piece_table->start_piece))
|
|
{
|
|
p = p->prev;
|
|
}
|
|
if (p->prev == iterator.piece_table->start_piece)
|
|
{
|
|
return nullptr;
|
|
}
|
|
p = p->prev;
|
|
while ((!p->prev->eol()) && (p->prev != iterator.piece_table->start_piece))
|
|
{
|
|
p = p->prev;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
PieceTable::Piece * PieceTable::Cursor::next_line() const
|
|
{
|
|
Piece * p = iterator.piece;
|
|
while ((!p->eol()) && (p->next != iterator.piece_table->end_piece))
|
|
{
|
|
p = p->next;
|
|
}
|
|
if (p->next == iterator.piece_table->end_piece)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return p->next;
|
|
}
|
|
|
|
void PieceTable::Cursor::init_column()
|
|
{
|
|
uint32_t chr = *iterator;
|
|
/* TODO: handle multi-column characters */
|
|
column = (chr == '\t') ? iterator.piece_table->tabstop - 1u : 0u;
|
|
}
|
|
|
|
void PieceTable::Cursor::calculate_column()
|
|
{
|
|
Cursor tmp_cursor = *this;
|
|
tmp_cursor.go_start_of_line();
|
|
while ((tmp_cursor.iterator.piece != iterator.piece) ||
|
|
(tmp_cursor.iterator.offset < iterator.offset))
|
|
{
|
|
tmp_cursor.go_right(1, true);
|
|
}
|
|
column = tmp_cursor.column;
|
|
}
|
|
|
|
void PieceTable::Cursor::forward_to_column(uint32_t c)
|
|
{
|
|
int32_t diff = abs((int32_t)(c - column));
|
|
if (diff == 0)
|
|
return;
|
|
while (check_go_right(1, false))
|
|
{
|
|
int32_t new_diff = abs((int32_t)(c - column));
|
|
if (new_diff > diff)
|
|
{
|
|
go_left(1);
|
|
return;
|
|
}
|
|
diff = new_diff;
|
|
}
|
|
}
|
|
|
|
void PieceTable::begin_insert(const Cursor & cursor, bool before)
|
|
{
|
|
Piece * piece = cursor.iterator.piece;
|
|
uint32_t offset = cursor.iterator.offset;
|
|
if (!before && cursor.iterator.has_char() && (*cursor != INTERNAL_EOL))
|
|
{
|
|
offset += cursor.iterator.num_bytes_in_code_point();
|
|
}
|
|
if (offset == 0u)
|
|
{
|
|
/* Insert happens at piece boundary between piece->prev and piece. */
|
|
m_insert_before_piece = piece->prev;
|
|
}
|
|
else if (offset >= piece->length)
|
|
{
|
|
/* Insert happens at piece boundary between piece and piece->next. */
|
|
m_insert_before_piece = piece;
|
|
}
|
|
else
|
|
{
|
|
/* Insert happens in the middle of a current piece. The piece must
|
|
* be split up. */
|
|
Piece * new_piece_1 = add_piece();
|
|
new_piece_1->start = piece->start;
|
|
new_piece_1->length = offset;
|
|
|
|
Piece * new_piece_2 = add_piece();
|
|
new_piece_2->start = piece->start + offset;
|
|
new_piece_2->length = piece->length - offset;
|
|
|
|
new_piece_1->prev = piece->prev;
|
|
new_piece_1->next = new_piece_2;
|
|
new_piece_2->prev = new_piece_1;
|
|
new_piece_2->next = piece->next;
|
|
|
|
Change * change = add_change();
|
|
change->before = piece->prev;
|
|
change->after = piece->next;
|
|
change->chains[0][0] = piece;
|
|
change->chains[0][1] = nullptr;
|
|
change->chains[1][0] = new_piece_1;
|
|
change->chains[1][1] = new_piece_2;
|
|
|
|
apply_change(change, 1u);
|
|
|
|
m_insert_before_piece = new_piece_1;
|
|
}
|
|
|
|
m_inserting_piece = nullptr;
|
|
}
|
|
|
|
void PieceTable::insert_code_point(uint32_t code_point)
|
|
{
|
|
if (m_inserting_piece == nullptr)
|
|
{
|
|
m_inserting_piece = add_piece();
|
|
m_inserting_piece->start = &m_append_buffer[m_append_buffer_index];
|
|
m_inserting_piece->length = 0u;
|
|
m_inserting_piece->prev = m_insert_before_piece;
|
|
m_inserting_piece->next = m_insert_before_piece->next;
|
|
|
|
Change * change = add_change();
|
|
change->before = m_insert_before_piece;
|
|
change->after = m_insert_before_piece->next;
|
|
change->chains[0][0] = nullptr;
|
|
change->chains[0][1] = nullptr;
|
|
change->chains[1][0] = m_inserting_piece;
|
|
change->chains[1][1] = nullptr;
|
|
|
|
apply_change(change, 1u);
|
|
}
|
|
|
|
uint8_t bytes = Encoding::encode(code_point, encoding, &m_append_buffer[m_append_buffer_index]);
|
|
m_inserting_piece->length += bytes;
|
|
m_append_buffer_index += bytes;
|
|
}
|
|
|
|
void PieceTable::end_insert()
|
|
{
|
|
}
|
|
|
|
void PieceTable::insertion_test(Cursor & c)
|
|
{
|
|
Piece * piece = add_piece();
|
|
piece->start = &m_append_buffer[m_append_buffer_index];
|
|
piece->length = 3u;
|
|
m_append_buffer[m_append_buffer_index++] = 'x';
|
|
m_append_buffer[m_append_buffer_index++] = 'y';
|
|
m_append_buffer[m_append_buffer_index++] = 'z';
|
|
|
|
Change * change = add_change();
|
|
Change * change2 = nullptr;
|
|
|
|
if (c.iterator.valid())
|
|
{
|
|
change->before = c.iterator.piece->prev;
|
|
change->after = c.iterator.piece->next;
|
|
change->chains[0][0] = c.iterator.piece;
|
|
change->chains[0][1] = nullptr;
|
|
if (c.iterator.offset > 0u)
|
|
{
|
|
Piece * piece2 = add_piece();
|
|
piece2->start = c.iterator.piece->start;
|
|
piece2->length = c.iterator.offset;
|
|
|
|
Piece * piece3 = add_piece();
|
|
piece3->start = c.iterator.piece->start + c.iterator.offset;
|
|
piece3->length = c.iterator.piece->length - c.iterator.offset;
|
|
|
|
piece2->next = piece;
|
|
piece->prev = piece2;
|
|
piece->next = piece3;
|
|
piece3->prev = piece;
|
|
|
|
change->chains[1][0] = piece2;
|
|
change->chains[1][1] = piece;
|
|
|
|
change2 = add_change();
|
|
change2->before = piece;
|
|
change2->after = c.iterator.piece->next;
|
|
change2->chains[0][0] = nullptr;
|
|
change2->chains[0][1] = nullptr;
|
|
change2->chains[1][0] = piece3;
|
|
change2->chains[1][1] = nullptr;
|
|
}
|
|
else
|
|
{
|
|
change->chains[1][0] = piece;
|
|
change->chains[1][1] = nullptr;
|
|
if (c.iterator.piece->length != 0u)
|
|
{
|
|
change->after = c.iterator.piece;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
change->before = start_piece;
|
|
change->after = end_piece;
|
|
change->chains[0][0] = nullptr;
|
|
change->chains[0][1] = nullptr;
|
|
change->chains[1][0] = piece;
|
|
change->chains[1][1] = nullptr;
|
|
}
|
|
|
|
apply_change(change, 1u);
|
|
if (change2 != nullptr)
|
|
{
|
|
apply_change(change2, 1u);
|
|
}
|
|
|
|
c.warp_to_inserted_piece(piece);
|
|
}
|
|
|
|
void PieceTable::undo()
|
|
{
|
|
if (m_changes_index > 0u)
|
|
{
|
|
m_changes_index--;
|
|
apply_change(&m_changes[m_changes_index], 0u);
|
|
}
|
|
}
|
|
|
|
void PieceTable::apply_change(Change * change, uint8_t forward)
|
|
{
|
|
Piece * p = change->before;
|
|
for (uint8_t i = 0; i < sizeof(change->chains[0]) / sizeof(change->chains[0][0]); i++)
|
|
{
|
|
if (change->chains[forward][i] == nullptr)
|
|
{
|
|
break;
|
|
}
|
|
p->next = change->chains[forward][i];
|
|
change->chains[forward][i]->prev = p;
|
|
p = change->chains[forward][i];
|
|
}
|
|
p->next = change->after;
|
|
change->after->prev = p;
|
|
}
|