mv src src-c

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

No files matched your search

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