#include "BufferView.h" #include #include #include BufferView::BufferView(std::shared_ptr buffer, std::shared_ptr iterator, CharacterWidthDeterminer & character_width_determiner) : m_buffer(buffer), m_iterator(iterator), m_character_width_determiner(character_width_determiner) { m_width = 1; m_height = 1; m_scroll_offset = 0; m_cursor_screen_row = 0; m_cursor_screen_column = 0; m_cursor_virtual_column = 0; m_cursor_row_offset = 0; m_rows_in_cursor_line = 0; m_target_screen_column = 0; m_target_virtual_column = 0; m_update_target_column = false; } void BufferView::resize(int width, int height) { m_width = std::max(1, width); m_height = std::max(1, height); } void BufferView::set_scroll_offset(int scroll_offset) { m_scroll_offset = std::max(0, scroll_offset); } BufferView::Iterator BufferView::vert_iter() { return Iterator(*this); } void BufferView::update() { /* Calculate number of rows in the cursor line and update cursor row offset * and cursor column values. */ auto start_of_line = std::make_shared(*m_iterator); start_of_line->go_start_of_line(); m_rows_in_cursor_line = calculate_rows_in_cursor_line(start_of_line); /* Limit the cursor screen row taking into account view dimensions, * available buffer contents above and below the current line, and scroll * offset. */ std::list>> backward_lines; int so = effective_scroll_offset(); int rows_above = screen_rows_above_line(start_of_line, backward_lines) + m_cursor_row_offset; int rows_below = screen_rows_below_line(start_of_line) + std::max(0, m_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_height - 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_height - rows_below - 1, m_cursor_screen_row); m_cursor_screen_row = std::min(rows_above, m_cursor_screen_row); /* Determine the first line that is visible in this view. */ auto line_iterator = std::make_shared(*m_iterator); int row_offset = m_cursor_screen_row - m_cursor_row_offset; if (row_offset <= 0) { line_iterator->go_start_of_line(); } else for (auto rows_iterator_pair : backward_lines) { row_offset -= rows_iterator_pair.first; line_iterator = rows_iterator_pair.second; if (row_offset <= 0) { break; } } /* Now start with first visible line and build up all lines visible in the view. */ m_lines.clear(); while ((row_offset < m_height) && (line_iterator->valid())) { LineDescriptor ld; ld.row_offset = row_offset; ld.n_rows = calculate_rows_in_line(line_iterator); ld.line = std::make_shared(*line_iterator); m_lines.push_back(ld); row_offset += ld.n_rows; if (!line_iterator->go_next_line()) break; } /* Reset some fields if buffer becomes empty. */ if (!m_iterator->valid()) { m_cursor_screen_row = 0; m_cursor_screen_column = 0; m_cursor_virtual_column = 0; } } bool BufferView::cursor_move(CursorMovement which, uint32_t c, bool allow_eol) { bool moved = false; switch (which) { case CursorMovement::LEFT: moved = m_iterator->go_left_in_line(); m_update_target_column = true; break; case CursorMovement::RIGHT: moved = m_iterator->go_right_in_line(allow_eol); m_update_target_column = true; break; case CursorMovement::UP: moved = m_iterator->go_previous_line(); break; case CursorMovement::DOWN: moved = m_iterator->go_next_line(); break; case CursorMovement::SOL: moved = m_iterator->go_start_of_line(); m_target_screen_column = 0; m_target_virtual_column = 0; break; case CursorMovement::EOL: moved = m_iterator->go_end_of_line(allow_eol); m_target_screen_column = INT_MAX; m_target_virtual_column = INT_MAX; break; case CursorMovement::START_OF_FILE: case CursorMovement::FIRST_LINE: { auto it = m_buffer->begin(); if (it != *m_iterator) { *m_iterator = it; moved = true; } } m_target_screen_column = 0; m_target_virtual_column = 0; break; case CursorMovement::END_OF_FILE: { auto it = m_buffer->end(); it.go_back(); /* TODO: optimize this */ it.go_start_of_line(); it.go_end_of_line(allow_eol); if (it != *m_iterator) { *m_iterator = it; moved = true; } } m_update_target_column = true; break; case 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; } } m_target_screen_column = 0; m_target_virtual_column = 0; break; case CursorMovement::SCREEN_ROW_UP: moved = move_cursor_screen_row_up(allow_eol); break; case CursorMovement::SCREEN_ROW_DOWN: moved = move_cursor_screen_row_down(allow_eol); break; case CursorMovement::FORWARD_UP_TO_CHAR: moved = m_iterator->go_forward_in_line_up_to_char(c); break; case CursorMovement::FORWARD_ON_TO_CHAR: moved = m_iterator->go_forward_in_line_on_to_char(c); break; case CursorMovement::BACK_UP_TO_CHAR: moved = m_iterator->go_backward_in_line_up_to_char(c); break; case CursorMovement::BACK_ON_TO_CHAR: moved = m_iterator->go_backward_in_line_on_to_char(c); break; case CursorMovement::TOP_OF_SCREEN: { int n_lines = n_screen_rows_with_content(); int y = (n_lines < m_height) ? 0 : effective_scroll_offset(); moved = cursor_move_to_screen_position(0, y, allow_eol); } break; case CursorMovement::MIDDLE_OF_SCREEN: { int n_lines = n_screen_rows_with_content(); int y = (n_lines < m_height) ? (n_lines / 2) : (m_height / 2); moved = cursor_move_to_screen_position(0, y, allow_eol); } break; case CursorMovement::BOTTOM_OF_SCREEN: { int n_lines = n_screen_rows_with_content(); int y = (n_lines < m_height) ? (n_lines - 1) : (m_height - effective_scroll_offset() - 1); moved = cursor_move_to_screen_position(0, y, allow_eol); } break; } if (moved) { switch (which) { case CursorMovement::LEFT: case CursorMovement::RIGHT: case CursorMovement::SCREEN_ROW_UP: case CursorMovement::SCREEN_ROW_DOWN: determine_new_cursor_screen_row(); break; case CursorMovement::UP: case CursorMovement::DOWN: move_forward_to_column(m_target_virtual_column, allow_eol); determine_new_cursor_screen_row(); break; case CursorMovement::SOL: case CursorMovement::EOL: case CursorMovement::FORWARD_UP_TO_CHAR: case CursorMovement::FORWARD_ON_TO_CHAR: case CursorMovement::BACK_UP_TO_CHAR: case CursorMovement::BACK_ON_TO_CHAR: determine_new_cursor_screen_row(); break; case CursorMovement::START_OF_FILE: case CursorMovement::FIRST_LINE: m_cursor_screen_row = 0; break; case CursorMovement::END_OF_FILE: case CursorMovement::LAST_LINE: m_cursor_screen_row = m_height - 1; break; case CursorMovement::TOP_OF_SCREEN: case CursorMovement::MIDDLE_OF_SCREEN: case CursorMovement::BOTTOM_OF_SCREEN: m_target_virtual_column = 0; m_target_screen_column = 0; break; } } return moved; } bool BufferView::cursor_move_to_line(size_t target_line) { size_t current_line = m_iterator->line(); std::shared_ptr it; if (target_line <= current_line / 2u) { it = m_buffer->beginp(); } else if (target_line <= ((current_line + m_buffer->end().line()) / 2u)) { it = m_iterator->clonep(); } else { it = m_buffer->endp(); it->go_back(); it->go_start_of_line(); } while (it->line() < target_line) { if (!it->go_next_line()) break; } while (it->line() > target_line) { if (!it->go_previous_line()) break; } if (current_line != it->line()) { *m_iterator = *it; determine_new_cursor_screen_row(); return true; } return false; } bool BufferView::cursor_move_to_screen_position(int x, int y, bool allow_eol) { bool moved = false; for (const LineDescriptor & screen_line : m_lines) { if ((y >= screen_line.row_offset) && (y < (screen_line.row_offset + screen_line.n_rows))) { Buffer::Iterator scan_iter = *m_iterator; bool set = false; int desired_row_offset = y - screen_line.row_offset; for (auto it = horiz_iter(screen_line.line); it.is_valid(); it++) { if (it.row_offset() == desired_row_offset) { if ((!set) || ((it.screen_column() <= x) && (allow_eol || (!it.is_eol())))) { scan_iter = *it.iterator(); set = true; } } } if (scan_iter != *m_iterator) { *m_iterator = scan_iter; moved = true; determine_new_cursor_screen_row(); } break; } } return moved; } void BufferView::scroll_view_up(int n_lines, bool allow_eol) { int orig_cursor_screen_row = m_cursor_screen_row; int so = effective_scroll_offset(); int rows_to_move_cursor = (so + n_lines) - (m_height - m_cursor_screen_row - 1); rows_to_move_cursor = std::min(calculate_rows_above_screen(rows_to_move_cursor), rows_to_move_cursor); int actual_lines_moved = 0; while (rows_to_move_cursor-- > 0) { if (move_cursor_screen_row_up(allow_eol)) { actual_lines_moved++; update(); } else { break; } } m_cursor_screen_row = orig_cursor_screen_row + (n_lines - actual_lines_moved); } void BufferView::scroll_view_down(int n_lines, bool allow_eol) { int orig_cursor_screen_row = m_cursor_screen_row; int so = effective_scroll_offset(); int rows_to_move_cursor = (so + n_lines) - m_cursor_screen_row; rows_to_move_cursor = std::min(calculate_rows_below_screen(rows_to_move_cursor), rows_to_move_cursor); int actual_lines_moved = 0; while (rows_to_move_cursor-- > 0) { if (move_cursor_screen_row_down(allow_eol)) { actual_lines_moved++; update(); } else { break; } } m_cursor_screen_row = orig_cursor_screen_row - (n_lines - actual_lines_moved); } /************************************************************************** * Internal functions *************************************************************************/ void BufferView::determine_new_cursor_screen_row() { if (m_lines.size() > 0) { if (m_iterator->line() < m_lines.begin()->line->line()) { m_cursor_screen_row = 0; return; } if (m_iterator->line() > m_lines.rbegin()->line->line()) { m_cursor_screen_row = m_height - 1; return; } for (const LineDescriptor & screen_line : m_lines) { if (screen_line.line->line() == m_iterator->line()) { m_cursor_screen_row = screen_line.row_offset + m_cursor_row_offset; return; } } } } int BufferView::calculate_rows_in_line(std::shared_ptr start_of_line) { int saved_row_offset = 0; for (auto it = horiz_iter(start_of_line); it.is_valid(); it++) { if (!it.is_eol()) { saved_row_offset = it.row_offset(); } else { break; } } return saved_row_offset + 1; } int BufferView::calculate_rows_in_cursor_line(std::shared_ptr start_of_line) { int saved_row_offset = 0; for (auto it = horiz_iter(start_of_line); it.is_valid(); it++) { if (*it.iterator() == *m_iterator) { m_cursor_virtual_column = it.virtual_column(); m_cursor_screen_column = it.screen_column(); if (m_update_target_column) { m_target_screen_column = m_cursor_screen_column; m_target_virtual_column = m_cursor_virtual_column; } m_cursor_row_offset = it.row_offset(); } if (!it.is_eol()) { saved_row_offset = it.row_offset(); } else { break; } } m_update_target_column = false; return saved_row_offset + 1; } int BufferView::screen_rows_below_line(std::shared_ptr line) { auto i = std::make_shared(*line); int rows = 0; while ((rows < m_height) && i->go_next_line()) { rows += calculate_rows_in_line(i); } return rows; } int BufferView::screen_rows_above_line( std::shared_ptr line, std::list>> & backward_lines) { auto i = std::make_shared(*line); int rows = 0; while ((rows < m_height) && 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, std::make_shared(*i))); } return rows; } void BufferView::move_forward_to_column(int to_column, bool allow_eol) { auto start_of_line = std::make_shared(*m_iterator); start_of_line->go_start_of_line(); for (auto it = horiz_iter(start_of_line); it.is_valid(); it++) { if (allow_eol || (!it.is_eol())) { if (it.virtual_column() <= to_column) { *m_iterator = *it.iterator(); } else { break; } } } } bool BufferView::move_cursor_screen_row_up(bool allow_eol) { auto cursor_line_walker = get_cursor_line_walker(); auto iterator_copy = m_iterator->clonep(); if (cursor_line_walker.row_offset() > 0) { /* There are screen rows above the current in the current line. */ iterator_copy->go_start_of_line(); move_forward_to_screen_position(iterator_copy, cursor_line_walker.row_offset() - 1, allow_eol); } else { if (!iterator_copy->go_previous_line()) { return false; } int target_row_offset = calculate_rows_in_line(iterator_copy) - 1; move_forward_to_screen_position(iterator_copy, target_row_offset, allow_eol); } return true; } bool BufferView::move_cursor_screen_row_down(bool allow_eol) { auto cursor_line_walker = get_cursor_line_walker(); int target_row_offset = cursor_line_walker.row_offset() + 1; bool moved = false; for (; cursor_line_walker.is_valid(); cursor_line_walker++) { if (cursor_line_walker.row_offset() == target_row_offset) { if (((!cursor_line_walker.is_eol()) || allow_eol) && ((!moved) || (cursor_line_walker.screen_column() <= m_target_screen_column))) { *m_iterator = *cursor_line_walker.iterator(); m_cursor_row_offset = cursor_line_walker.row_offset(); moved = true; } } else if (cursor_line_walker.row_offset() > target_row_offset) { break; } } if (!moved) { auto next_line = m_iterator->clonep(); if (!next_line->go_next_line()) { return false; } move_forward_to_screen_position(next_line, 0, allow_eol); } return true; } void BufferView::move_forward_to_screen_position( std::shared_ptr line, int target_row_offset, bool allow_eol) { *m_iterator = *line; bool moved_to_target_row = false; for (auto it = horiz_iter(line); it.is_valid(); it++) { if (((!it.is_eol()) || allow_eol) && ((it.row_offset() < target_row_offset) || (!moved_to_target_row) || (it.screen_column() <= m_target_screen_column))) { *m_iterator = *it.iterator(); m_cursor_row_offset = it.row_offset(); if (it.row_offset() == target_row_offset) { moved_to_target_row = true; } } else { break; } } } int BufferView::calculate_rows_above_screen(int stop_at) { if (m_lines.empty()) return 0; const LineDescriptor & first_line = *m_lines.begin(); auto iterator = std::make_shared(*first_line.line); int lines = std::max(0, -first_line.row_offset); while (lines < stop_at) { if (!iterator->go_previous_line()) break; lines += calculate_rows_in_line(iterator); } return lines; } int BufferView::calculate_rows_below_screen(int stop_at) { if (m_lines.empty()) return 0; const LineDescriptor & last_line = *m_lines.rbegin(); auto iterator = std::make_shared(*last_line.line); int lines = std::max(0, last_line.row_offset + last_line.n_rows - m_height); while (lines < stop_at) { if (!iterator->go_next_line()) break; lines += calculate_rows_in_line(iterator); } return lines; } int BufferView::n_screen_rows_with_content() const { if (m_lines.size() > 0u) { auto line_desc = m_lines.rbegin(); return line_desc->row_offset + line_desc->n_rows; } return 0; } BufferLineWalker BufferView::get_cursor_line_walker() { auto start_of_line = m_iterator->clonep(); start_of_line->go_start_of_line(); for (auto it = horiz_iter(start_of_line); it.is_valid(); it++) { if (*it.iterator() == *m_iterator) { return it; } } return horiz_iter(start_of_line); }