jes/src/core/Buffer.cc

493 lines
14 KiB
C++

#include "Buffer.h"
#include <stdio.h>
#include "System.h"
#include "File.h"
#include "TextLoader.h"
#include <cassert>
#include <string.h>
Buffer::Buffer()
{
common_pre_load_initialization();
load_empty_buffer();
common_post_load_initialization();
}
Buffer::Buffer(const char * filename)
{
common_pre_load_initialization();
if (!load_from_file(filename))
{
load_empty_buffer();
}
common_post_load_initialization();
}
Buffer::Buffer(const uint8_t * data, size_t data_length)
{
common_pre_load_initialization();
if (!load_from_memory(data, data_length))
{
load_empty_buffer();
}
common_post_load_initialization();
}
void Buffer::common_pre_load_initialization()
{
m_tabstop = 4u;
m_change_buffer = std::make_shared<GapBuffer>();
m_operation_level = 0;
m_current_change_operation_index = INVALID_CHANGE_OPERATION_INDEX;
m_encoding = Encoding::UTF_8;
}
void Buffer::common_post_load_initialization()
{
m_eof_iterator = add_iterator();
while (m_eof_iterator->valid())
{
m_eof_iterator->go_forward();
}
}
void Buffer::load_empty_buffer()
{
m_gap_buffer = std::make_shared<GapBuffer>();
m_eol_at_eof = true;
m_line_endings = LineEndings::LF;
}
bool Buffer::load_from_file(const char * filename)
{
File file;
if (!file.open(filename))
{
return false;
}
size_t file_size = file.get_size();
if (file_size < 0)
{
return false;
}
size_t buffer_size = ((unsigned long)file_size + System::page_size + (64u * 1024u)) & System::page_base_mask;
uint8_t * buffer = (uint8_t *)System::alloc_pages(buffer_size >> System::page_size_log);
if (buffer == NULL)
{
return false;
}
if (!file.read(buffer, file_size))
{
System::free_pages(buffer, buffer_size >> System::page_size_log);
return false;
}
load_text_in_buffer(buffer, buffer_size, file_size);
m_filename = std::make_shared<std::string>(filename);
return true;
}
bool Buffer::load_from_memory(const uint8_t * data, size_t data_length)
{
size_t buffer_size = ((unsigned long)data_length + 2u * System::page_size) & System::page_base_mask;
uint8_t * buffer = (uint8_t *)System::alloc_pages(buffer_size >> System::page_size_log);
if (buffer == NULL)
{
return false;
}
memcpy(buffer, data, data_length);
load_text_in_buffer(buffer, buffer_size, data_length);
return true;
}
void Buffer::load_text_in_buffer(uint8_t * buffer, size_t buffer_size, size_t data_length)
{
TextLoader text_loader;
size_t loaded_size;
text_loader.load_buffer(buffer, data_length, &loaded_size);
m_gap_buffer = std::make_shared<GapBuffer>(buffer, buffer_size, loaded_size);
m_encoding = text_loader.get_encoding();
m_eol_at_eof = text_loader.get_eol_at_eof();
m_line_endings = text_loader.get_line_endings();
}
bool Buffer::write_to_file(const char * filename)
{
File file;
if (!file.open(filename, true))
{
return false;
}
m_gap_buffer->compact();
Iterator start_of_line(this);
size_t bytes_written = 0u;
while (start_of_line.valid())
{
Iterator iterator = start_of_line;
iterator.go_end_of_line(true);
size_t len = iterator.address() - start_of_line.address();
if (len > 0u)
{
if (!file.write(start_of_line.address(), len))
{
return false;
}
bytes_written += len;
}
bool moved_down = iterator.go_next_line();
if (moved_down || m_eol_at_eof)
{
if (!file.write(LineEndings::spans[m_line_endings]))
{
return false;
}
bytes_written += LineEndings::spans[m_line_endings].length;
}
if (!moved_down)
{
break;
}
start_of_line = iterator;
}
return true;
}
void Buffer::enter_insert_mode()
{
m_insert_mode = true;
push_operation();
}
void Buffer::exit_insert_mode()
{
m_insert_mode = false;
pop_operation();
}
void Buffer::insert_code_point(const Buffer::Iterator & position, uint32_t code_point)
{
push_operation();
uint8_t encoded[Encoding::MAX_CODE_POINT_SIZE * 2];
uint8_t bytes = Encoding::encode(code_point, m_encoding, encoded);
if ((size() == 0u) && (code_point != '\n'))
{
bytes += Encoding::encode('\n', m_encoding, &encoded[bytes]);
}
insert_data(position.offset(), encoded, bytes, true);
pop_operation();
}
void Buffer::insert_data(size_t offset, const uint8_t data[], size_t length, bool do_record_change)
{
m_gap_buffer->insert(offset, data, length);
if (do_record_change)
{
record_change(offset, length, true);
}
warp_iterators_after_insert(offset, length);
post_warp_cursors();
}
void Buffer::erase_code_point(const Buffer::Iterator & position)
{
if (position.valid())
{
push_operation();
erase_data(position.offset(),
Encoding::num_bytes_in_code_point(m_encoding, position.address()),
true);
pop_operation();
}
}
void Buffer::erase_range(const Buffer::Iterator & start,
const Buffer::Iterator & end)
{
if (start.valid() && (start < end))
{
push_operation();
size_t start_offset = start.offset();
size_t end_offset = end.valid() ? end.offset() : m_gap_buffer->size();
erase_data(start_offset, end_offset - start_offset, true);
pop_operation();
}
}
void Buffer::erase_data(size_t offset, size_t length, bool do_record_change)
{
warp_iterators_before_delete(offset, length);
if (do_record_change)
{
record_change(offset, length, false);
}
m_gap_buffer->erase(offset, length);
post_warp_cursors();
}
size_t Buffer::lines_in_data(size_t offset, size_t length)
{
size_t lines = 0u;
for (size_t i = 0u; i < length; )
{
uint8_t bytes;
uint32_t c = Encoding::decode(m_encoding, m_gap_buffer->address(offset + i), &bytes);
if (c == '\n')
{
lines++;
}
i += bytes;
}
return lines;
}
void Buffer::post_warp_cursors()
{
if (size() > 0u)
{
for (auto cursor : m_cursors)
{
if (*cursor == *m_eof_iterator)
{
cursor->go_back();
}
if ((!insert_mode()) && (**cursor == '\n'))
{
cursor->go_left_in_line();
}
}
}
}
void Buffer::warp_iterators_after_insert(size_t offset, size_t length)
{
size_t lines = lines_in_data(offset, length);
for (auto iterator : m_iterators)
{
if (iterator->offset() >= offset)
{
iterator->warp(length, lines);
}
}
}
void Buffer::warp_iterators_before_delete(size_t offset, size_t length)
{
size_t lines = lines_in_data(offset, length);
for (auto iterator : m_iterators)
{
/* Move any iterators within the chunk to be deleted to after it. */
while ((iterator->offset() >= offset) &&
(iterator->offset() < (offset + length)))
{
iterator->go_forward();
if (!iterator->valid())
{
break;
}
}
if (iterator->offset() >= offset)
{
iterator->warp(-(ssize_t)length, -(ssize_t)lines);
}
}
}
void Buffer::pop_operation()
{
if (m_operation_level > 0)
{
m_operation_level--;
if (m_operation_level == 0)
{
save_current_operation();
}
}
}
void Buffer::record_change(size_t offset, size_t length, bool insert)
{
if (!m_current_change_operation)
{
m_current_change_operation = std::make_shared<ChangeOperation>();
m_current_change_operation->parent = m_current_change_operation_index;
}
if (m_current_change_operation->changes.size() > 0)
{
/* There is a previous change unit in progress. See if it can be
* modified to encompass the new change. */
ChangeUnit & cu = *m_current_change_operation->changes.rbegin();
if (insert && cu.insert &&
((offset + length) >= cu.buffer_position) &&
(offset <= (cu.buffer_position + cu.length)))
{
/* The new insertion can extend the previous insertion change unit. */
size_t change_buffer_offset = cu.change_buffer_offset + offset - cu.buffer_position;
m_gap_buffer->copy_to(offset, length, *m_change_buffer, change_buffer_offset);
cu.length += length;
cu.change_buffer_offset = std::min(cu.change_buffer_offset, change_buffer_offset);
cu.buffer_position = std::min(cu.buffer_position, offset);
return;
}
else if ((!insert) && (!cu.insert) &&
((offset == cu.buffer_position) ||
((offset + length) == cu.buffer_position)))
{
/* The new deletion can extend the previous deletion change unit. */
if (offset == cu.buffer_position)
{
/* The new deletion immediately follows the previous. */
m_gap_buffer->copy_to(offset, length, *m_change_buffer, cu.change_buffer_offset + cu.length);
}
else
{
/* The new deletion immediately precedes the previous. */
m_gap_buffer->copy_to(offset, length, *m_change_buffer, cu.change_buffer_offset);
cu.buffer_position = offset;
}
cu.length += length;
return;
}
else if ((!insert) && cu.insert &&
(offset >= cu.buffer_position) &&
((offset + length) <= (cu.buffer_position + cu.length)))
{
/* The deletion is removing from the previous insertion change unit. */
m_change_buffer->erase(cu.change_buffer_offset + (offset - cu.buffer_position), length);
if (cu.length <= length)
{
m_current_change_operation->changes.erase(--m_current_change_operation->changes.end());
}
else
{
cu.length -= length;
}
return;
}
}
/* Start a new change unit. */
size_t change_buffer_offset = m_change_buffer->size();
m_gap_buffer->copy_to(offset, length, *m_change_buffer, change_buffer_offset);
ChangeUnit cu;
cu.change_buffer_offset = change_buffer_offset;
cu.length = length;
cu.buffer_position = offset;
cu.insert = insert;
m_current_change_operation->changes.push_back(cu);
}
void Buffer::save_current_operation()
{
if (m_current_change_operation)
{
if (m_current_change_operation->changes.size() > 0u)
{
size_t new_change_operation_index = m_change_operations.size();
if (m_current_change_operation_index < new_change_operation_index)
{
m_change_operations[m_current_change_operation_index]->children.push_back(new_change_operation_index);
}
m_change_operations.push_back(m_current_change_operation);
m_current_change_operation_index = new_change_operation_index;
}
m_current_change_operation = nullptr;
}
}
void Buffer::apply_change_operation(const ChangeOperation & change_operation, bool forward)
{
if (forward)
{
for (auto change_unit : change_operation.changes)
{
apply_change_unit(change_unit, forward);
}
}
else
{
for (auto i = change_operation.changes.rbegin();
i != change_operation.changes.rend();
i++)
{
apply_change_unit(*i, forward);
}
}
}
void Buffer::apply_change_unit(const ChangeUnit & change_unit, bool forward)
{
if (forward == change_unit.insert)
{
m_change_buffer->compact();
insert_data(change_unit.buffer_position,
m_change_buffer->address(change_unit.change_buffer_offset),
change_unit.length,
false);
}
else
{
erase_data(change_unit.buffer_position,
change_unit.length,
false);
}
}
void Buffer::undo()
{
if (m_current_change_operation_index != INVALID_CHANGE_OPERATION_INDEX)
{
apply_change_operation(*m_change_operations[m_current_change_operation_index], false);
m_current_change_operation_index = m_change_operations[m_current_change_operation_index]->parent;
}
}
void Buffer::redo()
{
size_t child_index = INVALID_CHANGE_OPERATION_INDEX;
if (m_current_change_operation_index == INVALID_CHANGE_OPERATION_INDEX)
{
if (m_change_operations.size() > 0u)
{
child_index = 0u;
}
}
else if (m_change_operations[m_current_change_operation_index]->children.size() > 0u)
{
/* TODO: support redoing a child of the undo tree other than the first */
child_index = *m_change_operations[m_current_change_operation_index]->children.begin();
}
if (child_index != INVALID_CHANGE_OPERATION_INDEX)
{
apply_change_operation(*m_change_operations[child_index], true);
m_current_change_operation_index = child_index;
}
}
void Buffer::clear()
{
m_gap_buffer->clear();
for (auto iterator : m_iterators)
{
auto b = begin();
*iterator = b;
}
}
EncodedString Buffer::get_string()
{
m_gap_buffer->compact();
return EncodedString(m_gap_buffer->address(0u), m_gap_buffer->size(), m_encoding);
}