diff --git a/src/core/BufferView.cc b/src/core/BufferView.cc index 7e42296..7b87b7e 100644 --- a/src/core/BufferView.cc +++ b/src/core/BufferView.cc @@ -1,6 +1,7 @@ #include "BufferView.h" #include #include +#include BufferView::BufferView(std::shared_ptr buffer, std::shared_ptr iterator, @@ -16,6 +17,8 @@ BufferView::BufferView(std::shared_ptr buffer, m_cursor_screen_column = 0; m_cursor_virtual_column = 0; m_cursor_row_offset = 0; + m_target_column = 0; + m_update_target_column = false; } void BufferView::resize(int width, int height) @@ -42,9 +45,6 @@ void BufferView::update() start_of_line->go_start_of_line(); int rows_in_cursor_line = calculate_rows_in_cursor_line(start_of_line); - /* Update the cursor screen row in case the cursor position changed. */ - m_cursor_screen_row = determine_new_cursor_screen_row(); - /* Limit the cursor screen row taking into account view dimensions, * available buffer contents above and below the current line, and scroll * offset. */ @@ -98,31 +98,127 @@ void BufferView::update() } } +bool BufferView::cursor_move(CursorMovement which, 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_column = 0; + break; + case CursorMovement::EOL: + moved = m_iterator->go_end_of_line(allow_eol); + m_target_column = INT_MAX; + break; + case CursorMovement::FIRST_LINE: + { + auto it = m_buffer->begin(); + if (it != *m_iterator) + { + *m_iterator = it; + moved = true; + } + } + m_target_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_column = 0; + break; + case CursorMovement::SCREEN_ROW_UP: + /* TODO */ +#if 0 + moved = move_cursor_screen_row_up(); +#endif + break; + case CursorMovement::SCREEN_ROW_DOWN: + /* TODO */ +#if 0 + moved = move_cursor_screen_row_down(); +#endif + break; + } + if (moved) + { + switch (which) + { + case CursorMovement::LEFT: + case CursorMovement::RIGHT: + determine_new_cursor_screen_row(); + break; + case CursorMovement::UP: + case CursorMovement::DOWN: + move_forward_to_column(m_target_column, allow_eol); + determine_new_cursor_screen_row(); + break; + case CursorMovement::SOL: + case CursorMovement::EOL: + 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; +} + /************************************************************************** * Internal functions *************************************************************************/ -int BufferView::determine_new_cursor_screen_row() +void BufferView::determine_new_cursor_screen_row() { if (m_lines.size() > 0) { if (m_iterator->line() < m_lines.begin()->line->line()) { - return 0; + m_cursor_screen_row = 0; + return; } if (m_iterator->line() > m_lines.rbegin()->line->line()) { - return m_height; + m_cursor_screen_row = m_height - 1; + return; } for (const LineDescriptor & screen_line : m_lines) { if (screen_line.line->line() == m_iterator->line()) { - return screen_line.row_offset + m_cursor_row_offset; + m_cursor_screen_row = screen_line.row_offset + m_cursor_row_offset; + return; } } } - return 0; } int BufferView::calculate_rows_in_line(std::shared_ptr start_of_line) @@ -151,6 +247,10 @@ int BufferView::calculate_rows_in_cursor_line(std::shared_ptr { m_cursor_virtual_column = it.virtual_column(); m_cursor_screen_column = it.screen_column(); + if (m_update_target_column) + { + m_target_column = m_cursor_virtual_column; + } m_cursor_row_offset = it.row_offset(); } if (!it.is_eol()) @@ -162,6 +262,7 @@ int BufferView::calculate_rows_in_cursor_line(std::shared_ptr break; } } + m_update_target_column = false; return saved_row_offset + 1; } @@ -190,3 +291,23 @@ int BufferView::screen_rows_above_line( } 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; + } + } + } +} diff --git a/src/core/BufferView.h b/src/core/BufferView.h index f093151..44364c0 100644 --- a/src/core/BufferView.h +++ b/src/core/BufferView.h @@ -14,6 +14,20 @@ class BufferView { public: + enum class CursorMovement : uint8_t + { + LEFT, + RIGHT, + UP, + DOWN, + SOL, + EOL, + FIRST_LINE, + LAST_LINE, + SCREEN_ROW_UP, + SCREEN_ROW_DOWN, + }; + class Iterator { public: @@ -62,22 +76,16 @@ public: { return BufferLineWalker(m_buffer, start_of_line, m_width, m_character_width_determiner); } + bool cursor_move(CursorMovement which, bool allow_eol); 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; } + void set_cursor_screen_row(int cursor_screen_row) + { + m_cursor_screen_row = cursor_screen_row; + } protected: - std::shared_ptr m_buffer; - std::shared_ptr m_iterator; - int m_width; - 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; - CharacterWidthDeterminer & m_character_width_determiner; - struct LineDescriptor { int row_offset; @@ -85,18 +93,32 @@ protected: std::shared_ptr line; }; + std::shared_ptr m_buffer; + std::shared_ptr m_iterator; + std::vector m_lines; + CharacterWidthDeterminer & m_character_width_determiner; + int m_width; + 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; + int m_target_column; + bool m_update_target_column; + int effective_scroll_offset() { return std::min(m_scroll_offset, (m_height - 1) / 2); } - int determine_new_cursor_screen_row(); + void determine_new_cursor_screen_row(); int calculate_rows_in_line(std::shared_ptr start_of_line); int calculate_rows_in_cursor_line(std::shared_ptr start_of_line); int screen_rows_below_line(std::shared_ptr line); int screen_rows_above_line( std::shared_ptr line, std::list>> & backward_lines); - std::vector m_lines; + void move_forward_to_column(int to_column, bool allow_eol); }; #endif diff --git a/test/src/test_BufferView.cc b/test/src/test_BufferView.cc index 9238df9..11d5ef6 100644 --- a/test/src/test_BufferView.cc +++ b/test/src/test_BufferView.cc @@ -1,6 +1,8 @@ #include "gtest/gtest.h" #include "BufferView.h" +#define C(x) ((uint32_t)(x)) + static std::shared_ptr buffer1() { static const char data[] = @@ -44,6 +46,7 @@ static int LineNumber(std::shared_ptr iterator) { int line_number = 0; auto i = std::make_shared(*iterator); + i->go_start_of_line(); for (;;) { uint32_t c = **i; @@ -146,46 +149,46 @@ TEST(BufferViewTest, adjusts_cursor_row_as_cursor_moves) bv.resize(5, 5); bv.update(); EXPECT_EQ(0, bv.cursor_screen_row()); - iterator->go_next_line(); // to 1 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 1 bv.update(); EXPECT_EQ(1, bv.cursor_screen_row()); - iterator->go_next_line(); // to 2 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 2 bv.update(); EXPECT_EQ(2, bv.cursor_screen_row()); - iterator->go_next_line(); // to 3 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 3 bv.update(); EXPECT_EQ(3, bv.cursor_screen_row()); - iterator->go_next_line(); // to 4 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 4 bv.update(); EXPECT_EQ(4, bv.cursor_screen_row()); - iterator->go_next_line(); // to 5 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 5 bv.update(); EXPECT_EQ(4, bv.cursor_screen_row()); - iterator->go_next_line(); // to 6 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 6 bv.update(); EXPECT_EQ(4, bv.cursor_screen_row()); - iterator->go_next_line(); // to 7 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 7 bv.update(); EXPECT_EQ(4, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 6 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 6 bv.update(); EXPECT_EQ(2, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 5 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 5 bv.update(); EXPECT_EQ(1, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 4 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 4 bv.update(); EXPECT_EQ(0, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 3 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 3 bv.update(); EXPECT_EQ(0, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 2 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 2 bv.update(); EXPECT_EQ(0, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 1 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 1 bv.update(); EXPECT_EQ(0, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 0 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 0 bv.update(); EXPECT_EQ(0, bv.cursor_screen_row()); } @@ -199,51 +202,51 @@ TEST(BufferViewTest, adjusts_cursor_row_as_cursor_moves_with_scroll_offset_1) bv.set_scroll_offset(1); bv.update(); EXPECT_EQ(0, bv.cursor_screen_row()); - iterator->go_next_line(); // to 1 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 1 bv.update(); EXPECT_EQ(1, bv.cursor_screen_row()); - iterator->go_next_line(); // to 2 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 2 bv.update(); EXPECT_EQ(2, bv.cursor_screen_row()); - iterator->go_next_line(); // to 3 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 3 bv.update(); EXPECT_EQ(3, bv.cursor_screen_row()); - iterator->go_next_line(); // to 4 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 4 bv.update(); EXPECT_EQ(3, bv.cursor_screen_row()); - iterator->go_next_line(); // to 5 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 5 bv.update(); EXPECT_EQ(3, bv.cursor_screen_row()); - iterator->go_next_line(); // to 6 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 6 bv.update(); EXPECT_EQ(3, bv.cursor_screen_row()); - iterator->go_next_line(); // to 7 + bv.cursor_move(BufferView::CursorMovement::DOWN, false); // to 7 bv.update(); EXPECT_EQ(3, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 6 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 6 bv.update(); EXPECT_EQ(1, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 5 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 5 bv.update(); EXPECT_EQ(1, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 4 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 4 bv.update(); EXPECT_EQ(1, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 3 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 3 bv.update(); EXPECT_EQ(1, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 2 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 2 bv.update(); EXPECT_EQ(1, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 1 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 1 bv.update(); EXPECT_EQ(1, bv.cursor_screen_row()); - iterator->go_previous_line(); // to 0 + bv.cursor_move(BufferView::CursorMovement::UP, false); // to 0 bv.update(); EXPECT_EQ(0, bv.cursor_screen_row()); } -TEST(BufferViewTest, moves_view_down_while_traversing_a_wrapped_line) +TEST(BufferViewTest, moves_view_down_while_traversing_a_wrapped_line_right) { auto b = buffer1(); auto iterator = b->add_iterator(); @@ -251,15 +254,16 @@ TEST(BufferViewTest, moves_view_down_while_traversing_a_wrapped_line) bv.resize(4, 10); for (int i = 0; i < 9; i++) { - iterator->go_next_line(); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::DOWN, false)); bv.update(); } EXPECT_EQ(9, bv.cursor_screen_row()); EXPECT_EQ(0, bv.cursor_screen_column()); EXPECT_EQ(0, bv.cursor_virtual_column()); + EXPECT_EQ(C('9'), **iterator); for (int i = 0; i < 3; i++) { - iterator->go_forward(); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::RIGHT, false)); bv.update(); EXPECT_EQ(9, bv.cursor_screen_row()); EXPECT_EQ(1 + i, bv.cursor_screen_column()); @@ -267,7 +271,7 @@ TEST(BufferViewTest, moves_view_down_while_traversing_a_wrapped_line) } for (int i = 0; i < 4; i++) { - iterator->go_forward(); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::RIGHT, false)); bv.update(); EXPECT_EQ(9, bv.cursor_screen_row()); EXPECT_EQ(i, bv.cursor_screen_column()); @@ -275,10 +279,191 @@ TEST(BufferViewTest, moves_view_down_while_traversing_a_wrapped_line) } for (int i = 0; i < 4; i++) { - iterator->go_forward(); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::RIGHT, false)); bv.update(); EXPECT_EQ(9, bv.cursor_screen_row()); EXPECT_EQ(i, bv.cursor_screen_column()); EXPECT_EQ(8 + i, bv.cursor_virtual_column()); } + EXPECT_EQ(C('k'), **iterator); +} + +TEST(BufferViewTest, moves_view_down_while_jumping_to_the_end_of_a_wrapped_line) +{ + auto b = buffer1(); + auto iterator = b->add_iterator(); + BufferView bv(b, iterator, Cwd); + bv.resize(4, 10); + for (int i = 0; i < 9; i++) + { + bv.cursor_move(BufferView::CursorMovement::DOWN, false); + bv.update(); + } + EXPECT_EQ(9, bv.cursor_screen_row()); + EXPECT_EQ(0, bv.cursor_screen_column()); + EXPECT_EQ(0, bv.cursor_virtual_column()); + EXPECT_EQ(C('9'), **iterator); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::EOL, false)); + bv.update(); + EXPECT_EQ(9, bv.cursor_screen_row()); + EXPECT_EQ(3, bv.cursor_screen_column()); + EXPECT_EQ(15, bv.cursor_virtual_column()); + EXPECT_EQ(C('o'), **iterator); +} + +TEST(BufferViewTest, moves_view_up_while_traversing_a_wrapped_line_left) +{ + auto b = buffer1(); + auto iterator = b->add_iterator(); + BufferView bv(b, iterator, Cwd); + bv.resize(2, 3); + for (int i = 0; i < 4; i++) + { + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::DOWN, false)); + bv.update(); + } + EXPECT_EQ(2, bv.cursor_screen_row()); + EXPECT_EQ(0, bv.cursor_screen_column()); + EXPECT_EQ(0, bv.cursor_virtual_column()); + for (int i = 0; i < 4; i++) + { + iterator->go_back(); + } + /* Cursor at end of line 2 */ + bv.set_cursor_screen_row(0); + bv.update(); + EXPECT_EQ(0, bv.cursor_screen_row()); + EXPECT_EQ(1, bv.cursor_screen_column()); + EXPECT_EQ(3, bv.cursor_virtual_column()); + EXPECT_EQ(C('c'), **iterator); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::LEFT, false)); + bv.update(); + EXPECT_EQ(0, bv.cursor_screen_row()); + EXPECT_EQ(0, bv.cursor_screen_column()); + EXPECT_EQ(2, bv.cursor_virtual_column()); + EXPECT_EQ(C('b'), **iterator); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::LEFT, false)); + bv.update(); + EXPECT_EQ(0, bv.cursor_screen_row()); + EXPECT_EQ(1, bv.cursor_screen_column()); + EXPECT_EQ(1, bv.cursor_virtual_column()); + EXPECT_EQ(C('a'), **iterator); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::LEFT, false)); + bv.update(); + EXPECT_EQ(0, bv.cursor_screen_row()); + EXPECT_EQ(0, bv.cursor_screen_column()); + EXPECT_EQ(0, bv.cursor_virtual_column()); + EXPECT_EQ(C('2'), **iterator); + EXPECT_FALSE(bv.cursor_move(BufferView::CursorMovement::LEFT, false)); + bv.update(); + EXPECT_EQ(0, bv.cursor_screen_row()); + EXPECT_EQ(0, bv.cursor_screen_column()); + EXPECT_EQ(0, bv.cursor_virtual_column()); + EXPECT_EQ(C('2'), **iterator); +} + +TEST(BufferViewTest, moves_view_up_while_jumping_to_start_of_a_wrapped_line) +{ + auto b = buffer1(); + auto iterator = b->add_iterator(); + BufferView bv(b, iterator, Cwd); + bv.resize(2, 3); + for (int i = 0; i < 4; i++) + { + bv.cursor_move(BufferView::CursorMovement::DOWN, false); + bv.update(); + } + EXPECT_EQ(2, bv.cursor_screen_row()); + EXPECT_EQ(0, bv.cursor_screen_column()); + EXPECT_EQ(0, bv.cursor_virtual_column()); + for (int i = 0; i < 4; i++) + { + iterator->go_back(); + } + /* Cursor at end of line 2 */ + bv.set_cursor_screen_row(0); + bv.update(); + EXPECT_EQ(0, bv.cursor_screen_row()); + EXPECT_EQ(1, bv.cursor_screen_column()); + EXPECT_EQ(3, bv.cursor_virtual_column()); + EXPECT_EQ(C('c'), **iterator); + bv.cursor_move(BufferView::CursorMovement::SOL, false); + bv.update(); + EXPECT_EQ(0, bv.cursor_screen_row()); + EXPECT_EQ(0, bv.cursor_screen_column()); + EXPECT_EQ(0, bv.cursor_virtual_column()); + EXPECT_EQ(C('2'), **iterator); +} + +TEST(BufferViewTest, warps_view_when_jumping_to_beginning_and_end_of_buffer) +{ + auto b = buffer1(); + auto iterator = b->add_iterator(); + BufferView bv(b, iterator, Cwd); + bv.resize(4, 4); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::LAST_LINE, false)); + bv.update(); + EXPECT_EQ(12, LineNumber(iterator)); + EXPECT_EQ(3, bv.cursor_screen_row()); + EXPECT_EQ(0, bv.cursor_screen_column()); + EXPECT_EQ(0, bv.cursor_virtual_column()); + EXPECT_FALSE(bv.cursor_move(BufferView::CursorMovement::LAST_LINE, false)); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::FIRST_LINE, false)); + bv.update(); + EXPECT_EQ(0, LineNumber(iterator)); + EXPECT_EQ(0, bv.cursor_screen_row()); + EXPECT_EQ(0, bv.cursor_screen_column()); + EXPECT_EQ(0, bv.cursor_virtual_column()); + EXPECT_FALSE(bv.cursor_move(BufferView::CursorMovement::FIRST_LINE, false)); +} + +TEST(BufferViewTest, sets_target_column_to_eol_when_jumping_to_SOL_and_EOL) +{ + auto b = buffer1(); + auto iterator = b->add_iterator(); + BufferView bv(b, iterator, Cwd); + bv.resize(4, 4); + bv.cursor_move(BufferView::CursorMovement::EOL, false); + bv.update(); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::DOWN, false)); + bv.update(); + EXPECT_EQ(1, bv.cursor_screen_row()); + EXPECT_EQ(C('b'), **iterator); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::UP, false)); + bv.update(); + EXPECT_EQ(0, bv.cursor_screen_row()); + bv.cursor_move(BufferView::CursorMovement::SOL, false); + bv.update(); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::DOWN, false)); + EXPECT_EQ(C('1'), **iterator); + EXPECT_EQ(1, bv.cursor_screen_row()); +} + +TEST(BufferViewTest, sets_target_column_when_traversing_left_and_right) +{ + auto b = buffer1(); + auto iterator = b->add_iterator(); + BufferView bv(b, iterator, Cwd); + bv.resize(4, 4); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::DOWN, false)); + bv.update(); + EXPECT_EQ(1, LineNumber(iterator)); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::RIGHT, false)); + bv.update(); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::RIGHT, false)); + bv.update(); + EXPECT_EQ(1, LineNumber(iterator)); + EXPECT_EQ(C('b'), **iterator); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::DOWN, false)); + bv.update(); + EXPECT_EQ(2, LineNumber(iterator)); + EXPECT_EQ(C('b'), **iterator); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::LEFT, false)); + bv.update(); + EXPECT_EQ(2, LineNumber(iterator)); + EXPECT_EQ(C('a'), **iterator); + EXPECT_TRUE(bv.cursor_move(BufferView::CursorMovement::UP, false)); + bv.update(); + EXPECT_EQ(1, LineNumber(iterator)); + EXPECT_EQ(C('a'), **iterator); }