#include "Test.h" #include "Buffer.h" #include #include #include #include static std::vector split_lines_preserve_trailing_empty(const std::string &s) { std::vector out; std::size_t start = 0; for (std::size_t i = 0; i <= s.size(); i++) { if (i == s.size() || s[i] == '\n') { out.push_back(s.substr(start, i - start)); start = i + 1; } } if (out.empty()) out.push_back(std::string()); return out; } static std::vector line_starts_for(const std::string &s) { std::vector starts; starts.push_back(0); for (std::size_t i = 0; i < s.size(); i++) { if (s[i] == '\n') starts.push_back(i + 1); } return starts; } static std::size_t ref_linecol_to_offset(const std::string &s, std::size_t row, std::size_t col) { auto starts = line_starts_for(s); if (starts.empty()) return 0; if (row >= starts.size()) return s.size(); std::size_t start = starts[row]; std::size_t end = (row + 1 < starts.size()) ? starts[row + 1] : s.size(); if (end > start && s[end - 1] == '\n') end -= 1; // clamp before trailing newline return start + std::min(col, end - start); } static void check_buffer_matches_model(const Buffer &b, const std::string &model) { auto expected_lines = split_lines_preserve_trailing_empty(model); const auto &rows = b.Rows(); ASSERT_EQ(rows.size(), expected_lines.size()); ASSERT_EQ(b.Nrows(), rows.size()); auto starts = line_starts_for(model); ASSERT_EQ(starts.size(), expected_lines.size()); std::string via_views; for (std::size_t i = 0; i < rows.size(); i++) { ASSERT_EQ(std::string(rows[i]), expected_lines[i]); ASSERT_EQ(b.GetLineString(i), expected_lines[i]); std::size_t exp_start = starts[i]; std::size_t exp_end = (i + 1 < starts.size()) ? starts[i + 1] : model.size(); auto r = b.GetLineRange(i); ASSERT_EQ(r.first, exp_start); ASSERT_EQ(r.second, exp_end); auto v = b.GetLineView(i); ASSERT_EQ(std::string(v), model.substr(exp_start, exp_end - exp_start)); via_views.append(v.data(), v.size()); } ASSERT_EQ(via_views, model); } TEST (Buffer_RowsCache_MultiLineEdits_StayConsistent) { Buffer b; std::string model; check_buffer_matches_model(b, model); // Insert text and newlines in a few different ways. b.insert_text(0, 0, std::string("abc")); model.insert(0, "abc"); check_buffer_matches_model(b, model); b.split_line(0, 1); // a\nbc model.insert(ref_linecol_to_offset(model, 0, 1), "\n"); check_buffer_matches_model(b, model); b.insert_text(1, 2, std::string("X")); // a\nbcX model.insert(ref_linecol_to_offset(model, 1, 2), "X"); check_buffer_matches_model(b, model); b.join_lines(0); // abcX { std::size_t off = ref_linecol_to_offset(model, 0, std::numeric_limits::max()); if (off < model.size() && model[off] == '\n') model.erase(off, 1); } check_buffer_matches_model(b, model); // Insert a multi-line segment in one shot. b.insert_text(0, 2, std::string("\n123\nxyz")); model.insert(ref_linecol_to_offset(model, 0, 2), "\n123\nxyz"); check_buffer_matches_model(b, model); // Delete spanning across a newline. b.delete_text(0, 1, 5); { std::size_t start = ref_linecol_to_offset(model, 0, 1); std::size_t actual = std::min(5, model.size() - start); model.erase(start, actual); } check_buffer_matches_model(b, model); // Insert/delete whole rows. b.insert_row(1, std::string_view("ROW")); model.insert(ref_linecol_to_offset(model, 1, 0), "ROW\n"); check_buffer_matches_model(b, model); b.delete_row(1); { auto starts = line_starts_for(model); if (1 < (int) starts.size()) { std::size_t start = starts[1]; std::size_t end = (2 < starts.size()) ? starts[2] : model.size(); model.erase(start, end - start); } } check_buffer_matches_model(b, model); }