aboutsummaryrefslogtreecommitdiffhomepage
path: root/test/unit/testCellBuffer.cxx
diff options
context:
space:
mode:
authorNeil <nyamatongwe@gmail.com>2022-07-31 15:51:53 +1000
committerNeil <nyamatongwe@gmail.com>2022-07-31 15:51:53 +1000
commit926cb6f7d228b347db16a45e1f2632da475da1f0 (patch)
tree6263662cb8c023502d61b61eed34baa93ed35843 /test/unit/testCellBuffer.cxx
parent535e20189d5a2dd9b43a6ea0a74749a50678d631 (diff)
downloadscintilla-mirror-926cb6f7d228b347db16a45e1f2632da475da1f0.tar.gz
Added change history which can display document changes (modified, saved, ...)
in the margin or in the text.
Diffstat (limited to 'test/unit/testCellBuffer.cxx')
-rw-r--r--test/unit/testCellBuffer.cxx593
1 files changed, 587 insertions, 6 deletions
diff --git a/test/unit/testCellBuffer.cxx b/test/unit/testCellBuffer.cxx
index ef7db099b..b6735339a 100644
--- a/test/unit/testCellBuffer.cxx
+++ b/test/unit/testCellBuffer.cxx
@@ -3,6 +3,7 @@
**/
#include <cstddef>
+#include <cassert>
#include <cstring>
#include <stdexcept>
#include <string_view>
@@ -19,6 +20,8 @@
#include "SplitVector.h"
#include "Partitioning.h"
#include "RunStyles.h"
+#include "SparseVector.h"
+#include "ChangeHistory.h"
#include "CellBuffer.h"
#include "catch.hpp"
@@ -37,7 +40,7 @@ TEST_CASE("CellBuffer") {
SECTION("InsertOneLine") {
bool startSequence = false;
- const char *cpChange = cb.InsertString(0, sText, static_cast<int>(sLength), startSequence);
+ const char *cpChange = cb.InsertString(0, sText, sLength, startSequence);
REQUIRE(startSequence);
REQUIRE(sLength == cb.Length());
REQUIRE(memcmp(cpChange, sText, sLength) == 0);
@@ -54,7 +57,7 @@ TEST_CASE("CellBuffer") {
const char sText2[] = "Two\nLines";
const Sci::Position sLength2 = static_cast<Sci::Position>(strlen(sText2));
bool startSequence = false;
- const char *cpChange = cb.InsertString(0, sText2, static_cast<int>(sLength), startSequence);
+ const char *cpChange = cb.InsertString(0, sText2, sLength2, startSequence);
REQUIRE(startSequence);
REQUIRE(sLength2 == cb.Length());
REQUIRE(memcmp(cpChange, sText2, sLength2) == 0);
@@ -64,7 +67,7 @@ TEST_CASE("CellBuffer") {
REQUIRE(4 == cb.LineStart(1));
REQUIRE(1 == cb.LineFromPosition(5));
REQUIRE(sLength2 == cb.LineStart(2));
- REQUIRE(1 == cb.LineFromPosition(static_cast<int>(sLength)));
+ REQUIRE(1 == cb.LineFromPosition(sLength2));
REQUIRE(cb.CanUndo());
REQUIRE(!cb.CanRedo());
}
@@ -74,7 +77,7 @@ TEST_CASE("CellBuffer") {
cb.SetUndoCollection(false);
REQUIRE(!cb.IsCollectingUndo());
bool startSequence = false;
- const char *cpChange = cb.InsertString(0, sText, static_cast<int>(sLength), startSequence);
+ const char *cpChange = cb.InsertString(0, sText, sLength, startSequence);
REQUIRE(!startSequence);
REQUIRE(sLength == cb.Length());
REQUIRE(memcmp(cpChange, sText, sLength) == 0);
@@ -86,7 +89,7 @@ TEST_CASE("CellBuffer") {
const char sTextDeleted[] = "ci";
const char sTextAfterDeletion[] = "Sntilla";
bool startSequence = false;
- const char *cpChange = cb.InsertString(0, sText, static_cast<int>(sLength), startSequence);
+ const char *cpChange = cb.InsertString(0, sText, sLength, startSequence);
REQUIRE(startSequence);
REQUIRE(sLength == cb.Length());
REQUIRE(memcmp(cpChange, sText, sLength) == 0);
@@ -146,7 +149,7 @@ TEST_CASE("CellBuffer") {
cb.SetReadOnly(true);
REQUIRE(cb.IsReadOnly());
bool startSequence = false;
- cb.InsertString(0, sText, static_cast<int>(sLength), startSequence);
+ cb.InsertString(0, sText, sLength, startSequence);
REQUIRE(cb.Length() == 0);
}
@@ -438,3 +441,581 @@ TEST_CASE("CharacterIndex") {
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf16) == 5);
}
}
+
+TEST_CASE("ChangeHistory") {
+
+ ChangeHistory il;
+ const EditionSet empty;
+ struct Spanner {
+ Sci::Position position = 0;
+ Sci::Position length = 0;
+ };
+
+ SECTION("Start") {
+ REQUIRE(il.Length() == 0);
+ REQUIRE(il.DeletionCount(0,0) == 0);
+ REQUIRE(il.EditionAt(0) == 0);
+ REQUIRE(il.EditionEndRun(0) == 0);
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ REQUIRE(il.EditionNextDelete(0) == 1);
+ }
+
+ SECTION("Some Space") {
+ il.Insert(0, 10, false, true);
+ REQUIRE(il.Length() == 10);
+ REQUIRE(il.DeletionCount(0,10) == 0);
+ REQUIRE(il.EditionAt(0) == 0);
+ REQUIRE(il.EditionEndRun(0) == 10);
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ REQUIRE(il.EditionNextDelete(0) == 10);
+ REQUIRE(il.EditionDeletesAt(10) == 0);
+ REQUIRE(il.EditionNextDelete(10) == 11);
+ }
+
+ SECTION("An insert") {
+ il.Insert(0, 7, false, true);
+ il.SetSavePoint();
+ il.Insert(2, 3, true, true);
+ REQUIRE(il.Length() == 10);
+ REQUIRE(il.DeletionCount(0,10) == 0);
+
+ REQUIRE(il.EditionAt(0) == 0);
+ REQUIRE(il.EditionEndRun(0) == 2);
+ REQUIRE(il.EditionAt(2) == 2);
+ REQUIRE(il.EditionEndRun(2) == 5);
+ REQUIRE(il.EditionAt(5) == 0);
+ REQUIRE(il.EditionEndRun(5) == 10);
+ REQUIRE(il.EditionAt(10) == 0);
+
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ REQUIRE(il.EditionNextDelete(0) == 10);
+ REQUIRE(il.EditionDeletesAt(10) == 0);
+ }
+
+ SECTION("A delete") {
+ il.Insert(0, 10, false, true);
+ il.SetSavePoint();
+ il.DeleteRangeSavingHistory(2, 3, true, false);
+ REQUIRE(il.Length() == 7);
+ REQUIRE(il.DeletionCount(0,7) == 1);
+
+ REQUIRE(il.EditionAt(0) == 0);
+ REQUIRE(il.EditionEndRun(0) == 7);
+ REQUIRE(il.EditionAt(7) == 0);
+
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ const EditionSet one{ 2 };
+ REQUIRE(il.EditionNextDelete(0) == 2);
+ REQUIRE(il.EditionDeletesAt(2) == 2);
+ REQUIRE(il.EditionNextDelete(2) == 7);
+ REQUIRE(il.EditionDeletesAt(7) == 0);
+ }
+
+ SECTION("Insert, delete, and undo") {
+ il.Insert(0, 9, false, true);
+ il.SetSavePoint();
+ il.Insert(3, 1, true, true);
+ REQUIRE(il.EditionEndRun(0) == 3);
+ REQUIRE(il.EditionAt(3) == 2);
+ REQUIRE(il.EditionEndRun(3) == 4);
+ REQUIRE(il.EditionAt(4) == 0);
+
+ il.DeleteRangeSavingHistory(2, 3, true, false);
+ REQUIRE(il.Length() == 7);
+ REQUIRE(il.DeletionCount(0,7) == 1);
+
+ REQUIRE(il.EditionAt(0) == 0);
+ REQUIRE(il.EditionEndRun(0) == 7);
+ REQUIRE(il.EditionAt(7) == 0);
+
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ const EditionSet one{ 2 };
+ REQUIRE(il.EditionNextDelete(0) == 2);
+ REQUIRE(il.EditionDeletesAt(2) == 2);
+ REQUIRE(il.EditionNextDelete(2) == 7);
+ REQUIRE(il.EditionDeletesAt(7) == 0);
+
+ // Undo in detail (normally inside CellBuffer::PerformUndoStep)
+ il.UndoDeleteStep(2, 3, false);
+ REQUIRE(il.Length() == 10);
+ REQUIRE(il.DeletionCount(0, 10) == 0);
+ // The insertion has reappeared
+ REQUIRE(il.EditionEndRun(0) == 3);
+ REQUIRE(il.EditionAt(3) == 2);
+ REQUIRE(il.EditionEndRun(3) == 4);
+ REQUIRE(il.EditionAt(4) == 0);
+ }
+
+ SECTION("Deletes") {
+ il.Insert(0, 10, false, true);
+ il.SetSavePoint();
+ il.DeleteRangeSavingHistory(2, 3, true, false);
+ REQUIRE(il.Length() == 7);
+ REQUIRE(il.DeletionCount(0,7) == 1);
+
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ REQUIRE(il.EditionNextDelete(0) == 2);
+ REQUIRE(il.EditionDeletesAt(2) == 2);
+ REQUIRE(il.EditionNextDelete(2) == 7);
+ REQUIRE(il.EditionDeletesAt(7) == 0);
+
+ il.DeleteRangeSavingHistory(2, 1, true, false);
+ REQUIRE(il.Length() == 6);
+ REQUIRE(il.DeletionCount(0,6) == 2);
+
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ REQUIRE(il.EditionNextDelete(0) == 2);
+ REQUIRE(il.EditionDeletesAt(2) == 2);
+ REQUIRE(il.EditionNextDelete(2) == 6);
+ REQUIRE(il.EditionDeletesAt(6) == 0);
+
+ // Undo in detail (normally inside CellBuffer::PerformUndoStep)
+ il.UndoDeleteStep(2, 1, false);
+ REQUIRE(il.Length() == 7);
+ REQUIRE(il.DeletionCount(0, 7) == 1);
+
+ // Undo in detail (normally inside CellBuffer::PerformUndoStep)
+ il.UndoDeleteStep(2, 3, false);
+ REQUIRE(il.Length() == 10);
+ REQUIRE(il.DeletionCount(0, 10) == 0);
+ }
+
+ SECTION("Deletes 101") {
+ // Deletes that hit the start and end permanent positions
+ il.Insert(0, 3, false, true);
+ il.SetSavePoint();
+ REQUIRE(il.DeletionCount(0, 2) == 0);
+ il.DeleteRangeSavingHistory(1, 1, true, false);
+ REQUIRE(il.DeletionCount(0,2) == 1);
+ const EditionSet at1 = {2};
+ REQUIRE(il.DeletionsAt(1) == at1);
+ il.DeleteRangeSavingHistory(1, 1, false, false);
+ REQUIRE(il.DeletionCount(0,1) == 2);
+ const EditionSet at2 = { 2, 3 };
+ REQUIRE(il.DeletionsAt(1) == at2);
+ il.DeleteRangeSavingHistory(0, 1, false, false);
+ const EditionSet at3 = { 2, 3, 3 };
+ REQUIRE(il.DeletionsAt(0) == at3);
+ REQUIRE(il.DeletionCount(0,0) == 3);
+
+ // Undo them
+ il.UndoDeleteStep(0, 1, false);
+ REQUIRE(il.DeletionCount(0, 1) == 2);
+ REQUIRE(il.DeletionsAt(1) == at2);
+ il.UndoDeleteStep(1, 1, false);
+ REQUIRE(il.DeletionCount(0, 2) == 1);
+ REQUIRE(il.DeletionsAt(1) == at1);
+ il.UndoDeleteStep(1, 1, false);
+ REQUIRE(il.DeletionCount(0, 3) == 0);
+ }
+
+ SECTION("Deletes Stack") {
+ std::vector<Spanner> spans = {
+ {5, 1},
+ {4, 3},
+ {1, 1},
+ {1, 1},
+ {0, 1},
+ {0, 3},
+ };
+
+ // Deletes that hit the start and end permanent positions
+ il.Insert(0, 10, false, true);
+ REQUIRE(il.Length() == 10);
+ il.SetSavePoint();
+ REQUIRE(il.DeletionCount(0, 10) == 0);
+ for (size_t i = 0; i < std::size(spans); i++) {
+ il.DeleteRangeSavingHistory(spans[i].position, spans[i].length, false, false);
+ }
+ REQUIRE(il.Length() == 0);
+ for (size_t j = 0; j < std::size(spans); j++) {
+ const size_t i = std::size(spans) - j - 1;
+ il.UndoDeleteStep(spans[i].position, spans[i].length, false);
+ }
+ REQUIRE(il.DeletionCount(0, 10) == 0);
+ REQUIRE(il.Length() == 10);
+ }
+}
+
+struct InsertionResult {
+ Sci::Position position;
+ Sci::Position length;
+ int state;
+ bool operator==(const InsertionResult &other) const noexcept {
+ return position == other.position &&
+ length == other.length &&
+ state == other.state;
+ }
+};
+
+std::ostream &operator << (std::ostream &os, InsertionResult const &value) {
+ os << value.position << " " << value.length << " " << value.state;
+ return os;
+}
+
+using Insertions = std::vector<InsertionResult>;
+
+std::ostream &operator << (std::ostream &os, Insertions const &value) {
+ os << "(";
+ for (const InsertionResult &el : value) {
+ os << "(" << el << ") ";
+ }
+ os << ")";
+ return os;
+}
+
+Insertions HistoryInsertions(const CellBuffer &cb) {
+ Insertions result;
+ Sci::Position startPos = 0;
+ while (startPos < cb.Length()) {
+ const Sci::Position endPos = cb.EditionEndRun(startPos);
+ const int ed = cb.EditionAt(startPos);
+ if (ed) {
+ result.push_back({ startPos, endPos - startPos, ed });
+ }
+ startPos = endPos;
+ }
+ return result;
+}
+
+struct DeletionResult {
+ Sci::Position position;
+ int state;
+ bool operator==(const DeletionResult &other) const noexcept {
+ return position == other.position &&
+ state == other.state;
+ }
+};
+
+std::ostream &operator << (std::ostream &os, DeletionResult const &value) {
+ os << value.position << " " << value.state;
+ return os;
+}
+
+using Deletions = std::vector<DeletionResult>;
+
+std::ostream &operator << (std::ostream &os, Deletions const &value) {
+ os << "(";
+ for (const DeletionResult &el : value) {
+ os << "(" << el << ") ";
+ }
+ os << ")";
+ return os;
+}
+
+Deletions HistoryDeletions(const CellBuffer &cb) {
+ Deletions result;
+ Sci::Position positionDeletion = 0;
+ while (positionDeletion <= cb.Length()) {
+ const unsigned int editions = cb.EditionDeletesAt(positionDeletion);
+ if (editions & 1) {
+ result.push_back({ positionDeletion, 1 });
+ }
+ if (editions & 2) {
+ result.push_back({ positionDeletion, 2 });
+ }
+ if (editions & 4) {
+ result.push_back({ positionDeletion, 3 });
+ }
+ if (editions & 8) {
+ result.push_back({ positionDeletion, 4 });
+ }
+ positionDeletion = cb.EditionNextDelete(positionDeletion);
+ }
+ return result;
+}
+
+struct History {
+ Insertions insertions;
+ Deletions deletions;
+ bool operator==(const History &other) const {
+ return insertions == other.insertions &&
+ deletions == other.deletions;
+ }
+};
+
+std::ostream &operator << (std::ostream &os, History const &value) {
+ os << value.insertions << " " << value.deletions;
+ return os;
+}
+
+History HistoryOf(const CellBuffer &cb) {
+ return { HistoryInsertions(cb), HistoryDeletions(cb) };
+}
+
+void UndoBlock(CellBuffer &cb) {
+ const int steps = cb.StartUndo();
+ for (int step = 0; step < steps; step++) {
+ cb.PerformUndoStep();
+ }
+}
+
+void RedoBlock(CellBuffer &cb) {
+ const int steps = cb.StartRedo();
+ for (int step = 0; step < steps; step++) {
+ cb.PerformRedoStep();
+ }
+}
+
+TEST_CASE("CellBufferWithChangeHistory") {
+
+ SECTION("StraightUndoRedoSaveRevertRedo") {
+ CellBuffer cb(true, false);
+ cb.SetUndoCollection(false);
+ std::string sInsert = "abcdefghijklmnopqrstuvwxyz";
+ bool startSequence = false;
+ cb.InsertString(0, sInsert.c_str(), sInsert.length(), startSequence);
+ cb.SetUndoCollection(true);
+ cb.SetSavePoint();
+ cb.ChangeHistorySet(true);
+
+ const History history0 { {}, {} };
+ REQUIRE(HistoryOf(cb) == history0);
+
+ // 1
+ cb.InsertString(4, "_", 1, startSequence);
+ const History history1{ {{4, 1, 3}}, {} };
+ REQUIRE(HistoryOf(cb) == history1);
+
+ // 2
+ cb.DeleteChars(2, 1, startSequence);
+ const History history2{ {{3, 1, 3}},
+ {{2, 3}} };
+ REQUIRE(HistoryOf(cb) == history2);
+
+ // 3
+ cb.InsertString(1, "[!]", 3, startSequence);
+ const History history3{ { {1, 3, 3}, {6, 1, 3} },
+ { {5, 3} } };
+ REQUIRE(HistoryOf(cb) == history3);
+
+ // 4
+ cb.DeleteChars(2, 1, startSequence); // Inside an insertion
+ const History history4{ { {1, 2, 3}, {5, 1, 3} },
+ { {2, 3}, {4, 3} }};
+ REQUIRE(HistoryOf(cb) == history4);
+
+ // 5 Delete all the insertions and deletions
+ cb.DeleteChars(1, 6, startSequence); // Inside an insertion
+ const History history5{ { },
+ { {1, 3} } };
+ REQUIRE(HistoryOf(cb) == history5);
+
+ // Undo all
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history4);
+
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history3);
+
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history2);
+
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history1);
+
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history0);
+
+ // Redo all
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history1);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history2);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history3);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history4);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history5);
+
+ cb.SetSavePoint();
+ const History history5s{ { },
+ { {1, 2} } };
+ REQUIRE(HistoryOf(cb) == history5s);
+
+ // Change past save point
+ cb.InsertString(4, "123", 3, startSequence);
+ const History history6{ { {4, 3, 3} },
+ { {1, 2} } };
+ REQUIRE(HistoryOf(cb) == history6);
+
+ // Undo to save point: same as 5 but with save state instead of unsaved
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history5s);
+
+ // Reverting past save point, similar to 4 but with most saved and
+ // reverted delete at 1
+ UndoBlock(cb); // Reinsert most of original changes
+ const History history4s{ { {1, 2, 4}, {3, 2, 1}, {5, 1, 4}, {6, 1, 1} },
+ { {2, 2}, {4, 2} } };
+ REQUIRE(HistoryOf(cb) == history4s);
+
+ UndoBlock(cb); // Reinsert "!",
+ const History history3s{ { {1, 3, 4}, {4, 2, 1}, {6, 1, 4}, {7, 1, 1} },
+ { {5, 2} } };
+ REQUIRE(HistoryOf(cb) == history3s);
+
+ UndoBlock(cb); // Revert insertion of [!]
+ const History history2s{ { {1, 2, 1}, {3, 1, 4}, {4, 1, 1} },
+ { {1, 1}, {2, 2} } };
+ REQUIRE(HistoryOf(cb) == history2s);
+
+ UndoBlock(cb); // Revert deletion, inserts at 2
+ const History history1s{ { {1, 3, 1}, {4, 1, 4}, {5, 1, 1} },
+ { {1, 1} } };
+ REQUIRE(HistoryOf(cb) == history1s);
+
+ UndoBlock(cb); // Revert insertion of _ at 4, drops middle insertion run
+ // So merges down to 1 insertion
+ const History history0s{ { {1, 4, 1} },
+ { {1, 1}, {4, 1} } };
+ REQUIRE(HistoryOf(cb) == history0s);
+
+ // At origin but with changes from disk
+ // Now redo the steps
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history1s);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history2s);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history3s);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history4s);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history5s);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history6);
+ }
+
+ SECTION("Detached") {
+ CellBuffer cb(true, false);
+ cb.SetUndoCollection(false);
+ std::string sInsert = "abcdefghijklmnopqrstuvwxyz";
+ bool startSequence = false;
+ cb.InsertString(0, sInsert.c_str(), sInsert.length(), startSequence);
+ cb.SetUndoCollection(true);
+ cb.SetSavePoint();
+ cb.ChangeHistorySet(true);
+
+ const History history0{ {}, {} };
+ REQUIRE(HistoryOf(cb) == history0);
+
+ // 1
+ cb.InsertString(4, "_", 1, startSequence);
+ const History history1{ {{4, 1, 3}}, {} };
+ REQUIRE(HistoryOf(cb) == history1);
+
+ // 2
+ cb.DeleteChars(2, 1, startSequence);
+ const History history2{ {{3, 1, 3}},
+ {{2, 3}} };
+ REQUIRE(HistoryOf(cb) == history2);
+
+ cb.SetSavePoint();
+
+ UndoBlock(cb);
+ const History history1s{ {{2, 1, 1}, {4, 1, 2}}, {} };
+ REQUIRE(HistoryOf(cb) == history1s);
+
+ cb.InsertString(6, "()", 2, startSequence);
+ const History detached2{ {{2, 1, 1}, {4, 1, 2}, {6, 2, 3}}, {} };
+ REQUIRE(HistoryOf(cb) == detached2);
+
+ cb.DeleteChars(9, 3, startSequence);
+ const History detached3{ {{2, 1, 1}, {4, 1, 2}, {6, 2, 3}}, {{9,3}} };
+ REQUIRE(HistoryOf(cb) == detached3);
+
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == detached2);
+ UndoBlock(cb);
+ const History detached1{ {{2, 1, 1}, {4, 1, 2}}, {} };
+ REQUIRE(HistoryOf(cb) == detached1);
+ UndoBlock(cb);
+ const History detached0{ {{2, 1, 1}}, {{4,1}} };
+ REQUIRE(HistoryOf(cb) == detached0);
+ REQUIRE(!cb.CanUndo());
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == detached1);
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == detached2);
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == detached3);
+ }
+}
+
+namespace {
+
+// Implement low quality reproducible pseudo-random numbers.
+// Pseudo-random algorithm based on R. G. Dromey "How to Solve it by Computer" page 122.
+
+class RandomSequence {
+ static constexpr int mult = 109;
+ static constexpr int incr = 853;
+ static constexpr int modulus = 4096;
+ int randomValue = 127;
+public:
+ int Next() noexcept {
+ randomValue = (mult * randomValue + incr) % modulus;
+ return randomValue;
+ }
+};
+
+}
+
+#if 1
+TEST_CASE("CellBufferLong") {
+
+ // Call methods on CellBuffer pseudo-randomly trying to trigger assertion failures
+
+ CellBuffer cb(true, false);
+
+ SECTION("Random") {
+ RandomSequence rseq;
+ for (size_t i = 0l; i < 20000; i++) {
+ const int r = rseq.Next() % 10;
+ if (r <= 2) { // 30%
+ // Insert text
+ const int pos = rseq.Next() % (cb.Length() + 1);
+ const int len = rseq.Next() % 10 + 1;
+ std::string sInsert;
+ for (int j = 0; j < len; j++) {
+ sInsert.push_back(static_cast<char>('a' + j));
+ }
+ bool startSequence = false;
+ cb.InsertString(pos, sInsert.c_str(), len, startSequence);
+ } else if (r <= 5) { // 30%
+ // Delete Text
+ const Sci::Position pos = rseq.Next() % (cb.Length() + 1);
+ const int len = rseq.Next() % 10 + 1;
+ if (pos + len <= cb.Length()) {
+ bool startSequence = false;
+ cb.DeleteChars(pos, len, startSequence);
+ }
+ } else if (r <= 8) { // 30%
+ // Undo or redo
+ const bool undo = rseq.Next() % 2 == 1;
+ if (undo) {
+ UndoBlock(cb);
+ } else {
+ RedoBlock(cb);
+ }
+ } else { // 10%
+ // Save
+ cb.SetSavePoint();
+ }
+ }
+ }
+}
+#endif \ No newline at end of file