From 8e0796decf7aee9dbda43763220e5d2ff8e1c9e0 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Tue, 29 Aug 2017 23:03:49 -0400 Subject: [PATCH] move much of the buffer layout code from BufferPane to BufferView --- src/core/BufferView.cc | 125 ++++++++++++++++++++++++++++++++++++ src/core/BufferView.h | 18 +++++- test/src/test_BufferView.cc | 67 +++++++++++++++++++ 3 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 test/src/test_BufferView.cc diff --git a/src/core/BufferView.cc b/src/core/BufferView.cc index 1d237c0..32bc785 100644 --- a/src/core/BufferView.cc +++ b/src/core/BufferView.cc @@ -1,4 +1,6 @@ #include "BufferView.h" +#include +#include BufferView::BufferView(std::shared_ptr buffer, std::shared_ptr iterator, @@ -8,6 +10,9 @@ BufferView::BufferView(std::shared_ptr buffer, 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_buffer_line_walker = std::make_shared(buffer, character_width_determiner); } @@ -21,3 +26,123 @@ void BufferView::set_scroll_offset(int scroll_offset) { m_scroll_offset = std::max(0, scroll_offset); } + +void BufferView::iter_lines( + std::function callback) +{ + if (m_iterator->valid()) + { + update(); + Buffer::Iterator i = *m_first_line; + int row_offset = m_first_line_row_offset; + while (row_offset < m_height) + { + callback(row_offset, i); + row_offset += calculate_rows_in_line(i); + if (!i.go_next_line()) + break; + } + } +} + +/************************************************************************** + * Internal functions + *************************************************************************/ + +void BufferView::update() +{ + 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_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. */ + m_first_line = std::make_shared(*m_iterator); + m_first_line_row_offset = m_cursor_screen_row - m_cursor_row_offset; + if (m_first_line_row_offset <= 0) + { + m_first_line->go_start_of_line(); + } + else for (auto rows_iterator_pair : backward_lines) + { + m_first_line_row_offset -= rows_iterator_pair.first; + m_first_line = rows_iterator_pair.second; + if (m_first_line_row_offset <= 0) + { + break; + } + } +} + +int BufferView::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) -> bool { + uint32_t code_point = *i; + if ((code_point != '\n') && + (code_point != Buffer::Iterator::INVALID_CODE_POINT)) + { + saved_row_offset = row_offset; + return true; + } + return false; + }); + return saved_row_offset + 1; +} + +int BufferView::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) -> bool { + 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 true; + } + return false; + }); + return saved_row_offset + 1; +} + +int BufferView::screen_rows_below_line(const Buffer::Iterator & line) +{ + Buffer::Iterator i = 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( + const Buffer::Iterator & line, + std::list>> & backward_lines) +{ + Buffer::Iterator i = 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; +} diff --git a/src/core/BufferView.h b/src/core/BufferView.h index f7a9eab..441c99f 100644 --- a/src/core/BufferView.h +++ b/src/core/BufferView.h @@ -18,25 +18,41 @@ public: CharacterWidthDeterminer & character_width_determiner); void resize(int width, int height); void set_scroll_offset(int scroll_offset); - void iter_lines(std::function callback); + void iter_lines( + std::function callback); void walk_line( const Buffer::Iterator & start_of_line, std::function callback) { m_buffer_line_walker->walk_line(start_of_line, callback); } + int cursor_screen_row() const { return m_cursor_screen_row; } + int cursor_screen_column() const { return m_cursor_screen_column; } + int cursor_virtual_column() const { return m_cursor_virtual_column; } protected: std::shared_ptr m_iterator; + std::shared_ptr m_first_line; + int m_first_line_row_offset; int m_height; int m_scroll_offset; int m_cursor_screen_row; + int m_cursor_screen_column; + int m_cursor_virtual_column; + int m_cursor_row_offset; std::shared_ptr m_buffer_line_walker; int effective_scroll_offset() { return std::min(m_scroll_offset, (m_height - 1) / 2); } + void update(); + int calculate_rows_in_line(const Buffer::Iterator & start_of_line); + int calculate_rows_in_cursor_line(const Buffer::Iterator & start_of_line); + int screen_rows_below_line(const Buffer::Iterator & line); + int screen_rows_above_line( + const Buffer::Iterator & line, + std::list>> & backward_lines); }; #endif diff --git a/test/src/test_BufferView.cc b/test/src/test_BufferView.cc new file mode 100644 index 0000000..4375adf --- /dev/null +++ b/test/src/test_BufferView.cc @@ -0,0 +1,67 @@ +#include "gtest/gtest.h" +#include "BufferView.h" + +static std::shared_ptr buffer1() +{ + static const char data[] = + "0\n" + "1ab\n" + "2abc\n" + "3\n" + "4\n" + "5\n" + "6abcdefg\n" + "7\n" + "8\n" + "9abcdefghijklmno\n" + "10\n" + "11\n" + "12\n"; + return std::make_shared((const uint8_t *)data, strlen(data)); +} + +class MyCharacterWidthDeterminer : public CharacterWidthDeterminer +{ + int operator()(uint32_t character) + { + if (character < 32) + { + return 2; + } + else + { + return 1; + } + } +} Cwd; + +static int LineNumber(const Buffer::Iterator & iterator) +{ + int line_number = 0; + Buffer::Iterator i = iterator; + for (;;) + { + uint32_t c = *i; + if ((c < '0') || (c > '9')) + break; + line_number *= 10; + line_number += ((int)c - '0'); + i.go_forward(); + } + return line_number; +} + +TEST(BufferViewTest, iterates_through_screen_lines_with_no_wrapping) +{ + std::shared_ptr b = buffer1(); + std::shared_ptr iterator = b->add_iterator(); + BufferView bv(b, iterator, Cwd); + bv.resize(40, 8); + int times_called = 0; + bv.iter_lines([×_called](int row_offset, const Buffer::Iterator & iterator) -> bool { + EXPECT_EQ(times_called, LineNumber(iterator)); + times_called++; + return true; + }); + EXPECT_EQ(8, times_called); +}