mirror of
https://github.com/notepad-plus-plus/notepad-plus-plus.git
synced 2025-07-08 14:34:44 +02:00
update to https://www.scintilla.org/scintilla533.zip with: 1. Released 8 February 2023. 2. Fix SCI_LINESJOIN bug where carriage returns were incorrectly retained. Bug #2372. 3. Fix SCI_VERTICALCENTRECARET to update the vertical scroll position. 4. When an autocompletion list is shown in response to SCN_CHARADDED, do not process character as fill-up or stop. This avoids closing immediately when a character may both trigger and finish autocompletion. 5. On Cocoa fix character input bug where dotless 'i' and some other extended Latin characters could not be entered. The change also stops SCI_ASSIGNCMDKEY from working with these characters on Cocoa. Bug #2374. 6. On GTK, support IME context. Feature #1476. 7. On GTK on Win32, fix scrolling speed to not be too fast. Bug #2375. 8. On Qt, fix indicator drawing past left of text pane over margin. Bug #2373, Bug #1956. 9. On Qt, allow scrolling with mouse wheel when scroll bar hidden. and https://www.scintilla.org/lexilla522.zip with 1. Released 8 February 2023. 2. C++: Fix keywords that start with non-ASCII. Also affects other lexers. Issue #130. 3. Matlab: Include more prefix and suffix characters in numeric literals. Issue #120. 4. Matlab: More accurate treatment of line ends inside strings. Matlab and Octave are different here. Issue #18. 5. Modula-3: Don't treat identifier suffix that matches keyword as keyword. Issue #129. 6. Modula-3: Fix endless loop in folder. Issue #128. 7. Modula-3: Fix access to lines beyond document end in folder. Issue #131. 8. Python: Don't highlight match and case as keywords in contexts where they probably aren't used as keywords. Pull request #122. 9. X12: Support empty envelopes. Bug #2369. update CMakeLists.txt to latest changes within vcxproj file Close #13082
1020 lines
32 KiB
C++
1020 lines
32 KiB
C++
/** @file testCellBuffer.cxx
|
|
** Unit Tests for Scintilla internal data structures
|
|
**/
|
|
|
|
#include <cstddef>
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <stdexcept>
|
|
#include <string_view>
|
|
#include <vector>
|
|
#include <optional>
|
|
#include <algorithm>
|
|
#include <memory>
|
|
|
|
#include "ScintillaTypes.h"
|
|
|
|
#include "Debugging.h"
|
|
|
|
#include "Position.h"
|
|
#include "SplitVector.h"
|
|
#include "Partitioning.h"
|
|
#include "RunStyles.h"
|
|
#include "SparseVector.h"
|
|
#include "ChangeHistory.h"
|
|
#include "CellBuffer.h"
|
|
|
|
#include "catch.hpp"
|
|
|
|
using namespace Scintilla;
|
|
using namespace Scintilla::Internal;
|
|
|
|
// Test CellBuffer.
|
|
|
|
TEST_CASE("CellBuffer") {
|
|
|
|
const char sText[] = "Scintilla";
|
|
const Sci::Position sLength = static_cast<Sci::Position>(strlen(sText));
|
|
|
|
CellBuffer cb(true, false);
|
|
|
|
SECTION("InsertOneLine") {
|
|
bool startSequence = false;
|
|
const char *cpChange = cb.InsertString(0, sText, sLength, startSequence);
|
|
REQUIRE(startSequence);
|
|
REQUIRE(sLength == cb.Length());
|
|
REQUIRE(memcmp(cpChange, sText, sLength) == 0);
|
|
REQUIRE(1 == cb.Lines());
|
|
REQUIRE(0 == cb.LineStart(0));
|
|
REQUIRE(0 == cb.LineFromPosition(0));
|
|
REQUIRE(sLength == cb.LineStart(1));
|
|
REQUIRE(0 == cb.LineFromPosition(static_cast<int>(sLength)));
|
|
REQUIRE(cb.CanUndo());
|
|
REQUIRE(!cb.CanRedo());
|
|
}
|
|
|
|
SECTION("InsertTwoLines") {
|
|
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, sLength2, startSequence);
|
|
REQUIRE(startSequence);
|
|
REQUIRE(sLength2 == cb.Length());
|
|
REQUIRE(memcmp(cpChange, sText2, sLength2) == 0);
|
|
REQUIRE(2 == cb.Lines());
|
|
REQUIRE(0 == cb.LineStart(0));
|
|
REQUIRE(0 == cb.LineFromPosition(0));
|
|
REQUIRE(4 == cb.LineStart(1));
|
|
REQUIRE(1 == cb.LineFromPosition(5));
|
|
REQUIRE(sLength2 == cb.LineStart(2));
|
|
REQUIRE(1 == cb.LineFromPosition(sLength2));
|
|
REQUIRE(cb.CanUndo());
|
|
REQUIRE(!cb.CanRedo());
|
|
}
|
|
|
|
SECTION("UndoOff") {
|
|
REQUIRE(cb.IsCollectingUndo());
|
|
cb.SetUndoCollection(false);
|
|
REQUIRE(!cb.IsCollectingUndo());
|
|
bool startSequence = false;
|
|
const char *cpChange = cb.InsertString(0, sText, sLength, startSequence);
|
|
REQUIRE(!startSequence);
|
|
REQUIRE(sLength == cb.Length());
|
|
REQUIRE(memcmp(cpChange, sText, sLength) == 0);
|
|
REQUIRE(!cb.CanUndo());
|
|
REQUIRE(!cb.CanRedo());
|
|
}
|
|
|
|
SECTION("UndoRedo") {
|
|
const char sTextDeleted[] = "ci";
|
|
const char sTextAfterDeletion[] = "Sntilla";
|
|
bool startSequence = false;
|
|
const char *cpChange = cb.InsertString(0, sText, sLength, startSequence);
|
|
REQUIRE(startSequence);
|
|
REQUIRE(sLength == cb.Length());
|
|
REQUIRE(memcmp(cpChange, sText, sLength) == 0);
|
|
REQUIRE(memcmp(cb.BufferPointer(), sText, sLength) == 0);
|
|
REQUIRE(cb.CanUndo());
|
|
REQUIRE(!cb.CanRedo());
|
|
const char *cpDeletion = cb.DeleteChars(1, 2, startSequence);
|
|
REQUIRE(startSequence);
|
|
REQUIRE(memcmp(cpDeletion, sTextDeleted, strlen(sTextDeleted)) == 0);
|
|
REQUIRE(memcmp(cb.BufferPointer(), sTextAfterDeletion, strlen(sTextAfterDeletion)) == 0);
|
|
REQUIRE(cb.CanUndo());
|
|
REQUIRE(!cb.CanRedo());
|
|
|
|
int steps = cb.StartUndo();
|
|
REQUIRE(steps == 1);
|
|
cb.PerformUndoStep();
|
|
REQUIRE(memcmp(cb.BufferPointer(), sText, sLength) == 0);
|
|
REQUIRE(cb.CanUndo());
|
|
REQUIRE(cb.CanRedo());
|
|
|
|
steps = cb.StartUndo();
|
|
REQUIRE(steps == 1);
|
|
cb.PerformUndoStep();
|
|
REQUIRE(cb.Length() == 0);
|
|
REQUIRE(!cb.CanUndo());
|
|
REQUIRE(cb.CanRedo());
|
|
|
|
steps = cb.StartRedo();
|
|
REQUIRE(steps == 1);
|
|
cb.PerformRedoStep();
|
|
REQUIRE(memcmp(cb.BufferPointer(), sText, sLength) == 0);
|
|
REQUIRE(cb.CanUndo());
|
|
REQUIRE(cb.CanRedo());
|
|
|
|
steps = cb.StartRedo();
|
|
REQUIRE(steps == 1);
|
|
cb.PerformRedoStep();
|
|
REQUIRE(memcmp(cb.BufferPointer(), sTextAfterDeletion, strlen(sTextAfterDeletion)) == 0);
|
|
REQUIRE(cb.CanUndo());
|
|
REQUIRE(!cb.CanRedo());
|
|
|
|
cb.DeleteUndoHistory();
|
|
REQUIRE(!cb.CanUndo());
|
|
REQUIRE(!cb.CanRedo());
|
|
}
|
|
|
|
SECTION("LineEndTypes") {
|
|
REQUIRE(cb.GetLineEndTypes() == LineEndType::Default);
|
|
cb.SetLineEndTypes(LineEndType::Unicode);
|
|
REQUIRE(cb.GetLineEndTypes() == LineEndType::Unicode);
|
|
cb.SetLineEndTypes(LineEndType::Default);
|
|
REQUIRE(cb.GetLineEndTypes() == LineEndType::Default);
|
|
}
|
|
|
|
SECTION("ReadOnly") {
|
|
REQUIRE(!cb.IsReadOnly());
|
|
cb.SetReadOnly(true);
|
|
REQUIRE(cb.IsReadOnly());
|
|
bool startSequence = false;
|
|
cb.InsertString(0, sText, sLength, startSequence);
|
|
REQUIRE(cb.Length() == 0);
|
|
}
|
|
|
|
}
|
|
|
|
TEST_CASE("CharacterIndex") {
|
|
|
|
CellBuffer cb(true, false);
|
|
|
|
SECTION("Setup") {
|
|
REQUIRE(cb.LineCharacterIndex() == LineCharacterIndexType::None);
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 0);
|
|
cb.SetUTF8Substance(true);
|
|
|
|
cb.AllocateLineCharacterIndex(LineCharacterIndexType::Utf16);
|
|
REQUIRE(cb.LineCharacterIndex() == LineCharacterIndexType::Utf16);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 0);
|
|
|
|
cb.ReleaseLineCharacterIndex(LineCharacterIndexType::Utf16);
|
|
REQUIRE(cb.LineCharacterIndex() == LineCharacterIndexType::None);
|
|
}
|
|
|
|
SECTION("Insertion") {
|
|
cb.SetUTF8Substance(true);
|
|
|
|
cb.AllocateLineCharacterIndex(LineCharacterIndexType::Utf16 | LineCharacterIndexType::Utf32);
|
|
|
|
bool startSequence = false;
|
|
cb.InsertString(0, "a", 1, startSequence);
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 1);
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 1);
|
|
|
|
const char *hwair = "\xF0\x90\x8D\x88";
|
|
cb.InsertString(0, hwair, strlen(hwair), startSequence);
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 3);
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 2);
|
|
}
|
|
|
|
SECTION("Deletion") {
|
|
cb.SetUTF8Substance(true);
|
|
|
|
cb.AllocateLineCharacterIndex(LineCharacterIndexType::Utf16 | LineCharacterIndexType::Utf32);
|
|
|
|
bool startSequence = false;
|
|
const char *hwair = "a\xF0\x90\x8D\x88z";
|
|
cb.InsertString(0, hwair, strlen(hwair), startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 4);
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 3);
|
|
|
|
cb.DeleteChars(5, 1, startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 3);
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 2);
|
|
|
|
cb.DeleteChars(1, 4, startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 1);
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 1);
|
|
}
|
|
|
|
SECTION("Insert Complex") {
|
|
cb.SetUTF8Substance(true);
|
|
cb.SetLineEndTypes(LineEndType::Unicode);
|
|
cb.AllocateLineCharacterIndex(LineCharacterIndexType::Utf16 | LineCharacterIndexType::Utf32);
|
|
|
|
bool startSequence = false;
|
|
// 3 lines of text containing 8 bytes
|
|
const char *data = "a\n\xF0\x90\x8D\x88\nz";
|
|
cb.InsertString(0, data, strlen(data), startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 2);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf16) == 5);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf16) == 6);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 2);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf32) == 4);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf32) == 5);
|
|
|
|
// Insert a new line at end -> "a\n\xF0\x90\x8D\x88\nz\n" 4 lines
|
|
// Last line empty
|
|
cb.InsertString(strlen(data), "\n", 1, startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 2);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf16) == 5);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf16) == 7);
|
|
REQUIRE(cb.IndexLineStart(4, LineCharacterIndexType::Utf16) == 7);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 2);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf32) == 4);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf32) == 6);
|
|
REQUIRE(cb.IndexLineStart(4, LineCharacterIndexType::Utf32) == 6);
|
|
|
|
// Insert a new line before end -> "a\n\xF0\x90\x8D\x88\nz\n\n" 5 lines
|
|
cb.InsertString(strlen(data), "\n", 1, startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 2);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf16) == 5);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf16) == 7);
|
|
REQUIRE(cb.IndexLineStart(4, LineCharacterIndexType::Utf16) == 8);
|
|
REQUIRE(cb.IndexLineStart(5, LineCharacterIndexType::Utf16) == 8);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 2);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf32) == 4);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf32) == 6);
|
|
REQUIRE(cb.IndexLineStart(4, LineCharacterIndexType::Utf32) == 7);
|
|
REQUIRE(cb.IndexLineStart(5, LineCharacterIndexType::Utf32) == 7);
|
|
|
|
// Insert a valid 3-byte UTF-8 character at start ->
|
|
// "\xE2\x82\xACa\n\xF0\x90\x8D\x88\nz\n\n" 5 lines
|
|
|
|
const char *euro = "\xE2\x82\xAC";
|
|
cb.InsertString(0, euro, strlen(euro), startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 3);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf16) == 6);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf16) == 8);
|
|
REQUIRE(cb.IndexLineStart(4, LineCharacterIndexType::Utf16) == 9);
|
|
REQUIRE(cb.IndexLineStart(5, LineCharacterIndexType::Utf16) == 9);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 3);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf32) == 5);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf32) == 7);
|
|
REQUIRE(cb.IndexLineStart(4, LineCharacterIndexType::Utf32) == 8);
|
|
REQUIRE(cb.IndexLineStart(5, LineCharacterIndexType::Utf32) == 8);
|
|
|
|
// Insert a lone lead byte implying a 3 byte character at start of line 2 ->
|
|
// "\xE2\x82\xACa\n\EF\xF0\x90\x8D\x88\nz\n\n" 5 lines
|
|
// Should be treated as a single byte character
|
|
|
|
const char *lead = "\xEF";
|
|
cb.InsertString(5, lead, strlen(lead), startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 3);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf16) == 7);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf16) == 9);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 3);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf32) == 6);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf32) == 8);
|
|
|
|
// Insert an ASCII lead byte inside the 3-byte initial character ->
|
|
// "\xE2!\x82\xACa\n\EF\xF0\x90\x8D\x88\nz\n\n" 5 lines
|
|
// It should b treated as a single character and should cause the
|
|
// byte before and the 2 bytes after also be each treated as singles
|
|
// so 3 more characters on line 0.
|
|
|
|
const char *ascii = "!";
|
|
cb.InsertString(1, ascii, strlen(ascii), startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 6);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf16) == 10);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 6);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf32) == 9);
|
|
|
|
// Insert a NEL after the '!' to trigger the utf8 line end case ->
|
|
// "\xE2!\xC2\x85 \x82\xACa\n \EF\xF0\x90\x8D\x88\n z\n\n" 5 lines
|
|
|
|
const char *nel = "\xC2\x85";
|
|
cb.InsertString(2, nel, strlen(nel), startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 3);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf16) == 7);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf16) == 11);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 3);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf32) == 7);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf32) == 10);
|
|
}
|
|
|
|
SECTION("Delete Multiple lines") {
|
|
cb.SetUTF8Substance(true);
|
|
cb.AllocateLineCharacterIndex(LineCharacterIndexType::Utf16 | LineCharacterIndexType::Utf32);
|
|
|
|
bool startSequence = false;
|
|
// 3 lines of text containing 8 bytes
|
|
const char *data = "a\n\xF0\x90\x8D\x88\nz\nc";
|
|
cb.InsertString(0, data, strlen(data), startSequence);
|
|
|
|
// Delete first 2 new lines -> "az\nc"
|
|
cb.DeleteChars(1, strlen(data) - 4, startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 3);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf16) == 4);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 3);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf32) == 4);
|
|
}
|
|
|
|
SECTION("Delete Complex") {
|
|
cb.SetUTF8Substance(true);
|
|
cb.AllocateLineCharacterIndex(LineCharacterIndexType::Utf16 | LineCharacterIndexType::Utf32);
|
|
|
|
bool startSequence = false;
|
|
// 3 lines of text containing 8 bytes
|
|
const char *data = "a\n\xF0\x90\x8D\x88\nz";
|
|
cb.InsertString(0, data, strlen(data), startSequence);
|
|
|
|
// Delete lead byte from character on line 1 ->
|
|
// "a\n\x90\x8D\x88\nz"
|
|
// line 1 becomes 4 single byte characters
|
|
cb.DeleteChars(2, 1, startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 2);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf16) == 6);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf16) == 7);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 2);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf32) == 6);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf32) == 7);
|
|
|
|
// Delete first new line ->
|
|
// "a\x90\x8D\x88\nz"
|
|
// Only 2 lines with line 0 containing 5 single byte characters
|
|
cb.DeleteChars(1, 1, startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 5);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf16) == 6);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 5);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf32) == 6);
|
|
|
|
// Restore lead byte from character on line 0 making a 4-byte character ->
|
|
// "a\xF0\x90\x8D\x88\nz"
|
|
|
|
const char *lead4 = "\xF0";
|
|
cb.InsertString(1, lead4, strlen(lead4), startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 4);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf16) == 5);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf32) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf32) == 3);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf32) == 4);
|
|
}
|
|
|
|
SECTION("Insert separates new line bytes") {
|
|
cb.SetUTF8Substance(true);
|
|
cb.AllocateLineCharacterIndex(LineCharacterIndexType::Utf16 | LineCharacterIndexType::Utf32);
|
|
|
|
bool startSequence = false;
|
|
// 2 lines of text containing 4 bytes
|
|
const char *data = "a\r\nb";
|
|
cb.InsertString(0, data, strlen(data), startSequence);
|
|
|
|
// 3 lines of text containing 5 bytes ->
|
|
// "a\r!\nb"
|
|
const char *ascii = "!";
|
|
cb.InsertString(2, ascii, strlen(ascii), startSequence);
|
|
|
|
REQUIRE(cb.IndexLineStart(0, LineCharacterIndexType::Utf16) == 0);
|
|
REQUIRE(cb.IndexLineStart(1, LineCharacterIndexType::Utf16) == 2);
|
|
REQUIRE(cb.IndexLineStart(2, LineCharacterIndexType::Utf16) == 4);
|
|
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf16) == 5);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ChangeHistory") {
|
|
|
|
ChangeHistory il;
|
|
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 |