diff options
author | Neil <nyamatongwe@gmail.com> | 2022-07-31 15:51:53 +1000 |
---|---|---|
committer | Neil <nyamatongwe@gmail.com> | 2022-07-31 15:51:53 +1000 |
commit | 926cb6f7d228b347db16a45e1f2632da475da1f0 (patch) | |
tree | 6263662cb8c023502d61b61eed34baa93ed35843 /test | |
parent | 535e20189d5a2dd9b43a6ea0a74749a50678d631 (diff) | |
download | scintilla-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')
-rw-r--r-- | test/unit/Sci.natvis | 17 | ||||
-rw-r--r-- | test/unit/UnitTester.vcxproj | 13 | ||||
-rw-r--r-- | test/unit/makefile | 1 | ||||
-rw-r--r-- | test/unit/test.mak | 1 | ||||
-rw-r--r-- | test/unit/testCellBuffer.cxx | 593 |
5 files changed, 612 insertions, 13 deletions
diff --git a/test/unit/Sci.natvis b/test/unit/Sci.natvis index d6ac2b96c..c0bd9d15d 100644 --- a/test/unit/Sci.natvis +++ b/test/unit/Sci.natvis @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
- <Type Name="Scintilla::SplitVector<*>">
+ <Type Name="Scintilla::Internal::SplitVector<*>">
<DisplayString>{{size = {lengthBody}}}</DisplayString>
<Expand>
<Item Name="[size]">lengthBody</Item>
@@ -12,7 +12,20 @@ </IndexListItems>
</Expand>
</Type>
- <Type Name="Scintilla::Partitioning<*>">
+ <Type Name="Scintilla::Internal::XSparseVector<*>">
+ <DisplayString>{{size = {values->lengthBody}}}</DisplayString>
+ <Expand>
+ <IndexListItems>
+ <Size>values->lengthBody</Size>
+ <ValueNode>starts->body->body[($i<starts->body->part1Length)?$i:$i+starts->body->gapLength]+($i>starts->stepPartition?starts->stepLength:0)</ValueNode>
+ </IndexListItems>
+ <IndexListItems>
+ <Size>values->lengthBody</Size>
+ <ValueNode>values->body->body[($i<values->body->part1Length)?$i:$i+values->body->gapLength]+($i>values->stepPartition?values->stepLength:0)</ValueNode>
+ </IndexListItems>
+ </Expand>
+ </Type>
+ <Type Name="Scintilla::Internal::Partitioning<*>">
<DisplayString>{{size = {body->lengthBody}}}</DisplayString>
<Expand>
<IndexListItems>
diff --git a/test/unit/UnitTester.vcxproj b/test/unit/UnitTester.vcxproj index 8d41cf383..38b0eb7cc 100644 --- a/test/unit/UnitTester.vcxproj +++ b/test/unit/UnitTester.vcxproj @@ -22,32 +22,32 @@ <ProjectGuid>{35688A27-D91B-453A-8A05-65A7F28DEFBF}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>UnitTester</RootNamespace>
- <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
- <PlatformToolset>v141</PlatformToolset>
+ <PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
- <PlatformToolset>v141</PlatformToolset>
+ <PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
- <PlatformToolset>v141</PlatformToolset>
+ <PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
- <PlatformToolset>v141</PlatformToolset>
+ <PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
@@ -74,6 +74,8 @@ </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
+ <EnableClangTidyCodeAnalysis>true</EnableClangTidyCodeAnalysis>
+ <CodeAnalysisRuleSet>..\..\..\..\..\Users\Neil\SensibleRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
@@ -153,6 +155,7 @@ <ClCompile Include="..\..\src\CaseConvert.cxx" />
<ClCompile Include="..\..\src\CaseFolder.cxx" />
<ClCompile Include="..\..\src\CellBuffer.cxx" />
+ <ClCompile Include="..\..\src\ChangeHistory.cxx" />
<ClCompile Include="..\..\src\CharacterCategoryMap.cxx" />
<ClCompile Include="..\..\src\CharClassify.cxx" />
<ClCompile Include="..\..\src\ContractionState.cxx" />
diff --git a/test/unit/makefile b/test/unit/makefile index 89766826f..46747931f 100644 --- a/test/unit/makefile +++ b/test/unit/makefile @@ -56,6 +56,7 @@ TESTEDSRC=\ ../../src/CaseConvert.cxx \ ../../src/CaseFolder.cxx \ ../../src/CellBuffer.cxx \ + ../../src/ChangeHistory.cxx \ ../../src/CharacterCategoryMap.cxx \ ../../src/CharClassify.cxx \ ../../src/ContractionState.cxx \ diff --git a/test/unit/test.mak b/test/unit/test.mak index 99e4882c2..10a6ebac7 100644 --- a/test/unit/test.mak +++ b/test/unit/test.mak @@ -15,6 +15,7 @@ TESTEDSRC=\ ../../src/CaseConvert.cxx \ ../../src/CaseFolder.cxx \ ../../src/CellBuffer.cxx \ + ../../src/ChangeHistory.cxx \ ../../src/CharacterCategoryMap.cxx \ ../../src/CharClassify.cxx \ ../../src/ContractionState.cxx \ 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 |