#include "BufferPane.h" #include 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) : m_window(window), m_buffer(buffer), m_cwd(window) { m_iterator = buffer->add_cursor(); m_buffer_view = std::make_shared(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 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 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(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(); }