#include "BufferPane.h" BufferPane::BufferPane(Window * window, std::shared_ptr buffer) : m_window(window), m_buffer(buffer) { m_cursor_screen_row = 0; m_scroll_offset = 5; m_iterator = buffer->add_cursor(); m_target_column = 0; m_cursor_virtual_column = 0; m_show_status_bar = true; m_command_mode = false; m_focused = false; } void BufferPane::resize(int width, int height) { Pane::resize(width, height); m_columns = std::max(1, m_width / m_window->font()->get_advance()); int height_subtract = 0; if (m_show_status_bar) { height_subtract = m_window->font()->get_line_height() + 1; } m_rows = std::max(1, (m_height - height_subtract) / m_window->font()->get_line_height()); } void BufferPane::walk_line(const Buffer::Iterator & start_of_line, std::function callback) { int row_offset = 0; int screen_column = 0; int virtual_column = 0; Buffer::Iterator i = start_of_line; for (;;) { uint32_t code_point = *i; if ((code_point == '\n') || (!i.valid())) { callback(row_offset, screen_column, virtual_column, 0, i); break; } int c_width; if (code_point == '\t') { uint8_t tabstop = m_buffer->tabstop(); c_width = tabstop - virtual_column % tabstop; } else { c_width = character_width(code_point); if (((screen_column + c_width) > m_columns) && (screen_column > 0)) { row_offset++; screen_column = 0; } } callback(row_offset, screen_column, virtual_column, c_width, i); virtual_column += c_width; if (code_point == '\t') { screen_column += c_width; while (screen_column >= m_columns) { screen_column -= m_columns; row_offset++; } } else { if ((screen_column + c_width) >= m_columns) { row_offset++; screen_column = 0; } else { screen_column += c_width; } } i.go_forward(); } } int BufferPane::calculate_rows_in_line(const Buffer::Iterator & start_of_line) { int saved_row_offset = 0; walk_line(start_of_line, [&saved_row_offset](int row_offset, int screen_column, int virtual_column, int character_width, const Buffer::Iterator & i) { uint32_t code_point = *i; if ((code_point != '\n') && (code_point != Buffer::Iterator::INVALID_CODE_POINT)) { saved_row_offset = row_offset; } }); return saved_row_offset + 1; } int BufferPane::calculate_rows_in_cursor_line(const Buffer::Iterator & start_of_line) { int saved_row_offset = 0; walk_line(start_of_line, [this, &saved_row_offset](int row_offset, int screen_column, int virtual_column, int character_width, const Buffer::Iterator & i) { uint32_t code_point = *i; if (i == *m_iterator) { m_cursor_virtual_column = virtual_column; m_cursor_screen_column = screen_column; m_cursor_row_offset = row_offset; } if ((code_point != '\n') && (code_point != Buffer::Iterator::INVALID_CODE_POINT)) { saved_row_offset = row_offset; } }); return saved_row_offset + 1; } int BufferPane::calculate_rows_in_line_with_iterator_offset(const Buffer::Iterator & start_of_line, const Buffer::Iterator & reference, int * iterator_row_offset) { int saved_row_offset = 0; walk_line(start_of_line, [this, &saved_row_offset, &reference, &iterator_row_offset](int row_offset, int screen_column, int virtual_column, int character_width, const Buffer::Iterator & i) { uint32_t code_point = *i; if (i == reference) { *iterator_row_offset = row_offset; } if ((code_point != '\n') && (code_point != Buffer::Iterator::INVALID_CODE_POINT)) { saved_row_offset = row_offset; } }); return saved_row_offset + 1; } int BufferPane::screen_rows_below_line(const Buffer::Iterator & line) { Buffer::Iterator i = line; int rows = 0; while ((rows < m_rows) && i.go_next_line()) { rows += calculate_rows_in_line(i); } return rows; } int BufferPane::screen_rows_above_line(const Buffer::Iterator & line, std::list> & backward_lines) { Buffer::Iterator i = line; int rows = 0; while ((rows < m_rows) && 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(rows_in_this_line, i)); } return rows; } void BufferPane::update_cursor_row(std::list> & backward_lines) { Buffer::Iterator start_of_line = *m_iterator; start_of_line.go_start_of_line(); int rows_in_cursor_line = calculate_rows_in_cursor_line(start_of_line); int so = effective_scroll_offset(); int rows_above = screen_rows_above_line(*m_iterator, backward_lines) + m_cursor_row_offset; int rows_below = screen_rows_below_line(*m_iterator) + std::max(0, 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_rows - 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_rows - rows_below - 1, m_cursor_screen_row); m_cursor_screen_row = std::min(rows_above, m_cursor_screen_row); } bool BufferPane::move_cursor_screen_row_up() { size_t previous_iterator_offset = m_iterator->offset(); if (m_cursor_row_offset > 0) { Buffer::Iterator start_of_line = *m_iterator; start_of_line.go_start_of_line(); walk_line(start_of_line, [this](int row_offset, int screen_column, int virtual_column, int character_width, const Buffer::Iterator & i) { if (((*i != '\n') || insert_mode()) && (screen_column <= m_cursor_screen_column) && (row_offset < m_cursor_row_offset)) { *m_iterator = i; } }); } else { if (!m_iterator->go_previous_line()) { return false; } walk_line(*m_iterator, [this](int row_offset, int screen_column, int virtual_column, int character_width, const Buffer::Iterator & i) { if (((*i != '\n') || insert_mode()) && (screen_column <= m_cursor_screen_column)) { *m_iterator = i; } }); } return m_iterator->offset() != previous_iterator_offset; } bool BufferPane::move_cursor_screen_row_down() { size_t previous_iterator_offset = m_iterator->offset(); Buffer::Iterator start_of_line = *m_iterator; start_of_line.go_start_of_line(); walk_line(start_of_line, [this](int row_offset, int screen_column, int virtual_column, int character_width, const Buffer::Iterator & i) { if (((*i != '\n') || insert_mode()) && (screen_column <= m_cursor_screen_column) && (row_offset > m_cursor_row_offset)) { *m_iterator = i; } }); if (m_iterator->offset() != previous_iterator_offset) { return true; } if (!m_iterator->go_next_line()) { return false; } walk_line(*m_iterator, [this](int row_offset, int screen_column, int virtual_column, int character_width, const Buffer::Iterator & i) { if (((*i != '\n') || insert_mode()) && (screen_column <= m_cursor_screen_column) && (row_offset == 0)) { *m_iterator = i; } }); return m_iterator->offset() != previous_iterator_offset; } int BufferPane::determine_new_cursor_screen_row() { if (m_screen_lines.size() > 0) { if (m_iterator->line() < m_screen_lines.begin()->second.line()) { return 0; } if (m_iterator->line() > m_screen_lines.rbegin()->second.line()) { return m_rows; } for (auto screen_line : m_screen_lines) { if (screen_line.second.line() == m_iterator->line()) { calculate_rows_in_cursor_line(screen_line.second); return screen_line.first + m_cursor_row_offset; } } } return 0; } void BufferPane::draw() { m_screen_lines.clear(); if (m_iterator->valid()) { std::list> backward_lines; update_cursor_row(backward_lines); int screen_row = m_cursor_screen_row - m_cursor_row_offset; Buffer::Iterator i = *m_iterator; if (screen_row <= 0) { i.go_start_of_line(); } else for (auto rows_iterator_pair : backward_lines) { screen_row -= rows_iterator_pair.first; i = rows_iterator_pair.second; if (screen_row <= 0) { break; } } while (screen_row <= m_rows) { m_screen_lines.push_back(std::pair(screen_row, i)); screen_row += draw_buffer_line(screen_row, i); if (!i.go_next_line()) { break; } } } else { draw_cursor(col_x(0), row_y(0), 0, 1); } if (m_show_status_bar) { draw_status_bar(); } } int BufferPane::draw_buffer_line(int screen_row, const Buffer::Iterator & start_of_line) { int saved_row_offset = 0; walk_line(start_of_line, [this, &saved_row_offset, &screen_row](int row_offset, int screen_column, int virtual_column, int character_width, const Buffer::Iterator & i) { uint32_t code_point = *i; int draw_row = screen_row + row_offset; if ((draw_row >= 0) && (draw_row <= m_rows)) { if (i.line() == m_iterator->line()) { int row = draw_row; int col = screen_column; for (int i = 0; i < character_width; i++) { draw_crosshair(col_x(col), row_y(row)); col++; if (col >= m_columns) { row++; col = 0; } } } else if ((m_cursor_virtual_column >= virtual_column) && (m_cursor_virtual_column < (virtual_column + std::max(character_width, 1)))) { int col = screen_column + (m_cursor_virtual_column - virtual_column); int row = draw_row; while (screen_column >= m_columns) { screen_column -= m_columns; row++; } draw_crosshair(col_x(col), row_y(row)); } if (i == *m_iterator) { bool wrap = (code_point == '\t'); int row = draw_row; int col = screen_column; int w = std::max(1, character_width); for (int i = 0; i < w; i++) { draw_cursor(col_x(col), row_y(row), i, w); col++; if (wrap && (col >= m_columns)) { row++; col = 0; } } } if ((code_point == '\n') || (code_point == Buffer::Iterator::INVALID_CODE_POINT)) { if ((m_cursor_virtual_column > virtual_column) && (m_cursor_virtual_column < (virtual_column + (m_columns - screen_column)))) { draw_crosshair(col_x(screen_column + (m_cursor_virtual_column - virtual_column)), row_y(draw_row)); } } else { draw_character(code_point, draw_row, screen_column); } if ((code_point != '\n') && (code_point != Buffer::Iterator::INVALID_CODE_POINT)) { saved_row_offset = row_offset; } } }); return saved_row_offset + 1; } void BufferPane::draw_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(), 1.0, 1.0, 1.0, 1.0); m_window->gl()->draw_character(win_x(col_x(screen_column + 1)), win_y(y), character | 0x40u, *m_window->font(), 1.0, 1.0, 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); } } } int BufferPane::character_width(uint32_t character) { if (character < 0x20u) { return 2; } else if (character < 0x80u) { return 1u; } else { return std::max(1, m_window->font()->get_glyph(character)->get_advance() / m_window->font()->get_advance()); } } 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), 1, height, 1.0, 0.2, 1.0, 1.0); m_window->gl()->draw_rect(win_x(x + 1), win_y(y + height - 1), 2, 1, 1.0, 0.2, 1.0, 1.0); m_window->gl()->draw_rect(win_x(x + 1), win_y(y), 2, 1, 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) { if (!m_command_mode) { int width = m_window->font()->get_advance(); int 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::exit_insert_mode() { if (insert_mode()) { m_buffer->exit_insert_mode(); if (!m_iterator->is_start_of_line()) { m_iterator->go_back(); } 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 { if (code_point == '\n') { m_cursor_screen_row++; } m_buffer->insert_code_point(*m_iterator, code_point); } m_window->request_redraw(); } } void BufferPane::kill_character_at_cursor() { if (**m_iterator != '\n') { m_buffer->erase_code_point(*m_iterator); if (**m_iterator == '\n') { m_iterator->go_left_in_line(); } m_window->request_redraw(); } } void BufferPane::write_file() { if (m_buffer->filename() != "") { m_buffer->write_to_file(m_buffer->filename().c_str()); } } 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_cursor_virtual_column + 1u; } void BufferPane::cursor_move(Window::CursorMovement which) { bool moved = false; switch (which) { case Window::CursorMovement::LEFT: moved = m_iterator->go_left_in_line(); break; case Window::CursorMovement::RIGHT: moved = m_iterator->go_right_in_line(insert_mode()); break; case Window::CursorMovement::UP: moved = m_iterator->go_previous_line(); break; case Window::CursorMovement::DOWN: moved = m_iterator->go_next_line(); break; case Window::CursorMovement::SOL: moved = m_iterator->go_start_of_line(); break; case Window::CursorMovement::EOL: moved = m_iterator->go_end_of_line(insert_mode()); break; case Window::CursorMovement::FIRST_LINE: { auto it = m_buffer->begin(); if (it != *m_iterator) { *m_iterator = it; moved = true; } } break; case Window::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; } } break; case Window::CursorMovement::SCREEN_ROW_UP: moved = move_cursor_screen_row_up(); break; case Window::CursorMovement::SCREEN_ROW_DOWN: moved = move_cursor_screen_row_down(); break; } if (moved) { switch (which) { case Window::CursorMovement::LEFT: case Window::CursorMovement::RIGHT: { Buffer::Iterator start_of_line = *m_iterator; start_of_line.go_start_of_line(); calculate_rows_in_cursor_line(start_of_line); m_target_column = m_cursor_virtual_column; } break; case Window::CursorMovement::UP: forward_to_column(m_target_column, insert_mode()); break; case Window::CursorMovement::DOWN: forward_to_column(m_target_column, insert_mode()); break; case Window::CursorMovement::SOL: m_target_column = 0; break; case Window::CursorMovement::EOL: m_target_column = INT_MAX; break; case Window::CursorMovement::FIRST_LINE: m_cursor_screen_row = 0; break; case Window::CursorMovement::LAST_LINE: m_cursor_screen_row = m_rows; break; } m_cursor_screen_row = determine_new_cursor_screen_row(); 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(Window::CursorMovement::UP); 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_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::scroll_window_up(Window::ScrollMode scroll_mode) { int lines_to_scroll = calculate_lines_to_scroll(scroll_mode); int so = effective_scroll_offset(); int lines_to_move_cursor = (so + lines_to_scroll) - (m_rows - m_cursor_screen_row - 1); while (lines_to_move_cursor-- > 0) { move_cursor_screen_row_up(); } m_cursor_screen_row += lines_to_scroll; m_window->request_redraw(); } void BufferPane::scroll_window_down(Window::ScrollMode scroll_mode) { int lines_to_scroll = calculate_lines_to_scroll(scroll_mode); int so = effective_scroll_offset(); int lines_to_move_cursor = (so + lines_to_scroll) - m_cursor_screen_row; while (lines_to_move_cursor-- > 0) { move_cursor_screen_row_down(); } m_cursor_screen_row -= lines_to_scroll; m_window->request_redraw(); } void BufferPane::forward_to_column(int column, bool allow_eol) { Buffer::Iterator start_of_line = *m_iterator; start_of_line.go_start_of_line(); walk_line(start_of_line, [this, &column, &allow_eol](int row_offset, int screen_column, int virtual_column, int character_width, const Buffer::Iterator & i) { uint32_t code_point = *i; if ((code_point != '\n') || allow_eol) { if (virtual_column <= column) { *m_iterator = i; } } }); } 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(); if (filename == "") { 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_window->request_redraw(); } void BufferPane::redo() { m_buffer->redo(); m_window->request_redraw(); } void BufferPane::set_command_mode() { m_command_mode = true; set_show_status_bar(false); enter_insert_mode(Window::EnterInsertModeMode::START_OF_CHAR); } void BufferPane::clear() { m_buffer->clear(); *m_iterator = m_buffer->begin(); }