jes/src-c/core/BufferView.cc
2018-07-25 20:47:02 -04:00

634 lines
19 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::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<Buffer::Iterator> 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<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)
{
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<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;
}
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);
}