mv src src-c
This commit is contained in:
1 parent
a0b2f660a2
commit
50cd9efd86
75 files changed
No files matched your search
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef CHARACTERWIDTHDETERMINER_H
|
||||
#define CHARACTERWIDTHDETERMINER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class CharacterWidthDeterminer
|
||||
{
|
||||
public:
|
||||
virtual int operator()(uint32_t character) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 = [¤t_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, ¤t_arg, &arg_content] {
|
||||
if (arg_content)
|
||||
{
|
||||
m_args.push_back(EncodedString(¤t_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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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),
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 "";
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,590 @@
|
||||
#include "BufferPane.h"
|
||||
#include <cmath>
|
||||
|
||||
int BufferPane::Cwd::operator()(uint32_t character)
|
||||
{
|
||||
if (character < 0x20u)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
else if (character < 0x80u)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::max(1, m_window->font()->get_glyph(character)->get_advance() / m_window->font()->get_advance());
|
||||
}
|
||||
}
|
||||
|
||||
BufferPane::BufferPane(Window * window, std::shared_ptr<Buffer> buffer)
|
||||
: m_window(window), m_buffer(buffer), m_cwd(window)
|
||||
{
|
||||
m_iterator = buffer->add_cursor();
|
||||
m_buffer_view = std::make_shared<BufferView>(buffer, m_iterator, m_cwd);
|
||||
m_buffer_view->set_scroll_offset(5);
|
||||
m_buffer_view->update();
|
||||
m_show_status_bar = true;
|
||||
m_show_line_number_gutter = true;
|
||||
m_command_mode = false;
|
||||
m_focused = false;
|
||||
m_rows = 0;
|
||||
m_columns = 0;
|
||||
}
|
||||
|
||||
void BufferPane::resize(int width, int height)
|
||||
{
|
||||
Pane::resize(width, height);
|
||||
resize_buffer_view();
|
||||
}
|
||||
|
||||
void BufferPane::resize_buffer_view()
|
||||
{
|
||||
if (m_show_line_number_gutter)
|
||||
{
|
||||
int max_line_possible = 1;
|
||||
if (m_iterator->valid())
|
||||
{
|
||||
max_line_possible = std::min(m_iterator->line() + m_rows - m_buffer_view->cursor_screen_row(), m_buffer->end().line());
|
||||
}
|
||||
m_line_number_gutter_width = log10(max_line_possible) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_line_number_gutter_width = 0;
|
||||
}
|
||||
int columns = std::max(1, (m_width - col_x(0)) / m_window->font()->get_advance());
|
||||
int height_subtract = 0;
|
||||
if (m_show_status_bar)
|
||||
{
|
||||
height_subtract = m_window->font()->get_line_height() + 1;
|
||||
}
|
||||
int rows = std::max(1, (m_height - height_subtract) / m_window->font()->get_line_height());
|
||||
if ((rows != m_rows) || (columns != m_columns))
|
||||
{
|
||||
m_rows = rows;
|
||||
m_columns = columns;
|
||||
m_buffer_view->resize(m_columns, m_rows);
|
||||
m_buffer_view->update();
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::draw()
|
||||
{
|
||||
resize_buffer_view();
|
||||
if (m_iterator->valid())
|
||||
{
|
||||
for (auto line_iterator = m_buffer_view->vert_iter();
|
||||
line_iterator.is_valid();
|
||||
line_iterator++)
|
||||
{
|
||||
draw_buffer_line(line_iterator.row_offset(), line_iterator.iterator());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
draw_cursor(col_x(0), row_y(0), 0, 1);
|
||||
}
|
||||
if (m_show_line_number_gutter)
|
||||
{
|
||||
m_window->gl()->draw_rect(win_x(m_line_number_gutter_width * m_window->font()->get_advance() + 1), win_y(0), 1, m_height, 0.5, 0.5, 0.5, 1.0);
|
||||
}
|
||||
if (m_show_status_bar)
|
||||
{
|
||||
draw_status_bar();
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::scroll_window_up(Window::ScrollMode scroll_mode)
|
||||
{
|
||||
int lines_to_scroll = calculate_lines_to_scroll(scroll_mode);
|
||||
m_buffer_view->scroll_view_up(lines_to_scroll, insert_mode());
|
||||
m_buffer_view->update();
|
||||
m_window->request_redraw();
|
||||
}
|
||||
|
||||
void BufferPane::scroll_window_down(Window::ScrollMode scroll_mode)
|
||||
{
|
||||
int lines_to_scroll = calculate_lines_to_scroll(scroll_mode);
|
||||
m_buffer_view->scroll_view_down(lines_to_scroll, insert_mode());
|
||||
m_buffer_view->update();
|
||||
m_window->request_redraw();
|
||||
}
|
||||
|
||||
|
||||
void BufferPane::draw_buffer_line(int screen_row, std::shared_ptr<Buffer::Iterator> start_of_line)
|
||||
{
|
||||
if (m_show_line_number_gutter)
|
||||
{
|
||||
draw_line_number(screen_row, start_of_line->line());
|
||||
}
|
||||
int last_drawn_crosshair_row = -1;
|
||||
for (auto it = m_buffer_view->horiz_iter(start_of_line);
|
||||
it.is_valid();
|
||||
it++)
|
||||
{
|
||||
int draw_row = screen_row + it.row_offset();
|
||||
int cwidth = std::max(it.character_width(), 1);
|
||||
if ((draw_row < 0) || (draw_row > m_rows))
|
||||
continue;
|
||||
if (it.iterator()->line() == m_iterator->line())
|
||||
{
|
||||
if ((draw_row > last_drawn_crosshair_row) &&
|
||||
((!it.is_eol()) || (it.row_offset() == 0)))
|
||||
{
|
||||
draw_crosshair(col_x(0), row_y(draw_row), col_x(m_columns));
|
||||
last_drawn_crosshair_row = draw_row;
|
||||
}
|
||||
}
|
||||
else if ((m_buffer_view->cursor_virtual_column() >= it.virtual_column()) &&
|
||||
(m_buffer_view->cursor_virtual_column() < (it.virtual_column() + cwidth)))
|
||||
{
|
||||
int col = it.screen_column() + (m_buffer_view->cursor_virtual_column() - it.virtual_column());
|
||||
int row = draw_row;
|
||||
while (col >= m_columns)
|
||||
{
|
||||
col -= m_columns;
|
||||
row++;
|
||||
}
|
||||
draw_crosshair(col_x(col), row_y(row));
|
||||
}
|
||||
if (*it.iterator() == *m_iterator)
|
||||
{
|
||||
bool wrap = (it.code_point() == '\t');
|
||||
int row = draw_row;
|
||||
int col = it.screen_column();
|
||||
for (int i = 0; i < cwidth; i++)
|
||||
{
|
||||
draw_cursor(col_x(col), row_y(row), i, cwidth);
|
||||
col++;
|
||||
if (wrap && (col >= m_columns) && (i < (cwidth - 1)))
|
||||
{
|
||||
row++;
|
||||
col = 0;
|
||||
/* We draw another crosshair row here so that it can be
|
||||
* drawn before drawing the cursor over top of it. If we
|
||||
* did not do this then the draw_crosshair for the next
|
||||
* character on the new row would overwrite the wrapped
|
||||
* part of the cursor. */
|
||||
draw_crosshair(col_x(0), row_y(row), col_x(m_columns));
|
||||
last_drawn_crosshair_row = row;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (it.is_eol())
|
||||
{
|
||||
if ((m_buffer_view->cursor_virtual_column() > it.virtual_column()) &&
|
||||
(m_buffer_view->cursor_virtual_column() < (it.virtual_column() + (m_columns - it.screen_column()))))
|
||||
{
|
||||
draw_crosshair(col_x(it.screen_column() + (m_buffer_view->cursor_virtual_column() - it.virtual_column())),
|
||||
row_y(draw_row));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
draw_buffer_character(it.code_point(), draw_row, it.screen_column());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::draw_buffer_character(uint32_t character, int screen_row, int screen_column)
|
||||
{
|
||||
if ((character != ' ') &&
|
||||
(character != '\t') &&
|
||||
(character != '\n') &&
|
||||
(character != Buffer::Iterator::INVALID_CODE_POINT))
|
||||
{
|
||||
int x = col_x(screen_column);
|
||||
int y = row_y(screen_row);
|
||||
if (character < 0x20u)
|
||||
{
|
||||
m_window->gl()->draw_rect(win_x(x + 2), win_y(y),
|
||||
m_window->font()->get_advance() * 2 - 4, 1,
|
||||
0, 0.7, 1.0, 1.0);
|
||||
m_window->gl()->draw_character(win_x(x), win_y(y), '^', *m_window->font(), 0.0, 0.7, 1.0, 1.0);
|
||||
m_window->gl()->draw_character(win_x(col_x(screen_column + 1)), win_y(y), character | 0x40u, *m_window->font(), 0.0, 0.7, 1.0, 1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_window->gl()->draw_character(win_x(x), win_y(y), character, *m_window->font(), 1.0, 1.0, 1.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::draw_cursor(int x, int y, int i, int columns)
|
||||
{
|
||||
if (m_command_mode && (!m_focused))
|
||||
{
|
||||
return;
|
||||
}
|
||||
int width = m_window->font()->get_advance();
|
||||
int height = m_window->font()->get_line_height();
|
||||
if (insert_mode())
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
m_window->gl()->draw_rect(win_x(x), win_y(y), 3, height, 1.0, 0.2, 1.0, 1.0);
|
||||
}
|
||||
}
|
||||
else if (m_focused)
|
||||
{
|
||||
m_window->gl()->draw_rect(win_x(x), win_y(y), width, height, 1.0, 0.2, 1.0, 1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_window->gl()->draw_rect(win_x(x), win_y(y), width, 1, 1.0, 0.2, 1.0, 1.0);
|
||||
m_window->gl()->draw_rect(win_x(x), win_y(y) + height - 1, width, 1, 1.0, 0.2, 1.0, 1.0);
|
||||
if (i == 0)
|
||||
{
|
||||
m_window->gl()->draw_rect(win_x(x), win_y(y), 1, height, 1.0, 0.2, 1.0, 1.0);
|
||||
}
|
||||
if (i == (columns - 1))
|
||||
{
|
||||
m_window->gl()->draw_rect(win_x(x) + width - 1, win_y(y), 1, height, 1.0, 0.2, 1.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::draw_crosshair(int x, int y, int width, int height)
|
||||
{
|
||||
if (!m_command_mode)
|
||||
{
|
||||
if (width < 0)
|
||||
width = m_window->font()->get_advance();
|
||||
if (height < 0)
|
||||
height = m_window->font()->get_line_height();
|
||||
m_window->gl()->draw_rect(win_x(x), win_y(y), width, height, 0.1, 0.1, 0.1, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::draw_line_number(int screen_row, size_t line_number)
|
||||
{
|
||||
if (screen_row >= 0)
|
||||
{
|
||||
char line_number_string[20];
|
||||
int length = snprintf(line_number_string, sizeof(line_number_string), "%lu", line_number + 1u);
|
||||
int x = (m_line_number_gutter_width - length) * m_window->font()->get_advance();
|
||||
int y = row_y(screen_row);
|
||||
float r, g;
|
||||
if (line_number == m_iterator->line())
|
||||
{
|
||||
r = 1.0;
|
||||
g = 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
r = 0.6;
|
||||
g = 0.6;
|
||||
}
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
m_window->gl()->draw_character(win_x(x), win_y(y), line_number_string[i], *m_window->font(), r, g, 0.0, 1.0);
|
||||
x += m_window->font()->get_advance();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::exit_insert_mode()
|
||||
{
|
||||
if (insert_mode())
|
||||
{
|
||||
m_buffer->exit_insert_mode();
|
||||
if (!m_iterator->is_start_of_line())
|
||||
{
|
||||
m_iterator->go_back();
|
||||
}
|
||||
m_buffer_view->update();
|
||||
m_window->request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::insert_code_point(uint32_t code_point)
|
||||
{
|
||||
if (insert_mode())
|
||||
{
|
||||
if (code_point == '\b') /* Backspace */
|
||||
{
|
||||
Buffer::Iterator i = *m_iterator;
|
||||
i.go_back();
|
||||
if (i.valid())
|
||||
{
|
||||
m_buffer->erase_code_point(i);
|
||||
}
|
||||
}
|
||||
else if (code_point == 127) /* Delete */
|
||||
{
|
||||
m_buffer->erase_code_point(*m_iterator);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO: adjust cursor screen row in m_buffer_view. */
|
||||
m_buffer->insert_code_point(*m_iterator, code_point);
|
||||
}
|
||||
m_buffer_view->update();
|
||||
m_window->request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::kill_character_forward()
|
||||
{
|
||||
if (**m_iterator != '\n')
|
||||
{
|
||||
m_buffer->erase_code_point(*m_iterator);
|
||||
if (**m_iterator == '\n')
|
||||
{
|
||||
m_iterator->go_left_in_line();
|
||||
}
|
||||
m_buffer_view->update();
|
||||
m_window->request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::kill_character_backward()
|
||||
{
|
||||
Buffer::Iterator it = *m_iterator;
|
||||
if (it.go_left_in_line())
|
||||
{
|
||||
m_buffer->erase_code_point(it);
|
||||
m_buffer_view->update();
|
||||
m_window->request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
size_t BufferPane::display_column() const
|
||||
{
|
||||
if (!m_iterator->valid())
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
if ((**m_iterator == '\n') && (!insert_mode()))
|
||||
{
|
||||
Buffer::Iterator i = *m_iterator;
|
||||
if (!i.go_left_in_line())
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
return m_buffer_view->cursor_virtual_column() + 1u;
|
||||
}
|
||||
|
||||
void BufferPane::cursor_move(BufferPane::CursorMovement which, uint32_t c)
|
||||
{
|
||||
bool moved = m_buffer_view->cursor_move(which, c, insert_mode());
|
||||
if (moved)
|
||||
{
|
||||
m_buffer_view->update();
|
||||
m_window->request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::cursor_move_to_line(size_t target_line)
|
||||
{
|
||||
bool moved = m_buffer_view->cursor_move_to_line(target_line);
|
||||
if (moved)
|
||||
{
|
||||
m_buffer_view->update();
|
||||
m_window->request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::enter_insert_mode(Window::EnterInsertModeMode which)
|
||||
{
|
||||
if (insert_mode())
|
||||
return;
|
||||
|
||||
switch (which)
|
||||
{
|
||||
case Window::EnterInsertModeMode::START_OF_CHAR:
|
||||
m_buffer->enter_insert_mode();
|
||||
break;
|
||||
case Window::EnterInsertModeMode::END_OF_CHAR:
|
||||
if (**m_iterator != '\n')
|
||||
{
|
||||
m_iterator->go_forward();
|
||||
}
|
||||
m_buffer->enter_insert_mode();
|
||||
break;
|
||||
case Window::EnterInsertModeMode::START_OF_LINE:
|
||||
m_iterator->go_start_of_line();
|
||||
enter_insert_mode(Window::EnterInsertModeMode::START_OF_CHAR);
|
||||
break;
|
||||
case Window::EnterInsertModeMode::END_OF_LINE:
|
||||
m_iterator->go_end_of_line(false);
|
||||
enter_insert_mode(Window::EnterInsertModeMode::END_OF_CHAR);
|
||||
break;
|
||||
case Window::EnterInsertModeMode::NEW_LINE_BEFORE:
|
||||
m_iterator->go_start_of_line();
|
||||
m_buffer->enter_insert_mode();
|
||||
m_buffer->insert_code_point(*m_iterator, '\n');
|
||||
m_buffer->exit_insert_mode();
|
||||
cursor_move(CursorMovement::UP, 0u);
|
||||
enter_insert_mode(Window::EnterInsertModeMode::START_OF_CHAR);
|
||||
break;
|
||||
case Window::EnterInsertModeMode::NEW_LINE_AFTER:
|
||||
m_iterator->go_end_of_line(true);
|
||||
m_buffer->enter_insert_mode();
|
||||
m_buffer->insert_code_point(*m_iterator, '\n');
|
||||
break;
|
||||
}
|
||||
|
||||
m_buffer_view->update();
|
||||
m_window->request_redraw();
|
||||
}
|
||||
|
||||
int BufferPane::calculate_lines_to_scroll(Window::ScrollMode scroll_mode)
|
||||
{
|
||||
switch (scroll_mode)
|
||||
{
|
||||
case Window::ScrollMode::ONE_LINE:
|
||||
return 1;
|
||||
break;
|
||||
case Window::ScrollMode::WHEEL:
|
||||
return 3;
|
||||
break;
|
||||
case Window::ScrollMode::HALF_SCREEN:
|
||||
return m_rows / 2;
|
||||
break;
|
||||
case Window::ScrollMode::WHOLE_SCREEN:
|
||||
return m_rows - 1;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BufferPane::draw_status_bar()
|
||||
{
|
||||
m_window->gl()->draw_rect(win_x(0), win_y(m_window->font()->get_line_height()), m_width, 1, 0.5, 0.5, 0.5, 1.0);
|
||||
m_window->gl()->draw_rect(win_x(0), win_y(0), m_width, m_window->font()->get_line_height(), 0.0, 0.0, 0.0, 1.0);
|
||||
char cursor_position[20];
|
||||
sprintf(cursor_position, "%zu, %zu", display_line(), display_column());
|
||||
int cursor_position_length = strlen(cursor_position);
|
||||
int x = m_width - m_window->font()->get_advance() * cursor_position_length;
|
||||
m_window->gl()->draw_rect(win_x(x - 2), win_y(0), 1, m_window->font()->get_line_height(), 0.5, 0.5, 0.5, 1.0);
|
||||
std::string filename = m_buffer->filename() ? *m_buffer->filename() : "[No Name]";
|
||||
int filename_x = std::min(0, x - 3 - (int)filename.size() * m_window->font()->get_advance());
|
||||
for (int i = 0; i < cursor_position_length; i++)
|
||||
{
|
||||
m_window->gl()->draw_character(win_x(x), win_y(0), cursor_position[i], *m_window->font(), 1.0, 1.0, 1.0, 1.0);
|
||||
x += m_window->font()->get_advance();
|
||||
}
|
||||
for (size_t i = 0; i < filename.size(); i++)
|
||||
{
|
||||
m_window->gl()->draw_character(win_x(filename_x), win_y(0), filename[i], *m_window->font(), 1.0, 1.0, 1.0, 1.0);
|
||||
filename_x += m_window->font()->get_advance();
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::undo()
|
||||
{
|
||||
m_buffer->undo();
|
||||
m_buffer_view->update();
|
||||
m_window->request_redraw();
|
||||
}
|
||||
|
||||
void BufferPane::redo()
|
||||
{
|
||||
m_buffer->redo();
|
||||
m_buffer_view->update();
|
||||
m_window->request_redraw();
|
||||
}
|
||||
|
||||
void BufferPane::delete_motion(const Command::Unit & motion)
|
||||
{
|
||||
auto range = get_range_for_motion(motion, false);
|
||||
if (range)
|
||||
{
|
||||
m_buffer->erase_range(*range);
|
||||
m_buffer_view->update();
|
||||
}
|
||||
}
|
||||
|
||||
void BufferPane::change_motion(const Command::Unit & motion)
|
||||
{
|
||||
auto range = get_range_for_motion(motion, true);
|
||||
if (range)
|
||||
{
|
||||
m_buffer->push_operation();
|
||||
enter_insert_mode(Window::EnterInsertModeMode::START_OF_CHAR);
|
||||
m_buffer->erase_range(*range);
|
||||
m_buffer->pop_operation();
|
||||
m_buffer_view->update();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Buffer::Range> BufferPane::get_range_for_motion(const Command::Unit & motion, bool will_insert)
|
||||
{
|
||||
if (!m_iterator->valid())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto start = m_iterator->clonep();
|
||||
auto end = m_iterator->clonep();
|
||||
|
||||
switch (motion.id)
|
||||
{
|
||||
case Command::Motion::FORWARD_UP_TO_CHAR:
|
||||
if (!end->go_forward_in_line_up_to_char(motion.following_char))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
end->go_forward();
|
||||
break;
|
||||
|
||||
case Command::Motion::FORWARD_ON_TO_CHAR:
|
||||
if (!end->go_forward_in_line_on_to_char(motion.following_char))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
end->go_forward();
|
||||
break;
|
||||
|
||||
case Command::Motion::BACK_UP_TO_CHAR:
|
||||
if (!start->go_backward_in_line_up_to_char(motion.following_char))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
|
||||
case Command::Motion::BACK_ON_TO_CHAR:
|
||||
if (!start->go_backward_in_line_on_to_char(motion.following_char))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
|
||||
case Command::Motion::START_OF_LINE:
|
||||
start->go_start_of_line();
|
||||
break;
|
||||
|
||||
case Command::Motion::END_OF_LINE:
|
||||
end->go_end_of_line(true);
|
||||
break;
|
||||
|
||||
case Command::Motion::THIS_LINE:
|
||||
start->go_start_of_line();
|
||||
end->go_end_of_line(!will_insert);
|
||||
end->go_forward();
|
||||
break;
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_shared<Buffer::Range>(start, end);
|
||||
}
|
||||
|
||||
void BufferPane::set_command_mode()
|
||||
{
|
||||
m_command_mode = true;
|
||||
set_show_status_bar(false);
|
||||
set_show_line_number_gutter(false);
|
||||
enter_insert_mode(Window::EnterInsertModeMode::START_OF_CHAR);
|
||||
}
|
||||
|
||||
void BufferPane::clear()
|
||||
{
|
||||
m_buffer->clear();
|
||||
*m_iterator = m_buffer->begin();
|
||||
m_buffer_view->update();
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
#ifndef BUFFERPANE_H
|
||||
#define BUFFERPANE_H
|
||||
|
||||
#include "Buffer.h"
|
||||
#include "Pane.h"
|
||||
#include "Font.h"
|
||||
#include "GL.h"
|
||||
#include <memory>
|
||||
#include "Window.h"
|
||||
#include "CharacterWidthDeterminer.h"
|
||||
#include "BufferView.h"
|
||||
|
||||
class BufferPane : public Pane
|
||||
{
|
||||
public:
|
||||
class Cwd : public CharacterWidthDeterminer
|
||||
{
|
||||
public:
|
||||
Cwd(Window * window)
|
||||
{
|
||||
m_window = window;
|
||||
}
|
||||
int operator()(uint32_t character);
|
||||
|
||||
protected:
|
||||
Window * m_window;
|
||||
};
|
||||
|
||||
typedef BufferView::CursorMovement CursorMovement;
|
||||
|
||||
BufferPane(Window * window, std::shared_ptr<Buffer> buffer);
|
||||
void resize(int width, int height) override;
|
||||
void draw();
|
||||
void cursor_move(CursorMovement which, uint32_t c);
|
||||
void cursor_move_to_line(size_t target_line);
|
||||
void enter_insert_mode(Window::EnterInsertModeMode which);
|
||||
void insert_code_point(uint32_t code_point);
|
||||
void exit_insert_mode();
|
||||
void kill_character_forward();
|
||||
void kill_character_backward();
|
||||
bool insert_mode() const { return m_buffer->insert_mode(); }
|
||||
void scroll_window_up(Window::ScrollMode scroll_mode);
|
||||
void scroll_window_down(Window::ScrollMode scroll_mode);
|
||||
void undo();
|
||||
void redo();
|
||||
void delete_motion(const Command::Unit & motion);
|
||||
void change_motion(const Command::Unit & motion);
|
||||
void set_show_status_bar(bool show_status_bar)
|
||||
{
|
||||
m_show_status_bar = show_status_bar;
|
||||
}
|
||||
void set_show_line_number_gutter(bool show_line_number_gutter)
|
||||
{
|
||||
m_show_line_number_gutter = show_line_number_gutter;
|
||||
}
|
||||
void set_command_mode();
|
||||
void set_focused(bool focused)
|
||||
{
|
||||
m_focused = focused;
|
||||
}
|
||||
void clear();
|
||||
std::shared_ptr<Buffer> buffer() const { return m_buffer; }
|
||||
|
||||
protected:
|
||||
void draw_buffer_line(int screen_row, std::shared_ptr<Buffer::Iterator> start_of_line);
|
||||
void draw_buffer_character(uint32_t character, int screen_row, int screen_column);
|
||||
void draw_cursor(int x, int y, int i, int columns);
|
||||
void draw_crosshair(int x, int y, int width = -1, int height = -1);
|
||||
void draw_line_number(int screen_row, size_t line_number);
|
||||
int col_x(int col)
|
||||
{
|
||||
return (m_line_number_gutter_width + col) * m_window->font()->get_advance() + (m_show_line_number_gutter ? 3 : 0);
|
||||
}
|
||||
int row_y(int row)
|
||||
{
|
||||
return m_height - (row + 1) * m_window->font()->get_line_height();
|
||||
}
|
||||
size_t display_line() const { return m_iterator->line() + 1u; }
|
||||
size_t display_column() const;
|
||||
void draw_status_bar();
|
||||
int calculate_lines_to_scroll(Window::ScrollMode scroll_mode);
|
||||
void resize_buffer_view();
|
||||
std::shared_ptr<Buffer::Range> get_range_for_motion(const Command::Unit & motion, bool will_insert);
|
||||
|
||||
Window * m_window;
|
||||
std::shared_ptr<Buffer> m_buffer;
|
||||
Cwd m_cwd;
|
||||
std::shared_ptr<BufferView> m_buffer_view;
|
||||
int m_rows;
|
||||
int m_columns;
|
||||
int m_line_number_gutter_width;
|
||||
std::shared_ptr<Buffer::Iterator> m_iterator;
|
||||
std::list<std::pair<int, Buffer::Iterator>> m_screen_lines;
|
||||
bool m_show_status_bar;
|
||||
bool m_show_line_number_gutter;
|
||||
bool m_command_mode;
|
||||
bool m_focused;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,91 @@
|
||||
#include "Font.h"
|
||||
|
||||
#define round_up_26_6(val) (((val) + 63) >> 6u)
|
||||
|
||||
static FT_Library ft_library_handle;
|
||||
static bool ft_initialized;
|
||||
|
||||
bool Initialize_FreeType()
|
||||
{
|
||||
if (ft_initialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (FT_Init_FreeType(&ft_library_handle) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ft_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Font::load(const char * fname, int size)
|
||||
{
|
||||
if (!Initialize_FreeType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FT_New_Face(ft_library_handle, fname, 0, &m_face) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FT_Set_Pixel_Sizes(m_face, 0, size);
|
||||
|
||||
return preload_glyphs();
|
||||
}
|
||||
|
||||
bool Font::preload_glyphs()
|
||||
{
|
||||
static const char preload_glyph_list[] = "HMgjqxy_|^";
|
||||
|
||||
int max_top = -9999;
|
||||
int min_bottom = 9999;
|
||||
m_advance = 0;
|
||||
|
||||
for (size_t i = 0u; i < sizeof(preload_glyph_list) - 1u; i++)
|
||||
{
|
||||
std::shared_ptr<Glyph> g = get_glyph(preload_glyph_list[i]);
|
||||
if (g == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (m_face->glyph->bitmap_top > max_top)
|
||||
{
|
||||
max_top = m_face->glyph->bitmap_top;
|
||||
}
|
||||
int bitmap_bottom = m_face->glyph->bitmap_top - m_face->glyph->bitmap.rows;
|
||||
if (bitmap_bottom < min_bottom)
|
||||
{
|
||||
min_bottom = bitmap_bottom;
|
||||
}
|
||||
if (g->get_advance() > m_advance)
|
||||
{
|
||||
m_advance = g->get_advance();
|
||||
}
|
||||
}
|
||||
|
||||
m_line_height = round_up_26_6(m_face->size->metrics.height);
|
||||
m_baseline_offset = (m_line_height - (max_top - min_bottom)) / 2 - min_bottom;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<Glyph> Font::get_glyph(FT_ULong character)
|
||||
{
|
||||
auto it = m_glyphs.find(character);
|
||||
if (it != m_glyphs.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
std::shared_ptr<Glyph> glyph = std::make_shared<Glyph>();
|
||||
if (!glyph->load(m_face, character))
|
||||
{
|
||||
glyph = nullptr;
|
||||
}
|
||||
m_glyphs[character] = glyph;
|
||||
return glyph;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef FONT_H
|
||||
#define FONT_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include "Glyph.h"
|
||||
|
||||
class Font
|
||||
{
|
||||
public:
|
||||
bool load(const char * fname, int size);
|
||||
std::shared_ptr<Glyph> get_glyph(FT_ULong character);
|
||||
int get_advance() { return m_advance; }
|
||||
int get_line_height() { return m_line_height; }
|
||||
int get_baseline_offset() { return m_baseline_offset; }
|
||||
|
||||
protected:
|
||||
bool preload_glyphs();
|
||||
|
||||
FT_Face m_face;
|
||||
std::unordered_map<FT_ULong, std::shared_ptr<Glyph>> m_glyphs;
|
||||
int m_advance;
|
||||
int m_line_height;
|
||||
int m_baseline_offset;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,52 @@
|
||||
#include "Glyph.h"
|
||||
|
||||
#define round_up_26_6(val) (((val) + 63) >> 6u)
|
||||
|
||||
bool Glyph::load(FT_Face face, FT_ULong char_code)
|
||||
{
|
||||
if (FT_Load_Char(face, char_code, FT_LOAD_RENDER) != 0)
|
||||
return false;
|
||||
|
||||
m_texture = glcxx::Texture::create();
|
||||
m_texture->bind(GL_TEXTURE_2D);
|
||||
int width = face->glyph->bitmap.width;
|
||||
int rounded_width = glcxx::Texture::next_power_of_2(width);
|
||||
int height = face->glyph->bitmap.rows;
|
||||
int rounded_height = glcxx::Texture::next_power_of_2(height);
|
||||
m_advance = round_up_26_6(face->glyph->advance.x);
|
||||
uint8_t * texture = new uint8_t[rounded_width * rounded_height];
|
||||
memset(texture, 0, rounded_width * rounded_height);
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
memcpy(&texture[rounded_width * i],
|
||||
&face->glyph->bitmap.buffer[width * (height - i - 1)],
|
||||
width);
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, rounded_width, rounded_height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texture);
|
||||
delete[] texture;
|
||||
|
||||
m_array = glcxx::Array::create();
|
||||
m_array->bind();
|
||||
|
||||
int left = face->glyph->bitmap_left;
|
||||
int top = face->glyph->bitmap_top;
|
||||
float s_max = width / (float)rounded_width;
|
||||
float t_max = height / (float)rounded_height;
|
||||
m_buffer = glcxx::Buffer::create(GL_ARRAY_BUFFER, GL_STATIC_DRAW, {
|
||||
(GLfloat)left, (GLfloat)(top - height), 0.0, 0.0,
|
||||
(GLfloat)(left + width), (GLfloat)(top - height), s_max, 0.0,
|
||||
(GLfloat)left, (GLfloat)top, 0.0, t_max,
|
||||
(GLfloat)(left + width), (GLfloat)top, s_max, t_max,
|
||||
});
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef GLYPH_H
|
||||
#define GLYPH_H
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include "glcxx.hpp"
|
||||
|
||||
class Glyph
|
||||
{
|
||||
public:
|
||||
bool load(FT_Face face, FT_ULong char_code);
|
||||
int get_advance() { return m_advance; }
|
||||
void render()
|
||||
{
|
||||
m_array->bind();
|
||||
m_texture->bind(GL_TEXTURE_2D);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_advance;
|
||||
std::shared_ptr<glcxx::Texture> m_texture;
|
||||
std::shared_ptr<glcxx::Buffer> m_buffer;
|
||||
std::shared_ptr<glcxx::Array> m_array;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
#ifndef PANE_H
|
||||
#define PANE_H
|
||||
|
||||
class Pane
|
||||
{
|
||||
public:
|
||||
Pane()
|
||||
{
|
||||
m_x = 0;
|
||||
m_y = 0;
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
}
|
||||
|
||||
virtual void move(int x, int y)
|
||||
{
|
||||
m_x = x;
|
||||
m_y = y;
|
||||
}
|
||||
|
||||
virtual void resize(int width, int height)
|
||||
{
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
}
|
||||
|
||||
int win_x(int offset)
|
||||
{
|
||||
return m_x + offset;
|
||||
}
|
||||
|
||||
int win_y(int offset)
|
||||
{
|
||||
return m_y + offset;
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_x;
|
||||
int m_y;
|
||||
int m_width;
|
||||
int m_height;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,681 @@
|
||||
#include "gl3w.h"
|
||||
#include "Window.h"
|
||||
#include "Runtime.h"
|
||||
#include "BufferPane.h"
|
||||
#include <iostream>
|
||||
#include "jes_icon-32x32.h"
|
||||
#include <unistd.h>
|
||||
#include <unordered_map>
|
||||
#include "DefaultCommandMap.h"
|
||||
#include "Path.h"
|
||||
|
||||
#define INITIAL_WIDTH 800
|
||||
#define INITIAL_HEIGHT 800
|
||||
#define FONT_SIZE 16
|
||||
|
||||
/* Process multiple queued events for up to this long before stopping to
|
||||
* redraw the screen. */
|
||||
#define MAX_EVENT_PROCESS_TIME 50000
|
||||
/* Amount of time until a status message fades out */
|
||||
#define STATUS_TIME 4000
|
||||
|
||||
/**
|
||||
* Initialize OpenGL.
|
||||
*
|
||||
* @retval true OpenGL was loaded successfully.
|
||||
* @retval false Loading OpenGL failed.
|
||||
*/
|
||||
static bool Initialize_OpenGL()
|
||||
{
|
||||
if (gl3wInit() != 0)
|
||||
{
|
||||
std::cerr << "Failed to initialize gl3w." << std::endl;
|
||||
|
||||
/* Failed to gl3wInit() */
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gl3wIsSupported(3, 0))
|
||||
{
|
||||
std::cerr << "Error: OpenGL 3.0 is required." << std::endl;
|
||||
|
||||
std::cerr << "OpenGL version: "
|
||||
<< glGetString(GL_VERSION)
|
||||
<< ", GLSL: "
|
||||
<< glGetString(GL_SHADING_LANGUAGE_VERSION)
|
||||
<< std::endl;
|
||||
|
||||
/* OpenGL 3.0 is not supported */
|
||||
return false;
|
||||
}
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Window::set_window_icon()
|
||||
{
|
||||
uint8_t * icon_bgra = (uint8_t *)malloc(32u * 32u * 4u);
|
||||
uint8_t * out_icon = icon_bgra;
|
||||
const uint8_t * in_icon = jes_icon_32x32;
|
||||
for (int row = 0; row < 32; row++)
|
||||
{
|
||||
for (int col = 0; col < 32; col++)
|
||||
{
|
||||
uint8_t r = *in_icon++;
|
||||
uint8_t g = *in_icon++;
|
||||
uint8_t b = *in_icon++;
|
||||
*out_icon++ = b;
|
||||
*out_icon++ = g;
|
||||
*out_icon++ = r;
|
||||
*out_icon++ = 0xFFu;
|
||||
}
|
||||
}
|
||||
Jtk_SetWindowIcon(m_window, icon_bgra, 32, 32);
|
||||
free(icon_bgra);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Window.
|
||||
*
|
||||
* @retval true The Window was created successfully.
|
||||
* @retval false There was an error while creating the Window.
|
||||
*/
|
||||
bool Window::create(std::shared_ptr<Buffer> buffer)
|
||||
{
|
||||
if (!Jtk_Init())
|
||||
{
|
||||
std::cerr << "Error initializing GUI" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_window = Jtk_CreateWindow();
|
||||
if (m_window == NULL)
|
||||
{
|
||||
std::cerr << "Error creating window" << std::endl;
|
||||
return false;
|
||||
}
|
||||
Jtk_SetWindowTitle(m_window, "jes");
|
||||
|
||||
set_window_icon();
|
||||
|
||||
if (!Initialize_OpenGL())
|
||||
{
|
||||
std::cerr << "Error initializing OpenGL" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_exit_requested = false;
|
||||
|
||||
/* TODO: user configurable font, size */
|
||||
std::string font_path = Runtime::find(Runtime::FONT, "DejaVuSansMono");
|
||||
if (font_path == "")
|
||||
{
|
||||
std::cerr << "Unable to locate font" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_font = std::make_shared<Font>();
|
||||
if (!m_font->load(font_path.c_str(), FONT_SIZE))
|
||||
{
|
||||
std::cerr << "Error loading font" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_gl = std::make_shared<GL>();
|
||||
|
||||
m_target_column = 0u; /* TODO: fix */
|
||||
|
||||
glClearColor (0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
m_buffer_pane = std::make_shared<BufferPane>(this, buffer);
|
||||
m_focused_buffer_pane = m_buffer_pane;
|
||||
m_buffer_pane->set_focused(true);
|
||||
m_command_buffer = std::make_shared<Buffer>();
|
||||
m_command_buffer_pane = std::make_shared<BufferPane>(this, m_command_buffer);
|
||||
m_command_buffer_pane->set_command_mode();
|
||||
m_command_buffer_screen_rows = 1;
|
||||
m_command_invalid = false;
|
||||
m_status_message_timer = (size_t)-1;
|
||||
|
||||
resize(INITIAL_WIDTH, INITIAL_HEIGHT);
|
||||
|
||||
m_redraw_requested = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the Window event loop.
|
||||
*
|
||||
* TODO: make this a static method to support multiple GUI windows.
|
||||
*/
|
||||
void Window::run_event_loop()
|
||||
{
|
||||
Jtk_Event event;
|
||||
|
||||
while (!m_exit_requested)
|
||||
{
|
||||
if (m_redraw_requested)
|
||||
{
|
||||
m_redraw_requested = false;
|
||||
redraw();
|
||||
}
|
||||
Jtk_WaitEvent(&event);
|
||||
uint64_t event_time = Jtk_UsTime();
|
||||
handle_event(event);
|
||||
while (Jtk_CheckEvent(&event) &&
|
||||
((Jtk_UsTime() - event_time) < MAX_EVENT_PROCESS_TIME))
|
||||
{
|
||||
handle_event(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a Jtk event.
|
||||
*/
|
||||
void Window::handle_event(Jtk_Event & event)
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case JTK_EVENT_WINDOW_CLOSE:
|
||||
m_exit_requested = true;
|
||||
break;
|
||||
|
||||
case JTK_EVENT_KEY_PRESS:
|
||||
#if 0
|
||||
if (!event.key.repeat)
|
||||
{
|
||||
Jtk_BeginKeyRepeat(&event.key, 300u, 25u);
|
||||
}
|
||||
#endif
|
||||
handle_keypress(event.key.key);
|
||||
m_redraw_requested = true;
|
||||
break;
|
||||
|
||||
case JTK_EVENT_WINDOW_EXPOSE:
|
||||
m_redraw_requested = true;
|
||||
break;
|
||||
|
||||
case JTK_EVENT_WINDOW_RESIZE:
|
||||
resize(event.resize.width, event.resize.height);
|
||||
m_redraw_requested = true;
|
||||
break;
|
||||
|
||||
case JTK_EVENT_BUTTON_PRESS:
|
||||
if (event.button.button == 4)
|
||||
{
|
||||
m_focused_buffer_pane->scroll_window_up(ScrollMode::WHEEL);
|
||||
}
|
||||
else if (event.button.button == 5)
|
||||
{
|
||||
m_focused_buffer_pane->scroll_window_down(ScrollMode::WHEEL);
|
||||
}
|
||||
break;
|
||||
|
||||
case JTK_EVENT_TIMER:
|
||||
if (event.timer.timer_id == m_status_message_timer)
|
||||
{
|
||||
handle_status_message_timer();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Window::handle_keypress(uint32_t keyval)
|
||||
{
|
||||
uint32_t ctrl_keyval = keyval & (JES_KEY_KEYCODE_MASK | JES_KEY_MODS_CTRL);
|
||||
uint32_t keycode = keyval & JES_KEY_KEYCODE_MASK;
|
||||
if ((JES_KEY_SHIFT_L <= keycode) && (keycode <= JES_KEY_SUPER_R))
|
||||
{
|
||||
/* Ignore modifier key presses. */
|
||||
}
|
||||
else if (m_focused_buffer_pane->insert_mode())
|
||||
{
|
||||
if (keycode == '\033')
|
||||
{
|
||||
if (m_focused_buffer_pane == m_command_buffer_pane)
|
||||
{
|
||||
m_command_buffer_pane->clear();
|
||||
m_command_buffer_screen_rows = 1;
|
||||
change_focus(m_buffer_pane);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_focused_buffer_pane->exit_insert_mode();
|
||||
}
|
||||
}
|
||||
else if ((keycode == '\n') && (m_focused_buffer_pane == m_command_buffer_pane))
|
||||
{
|
||||
EncodedString command = m_command_buffer->get_string();
|
||||
m_command_buffer_pane->clear();
|
||||
m_command_buffer_screen_rows = 1;
|
||||
change_focus(m_buffer_pane);
|
||||
handle_command(command);
|
||||
}
|
||||
else if (check_insert_mode_command(ctrl_keyval))
|
||||
{
|
||||
/* Command was handled, don't do anything else. */
|
||||
}
|
||||
else if ((ctrl_keyval & JES_KEY_MODS_CTRL) == 0u)
|
||||
{
|
||||
m_focused_buffer_pane->insert_code_point(ctrl_keyval);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((keycode == '\033') || (keycode == '\b'))
|
||||
{
|
||||
m_command_input.clear();
|
||||
m_command_invalid = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
clear_status();
|
||||
m_command_input.push_back(ctrl_keyval);
|
||||
evaluate_command_input();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Window::evaluate_command_input()
|
||||
{
|
||||
auto cm = DefaultCommandMap::get();
|
||||
Command command;
|
||||
switch (cm->lookup_command(&m_command_input[0], m_command_input.size(), command))
|
||||
{
|
||||
case CommandMap::COMMAND_INVALID:
|
||||
m_command_invalid = true;
|
||||
break;
|
||||
case CommandMap::COMMAND_INCOMPLETE:
|
||||
break;
|
||||
case CommandMap::COMMAND_COMPLETE:
|
||||
execute_command(command);
|
||||
m_command_input.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool Window::check_insert_mode_command(uint32_t insert_mode_command)
|
||||
{
|
||||
auto cm = DefaultCommandMap::get_insert_mode();
|
||||
Command command;
|
||||
if (cm->lookup_command(&insert_mode_command, 1u, command) == CommandMap::COMMAND_COMPLETE)
|
||||
{
|
||||
execute_command(command);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Window::execute_command(const Command & command)
|
||||
{
|
||||
switch (command.main.id)
|
||||
{
|
||||
case Command::GO_FORWARD_UP_TO_CHAR:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::FORWARD_UP_TO_CHAR, command.main.following_char);
|
||||
break;
|
||||
case Command::GO_FORWARD_ON_TO_CHAR:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::FORWARD_ON_TO_CHAR, command.main.following_char);
|
||||
break;
|
||||
case Command::GO_BACK_UP_TO_CHAR:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::BACK_UP_TO_CHAR, command.main.following_char);
|
||||
break;
|
||||
case Command::GO_BACK_ON_TO_CHAR:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::BACK_ON_TO_CHAR, command.main.following_char);
|
||||
break;
|
||||
case Command::DELETE_MOTION:
|
||||
m_focused_buffer_pane->delete_motion(command.motion);
|
||||
break;
|
||||
case Command::DELETE_LINE:
|
||||
{
|
||||
Command::Unit motion = command.motion;
|
||||
motion.id = Command::Motion::THIS_LINE;
|
||||
m_focused_buffer_pane->delete_motion(motion);
|
||||
}
|
||||
break;
|
||||
case Command::DELETE_CHAR:
|
||||
m_focused_buffer_pane->kill_character_forward();
|
||||
break;
|
||||
case Command::DELETE_CHAR_BACK:
|
||||
m_focused_buffer_pane->kill_character_backward();
|
||||
break;
|
||||
case Command::CHANGE_MOTION:
|
||||
m_focused_buffer_pane->change_motion(command.motion);
|
||||
break;
|
||||
case Command::CHANGE_LINE:
|
||||
{
|
||||
Command::Unit motion = command.motion;
|
||||
motion.id = Command::Motion::THIS_LINE;
|
||||
m_focused_buffer_pane->change_motion(motion);
|
||||
}
|
||||
break;
|
||||
case Command::YANK_MOTION:
|
||||
break;
|
||||
case Command::YANK_LINE:
|
||||
break;
|
||||
case Command::UNDO:
|
||||
m_focused_buffer_pane->undo();
|
||||
break;
|
||||
case Command::REDO:
|
||||
m_focused_buffer_pane->redo();
|
||||
break;
|
||||
case Command::ENTER_INSERT_MODE:
|
||||
m_focused_buffer_pane->enter_insert_mode(EnterInsertModeMode::START_OF_CHAR);
|
||||
break;
|
||||
case Command::ENTER_INSERT_MODE_AFTER_CHAR:
|
||||
m_focused_buffer_pane->enter_insert_mode(EnterInsertModeMode::END_OF_CHAR);
|
||||
break;
|
||||
case Command::ENTER_INSERT_MODE_AFTER_LINE:
|
||||
m_focused_buffer_pane->enter_insert_mode(EnterInsertModeMode::END_OF_LINE);
|
||||
break;
|
||||
case Command::ENTER_INSERT_MODE_START_OF_LINE:
|
||||
m_focused_buffer_pane->enter_insert_mode(EnterInsertModeMode::START_OF_LINE);
|
||||
break;
|
||||
case Command::ENTER_INSERT_MODE_NEW_LINE_BEFORE:
|
||||
m_focused_buffer_pane->enter_insert_mode(EnterInsertModeMode::NEW_LINE_BEFORE);
|
||||
break;
|
||||
case Command::ENTER_INSERT_MODE_NEW_LINE_AFTER:
|
||||
m_focused_buffer_pane->enter_insert_mode(EnterInsertModeMode::NEW_LINE_AFTER);
|
||||
break;
|
||||
case Command::PUT:
|
||||
break;
|
||||
case Command::PUT_BEFORE:
|
||||
break;
|
||||
case Command::CHANGE_CHAR:
|
||||
break;
|
||||
case Command::GO_START_OF_FILE:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::START_OF_FILE, 0u);
|
||||
break;
|
||||
case Command::GO_END_OF_FILE:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::END_OF_FILE, 0u);
|
||||
break;
|
||||
case Command::GO_TO_LINE:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::FIRST_LINE, 0u);
|
||||
break;
|
||||
case Command::GO_TO_LAST_LINE:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::LAST_LINE, 0u);
|
||||
break;
|
||||
case Command::GO_LEFT:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::LEFT, 0u);
|
||||
break;
|
||||
case Command::GO_DOWN:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::DOWN, 0u);
|
||||
break;
|
||||
case Command::GO_UP:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::UP, 0u);
|
||||
break;
|
||||
case Command::GO_RIGHT:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::RIGHT, 0u);
|
||||
break;
|
||||
case Command::GO_START_OF_LINE:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::SOL, 0u);
|
||||
break;
|
||||
case Command::GO_END_OF_LINE:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::EOL, 0u);
|
||||
break;
|
||||
case Command::GO_TOP_OF_SCREEN:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::TOP_OF_SCREEN, 0u);
|
||||
break;
|
||||
case Command::GO_MIDDLE_OF_SCREEN:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::MIDDLE_OF_SCREEN, 0u);
|
||||
break;
|
||||
case Command::GO_BOTTOM_OF_SCREEN:
|
||||
m_focused_buffer_pane->cursor_move(BufferPane::CursorMovement::BOTTOM_OF_SCREEN, 0u);
|
||||
break;
|
||||
case Command::NEXT:
|
||||
break;
|
||||
case Command::PREV:
|
||||
break;
|
||||
case Command::SCROLL_WINDOW_UP_ONE_LINE:
|
||||
m_focused_buffer_pane->scroll_window_up(ScrollMode::ONE_LINE);
|
||||
break;
|
||||
case Command::SCROLL_WINDOW_DOWN_ONE_LINE:
|
||||
m_focused_buffer_pane->scroll_window_down(ScrollMode::ONE_LINE);
|
||||
break;
|
||||
case Command::SCROLL_WINDOW_UP_HALF_SCREEN:
|
||||
m_focused_buffer_pane->scroll_window_up(ScrollMode::HALF_SCREEN);
|
||||
break;
|
||||
case Command::SCROLL_WINDOW_DOWN_HALF_SCREEN:
|
||||
m_focused_buffer_pane->scroll_window_down(ScrollMode::HALF_SCREEN);
|
||||
break;
|
||||
case Command::SCROLL_WINDOW_UP_WHOLE_SCREEN:
|
||||
m_focused_buffer_pane->scroll_window_up(ScrollMode::WHOLE_SCREEN);
|
||||
break;
|
||||
case Command::SCROLL_WINDOW_DOWN_WHOLE_SCREEN:
|
||||
m_focused_buffer_pane->scroll_window_down(ScrollMode::WHOLE_SCREEN);
|
||||
break;
|
||||
case Command::ENTER_COMMAND_LINE_PROMPT:
|
||||
m_command_buffer_pane->clear();
|
||||
change_focus(m_command_buffer_pane);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Window::resize(size_t width, size_t height)
|
||||
{
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
glViewport(0, 0, m_width, m_height);
|
||||
m_gl->resize(m_width, m_height);
|
||||
int command_buffer_height = m_command_buffer_screen_rows * m_font->get_line_height();
|
||||
m_buffer_pane->move(0, command_buffer_height + 1);
|
||||
m_buffer_pane->resize(m_width, m_height - command_buffer_height - 1);
|
||||
m_command_buffer_pane->resize(m_width, command_buffer_height);
|
||||
m_command_buffer_pane->move(0, 0);
|
||||
}
|
||||
|
||||
void Window::redraw()
|
||||
{
|
||||
static int last_command_buffer_screen_rows = 0;
|
||||
/* TODO: figure out number of command buffer screen rows. */
|
||||
m_command_buffer_screen_rows = 1;
|
||||
if (m_command_buffer_screen_rows != last_command_buffer_screen_rows)
|
||||
{
|
||||
resize(m_width, m_height);
|
||||
}
|
||||
last_command_buffer_screen_rows = m_command_buffer_screen_rows;
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
m_buffer_pane->draw();
|
||||
m_gl->draw_rect(0, m_command_buffer_screen_rows * m_font->get_line_height(), m_width, 1, 0.5, 0.5, 0.5, 1.0);
|
||||
if (m_focused_buffer_pane->insert_mode())
|
||||
{
|
||||
m_command_buffer_pane->draw();
|
||||
}
|
||||
else if (m_status_message_timer != (size_t)-1)
|
||||
{
|
||||
draw_status_message();
|
||||
}
|
||||
else
|
||||
{
|
||||
draw_command_input();
|
||||
}
|
||||
|
||||
Jtk_SwapBuffers(m_window);
|
||||
}
|
||||
|
||||
void Window::draw_status_message()
|
||||
{
|
||||
int x = 0;
|
||||
uint64_t current_time = Jtk_UsTime();
|
||||
float alpha, g, b;
|
||||
if (m_status_error)
|
||||
{
|
||||
g = 0.0;
|
||||
b = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
g = 1.0;
|
||||
b = 1.0;
|
||||
}
|
||||
if (((current_time - m_status_message_basetime) / 1000) < (STATUS_TIME / 2))
|
||||
{
|
||||
alpha = 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
alpha = 1.0 - (((current_time - m_status_message_basetime) / 1000) - (STATUS_TIME / 2)) / (float)(STATUS_TIME / 2);
|
||||
if (alpha < 0.0)
|
||||
{
|
||||
alpha = 0.0;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0u, length = m_status_message.size(); i < length; i++)
|
||||
{
|
||||
m_gl->draw_character(x, 0, m_status_message[i], *m_font, 1.0, g, b, alpha);
|
||||
x += m_font->get_advance();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::handle_status_message_timer()
|
||||
{
|
||||
if (((Jtk_UsTime() - m_status_message_basetime) / 1000) >= STATUS_TIME)
|
||||
{
|
||||
Jtk_RemoveTimer(m_status_message_timer);
|
||||
m_status_message_timer = (size_t)-1;
|
||||
}
|
||||
m_redraw_requested = true;
|
||||
}
|
||||
|
||||
void Window::draw_command_input()
|
||||
{
|
||||
int x = 0;
|
||||
float g, b;
|
||||
if (m_command_invalid)
|
||||
{
|
||||
g = 0.0;
|
||||
b = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
g = 1.0;
|
||||
b = 1.0;
|
||||
}
|
||||
for (size_t i = 0u, length = m_command_input.size(); i < length; i++)
|
||||
{
|
||||
m_gl->draw_character(x, 0, m_command_input[i], *m_font, 1.0, g, b, 1.0);
|
||||
x += m_font->get_advance();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::change_focus(std::shared_ptr<BufferPane> buffer_pane)
|
||||
{
|
||||
m_focused_buffer_pane->set_focused(false);
|
||||
m_focused_buffer_pane = buffer_pane;
|
||||
m_focused_buffer_pane->set_focused(true);
|
||||
request_redraw();
|
||||
}
|
||||
|
||||
static bool is_number(const EncodedString & s)
|
||||
{
|
||||
size_t size = s.size();
|
||||
if (size < 1u)
|
||||
return false;
|
||||
for (size_t i = 0u; i < size; i++)
|
||||
{
|
||||
if ((s[i] < '0') || (s[i] > '9'))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Window::set_status(const std::string & status, bool error)
|
||||
{
|
||||
clear_status();
|
||||
m_status_error = error;
|
||||
m_status_message = status;
|
||||
m_status_message_basetime = Jtk_UsTime();
|
||||
m_status_message_timer = Jtk_AddTimer(STATUS_TIME / 2, 50u, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void Window::clear_status()
|
||||
{
|
||||
if (m_status_message_timer != (size_t)-1)
|
||||
{
|
||||
Jtk_RemoveTimer(m_status_message_timer);
|
||||
m_status_message_timer = (size_t)-1;
|
||||
}
|
||||
}
|
||||
|
||||
void Window::handle_command(const EncodedString & command)
|
||||
{
|
||||
CommandParser cp;
|
||||
if (cp.parse(command))
|
||||
{
|
||||
if (cp.size() >= 1)
|
||||
{
|
||||
if (cp[0] == "w")
|
||||
{
|
||||
command_write_file(cp);
|
||||
}
|
||||
else if (cp[0] == "q")
|
||||
{
|
||||
command_quit(cp);
|
||||
}
|
||||
else if (is_number(cp[0]))
|
||||
{
|
||||
size_t target_line = (size_t)atoi((const char *)&cp[0][0]);
|
||||
if (target_line != 0u)
|
||||
{
|
||||
target_line--;
|
||||
}
|
||||
m_focused_buffer_pane->cursor_move_to_line(target_line);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO: handle command parsing failure */
|
||||
}
|
||||
}
|
||||
|
||||
void Window::command_write_file(const CommandParser & cp)
|
||||
{
|
||||
std::string path;
|
||||
std::shared_ptr<Buffer> buffer = m_focused_buffer_pane->buffer();
|
||||
auto filename = buffer->filename();
|
||||
if (cp.size() >= 2)
|
||||
{
|
||||
path = cp[1].to_string();
|
||||
if (!filename)
|
||||
{
|
||||
buffer->set_filename(path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (filename)
|
||||
{
|
||||
path = *filename;
|
||||
}
|
||||
}
|
||||
if (path != "")
|
||||
{
|
||||
size_t n_lines;
|
||||
size_t n_bytes;
|
||||
if (buffer->write_to_file(Path::clean(path).c_str(), &n_lines, &n_bytes))
|
||||
{
|
||||
char msg[100];
|
||||
sprintf(msg, "Wrote file: %u lines, %u bytes",
|
||||
(unsigned int)n_lines, (unsigned int)n_bytes);
|
||||
set_status(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
set_status("Error writing file", true);
|
||||
}
|
||||
}
|
||||
m_redraw_requested = true;
|
||||
}
|
||||
|
||||
void Window::command_quit(const CommandParser & cp)
|
||||
{
|
||||
m_exit_requested = true;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#ifndef WINDOW_H
|
||||
#define WINDOW_H
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "Font.h"
|
||||
#include "Buffer.h"
|
||||
#include "GL.h"
|
||||
#include "EncodedString.h"
|
||||
#include "CommandParser.h"
|
||||
#include "Jtk.h"
|
||||
#include "Command.h"
|
||||
|
||||
class BufferPane;
|
||||
|
||||
class Window
|
||||
{
|
||||
public:
|
||||
enum class ScrollMode : uint8_t
|
||||
{
|
||||
ONE_LINE,
|
||||
WHEEL,
|
||||
HALF_SCREEN,
|
||||
WHOLE_SCREEN,
|
||||
};
|
||||
|
||||
enum class EnterInsertModeMode : uint8_t
|
||||
{
|
||||
START_OF_CHAR,
|
||||
END_OF_CHAR,
|
||||
START_OF_LINE,
|
||||
END_OF_LINE,
|
||||
NEW_LINE_BEFORE,
|
||||
NEW_LINE_AFTER,
|
||||
};
|
||||
|
||||
bool create(std::shared_ptr<Buffer> buffer);
|
||||
void run_event_loop();
|
||||
void request_redraw() { m_redraw_requested = true; }
|
||||
std::shared_ptr<Font> font() const { return m_font; }
|
||||
std::shared_ptr<GL> gl() const { return m_gl; }
|
||||
|
||||
protected:
|
||||
void resize(size_t width, size_t height);
|
||||
void redraw();
|
||||
void handle_event(Jtk_Event & event);
|
||||
void handle_keypress(uint32_t keyval);
|
||||
void change_focus(std::shared_ptr<BufferPane> buffer_pane);
|
||||
void set_window_icon();
|
||||
void handle_command(const EncodedString & command);
|
||||
void evaluate_command_input();
|
||||
bool check_insert_mode_command(uint32_t insert_mode_command);
|
||||
void execute_command(const Command & command);
|
||||
void handle_status_message_timer();
|
||||
void draw_command_input();
|
||||
void draw_status_message();
|
||||
|
||||
void command_write_file(const CommandParser & cp);
|
||||
void command_quit(const CommandParser & cp);
|
||||
void set_status(const std::string & status, bool error = false);
|
||||
void clear_status();
|
||||
|
||||
void * m_window;
|
||||
bool m_exit_requested;
|
||||
bool m_redraw_requested;
|
||||
int m_width;
|
||||
int m_height;
|
||||
int m_command_buffer_screen_rows;
|
||||
|
||||
std::shared_ptr<Font> m_font;
|
||||
uint32_t m_target_column;
|
||||
|
||||
std::shared_ptr<GL> m_gl;
|
||||
|
||||
std::shared_ptr<BufferPane> m_buffer_pane;
|
||||
std::shared_ptr<BufferPane> m_focused_buffer_pane;
|
||||
std::shared_ptr<Buffer> m_command_buffer;
|
||||
std::shared_ptr<BufferPane> m_command_buffer_pane;
|
||||
std::vector<uint32_t> m_command_input;
|
||||
std::string m_status_message;
|
||||
size_t m_status_message_timer;
|
||||
uint64_t m_status_message_basetime;
|
||||
bool m_status_error;
|
||||
bool m_command_invalid;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "FlatShader.h"
|
||||
#include "Runtime.h"
|
||||
|
||||
FlatShader::FlatShader()
|
||||
{
|
||||
std::string v_path = Runtime::find(Runtime::SHADER, "flat.v");
|
||||
std::string f_path = Runtime::find(Runtime::SHADER, "flat.f");
|
||||
|
||||
m_program = glcxx::Program::create(
|
||||
glcxx::Shader::create_from_file(GL_VERTEX_SHADER, v_path.c_str()),
|
||||
glcxx::Shader::create_from_file(GL_FRAGMENT_SHADER, f_path.c_str()),
|
||||
"coords", 0);
|
||||
|
||||
m_uniforms.viewport_size = m_program->get_uniform_location("viewport_size");
|
||||
m_uniforms.position = m_program->get_uniform_location("position");
|
||||
m_uniforms.color = m_program->get_uniform_location("color");
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef FLATSHADER_H
|
||||
#define FLATSHADER_H
|
||||
|
||||
#include "glcxx.hpp"
|
||||
#include <memory>
|
||||
|
||||
class FlatShader
|
||||
{
|
||||
public:
|
||||
FlatShader();
|
||||
|
||||
void use() { m_program->use(); }
|
||||
|
||||
void set_viewport_size(int width, int height)
|
||||
{
|
||||
glUniform2i(m_uniforms.viewport_size, width, height);
|
||||
}
|
||||
|
||||
void set_color(float r, float g, float b, float a)
|
||||
{
|
||||
glUniform4f(m_uniforms.color, r, g, b, a);
|
||||
}
|
||||
|
||||
void set_position(int x, int y)
|
||||
{
|
||||
glUniform2i(m_uniforms.position, x, y);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<glcxx::Program> m_program;
|
||||
|
||||
struct
|
||||
{
|
||||
GLint viewport_size;
|
||||
GLint color;
|
||||
GLint position;
|
||||
} m_uniforms;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "GL.h"
|
||||
|
||||
GL::GL()
|
||||
{
|
||||
m_shaders.text = std::make_shared<TextShader>();
|
||||
m_shaders.text->use();
|
||||
m_shaders.text->set_texture(0);
|
||||
m_shaders.text->set_color(1.0, 1.0, 1.0, 1.0);
|
||||
|
||||
m_shaders.rect = std::make_shared<RectShader>();
|
||||
|
||||
GLint rect_coords[] = {
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0, 1};
|
||||
m_rect_array = glcxx::Array::create();
|
||||
m_rect_array->bind();
|
||||
m_rect_buffer = glcxx::Buffer::create(GL_ARRAY_BUFFER, GL_STATIC_DRAW,
|
||||
rect_coords, sizeof(rect_coords));
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 2, GL_INT, GL_FALSE, 0, 0);
|
||||
}
|
||||
|
||||
void GL::draw_rect(int x, int y, int width, int height,
|
||||
float r, float g, float b, float a)
|
||||
{
|
||||
m_rect_array->bind();
|
||||
m_shaders.rect->use();
|
||||
m_shaders.rect->set_color(r, g, b, a);
|
||||
m_shaders.rect->set_position(x, y);
|
||||
m_shaders.rect->set_size(width, height);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
}
|
||||
|
||||
void GL::draw_character(int x, int y, uint32_t character, Font & font,
|
||||
float r, float g, float b, float a)
|
||||
{
|
||||
m_shaders.text->use();
|
||||
m_shaders.text->set_color(r, g, b, a);
|
||||
m_shaders.text->set_position(x, y + font.get_baseline_offset());
|
||||
font.get_glyph(character)->render();
|
||||
}
|
||||
|
||||
void GL::resize(int width, int height)
|
||||
{
|
||||
m_shaders.text->use();
|
||||
m_shaders.text->set_viewport_size(width, height);
|
||||
m_shaders.rect->use();
|
||||
m_shaders.rect->set_viewport_size(width, height);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef GL_H
|
||||
#define GL_H
|
||||
|
||||
#include "glcxx.hpp"
|
||||
#include "TextShader.h"
|
||||
#include "FlatShader.h"
|
||||
#include "RectShader.h"
|
||||
#include "Font.h"
|
||||
#include <memory>
|
||||
|
||||
class GL
|
||||
{
|
||||
public:
|
||||
GL();
|
||||
|
||||
void draw_rect(int x, int y, int width, int height,
|
||||
float r, float g, float b, float a);
|
||||
void draw_character(int x, int y, uint32_t character, Font & font,
|
||||
float r, float g, float b, float a);
|
||||
void resize(int width, int height);
|
||||
|
||||
protected:
|
||||
struct
|
||||
{
|
||||
std::shared_ptr<TextShader> text;
|
||||
std::shared_ptr<FlatShader> flat;
|
||||
std::shared_ptr<RectShader> rect;
|
||||
} m_shaders;
|
||||
|
||||
std::shared_ptr<glcxx::Array> m_rect_array;
|
||||
std::shared_ptr<glcxx::Buffer> m_rect_buffer;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "RectShader.h"
|
||||
#include "Runtime.h"
|
||||
|
||||
RectShader::RectShader()
|
||||
{
|
||||
std::string v_path = Runtime::find(Runtime::SHADER, "rect.v");
|
||||
std::string f_path = Runtime::find(Runtime::SHADER, "rect.f");
|
||||
|
||||
m_program = glcxx::Program::create(
|
||||
glcxx::Shader::create_from_file(GL_VERTEX_SHADER, v_path.c_str()),
|
||||
glcxx::Shader::create_from_file(GL_FRAGMENT_SHADER, f_path.c_str()),
|
||||
"coords", 0);
|
||||
|
||||
m_uniforms.viewport_size = m_program->get_uniform_location("viewport_size");
|
||||
m_uniforms.color = m_program->get_uniform_location("color");
|
||||
m_uniforms.position = m_program->get_uniform_location("position");
|
||||
m_uniforms.size = m_program->get_uniform_location("size");
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef RECTSHADER_H
|
||||
#define RECTSHADER_H
|
||||
|
||||
#include "glcxx.hpp"
|
||||
#include <memory>
|
||||
|
||||
class RectShader
|
||||
{
|
||||
public:
|
||||
RectShader();
|
||||
|
||||
void use() { m_program->use(); }
|
||||
|
||||
void set_viewport_size(int width, int height)
|
||||
{
|
||||
glUniform2i(m_uniforms.viewport_size, width, height);
|
||||
}
|
||||
|
||||
void set_color(float r, float g, float b, float a)
|
||||
{
|
||||
glUniform4f(m_uniforms.color, r, g, b, a);
|
||||
}
|
||||
|
||||
void set_position(int x, int y)
|
||||
{
|
||||
glUniform2i(m_uniforms.position, x, y);
|
||||
}
|
||||
|
||||
void set_size(int x, int y)
|
||||
{
|
||||
glUniform2i(m_uniforms.size, x, y);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<glcxx::Program> m_program;
|
||||
|
||||
struct
|
||||
{
|
||||
GLint viewport_size;
|
||||
GLint color;
|
||||
GLint position;
|
||||
GLint size;
|
||||
} m_uniforms;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "TextShader.h"
|
||||
#include "Runtime.h"
|
||||
|
||||
TextShader::TextShader()
|
||||
{
|
||||
std::string v_path = Runtime::find(Runtime::SHADER, "text.v");
|
||||
std::string f_path = Runtime::find(Runtime::SHADER, "text.f");
|
||||
|
||||
m_program = glcxx::Program::create(
|
||||
glcxx::Shader::create_from_file(GL_VERTEX_SHADER, v_path.c_str()),
|
||||
glcxx::Shader::create_from_file(GL_FRAGMENT_SHADER, f_path.c_str()),
|
||||
"coords", 0);
|
||||
|
||||
m_uniforms.viewport_size = m_program->get_uniform_location("viewport_size");
|
||||
m_uniforms.texture = m_program->get_uniform_location("texture");
|
||||
m_uniforms.color = m_program->get_uniform_location("color");
|
||||
m_uniforms.position = m_program->get_uniform_location("position");
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef TEXTSHADER_H
|
||||
#define TEXTSHADER_H
|
||||
|
||||
#include "glcxx.hpp"
|
||||
#include <memory>
|
||||
|
||||
class TextShader
|
||||
{
|
||||
public:
|
||||
TextShader();
|
||||
|
||||
void use() { m_program->use(); }
|
||||
|
||||
void set_viewport_size(int width, int height)
|
||||
{
|
||||
glUniform2i(m_uniforms.viewport_size, width, height);
|
||||
}
|
||||
|
||||
void set_texture(int texture)
|
||||
{
|
||||
glUniform1i(m_uniforms.texture, texture);
|
||||
}
|
||||
|
||||
void set_color(float r, float g, float b, float a)
|
||||
{
|
||||
glUniform4f(m_uniforms.color, r, g, b, a);
|
||||
}
|
||||
|
||||
void set_position(int x, int y)
|
||||
{
|
||||
glUniform2i(m_uniforms.position, x, y);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<glcxx::Program> m_program;
|
||||
|
||||
struct
|
||||
{
|
||||
GLint viewport_size;
|
||||
GLint texture;
|
||||
GLint color;
|
||||
GLint position;
|
||||
} m_uniforms;
|
||||
};
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,99 @@
|
||||
#include "jes_icon-32x32.h"
|
||||
|
||||
/* GIMP RGB C-Source image dump (jes_icon-32x32.c) */
|
||||
|
||||
const uint8_t jes_icon_32x32[] = {
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\377\063\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\063\377\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000PPP\377\377\377\040\040\040\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\063\377\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000PPP\377\377\377\040\040\040\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\063\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\377\063\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\310\310\310\377\377\377"
|
||||
"\377\377\377\377\377\377\040\040\040\000\000\000\000\000\000\000\000\000***\261\261\261\361\361"
|
||||
"\361\365\365\365\265\265\265\"\"\"\000\000\000\000\000\000\000\000\000\000\000\000;;;\300\300\300"
|
||||
"\360\360\360\347\347\347\240\240\240\033\033\033\000\000\000\000\000\000\377\063\377\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000PPP\377\377\377\040\040\040\000\000\000\000\000"
|
||||
"\000&&&\360\360\360\252\252\252\035\035\035\030\030\030\247\247\247\334\334\334\005"
|
||||
"\005\005\000\000\000\000\000\000\022\022\022\360\360\360\221\221\221\022\022\022\025\025\025[[[\177"
|
||||
"\177\177\000\000\000\000\000\000\377\063\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"PPP\377\377\377\040\040\040\000\000\000\000\000\000\252\252\252\344\344\344\010\010\010\000\000"
|
||||
"\000\000\000\000\017\017\017\366\366\366```\000\000\000\000\000\000@@@\377\377\377\064\064\064\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\063\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000PPP\377\377\377\040\040\040\000\000\000\000\000\000\357\357\357\236\236\236"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\320\320\320\222\222\222\000\000\000\000\000\000\034\034\034\372"
|
||||
"\372\372\277\277\277JJJ\017\017\017\000\000\000\000\000\000\000\000\000\000\000\000\377\063\377\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000PPP\377\377\377\040\040\040\000\000\000\000\000\000"
|
||||
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\377\377\377\255\255\255\000\000\000\000\000\000\000\000\000LLL\310\310\310\375\375\375\376"
|
||||
"\376\376\307\307\307\061\061\061\000\000\000\000\000\000\377\063\377\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000PPP\377\377\377\040\040\040\000\000\000\000\000\000\360\360\360\211"
|
||||
"\211\211\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\013\013\013RRR\342\342\342\334\334\334\000\000\000\000\000\000\377\063\377\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000PPP\377\377\377\040\040\040\000\000\000\000\000\000\255\255"
|
||||
"\255\312\312\312\001\001\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000vvv\372\372\372\003\003\003\000\000\000\377\063\377\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000PPP\377\377\377\040\040\040\000\000\000\000\000\000***\363"
|
||||
"\363\363\232\232\232\"\"\"\013\013\013\065\065\065\230\230\230TTT\000\000\000\000\000\000M"
|
||||
"MM\227\227\227\063\063\063\012\012\012)))\320\320\320\266\266\266\000\000\000\000\000\000\377"
|
||||
"\063\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000PPP\377\377\377\040\040\040"
|
||||
"\000\000\000\000\000\000\000\000\000+++\255\255\255\355\355\355\365\365\365\312\312\312iii"
|
||||
"\007\007\007\000\000\000\000\000\000\006\006\006hhh\314\314\314\365\365\365\343\343\343\230\230\230"
|
||||
"\023\023\023\000\000\000\000\000\000\377\063\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000^^^\377\377\377\016\016\016\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\377\063\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\016\016\016\274\274"
|
||||
"\274\316\316\316\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\377\063\377\000\000\000\000\000\000\000\000\000\000\000\000\214\214\214\377\377\377\371\371\371"
|
||||
"\306\306\306+++\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\377\063\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\063\377\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\063\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef JES_ICON_32X32_H
|
||||
#define JES_ICON_32X32_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
extern const uint8_t jes_icon_32x32[];
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,59 @@
|
||||
#include "Jtk.h"
|
||||
|
||||
#ifdef JTK_X
|
||||
|
||||
#include <GL/glx.h>
|
||||
#include <stdio.h>
|
||||
|
||||
Display * g_display;
|
||||
XVisualInfo * g_vi;
|
||||
XSetWindowAttributes g_swa;
|
||||
GLXContext g_context;
|
||||
|
||||
/**
|
||||
* Initialize the Jtk subsystem.
|
||||
*
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
bool Jtk_Init()
|
||||
{
|
||||
static int glx_attribute_list[] = {
|
||||
GLX_RGBA,
|
||||
GLX_DOUBLEBUFFER,
|
||||
None,
|
||||
};
|
||||
|
||||
g_display = XOpenDisplay(NULL);
|
||||
if (g_display == NULL)
|
||||
{
|
||||
fprintf(stderr, "XOpenDisplay() failure\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_vi = glXChooseVisual(g_display, DefaultScreen(g_display),
|
||||
glx_attribute_list);
|
||||
if (g_vi == NULL)
|
||||
{
|
||||
fprintf(stderr, "glXChooseVisual() failure\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_context = glXCreateContext(g_display, g_vi, NULL, True);
|
||||
if (g_context == NULL)
|
||||
{
|
||||
fprintf(stderr, "glXCreateContext() failure\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
Colormap colormap = XCreateColormap(g_display,
|
||||
RootWindow(g_display, g_vi->screen), g_vi->visual, AllocNone);
|
||||
g_swa.colormap = colormap;
|
||||
g_swa.border_pixel = 0;
|
||||
g_swa.event_mask = StructureNotifyMask |
|
||||
KeyPressMask | KeyReleaseMask |
|
||||
ButtonPressMask | ButtonReleaseMask;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef JTK_H
|
||||
#define JTK_H
|
||||
|
||||
#include "Jtk_event.h"
|
||||
#include "keys.h"
|
||||
#include "Jtk_time.h"
|
||||
#include "Jtk_timer.h"
|
||||
#include "Jtk_window.h"
|
||||
|
||||
bool Jtk_Init();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,482 @@
|
||||
#include "Jtk.h"
|
||||
|
||||
#ifdef JTK_X
|
||||
|
||||
#include <GL/glx.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include "Jtk_internal.h"
|
||||
#include <unordered_map>
|
||||
|
||||
/** Do not wait longer than 100ms */
|
||||
#define MAX_WAIT_TIME 100000u
|
||||
|
||||
static std::unordered_map<unsigned int, size_t> g_repeating_keys;
|
||||
static std::unordered_map<size_t, unsigned int> g_key_repeat_timers;
|
||||
|
||||
#if 0
|
||||
static void StopKeyRepeat(unsigned int x_keycode)
|
||||
{
|
||||
auto it = g_repeating_keys.find(x_keycode);
|
||||
if (it != g_repeating_keys.end())
|
||||
{
|
||||
size_t timer_id = it->second;
|
||||
Jtk_RemoveTimer(timer_id);
|
||||
g_repeating_keys.erase(x_keycode);
|
||||
g_key_repeat_timers.erase(timer_id);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
static unsigned int GetXState()
|
||||
{
|
||||
Window win;
|
||||
int i;
|
||||
unsigned int state;
|
||||
XQueryPointer(g_display, RootWindow(g_display, DefaultScreen(g_display)), &win, &win, &i, &i, &i, &i, &state);
|
||||
return state;
|
||||
}
|
||||
#endif
|
||||
|
||||
static uint32_t XStateToJtkKeyMods(unsigned int x_state)
|
||||
{
|
||||
uint32_t mods = 0u;
|
||||
/* OR in the modifier states */
|
||||
if (x_state & ShiftMask)
|
||||
{
|
||||
mods |= JES_KEY_MODS_SHIFT;
|
||||
}
|
||||
if (x_state & LockMask)
|
||||
{
|
||||
mods |= JES_KEY_MODS_LOCK;
|
||||
}
|
||||
if (x_state & ControlMask)
|
||||
{
|
||||
mods |= JES_KEY_MODS_CTRL;
|
||||
}
|
||||
return mods;
|
||||
}
|
||||
|
||||
static uint32_t XKeyToJtkKey(unsigned int x_keycode, unsigned int x_state)
|
||||
{
|
||||
XKeyEvent x_key_event;
|
||||
x_key_event.type = KeyPress;
|
||||
x_key_event.display = g_display;
|
||||
/* Turn off the ControlMask bit for looking up keys. We'll handle control
|
||||
* keys ourselves. */
|
||||
x_key_event.state = x_state & ~ControlMask;
|
||||
x_key_event.keycode = x_keycode;
|
||||
char buffer;
|
||||
KeySym keysym;
|
||||
uint32_t key = JES_KEY_UNKNOWN;
|
||||
|
||||
if (XLookupString(&x_key_event, &buffer, 1, &keysym, nullptr) > 0)
|
||||
{
|
||||
if (buffer == '\r')
|
||||
{
|
||||
key = '\n';
|
||||
}
|
||||
else
|
||||
{
|
||||
key = buffer;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (keysym)
|
||||
{
|
||||
case XK_F1: key = JES_KEY_F1; break;
|
||||
case XK_F2: key = JES_KEY_F2; break;
|
||||
case XK_F3: key = JES_KEY_F3; break;
|
||||
case XK_F4: key = JES_KEY_F4; break;
|
||||
case XK_F5: key = JES_KEY_F5; break;
|
||||
case XK_F6: key = JES_KEY_F6; break;
|
||||
case XK_F7: key = JES_KEY_F7; break;
|
||||
case XK_F8: key = JES_KEY_F8; break;
|
||||
case XK_F9: key = JES_KEY_F9; break;
|
||||
case XK_F10: key = JES_KEY_F10; break;
|
||||
case XK_F11: key = JES_KEY_F11; break;
|
||||
case XK_F12: key = JES_KEY_F12; break;
|
||||
case XK_F13: key = JES_KEY_F13; break;
|
||||
case XK_F14: key = JES_KEY_F14; break;
|
||||
case XK_F15: key = JES_KEY_F15; break;
|
||||
case XK_F16: key = JES_KEY_F16; break;
|
||||
case XK_F17: key = JES_KEY_F17; break;
|
||||
case XK_F18: key = JES_KEY_F18; break;
|
||||
case XK_F19: key = JES_KEY_F19; break;
|
||||
case XK_F20: key = JES_KEY_F20; break;
|
||||
case XK_F21: key = JES_KEY_F21; break;
|
||||
case XK_F22: key = JES_KEY_F22; break;
|
||||
case XK_F23: key = JES_KEY_F23; break;
|
||||
case XK_F24: key = JES_KEY_F24; break;
|
||||
case XK_F25: key = JES_KEY_F25; break;
|
||||
case XK_F26: key = JES_KEY_F26; break;
|
||||
case XK_F27: key = JES_KEY_F27; break;
|
||||
case XK_F28: key = JES_KEY_F28; break;
|
||||
case XK_F29: key = JES_KEY_F29; break;
|
||||
case XK_F30: key = JES_KEY_F30; break;
|
||||
case XK_F31: key = JES_KEY_F31; break;
|
||||
|
||||
case XK_Shift_L: key = JES_KEY_SHIFT_L; break;
|
||||
case XK_Shift_R: key = JES_KEY_SHIFT_R; break;
|
||||
case XK_Control_L: key = JES_KEY_CTRL_L; break;
|
||||
case XK_Control_R: key = JES_KEY_CTRL_R; break;
|
||||
case XK_Caps_Lock: key = JES_KEY_CAPS_LOCK; break;
|
||||
case XK_Shift_Lock: key = JES_KEY_SHIFT_LOCK;break;
|
||||
|
||||
case XK_Meta_L: key = JES_KEY_META_L; break;
|
||||
case XK_Meta_R: key = JES_KEY_META_R; break;
|
||||
case XK_Alt_L: key = JES_KEY_ALT_L; break;
|
||||
case XK_Alt_R: key = JES_KEY_ALT_R; break;
|
||||
case XK_Super_L: key = JES_KEY_SUPER_L; break;
|
||||
case XK_Super_R: key = JES_KEY_SUPER_R; break;
|
||||
|
||||
case XK_Home: key = JES_KEY_HOME; break;
|
||||
case XK_Left: key = JES_KEY_LEFT; break;
|
||||
case XK_Up: key = JES_KEY_UP; break;
|
||||
case XK_Right: key = JES_KEY_RIGHT; break;
|
||||
case XK_Down: key = JES_KEY_DOWN; break;
|
||||
case XK_Page_Up: key = JES_KEY_PAGE_UP; break;
|
||||
case XK_Page_Down: key = JES_KEY_PAGE_DOWN; break;
|
||||
case XK_End: key = JES_KEY_END; break;
|
||||
case XK_Begin: key = JES_KEY_BEGIN; break;
|
||||
|
||||
case XK_Select: key = JES_KEY_SELECT; break;
|
||||
case XK_Print: key = JES_KEY_PRINT; break;
|
||||
case XK_Execute: key = JES_KEY_EXECUTE; break;
|
||||
case XK_Insert: key = JES_KEY_INSERT; break;
|
||||
case XK_Undo: key = JES_KEY_UNDO; break;
|
||||
case XK_Redo: key = JES_KEY_REDO; break;
|
||||
case XK_Menu: key = JES_KEY_MENU; break;
|
||||
case XK_Find: key = JES_KEY_FIND; break;
|
||||
case XK_Cancel: key = JES_KEY_CANCEL; break;
|
||||
case XK_Help: key = JES_KEY_HELP; break;
|
||||
case XK_Break: key = JES_KEY_BREAK; break;
|
||||
case XK_Num_Lock: key = JES_KEY_NUM_LOCK; break;
|
||||
|
||||
case XK_KP_Space: key = JES_KEY_KP_SPACE; break;
|
||||
case XK_KP_Tab: key = JES_KEY_KP_TAB; break;
|
||||
case XK_KP_Enter: key = JES_KEY_KP_ENTER; break;
|
||||
case XK_KP_F1: key = JES_KEY_KP_F1; break;
|
||||
case XK_KP_F2: key = JES_KEY_KP_F2; break;
|
||||
case XK_KP_F3: key = JES_KEY_KP_F3; break;
|
||||
case XK_KP_F4: key = JES_KEY_KP_F4; break;
|
||||
case XK_KP_Home: key = JES_KEY_KP_HOME; break;
|
||||
case XK_KP_Left: key = JES_KEY_KP_LEFT; break;
|
||||
case XK_KP_Up: key = JES_KEY_KP_UP; break;
|
||||
case XK_KP_Right: key = JES_KEY_KP_RIGHT; break;
|
||||
case XK_KP_Down: key = JES_KEY_KP_DOWN; break;
|
||||
case XK_KP_Page_Up: key = JES_KEY_KP_PAGE_UP; break;
|
||||
case XK_KP_Page_Down: key = JES_KEY_KP_PAGE_DOWN; break;
|
||||
case XK_KP_End: key = JES_KEY_KP_END; break;
|
||||
case XK_KP_Begin: key = JES_KEY_KP_BEGIN; break;
|
||||
case XK_KP_Insert: key = JES_KEY_KP_INSERT; break;
|
||||
case XK_KP_Delete: key = JES_KEY_KP_DELETE; break;
|
||||
case XK_KP_Equal: key = JES_KEY_KP_EQUAL; break;
|
||||
case XK_KP_Multiply: key = JES_KEY_KP_MULTIPLY; break;
|
||||
case XK_KP_Add: key = JES_KEY_KP_ADD; break;
|
||||
case XK_KP_Separator: key = JES_KEY_KP_SEPARATOR; break;
|
||||
case XK_KP_Subtract: key = JES_KEY_KP_SUBTRACT; break;
|
||||
case XK_KP_Decimal: key = JES_KEY_KP_DECIMAL; break;
|
||||
case XK_KP_Divide: key = JES_KEY_KP_DIVIDE; break;
|
||||
|
||||
case XK_KP_0: key = JES_KEY_KP_0; break;
|
||||
case XK_KP_1: key = JES_KEY_KP_1; break;
|
||||
case XK_KP_2: key = JES_KEY_KP_2; break;
|
||||
case XK_KP_3: key = JES_KEY_KP_3; break;
|
||||
case XK_KP_4: key = JES_KEY_KP_4; break;
|
||||
case XK_KP_5: key = JES_KEY_KP_5; break;
|
||||
case XK_KP_6: key = JES_KEY_KP_6; break;
|
||||
case XK_KP_7: key = JES_KEY_KP_7; break;
|
||||
case XK_KP_8: key = JES_KEY_KP_8; break;
|
||||
case XK_KP_9: key = JES_KEY_KP_9; break;
|
||||
}
|
||||
}
|
||||
|
||||
/* OR in the modifier states */
|
||||
key |= XStateToJtkKeyMods(x_state);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static Bool KeyRepeatCheckIfEvent(Display * display, XEvent * chkev,
|
||||
XPointer arg)
|
||||
{
|
||||
XEvent * release_event = (XEvent *)arg;
|
||||
if (chkev->type == KeyPress &&
|
||||
chkev->xkey.keycode == release_event->xkey.keycode &&
|
||||
chkev->xkey.time - release_event->xkey.time < 2)
|
||||
return True;
|
||||
return False;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this is a repeated key.
|
||||
*/
|
||||
static bool IsRepeatKey(Display * display, XEvent * event)
|
||||
{
|
||||
XEvent dummyev;
|
||||
if (XPending(display))
|
||||
{
|
||||
if (XCheckIfEvent(display, &dummyev, KeyRepeatCheckIfEvent,
|
||||
(XPointer)event) == True)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
static Bool MatchKeyPress(Display * display, XEvent * event, XPointer arg)
|
||||
{
|
||||
XEvent * match_event = (XEvent *)arg;
|
||||
return (event->type == match_event->type) &&
|
||||
(event->xkey.window == match_event->xkey.window) &&
|
||||
(event->xkey.state == match_event->xkey.state) &&
|
||||
(event->xkey.keycode == match_event->xkey.keycode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an X key press event.
|
||||
*
|
||||
* @param x_event
|
||||
* Pointer to the X event.
|
||||
* @param event
|
||||
* Pointer to the Jtk event.
|
||||
*/
|
||||
static bool ProcessXKeyPressEvent(XEvent * x_event, Jtk_Event * event)
|
||||
{
|
||||
unsigned int x_keycode = x_event->xkey.keycode;
|
||||
event->type = JTK_EVENT_KEY_PRESS;
|
||||
event->key.repeat = false;
|
||||
event->key.key = XKeyToJtkKey(x_keycode, x_event->xkey.state);
|
||||
event->key.x_keycode = x_keycode;
|
||||
/* Remove any following keypress events for the same keycode from the X
|
||||
* queue. */
|
||||
XEvent remove_event;
|
||||
while (XCheckIfEvent(g_display, &remove_event, MatchKeyPress, (XPointer)x_event) == True)
|
||||
{
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an X key release event.
|
||||
*
|
||||
* @param x_event
|
||||
* Pointer to the X event.
|
||||
* @param event
|
||||
* Pointer to the Jtk event.
|
||||
*/
|
||||
static bool ProcessXKeyReleaseEvent(XEvent * x_event, Jtk_Event * event)
|
||||
{
|
||||
#if 0
|
||||
if (IsRepeatKey(g_display, x_event))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
StopKeyRepeat(x_event->xkey.keycode);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
static Bool MatchButtonPress(Display * display, XEvent * event, XPointer arg)
|
||||
{
|
||||
XEvent * match_event = (XEvent *)arg;
|
||||
return (event->type == match_event->type) &&
|
||||
(event->xbutton.window == match_event->xbutton.window) &&
|
||||
(event->xbutton.state == match_event->xbutton.state) &&
|
||||
(event->xbutton.button == match_event->xbutton.button);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an X button press event.
|
||||
*/
|
||||
static bool ProcessXButtonPressEvent(XEvent * x_event, Jtk_Event * event)
|
||||
{
|
||||
event->type = JTK_EVENT_BUTTON_PRESS;
|
||||
event->button.mods = XStateToJtkKeyMods(x_event->xbutton.state);
|
||||
event->button.button = x_event->xbutton.button;
|
||||
/* If this is a mouse wheel scroll event, remove any following scroll
|
||||
* events. */
|
||||
if ((event->button.button == 4) || (event->button.button == 5))
|
||||
{
|
||||
XEvent remove_event;
|
||||
while (XCheckIfEvent(g_display, &remove_event, MatchButtonPress, (XPointer)x_event) == True)
|
||||
{
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an X configure event.
|
||||
*
|
||||
* @param x_event
|
||||
* Pointer to the X event.
|
||||
* @param event
|
||||
* Pointer to the Jtk event.
|
||||
*/
|
||||
static bool ProcessConfigureEvent(XEvent * x_event, Jtk_Event * event)
|
||||
{
|
||||
event->type = JTK_EVENT_WINDOW_RESIZE;
|
||||
event->resize.width = x_event->xconfigure.width;
|
||||
event->resize.height = x_event->xconfigure.height;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an X ClientMessage event.
|
||||
*
|
||||
* @param x_event
|
||||
* Pointer to the X event.
|
||||
* @param event
|
||||
* Pointer to the Jtk event.
|
||||
*/
|
||||
static bool ProcessXClientMessageEvent(XEvent * x_event, Jtk_Event * event)
|
||||
{
|
||||
Atom wm_delete_window_atom = XInternAtom(g_display, "WM_DELETE_WINDOW", False);
|
||||
if (x_event->xclient.data.l[0] == (long)wm_delete_window_atom)
|
||||
{
|
||||
event->type = JTK_EVENT_WINDOW_CLOSE;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an X event.
|
||||
*
|
||||
* @param x_event
|
||||
* Pointer to the X event.
|
||||
* @param event
|
||||
* Pointer to the Jtk event.
|
||||
*
|
||||
* @retval true
|
||||
* The event should be passed to the user and event has been filled in.
|
||||
* @retval false
|
||||
* The event should not be passed to the user.
|
||||
*/
|
||||
static bool ProcessXEvent(XEvent * x_event, Jtk_Event * event)
|
||||
{
|
||||
switch (x_event->type)
|
||||
{
|
||||
case KeyPress:
|
||||
return ProcessXKeyPressEvent(x_event, event);
|
||||
|
||||
case KeyRelease:
|
||||
return ProcessXKeyReleaseEvent(x_event, event);
|
||||
|
||||
case ButtonPress:
|
||||
return ProcessXButtonPressEvent(x_event, event);
|
||||
|
||||
case ButtonRelease:
|
||||
break;
|
||||
|
||||
case Expose:
|
||||
event->type = JTK_EVENT_WINDOW_EXPOSE;
|
||||
return true;
|
||||
|
||||
case GraphicsExpose:
|
||||
event->type = JTK_EVENT_WINDOW_EXPOSE;
|
||||
return true;
|
||||
|
||||
case MapNotify:
|
||||
event->type = JTK_EVENT_WINDOW_EXPOSE;
|
||||
return true;
|
||||
|
||||
case ConfigureNotify:
|
||||
return ProcessConfigureEvent(x_event, event);
|
||||
|
||||
case ClientMessage:
|
||||
return ProcessXClientMessageEvent(x_event, event);
|
||||
|
||||
case MappingNotify:
|
||||
XRefreshKeyboardMapping(&x_event->xmapping);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Jtk_CheckEvent(Jtk_Event * event)
|
||||
{
|
||||
/* First check for an X event. */
|
||||
while (XPending(g_display) > 0)
|
||||
{
|
||||
XEvent x_event;
|
||||
XNextEvent(g_display, &x_event);
|
||||
if (ProcessXEvent(&x_event, event))
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Next check if any timer has expired. */
|
||||
size_t timer_id = Jtk_GetExpiredTimer();
|
||||
if (timer_id != (size_t)-1)
|
||||
{
|
||||
#if 0
|
||||
auto it = g_key_repeat_timers.find(timer_id);
|
||||
if (it != g_key_repeat_timers.end())
|
||||
{
|
||||
event->type = JTK_EVENT_KEY_PRESS;
|
||||
event->key.repeat = true;
|
||||
event->key.key = XKeyToJtkKey(it->second);
|
||||
event->key.x_keycode = it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
#endif
|
||||
event->type = JTK_EVENT_TIMER;
|
||||
event->timer.timer_id = timer_id;
|
||||
event->timer.user1 = Jtk_GetTimerUserData(timer_id, 0u);
|
||||
event->timer.user2 = Jtk_GetTimerUserData(timer_id, 1u);
|
||||
#if 0
|
||||
}
|
||||
#endif
|
||||
Jtk_ServiceTimer(timer_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Jtk_WaitEvent(Jtk_Event * event)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (Jtk_CheckEvent(event))
|
||||
return;
|
||||
|
||||
/* Wait for something to happen. */
|
||||
uint64_t time_to_wait = Jtk_TimeToNextTimerExpiration();
|
||||
if (time_to_wait > MAX_WAIT_TIME)
|
||||
{
|
||||
time_to_wait = MAX_WAIT_TIME;
|
||||
}
|
||||
|
||||
int x_fd = ConnectionNumber(g_display);
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(x_fd, &fds);
|
||||
struct timeval tv;
|
||||
tv.tv_sec = time_to_wait / 1000000u;
|
||||
tv.tv_usec = time_to_wait % 1000000u;
|
||||
select(x_fd + 1, &fds, nullptr, nullptr, &tv);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
void Jtk_BeginKeyRepeat(Jtk_KeyEvent * key_event, uint32_t delay, uint32_t interval)
|
||||
{
|
||||
StopKeyRepeat(key_event->x_keycode);
|
||||
size_t timer_id = Jtk_AddTimer(delay, interval, nullptr, nullptr);
|
||||
g_repeating_keys[key_event->x_keycode] = timer_id;
|
||||
g_key_repeat_timers[timer_id] = key_event->x_keycode;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef JTK_EVENT_H
|
||||
#define JTK_EVENT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define JTK_EVENT_WINDOW_CLOSE 1u
|
||||
#define JTK_EVENT_WINDOW_EXPOSE 2u
|
||||
#define JTK_EVENT_KEY_PRESS 3u
|
||||
#define JTK_EVENT_KEY_RELEASE 4u
|
||||
#define JTK_EVENT_BUTTON_PRESS 5u
|
||||
#define JTK_EVENT_BUTTON_RELEASE 6u
|
||||
#define JTK_EVENT_TIMER 7u
|
||||
#define JTK_EVENT_WINDOW_RESIZE 8u
|
||||
|
||||
typedef struct
|
||||
{
|
||||
bool repeat;
|
||||
uint32_t key;
|
||||
#ifdef JTK_X
|
||||
unsigned int x_keycode;
|
||||
#endif
|
||||
} Jtk_KeyEvent;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t mods;
|
||||
uint8_t button;
|
||||
} Jtk_ButtonEvent;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
size_t timer_id;
|
||||
void * user1;
|
||||
void * user2;
|
||||
} Jtk_TimerEvent;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
size_t width;
|
||||
size_t height;
|
||||
} Jtk_WindowResizeEvent;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t type;
|
||||
union
|
||||
{
|
||||
Jtk_KeyEvent key;
|
||||
Jtk_ButtonEvent button;
|
||||
Jtk_TimerEvent timer;
|
||||
Jtk_WindowResizeEvent resize;
|
||||
};
|
||||
} Jtk_Event;
|
||||
|
||||
bool Jtk_CheckEvent(Jtk_Event * event);
|
||||
void Jtk_WaitEvent(Jtk_Event * event);
|
||||
#if 0
|
||||
void Jtk_BeginKeyRepeat(Jtk_KeyEvent * key_event, uint32_t delay, uint32_t interval);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef JTK_INTERNAL_H
|
||||
#define JTK_INTERNAL_H
|
||||
|
||||
extern Display * g_display;
|
||||
extern XVisualInfo * g_vi;
|
||||
extern XSetWindowAttributes g_swa;
|
||||
extern GLXContext g_context;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
#include "Jtk.h"
|
||||
#include <sys/time.h>
|
||||
|
||||
uint64_t Jtk_UsTime()
|
||||
{
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, nullptr);
|
||||
return (uint64_t)tv.tv_sec * 1000000u + (uint64_t)tv.tv_usec;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef JTK_TIME_H
|
||||
#define JTK_TIME_H
|
||||
|
||||
#include "Jtk_timer.h"
|
||||
|
||||
bool Jtk_Init();
|
||||
uint64_t Jtk_UsTime();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,157 @@
|
||||
#include "Jtk.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <list>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint64_t next; /**< Time the timer next expires (us). */
|
||||
uint64_t interval; /**< Timer interval (us). */
|
||||
size_t id; /**< Timer ID. */
|
||||
void * user[2]; /**< User data pointers. */
|
||||
} Timer;
|
||||
|
||||
/** Vector used to allocate timer IDs. */
|
||||
static std::vector<std::shared_ptr<Timer>> g_timers;
|
||||
|
||||
/** Linked list used to traverse all active timers. */
|
||||
static std::list<std::shared_ptr<Timer>> g_active_timers;
|
||||
|
||||
static size_t AllocateTimerID()
|
||||
{
|
||||
for (size_t i = 0; i < g_timers.size(); i++)
|
||||
{
|
||||
if (!g_timers[i])
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
g_timers.push_back(nullptr);
|
||||
return g_timers.size() - 1u;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a timer.
|
||||
*
|
||||
* @param delay
|
||||
* Delay time in milliseconds.
|
||||
* @param interval
|
||||
* Interval time in milliseconds. A value of 0 indicates that the timer
|
||||
* is not periodic.
|
||||
* @param user1
|
||||
* User data pointer 1. This value is not used by Jtk but can be retrieved by
|
||||
* the user when a timer expires.
|
||||
* @param user2
|
||||
* User data pointer 2. This value is not used by Jtk but can be retrieved by
|
||||
* the user when a timer expires.
|
||||
*
|
||||
* @return
|
||||
* Timer ID.
|
||||
*/
|
||||
size_t Jtk_AddTimer(uint32_t delay, uint32_t interval, void * user1, void * user2)
|
||||
{
|
||||
uint64_t current_system_time = Jtk_UsTime();
|
||||
size_t timer_id = AllocateTimerID();
|
||||
auto timer = std::make_shared<Timer>();
|
||||
timer->next = current_system_time + (delay * 1000u);
|
||||
timer->interval = interval * 1000u;
|
||||
timer->id = timer_id;
|
||||
timer->user[0] = user1;
|
||||
timer->user[1] = user2;
|
||||
g_timers[timer_id] = timer;
|
||||
g_active_timers.push_back(timer);
|
||||
return timer_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a timer.
|
||||
*
|
||||
* @param timer_id
|
||||
* The ID of the timer to remove.
|
||||
*/
|
||||
void Jtk_RemoveTimer(size_t timer_id)
|
||||
{
|
||||
if (timer_id < g_timers.size())
|
||||
{
|
||||
auto timer = g_timers[timer_id];
|
||||
g_active_timers.remove(timer);
|
||||
g_timers[timer_id] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the amount of time until the next timer expires (in us).
|
||||
*
|
||||
* @return
|
||||
* Time (in us) until the next timer expires. This will be 0 if an active
|
||||
* timer has already expired, and will be (uint64_t)-1 if there are no
|
||||
* active timers.
|
||||
*/
|
||||
uint64_t Jtk_TimeToNextTimerExpiration()
|
||||
{
|
||||
uint64_t time = (uint64_t)-1;
|
||||
uint64_t current_system_time = Jtk_UsTime();
|
||||
for (auto & timer : g_active_timers)
|
||||
{
|
||||
if (timer->next <= current_system_time)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
uint64_t time_until_this_timer = timer->next - current_system_time;
|
||||
if (time_until_this_timer < time)
|
||||
{
|
||||
time = time_until_this_timer;
|
||||
}
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service a timer.
|
||||
*
|
||||
* This will increment the timer's next activation time by its interval, or
|
||||
* for a timer with an interval of 0, will remove the timer.
|
||||
*
|
||||
* @param timer_id
|
||||
* The ID of the timer to service.
|
||||
*/
|
||||
void Jtk_ServiceTimer(size_t timer_id)
|
||||
{
|
||||
auto timer = g_timers[timer_id];
|
||||
if (timer->interval == 0u)
|
||||
{
|
||||
Jtk_RemoveTimer(timer_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
timer->next += timer->interval;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ID of an expired timer.
|
||||
*
|
||||
* @return
|
||||
* The ID of an expired timer, or (size_t)-1 if no timer is expired.
|
||||
*/
|
||||
size_t Jtk_GetExpiredTimer()
|
||||
{
|
||||
uint64_t current_system_time = Jtk_UsTime();
|
||||
for (auto & timer : g_active_timers)
|
||||
{
|
||||
if (timer->next <= current_system_time)
|
||||
{
|
||||
return timer->id;
|
||||
}
|
||||
}
|
||||
return (size_t)-1;
|
||||
}
|
||||
|
||||
void * Jtk_GetTimerUserData(size_t timer_id, uint8_t index)
|
||||
{
|
||||
if (timer_id < g_timers.size())
|
||||
{
|
||||
return g_timers[timer_id]->user[index];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef JTK_TIMER_H
|
||||
#define JTK_TIMER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
size_t Jtk_AddTimer(uint32_t delay, uint32_t interval, void * user1, void * user2);
|
||||
void Jtk_RemoveTimer(size_t timer_id);
|
||||
uint64_t Jtk_TimeToNextTimerExpiration();
|
||||
void Jtk_ServiceTimer(size_t timer_id);
|
||||
size_t Jtk_GetExpiredTimer();
|
||||
void * Jtk_GetTimerUserData(size_t timer_id, uint8_t index);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,95 @@
|
||||
#include "Jtk.h"
|
||||
|
||||
#ifdef JTK_X
|
||||
|
||||
#include <GL/glx.h>
|
||||
#include "Jtk_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <cstring>
|
||||
|
||||
static Bool WaitForNotify(Display * display, XEvent * event, XPointer arg)
|
||||
{
|
||||
return (event->type == MapNotify) && (event->xmap.window == (Window)arg);
|
||||
}
|
||||
|
||||
void * Jtk_CreateWindow()
|
||||
{
|
||||
XEvent event;
|
||||
Window window = XCreateWindow(g_display,
|
||||
RootWindow(g_display, g_vi->screen),
|
||||
0, 0, 800, 800, 0, g_vi->depth, InputOutput, g_vi->visual,
|
||||
CWBorderPixel | CWColormap | CWEventMask, &g_swa);
|
||||
XMapWindow(g_display, window);
|
||||
XIfEvent(g_display, &event, WaitForNotify, (XPointer)window);
|
||||
if (glXMakeCurrent(g_display, window, g_context) == False)
|
||||
{
|
||||
fprintf(stderr, "glXMakeCurrent() failure\n");
|
||||
XDestroyWindow(g_display, window);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Disable the window close button. */
|
||||
Atom wm_delete_window_atom = XInternAtom(g_display, "WM_DELETE_WINDOW", False);
|
||||
XSetWMProtocols(g_display, window, &wm_delete_window_atom, 1);
|
||||
|
||||
return (void *)window;
|
||||
}
|
||||
|
||||
void Jtk_SwapBuffers(void * window)
|
||||
{
|
||||
glXSwapBuffers(g_display, (Window)window);
|
||||
}
|
||||
|
||||
void Jtk_CloseWindow(void * window)
|
||||
{
|
||||
XDestroyWindow(g_display, (Window)window);
|
||||
}
|
||||
|
||||
void Jtk_SetWindowTitle(void * window, const char * title)
|
||||
{
|
||||
XTextProperty title_property;
|
||||
if (XStringListToTextProperty((char **)&title, 1, &title_property) != 0)
|
||||
{
|
||||
XSetTextProperty(g_display, (Window)window, &title_property, XA_WM_NAME);
|
||||
XFree(title_property.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the window icon.
|
||||
*
|
||||
* @param window
|
||||
* The window to operate on.
|
||||
* @param data
|
||||
* The format of data must be 32 bits per pixel in BGRA format (e.g. data[0]
|
||||
* is blue value, data[3] is alpha value of first pixel).
|
||||
* @param width
|
||||
* Icon width.
|
||||
* @param height
|
||||
* Icon height.
|
||||
*/
|
||||
void Jtk_SetWindowIcon(void * window, const uint8_t * data,
|
||||
size_t width, size_t height)
|
||||
{
|
||||
Atom net_wm_icon_atom = XInternAtom(g_display, "_NET_WM_ICON", False);
|
||||
size_t property_size = (2u + width * height) * sizeof(long);
|
||||
unsigned long * property_data = (unsigned long *)malloc(property_size);
|
||||
property_data[0] = width;
|
||||
property_data[1] = height;
|
||||
unsigned long * dest = &property_data[2];
|
||||
const uint32_t * src = (const uint32_t *)data;
|
||||
for (size_t row = 0u; row < height; row++)
|
||||
{
|
||||
for (size_t col = 0u; col < width; col++)
|
||||
{
|
||||
*dest++ = *src++;
|
||||
}
|
||||
}
|
||||
XChangeProperty(g_display, (Window)window, net_wm_icon_atom, XA_CARDINAL,
|
||||
32, PropModeReplace, (uint8_t *)property_data, property_size);
|
||||
XFlush(g_display);
|
||||
free(property_data);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef JTK_WINDOW_H
|
||||
#define JTK_WINDOW_H
|
||||
|
||||
void * Jtk_CreateWindow();
|
||||
void Jtk_SwapBuffers(void * window);
|
||||
void Jtk_CloseWindow(void * window);
|
||||
void Jtk_SetWindowTitle(void * window, const char * title);
|
||||
void Jtk_SetWindowIcon(void * window, const uint8_t * data,
|
||||
size_t width, size_t height);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
#include <iostream>
|
||||
#include "Window.h"
|
||||
#include "Runtime.h"
|
||||
#include <memory>
|
||||
#include "System.h"
|
||||
#include "DefaultCommandMap.h"
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
if (!System::init(argv))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
Runtime::init(System::executable_path().c_str(), APPNAME);
|
||||
DefaultCommandMap::build();
|
||||
|
||||
const char * filename = nullptr;
|
||||
if (argc > 1)
|
||||
{
|
||||
filename = argv[1];
|
||||
}
|
||||
std::shared_ptr<Buffer> b = std::make_shared<Buffer>(filename);
|
||||
Window w;
|
||||
if (!w.create(b))
|
||||
{
|
||||
std::cerr << "Error creating window." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
w.run_event_loop();
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user