424 lines
12 KiB
C++
424 lines
12 KiB
C++
#include "Buffer.h"
|
|
#include <stdio.h>
|
|
#include "System.h"
|
|
#include "File.h"
|
|
#include "TextLoader.h"
|
|
#include <cassert>
|
|
#include <string.h>
|
|
|
|
Buffer::Buffer()
|
|
{
|
|
load_empty_buffer();
|
|
common_initialization();
|
|
}
|
|
|
|
Buffer::Buffer(const char * filename)
|
|
{
|
|
if (!load_from_file(filename))
|
|
{
|
|
load_empty_buffer();
|
|
}
|
|
common_initialization();
|
|
}
|
|
|
|
Buffer::Buffer(const uint8_t * data, size_t data_length)
|
|
{
|
|
if (!load_from_memory(data, data_length))
|
|
{
|
|
load_empty_buffer();
|
|
}
|
|
common_initialization();
|
|
}
|
|
|
|
void Buffer::common_initialization()
|
|
{
|
|
m_tabstop = 4u;
|
|
m_eof_iterator = add_iterator();
|
|
while (m_eof_iterator->valid())
|
|
{
|
|
m_eof_iterator->go_forward();
|
|
}
|
|
m_change_buffer = std::make_shared<GapBuffer>();
|
|
m_operation_level = 0;
|
|
m_current_change_operation_index = INVALID_CHANGE_OPERATION_INDEX;
|
|
}
|
|
|
|
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 = 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);
|
|
pop_operation();
|
|
}
|
|
|
|
void Buffer::insert_data(size_t offset, const uint8_t data[], size_t length)
|
|
{
|
|
m_gap_buffer->insert(offset, data, length);
|
|
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()));
|
|
pop_operation();
|
|
}
|
|
}
|
|
|
|
void Buffer::erase_data(size_t offset, size_t length)
|
|
{
|
|
warp_iterators_before_delete(offset, length);
|
|
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), &bytes);
|
|
if (c == '\n')
|
|
{
|
|
lines++;
|
|
}
|
|
i += bytes;
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
void Buffer::post_warp_cursors()
|
|
{
|
|
for (auto cursor : m_cursors)
|
|
{
|
|
if ((*cursor == *m_eof_iterator) && (size() > 0u))
|
|
{
|
|
cursor->go_back();
|
|
}
|
|
}
|
|
}
|
|
|
|
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.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)
|
|
{
|
|
for (auto change_unit : change_operation.changes)
|
|
{
|
|
apply_change_unit(change_unit, forward);
|
|
}
|
|
}
|
|
|
|
void Buffer::apply_change_unit(const ChangeUnit & change_unit, bool forward)
|
|
{
|
|
if (forward == change_unit.insert)
|
|
{
|
|
insert_data(change_unit.buffer_position,
|
|
m_change_buffer->address(change_unit.change_buffer_offset),
|
|
change_unit.length);
|
|
}
|
|
else
|
|
{
|
|
erase_data(change_unit.buffer_position, change_unit.length);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|