mv src src-c

This commit is contained in:
josh committed 2018-07-25 20:47:02 -04:00
1 parent a0b2f660a2
commit 50cd9efd86
75 files changed

No files matched your search

+291
View File
@@ -0,0 +1,291 @@
#include "Buffer.h"
bool Buffer::Iterator::is_start_of_line()
{
if (valid())
{
if (m_offset == 0u)
{
return true;
}
const uint8_t * a = m_buffer->address(m_offset - 1u);
a = Encoding::beginning_of_code_point(m_buffer->encoding(), a);
if (Encoding::decode(m_buffer->encoding(), a) == '\n')
{
return true;
}
}
return false;
}
bool Buffer::Iterator::is_end_of_line(bool eol_only)
{
if (valid())
{
if (**this == '\n')
{
return true;
}
if (!eol_only)
{
return false;
}
Iterator i2 = *this;
if (!i2.go_forward())
{
return false;
}
if (*i2 == '\n')
{
return true;
}
}
return false;
}
bool Buffer::Iterator::go_back()
{
if (valid() || (m_offset == m_buffer->size()))
{
if (m_offset == 0u)
{
m_offset = (size_t)-1;
}
else
{
const uint8_t * a = m_buffer->address(m_offset - 1u);
const uint8_t * beginning_of_code_point = Encoding::beginning_of_code_point(m_buffer->encoding(), a);
m_offset -= ((a - beginning_of_code_point) + 1u);
if (Encoding::decode(m_buffer->encoding(), address()) == '\n')
{
m_line--;
}
}
return true;
}
return false;
}
bool Buffer::Iterator::go_forward()
{
if (valid())
{
uint8_t size;
uint32_t c = Encoding::decode(m_buffer->encoding(), address(), &size);
m_offset += size;
if (c == '\n')
{
m_line++;
}
return true;
}
else if (m_offset == (size_t)-1)
{
m_offset = 0u;
return true;
}
return false;
}
bool Buffer::Iterator::go_start_of_line()
{
bool moved = false;
Iterator i2 = *this;
i2.go_back();
while (i2.valid() && (*i2 != '\n'))
{
m_offset = i2.m_offset;
moved = true;
i2.go_back();
}
return moved;
}
bool Buffer::Iterator::go_end_of_line(bool allow_eol)
{
bool moved = false;
if (go_right_in_line(allow_eol))
{
moved = true;
}
while (go_right_in_line(allow_eol))
{
;
}
return moved;
}
bool Buffer::Iterator::go_left_in_line()
{
bool moved = false;
Iterator i2 = *this;
i2.go_back();
if (i2.valid() && (*i2 != '\n'))
{
m_offset = i2.m_offset;
moved = true;
}
return moved;
}
bool Buffer::Iterator::go_right_in_line(bool allow_eol)
{
if (valid() && (**this != '\n'))
{
if (allow_eol)
{
return go_forward();
}
else
{
Iterator i2 = *this;
i2.go_forward();
if (i2.valid() && (*i2 != '\n'))
{
m_offset = i2.m_offset;
return true;
}
}
}
return false;
}
bool Buffer::Iterator::go_previous_line()
{
Iterator i2 = *this;
while (i2.valid())
{
i2.go_back();
if (*i2 == '\n')
{
i2.go_start_of_line();
m_line--;
m_offset = i2.m_offset;
return true;
}
}
return false;
}
bool Buffer::Iterator::go_next_line()
{
Iterator i2 = *this;
i2.go_end_of_line(true);
i2.go_forward();
if (i2.valid())
{
m_line++;
m_offset = i2.m_offset;
return true;
}
return false;
}
void Buffer::Iterator::warp_to_offset(size_t offset)
{
Iterator i = m_buffer->begin();
m_offset = i.m_offset;
m_line = i.m_line;
for (;;)
{
i.go_forward();
if (i.m_offset > offset)
{
break;
}
m_offset = i.m_offset;
m_line = i.m_line;
if (!i.valid())
{
break;
}
}
}
bool Buffer::Iterator::go_forward_in_line_up_to_char(uint32_t c)
{
size_t last_offset = m_offset;
Buffer::Iterator it = *this;
while (it.go_right_in_line(false))
{
if (*it == c)
{
if (last_offset != m_offset)
{
m_offset = last_offset;
return true;
}
else
{
return false;
}
}
last_offset = it.m_offset;
}
return false;
}
bool Buffer::Iterator::go_forward_in_line_on_to_char(uint32_t c)
{
Buffer::Iterator it = *this;
while (it.go_right_in_line(false))
{
if (*it == c)
{
m_offset = it.m_offset;
return true;
}
}
return false;
}
bool Buffer::Iterator::go_backward_in_line_up_to_char(uint32_t c)
{
size_t last_offset = m_offset;
Buffer::Iterator it = *this;
while (it.go_left_in_line())
{
if (*it == c)
{
if (last_offset != m_offset)
{
m_offset = last_offset;
return true;
}
else
{
return false;
}
}
last_offset = it.m_offset;
}
return false;
}
bool Buffer::Iterator::go_backward_in_line_on_to_char(uint32_t c)
{
Buffer::Iterator it = *this;
while (it.go_left_in_line())
{
if (*it == c)
{
m_offset = it.m_offset;
return true;
}
}
return false;
}
Buffer::Iterator Buffer::Iterator::prev()
{
Iterator i = *this;
i.go_back();
return i;
}
Buffer::Iterator Buffer::Iterator::next()
{
Iterator i = *this;
i.go_forward();
return i;
}
+515
View File
@@ -0,0 +1,515 @@
#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, size_t * n_lines, size_t * n_bytes)
{
size_t l_n_lines = 0;
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())
{
l_n_lines++;
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;
}
if (n_lines != nullptr)
{
*n_lines = l_n_lines;
}
if (n_bytes != nullptr)
{
*n_bytes = bytes_written;
}
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::Range & range)
{
if (range.valid() && (range.size() > 0u))
{
push_operation();
erase_data(range.start_offset(), range.size(), 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);
}
void Buffer::set_filename(const std::string & filename)
{
m_filename = std::make_shared<std::string>(filename);
}
size_t Buffer::n_lines() const
{
auto it = end();
it.go_back();
if (it.valid())
{
return it.line() + 1u;
}
return 0u;
}
+234
View File
@@ -0,0 +1,234 @@
#ifndef BUFFER_H
#define BUFFER_H
#include <list>
#include <memory>
#include "LineEndings.h"
#include "Encoding.h"
#include "GapBuffer.h"
#include <string>
#include "ChangeOperation.h"
#include <vector>
#include "EncodedString.h"
class Buffer
{
public:
enum : uint32_t
{
INVALID_CHANGE_OPERATION_INDEX = 0xFFFFFFFFu,
};
struct Range;
class Iterator
{
public:
enum : uint32_t
{
INVALID_CODE_POINT = 0xFFFFFFFFu,
};
Iterator(const Buffer * buffer)
{
m_buffer = buffer;
m_offset = 0u;
m_line = 0u;
}
bool valid() const
{
return m_offset < m_buffer->size();
}
bool is_start_of_line();
bool is_end_of_line(bool eol_only);
bool go_back();
bool go_forward();
bool go_start_of_line();
bool go_end_of_line(bool allow_eol);
bool go_left_in_line();
bool go_right_in_line(bool allow_eol);
bool go_previous_line();
bool go_next_line();
bool go_forward_in_line_up_to_char(uint32_t c);
bool go_forward_in_line_on_to_char(uint32_t c);
bool go_backward_in_line_up_to_char(uint32_t c);
bool go_backward_in_line_on_to_char(uint32_t c);
Iterator prev();
Iterator next();
void warp(ssize_t offset_offset, ssize_t line_offset)
{
m_offset += offset_offset;
m_line += line_offset;
}
void warp_to_offset(size_t offset);
uint8_t * address() const
{
return m_buffer->address(m_offset);
}
uint32_t operator*() const
{
if (valid())
{
return Encoding::decode(m_buffer->encoding(), address());
}
else
{
return INVALID_CODE_POINT;
}
}
bool operator==(const Iterator & other) const
{
return m_offset == other.m_offset;
}
bool operator!=(const Iterator & other) const
{
return m_offset != other.m_offset;
}
bool operator<(const Iterator & other) const
{
return m_offset < other.m_offset;
}
bool operator<=(const Iterator & other) const
{
return m_offset <= other.m_offset;
}
bool operator>(const Iterator & other) const
{
return m_offset > other.m_offset;
}
bool operator>=(const Iterator & other) const
{
return m_offset >= other.m_offset;
}
size_t line() const { return m_line; }
size_t offset() const { return m_offset; }
std::shared_ptr<Iterator> clonep()
{
return std::make_shared<Iterator>(*this);
}
protected:
friend struct Range;
const Buffer * m_buffer;
size_t m_offset;
size_t m_line;
};
struct Range
{
std::shared_ptr<Iterator> start;
std::shared_ptr<Iterator> end;
Range(std::shared_ptr<Iterator> _start, std::shared_ptr<Iterator> _end)
: start(_start), end(_end)
{
}
bool valid() const
{
return start->valid() && (!end->valid() || (*start <= *end));
}
size_t start_offset() const
{
return start->offset();
}
size_t end_offset() const
{
if (end->valid())
{
return end->offset();
}
else
{
return start->m_buffer->size();
}
}
size_t size() const
{
return end_offset() - start_offset();
}
};
Buffer();
Buffer(const char * filename);
Buffer(const uint8_t * data, size_t data_length);
bool write_to_file(const char * filename, size_t * n_lines = nullptr, size_t * n_bytes = nullptr);
std::shared_ptr<Iterator> add_iterator()
{
std::shared_ptr<Iterator> iterator = std::make_shared<Iterator>(this);
m_iterators.push_back(iterator);
return iterator;
}
std::shared_ptr<Iterator> add_cursor()
{
auto iterator = add_iterator();
m_cursors.push_back(iterator);
return iterator;
}
void clear();
EncodedString get_string();
size_t size() const { return m_gap_buffer->size(); }
uint8_t * address(size_t offset) const { return m_gap_buffer->address(offset); }
Encoding::Type encoding() const { return m_encoding; }
uint8_t tabstop() const { return m_tabstop; }
void enter_insert_mode();
void exit_insert_mode();
bool insert_mode() const { return m_insert_mode; }
void insert_code_point(const Buffer::Iterator & position, uint32_t code_point);
void erase_code_point(const Buffer::Iterator & position);
void erase_range(const Buffer::Range & range);
std::shared_ptr<std::string> filename() const { return m_filename; }
void set_filename(const std::string & filename);
Iterator begin() const { return Iterator(this); }
std::shared_ptr<Iterator> beginp() const { return std::make_shared<Iterator>(this); }
Iterator end() const { return *m_eof_iterator; }
std::shared_ptr<Iterator> endp() const { return std::make_shared<Iterator>(*m_eof_iterator); }
void push_operation() { m_operation_level++; }
void pop_operation();
void undo();
void redo();
size_t n_lines() const;
#ifndef ENABLE_TESTING
protected:
#endif
bool m_eol_at_eof;
LineEndings::Type m_line_endings;
std::shared_ptr<GapBuffer> m_gap_buffer;
std::shared_ptr<GapBuffer> m_change_buffer;
Encoding::Type m_encoding;
uint8_t m_tabstop;
bool m_insert_mode;
std::list<std::shared_ptr<Iterator>> m_iterators;
std::list<std::shared_ptr<Iterator>> m_cursors;
std::shared_ptr<Iterator> m_eof_iterator;
std::shared_ptr<std::string> m_filename;
std::shared_ptr<ChangeOperation> m_current_change_operation;
int m_operation_level;
size_t m_current_change_operation_index;
std::vector<std::shared_ptr<ChangeOperation>> m_change_operations;
void common_pre_load_initialization();
void common_post_load_initialization();
bool load_from_file(const char * filename);
void load_empty_buffer();
bool load_from_memory(const uint8_t * data, size_t data_length);
void load_text_in_buffer(uint8_t * buffer, size_t buffer_size, size_t data_length);
void record_change(size_t offset, size_t length, bool insert);
void save_current_operation();
void warp_iterators_after_insert(size_t offset, size_t length);
void warp_iterators_before_delete(size_t offset, size_t length);
void insert_data(size_t offset, const uint8_t data[], size_t length, bool do_record_change);
void erase_data(size_t offset, size_t length, bool do_record_change);
size_t lines_in_data(size_t offset, size_t length);
void post_warp_cursors();
void apply_change_operation(const ChangeOperation & change_operation, bool forward);
void apply_change_unit(const ChangeUnit & change_unit, bool forward);
};
#endif
+101
View File
@@ -0,0 +1,101 @@
#include "BufferLineWalker.h"
/**
* Construct a BufferLineWalker.
*
* @param buffer The Buffer we're iterating.
* @param start_of_line Buffer iterator pointing to the beginning of the buffer line.
* @param view_width View width, beyond which characters will wrap to the next line.
* @param character_width_determiner Functor to return the width (in columns) of a character.
*/
BufferLineWalker::BufferLineWalker(
std::shared_ptr<Buffer> buffer,
std::shared_ptr<Buffer::Iterator> start_of_line,
int view_width,
CharacterWidthDeterminer & character_width_determiner)
: m_buffer(buffer),
m_character_width_determiner(character_width_determiner)
{
m_view_width = view_width;
m_row_offset = 0;
m_screen_column = 0;
m_virtual_column = 0;
m_iterator = std::make_shared<Buffer::Iterator>(*start_of_line);
process_new_character();
}
void BufferLineWalker::process_new_character()
{
if ((!m_iterator) || (!m_iterator->valid()))
{
m_code_point = '\n';
m_character_width = 0;
return;
}
m_code_point = **m_iterator;
if (m_code_point == '\n')
{
m_character_width = 0;
return;
}
if (m_code_point == '\t')
{
uint8_t tabstop = m_buffer->tabstop();
m_character_width = tabstop - m_virtual_column % tabstop;
return;
}
m_character_width = m_character_width_determiner(m_code_point);
if (((m_screen_column + m_character_width) > m_view_width) &&
(m_screen_column > 0))
{
m_row_offset++;
m_screen_column = 0;
}
}
void BufferLineWalker::operator++(int unused)
{
if (!m_iterator)
return;
if ((m_code_point == '\n') || (!m_iterator->valid()))
{
m_code_point = '\n';
m_iterator.reset();
return;
}
/* Adjust virtual column. */
m_virtual_column += m_character_width;
/* Adjust screen position. */
if (m_code_point == '\t')
{
m_screen_column += m_character_width;
while (m_screen_column >= m_view_width)
{
m_screen_column -= m_view_width;
m_row_offset++;
}
}
else
{
if ((m_screen_column + m_character_width) >= m_view_width)
{
m_row_offset++;
m_screen_column = 0;
}
else
{
m_screen_column += m_character_width;
}
}
m_iterator->go_forward();
process_new_character();
}
+55
View File
@@ -0,0 +1,55 @@
#ifndef BUFFERLINEWALKER_H
#define BUFFERLINEWALKER_H
#include "Buffer.h"
#include "CharacterWidthDeterminer.h"
#include <functional>
/**
* Provides functionality to walk a line of a buffer taking into account the
* view width and multi-column characters.
*/
class BufferLineWalker
{
public:
BufferLineWalker(
std::shared_ptr<Buffer> buffer,
std::shared_ptr<Buffer::Iterator> start_of_line,
int view_width,
CharacterWidthDeterminer & character_width_determiner);
void operator++() { return (*this)++; }
void operator++(int unused);
bool is_eol() const
{
if (is_valid())
{
return (m_code_point == '\n');
}
return false;
}
bool is_valid() const
{
return (bool)m_iterator;
}
int row_offset() const { return m_row_offset; }
int screen_column() const { return m_screen_column; }
int virtual_column() const { return m_virtual_column; }
int character_width() const { return m_character_width; }
uint32_t code_point() const { return m_code_point; }
std::shared_ptr<Buffer::Iterator> iterator() const { return m_iterator; }
protected:
std::shared_ptr<Buffer> m_buffer;
std::shared_ptr<Buffer::Iterator> m_iterator;
CharacterWidthDeterminer & m_character_width_determiner;
int m_view_width;
int m_row_offset;
int m_screen_column;
int m_virtual_column;
int m_character_width;
uint32_t m_code_point;
void process_new_character();
};
#endif
+633
View File
@@ -0,0 +1,633 @@
#include "BufferView.h"
#include <list>
#include <utility>
#include <limits.h>
BufferView::BufferView(std::shared_ptr<Buffer> buffer,
std::shared_ptr<Buffer::Iterator> iterator,
CharacterWidthDeterminer & character_width_determiner)
: m_buffer(buffer),
m_iterator(iterator),
m_character_width_determiner(character_width_determiner)
{
m_width = 1;
m_height = 1;
m_scroll_offset = 0;
m_cursor_screen_row = 0;
m_cursor_screen_column = 0;
m_cursor_virtual_column = 0;
m_cursor_row_offset = 0;
m_rows_in_cursor_line = 0;
m_target_screen_column = 0;
m_target_virtual_column = 0;
m_update_target_column = false;
}
void BufferView::resize(int width, int height)
{
m_width = std::max(1, width);
m_height = std::max(1, height);
}
void BufferView::set_scroll_offset(int scroll_offset)
{
m_scroll_offset = std::max(0, scroll_offset);
}
BufferView::Iterator BufferView::vert_iter()
{
return Iterator(*this);
}
void BufferView::update()
{
/* Calculate number of rows in the cursor line and update cursor row offset
* and cursor column values. */
auto start_of_line = std::make_shared<Buffer::Iterator>(*m_iterator);
start_of_line->go_start_of_line();
m_rows_in_cursor_line = calculate_rows_in_cursor_line(start_of_line);
/* Limit the cursor screen row taking into account view dimensions,
* available buffer contents above and below the current line, and scroll
* offset. */
std::list<std::pair<int, std::shared_ptr<Buffer::Iterator>>> backward_lines;
int so = effective_scroll_offset();
int rows_above = screen_rows_above_line(start_of_line, backward_lines) + m_cursor_row_offset;
int rows_below = screen_rows_below_line(start_of_line) + std::max(0, m_rows_in_cursor_line - m_cursor_row_offset - 1);
int min_rows_to_leave_above = std::min(rows_above, so);
int min_rows_to_leave_below = std::min(rows_below, so);
m_cursor_screen_row = std::min(m_height - min_rows_to_leave_below - 1, m_cursor_screen_row);
m_cursor_screen_row = std::max(min_rows_to_leave_above, m_cursor_screen_row);
m_cursor_screen_row = std::max(m_height - rows_below - 1, m_cursor_screen_row);
m_cursor_screen_row = std::min(rows_above, m_cursor_screen_row);
/* Determine the first line that is visible in this view. */
auto line_iterator = std::make_shared<Buffer::Iterator>(*m_iterator);
int row_offset = m_cursor_screen_row - m_cursor_row_offset;
if (row_offset <= 0)
{
line_iterator->go_start_of_line();
}
else for (auto rows_iterator_pair : backward_lines)
{
row_offset -= rows_iterator_pair.first;
line_iterator = rows_iterator_pair.second;
if (row_offset <= 0)
{
break;
}
}
/* Now start with first visible line and build up all lines visible in the view. */
m_lines.clear();
while ((row_offset < m_height) && (line_iterator->valid()))
{
LineDescriptor ld;
ld.row_offset = row_offset;
ld.n_rows = calculate_rows_in_line(line_iterator);
ld.line = std::make_shared<Buffer::Iterator>(*line_iterator);
m_lines.push_back(ld);
row_offset += ld.n_rows;
if (!line_iterator->go_next_line())
break;
}
/* Reset some fields if buffer becomes empty. */
if (!m_iterator->valid())
{
m_cursor_screen_row = 0;
m_cursor_screen_column = 0;
m_cursor_virtual_column = 0;
}
}
bool BufferView::cursor_move(CursorMovement which, uint32_t c, bool allow_eol)
{
bool moved = false;
switch (which)
{
case CursorMovement::LEFT:
moved = m_iterator->go_left_in_line();
m_update_target_column = true;
break;
case CursorMovement::RIGHT:
moved = m_iterator->go_right_in_line(allow_eol);
m_update_target_column = true;
break;
case CursorMovement::UP:
moved = m_iterator->go_previous_line();
break;
case CursorMovement::DOWN:
moved = m_iterator->go_next_line();
break;
case CursorMovement::SOL:
moved = m_iterator->go_start_of_line();
m_target_screen_column = 0;
m_target_virtual_column = 0;
break;
case CursorMovement::EOL:
moved = m_iterator->go_end_of_line(allow_eol);
m_target_screen_column = INT_MAX;
m_target_virtual_column = INT_MAX;
break;
case CursorMovement::START_OF_FILE:
case CursorMovement::FIRST_LINE:
{
auto it = m_buffer->begin();
if (it != *m_iterator)
{
*m_iterator = it;
moved = true;
}
}
m_target_screen_column = 0;
m_target_virtual_column = 0;
break;
case CursorMovement::END_OF_FILE:
{
auto it = m_buffer->end();
it.go_back();
/* TODO: optimize this */
it.go_start_of_line();
it.go_end_of_line(allow_eol);
if (it != *m_iterator)
{
*m_iterator = it;
moved = true;
}
}
m_update_target_column = true;
break;
case CursorMovement::LAST_LINE:
{
auto it = m_buffer->end();
it.go_back();
it.go_start_of_line();
if (it != *m_iterator)
{
*m_iterator = it;
moved = true;
}
}
m_target_screen_column = 0;
m_target_virtual_column = 0;
break;
case CursorMovement::SCREEN_ROW_UP:
moved = move_cursor_screen_row_up(allow_eol);
break;
case CursorMovement::SCREEN_ROW_DOWN:
moved = move_cursor_screen_row_down(allow_eol);
break;
case CursorMovement::FORWARD_UP_TO_CHAR:
moved = m_iterator->go_forward_in_line_up_to_char(c);
break;
case CursorMovement::FORWARD_ON_TO_CHAR:
moved = m_iterator->go_forward_in_line_on_to_char(c);
break;
case CursorMovement::BACK_UP_TO_CHAR:
moved = m_iterator->go_backward_in_line_up_to_char(c);
break;
case CursorMovement::BACK_ON_TO_CHAR:
moved = m_iterator->go_backward_in_line_on_to_char(c);
break;
case CursorMovement::TOP_OF_SCREEN:
{
int n_lines = n_screen_rows_with_content();
int y = (n_lines < m_height) ? 0 : effective_scroll_offset();
moved = cursor_move_to_screen_position(0, y, allow_eol);
}
break;
case CursorMovement::MIDDLE_OF_SCREEN:
{
int n_lines = n_screen_rows_with_content();
int y = (n_lines < m_height) ? (n_lines / 2) : (m_height / 2);
moved = cursor_move_to_screen_position(0, y, allow_eol);
}
break;
case CursorMovement::BOTTOM_OF_SCREEN:
{
int n_lines = n_screen_rows_with_content();
int y = (n_lines < m_height) ? (n_lines - 1) : (m_height - effective_scroll_offset() - 1);
moved = cursor_move_to_screen_position(0, y, allow_eol);
}
break;
}
if (moved)
{
switch (which)
{
case CursorMovement::LEFT:
case CursorMovement::RIGHT:
case CursorMovement::SCREEN_ROW_UP:
case CursorMovement::SCREEN_ROW_DOWN:
determine_new_cursor_screen_row();
break;
case CursorMovement::UP:
case CursorMovement::DOWN:
move_forward_to_column(m_target_virtual_column, allow_eol);
determine_new_cursor_screen_row();
break;
case CursorMovement::SOL:
case CursorMovement::EOL:
case CursorMovement::FORWARD_UP_TO_CHAR:
case CursorMovement::FORWARD_ON_TO_CHAR:
case CursorMovement::BACK_UP_TO_CHAR:
case CursorMovement::BACK_ON_TO_CHAR:
determine_new_cursor_screen_row();
break;
case CursorMovement::START_OF_FILE:
case CursorMovement::FIRST_LINE:
m_cursor_screen_row = 0;
break;
case CursorMovement::END_OF_FILE:
case CursorMovement::LAST_LINE:
m_cursor_screen_row = m_height - 1;
break;
case CursorMovement::TOP_OF_SCREEN:
case CursorMovement::MIDDLE_OF_SCREEN:
case CursorMovement::BOTTOM_OF_SCREEN:
m_target_virtual_column = 0;
m_target_screen_column = 0;
break;
}
}
return moved;
}
bool BufferView::cursor_move_to_line(size_t target_line)
{
size_t current_line = m_iterator->line();
std::shared_ptr<Buffer::Iterator> it;
if (target_line <= current_line / 2u)
{
it = m_buffer->beginp();
}
else if (target_line <= ((current_line + m_buffer->end().line()) / 2u))
{
it = m_iterator->clonep();
}
else
{
it = m_buffer->endp();
it->go_back();
it->go_start_of_line();
}
while (it->line() < target_line)
{
if (!it->go_next_line())
break;
}
while (it->line() > target_line)
{
if (!it->go_previous_line())
break;
}
if (current_line != it->line())
{
*m_iterator = *it;
determine_new_cursor_screen_row();
return true;
}
return false;
}
bool BufferView::cursor_move_to_screen_position(int x, int y, bool allow_eol)
{
bool moved = false;
for (const LineDescriptor & screen_line : m_lines)
{
if ((y >= screen_line.row_offset) &&
(y < (screen_line.row_offset + screen_line.n_rows)))
{
Buffer::Iterator scan_iter = *m_iterator;
bool set = false;
int desired_row_offset = y - screen_line.row_offset;
for (auto it = horiz_iter(screen_line.line); it.is_valid(); it++)
{
if (it.row_offset() == desired_row_offset)
{
if ((!set) ||
((it.screen_column() <= x) && (allow_eol || (!it.is_eol()))))
{
scan_iter = *it.iterator();
set = true;
}
}
}
if (scan_iter != *m_iterator)
{
*m_iterator = scan_iter;
moved = true;
determine_new_cursor_screen_row();
}
break;
}
}
return moved;
}
void BufferView::scroll_view_up(int n_lines, bool allow_eol)
{
int orig_cursor_screen_row = m_cursor_screen_row;
int so = effective_scroll_offset();
int rows_to_move_cursor = (so + n_lines) - (m_height - m_cursor_screen_row - 1);
rows_to_move_cursor = std::min(calculate_rows_above_screen(rows_to_move_cursor), rows_to_move_cursor);
int actual_lines_moved = 0;
while (rows_to_move_cursor-- > 0)
{
if (move_cursor_screen_row_up(allow_eol))
{
actual_lines_moved++;
update();
}
else
{
break;
}
}
m_cursor_screen_row = orig_cursor_screen_row + (n_lines - actual_lines_moved);
}
void BufferView::scroll_view_down(int n_lines, bool allow_eol)
{
int orig_cursor_screen_row = m_cursor_screen_row;
int so = effective_scroll_offset();
int rows_to_move_cursor = (so + n_lines) - m_cursor_screen_row;
rows_to_move_cursor = std::min(calculate_rows_below_screen(rows_to_move_cursor), rows_to_move_cursor);
int actual_lines_moved = 0;
while (rows_to_move_cursor-- > 0)
{
if (move_cursor_screen_row_down(allow_eol))
{
actual_lines_moved++;
update();
}
else
{
break;
}
}
m_cursor_screen_row = orig_cursor_screen_row - (n_lines - actual_lines_moved);
}
/**************************************************************************
* Internal functions
*************************************************************************/
void BufferView::determine_new_cursor_screen_row()
{
if (m_lines.size() > 0)
{
if (m_iterator->line() < m_lines.begin()->line->line())
{
m_cursor_screen_row = 0;
return;
}
if (m_iterator->line() > m_lines.rbegin()->line->line())
{
m_cursor_screen_row = m_height - 1;
return;
}
for (const LineDescriptor & screen_line : m_lines)
{
if (screen_line.line->line() == m_iterator->line())
{
m_cursor_screen_row = screen_line.row_offset + m_cursor_row_offset;
return;
}
}
}
}
int BufferView::calculate_rows_in_line(std::shared_ptr<Buffer::Iterator> start_of_line)
{
int saved_row_offset = 0;
for (auto it = horiz_iter(start_of_line); it.is_valid(); it++)
{
if (!it.is_eol())
{
saved_row_offset = it.row_offset();
}
else
{
break;
}
}
return saved_row_offset + 1;
}
int BufferView::calculate_rows_in_cursor_line(std::shared_ptr<Buffer::Iterator> start_of_line)
{
int saved_row_offset = 0;
for (auto it = horiz_iter(start_of_line); it.is_valid(); it++)
{
if (*it.iterator() == *m_iterator)
{
m_cursor_virtual_column = it.virtual_column();
m_cursor_screen_column = it.screen_column();
if (m_update_target_column)
{
m_target_screen_column = m_cursor_screen_column;
m_target_virtual_column = m_cursor_virtual_column;
}
m_cursor_row_offset = it.row_offset();
}
if (!it.is_eol())
{
saved_row_offset = it.row_offset();
}
else
{
break;
}
}
m_update_target_column = false;
return saved_row_offset + 1;
}
int BufferView::screen_rows_below_line(std::shared_ptr<Buffer::Iterator> line)
{
auto i = std::make_shared<Buffer::Iterator>(*line);
int rows = 0;
while ((rows < m_height) && i->go_next_line())
{
rows += calculate_rows_in_line(i);
}
return rows;
}
int BufferView::screen_rows_above_line(
std::shared_ptr<Buffer::Iterator> line,
std::list<std::pair<int, std::shared_ptr<Buffer::Iterator>>> & backward_lines)
{
auto i = std::make_shared<Buffer::Iterator>(*line);
int rows = 0;
while ((rows < m_height) && i->go_previous_line())
{
int rows_in_this_line = calculate_rows_in_line(i);
rows += rows_in_this_line;
backward_lines.push_back(std::pair<int, std::shared_ptr<Buffer::Iterator>>(rows_in_this_line, std::make_shared<Buffer::Iterator>(*i)));
}
return rows;
}
void BufferView::move_forward_to_column(int to_column, bool allow_eol)
{
auto start_of_line = std::make_shared<Buffer::Iterator>(*m_iterator);
start_of_line->go_start_of_line();
for (auto it = horiz_iter(start_of_line); it.is_valid(); it++)
{
if (allow_eol || (!it.is_eol()))
{
if (it.virtual_column() <= to_column)
{
*m_iterator = *it.iterator();
}
else
{
break;
}
}
}
}
bool BufferView::move_cursor_screen_row_up(bool allow_eol)
{
auto cursor_line_walker = get_cursor_line_walker();
auto iterator_copy = m_iterator->clonep();
if (cursor_line_walker.row_offset() > 0)
{
/* There are screen rows above the current in the current line. */
iterator_copy->go_start_of_line();
move_forward_to_screen_position(iterator_copy, cursor_line_walker.row_offset() - 1, allow_eol);
}
else
{
if (!iterator_copy->go_previous_line())
{
return false;
}
int target_row_offset = calculate_rows_in_line(iterator_copy) - 1;
move_forward_to_screen_position(iterator_copy, target_row_offset, allow_eol);
}
return true;
}
bool BufferView::move_cursor_screen_row_down(bool allow_eol)
{
auto cursor_line_walker = get_cursor_line_walker();
int target_row_offset = cursor_line_walker.row_offset() + 1;
bool moved = false;
for (; cursor_line_walker.is_valid(); cursor_line_walker++)
{
if (cursor_line_walker.row_offset() == target_row_offset)
{
if (((!cursor_line_walker.is_eol()) || allow_eol) &&
((!moved) || (cursor_line_walker.screen_column() <= m_target_screen_column)))
{
*m_iterator = *cursor_line_walker.iterator();
m_cursor_row_offset = cursor_line_walker.row_offset();
moved = true;
}
}
else if (cursor_line_walker.row_offset() > target_row_offset)
{
break;
}
}
if (!moved)
{
auto next_line = m_iterator->clonep();
if (!next_line->go_next_line())
{
return false;
}
move_forward_to_screen_position(next_line, 0, allow_eol);
}
return true;
}
void BufferView::move_forward_to_screen_position(
std::shared_ptr<Buffer::Iterator> line, int target_row_offset,
bool allow_eol)
{
*m_iterator = *line;
bool moved_to_target_row = false;
for (auto it = horiz_iter(line); it.is_valid(); it++)
{
if (((!it.is_eol()) || allow_eol) &&
((it.row_offset() < target_row_offset) ||
(!moved_to_target_row) ||
(it.screen_column() <= m_target_screen_column)))
{
*m_iterator = *it.iterator();
m_cursor_row_offset = it.row_offset();
if (it.row_offset() == target_row_offset)
{
moved_to_target_row = true;
}
}
else
{
break;
}
}
}
int BufferView::calculate_rows_above_screen(int stop_at)
{
if (m_lines.empty())
return 0;
const LineDescriptor & first_line = *m_lines.begin();
auto iterator = std::make_shared<Buffer::Iterator>(*first_line.line);
int lines = std::max(0, -first_line.row_offset);
while (lines < stop_at)
{
if (!iterator->go_previous_line())
break;
lines += calculate_rows_in_line(iterator);
}
return lines;
}
int BufferView::calculate_rows_below_screen(int stop_at)
{
if (m_lines.empty())
return 0;
const LineDescriptor & last_line = *m_lines.rbegin();
auto iterator = std::make_shared<Buffer::Iterator>(*last_line.line);
int lines = std::max(0, last_line.row_offset + last_line.n_rows - m_height);
while (lines < stop_at)
{
if (!iterator->go_next_line())
break;
lines += calculate_rows_in_line(iterator);
}
return lines;
}
int BufferView::n_screen_rows_with_content() const
{
if (m_lines.size() > 0u)
{
auto line_desc = m_lines.rbegin();
return line_desc->row_offset + line_desc->n_rows;
}
return 0;
}
BufferLineWalker BufferView::get_cursor_line_walker()
{
auto start_of_line = m_iterator->clonep();
start_of_line->go_start_of_line();
for (auto it = horiz_iter(start_of_line); it.is_valid(); it++)
{
if (*it.iterator() == *m_iterator)
{
return it;
}
}
return horiz_iter(start_of_line);
}
+149
View File
@@ -0,0 +1,149 @@
#ifndef BUFFERVIEW_H
#define BUFFERVIEW_H
#include "Buffer.h"
#include "CharacterWidthDeterminer.h"
#include <functional>
#include "BufferLineWalker.h"
#include <vector>
/**
* Tracks a "view" of a buffer, which is a two-dimensional grid of characters
* that displays a section of a buffer's contents.
*/
class BufferView
{
public:
enum class CursorMovement : uint8_t
{
LEFT,
RIGHT,
UP,
DOWN,
SOL,
EOL,
START_OF_FILE,
END_OF_FILE,
FIRST_LINE,
LAST_LINE,
SCREEN_ROW_UP,
SCREEN_ROW_DOWN,
FORWARD_UP_TO_CHAR,
FORWARD_ON_TO_CHAR,
BACK_UP_TO_CHAR,
BACK_ON_TO_CHAR,
TOP_OF_SCREEN,
MIDDLE_OF_SCREEN,
BOTTOM_OF_SCREEN,
};
class Iterator
{
public:
Iterator(BufferView & bv) : m_buffer_view(bv)
{
m_index = 0u;
}
void operator++()
{
(*this)++;
}
void operator++(int unused)
{
m_index++;
}
bool is_valid()
{
return m_index < m_buffer_view.m_lines.size();
}
int row_offset() const
{
return m_buffer_view.m_lines[m_index].row_offset;
}
int n_rows() const
{
return m_buffer_view.m_lines[m_index].n_rows;
}
std::shared_ptr<Buffer::Iterator> iterator() const
{
return m_buffer_view.m_lines[m_index].line;
}
protected:
BufferView & m_buffer_view;
size_t m_index;
};
BufferView(std::shared_ptr<Buffer> buffer,
std::shared_ptr<Buffer::Iterator> iterator,
CharacterWidthDeterminer & character_width_determiner);
void resize(int width, int height);
void set_scroll_offset(int scroll_offset);
void update();
Iterator vert_iter();
BufferLineWalker horiz_iter(std::shared_ptr<Buffer::Iterator> start_of_line)
{
return BufferLineWalker(m_buffer, start_of_line, m_width, m_character_width_determiner);
}
bool cursor_move(CursorMovement which, uint32_t c, bool allow_eol);
bool cursor_move_to_line(size_t line);
bool cursor_move_to_screen_position(int x, int y, bool allow_eol);
int cursor_screen_row() const { return m_cursor_screen_row; }
int cursor_screen_column() const { return m_cursor_screen_column; }
int cursor_virtual_column() const { return m_cursor_virtual_column; }
void set_cursor_screen_row(int cursor_screen_row)
{
m_cursor_screen_row = cursor_screen_row;
}
void scroll_view_up(int n_lines, bool allow_eol);
void scroll_view_down(int n_lines, bool allow_eol);
protected:
struct LineDescriptor
{
int row_offset;
int n_rows;
std::shared_ptr<Buffer::Iterator> line;
};
std::shared_ptr<Buffer> m_buffer;
std::shared_ptr<Buffer::Iterator> m_iterator;
std::vector<LineDescriptor> m_lines;
CharacterWidthDeterminer & m_character_width_determiner;
int m_width;
int m_height;
int m_scroll_offset;
int m_cursor_screen_row;
int m_cursor_screen_column;
int m_cursor_virtual_column;
/** Holds the screen row offset of the cursor in the current line */
int m_cursor_row_offset;
int m_rows_in_cursor_line;
int m_target_screen_column;
int m_target_virtual_column;
bool m_update_target_column;
int effective_scroll_offset()
{
return std::min(m_scroll_offset, (m_height - 1) / 2);
}
void determine_new_cursor_screen_row();
int calculate_rows_in_line(std::shared_ptr<Buffer::Iterator> start_of_line);
int calculate_rows_in_cursor_line(std::shared_ptr<Buffer::Iterator> start_of_line);
int screen_rows_below_line(std::shared_ptr<Buffer::Iterator> line);
int screen_rows_above_line(
std::shared_ptr<Buffer::Iterator> line,
std::list<std::pair<int, std::shared_ptr<Buffer::Iterator>>> & backward_lines);
void move_forward_to_column(int to_column, bool allow_eol);
bool move_cursor_screen_row_up(bool allow_eol);
bool move_cursor_screen_row_down(bool allow_eol);
void move_forward_to_screen_position(
std::shared_ptr<Buffer::Iterator> line, int target_row_offset,
bool allow_eol);
int calculate_rows_above_screen(int stop_at);
int calculate_rows_below_screen(int stop_at);
int n_screen_rows_with_content() const;
BufferLineWalker get_cursor_line_walker();
};
#endif
+16
View File
@@ -0,0 +1,16 @@
#ifndef CHANGEOPERATION_H
#define CHANGEOPERATION_H
#include "ChangeUnit.h"
#include <list>
#include <stdlib.h>
class ChangeOperation
{
public:
size_t parent;
std::list<ChangeUnit> changes;
std::list<size_t> children;
};
#endif
+15
View File
@@ -0,0 +1,15 @@
#ifndef CHANGEUNIT_H
#define CHANGEUNIT_H
#include <stdlib.h>
class ChangeUnit
{
public:
size_t change_buffer_offset;
size_t length;
size_t buffer_position;
bool insert;
};
#endif
+12
View File
@@ -0,0 +1,12 @@
#ifndef CHARACTERWIDTHDETERMINER_H
#define CHARACTERWIDTHDETERMINER_H
#include <stdint.h>
class CharacterWidthDeterminer
{
public:
virtual int operator()(uint32_t character) = 0;
};
#endif
+14
View File
@@ -0,0 +1,14 @@
#include "Command.h"
#include <string.h>
bool Command::Unit::operator==(const Command::Unit & other) const
{
return (id == other.id) &&
(count == other.count) &&
(following_char == other.following_char);
}
bool Command::operator==(const Command & other) const
{
return (main == other.main) && (motion == other.motion);
}
+104
View File
@@ -0,0 +1,104 @@
#ifndef COMMAND_H
#define COMMAND_H
#include <stdint.h>
class Command
{
public:
enum : uint32_t
{
NOP,
GO_FORWARD_UP_TO_CHAR,
GO_FORWARD_ON_TO_CHAR,
GO_BACK_UP_TO_CHAR,
GO_BACK_ON_TO_CHAR,
DELETE_MOTION,
DELETE_LINE,
DELETE_CHAR,
DELETE_CHAR_BACK,
CHANGE_MOTION,
CHANGE_LINE,
YANK_MOTION,
YANK_LINE,
UNDO,
REDO,
ENTER_INSERT_MODE,
ENTER_INSERT_MODE_AFTER_CHAR,
ENTER_INSERT_MODE_AFTER_LINE,
ENTER_INSERT_MODE_START_OF_LINE,
ENTER_INSERT_MODE_NEW_LINE_BEFORE,
ENTER_INSERT_MODE_NEW_LINE_AFTER,
PUT,
PUT_BEFORE,
CHANGE_CHAR,
GO_START_OF_FILE,
GO_END_OF_FILE,
GO_TO_LINE,
GO_TO_LAST_LINE,
GO_LEFT,
GO_DOWN,
GO_UP,
GO_RIGHT,
GO_START_OF_LINE,
GO_END_OF_LINE,
GO_TOP_OF_SCREEN,
GO_MIDDLE_OF_SCREEN,
GO_BOTTOM_OF_SCREEN,
NEXT,
PREV,
SCROLL_WINDOW_UP_ONE_LINE,
SCROLL_WINDOW_DOWN_ONE_LINE,
SCROLL_WINDOW_UP_HALF_SCREEN,
SCROLL_WINDOW_DOWN_HALF_SCREEN,
SCROLL_WINDOW_UP_WHOLE_SCREEN,
SCROLL_WINDOW_DOWN_WHOLE_SCREEN,
ENTER_COMMAND_LINE_PROMPT,
COMMAND_COUNT,
};
class Motion
{
public:
enum : uint32_t
{
NOP,
FORWARD_UP_TO_CHAR,
FORWARD_ON_TO_CHAR,
BACK_UP_TO_CHAR,
BACK_ON_TO_CHAR,
START_OF_LINE,
END_OF_LINE,
/* These motions aren't command-mode sequences but are used
* internally. */
THIS_LINE,
MOTION_COUNT,
};
};
struct Unit
{
uint32_t id;
uint32_t count;
uint32_t following_char;
Unit()
{
id = 0u;
count = 0u;
following_char = 0u;
}
bool operator==(const Unit & other) const;
};
Unit main;
Unit motion;
bool operator==(const Command & other) const;
};
#endif
+142
View File
@@ -0,0 +1,142 @@
#include "CommandMap.h"
#include "Command.h"
#include <cstring>
void CommandMap::add(const char * s, uint32_t id,
std::shared_ptr<CommandMap> next_map, bool following_char)
{
size_t length = strlen(s);
if (length < 1u)
return;
std::vector<uint32_t> v(length);
for (size_t i = 0u; i < length; i++)
{
v[i] = s[i];
}
add(v, id, next_map, following_char);
}
void CommandMap::add(const std::vector<uint32_t> & s, uint32_t id,
std::shared_ptr<CommandMap> next_map, bool following_char)
{
Node * node = &m_root_node;
size_t length = s.size();
if (length < 1u)
return;
for (size_t i = 0u; i < length; i++)
{
const uint32_t c = s[i];
auto it = node->next_chars.find(c);
if (it != node->next_chars.end())
{
node = &*it->second;
}
else
{
auto new_node = std::make_shared<Node>();
node->next_chars[c] = new_node;
node = &*new_node;
}
}
uint8_t flags = Node::FLAG_TERMINATOR;
if (following_char)
flags |= Node::FLAG_FOLLOWING_CHAR;
node->id = id;
node->flags = flags;
node->next_map = next_map;
}
uint8_t CommandMap::lookup_command(const uint32_t * command_characters,
size_t length, Command & command) const
{
std::list<Command::Unit> units;
uint8_t scan_result = scan_units(units, command_characters, length, &m_root_node);
if (scan_result == COMMAND_COMPLETE)
{
int i = 0;
for (const auto & unit : units)
{
switch (i)
{
case 0:
command.main = unit;
break;
case 1:
command.motion = unit;
break;
}
i++;
}
}
return scan_result;
}
uint8_t CommandMap::scan_units(
std::list<Command::Unit> & units,
const uint32_t * command_characters, size_t length,
const Node * start_node) const
{
if (length < 1u)
return COMMAND_INCOMPLETE;
uint8_t result = COMMAND_INCOMPLETE;
uint8_t recurse_result = COMMAND_INVALID;
bool gathering_count = true;
Command::Unit unit;
const Node * node = start_node;
for (size_t i = 0u; i < length; i++)
{
uint32_t c = command_characters[i];
if (gathering_count &&
((('1' <= c) && (c <= '9')) ||
((c == '0') && (unit.count != 0u))))
{
unit.count *= 10u;
unit.count += (c - '0');
}
else
{
gathering_count = false;
auto it = node->next_chars.find(c);
if (it != node->next_chars.end())
{
node = &*it->second;
if (node->flags & Node::FLAG_TERMINATOR)
{
unit.id = node->id;
if (node->flags & Node::FLAG_FOLLOWING_CHAR)
{
if (i < (length - 1u))
{
unit.following_char = command_characters[i + 1u];
units.push_front(unit);
return COMMAND_COMPLETE;
}
return COMMAND_INCOMPLETE;
}
else if (node->next_map)
{
recurse_result = scan_units(units,
&command_characters[i + 1u],
length - i - 1u, &node->next_map->m_root_node);
if (recurse_result == COMMAND_COMPLETE)
{
units.push_front(unit);
return COMMAND_COMPLETE;
}
result = recurse_result;
}
else
{
units.push_front(unit);
return COMMAND_COMPLETE;
}
}
}
else
{
return recurse_result;
}
}
}
return result;
}
+56
View File
@@ -0,0 +1,56 @@
#ifndef COMMANDMAP_H
#define COMMANDMAP_H
#include <unordered_map>
#include <memory>
#include <list>
#include <vector>
#include "Command.h"
class CommandMap
{
public:
enum : uint8_t
{
COMMAND_INVALID,
COMMAND_INCOMPLETE,
COMMAND_COMPLETE,
};
void add(const char * s, uint32_t id, std::shared_ptr<CommandMap> next_map,
bool following_char);
void add(const std::vector<uint32_t> & s, uint32_t id,
std::shared_ptr<CommandMap> next_map, bool following_char);
uint8_t lookup_command(const uint32_t * command_characters, size_t length,
Command & command) const;
protected:
struct Node
{
enum : uint8_t
{
FLAG_FOLLOWING_CHAR = 0x1u,
FLAG_TERMINATOR = 0x2u,
};
std::unordered_map<uint32_t, std::shared_ptr<Node>> next_chars;
std::shared_ptr<CommandMap> next_map;
uint32_t id;
uint8_t flags;
Node()
{
id = 0u;
flags = 0u;
}
};
Node m_root_node;
uint8_t scan_units(
std::list<Command::Unit> & units,
const uint32_t * command_characters, size_t length,
const Node * start_node) const;
};
#endif
+72
View File
@@ -0,0 +1,72 @@
#include "CommandParser.h"
bool CommandParser::parse(const EncodedString & command)
{
std::vector<uint8_t> current_arg;
EncodedString::iterator end = command.end();
EncodedString::iterator it = command.begin();
uint32_t quote = 0u;
bool arg_content = false;
auto append_char = [&current_arg, &it, &command, &arg_content]() {
size_t offset = it.offset();
for (size_t i = 0u, sz = it.size(); i < sz; i++)
{
current_arg.push_back(command[offset + i]);
}
arg_content = true;
};
auto collect_arg = [this, &current_arg, &arg_content] {
if (arg_content)
{
m_args.push_back(EncodedString(&current_arg[0], current_arg.size()));
current_arg.clear();
arg_content = false;
}
arg_content = false;
};
while (it != end)
{
uint32_t cp = *it;
if (cp == '\n')
{
/* Skip newline character */
}
else if ((quote != 0u) && (cp == quote))
{
quote = 0u;
}
else if ((quote == 0u) && ((cp == '\'') || (cp == '"')))
{
quote = cp;
arg_content = true;
}
else if (cp == '\\')
{
if (++it == end)
{
m_parse_error = "Unterminated escape sequence";
return false;
}
append_char();
}
else if ((cp == ' ') && (quote == 0u))
{
collect_arg();
}
else
{
append_char();
}
++it;
}
if (quote != 0u)
{
m_parse_error = std::string("Unterminated quoted region");
return false;
}
collect_arg();
return true;
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef COMMANDPARSER_H
#define COMMANDPARSER_H
#include <vector>
#include <string>
#include "EncodedString.h"
class CommandParser
{
public:
bool parse(const EncodedString & command);
const EncodedString & operator[](size_t index) const
{
return m_args[index];
}
size_t size() const
{
return m_args.size();
}
const std::string & parse_error() const
{
return m_parse_error;
}
const std::vector<EncodedString> args() const
{
return m_args;
}
protected:
std::vector<EncodedString> m_args;
std::string m_parse_error;
};
#endif
+101
View File
@@ -0,0 +1,101 @@
#include "DefaultCommandMap.h"
#include "keys.h"
std::shared_ptr<CommandMap> g_DefaultCommandMap;
std::shared_ptr<CommandMap> g_DefaultInsertModeCommandMap;
static void build_command_mode()
{
g_DefaultCommandMap = std::make_shared<CommandMap>();
CommandMap * dcm = &*g_DefaultCommandMap;
auto motion_map = std::make_shared<CommandMap>();
motion_map->add("t", Command::Motion::FORWARD_UP_TO_CHAR, nullptr, true);
motion_map->add("f", Command::Motion::FORWARD_ON_TO_CHAR, nullptr, true);
motion_map->add("T", Command::Motion::BACK_UP_TO_CHAR, nullptr, true);
motion_map->add("F", Command::Motion::BACK_ON_TO_CHAR, nullptr, true);
motion_map->add("0", Command::Motion::START_OF_LINE, nullptr, false);
motion_map->add("$", Command::Motion::END_OF_LINE, nullptr, false);
dcm->add("$", Command::GO_END_OF_LINE, nullptr, false);
dcm->add("0", Command::GO_START_OF_LINE, nullptr, false);
dcm->add({JES_KEY_MODS_CTRL + 'e'}, Command::SCROLL_WINDOW_DOWN_ONE_LINE, nullptr, false);
dcm->add("r", Command::REDO, nullptr, false);
dcm->add("t", Command::GO_FORWARD_UP_TO_CHAR, nullptr, true);
dcm->add("T", Command::GO_BACK_UP_TO_CHAR, nullptr, true);
dcm->add("y", Command::YANK_MOTION, motion_map, false);
dcm->add("yy", Command::YANK_LINE, nullptr, false);
dcm->add({JES_KEY_MODS_CTRL + 'y'}, Command::SCROLL_WINDOW_UP_ONE_LINE, nullptr, false);
dcm->add("u", Command::UNDO, nullptr, false);
dcm->add({JES_KEY_MODS_CTRL + 'u'}, Command::SCROLL_WINDOW_UP_HALF_SCREEN, nullptr, false);
dcm->add("i", Command::ENTER_INSERT_MODE, nullptr, false);
dcm->add("o", Command::ENTER_INSERT_MODE_NEW_LINE_AFTER, nullptr, false);
dcm->add("O", Command::ENTER_INSERT_MODE_NEW_LINE_BEFORE, nullptr, false);
dcm->add("p", Command::PUT, nullptr, false);
dcm->add("P", Command::PUT_BEFORE, nullptr, false);
dcm->add("a", Command::ENTER_INSERT_MODE_AFTER_CHAR, nullptr, false);
dcm->add("A", Command::ENTER_INSERT_MODE_AFTER_LINE, nullptr, false);
dcm->add("s", Command::CHANGE_CHAR, nullptr, false);
dcm->add("d", Command::DELETE_MOTION, motion_map, false);
dcm->add("dd", Command::DELETE_LINE, nullptr, false);
dcm->add({JES_KEY_MODS_CTRL + 'd'}, Command::SCROLL_WINDOW_DOWN_HALF_SCREEN, nullptr, false);
dcm->add("f", Command::GO_FORWARD_ON_TO_CHAR, nullptr, true);
dcm->add("F", Command::GO_BACK_ON_TO_CHAR, nullptr, true);
dcm->add({JES_KEY_MODS_CTRL + 'f'}, Command::SCROLL_WINDOW_DOWN_WHOLE_SCREEN, nullptr, false);
dcm->add("gg", Command::GO_TO_LINE, nullptr, false);
dcm->add("G", Command::GO_TO_LAST_LINE, nullptr, false);
dcm->add("h", Command::GO_LEFT, nullptr, false);
dcm->add("H", Command::GO_TOP_OF_SCREEN, nullptr, false);
dcm->add("j", Command::GO_DOWN, nullptr, false);
dcm->add("k", Command::GO_UP, nullptr, false);
dcm->add("l", Command::GO_RIGHT, nullptr, false);
dcm->add("L", Command::GO_BOTTOM_OF_SCREEN, nullptr, false);
dcm->add(":", Command::ENTER_COMMAND_LINE_PROMPT, nullptr, false);
dcm->add("x", Command::DELETE_CHAR, nullptr, false);
dcm->add("X", Command::DELETE_CHAR_BACK, nullptr, false);
dcm->add("c", Command::CHANGE_MOTION, motion_map, false);
dcm->add("cc", Command::CHANGE_LINE, nullptr, false);
dcm->add({JES_KEY_MODS_CTRL + 'b'}, Command::SCROLL_WINDOW_UP_WHOLE_SCREEN, nullptr, false);
dcm->add("n", Command::NEXT, nullptr, false);
dcm->add("N", Command::PREV, nullptr, false);
dcm->add("M", Command::GO_MIDDLE_OF_SCREEN, nullptr, false);
dcm->add({JES_KEY_HOME}, Command::GO_START_OF_LINE, nullptr, false);
dcm->add({JES_KEY_MODS_CTRL + JES_KEY_HOME}, Command::GO_START_OF_FILE, nullptr, false);
dcm->add({JES_KEY_END}, Command::GO_END_OF_LINE, nullptr, false);
dcm->add({JES_KEY_MODS_CTRL + JES_KEY_END}, Command::GO_END_OF_FILE, nullptr, false);
dcm->add({JES_KEY_PAGE_UP}, Command::SCROLL_WINDOW_UP_WHOLE_SCREEN, nullptr, false);
dcm->add({JES_KEY_PAGE_DOWN}, Command::SCROLL_WINDOW_DOWN_WHOLE_SCREEN, nullptr, false);
dcm->add({JES_KEY_UP}, Command::GO_UP, nullptr, false);
dcm->add({JES_KEY_LEFT}, Command::GO_LEFT, nullptr, false);
dcm->add({JES_KEY_DOWN}, Command::GO_DOWN, nullptr, false);
dcm->add({JES_KEY_RIGHT}, Command::GO_RIGHT, nullptr, false);
}
static void build_insert_mode()
{
g_DefaultInsertModeCommandMap = std::make_shared<CommandMap>();
CommandMap * dcm = &*g_DefaultInsertModeCommandMap;
dcm->add({JES_KEY_HOME}, Command::GO_START_OF_LINE, nullptr, false);
dcm->add({JES_KEY_MODS_CTRL + JES_KEY_HOME}, Command::GO_START_OF_FILE, nullptr, false);
dcm->add({JES_KEY_END}, Command::GO_END_OF_LINE, nullptr, false);
dcm->add({JES_KEY_MODS_CTRL + JES_KEY_END}, Command::GO_END_OF_FILE, nullptr, false);
dcm->add({JES_KEY_PAGE_UP}, Command::SCROLL_WINDOW_UP_WHOLE_SCREEN, nullptr, false);
dcm->add({JES_KEY_PAGE_DOWN}, Command::SCROLL_WINDOW_DOWN_WHOLE_SCREEN, nullptr, false);
dcm->add({JES_KEY_UP}, Command::GO_UP, nullptr, false);
dcm->add({JES_KEY_LEFT}, Command::GO_LEFT, nullptr, false);
dcm->add({JES_KEY_DOWN}, Command::GO_DOWN, nullptr, false);
dcm->add({JES_KEY_RIGHT}, Command::GO_RIGHT, nullptr, false);
}
void DefaultCommandMap::build()
{
build_command_mode();
build_insert_mode();
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef DEFAULTCOMMANDMAP_H
#define DEFAULTCOMMANDMAP_H
#include "CommandMap.h"
extern std::shared_ptr<CommandMap> g_DefaultCommandMap;
extern std::shared_ptr<CommandMap> g_DefaultInsertModeCommandMap;
class DefaultCommandMap
{
public:
static void build();
static std::shared_ptr<CommandMap> get()
{
return g_DefaultCommandMap;
}
static std::shared_ptr<CommandMap> get_insert_mode()
{
return g_DefaultInsertModeCommandMap;
}
};
#endif
+48
View File
@@ -0,0 +1,48 @@
#include "EncodedString.h"
#include <string.h>
uint32_t EncodedString::iterator::operator*()
{
if (m_offset >= m_encoded_string.size())
{
return INVALID_CODE_POINT;
}
return Encoding::decode(m_encoded_string.encoding(), &m_encoded_string[m_offset]);
}
EncodedString::iterator & EncodedString::iterator::operator++()
{
if (m_offset < m_encoded_string.size())
{
m_offset += Encoding::num_bytes_in_code_point(m_encoded_string.encoding(), &m_encoded_string[m_offset]);
}
return *this;
}
size_t EncodedString::iterator::size() const
{
if (m_offset < m_encoded_string.size())
{
return Encoding::num_bytes_in_code_point(m_encoded_string.encoding(), &m_encoded_string[m_offset]);
}
return 0u;
}
EncodedString::EncodedString(const uint8_t * data, size_t size, Encoding::Type encoding)
: m_encoding(encoding),
m_size(size)
{
m_data = std::make_shared<std::vector<uint8_t>>(size);
memcpy(&(*m_data)[0], data, size);
}
EncodedString::iterator EncodedString::begin() const
{
return iterator(*this, 0u);
}
EncodedString::iterator EncodedString::end() const
{
return iterator(*this, m_size);
}
+106
View File
@@ -0,0 +1,106 @@
#ifndef ENCODEDSTRING_H
#define ENCODEDSTRING_H
#include "Encoding.h"
#include <stdint.h>
#include <string.h>
#include <string>
#include <memory>
#include <vector>
class EncodedString
{
public:
enum : uint32_t
{
INVALID_CODE_POINT = 0xFFFFFFFFu,
};
class iterator
{
public:
iterator(const EncodedString & encoded_string, size_t offset)
: m_encoded_string(encoded_string),
m_offset(offset)
{
}
uint32_t operator*();
iterator & operator++();
size_t offset() const
{
return m_offset;
}
size_t size() const;
bool operator==(const iterator & other) const
{
return m_offset == other.m_offset;
}
bool operator!=(const iterator & other) const
{
return m_offset != other.m_offset;
}
protected:
const EncodedString & m_encoded_string;
size_t m_offset;
};
EncodedString(const uint8_t * data, size_t size, Encoding::Type encoding = Encoding::UTF_8);
EncodedString(const std::string & s)
: EncodedString((const uint8_t *)&s[0], s.size())
{
}
iterator begin() const;
iterator end() const;
size_t size() const
{
return m_size;
}
Encoding::Type encoding() const
{
return m_encoding;
}
const uint8_t & operator[](size_t index) const
{
return (*m_data)[index];
}
bool operator==(const EncodedString & other) const
{
return (m_encoding == other.m_encoding) &&
(m_size == other.m_size) &&
(memcmp(&(*m_data)[0], &(*other.m_data)[0], m_size) == 0);
}
bool operator!=(const EncodedString & other) const
{
return !(*this == other);
}
bool operator==(const std::string & s) const
{
return (m_size == s.size()) && (memcmp(&(*m_data)[0], &s[0], m_size) == 0);
}
bool operator!=(const std::string & s) const
{
return !(*this == s);
}
std::string to_string() const
{
return std::string((const char *)&(*m_data)[0], m_size);
}
protected:
Encoding::Type m_encoding;
std::shared_ptr<std::vector<uint8_t>> m_data;
size_t m_size;
};
static inline bool operator==(const std::string & ss, const EncodedString & es)
{
return es == ss;
}
static inline bool operator!=(const std::string & ss, const EncodedString & es)
{
return es != ss;
}
#endif
+244
View File
@@ -0,0 +1,244 @@
#include "Encoding.h"
static inline bool
check_continuation_bytes(const uint8_t ** buffer,
uint8_t continuation_bytes_to_check)
{
for (uint8_t i = 0; i < continuation_bytes_to_check; i++)
{
if ((**buffer & 0xC0u) != 0x80u)
return false;
(*buffer)++;
}
return true;
}
Encoding::Type Encoding::detect_encoding(const uint8_t * buffer, size_t length)
{
for (size_t i = 0u; i < length; i++)
{
const uint8_t c = *buffer;
uint8_t continuation_bytes_to_check;
if ((c & 0x80u) == 0u)
{
buffer++;
continue;
}
else if ((c & 0xE0u) == 0xC0u)
{
continuation_bytes_to_check = 1u;
}
else if ((c & 0xF0u) == 0xE0u)
{
continuation_bytes_to_check = 2u;
}
else if ((c & 0xF8u) == 0xF0u)
{
continuation_bytes_to_check = 3u;
}
else if ((c & 0xFCu) == 0xF8u)
{
continuation_bytes_to_check = 4u;
}
else if ((c & 0xFEu) == 0xFCu)
{
continuation_bytes_to_check = 5u;
}
else
{
return CP_1252;
}
buffer++;
if (((i + continuation_bytes_to_check) < length) &&
check_continuation_bytes(&buffer, continuation_bytes_to_check))
{
i += continuation_bytes_to_check;
continue;
}
return CP_1252;
}
return UTF_8;
}
uint8_t Encoding::num_bytes_in_code_point(Type type, const uint8_t * encoded)
{
switch (type)
{
case UTF_8:
{
if ((*encoded & 0x80u) == 0u)
return 1u;
encoded++;
uint8_t n = 1u;
while ((*encoded++ & 0xC0u) == 0x80u)
n++;
return n;
}
break;
case CP_1252:
return 1u;
}
return 0u;
}
const uint8_t * Encoding::beginning_of_code_point(Type type, const uint8_t * encoded)
{
switch (type)
{
case UTF_8:
while ((*encoded & 0xC0u) == 0x80u)
encoded--;
return encoded;
case CP_1252:
return encoded;
}
return nullptr;
}
uint32_t Encoding::decode(Type type, const uint8_t * encoded, uint8_t * encoded_size)
{
uint32_t result = 0u;
uint8_t size = 0u;
switch (type)
{
case UTF_8:
{
const uint_fast8_t c = *encoded;
if ((c & 0x80u) == 0u)
{
result = c;
size = 1u;
}
else
{
uint_fast8_t following_bytes = 0u;
if ((c & 0xE0u) == 0xC0u)
{
result = c & 0x1Fu;
following_bytes = 1u;
}
else if ((c & 0xF0u) == 0xE0u)
{
result = c & 0x0Fu;
following_bytes = 2u;
}
else if ((c & 0xF8u) == 0xF0u)
{
result = c & 0x07u;
following_bytes = 3u;
}
else if ((c & 0xFCu) == 0xF8u)
{
result = c & 0x03u;
following_bytes = 4u;
}
else if ((c & 0xFEu) == 0xFCu)
{
result = c & 0x01u;
following_bytes = 5u;
}
size = following_bytes + 1u;
while (following_bytes-- > 0u)
{
encoded++;
result <<= 6u;
result |= *encoded & 0x3Fu;
}
}
}
break;
case CP_1252:
/* TODO: map byte to code point */
result = *encoded;
size = 1u;
break;
}
if (encoded_size != nullptr)
{
*encoded_size = size;
}
return result;
}
uint8_t Encoding::num_bytes_to_encode_code_point(uint32_t code_point, Type type)
{
switch (type)
{
case UTF_8:
if (code_point <= 0x7Fu)
{
return 1u;
}
else if (code_point <= 0x7FFu)
{
return 2u;
}
else if (code_point <= 0xFFFFu)
{
return 3u;
}
else if (code_point <= 0x1FFFFFu)
{
return 4u;
}
else if (code_point <= 0x3FFFFFFu)
{
return 5u;
}
else
{
return 6u;
}
break;
case CP_1252:
if (code_point <= 0xFFu)
{
return 1u;
}
else
{
return 0u;
}
break;
}
return 0u;
}
uint8_t Encoding::encode(uint32_t code_point, Type type, uint8_t * buffer)
{
switch (type)
{
case UTF_8:
{
uint8_t size = num_bytes_to_encode_code_point(code_point, type);
if (size == 1u)
{
*buffer = code_point;
}
else if (size > 1u)
{
*buffer = (0xFFu << (8u - size)) | (code_point >> (6u * (size - 1u)));
for (uint8_t i = 0u, end = size - 1u; i < end; i++)
{
buffer[end - i] = 0x80u | (code_point >> (6u * i) & 0x3Fu);
}
}
return size;
}
break;
case CP_1252:
if (code_point <= 0xFFu)
{
*buffer = code_point;
return 1u;
}
break;
}
return 0u;
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef ENCODING_H
#define ENCODING_H
#include <stdint.h>
#include <stdlib.h>
class Encoding
{
public:
enum Type
{
UTF_8,
CP_1252,
};
enum : uint32_t
{
MAX_CODE_POINT_SIZE = 8u,
};
static Type detect_encoding(const uint8_t * buffer, size_t length);
static uint8_t num_bytes_in_code_point(Type type, const uint8_t * encoded);
static const uint8_t * beginning_of_code_point(Type type, const uint8_t * encoded);
static uint32_t decode(Type type, const uint8_t * encoded, uint8_t * encoded_size = nullptr);
static uint8_t num_bytes_to_encode_code_point(uint32_t code_point, Type type);
static uint8_t encode(uint32_t code_point, Type type, uint8_t * buffer);
};
#endif
+123
View File
@@ -0,0 +1,123 @@
#include "File.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#ifdef _WIN32
#define JES_O_BINARY O_BINARY
#else
#define JES_O_BINARY 0
#endif
/**
* Open a file.
*
* @param[in] filename The filename.
* @param[in] writing Whether to open for writing.
*/
bool File::open(const char * filename, bool writing)
{
int fd;
if (writing)
{
/* TODO: handle mode */
fd = ::open(filename, O_WRONLY | O_CREAT | O_TRUNC | JES_O_BINARY, 0644);
}
else
{
fd = ::open(filename, O_RDONLY | JES_O_BINARY);
}
if (fd < 0)
{
return false;
}
m_fd = fd;
return true;
}
/**
* Close the file.
*/
void File::close()
{
if (m_fd >= 0)
{
::close(m_fd);
m_fd = -1;
}
}
/**
* Get the file's size.
*
* This function will reset the file position to the beginning of the file.
*
* @return The file's size, or -1 on error.
*/
size_t File::get_size()
{
if (m_fd >= 0)
{
off_t o = lseek(m_fd, 0, SEEK_END);
lseek(m_fd, 0, SEEK_SET);
return o;
}
return -1;
}
/**
* Read from the file.
*
* @param[in] buf Memory buffer to read into. Must be large enough to hold
* size bytes.
* @param[in] size Number of bytes to read.
*
* @retval false The file failed to read.
* @retval true The file was read successfully.
*/
bool File::read(uint8_t * buf, size_t size)
{
size_t n_bytes_read = 0u;
for (;;)
{
off_t rd_size = ::read(m_fd, &buf[n_bytes_read], size - n_bytes_read);
if (rd_size <= 0)
break;
n_bytes_read += (size_t)rd_size;
if (n_bytes_read >= size)
break;
}
if (n_bytes_read != size)
{
return false;
}
return true;
}
bool File::write(const uint8_t * buf, size_t size)
{
if (size <= 0)
return true;
size_t n_bytes_written = 0u;
for (;;)
{
off_t write_size = ::write(m_fd, &buf[n_bytes_written], size - n_bytes_written);
if (write_size <= 0)
break;
n_bytes_written += (size_t)write_size;
if (n_bytes_written >= size)
break;
}
if (n_bytes_written != size)
{
return false;
}
return true;
}
+27
View File
@@ -0,0 +1,27 @@
#ifndef FILE_H
#define FILE_H
#include <stdint.h>
#include <stdlib.h>
#include "Span.h"
class File
{
public:
File() : m_fd(-1) {}
~File() { close(); }
bool open(const char * filename, bool writing = false);
void close();
size_t get_size();
bool read(uint8_t * buf, size_t size);
bool write(const uint8_t * buf, size_t size);
bool write(const Span & span)
{
return write(span.start, span.length);
}
protected:
int m_fd;
};
#endif
+129
View File
@@ -0,0 +1,129 @@
#include "GapBuffer.h"
#include "System.h"
#include <string.h>
GapBuffer::GapBuffer()
{
int num_pages = (64u * 1024u + System::page_size - 1u) >> System::page_size_log;
m_buffer = (uint8_t *)System::alloc_pages(num_pages);
m_buffer_size = System::page_size;
m_size = 0u;
m_gap_position = 0u;
}
GapBuffer::GapBuffer(uint8_t * buffer, size_t buffer_size, size_t size)
{
m_buffer = buffer;
m_buffer_size = buffer_size;
m_size = size;
m_gap_position = size;
}
GapBuffer::~GapBuffer()
{
System::free_pages(m_buffer, m_buffer_size >> System::page_size_log);
}
void GapBuffer::insert(size_t position, const uint8_t * data, size_t length)
{
ensure_free(length);
move_gap(position);
memcpy(&m_buffer[m_gap_position], data, length);
m_gap_position += length;
m_size += length;
}
void GapBuffer::erase(size_t position, size_t length)
{
if ((position < m_size) && ((position + length) <= m_size))
{
if ((position + length) == m_gap_position)
{
m_gap_position -= length;
m_size -= length;
}
else
{
move_gap(position);
m_size -= length;
}
}
}
void GapBuffer::clear()
{
m_size = 0u;
m_gap_position = 0u;
}
#ifdef ENABLE_TESTING
std::string GapBuffer::get_string()
{
compact();
return std::string((char *)m_buffer, m_size);
}
void GapBuffer::set_string(const std::string & s)
{
clear();
insert(0u, (const uint8_t *)s.c_str(), s.size());
}
#endif
void GapBuffer::ensure_free(size_t length)
{
if (gap_size() < length)
{
/* We're out of space. Allocate more and move data. */
size_t new_size = (m_buffer_size + (128u * 1024u)) & System::page_base_mask;
size_t new_num_pages = new_size >> System::page_size_log;
uint8_t * new_buffer = (uint8_t *)System::alloc_pages(new_num_pages);
memcpy(new_buffer, m_buffer, m_gap_position);
memcpy(&new_buffer[m_gap_position], &m_buffer[m_gap_position + gap_size()], m_size - m_gap_position);
/* Free the old buffer */
System::free_pages(m_buffer, m_buffer_size >> System::page_size_log);
m_buffer = new_buffer;
m_buffer_size = new_size;
m_gap_position = m_size;
}
}
void GapBuffer::move_gap(size_t position)
{
if (position != m_gap_position)
{
if (position < m_gap_position)
{
memmove(&m_buffer[position + gap_size()], &m_buffer[position], m_gap_position - position);
}
else
{
memmove(&m_buffer[m_gap_position], &m_buffer[m_gap_position + gap_size()], position - m_gap_position);
}
m_gap_position = position;
}
}
void GapBuffer::copy_to(size_t source_position, size_t length, GapBuffer & other, size_t destination_position)
{
if ((source_position < m_size) &&
(length > 0u) &&
((source_position + length) <= m_size))
{
if ((m_gap_position <= source_position) ||
(m_gap_position >= (source_position + length)))
{
/* The gap is before or after the range of data to copy. */
other.insert(destination_position, address(source_position), length);
}
else
{
/* The gap is in the middle of the range of data to copy. */
size_t pre_gap_size = m_gap_position - source_position;
other.insert(destination_position, address(source_position), pre_gap_size);
other.insert(destination_position + pre_gap_size, address(source_position + pre_gap_size), length - pre_gap_size);
}
}
}
+169
View File
@@ -0,0 +1,169 @@
#ifndef GAPBUFFER_H
#define GAPBUFFER_H
#include <stdint.h>
#include <stdlib.h>
#ifdef ENABLE_TESTING
#include <string>
#endif
class GapBuffer
{
public:
/**
* Create a new GapBuffer object.
*
* The GapBuffer will allocate its own memory.
*/
GapBuffer();
/**
* Create a GapBuffer object that points to preallocated memory.
*
* The GapBuffer object will take over ownership of the allocated memory.
*
* @param buffer
* Pointer to buffer allocated by System::alloc_pages().
* @param buffer_size
* Size of the buffer allocated by System::alloc_pages().
* @param size
* Size of the data in the buffer.
*/
GapBuffer(uint8_t * buffer, size_t buffer_size, size_t size);
/**
* Destroy the GapBuffer object and free its memory.
*/
~GapBuffer();
/**
* Insert data into the gap buffer at the given position.
*
* @param position
* Position in the gap buffer to insert the code point at.
* @param data
* Pointer to the data to insert.
* @param length
* Length of the data to insert.
*/
void insert(size_t position, const uint8_t * data, size_t length);
/**
* Erase data from the gap buffer.
*
* @param position
* Position in the gap buffer to erase data from.
* @param length
* Length of the data to erase.
*/
void erase(size_t position, size_t length);
/**
* Erase all data from the gap buffer.
*/
void clear();
/**
* Get the size of the data stored within the buffer.
*/
size_t size() const
{
return m_size;
}
/**
* Get the memory address of a buffer location.
*
* @param offset
* The index into the gap buffer data.
*/
uint8_t * address(size_t offset) const
{
if (offset < m_gap_position)
{
return &m_buffer[offset];
}
else
{
return &m_buffer[offset + gap_size()];
}
}
/**
* Move buffer gap to the end of the buffer so the contents are congruent
* at the beginning of the buffer.
*/
void compact()
{
move_gap(m_size);
}
/**
* Copy a range of data from this GapBuffer to another.
*
* @param source_position
* Position in the gap buffer containing the data to copy.
* @param length
* Length of the data to copy.
* @param other
* Destination GapBuffer to copy the data into.
* @param destination_position
* Position in the destination GapBuffer to copy the data to.
*/
void copy_to(size_t source_position, size_t length, GapBuffer & other, size_t destination_position);
#ifdef ENABLE_TESTING
/**
* Get the contents of the gap buffer as a string.
*
* This makes a copy of the entire buffer in a std::string. This method is
* intended for use by unit tests.
*/
std::string get_string();
/**
* Replace the buffer contents with a string.
*/
void set_string(const std::string & s);
#endif
protected:
/** Address of the allocated memory buffer. */
uint8_t * m_buffer;
/** Size of the allocated memory buffer. */
size_t m_buffer_size;
/** Size of the data stored in the buffer. */
size_t m_size;
/** Index of the gap in the memory buffer. */
size_t m_gap_position;
/**
* Get the gap size.
*/
size_t gap_size() const
{
return m_buffer_size - m_size;
}
/**
* Verify that there is enough free space in the gap, and if not, grow the
* gap and move data.
*
* @param length
* The number of bytes that must be free.
*/
void ensure_free(size_t length);
/**
* Move the gap buffer gap position to the requested position.
*
* @param position
* New gap position. Must be <= m_size.
*/
void move_gap(size_t position);
};
#endif
+11
View File
@@ -0,0 +1,11 @@
#include "LineEndings.h"
static uint8_t lf[] = "\n";
static uint8_t crlf[] = "\r\n";
static uint8_t cr[] = "\r";
const Span LineEndings::spans[] = {
Span(lf, 1u),
Span(crlf, 2u),
Span(cr, 1u),
};
+20
View File
@@ -0,0 +1,20 @@
#ifndef LINEENDINGS_H
#define LINEENDINGS_H
#include "Span.h"
class LineEndings
{
public:
enum Type
{
LF,
CRLF,
CR,
COUNT,
};
static const Span spans[COUNT];
};
#endif
+72
View File
@@ -0,0 +1,72 @@
#ifndef PAGEDBUFFER_H
#define PAGEDBUFFER_H
#include "System.h"
#include <vector>
#include <stdint.h>
#include <stdlib.h>
template <typename T>
class PagedBuffer
{
public:
/**
* Create a PagedBuffer.
*/
PagedBuffer<T>()
{
m_n_per_page = System::page_size / sizeof(T);
m_size = 0u;
}
~PagedBuffer<T>()
{
for (auto it : m_pages)
{
System::free_pages(it, 1);
}
}
const T & operator[](unsigned int index) const
{
while (index >= (m_pages.size() * m_n_per_page))
{
grow();
}
return m_pages[page_index(index)][index_in_page(index)];
}
T & operator[](unsigned int index)
{
while (index >= (m_pages.size() * m_n_per_page))
{
grow();
}
return m_pages[page_index(index)][index_in_page(index)];
}
size_t size() { return m_size; }
protected:
unsigned int page_index(unsigned int index)
{
return index / m_n_per_page;
}
unsigned int index_in_page(unsigned int index)
{
return index % m_n_per_page;
}
void grow()
{
m_pages.push_back((T *)System::alloc_pages(1));
m_size += m_n_per_page;
}
std::vector<T *> m_pages;
unsigned long m_size;
unsigned int m_n_per_page;
};
#endif
+89
View File
@@ -0,0 +1,89 @@
#include "Path.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
std::string Path::dirname(const std::string & s)
{
size_t pos = s.find_last_of("/\\");
if (pos == std::string::npos)
{
return ".";
}
if ((pos == 0u) || (pos == 2u && s[1] == ':'))
{
return std::string(s, 0, pos + 1u);
}
return std::string(s, 0, pos);
}
std::string Path::_join(const std::string & first, const std::string & second)
{
if (first == "")
{
return second;
}
if (second == "")
{
return first;
}
char last = first[first.size() - 1u];
if ((last == '/') || (last == '\\'))
{
return first + second;
}
return first + "/" + second;
}
bool Path::is_file(const std::string & s)
{
struct stat st;
if (stat(s.c_str(), &st) != 0)
{
return false;
}
return S_ISREG(st.st_mode);
}
bool Path::is_dir(const std::string & s)
{
struct stat st;
if (stat(s.c_str(), &st) != 0)
{
return false;
}
return S_ISDIR(st.st_mode);
}
std::shared_ptr<std::vector<std::shared_ptr<std::string>>> Path::listdir(const std::string & path)
{
std::shared_ptr<std::vector<std::shared_ptr<std::string>>> result = std::make_shared<std::vector<std::shared_ptr<std::string>>>();
DIR * dir = opendir(path.c_str());
if (dir != nullptr)
{
struct dirent * ent;
while ((ent = readdir(dir)) != nullptr)
{
result->push_back(std::make_shared<std::string>(ent->d_name));
}
closedir(dir);
}
return result;
}
std::string Path::clean(const std::string & s)
{
std::string p = s;
if (p.substr(0, 2) == "~/")
{
char * home = getenv("HOME");
if (home != nullptr)
{
p = home + p.substr(1, p.size());
}
}
return p;
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef PATH_H
#define PATH_H
#include <string>
#include <vector>
#include <memory>
class Path
{
public:
static std::string dirname(const std::string & s);
template <typename... Parts>
static std::string join(Parts... parts)
{
return _join(parts...);
}
static bool is_file(const std::string & s);
static bool is_dir(const std::string & s);
static std::shared_ptr<std::vector<std::shared_ptr<std::string>>> listdir(const std::string & path);
static std::string clean(const std::string & s);
protected:
static std::string _join(const std::string & first, const std::string & second);
template <typename... Parts>
static std::string _join(const std::string & first, const std::string & second, Parts... more)
{
return _join(_join(first, second), more...);
}
};
#endif
+76
View File
@@ -0,0 +1,76 @@
#include "Runtime.h"
#include "Path.h"
static const std::initializer_list<const char *> Font_Extensions = {"", ".ttf", ".otf"};
static const std::initializer_list<const char *> Shader_Extensions = {"", ".glsl"};
static std::string System_Runtime_Dir;
void Runtime::init(const char * executable_path, const char * appname)
{
System_Runtime_Dir = Path::join(Path::dirname(Path::dirname(executable_path)), "share", appname);
}
std::string Runtime::find(Type t, const std::string & name)
{
if (Path::is_file(name))
{
return name;
}
switch (t)
{
case FONT:
return find_first_under("fonts", name, Font_Extensions, true);
case SHADER:
return find_first_under("shaders", name, Shader_Extensions);
}
return "";
}
std::string Runtime::find_first_under(const std::string & runtime_subpath,
const std::string & name,
const std::initializer_list<const char *> & extensions,
bool look_in_subdirs)
{
std::string system_runtime_path = Path::join(System_Runtime_Dir, runtime_subpath);
std::string first = find_first_in(system_runtime_path, name, extensions);
if (first.empty() && look_in_subdirs)
{
auto dir_ents = Path::listdir(system_runtime_path);
for (auto dir_ent : *dir_ents)
{
if ((*dir_ent)[0] != '.')
{
std::string subpath = Path::join(system_runtime_path, *dir_ent);
if (Path::is_dir(subpath))
{
std::string p = find_first_in(subpath, name, extensions);
if (!p.empty())
{
return p;
}
}
}
}
}
return first;
}
std::string Runtime::find_first_in(const std::string & path,
const std::string & name,
const std::initializer_list<const char *> & extensions)
{
for (auto ext : extensions)
{
std::string attempt_path = Path::join(path, name + ext);
if (Path::is_file(attempt_path))
{
return attempt_path;
}
}
return "";
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef RUNTIME_H
#define RUNTIME_H
#include <string>
#include <initializer_list>
class Runtime
{
public:
enum Type
{
FONT,
SHADER,
};
static std::string find(Type t, const std::string & name);
static void init(const char * executable_path, const char * appname);
protected:
static std::string find_first_under(const std::string & runtime_subpath,
const std::string & name,
const std::initializer_list<const char *> & extensions,
bool look_in_subdirs = false);
static std::string find_first_in(const std::string & path,
const std::string & name,
const std::initializer_list<const char *> & extensions);
};
#endif
+19
View File
@@ -0,0 +1,19 @@
#ifndef SPAN_H
#define SPAN_H
#include <stdint.h>
#include <stdlib.h>
struct Span
{
uint8_t * start;
size_t length;
Span(uint8_t * _start, size_t _length)
: start(_start),
length(_length)
{
}
};
#endif
+105
View File
@@ -0,0 +1,105 @@
#include "System.h"
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#ifdef PLATFORM_WINDOWS
#include "windows.h"
#else
#include <sys/mman.h>
#endif
unsigned long System::page_size;
unsigned int System::page_size_log;
unsigned long System::page_base_mask;
unsigned long System::page_offset_mask;
static std::string Executable_Path;
bool System::init(char * argv[])
{
#if defined(PLATFORM_LINUX)
long system_page_size = sysconf(_SC_PAGESIZE);
if (system_page_size < 0)
{
fprintf(stderr, "Unable to determine operating system page size.\n");
return false;
}
#elif defined(PLATFORM_WINDOWS)
SYSTEM_INFO system_info = {0};
GetSystemInfo(&system_info);
unsigned long system_page_size = system_info.dwPageSize;
#endif
System::page_size = (unsigned long)system_page_size;
System::page_size_log = 0u;
System::page_offset_mask = System::page_size - 1u;
System::page_base_mask = ~System::page_offset_mask;
for (uint_fast8_t i = 0u; i < 32u; i++)
{
unsigned long v = 1u << i;
if (v == System::page_size)
{
System::page_size_log = i;
break;
}
}
if ((System::page_size < 256u) ||
(System::page_size > (4u * 1024u * 1024u)) ||
(System::page_size_log == 0u))
{
fprintf(stderr, "Unsupported page size detected: 0x%lx\n", System::page_size);
return false;
}
#if defined(PLATFORM_LINUX)
char link_buffer[1000];
ssize_t len = readlink("/proc/self/exe", link_buffer, sizeof(link_buffer));
if ((len > 0) && (len < (ssize_t)sizeof(link_buffer)))
{
Executable_Path = std::string(link_buffer, len);
}
else
{
return false;
}
#elif defined(PLATFORM_WINDOWS)
Executable_Path = argv[0];
#endif
return true;
}
void * System::alloc_pages(int num)
{
#ifdef PLATFORM_WINDOWS
return VirtualAlloc(
NULL,
num << System::page_size_log,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
#else
return mmap(
NULL,
num << System::page_size_log,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0);
#endif
}
void System::free_pages(void * addr, int num)
{
#ifdef PLATFORM_WINDOWS
(void)VirtualFree(addr, 0, MEM_RELEASE);
#else
(void)munmap(addr, num << System::page_size_log);
#endif
}
std::string System::executable_path()
{
return Executable_Path;
}
+20
View File
@@ -0,0 +1,20 @@
#ifndef SYSTEM_H
#define SYSTEM_H
#include <string>
class System
{
public:
static bool init(char * argv[]);
static void * alloc_pages(int num);
static void free_pages(void * addr, int num);
static std::string executable_path();
static unsigned long page_size;
static unsigned int page_size_log;
static unsigned long page_base_mask;
static unsigned long page_offset_mask;
};
#endif
+120
View File
@@ -0,0 +1,120 @@
#include "TextLoader.h"
#include <stdint.h>
#include <string.h>
/** Create a TextLoader. */
TextLoader::TextLoader()
{
m_line_endings = LineEndings::LF;
m_encoding = Encoding::UTF_8;
m_eol_at_eof = true;
m_num_lines = 0u;
}
/**
* Scan text to detect line endings and record their positions.
*
* @param buffer Buffer containing the text to import.
* @param size Size of the text to load. The buffer must be at least one
* byte larger than this.
* @param out_size Size of the loaded buffer.
*/
void TextLoader::load_buffer(uint8_t * buffer, size_t size, size_t * out_size)
{
std::list<size_t> cr_indexes;
size_t n_lines[LineEndings::COUNT] = {0};
bool crlf = true;
size_t next_start_of_line_index = 0u;
size_t out_size_local = size;
for (size_t i = 0; i < size; i++)
{
if (buffer[i] == '\r')
{
cr_indexes.push_back(i);
n_lines[LineEndings::CR]++;
if (crlf)
{
if ((i < (size - 1)) && (buffer[i + 1] == '\n'))
{
n_lines[LineEndings::LF]++;
n_lines[LineEndings::CRLF]++;
i++;
}
else
{
crlf = false;
}
}
next_start_of_line_index = i + 1;
}
else if (buffer[i] == '\n')
{
crlf = false;
n_lines[LineEndings::LF]++;
next_start_of_line_index = i + 1;
}
}
if (crlf && (n_lines[LineEndings::LF] > 0u))
{
m_line_endings = LineEndings::CRLF;
}
else if ((n_lines[LineEndings::CR] > 0u) && (n_lines[LineEndings::LF] == 0u))
{
m_line_endings = LineEndings::CR;
}
else
{
m_line_endings = LineEndings::LF;
}
m_num_lines = n_lines[m_line_endings];
/* Check if there is a line that was not terminated by a EOL sequence at
* the end of the file. */
if (next_start_of_line_index < size)
{
m_num_lines++;
m_eol_at_eof = false;
if (crlf)
{
cr_indexes.push_back(size);
}
else
{
buffer[size] = '\n';
out_size_local++;
}
}
m_encoding = Encoding::detect_encoding(buffer, size);
if (m_line_endings == LineEndings::CRLF)
{
/* Compress all CRLF sequences to LF in memory. */
size_t dest = 0u;
size_t src = 0u;
for (auto cr_index : cr_indexes)
{
size_t size = cr_index - src;
if ((src != dest) && (size > 0u))
{
memmove(&buffer[dest], &buffer[src], size);
}
dest += size;
src = cr_index + 2u;
buffer[dest++] = '\n';
}
out_size_local = dest;
}
else if (m_line_endings == LineEndings::CR)
{
/* Convert all \r to \n */
for (auto cr_index : cr_indexes)
{
buffer[cr_index] = '\n';
}
}
*out_size = out_size_local;
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef TEXTLOADER_H
#define TEXTLOADER_H
#include <stdint.h>
#include <list>
#include <memory>
#include "LineEndings.h"
#include "Encoding.h"
class TextLoader
{
public:
TextLoader();
void load_buffer(uint8_t * buffer, size_t size, size_t * out_size);
size_t num_lines()
{
return m_num_lines;
}
LineEndings::Type get_line_endings() { return m_line_endings; }
Encoding::Type get_encoding() { return m_encoding; }
bool get_eol_at_eof() { return m_eol_at_eof; }
protected:
LineEndings::Type m_line_endings;
Encoding::Type m_encoding;
bool m_eol_at_eof;
size_t m_num_lines;
};
#endif
+32
View File
@@ -0,0 +1,32 @@
#include "Timer.h"
#include <sys/time.h>
#include <stdio.h>
static long get_usec()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000000 + tv.tv_usec;
}
Timer::Timer()
{
m_start_time = 0;
m_elapsed = 0;
start();
}
void Timer::start()
{
m_start_time = get_usec();
}
void Timer::stop()
{
m_elapsed = get_usec() - m_start_time;
}
void Timer::print()
{
printf("Elapsed: %ldus\n", m_elapsed);
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef TIMER_H
#define TIMER_H
class Timer
{
public:
Timer();
void start();
void stop();
void print();
long get_elapsed() { return m_elapsed; }
protected:
long m_start_time;
long m_elapsed;
};
#endif
+120
View File
@@ -0,0 +1,120 @@
#ifndef KEYS_H
#define KEYS_H
#define JES_KEY_MODS_MASK 0xFF000000u
#define JES_KEY_MODS_SHIFT 0x01000000u
#define JES_KEY_MODS_LOCK 0x02000000u
#define JES_KEY_MODS_CTRL 0x04000000u
#define JES_KEY_KEYCODE_MASK 0x00FFFFFFu
#define JES_KEY_F1 0x00FF0001u
#define JES_KEY_F2 0x00FF0002u
#define JES_KEY_F3 0x00FF0003u
#define JES_KEY_F4 0x00FF0004u
#define JES_KEY_F5 0x00FF0005u
#define JES_KEY_F6 0x00FF0006u
#define JES_KEY_F7 0x00FF0007u
#define JES_KEY_F8 0x00FF0008u
#define JES_KEY_F9 0x00FF0009u
#define JES_KEY_F10 0x00FF000Au
#define JES_KEY_F11 0x00FF000Bu
#define JES_KEY_F12 0x00FF000Cu
#define JES_KEY_F13 0x00FF000Du
#define JES_KEY_F14 0x00FF000Eu
#define JES_KEY_F15 0x00FF000Fu
#define JES_KEY_F16 0x00FF0010u
#define JES_KEY_F17 0x00FF0011u
#define JES_KEY_F18 0x00FF0012u
#define JES_KEY_F19 0x00FF0013u
#define JES_KEY_F20 0x00FF0014u
#define JES_KEY_F21 0x00FF0015u
#define JES_KEY_F22 0x00FF0016u
#define JES_KEY_F23 0x00FF0017u
#define JES_KEY_F24 0x00FF0018u
#define JES_KEY_F25 0x00FF0019u
#define JES_KEY_F26 0x00FF001Au
#define JES_KEY_F27 0x00FF001Bu
#define JES_KEY_F28 0x00FF001Cu
#define JES_KEY_F29 0x00FF001Du
#define JES_KEY_F30 0x00FF001Eu
#define JES_KEY_F31 0x00FF001Fu
#define JES_KEY_SHIFT_L 0x00FF0030u
#define JES_KEY_SHIFT_R 0x00FF0031u
#define JES_KEY_CTRL_L 0x00FF0032u
#define JES_KEY_CTRL_R 0x00FF0033u
#define JES_KEY_CAPS_LOCK 0x00FF0034u
#define JES_KEY_SHIFT_LOCK 0x00FF0035u
#define JES_KEY_META_L 0x00FF0036u
#define JES_KEY_META_R 0x00FF0037u
#define JES_KEY_ALT_L 0x00FF0038u
#define JES_KEY_ALT_R 0x00FF0039u
#define JES_KEY_SUPER_L 0x00FF003Au
#define JES_KEY_SUPER_R 0x00FF003Bu
#define JES_KEY_HOME 0x00FF003Cu
#define JES_KEY_LEFT 0x00FF003Du
#define JES_KEY_UP 0x00FF003Eu
#define JES_KEY_RIGHT 0x00FF003Fu
#define JES_KEY_DOWN 0x00FF0040u
#define JES_KEY_PAGE_UP 0x00FF0042u
#define JES_KEY_PAGE_DOWN 0x00FF0044u
#define JES_KEY_END 0x00FF0045u
#define JES_KEY_BEGIN 0x00FF0046u
#define JES_KEY_SELECT 0x00FF0047u
#define JES_KEY_PRINT 0x00FF0048u
#define JES_KEY_EXECUTE 0x00FF0049u
#define JES_KEY_INSERT 0x00FF004Au
#define JES_KEY_UNDO 0x00FF004Bu
#define JES_KEY_REDO 0x00FF004Cu
#define JES_KEY_MENU 0x00FF004Du
#define JES_KEY_FIND 0x00FF004Eu
#define JES_KEY_CANCEL 0x00FF004Fu
#define JES_KEY_HELP 0x00FF0050u
#define JES_KEY_BREAK 0x00FF0051u
#define JES_KEY_NUM_LOCK 0x00FF0054u
#define JES_KEY_KP_SPACE 0x00FF0055u
#define JES_KEY_KP_TAB 0x00FF0056u
#define JES_KEY_KP_ENTER 0x00FF0057u
#define JES_KEY_KP_F1 0x00FF0058u
#define JES_KEY_KP_F2 0x00FF0059u
#define JES_KEY_KP_F3 0x00FF005Au
#define JES_KEY_KP_F4 0x00FF005Bu
#define JES_KEY_KP_HOME 0x00FF005Cu
#define JES_KEY_KP_LEFT 0x00FF005Du
#define JES_KEY_KP_UP 0x00FF005Eu
#define JES_KEY_KP_RIGHT 0x00FF005Fu
#define JES_KEY_KP_DOWN 0x00FF0060u
#define JES_KEY_KP_PAGE_UP 0x00FF0062u
#define JES_KEY_KP_PAGE_DOWN 0x00FF0064u
#define JES_KEY_KP_END 0x00FF0065u
#define JES_KEY_KP_BEGIN 0x00FF0066u
#define JES_KEY_KP_INSERT 0x00FF0067u
#define JES_KEY_KP_DELETE 0x00FF0068u
#define JES_KEY_KP_EQUAL 0x00FF0069u
#define JES_KEY_KP_MULTIPLY 0x00FF006Au
#define JES_KEY_KP_ADD 0x00FF006Bu
#define JES_KEY_KP_SEPARATOR 0x00FF006Cu
#define JES_KEY_KP_SUBTRACT 0x00FF006Du
#define JES_KEY_KP_DECIMAL 0x00FF006Eu
#define JES_KEY_KP_DIVIDE 0x00FF006Fu
#define JES_KEY_KP_0 0x00FF0070u
#define JES_KEY_KP_1 0x00FF0071u
#define JES_KEY_KP_2 0x00FF0072u
#define JES_KEY_KP_3 0x00FF0073u
#define JES_KEY_KP_4 0x00FF0074u
#define JES_KEY_KP_5 0x00FF0075u
#define JES_KEY_KP_6 0x00FF0076u
#define JES_KEY_KP_7 0x00FF0077u
#define JES_KEY_KP_8 0x00FF0078u
#define JES_KEY_KP_9 0x00FF0079u
#define JES_KEY_UNKNOWN 0x00FFFFFFu
#endif