480 lines
15 KiB
C++
480 lines
15 KiB
C++
#include "BufferView.h"
|
|
#include <list>
|
|
#include <utility>
|
|
#include <limits.h>
|
|
|
|
BufferView::BufferView(std::shared_ptr<Buffer> buffer,
|
|
std::shared_ptr<Buffer::Iterator> 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<Buffer::Iterator>(*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<std::pair<int, std::shared_ptr<Buffer::Iterator>>> 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<Buffer::Iterator>(*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<Buffer::Iterator>(*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::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::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;
|
|
}
|
|
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::FIRST_LINE:
|
|
m_cursor_screen_row = 0;
|
|
break;
|
|
case CursorMovement::LAST_LINE:
|
|
m_cursor_screen_row = m_height - 1;
|
|
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<Buffer::Iterator> 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<Buffer::Iterator> 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<Buffer::Iterator> line)
|
|
{
|
|
auto i = std::make_shared<Buffer::Iterator>(*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<Buffer::Iterator> line,
|
|
std::list<std::pair<int, std::shared_ptr<Buffer::Iterator>>> & backward_lines)
|
|
{
|
|
auto i = std::make_shared<Buffer::Iterator>(*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<int, std::shared_ptr<Buffer::Iterator>>(rows_in_this_line, std::make_shared<Buffer::Iterator>(*i)));
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
void BufferView::move_forward_to_column(int to_column, bool allow_eol)
|
|
{
|
|
auto start_of_line = std::make_shared<Buffer::Iterator>(*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)
|
|
{
|
|
if (m_cursor_row_offset > 0)
|
|
{
|
|
/* There are screen rows above the current in the current line. */
|
|
auto start_of_line = std::make_shared<Buffer::Iterator>(*m_iterator);
|
|
start_of_line->go_start_of_line();
|
|
move_forward_to_screen_position(start_of_line, m_cursor_row_offset - 1, allow_eol);
|
|
}
|
|
else
|
|
{
|
|
auto previous_line = std::make_shared<Buffer::Iterator>(*m_iterator);
|
|
if (!previous_line->go_previous_line())
|
|
{
|
|
return false;
|
|
}
|
|
int target_row_offset = calculate_rows_in_line(previous_line) - 1;
|
|
move_forward_to_screen_position(previous_line, target_row_offset, allow_eol);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BufferView::move_cursor_screen_row_down(bool allow_eol)
|
|
{
|
|
if (m_rows_in_cursor_line > (m_cursor_row_offset + 1))
|
|
{
|
|
/* There are screen rows below the current in the current line. */
|
|
auto start_of_line = std::make_shared<Buffer::Iterator>(*m_iterator);
|
|
start_of_line->go_start_of_line();
|
|
move_forward_to_screen_position(start_of_line, m_cursor_row_offset + 1, allow_eol);
|
|
}
|
|
else
|
|
{
|
|
auto next_line = std::make_shared<Buffer::Iterator>(*m_iterator);
|
|
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<Buffer::Iterator> 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<Buffer::Iterator>(*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<Buffer::Iterator>(*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;
|
|
}
|