jes/src/gui/BufferPane.cc

560 lines
16 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::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)
{
std::shared_ptr<Buffer::Range> range = get_range_for_motion(motion);
if (range)
{
m_buffer->erase_range(*range);
m_buffer_view->update();
}
}
std::shared_ptr<Buffer::Range> BufferPane::get_range_for_motion(const Command::Unit & motion)
{
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::THIS_LINE:
start->go_start_of_line();
end->go_end_of_line(true);
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();
}