591 lines
17 KiB
C++
591 lines
17 KiB
C++
#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();
|
|
}
|