#include <sstream>
#include <termbox.h>
#include "Buffer.h"
#include "Event.h"
#include "Line.h"
#include "Gutter.h"
#include "Syntax.h"
void Buffer::set_relative_position(uint new_x) {
assert(new_x <= current_line().length());
m_absolute_position += new_x - m_relative_position;
m_relative_position = new_x;
}
Line Buffer::current_line() {
return m_text.get_line(current_line_nb);
}
void Buffer::draw(Canvas canvas) {
const uint clbg = 235;
const uint bg = 234;
m_canvas = canvas;
auto line = m_text.get_line(m_first_visible_line_nb);
auto screen_line = 0;
for(; screen_line < canvas.height(); line = line.next()) {
bool isCurLine = line.number() == current_line_nb;
auto bgcolor = isCurLine ? clbg : bg;
// Word wrap
uint start = 0;
for(uint y = 0; y < line.height(*this); y++) {
// Draw background
int gutter_width = Gutter::width(*this);
tb_change_cell(gutter_width, screen_line, ' ', 15, bgcolor + 1);
for(int x = gutter_width + 1; x < tb_width() - 1; x++) {
tb_change_cell(x, screen_line, ' ', 15, bgcolor);
}
// Draw text
line.printFrom(start, gutter_width + 1, screen_line, bgcolor);
start += max_line_width();
// Draw [...] if not current line
if(!isCurLine && line.height(*this) > 1) {
Canvas::print(L"...", tb_width() - 3, screen_line, 15, bg);
screen_line++;
break;
} else {
// Make sure text isn't going too far
tb_change_cell(tb_width() - 1, screen_line, ' ', 15, bgcolor);
}
screen_line++;
}
if(line.is_last()) break;
}
Gutter::draw(*this);
}
bool Buffer::move_down() {
auto old_y = current_line_nb;
if(current_line_nb == m_text.get_nb_lines()) return false;
current_line_nb++;
scroll(old_y);
if(m_relative_position > current_line().length()) {
m_relative_position = current_line().length();
}
m_absolute_position = current_line().start() + m_relative_position;
return true;
}
bool Buffer::move_to(uint line_number) {
if(line_number < 1) line_number = 1;
if(line_number > m_text.get_nb_lines()) line_number = m_text.get_nb_lines();
auto old_y = current_line_nb;
if(current_line_nb == line_number) return false;
current_line_nb = line_number;
scroll(old_y);
m_absolute_position = current_line().start() + m_relative_position;
return true;
}
bool Buffer::move_to(uint line_number, uint column) {
move_to(line_number);
set_relative_position(column);
return true;
}
bool Buffer::move_up() {
auto old_y = current_line_nb;
if(current_line_nb == 1) return false;
current_line_nb--;
scroll(old_y);
if(m_relative_position > current_line().length()) {
m_relative_position = current_line().length();
}
m_absolute_position = current_line().start() + m_relative_position;
return true;
}
// TODO this is currently forward-erasing 2 characters ??
bool Buffer::erase_backwards(bool log_events) {
if(m_absolute_position == 0) return false;
std::wstring c;
c.insert(0, 1, m_text[m_absolute_position - 1]);
if(m_relative_position == 0) {
move_up();
set_relative_position(current_line().length());
} else {
m_absolute_position--;
m_relative_position--;
}
m_text.erase(m_absolute_position, 1);
if(log_events) {
m_history.log_event(new EraseEvent(c.c_str(), current_line_nb, m_relative_position, false));
}
return true;
}
bool Buffer::erase_forwards(bool log_events) {
if(m_absolute_position == m_text.size()) return false;
std::wstring c;
c.insert(0, 1, m_text[m_absolute_position]);
m_text.erase(m_absolute_position, 1);
if(log_events) {
m_history.log_event(new EraseEvent(c.c_str(), current_line_nb, m_relative_position, true));
}
return true;
}
uint Buffer::first_visible_line_nb() {
return m_first_visible_line_nb;
}
Canvas Buffer::get_canvas() {
// currently, there is only one buffer, but there may be
// multiple buffers on screen in the future
return Canvas{ 0, 0, tb_width(), tb_height() - 1 };
}
uint Buffer::get_visible_x() {
return current_line().width(m_relative_position) % max_line_width() + 1;
}
uint Buffer::get_visible_y() {
return current_line_nb - m_first_visible_line_nb + (current_line().width(m_relative_position) / max_line_width());
}
bool Buffer::handle_key(tb_event *ev) {
bool handled = true;
switch(ev->key) {
case TB_KEY_HOME:
set_relative_position(0);
break;
case TB_KEY_END:
set_relative_position(current_line().length());
break;
case TB_KEY_ARROW_LEFT:
if(m_relative_position == 0) {
if(move_up()) {
set_relative_position(current_line().length());
} else {
handled = false;
}
} else if(m_absolute_position > 0) {
m_absolute_position--;
m_relative_position--;
} else {
handled = false;
}
break;
case TB_KEY_ARROW_RIGHT:
if(m_relative_position == current_line().length()) {
if(move_down()) {
set_relative_position(0);
} else {
handled = false;
}
} else if(m_absolute_position < m_text.size()) {
m_absolute_position++;
m_relative_position++;
} else {
handled = false;
}
break;
case TB_KEY_ARROW_DOWN:
if(!move_down())
handled = false;
break;
case TB_KEY_ARROW_UP:
if(!move_up())
handled = false;
break;
case TB_KEY_BACKSPACE:
case TB_KEY_BACKSPACE2:
if(!erase_backwards(true)) {
handled = false;
}
break;
case TB_KEY_DELETE:
if(!erase_forwards(true)) {
handled = false;
}
break;
case TB_KEY_PGDN:
for(int i = 0; i < 0.8 * m_canvas.height(); i++) {
if(!move_down())
break;
}
break;
case TB_KEY_PGUP:
for(int i = 0; i < 0.8 * m_canvas.height(); i++) {
if(!move_up())
break;
}
break;
case TB_KEY_ENTER:
insert(L'\n', true);
break;
case TB_KEY_TAB:
insert(L'\t', true);
break;
case TB_KEY_SPACE:
insert(L' ', true);
break;
case TB_KEY_CTRL_Z:
m_history.undo(*this);
break;
case TB_KEY_CTRL_Y:
m_history.redo(*this);
break;
default:
handled = false;
}
if(!handled && ev->ch) {
insert(ev->ch, true);
handled = true;
}
if(handled) {
Syntax::update(current_line());
}
return handled;
}
bool Buffer::handle_mouse(tb_event* ev) {
switch(ev->key) {
case TB_KEY_MOUSE_WHEEL_UP:
move_up();
move_up();
move_up();
return true;
case TB_KEY_MOUSE_WHEEL_DOWN:
move_down();
move_down();
move_down();
return true;
}
return false;
}
void Buffer::insert(const wchar_t c, bool log_events) {
if(c == L'\n') {
int next_indent = current_line().get_next_indentation();
current_line().insert(L'\n', m_relative_position);
move_down();
set_relative_position(0);
if(log_events) {
m_history.log_event(new WriteEvent(L"\n", current_line_nb, m_relative_position));
}
// Make sure we have a correct start_commented on the new line
Syntax::mark_start_commented(current_line());
// Auto-indent
if(next_indent > 0) {
std::wstring indentation = L"";
for(int i = 0; i < next_indent; i++) {
indentation += L'\t';
}
insert(indentation.c_str(), log_events);
}
} else {
if(!current_line().is_first()) {
auto x = m_relative_position;
auto normal_indent = current_line().prev().get_next_indentation();
auto current_indent = current_line().get_current_indentation();
// Deindent
if(c == L'}' && x > 0 && current_indent == normal_indent) {
if(current_line()[x - 1] == L'\t') {
erase_backwards(log_events);
}
}
}
current_line().insert(c, m_relative_position);
m_relative_position++;
m_absolute_position++;
if(log_events) {
std::wstring key;
key.insert(0, 1, c);
m_history.log_event(new WriteEvent(key.c_str(), current_line_nb, m_relative_position));
}
}
}
void Buffer::insert(const wchar_t* str, bool log_events) {
// TODO optimize by inserting lines and newlines instead
for(; *str != '\0'; str++) {
insert(*str, log_events);
}
}
int Buffer::max_line_width() {
return m_canvas.width() - Gutter::width(*this) - 2;
}
void Buffer::screen_to(uint screen_x, uint screen_y) {
uint old_y = current_line_nb;
move_to(m_first_visible_line_nb + screen_y);
scroll(old_y);
// TODO set screen_x
}
void Buffer::scroll(uint old_current_line_nb) {
if(old_current_line_nb == current_line_nb) return;
const uint padding_min = 0.3 * m_canvas.height();
const uint padding_max = 0.7 * m_canvas.height();
if(old_current_line_nb > current_line_nb) {
// Scroll up
if(current_line_nb < padding_min)
m_first_visible_line_nb = 1;
else if(m_first_visible_line_nb + padding_min > current_line_nb)
m_first_visible_line_nb = current_line_nb - padding_min;
if(m_first_visible_line_nb < 1)
m_first_visible_line_nb = 1;
} else {
// Scroll down
if(m_first_visible_line_nb + padding_max < current_line_nb)
m_first_visible_line_nb = current_line_nb - padding_max;
if(m_first_visible_line_nb > m_text.get_nb_lines())
m_first_visible_line_nb = m_text.get_nb_lines();
}
}