lazarus/components/synedit/test/testwordwrap.pas

2522 lines
93 KiB
ObjectPascal

unit TestWordWrap;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, math, TestBase, SynEditViewedLineMap, SynEditMiscClasses,
SynEditTypes, SynEditWrappedView,
LazSynEditText, SynEditHighlighterFoldBase, LazLoggerBase,
SynEditKeyCmds, SynEdit, SynEditPointClasses, testregistry;
type
TIntArray = Array of integer;
{ TExpWraps }
TExpWraps = object
w: Array of Integer;
len: Integer;
function Init(const a: array of integer): TExpWraps;
procedure SetCapacity(l: Integer);
procedure InitFill(AFrom, ATo: integer; AIncrease: Integer = 1);
procedure FillRange(AStartIdx, ACount, AFromVal: integer; AIncrease: Integer = 1);
procedure Join(const a: TExpWraps; AInsertPos: Integer = -1);
procedure Join(const a: array of integer; AInsertPos: Integer = -1);
procedure SpliceArray(ADelFrom, ADelCount: integer);
end;
{ TTestWordWrapBase }
TTestWordWrapBase = class(TTestBase)
protected
function TheTree: TSynLineMapAVLTree; virtual; abstract;
function TreeNodeCount: integer;
procedure CheckTree(AName: String); virtual;
procedure CheckTree(AName: String; ANode: TSynEditLineMapPage; ANodeLine: TLineIdx; AMinLine, AMaxLine: TLineIdx);
end;
{ TTestWordWrap }
TTestWordWrap = class(TTestWordWrapBase)
private
FTree: TSynLineMapAVLTree;
procedure AssertRealToWrapOffsets(const AName: String; ALine: TSynWordWrapLineMap;
const ExpWrapOffsets: TExpWraps; AStartOffs: Integer = 0);
procedure AssertWrapToRealOffset(const AName: String; ALine: TSynWordWrapLineMap;
const ExpRealAndSubOffsets: TExpWraps; AStartOffs: Integer = 0);
procedure AssertLineForWraps(const AName: String; ALine: TSynWordWrapLineMap;
const ExpWrapForEachLine: TExpWraps; AnExpAllValid: Boolean = False);
procedure InitLine(ALine: TSynWordWrapLineMap;
const AWrapValues: TExpWraps);
function OnPageNeeded(AMapTree: TSynLineMapAVLTree): TSynEditLineMapPage;
procedure ValidateWraps(ALine: TSynWordWrapLineMap;
const AWrapValues: TExpWraps; AStartOffs: Integer = 0; ABackward: Boolean = False);
procedure ValidateNeededWraps(ALine: TSynWordWrapLineMap; const AWrapValues: TExpWraps);
procedure ValidateTreeWraps(const AWrapValues: TExpWraps; AStartOffs: Integer = 0);
procedure AssertTreeForWraps(const AName: String; const ExpWrapForEachLine: TExpWraps; AStartOffs: Integer = 0);
function CreateTree(APageJoinSize, APageSplitSize, APageJoinDistance: Integer): TSynLineMapAVLTree;
protected
function TheTree: TSynLineMapAVLTree; override;
procedure SetUp; override;
procedure TearDown; override;
published
procedure TestWordWrapLineMap;
procedure TestWordWrapLineMapInvalidate;
procedure TestWordWrapLineMapInvalidateNoneContineous;
procedure TestWordWrapLineMapValidate;
procedure TestWordWrapLineMapMerge;
procedure TestWordWrapLineMapMergeInvalidate;
procedure TestWordWrapJoinWithSibling;
procedure TestWordWrapTreeInsertThenDelete;
procedure TestWordWrapTreeDeleteThenInsert;
end;
TPointType = (ptViewed, ptAlternateViewed, ptPhys, ptLog);
TPointSpecs = record
XY: array [TPointType] of TPoint;
LogOffs: Integer;
end;
TCommandAndPointSpecs = record
Exp: TPointSpecs;
Cmd: Array of TSynEditorCommand;
RunOnlyIf: Boolean;
end;
TTestWrapLineInfo = record
TextIdx: TLineIdx;
ViewedIdx, ViewedTopIdx, ViewedBottomIdx: TLineIdx;
SubIdx: Integer;
//FirstLogX: Integer;
TextStartMatch: String;
NoTrim: Boolean;
end;
TTestViewedLineRangeInfo = array of TTestWrapLineInfo;
TTripleBool = (tTrue, tFalse, tKeep);
function l(ATxtIdx: TLineIdx; ASubIdx: Integer; AText: String; ANoTrim: Boolean = False): TTestWrapLineInfo;
function ViewedExp(AFirstViewedIdx: TLineIdx; ALines:
array of TTestWrapLineInfo; ANoTrim: TTripleBool = tKeep): TTestViewedLineRangeInfo;
type
{ TTestWordWrapPluginBase }
TTestWordWrapPluginBase = class(TTestWordWrapBase)
private
procedure ClearCaret;
function GetTreeNodeHolder(AIndex: Integer): TSynEditLineMapPageHolder;
procedure SetCaret(SourcePt: TPointType; APos: TPoint);
procedure TestCaret(AName: String; SourcePt, ExpPt: TPointType; AnExp: TPoint;
AnExpOffs: Integer = -1);
protected
FWordWrap: TLazSynEditLineWrapPlugin;
class procedure AssertEquals(const AMessage: string; Expected, Actual: TPoint); overload;
procedure AddLines(AFirstLineIdx, ACount, ALen: Integer; AnID: String; SkipBeginUpdate: Boolean = False; AReplaceExisting: Boolean = False);
procedure InternalCheckLine(AName: String; dsp: TLazSynDisplayView; ALine: TLineIdx; AExpTextStart: String; NoTrim: Boolean = False);
procedure CheckLine(AName: String; ALine: TLineIdx; AExpTextStart: String; NoTrim: Boolean = False);
procedure CheckLines(AName: String; AStartLine: TLineIdx; AExpTextStart: array of String; NoTrim: Boolean = False);
procedure CheckLine(AName: String; AExpLine: TTestWrapLineInfo);
procedure CheckLines(AName: String; AExpLines: TTestViewedLineRangeInfo);
procedure CheckXyMap(AName: String; APhysTExtXY, AViewedXY: TPoint; OnlyViewToText: Boolean = False);
procedure CheckXyMap(AName: String; APhysTExtX, APhysTExtY, AViewedX, AViewedY: integer; OnlyViewToText: Boolean = False);
procedure CheckXyMap(AName: String; APoints: TPointSpecs);
procedure CheckXyMap(AName: String; APoints: TPointSpecs;
ATestCommands: array of TCommandAndPointSpecs);
procedure CheckLineIndexMapping(AName: String; ATextIdx, AViewTopIdx, AViewBottomIdx: TLineIdx);
function TheTree: TSynLineMapAVLTree; override;
property TreeNodeHolder[AIndex: Integer]: TSynEditLineMapPageHolder read GetTreeNodeHolder;
procedure ReCreateEdit(ADispWidth: Integer);
procedure SetUp; override;
procedure TearDown; override;
end;
TTestWordWrapPlugin = class(TTestWordWrapPluginBase)
published
procedure TestEditorWrap;
procedure TestWrapSplitJoin;
procedure TestEditorEdit;
end;
implementation
function p(VX, VY, AVX, AVY, PX, PY, LX, LY: Integer; Offs: Integer = -1): TPointSpecs; overload;
begin
with Result do begin
XY[ptViewed].X := VX;
XY[ptViewed].Y := VY;
XY[ptAlternateViewed].X := AVX;
XY[ptAlternateViewed].Y := AVY;
XY[ptPhys].X := PX;
XY[ptPhys].Y := PY;
XY[ptLog].X := LX;
XY[ptLog].Y := LY;
LogOffs := Offs;
end;
end;
function p(VX, VY, PX, PY, LX, LY: Integer; Offs: Integer = -1): TPointSpecs; overload;
begin
Result := p(VX, VY, -1, -1, PX, PY, LX, LY, Offs);
end;
function c(Cmd: Array of TSynEditorCommand; VX, VY, AVX, AVY, PX, PY, LX, LY: Integer; Offs: Integer = -1; RunOnlyIf: Boolean = True): TCommandAndPointSpecs; overload;
begin
Result.Exp := p(VX, VY, AVX, AVY, PX, PY, LX, LY, Offs);
SetLength(Result.Cmd, Length(Cmd));
move(Cmd[0], Result.Cmd[0], SizeOf(cmd[0]) * Length(Cmd));
Result.RunOnlyIf := RunOnlyIf;
end;
function c(Cmd: Array of TSynEditorCommand; VX, VY, PX, PY, LX, LY: Integer; Offs: Integer = -1; RunOnlyIf: Boolean = True): TCommandAndPointSpecs; overload;
begin
Result := c(Cmd, VX, VY, -1, -1, PX, PY, LX, LY, Offs, RunOnlyIf);
end;
function c(Cmd: TSynEditorCommand; VX, VY, PX, PY, LX, LY: Integer; Offs: Integer = -1; RunOnlyIf: Boolean = True): TCommandAndPointSpecs; overload;
begin
Result := c([Cmd], VX, VY, -1, -1, PX, PY, LX, LY, Offs, RunOnlyIf);
end;
function FillArray(AFrom, ATo: integer; AIncrease: Integer = 1): TIntArray;
var
i: Integer;
begin
SetLength(Result, ATo - AFrom + 1);
for i := 0 to high(Result) do
Result[i] := AFrom + i * AIncrease;
end;
function l(ATxtIdx: TLineIdx; ASubIdx: Integer; AText: String; ANoTrim: Boolean
): TTestWrapLineInfo;
begin
Result.TextIdx := ATxtIdx;
Result.SubIdx := ASubIdx;
Result.TextStartMatch := AText;
Result.NoTrim := ANoTrim;
end;
function ViewedExp(AFirstViewedIdx: TLineIdx;
ALines: array of TTestWrapLineInfo; ANoTrim: TTripleBool
): TTestViewedLineRangeInfo;
var
i, j: Integer;
begin
SetLength(Result, Length(ALines));
j := 0;
for i := 0 to Length(ALines) - 1 do begin
if (i > 0) and (ALines[i].SubIdx = 0) then begin
while j < i do begin
Result[j].ViewedBottomIdx := AFirstViewedIdx - 1;
inc(j);
end;
j := i;
end;
Result[i] := ALines[i];
Result[i].ViewedIdx := AFirstViewedIdx;
Result[i].ViewedTopIdx := AFirstViewedIdx - Result[i].SubIdx;
case ANoTrim of
tFalse: Result[i].NoTrim := False;
tTrue: Result[i].NoTrim := True;
end;
inc(AFirstViewedIdx);
end;
while j < Length(ALines) do begin
Result[j].ViewedBottomIdx := AFirstViewedIdx - 1;
inc(j);
end;
end;
{ TTestWordWrapBase }
function TTestWordWrapBase.TreeNodeCount: integer;
var
n: TSynEditLineMapPageHolder;
begin
Result := 0;
n := TheTree.FirstPage;
while n.HasPage do begin
inc(Result);
n := n.Next;
end;
end;
procedure TTestWordWrapBase.CheckTree(AName: String);
var
n: TSynEditLineMapPageHolder;
begin
if TheTree = nil then
AssertTrue(AName, False);
n := TheTree.FirstPage;
if n.HasPage then
CheckTree(AName, n.Page, n.StartLine, 0, MaxInt);
end;
procedure TTestWordWrapBase.CheckTree(AName: String;
ANode: TSynEditLineMapPage; ANodeLine: TLineIdx; AMinLine, AMaxLine: TLineIdx
);
var
n: TSynEditLineMapPage;
dummy, i, EndLine: Integer;
nl: TLineIdx;
begin
nl := ANodeLine;
n := ANode.Precessor(nl, dummy);
while n <> nil do begin
ANode := n;
ANodeLine := nl;
n := ANode.Precessor(nl, dummy);
end;
i := 0;
while ANode <> nil do begin
AssertTrue(Format('%s(%d): MinLine', [AName, i]), ANodeLine >= AMinLine);
EndLine := ANodeLine + Max(0, ANode.RealEndLine);
AssertTrue(Format('%s(%d): EndLine', [AName, i]), EndLine <= AMaxLine);
AMinLine := EndLine + 1;
ANode := ANode.Successor(ANodeLine, dummy);
inc(i);
end;
end;
{ TExpWraps }
function TExpWraps.Init(const a: array of integer): TExpWraps;
begin
len := Length(a);
if len > 0 then begin
SetCapacity(len);
move(a[0], w[0], SizeOf(w[0]) * len);
end;
Result := self;
end;
procedure TExpWraps.SetCapacity(l: Integer);
begin
if Length(w) < l then
SetLength(w, l*2);
end;
procedure TExpWraps.InitFill(AFrom, ATo: integer; AIncrease: Integer);
var
p: PLongInt;
i: Integer;
begin
len := ATo - AFrom + 1;
SetCapacity(len);
p := @w[0];
for i := 0 to len - 1 do begin
p^ := AFrom;
inc(p);
inc(AFrom, AIncrease);
end;
end;
procedure TExpWraps.FillRange(AStartIdx, ACount, AFromVal: integer;
AIncrease: Integer);
var
p: PLongInt;
i: Integer;
begin
if len < AStartIdx + ACount then
len := AStartIdx + ACount;
SetCapacity(len);
p := @w[AStartIdx];
for i := 0 to ACount - 1 do begin
p^ := AFromVal;
inc(p);
inc(AFromVal, AIncrease);
end;
end;
procedure TExpWraps.Join(const a: TExpWraps; AInsertPos: Integer);
var
i, old: Integer;
begin
if AInsertPos < 0 then
AInsertPos := Len;
i := (Len-AInsertPos);
old := len;
len := len + a.len;
if i < 0 then
len := len - i;
SetCapacity(len);
if i > 0 then begin
move(w[AInsertPos], w[AInsertPos+a.len], sizeof(w[0]) * i);
end
else
if i < 0 then begin
FillDWord(w[old], -i, 1);
end;
move(a.w[0], w[AInsertPos], sizeof(w[0]) * a.len);
end;
procedure TExpWraps.Join(const a: array of integer; AInsertPos: Integer);
var
i, la, old: Integer;
begin
if AInsertPos < 0 then
AInsertPos := Len;
i := (Len-AInsertPos);
la := Length(a);
old := len;
len := len + la;
if i < 0 then
len := len - i;
SetCapacity(len);
if i > 0 then begin
move(w[AInsertPos], w[AInsertPos+la], sizeof(w[0]) * i);
end
else
if i < 0 then begin
FillDWord(w[old], -i, 1);
end;
move(a[0], w[AInsertPos], sizeof(w[0]) * la);
end;
procedure TExpWraps.SpliceArray(ADelFrom, ADelCount: integer);
var
i: Integer;
begin
len := len - ADelCount;
i := Length(w) - ADelFrom - ADelCount;
if i > 0 then
move(w[ADelFrom+ADelCount], w[ADelFrom], sizeof(w[0]) * (i));
end;
{ TTestWordWrap }
procedure TTestWordWrap.AssertRealToWrapOffsets(const AName: String;
ALine: TSynWordWrapLineMap; const ExpWrapOffsets: TExpWraps;
AStartOffs: Integer);
var
i: Integer;
begin
for i := 0 to ExpWrapOffsets.len - 1 do
AssertEquals(format('%s: RealToWrap Idx %d StartOffs: %d ', [AName, i, AStartOffs]),
ExpWrapOffsets.w[i], ALine.WrappedOffsetFor[AStartOffs + i]);
end;
procedure TTestWordWrap.AssertWrapToRealOffset(const AName: String;
ALine: TSynWordWrapLineMap; const ExpRealAndSubOffsets: TExpWraps;
AStartOffs: Integer);
var
i, sub, r: Integer;
begin
for i := 0 to ExpRealAndSubOffsets.len div 2 - 1 do begin
r := ALine.GetOffsetForWrap(AStartOffs + i, sub);
AssertEquals(format('%s: WrapToReal Idx %d StartOffs: %d ', [AName, i, AStartOffs]),
ExpRealAndSubOffsets.w[i*2], r);
AssertEquals(format('%s: WrapToReal(SUB) Idx %d StartOffs: %d ', [AName, i, AStartOffs]),
ExpRealAndSubOffsets.w[i*2+1], sub);
end;
end;
procedure TTestWordWrap.AssertLineForWraps(const AName: String;
ALine: TSynWordWrapLineMap; const ExpWrapForEachLine: TExpWraps;
AnExpAllValid: Boolean);
var
i, j, ExpWrap, TestWrapToReal, GotReal, sub: Integer;
begin
if AnExpAllValid then
AssertTrue(AName + ' - all lines valid', ALine.FirstInvalidLine < 0);
i := 0;
while (i < ExpWrapForEachLine.len) and (ExpWrapForEachLine.w[i] = 1) do
inc(i);
if i = ExpWrapForEachLine.len then
i := 0;
AssertEquals(Format('%s: Offset', [AName]), i, ALine.Offset);
j := ExpWrapForEachLine.len - 1;
while (j >= 0) and (ExpWrapForEachLine.w[j] = 1) do
dec(j);
AssertEquals(Format('%s: RealCount', [AName]), j + 1 - i, ALine.RealCount);
ExpWrap := 0;
TestWrapToReal := 0;
for i := 0 to ExpWrapForEachLine.len - 1 do begin
AssertEquals(Format('%s: RealToWrap Idx %d', [AName, i]), ExpWrap, ALine.WrappedOffsetFor[i]);
ExpWrap := ExpWrap + ExpWrapForEachLine.w[i];
for j := 0 to ExpWrapForEachLine.w[i] - 1 do begin
GotReal := ALine.GetOffsetForWrap(TestWrapToReal, sub);
AssertEquals(Format('%s: WrapToReal Idx %d', [AName, TestWrapToReal]), i, GotReal);
AssertEquals(Format('%s: WrapToReal Idx %d SUB', [AName, TestWrapToReal]), j, sub);
inc(TestWrapToReal);
end;
end;
CheckTree(AName+'TreeCheck');
end;
procedure TTestWordWrap.InitLine(ALine: TSynWordWrapLineMap;
const AWrapValues: TExpWraps);
begin
ALine.DeleteLinesAtOffset(0, max(ALine.RealCount + ALine.Offset, ALine.LastInvalidLine+1));
if AWrapValues.len > 0 then begin
ALine.InsertLinesAtOffset(0, AWrapValues.len);
ValidateWraps(ALine, AWrapValues);
end;
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
end;
function TTestWordWrap.OnPageNeeded(AMapTree: TSynLineMapAVLTree
): TSynEditLineMapPage;
begin
Result := TSynWordWrapIndexPage.Create(AMapTree);
//TSynWordWrapIndexPage(Result).FSynEditWrappedPlugin := Self;
end;
procedure TTestWordWrap.ValidateWraps(ALine: TSynWordWrapLineMap;
const AWrapValues: TExpWraps; AStartOffs: Integer; ABackward: Boolean);
var
i: Integer;
begin
if ABackward then begin
for i := AWrapValues.len - 1 downto 0 do
ALine.ValidateLine(AStartOffs + i, AWrapValues.w[i]);
end
else begin
for i := 0 to AWrapValues.len - 1 do
ALine.ValidateLine(AStartOffs + i, AWrapValues.w[i]);
end;
ALine.EndValidate;
end;
procedure TTestWordWrap.ValidateNeededWraps(ALine: TSynWordWrapLineMap;
const AWrapValues: TExpWraps);
var
i: Integer;
begin
i := ALine.FirstInvalidLine;
while i >= 0 do begin
ALine.ValidateLine(i, AWrapValues.w[i]);
i := ALine.FirstInvalidLine;
end;
ALine.EndValidate;
end;
procedure TTestWordWrap.ValidateTreeWraps(const AWrapValues: TExpWraps;
AStartOffs: Integer);
var
i: Integer;
LowLine, HighLine: TLineIdx;
begin
while FTree.NextBlockForValidation(LowLine, HighLine) do begin
for i := LowLine to HighLine do begin
AssertTrue(i-AStartOffs < AWrapValues.len);
FTree.ValidateLine(i, AWrapValues.w[i-AStartOffs]);
end;
end;
FTree.EndValidate;
end;
procedure TTestWordWrap.AssertTreeForWraps(const AName: String;
const ExpWrapForEachLine: TExpWraps; AStartOffs: Integer);
var
i, w: Integer;
sub: TLineIdx;
begin
w := AStartOffs;
for i := 0 to (ExpWrapForEachLine.len - 1) do begin
AssertEquals(Format('%s // l=%d getWrap', [AName, i]),
w,
FTree.GetWrapLineForForText(AStartOffs + i)
);
w := w + ExpWrapForEachLine.w[i];
AssertEquals(Format('%s // l=%d getLine', [AName, i]),
i,
FTree.GetLineForForWrap(w-1, sub)
);
AssertEquals(Format('%s // l=%d sub', [AName, i]),
ExpWrapForEachLine.w[i]-1,
sub
);
end;
CheckTree(AName+'TreeCheck');
end;
function TTestWordWrap.CreateTree(APageJoinSize, APageSplitSize,
APageJoinDistance: Integer): TSynLineMapAVLTree;
begin
Result := TSynLineMapAVLTree.Create(APageJoinSize, APageSplitSize, APageJoinDistance);
Result.PageCreatorProc := @OnPageNeeded;
end;
function TTestWordWrap.TheTree: TSynLineMapAVLTree;
begin
Result := FTree;
end;
procedure TTestWordWrap.SetUp;
begin
FTree := CreateTree(15, 60, 20);
inherited SetUp;
end;
procedure TTestWordWrap.TearDown;
begin
inherited TearDown;
FTree.Free;
end;
procedure TTestWordWrap.TestWordWrapLineMap;
var
ALine: TSynWordWrapLineMap;
ANode: TSynEditLineMapPage;
i: Integer;
ATestName: String;
w: TExpWraps;
begin
ANode := FTree.FindPageForLine(0, afmCreate).Page;
ALine := TSynWordWrapIndexPage(ANode).SynWordWrapLineMapStore;
ALine.InsertLinesAtOffset(0, 5);
ALine.InvalidateLines(2,3);
ValidateWraps(ALine, w.init([1, 1, 3, 3, 1]));
AssertLineForWraps('', ALine, w.init([1, 1, 3, 3, 1, 1,1]));
//AssertRealToWrapOffsets('', ALine, [0, 1, 2, 5, 8, 9, 10]);
//AssertWrapToRealOffset('', ALine, [0,0, 1,0, 2,0, 2,1, 2,2, 3,0, 3,1, 3,2, 4,0, 5,0]);
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
for i := 1 to 2 do begin
// insert into offset
ATestName := 'Insert at start of "Offset"';
ALine.InsertLinesAtOffset(0, 2);
ValidateWraps(ALine, w.init([2, 2]), 0, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([2, 2, 1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(0, 2);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ATestName := 'Insert at middle of "Offset"';
ALine.InsertLinesAtOffset(1, 2);
ValidateWraps(ALine, w.init([2, 2]), 1, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 2, 2, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(1, 2);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ATestName := 'Insert at end of "Offset"';
ALine.InsertLinesAtOffset(2, 2);
ValidateWraps(ALine, w.init([2, 2]), 2, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 2, 2, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(2, 2);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ATestName := 'Insert at start of "Offset" - single lines';
ALine.InsertLinesAtOffset(0, 2);
ValidateWraps(ALine, w.init([1, 1]), 0, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(0, 2);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ATestName := 'Insert at middle of "Offset" - single lines';
ALine.InsertLinesAtOffset(1, 2);
ValidateWraps(ALine, w.init([1, 1]), 1, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(1, 2);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ATestName := 'Insert at end of "Offset" - single lines';
ALine.InsertLinesAtOffset(2, 2);
ValidateWraps(ALine, w.init([1, 1]), 2, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(2, 2);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ATestName := 'Insert at start of "Offset" - single/wrap lines';
ALine.InsertLinesAtOffset(0, 2);
ValidateWraps(ALine, w.init([1, 2]), 0, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 2, 1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(1, 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(0, 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ATestName := 'Delete mixed offset/data';
ALine.DeleteLinesAtOffset(1, 2);
AssertLineForWraps(ATestName, ALine, w.init([1, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.InsertLinesAtOffset(1, 2);
ValidateWraps(ALine, w.init([1, 3]), 1, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
// insert into data
ATestName := 'Insert at middle of Data';
ALine.InsertLinesAtOffset(3, 2);
ValidateWraps(ALine, w.init([2, 2]), 3, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 2, 2, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(3, 2);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ATestName := 'Insert at middle of Data';
ALine.InsertLinesAtOffset(3, 2);
ValidateWraps(ALine, w.init([2, 2]), 3, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 2, 2, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(3, 2);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
// insert after data
ATestName := 'Insert at end of Data';
ALine.InsertLinesAtOffset(5, 1);
ValidateWraps(ALine, w.init([4]), 5, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 4, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(5, 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ATestName := 'Insert at end of Data - single line';
ALine.InsertLinesAtOffset(5, 1);
ValidateWraps(ALine, w.init([1]), 5, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(5, 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ATestName := 'Insert behind end of Data';
ALine.InsertLinesAtOffset(6, 1);
ValidateWraps(ALine, w.init([4]), 6, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1, 4, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(6, 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ATestName := 'Insert behind end of Data - single line';
ALine.InsertLinesAtOffset(6, 1);
ValidateWraps(ALine, w.init([1]), 6, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.DeleteLinesAtOffset(6, 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ATestName := 'Delete mixed data/after';
ALine.DeleteLinesAtOffset(3, 2);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.InsertLinesAtOffset(3, 2);
ValidateWraps(ALine, w.init([3, 1]), 3, i mod 1 = 1);
AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
end;
ALine.InvalidateLines(0, 4);
ValidateWraps(ALine, w.init([1,1,1,1,1]), 0, False);
AssertLineForWraps('', ALine, w.init([1, 1, 1, 1, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.InsertLinesAtOffset(0, 5);
ValidateWraps(ALine, w.init([1, 1, 3, 3, 1]));
AssertLineForWraps('', ALine, w.init([1, 1, 3, 3, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
ALine.InvalidateLines(0, 4);
ValidateWraps(ALine, w.init([1,1,1,1,1]), 0, True);
AssertLineForWraps('', ALine, w.init([1, 1, 1, 1, 1, 1,1]));
AssertEquals('all valid', -1, ALine.FirstInvalidLine);
end;
procedure TTestWordWrap.TestWordWrapLineMapInvalidate;
var
ANode1: TSynWordWrapIndexPage;
ALine1: TSynWordWrapLineMap;
//ATestName: String;
w: TExpWraps;
begin
// invalidate and insert/remove lines
ANode1 := TSynWordWrapIndexPage(FTree.FindPageForLine(0, afmCreate).Page);
ALine1 := ANode1.SynWordWrapLineMapStore;
InitLine(ALine1, w.init([1]));
ALine1.InvalidateLines(3,6);
AssertEquals('invalid', 3, ALine1.FirstInvalidLine);
AssertEquals('invalid', 6, ALine1.LastInvalidLine);
ALine1.DeleteLinesAtOffset(6,2);
AssertEquals('invalid', 3, ALine1.FirstInvalidLine);
AssertEquals('invalid', 5, ALine1.LastInvalidLine);
ALine1.InsertLinesAtOffset(2,1);
AssertEquals('invalid', 2, ALine1.FirstInvalidLine);
ValidateWraps(ALine1, w.init([1]), 2);
AssertEquals('invalid', 4, ALine1.FirstInvalidLine);
AssertEquals('invalid', 6, ALine1.LastInvalidLine);
ALine1.InsertLinesAtOffset(5,1);
AssertEquals('invalid', 4, ALine1.FirstInvalidLine);
AssertEquals('invalid', 7, ALine1.LastInvalidLine);
ALine1.DeleteLinesAtOffset(4,1);
AssertEquals('invalid', 4, ALine1.FirstInvalidLine);
AssertEquals('invalid', 6, ALine1.LastInvalidLine);
end;
procedure TTestWordWrap.TestWordWrapLineMapInvalidateNoneContineous;
var
ANode1: TSynWordWrapIndexPage;
ALine1: TSynWordWrapLineMap;
//ATestName: String;
w: TExpWraps;
begin
// invalidate and insert/remove lines
ANode1 := TSynWordWrapIndexPage(FTree.FindPageForLine(0, afmCreate).Page);
ALine1 := ANode1.SynWordWrapLineMapStore;
InitLine(ALine1, w.init([1]));
ALine1.InvalidateLines(30,31);
ALine1.InvalidateLines(32,35);
ALine1.InvalidateLines(40,41);
ALine1.InvalidateLines(10,11);
ALine1.InvalidateLines(20,21);
ALine1.InvalidateLines(22,23);
AssertEquals('invalid first from', 10, ALine1.FirstInvalidLine);
AssertEquals('invalid first to', 11, ALine1.FirstInvalidEndLine);
AssertEquals('invalid last', 41, ALine1.LastInvalidLine);
ALine1.ValidateLine(10, 1);
AssertEquals('invalid first from', 11, ALine1.FirstInvalidLine);
AssertEquals('invalid first to', 11, ALine1.FirstInvalidEndLine);
AssertEquals('invalid last', 41, ALine1.LastInvalidLine);
ALine1.ValidateLine(11, 1);
AssertEquals('invalid first from', 20, ALine1.FirstInvalidLine);
AssertEquals('invalid first to', 23, ALine1.FirstInvalidEndLine);
AssertEquals('invalid last', 41, ALine1.LastInvalidLine);
ALine1.ValidateLine(20, 1);
AssertEquals('invalid first from', 21, ALine1.FirstInvalidLine);
AssertEquals('invalid first to', 23, ALine1.FirstInvalidEndLine);
AssertEquals('invalid last', 41, ALine1.LastInvalidLine);
ALine1.ValidateLine(21, 1);
ALine1.ValidateLine(22, 1);
ALine1.ValidateLine(23, 1);
AssertEquals('invalid first from', 30, ALine1.FirstInvalidLine);
AssertEquals('invalid first to', 35, ALine1.FirstInvalidEndLine);
AssertEquals('invalid last', 41, ALine1.LastInvalidLine);
ALine1.ValidateLine(30, 1);
ALine1.ValidateLine(31, 1);
ALine1.ValidateLine(32, 1);
ALine1.ValidateLine(33, 1);
ALine1.ValidateLine(34, 1);
ALine1.ValidateLine(35, 1);
AssertEquals('invalid first from', 40, ALine1.FirstInvalidLine);
AssertEquals('invalid first to', 41, ALine1.FirstInvalidEndLine);
AssertEquals('invalid last', 41, ALine1.LastInvalidLine);
ALine1.ValidateLine(40, 1);
AssertEquals('invalid first from', 41, ALine1.FirstInvalidLine);
AssertEquals('invalid first to', 41, ALine1.FirstInvalidEndLine);
AssertEquals('invalid last', 41, ALine1.LastInvalidLine);
ALine1.ValidateLine(41, 1);
AssertEquals('invalid first from', -1, ALine1.FirstInvalidLine);
AssertEquals('invalid first to', -1, ALine1.FirstInvalidEndLine);
AssertEquals('invalid last', -1, ALine1.LastInvalidLine);
end;
procedure TTestWordWrap.TestWordWrapLineMapValidate;
var
ANode1: TSynWordWrapIndexPage;
ALine1: TSynWordWrapLineMap;
ATestName: String;
w: TExpWraps;
i: Integer;
begin
// invalidate/ re-validate => increase/decrease offset/tail by switching between wrap and one-line lines
ANode1 := TSynWordWrapIndexPage(FTree.FindPageForLine(0, afmCreate).Page);
ALine1 := ANode1.SynWordWrapLineMapStore;
ATestName := 'fill one-lines at start - increasing';
InitLine(ALine1, w.init(FillArray(10, 19)));
w.Join([1,1]);
for i := 0 to 3 do begin
ALine1.InvalidateLines(0, 3);
w.w[i] := 1;
ValidateNeededWraps(ALine1, w);
AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
end;
ATestName := 'fill one-lines at start - decreasing';
InitLine(ALine1, w.init(FillArray(10, 19)));
w.Join([1,1]);
for i := 3 downto 0 do begin
ALine1.InvalidateLines(0, 3);
w.w[i] := 1;
ValidateNeededWraps(ALine1, w);
AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
end;
ATestName := 'fill one-lines at end - decreasing';
InitLine(ALine1, w.init(FillArray(10, 19)));
w.Join([1,1]);
for i := 9 downto 7 do begin
ALine1.InvalidateLines(7, 9);
w.w[i] := 1;
ValidateNeededWraps(ALine1, w);
AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
end;
ATestName := 'fill one-lines at end - increasing';
InitLine(ALine1, w.init(FillArray(10, 19)));
w.Join([1,1]);
for i := 7 to 9 do begin
ALine1.InvalidateLines(7, 9);
w.w[i] := 1;
ValidateNeededWraps(ALine1, w);
AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
end;
ATestName := 'fill one-lines - all, incr';
InitLine(ALine1, w.init(FillArray(10, 19)));
w.Join([1,1]);
for i := 0 to 9 do begin
ALine1.InvalidateLines(0, 9);
w.w[i] := 1;
ValidateNeededWraps(ALine1, w);
AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
end;
ATestName := 'fill one-lines - all, decr';
InitLine(ALine1, w.init(FillArray(10, 19)));
w.Join([1,1]);
for i := 9 downto 0 do begin
ALine1.InvalidateLines(0, 9);
w.w[i] := 1;
ValidateNeededWraps(ALine1, w);
AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
end;
ATestName := 'fill one-lines - all, incr then decr';
InitLine(ALine1, w.init(FillArray(10, 19)));
w.Join([1,1]);
for i := 0 to 4 do begin
ALine1.InvalidateLines(0, 9);
w.w[i] := 1;
ValidateNeededWraps(ALine1, w);
AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
end;
for i := 9 downto 5 do begin
ALine1.InvalidateLines(0, 9);
w.w[i] := 1;
ValidateNeededWraps(ALine1, w);
AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
end;
ATestName := 'fill one-lines - all, decr then incr';
InitLine(ALine1, w.init(FillArray(10, 19)));
w.Join([1,1]);
for i := 9 downto 5 do begin
ALine1.InvalidateLines(0, 9);
w.w[i] := 1;
ValidateNeededWraps(ALine1, w);
AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
end;
for i := 0 to 4 do begin
ALine1.InvalidateLines(0, 9);
w.w[i] := 1;
ValidateNeededWraps(ALine1, w);
AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
end;
end;
procedure TTestWordWrap.TestWordWrapLineMapMerge;
var
ANode1, ANode2: TSynWordWrapIndexPage;
ALine1, ALine2: TSynWordWrapLineMap;
ATestName: String;
w: TExpWraps;
begin
ANode1 := TSynWordWrapIndexPage(FTree.FindPageForLine(0, afmCreate).Page);
ANode2 := TSynWordWrapIndexPage(FTree.FindPageForLine(100, afmCreate).Page);
ALine1 := ANode1.SynWordWrapLineMapStore;
ALine2 := ANode2.SynWordWrapLineMapStore;
ATestName := 'Insert at start: no-offset => no-offset';
InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
//ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
AssertLineForWraps('', ALine1, w.init([4, 5, 6, 2, 1, 3, 3, 1, 1,1]));
ATestName := 'Insert at start: no-offset => offset';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
//ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
AssertLineForWraps('', ALine1, w.init([4, 5, 6, 1, 1, 3, 3, 1, 1,1]));
ATestName := 'Insert at start: offset => no offset';
InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
InitLine(ALine2, w.init([1, 5, 6]));
ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
//ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
AssertLineForWraps('', ALine1, w.init([1, 5, 6, 2, 1, 3, 3, 1, 1,1]));
ATestName := 'Insert at start: offset => offset';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([1, 5, 6]));
ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
//ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
AssertLineForWraps('', ALine1, w.init([1, 5, 6, 1, 1, 3, 3, 1, 1,1]));
ATestName := 'Insert at start: no-offset 2nd => no-offset';
InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine2.MoveLinesAtEndTo(ALine1, 1, 2);
//ALine1.InsertLinesFromPage(ALine2, 1, 0, 2);
AssertLineForWraps('', ALine1, w.init([5, 6, 2, 1, 3, 3, 1, 1,1]));
ATestName := 'Insert at start: no-offset 2nd => offset';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine2.MoveLinesAtEndTo(ALine1, 1, 2);
//ALine1.InsertLinesFromPage(ALine2, 1, 0, 2);
AssertLineForWraps('', ALine1, w.init([5, 6, 1, 1, 3, 3, 1, 1,1]));
ATestName := 'Insert at start: offset 2nd => no offset';
InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
InitLine(ALine2, w.init([1, 5, 6]));
ALine2.MoveLinesAtEndTo(ALine1, 1, 2);
//ALine1.InsertLinesFromPage(ALine2, 1, 0, 2);
AssertLineForWraps('', ALine1, w.init([5, 6, 2, 1, 3, 3, 1, 1,1]));
ATestName := 'Insert at start: offset 2nd => offset';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([1, 5, 6]));
ALine2.MoveLinesAtEndTo(ALine1, 1, 2);
//ALine1.InsertLinesFromPage(ALine2, 1, 0, 2);
AssertLineForWraps('', ALine1, w.init([5, 6, 1, 1, 3, 3, 1, 1,1]));
ATestName := 'Insert at start: offset 3rd => no offset';
InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
InitLine(ALine2, w.init([1, 5, 6, 7]));
ALine2.MoveLinesAtEndTo(ALine1, 2, 2);
//ALine1.InsertLinesFromPage(ALine2, 2, 0, 2);
AssertLineForWraps('', ALine1, w.init([6, 7, 2, 1, 3, 3, 1, 1,1]));
ATestName := 'Insert at start: offset 3rd => offset';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([1, 5, 6, 7]));
ALine2.MoveLinesAtEndTo(ALine1, 2, 2);
//ALine1.InsertLinesFromPage(ALine2, 2, 0, 2);
AssertLineForWraps('', ALine1, w.init([6, 7, 1, 1, 3, 3, 1, 1,1]));
ATestName := 'Insert at start: overlen';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine2.MoveLinesAtEndTo(ALine1, 0, 4);
//ALine1.InsertLinesFromPage(ALine2, 0, 0, 4);
AssertLineForWraps(ATestName, ALine1, w.init([4, 5, 6, 1, 1, 1, 3, 3, 1, 1,1]));
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine2.MoveLinesAtEndTo(ALine1, 1, 4);
//ALine1.InsertLinesFromPage(ALine2, 1, 0, 4);
AssertLineForWraps(ATestName, ALine1, w.init([5, 6, 1, 1, 1, 1, 3, 3, 1, 1,1]));
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([1]));
ALine2.MoveLinesAtEndTo(ALine1, 1, 4);
//ALine1.InsertLinesFromPage(ALine2, 1, 0, 4);
AssertLineForWraps(ATestName, ALine1, w.init([1, 1, 1, 1, 1, 1, 3, 3, 1, 1,1]));
ATestName := 'Insert at end';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine2.MoveLinesAtStartTo(ALine1, 2, 5);
//ALine1.InsertLinesFromPage(ALine2, 0, 5, 3);
AssertLineForWraps(ATestName, ALine1, w.init([1, 1, 3, 3, 1, 4, 5, 6, 1,1]));
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine2.MoveLinesAtStartTo(ALine1, 2, 6);
//ALine1.InsertLinesFromPage(ALine2, 0, 6, 3);
AssertLineForWraps(ATestName, ALine1, w.init([1, 1, 3, 3, 1, 1, 4, 5, 6, 1,1]));
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([1, 5, 6]));
ALine2.MoveLinesAtStartTo(ALine1, 2, 5);
//ALine1.InsertLinesFromPage(ALine2, 0, 5, 3);
AssertLineForWraps(ATestName, ALine1, w.init([1, 1, 3, 3, 1, 1, 5, 6, 1,1]));
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([1, 5, 6]));
ALine2.MoveLinesAtStartTo(ALine1, 2, 6);
//ALine1.InsertLinesFromPage(ALine2, 0, 6, 3);
AssertLineForWraps(ATestName, ALine1, w.init([1, 1, 3, 3, 1, 1, 1, 5, 6, 1,1]));
///////////////////////////////////////
(* split node at none wrapping lines - ensure the none-wrap "WrappedExtraSums" are stripped *)
ATestName := 'Insert at start: empty lines in the middle -> dest 2';
InitLine(ALine1, w.init([4, 5]));
InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
ALine2.MoveLinesAtEndTo(ALine1, 3, 3);
AssertLineForWraps('', ALine1, w.init([1, 1, 3, 4, 5, 1,1]));
ATestName := 'Insert at start: empty lines in the middle -> dest 1';
InitLine(ALine1, w.init([4]));
InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
ALine2.MoveLinesAtEndTo(ALine1, 3, 3);
AssertLineForWraps('', ALine1, w.init([1, 1, 3, 4, 1,1]));
ATestName := 'Insert at start: empty lines in the middle -> empty dest';
InitLine(ALine1, w.init([]));
InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
ALine2.MoveLinesAtEndTo(ALine1, 3, 3);
AssertLineForWraps('', ALine1, w.init([1, 1, 3, 1,1]));
ATestName := 'Insert at start: empty lines in the middle -> dest 2 - with dest offset';
InitLine(ALine1, w.init([1, 1, 4, 5]));
InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
ALine2.MoveLinesAtEndTo(ALine1, 3, 3);
AssertLineForWraps('', ALine1, w.init([1, 1, 3, 1, 1, 4, 5, 1,1]));
ATestName := 'Insert at start: empty lines in the middle -> dest 2 - with source offset';
InitLine(ALine1, w.init([4, 5]));
InitLine(ALine2, w.init([1, 1, 2, 1, 1, 1, 1, 3]));
ALine2.MoveLinesAtEndTo(ALine1, 5, 3);
AssertLineForWraps('', ALine1, w.init([1, 1, 3, 4, 5, 1,1]));
ATestName := 'Insert at end : empty lines in the middle -> dest 2';
InitLine(ALine1, w.init([4, 5]));
InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
ALine2.MoveLinesAtStartTo(ALine1, 3, 2);
AssertLineForWraps('', ALine1, w.init([4, 5, 2, 1, 1, 1,1]));
ATestName := 'Insert at end : empty lines in the middle -> dest 1';
InitLine(ALine1, w.init([4]));
InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
ALine2.MoveLinesAtStartTo(ALine1, 3, 1);
AssertLineForWraps('', ALine1, w.init([4, 2, 1, 1, 1,1]));
ATestName := 'Insert at end : empty lines in the middle -> emyty dest';
InitLine(ALine1, w.init([]));
InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
ALine2.MoveLinesAtStartTo(ALine1, 3, 0);
AssertLineForWraps('', ALine1, w.init([2, 1, 1, 1,1]));
ATestName := 'Insert at end : empty lines in the middle -> dest 2 - with dest offset';
InitLine(ALine1, w.init([1, 1, 4, 5]));
InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
ALine2.MoveLinesAtStartTo(ALine1, 3, 4);
AssertLineForWraps('', ALine1, w.init([1, 1, 4, 5, 2, 1, 1, 1,1]));
ATestName := 'Insert at end : empty lines in the middle -> dest 2 - with source offset';
InitLine(ALine1, w.init([4, 5]));
InitLine(ALine2, w.init([1, 1, 2, 1, 1, 1, 1, 2]));
ALine2.MoveLinesAtStartTo(ALine1, 5, 2);
AssertLineForWraps('', ALine1, w.init([4, 5, 1, 1, 2, 1, 1, 1,1]));
end;
procedure TTestWordWrap.TestWordWrapLineMapMergeInvalidate;
var
ANode1, ANode2: TSynWordWrapIndexPage;
ALine1, ALine2: TSynWordWrapLineMap;
ATestName: String;
w: TExpWraps;
procedure DoMoveLinesAtEndTo(const AName: String;
const AWrapValues1, AWrapValues2: array of integer; AInvalLine: Integer; const AInvalDest: Boolean;
ASourceStartLine, ALineCount: Integer;
Exp: array of integer; ExpInval: Integer
);
begin
InitLine(ALine1, w.init(AWrapValues1));
InitLine(ALine2, w.init(AWrapValues2));
if AInvalDest then
ALine1.InvalidateLines(AInvalLine, AInvalLine)
else
ALine2.InvalidateLines(AInvalLine, AInvalLine);
ALine2.MoveLinesAtEndTo(ALine1, ASourceStartLine, ALineCount);
AssertLineForWraps(AName, ALine1, w.init(Exp));
AssertEquals(AName+' invalid', ExpInval, ALine1.FirstInvalidLine);
AssertEquals(AName+' invalid', ExpInval, ALine1.LastInvalidLine);
end;
procedure DoMoveLinesAtStartTo(const AName: String;
const AWrapValues1, AWrapValues2: array of integer; AInvalLine: Integer; const AInvalDest: Boolean;
ASourceEndLine, ATargetStartLine: Integer;
Exp: array of integer; ExpInval: Integer
);
begin
InitLine(ALine1, w.init(AWrapValues1));
InitLine(ALine2, w.init(AWrapValues2));
if AInvalDest then
ALine1.InvalidateLines(AInvalLine, AInvalLine)
else
ALine2.InvalidateLines(AInvalLine, AInvalLine);
ALine2.MoveLinesAtStartTo(ALine1, ASourceEndLine, ATargetStartLine);
AssertLineForWraps(AName, ALine1, w.init(Exp));
AssertEquals(AName+' invalid', ExpInval, ALine1.FirstInvalidLine);
AssertEquals(AName+' invalid', ExpInval, ALine1.LastInvalidLine);
end;
begin
ANode1 := TSynWordWrapIndexPage(FTree.FindPageForLine(0, afmCreate).Page);
ANode2 := TSynWordWrapIndexPage(FTree.FindPageForLine(100, afmCreate).Page);
ALine1 := ANode1.SynWordWrapLineMapStore;
ALine2 := ANode2.SynWordWrapLineMapStore;
ATestName := 'Insert at start: target inval';
InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine1.InvalidateLines(1, 1);
ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
//ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
AssertLineForWraps('', ALine1, w.init([4, 5, 6, 2, 1, 3, 3, 1, 1,1]));
AssertEquals('invalid', 4, ALine1.FirstInvalidLine);
AssertEquals('invalid', 4, ALine1.LastInvalidLine);
ATestName := 'Insert at start: source inval';
InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine2.InvalidateLines(1, 1);
ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
//ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
AssertLineForWraps('', ALine1, w.init([4, 5, 6, 2, 1, 3, 3, 1, 1,1]));
AssertEquals('invalid', 1, ALine1.FirstInvalidLine);
AssertEquals('invalid', 1, ALine1.LastInvalidLine);
ATestName := 'Insert at start: source inval';
InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine2.InvalidateLines(0, 1);
ALine2.MoveLinesAtEndTo(ALine1, 1, 2);
//ALine1.InsertLinesFromPage(ALine2, 1, 0, 2);
AssertLineForWraps('', ALine1, w.init([5, 6, 2, 1, 3, 3, 1, 1,1]));
AssertEquals('invalid', 0, ALine1.FirstInvalidLine);
AssertEquals('invalid', 0, ALine1.LastInvalidLine);
ATestName := 'Insert at start: source inval';
InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine2.InvalidateLines(0, 1);
ALine2.MoveLinesAtEndTo(ALine1, 2, 1);
//ALine1.InsertLinesFromPage(ALine2, 2, 0, 1);
AssertLineForWraps('', ALine1, w.init([6, 2, 1, 3, 3, 1, 1,1]));
AssertEquals('invalid', -1, ALine1.FirstInvalidLine);
AssertEquals('invalid', -1, ALine1.LastInvalidLine);
ATestName := 'Insert at start: both inval';
InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine1.InvalidateLines(1, 1);
ALine2.InvalidateLines(2, 2);
ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
//ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
AssertLineForWraps('', ALine1, w.init([4, 5, 6, 2, 1, 3, 3, 1, 1,1]));
AssertEquals('invalid', 2, ALine1.FirstInvalidLine);
AssertEquals('invalid', 4, ALine1.LastInvalidLine);
ATestName := 'Insert at end: source inval';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([4, 5, 6]));
ALine2.InvalidateLines(2, 2);
ALine2.MoveLinesAtStartTo(ALine1, 2, 5);
//ALine1.InsertLinesFromPage(ALine2, 0, 5, 3);
AssertLineForWraps(ATestName, ALine1, w.init([1, 1, 3, 3, 1, 4, 5, 6, 1,1]));
AssertEquals('invalid', 7, ALine1.FirstInvalidLine);
AssertEquals('invalid', 7, ALine1.LastInvalidLine);
ATestName := 'Insert from end to empty';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([]));
ALine1.MoveLinesAtEndTo(ALine2, 3, 3);
AssertLineForWraps(ATestName, ALine2, w.init([3, 1, 1, 1,1,1]));
ATestName := 'Insert from end to empty';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([]));
ALine1.MoveLinesAtEndTo(ALine2, 2, 3);
AssertLineForWraps(ATestName, ALine2, w.init([3, 3, 1, 1,1,1]));
ATestName := 'Insert from end to empty';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([]));
ALine1.MoveLinesAtEndTo(ALine2, 1, 3);
AssertLineForWraps(ATestName, ALine2, w.init([1, 3, 3, 1,1,1]));
ATestName := 'Insert from end to empty';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([]));
ALine1.MoveLinesAtEndTo(ALine2, 1, 4);
AssertLineForWraps(ATestName, ALine2, w.init([1, 3, 3, 1, 1,1,1]));
ATestName := 'Insert from after end to empty';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([]));
ALine1.MoveLinesAtEndTo(ALine2, 6, 3);
AssertLineForWraps(ATestName, ALine2, w.init([1, 1, 1, 1,1,1]));
ATestName := 'Insert from start to empty';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([]));
ALine1.MoveLinesAtStartTo(ALine2, 2, 0);
AssertLineForWraps(ATestName, ALine2, w.init([1, 1, 3, 1,1,1]));
ATestName := 'Insert from start to empty';
InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
InitLine(ALine2, w.init([]));
ALine1.MoveLinesAtStartTo(ALine2, 2, 0);
AssertLineForWraps(ATestName, ALine2, w.init([2, 1, 3, 1,1,1,1]));
ATestName := 'Insert from start to empty';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([]));
ALine1.MoveLinesAtStartTo(ALine2, 1, 0);
AssertLineForWraps(ATestName, ALine2, w.init([1, 1, 1,1,1,1]));
ATestName := 'Insert from start to empty';
InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
InitLine(ALine2, w.init([]));
ALine1.MoveLinesAtStartTo(ALine2, 2, 3);
AssertLineForWraps(ATestName, ALine2, w.init([1, 1, 1, 1, 1, 3, 1,1,1]));
///////////////////////////////////////
(* split node at none wrapping lines - ensure the none-wrap "WrappedExtraSums" are stripped *)
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2',
[4, 5], [2, 1, 1, 1, 1, 3], 1, True, 3, 3, {=>} [1, 1, 3, 4, 5, 1,1], 4);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2',
[4, 5], [2, 1, 1, 1, 1, 3], 1, False, 3, 3, {=>} [1, 1, 3, 4, 5, 1,1], -1);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2',
[4, 5], [2, 1, 1, 1, 1, 3], 2, False, 3, 3, {=>} [1, 1, 3, 4, 5, 1,1], -1);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2',
[4, 5], [2, 1, 1, 1, 1, 3], 3, False, 3, 3, {=>} [1, 1, 3, 4, 5, 1,1], 0);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2',
[4, 5], [2, 1, 1, 1, 1, 3], 4, False, 3, 3, {=>} [1, 1, 3, 4, 5, 1,1], 1);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - gap at source end',
[4, 5], [2, 1, 1, 1, 1, 3], 1, True, 3, 4, {=>} [1, 1, 3, 1, 4, 5, 1,1], 5);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - gap at source end',
[4, 5], [2, 1, 1, 1, 1, 3], 1, False, 3, 4, {=>} [1, 1, 3, 1, 4, 5, 1,1], -1);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - gap at source end',
[4, 5], [2, 1, 1, 1, 1, 3], 4, False, 3, 4, {=>} [1, 1, 3, 1, 4, 5, 1,1], 1);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 1',
[4], [2, 1, 1, 1, 1, 3], 1, True, 3, 3, {=>} [1, 1, 3, 4, 1,1], 4);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 1',
[4], [2, 1, 1, 1, 1, 3], 1, False, 3, 3, {=>} [1, 1, 3, 4, 1,1], -1);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 1',
[4], [2, 1, 1, 1, 1, 3], 3, False, 3, 3, {=>} [1, 1, 3, 4, 1,1], 0);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> empty dest',
[], [2, 1, 1, 1, 1, 3], 1, True, 3, 3, {=>} [1, 1, 3, 1,1], 4);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> empty dest',
[], [2, 1, 1, 1, 1, 3], 1, False, 3, 3, {=>} [1, 1, 3, 1,1], -1);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> empty dest',
[], [2, 1, 1, 1, 1, 3], 4, False, 3, 3, {=>} [1, 1, 3, 1,1], 1);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset',
[1,1,4, 5], [2, 1, 1, 1, 1, 3], 1, True, 3, 3, {=>} [1, 1, 3, 1,1,4, 5, 1,1], 4);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset',
[1,1,4, 5], [2, 1, 1, 1, 1, 3], 1, False, 3, 3, {=>} [1, 1, 3, 1,1,4, 5, 1,1], -1);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset',
[1,1,4, 5], [2, 1, 1, 1, 1, 3], 4, False, 3, 3, {=>} [1, 1, 3, 1,1,4, 5, 1,1], 1);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
[4, 5], [1,1,2, 1, 1, 1, 1, 3], 1, True, 5, 3, {=>} [1, 1, 3, 4, 5, 1,1], 4);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
[4, 5], [1,1,2, 1, 1, 1, 1, 3], 1, False, 5, 3, {=>} [1, 1, 3, 4, 5, 1,1], -1);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
[4, 5], [1,1,2, 1, 1, 1, 1, 3], 3, False, 5, 3, {=>} [1, 1, 3, 4, 5, 1,1], -1);
DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
[4, 5], [1,1,2, 1, 1, 1, 1, 3], 6, False, 5, 3, {=>} [1, 1, 3, 4, 5, 1,1], 1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2',
[4, 5], [2, 1, 1, 1, 1, 3], 1, True, 3, 2, {=>} [4, 5, 2, 1, 1, 1,1], 1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2',
[4, 5], [2, 1, 1, 1, 1, 3], 1, False, 3, 2, {=>} [4, 5, 2, 1, 1, 1,1], 3);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2',
[4, 5], [2, 1, 1, 1, 1, 3], 4, False, 3, 2, {=>} [4, 5, 2, 1, 1, 1,1], -1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - gap at target end',
[4, 5], [2, 1, 1, 1, 1, 3], 1, True, 3, 3, {=>} [4, 5, 1, 2, 1, 1, 1,1], 1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - gap at target end',
[4, 5], [2, 1, 1, 1, 1, 3], 1, False, 3, 3, {=>} [4, 5, 1, 2, 1, 1, 1,1], 4);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - gap at target end',
[4, 5], [2, 1, 1, 1, 1, 3], 4, False, 3, 3, {=>} [4, 5, 1, 2, 1, 1, 1,1], -1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 1',
[4], [2, 1, 1, 1, 1, 3], 1, True, 3, 1, {=>} [4, 2, 1, 1, 1,1], 1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 1',
[4], [2, 1, 1, 1, 1, 3], 1, False, 3, 1, {=>} [4, 2, 1, 1, 1,1], 2);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 1',
[4], [2, 1, 1, 1, 1, 3], 4, False, 3, 1, {=>} [4, 2, 1, 1, 1,1], -1);
// empty dest can not have invalid...
//DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> empty dest',
// [], [2, 1, 1, 1, 1, 3], 1, True, 3, 0, {=>} [2, 1, 1, 1,1], 1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> empty dest',
[], [2, 1, 1, 1, 1, 3], 1, False, 3, 0, {=>} [2, 1, 1, 1,1], 1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> empty dest',
[], [2, 1, 1, 1, 1, 3], 4, False, 3, 0, {=>} [2, 1, 1, 1,1], -1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> empty dest - gap',
[], [2, 1, 1, 1, 1, 3], 1, True, 3, 2, {=>} [1,1, 2, 1, 1, 1,1], 1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> empty dest - gap',
[], [2, 1, 1, 1, 1, 3], 1, False, 3, 2, {=>} [1,1, 2, 1, 1, 1,1], 3);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> empty dest- gap',
[], [2, 1, 1, 1, 1, 3], 4, False, 3, 2, {=>} [1,1, 2, 1, 1, 1,1], -1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset',
[1,1, 4, 5], [2, 1, 1, 1, 1, 3], 1, True, 3, 4, {=>} [1,1, 4, 5, 2, 1, 1, 1,1], 1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset',
[1,1, 4, 5], [2, 1, 1, 1, 1, 3], 1, False, 3, 4, {=>} [1,1, 4, 5, 2, 1, 1, 1,1], 5);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset',
[1,1, 4, 5], [2, 1, 1, 1, 1, 3], 4, False, 3, 4, {=>} [1,1, 4, 5, 2, 1, 1, 1,1], -1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset - gap',
[1,1, 4, 5], [2, 1, 1, 1, 1, 3], 1, True, 3, 5, {=>} [1,1, 4, 5, 1, 2, 1, 1, 1,1], 1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset - gap',
[1,1, 4, 5], [2, 1, 1, 1, 1, 3], 1, False, 3, 5, {=>} [1,1, 4, 5, 1, 2, 1, 1, 1,1], 6);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset - gap',
[1,1, 4, 5], [2, 1, 1, 1, 1, 3], 4, False, 3, 5, {=>} [1,1, 4, 5, 1, 2, 1, 1, 1,1], -1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
[4, 5], [1,1, 2, 1, 1, 1, 1, 3], 1, True, 5, 2, {=>} [4, 5, 1,1, 2, 1, 1, 1,1], 1);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
[4, 5], [1,1, 2, 1, 1, 1, 1, 3], 1, False, 5, 2, {=>} [4, 5, 1,1, 2, 1, 1, 1,1], 3);
DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
[4, 5], [1,1, 2, 1, 1, 1, 1, 3], 6, False, 5, 2, {=>} [4, 5, 1,1, 2, 1, 1, 1,1], -1);
end;
procedure TTestWordWrap.TestWordWrapJoinWithSibling;
var
CurWraps: TExpWraps;
procedure DoTestAssert(AName: String; ExpNodeCnt: Integer);
begin
//debugln(AName); FTree.DebugDump ;
AssertTreeForWraps(AName + ' Wrap', CurWraps);
AssertEquals(AName + ' Cnt ', ExpNodeCnt, TreeNodeCount);
end;
procedure DoTestInit(AName: String; AFromVal, ALineCount, AnIncrease, ExpNodeCnt: Integer);
begin
FTree.Clear;
CurWraps.InitFill(AFromVal, AFromVal+ALineCount-1, AnIncrease); // 4 pages
FTree.AdjustForLinesInserted(0, ALineCount, 0);
ValidateTreeWraps(CurWraps);
DoTestAssert(Format('%s (Init %d, %d) Wrap', [AName, AFromVal, ALineCount]), ExpNodeCnt);
end;
procedure DoTestChgWrp(AName: String; AStartLine, ALineCount, AFromVal, AnIncrease, ExpNodeCnt: Integer);
begin
CurWraps.FillRange(AStartLine, ALineCount, AFromVal, AnIncrease);
FTree.InvalidateLines(AStartLine, AStartLine + ALineCount - 1);
ValidateTreeWraps(CurWraps);
DoTestAssert(Format('%s (Changed %d, %d) Wrap', [AName, AStartLine, ALineCount]), ExpNodeCnt);
end;
procedure DoTestDelete(AName: String; AStartLine, ALineCount, ExpNodeCnt: Integer);
begin
FTree.AdjustForLinesDeleted(AStartLine, ALineCount,0);
CurWraps.SpliceArray(AStartLine, ALineCount);
DoTestAssert(Format('%s (Deleted %d, %d) Wrap', [AName, AStartLine, ALineCount]), ExpNodeCnt);
end;
var
OffsetAtStart, OffsetAtEnd, Node1Del, Node2Del, FinalNodeCount: Integer;
N: String;
begin
(* After "DoTestInit" each node should be filled to the max.
So the test knows where each node begins
*)
FTree := CreateTree(10, 30, 4); // APageJoinSize, APageSplitSize, APageJoinDistance
For OffsetAtStart := 0 to 3 do
For OffsetAtEnd := 0 to 3 do // RealEnd = NextNode.Startline-1 - x
For Node1Del := 18 to 25 do
For Node2Del := 18 to 25 do
begin
N := Format('Start: %d End: %d Del1: %d Del2: %d', [OffsetAtStart, OffsetAtEnd, Node1Del, Node2Del]);
FinalNodeCount := 3;
if (OffsetAtStart + OffsetAtEnd > 4) or // APageJoinDistance not met
(Node1Del + OffsetAtEnd < 20) or (Node2Del + OffsetAtStart < 20)
then
FinalNodeCount := 4;
DoTestInit (N, 10, 4*30, 1, 4); // Create 4 full nodes
if OffsetAtStart > 0 then
DoTestChgWrp(N, 90, OffsetAtStart, 1, 0, 4); // At Start of Last node
if OffsetAtEnd > 0 then
DoTestChgWrp(N, 90-OffsetAtEnd, OffsetAtEnd, 1, 0, 4); // At End of 2nd-Last node
DoTestDelete(N, 60, Node1Del, 4);
DoTestDelete(N, 95-Node1Del, Node2Del, FinalNodeCount);
// Delete from 2nd node before 1st node
DoTestInit (N, 10, 4*30, 1, 4); // Create 4 full nodes
if OffsetAtStart > 0 then
DoTestChgWrp(N, 90, OffsetAtStart, 1, 0, 4); // At Start of Last node
if OffsetAtEnd > 0 then
DoTestChgWrp(N, 90-OffsetAtEnd, OffsetAtEnd, 1, 0, 4); // At End of 2nd-Last node
DoTestDelete(N, 95, Node2Del, 4);
DoTestDelete(N, 60, Node1Del, FinalNodeCount);
end;
end;
procedure TTestWordWrap.TestWordWrapTreeInsertThenDelete;
var
CurWraps: TExpWraps;
InsPos, InsLen, DelPos, DelCount: Integer;
begin
FTree.Free;
FTree := CreateTree(2, 9, 4);
// FTree := TSynLineMapAVLTree.Create(TSynWordWrapIndexPage, 2, 11, 4);
for DelPos := 0 to 26 do
for DelCount := 1 to Min(5, 27-DelPos) do
for InsPos := 0 to 29 do
for InsLen := 1 to 4 do begin
FTree.Clear;
// init
CurWraps.InitFill(10, 10+26);
FTree.AdjustForLinesInserted(0, 27, 0);
ValidateTreeWraps(CurWraps);
//FTree.DebugDump;
AssertTreeForWraps(Format('Before ins at pos %d Len %d', [InsPos, InsLen]), CurWraps);
// ins
CurWraps.Join(FillArray(500, 499+InsLen), InsPos);
FTree.AdjustForLinesInserted(InsPos, InsLen, 0);
//FTree.DebugDump;
ValidateTreeWraps(CurWraps);
//FTree.DebugDump;
AssertTreeForWraps(Format('After ins at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]), CurWraps);
// del
FTree.AdjustForLinesDeleted(DelPos, DelCount, 0);
CurWraps.SpliceArray(DelPos, DelCount);
AssertTrue(Format('valid After del at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]),
not FTree.NeedsValidation);
AssertTreeForWraps(Format('After del at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]), CurWraps);
end;
end;
procedure TTestWordWrap.TestWordWrapTreeDeleteThenInsert;
var
CurWraps: TExpWraps;
InsPos, InsLen, DelPos, DelCount: Integer;
begin
FTree.Free;
FTree := CreateTree(2, 9, 4);
// FTree := TSynLineMapAVLTree.Create(TSynWordWrapIndexPage, 2, 11, 4);
for DelPos := 0 to 26 do
for DelCount := 1 to Min(5, 27-DelPos) do
for InsPos := 0 to 29 do
for InsLen := 1 to 4 do begin
//if (InsPos<>15) or (InsLen<>1) or (DelPos<>0) or (DelCount<>1) then continue;
FTree.Clear;
// init
CurWraps.InitFill(10, 10+26);
FTree.AdjustForLinesInserted(0, 27, 0);
ValidateTreeWraps(CurWraps);
//FTree.DebugDump;
AssertTreeForWraps(Format('Before del at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]), CurWraps);
// del
FTree.AdjustForLinesDeleted(DelPos, DelCount, 0);
CurWraps.SpliceArray(DelPos, DelCount);
AssertTrue(Format('valid After del at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]),
not FTree.NeedsValidation);
AssertTreeForWraps(Format('After del at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]), CurWraps);
// ins
FTree.AdjustForLinesInserted(InsPos, InsLen, 0);
CurWraps.Join(FillArray(500, 499+InsLen), InsPos);
//FTree.DebugDump;
ValidateTreeWraps(CurWraps);
//FTree.DebugDump;
AssertTreeForWraps(Format('After del/ins : del at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]), CurWraps);
end;
end;
{ TTestWordWrapPluginBase }
procedure TTestWordWrapPluginBase.ClearCaret;
begin
SynEdit.CaretXY := Point(2,2);
SynEdit.CaretXY := Point(1,1);
end;
function TTestWordWrapPluginBase.GetTreeNodeHolder(AIndex: Integer
): TSynEditLineMapPageHolder;
begin
Result := FWordWrap.FLineMapView.Tree.FirstPage;
while (AIndex > 0) and Result.HasPage do begin
dec(AIndex);
Result := Result.Next;
end;
end;
procedure TTestWordWrapPluginBase.SetCaret(SourcePt: TPointType; APos: TPoint);
begin
case SourcePt of
ptViewed: SynEdit.CaretObj.ViewedLineCharPos := Apos;
ptAlternateViewed: SynEdit.CaretObj.ViewedLineCharPos := Apos;
ptPhys: SynEdit.CaretObj.LineCharPos := Apos;
ptLog: SynEdit.CaretObj.LineBytePos := APos;
end;
end;
procedure TTestWordWrapPluginBase.TestCaret(AName: String;SourcePt, ExpPt: TPointType;
AnExp: TPoint; AnExpOffs: Integer);
var
got: TPoint;
src, dest: String;
begin
case ExpPt of
ptViewed: got := SynEdit.CaretObj.ViewedLineCharPos;
ptAlternateViewed: got := SynEdit.CaretObj.ViewedLineCharPos;
ptPhys: got := SynEdit.CaretObj.LineCharPos;
ptLog: got := SynEdit.CaretObj.LineBytePos;
end;
writestr(src, SourcePt);
writestr(dest, ExpPt);
AssertEquals(Format('%s (%s -> %s)', [AName, src, dest]), AnExp, got);
if (ExpPt = ptLog) and (AnExpOffs >= 0) then
AssertEquals(Format('%s (%s -> %s) Offs: ', [AName, src, dest]), AnExpOffs, SynEdit.CaretObj.BytePosOffset);
end;
class procedure TTestWordWrapPluginBase.AssertEquals(const AMessage: string;
Expected, Actual: TPoint);
begin
AssertEquals(AMessage, dbgs(Expected), dbgs(Actual));
end;
procedure TTestWordWrapPluginBase.AddLines(AFirstLineIdx, ACount,
ALen: Integer; AnID: String; SkipBeginUpdate: Boolean;
AReplaceExisting: Boolean);
var
i, j: Integer;
l: String;
begin
if not SkipBeginUpdate then SynEdit.BeginUpdate;
for i := 0 to ACount - 1 do begin
l := '';
j := 0;
while Length(l) < ALen do begin
l := l + copy(AnID+'_'+IntToStr(i)+'_'+IntToStr(j) + ' ',1,12);
inc(j);
end;
l := copy(l, 1, ALen);
if AReplaceExisting then
SynEdit.Lines[AFirstLineIdx + i] := l
else
SynEdit.Lines.Insert(AFirstLineIdx + i, l);
end;
if not SkipBeginUpdate then SynEdit.EndUpdate;
end;
procedure TTestWordWrapPluginBase.InternalCheckLine(AName: String;
dsp: TLazSynDisplayView; ALine: TLineIdx; AExpTextStart: String;
NoTrim: Boolean);
var
gotRealLine: TLineIdx;
gotStartPos, GotLineLen: Integer;
gotTokenOk: Boolean;
gotToken: TLazSynDisplayTokenInfo;
gotText: PChar;
s: String;
begin
dsp.SetHighlighterTokensLine(ALine, gotRealLine, gotStartPos, GotLineLen);
gotTokenOk := dsp.GetNextHighlighterToken(gotToken);
if gotTokenOk then
gotText := gotToken.TokenStart
else
gotText := '';
if AExpTextStart = '' then begin
AssertEquals(AName, '', gotText);
AssertEquals(AName, 0, GotLineLen);
exit;
end
else
if gotText = '' then begin
AssertTrue(AName, False);
exit;
end;
if NoTrim then
s := copy(gotText, 1, Length(AExpTextStart))
else
s := copy(Trim(gotText), 1, Length(AExpTextStart));
if not(AExpTextStart = s) then begin
debugln(['Failed ', AName, ' ', ALine, ':']);
DebugLn(['GOT: "', StringReplace(gotText, #9, '#9', [rfReplaceAll]), '"']);
DebugLn(['EXP: "', StringReplace(AExpTextStart, #9, '#9', [rfReplaceAll]), '"']);
end;
AssertEquals(AName, AExpTextStart, s);
end;
procedure TTestWordWrapPluginBase.CheckLine(AName: String; ALine: TLineIdx;
AExpTextStart: String; NoTrim: Boolean);
var
v: TSynEditStringsLinked;
dsp: TLazSynDisplayView;
begin
v := SynEdit.TextViewsManager.SynTextView[SynEdit.TextViewsManager.Count - 1];
dsp := v.DisplayView;
dsp.InitHighlighterTokens(nil);
try
InternalCheckLine(AName, dsp, ALine, AExpTextStart, NoTrim);
finally
dsp.FinishHighlighterTokens;
end;
end;
procedure TTestWordWrapPluginBase.CheckLines(AName: String;
AStartLine: TLineIdx; AExpTextStart: array of String; NoTrim: Boolean);
var
v: TSynEditStringsLinked;
dsp: TLazSynDisplayView;
i, gotStartPos, GotLineLen: Integer;
gotTokenOk: Boolean;
gotToken: TLazSynDisplayTokenInfo;
s: String;
gotRealLine: TLineIdx;
begin
v := SynEdit.TextViewsManager.SynTextView[SynEdit.TextViewsManager.Count - 1];
dsp := v.DisplayView;
dsp.InitHighlighterTokens(nil);
try
try
for i := 0 to Length(AExpTextStart)-1 do
InternalCheckLine(AName, dsp, AStartLine+i, AExpTextStart[i], NoTrim);
except
dsp.FinishHighlighterTokens;
dsp.InitHighlighterTokens(nil);
for i := 0 to Length(AExpTextStart)-1 do begin
dsp.SetHighlighterTokensLine(AStartLine+i, gotRealLine, gotStartPos, GotLineLen);
s := '';
while dsp.GetNextHighlighterToken(gotToken) and (gotToken.TokenLength > 0) do
s := s + copy(gotToken.TokenStart, 1, gotToken.TokenLength);
debugln('Line %d (real %d): "%s" %d/%d start %d',
[AStartLine+i, gotRealLine, StringReplace(s, #9, '#9', [rfReplaceAll]), length(s), GotLineLen, gotStartPos]);
end;
raise;
end;
finally
dsp.FinishHighlighterTokens;
end;
end;
procedure TTestWordWrapPluginBase.CheckLine(AName: String;
AExpLine: TTestWrapLineInfo);
begin
CheckLine(AName, AExpLine.ViewedIdx, AExpLine.TextStartMatch, AExpLine.NoTrim);
end;
procedure TTestWordWrapPluginBase.CheckLines(AName: String;
AExpLines: TTestViewedLineRangeInfo);
var
i: Integer;
n: TSynEditLineMapPageHolder;
begin
for i := 0 to length(AExpLines) - 1 do begin
CheckLine(Format('%s (%d)', [AName, i]), AExpLines[i]);
CheckLineIndexMapping(Format('%s (%d)', [AName, i]), AExpLines[i].TextIdx, AExpLines[i].ViewedTopIdx, AExpLines[i].ViewedBottomIdx);
end;
if TheTree <> nil then begin
n := TheTree.FirstPage;
if n.HasPage then
CheckTree(AName+' (TreeCheck)', n.Page, n.StartLine, 0, SynEdit.Lines.Count-1);
end;
end;
procedure TTestWordWrapPluginBase.CheckXyMap(AName: String; APhysTExtXY,
AViewedXY: TPoint; OnlyViewToText: Boolean);
var
v: TSynEditStringsLinked;
GotTextXY, GotViewXY: TPoint;
begin
v := SynEdit.TextViewsManager.SynTextView[SynEdit.TextViewsManager.Count - 1];
GotTextXY := v.ViewXYToTextXY(AViewedXY);
GotViewXY := v.TextXYToViewXY(APhysTExtXY);
AssertTrue(Format('%s: Viewed %s to Text %s (exp) => got %s', [AName, dbgs(AViewedXY), dbgs(APhysTExtXY), dbgs(GotTextXY)]),
(GotTextXY.x = APhysTExtXY.x) and (GotTextXY.y = APhysTExtXY.y) );
if not OnlyViewToText then
AssertTrue(Format('%s: Text %s to viewed %s (exp) => got %s', [AName, dbgs(APhysTExtXY), dbgs(AViewedXY), dbgs(GotViewXY)]),
(GotViewXY.x = AViewedXY.x) and (GotViewXY.y = AViewedXY.y) );
end;
procedure TTestWordWrapPluginBase.CheckXyMap(AName: String; APhysTExtX,
APhysTExtY, AViewedX, AViewedY: integer; OnlyViewToText: Boolean);
begin
CheckXyMap(AName, Point(APhysTExtX, APhysTExtY), Point(AViewedX, AViewedY), OnlyViewToText);
end;
procedure TTestWordWrapPluginBase.CheckXyMap(AName: String;
APoints: TPointSpecs);
var
StartP, TestP: TPointType;
begin
CheckXyMap(AName + 'XyMap', APoints.xy[ptPhys], APoints.xy[ptViewed]);
if APoints.xy[ptAlternateViewed].x > 0 then
CheckXyMap(AName+ 'XyMap(a)', APoints.xy[ptPhys], APoints.xy[ptAlternateViewed], True);
for TestP in TPointType do begin
if TestP = ptAlternateViewed then
continue;
ClearCaret;
SynEdit.CaretXY := APoints.XY[ptPhys];
TestCaret(AName, ptPhys, TestP, APoints.XY[TestP], APoints.LogOffs);
if APoints.LogOffs <= 0 then begin
ClearCaret;
SynEdit.LogicalCaretXY := APoints.XY[ptLog];
TestCaret(AName, ptLog, TestP, APoints.XY[TestP], APoints.LogOffs);
end;
AName := AName + ' [CaretObj] ';
for StartP in TPointType do begin
if APoints.xy[StartP].x <= 0 then
Continue;
if (StartP = ptAlternateViewed) then // and (TestP = ptViewed) then
continue;
if (StartP = ptLog) and (APoints.LogOffs > 0) then
continue;
ClearCaret;
SetCaret(StartP, APoints.XY[StartP]);
TestCaret(AName, StartP, TestP, APoints.XY[TestP], APoints.LogOffs);
end;
end;
end;
procedure TTestWordWrapPluginBase.CheckXyMap(AName: String;
APoints: TPointSpecs; ATestCommands: array of TCommandAndPointSpecs);
var
i: Integer;
StartP, TestP: TPointType;
n: string;
c: TSynEditorCommand;
begin
CheckXyMap(AName+'(p)', APoints);
for i := 0 to Length(ATestCommands) - 1 do begin
if not ATestCommands[i].RunOnlyIf then
Continue;
n := AName+'(p'+IntToStr(i)+')';
CheckXyMap(n, ATestCommands[i].Exp);
for StartP in TPointType do begin
if APoints.xy[StartP].x <= 0 then
Continue;
if (StartP = ptAlternateViewed) then // and (TestP = ptViewed) then
continue;
if (StartP = ptLog) and (APoints.LogOffs > 0) then
continue;
for TestP in TPointType do begin
if TestP = ptAlternateViewed then
continue;
ClearCaret;
SetCaret(StartP, APoints.XY[StartP]);
for c in ATestCommands[i].Cmd do
SynEdit.ExecuteCommand(c, '', nil);
TestCaret(n, StartP, TestP, ATestCommands[i].Exp.XY[TestP], ATestCommands[i].Exp.LogOffs);
end;
end;
end;
end;
procedure TTestWordWrapPluginBase.CheckLineIndexMapping(AName: String;
ATextIdx, AViewTopIdx, AViewBottomIdx: TLineIdx);
var
v: TSynEditStringsLinked;
i: TLineIdx;
dv: TLazSynDisplayView;
r: TLineRange;
begin
v := SynEdit.TextViewsManager.SynTextView[SynEdit.TextViewsManager.Count - 1];
dv := v.DisplayView;
AssertEquals(AName + ' TextToViewIndex', AViewTopIdx, v.TextToViewIndex(ATextIdx));
for i := AViewTopIdx to AViewBottomIdx do
AssertEquals(AName + ' ViewToTextIndex', ATextIdx, v.ViewToTextIndex(i));
r := dv.TextToViewIndex(ATextIdx);
AssertEquals(AName + 'DispView.TextToViewIndex Top', AViewTopIdx, r.Top);
AssertEquals(AName + 'DispView.TextToViewIndex Bottom', AViewBottomIdx, r.Bottom);
for i := AViewTopIdx to AViewBottomIdx do begin
AssertEquals(AName + ' ViewToTextIndex', ATextIdx, dv.ViewToTextIndex(i));
AssertEquals(AName + ' ViewToTextIndexEx', ATextIdx, dv.ViewToTextIndexEx(i, r));
AssertEquals(AName + ' ViewToTextIndexEx Top', AViewTopIdx, r.Top);
AssertEquals(AName + ' ViewToTextIndexEx Bottom', AViewBottomIdx, r.Bottom);
end;
end;
function TTestWordWrapPluginBase.TheTree: TSynLineMapAVLTree;
begin
Result := nil;
if FWordWrap = nil then
exit;
Result := FWordWrap.FLineMapView.Tree;
end;
procedure TTestWordWrapPluginBase.ReCreateEdit(ADispWidth: Integer);
begin
TearDown;
SetUp;
SetSynEditWidth(ADispWidth);
end;
procedure TTestWordWrapPluginBase.SetUp;
begin
inherited SetUp;
FWordWrap := TLazSynEditLineWrapPlugin.Create(SynEdit);
end;
procedure TTestWordWrapPluginBase.TearDown;
begin
inherited TearDown;
end;
{ TTestWordWrapPlugin }
procedure TTestWordWrapPlugin.TestEditorWrap;
var
SkipTab, AllowPastEOL, KeepX: Boolean;
begin
SynEdit.Options := [];
SynEdit.TabWidth := 4;
SetLines([
'abc def ' + 'ABC DEFG ' + 'XYZ',
//'A' #9'B' #9'C ' + 'DEF G'#9'H' #9 + '' #9 #9'xy',
'A'#9'B'#9'C ' + 'DEF G'#9'H'#9 + #9#9'xy',
'äää ööö ' + 'ÄÄÄ ÖÖÖ ' + 'ÜÜÜ',
'999'
]);
SetSynEditWidth(10);
CheckLines('', 0, [
'abc def ',
'ABC DEFG ',
'XYZ',
'A'#9'B'#9'C ',
'DEF G'#9'H'#9,
#9#9'xy',
'äää ööö ',
'ÄÄÄ ÖÖÖ ',
'ÜÜÜ',
'999'
], True);
CheckLines('', ViewedExp(0, [
l(0, 0, 'abc def '),
l(0, 1, 'ABC DEFG '),
l(0, 2, 'XYZ'),
l(1, 0, 'A'#9'B'#9'C '),
l(1, 1, 'DEF G'#9'H'#9),
l(1, 2, #9#9'xy'),
l(2, 0, 'äää ööö '),
l(2, 1, 'ÄÄÄ ÖÖÖ '),
l(2, 2, 'ÜÜÜ'),
l(3, 0, '999')
], tTrue));
//CheckXyMap('', pt(4,3, 21,1, 21,1); // after "Z" EOL
for AllowPastEOL in boolean do
for KeepX in boolean do
for SkipTab in boolean do begin
if AllowPastEOL
then SynEdit.Options := SynEdit.Options + [eoScrollPastEol]
else SynEdit.Options := SynEdit.Options - [eoScrollPastEol];
if SkipTab
then SynEdit.Options2 := SynEdit.Options2 + [eoCaretSkipTab]
else SynEdit.Options2 := SynEdit.Options2 - [eoCaretSkipTab];
if KeepX
then SynEdit.Options := SynEdit.Options + [eoKeepCaretX]
else SynEdit.Options := SynEdit.Options - [eoKeepCaretX];
FWordWrap.CaretWrapPos := wcpEOL;
CheckXyMap('wcpEOL', p( 1,1, 1,1, 1,1),
[c(ecRight, 2,1, 2,1, 2,1,0),
c(ecDown, 2,2, 10,1, 10,1,0),
c([ecDown,ecDown], 2,3, 19,1, 19,1,0),
c([ecDown,ecDown, ecRight], 3,3, 20,1, 20,1,0),
c([ecDown,ecDown, ecRight,ecDown], 2,4, 2,2, 2,2,0, SkipTab),
c([ecDown,ecDown, ecRight,ecDown], 3,4, 3,2, 2,2,1, not SkipTab),
c([ecDown,ecDown,ecDown], 1,4, 1,2, 1,2,0, KeepX),
c([ecDown,ecDown,ecDown], 2,4, 2,2, 2,2,0, not KeepX),
c([ecDown,ecDown,ecDown,ecDown], 2,5, 12,2, 8,2,0)
]);
CheckXyMap('wcpEOL', p( 2,1, 2,1, 2,1),
[c(ecDown, 2,2, 10,1, 10,1,0)
]);
CheckXyMap('wcpEOL', p( 7,1, 7,1, 7,1), // after "e"
[c([ecColSelDown], 7,4, 7,2, 4,2,1, not SkipTab ), // A#9B#|9 // 1 in tab
c([ecColSelDown], 6,4, 6,2, 4,2,0, SkipTab ), // A#9B|#9 // 1 in tab
c([ecColSelDown,ecColSelDown], 7,7, 7,3, 12,3,0, (not SkipTab) or KeepX ),
c([ecColSelDown,ecColSelDown], 6,7, 6,3, 10,3,0, SkipTab and not KeepX ),
c([ecColSelDown,ecColSelDown,ecColSelDown], 4,10, 4,4, 4,4,0, not AllowPastEOL),
c([ecColSelDown,ecColSelDown,ecColSelDown], 7,10, 7,4, 7,4,0, AllowPastEOL and ((not SkipTab) or KeepX) )
]);
CheckXyMap('wcpEOL', p( 8,1, 8,1, 8,1));
if AllowPastEOL then
CheckXyMap('wcpEOL', p( 9,1, 1,2, 9,1, 9,1), // def |
[c([ecDown], 9,2, 17,1, 17,1,0), // DEFG|
c([ecDown,ecDown], 9,3, 26,1, 26,1,0), // XYZ |
c([ecDown,ecDown,ecDown], 9,4, 9,2, 5,2,0), // A#9B#9|C
c([ecDown,ecDown,ecDown,ecDown], 8,5, 18,2, 14,2,0, SkipTab), // G#9|H#9
c([ecDown,ecDown,ecDown,ecDown], 9,5, 19,2, 14,2,1, not SkipTab), // G#9H#|9 //in #9
c([ecDown,ecDown,ecDown,ecDown,ecDown], 9,6, 29,2, 17,2,0, (not SkipTab) or KeepX), // before x
c([ecDown,ecDown,ecDown,ecDown,ecDown], 5,6, 25,2, 16,2,0, SkipTab and not KeepX), // in tab, before x
c([ecDown,ecDown,ecDown,ecDown,ecDown,ecDown], 9,7, 9,3, 15,3,0, (not SkipTab) or KeepX),
c([ecDown,ecDown,ecDown,ecDown,ecDown,ecDown], 5,7, 5,3, 8,3,0, SkipTab and not KeepX)
])
else // not AllowPastEOL then
CheckXyMap('wcpEOL', p( 9,1, 1,2, 9,1, 9,1), // after " " / still on line 1
[c([ecDown], 9,2, 17,1, 17,1,0), // DEFG|
c([ecDown,ecDown], 4,3, 21,1, 21,1,0), // XYZ|
c([ecDown,ecDown,ecDown], 9,4, 9,2, 5,2,0, KeepX), // A#9B#9|C
c([ecDown,ecDown,ecDown], 4,4, 4,2, 2,2,2, (not KeepX) and (not SkipTab) ), // A#|9B#9C //in tab
c([ecDown,ecDown,ecDown], 2,4, 2,2, 2,2,0, (not KeepX) and SkipTab) // A#|9B#9C //in tab
]);
CheckXyMap('wcpEOL', p( 2,2, 10,1, 10,1));
CheckXyMap('wcpEOL', p( 4,2, 12,1, 12,1), // ABC| DE
[c([ecColSelDown], 2,5, 12,2, 8,2,0), // D|EF G#9
c([ecColSelDown,ecColSelDown], 4,8, 12,3, 21,3,0),
c([ecColSelDown,ecColSelDown,ecColSelDown], 4,10, 4,4, 4,4,0, not AllowPastEOL)
]);
CheckXyMap('wcpEOL', p( 9,2, 17,1, 17,1)); // after "G"
CheckXyMap('wcpEOL', p(10,2, 1,3, 18,1, 18,1)); // after " "
CheckXyMap('wcpEOL', p( 2,3, 19,1, 19,1)); // after "X"
CheckXyMap('wcpEOL', p( 4,3, 21,1, 21,1)); // after "Z" EOL
if AllowPastEOL then
CheckXyMap('wcpEOL', p( 5,3, 22,1, 22,1)); // at EOL + 1
CheckXyMap('wcpEOL', p( 1,4, 1,2, 1,2),
[c([ecDown], 2,5, 12,2, 8,2,0),
c([ecDown,ecDown], 5,6, 25,2, 16,2,0, SkipTab),
c([ecDown,ecDown], 2,6, 22,2, 15,2,1, not SkipTab),
c([ecDown,ecDown,ecDown], 1,7, 1,3, 1,3,0, KeepX),
c([ecDown,ecDown,ecDown], 5,7, 5,3, 8,3,0, (SkipTab) and not KeepX)
]);
CheckXyMap('wcpEOL', p( 2,4, 2,2, 2,2, 0)); // before tab
if not SkipTab then
CheckXyMap('wcpEOL', p( 3,4, 3,2, 2,2, 1)); // 1 inside tab
CheckXyMap('wcpEOL', p( 5,4, 5,2, 3,2, 0)); // after tab
CheckXyMap('wcpEOL', p( 9,4, 9,2, 5,2)); // after tab, before "C"
CheckXyMap('wcpEOL', p(10,4, 10,2, 6,2)); // after "C"
CheckXyMap('wcpEOL', p(11,4, 1,5, 11,2, 7,2)); // after " "
CheckXyMap('wcpEOL', p( 2,5, 12,2, 8,2)); // after "D"
CheckXyMap('wcpEOL', p( 8,5, 18,2, 14,2)); // after "H"
if not SkipTab then
CheckXyMap('wcpEOL', p( 9,5, 19,2, 14,2, 1)); // in #9
if not SkipTab then
CheckXyMap('wcpEOL', p(10,5, 20,2, 14,2, 2)); // in #9
CheckXyMap('wcpEOL', p(11,5, 1,6, 21,2, 15,2)); // after #9
if not SkipTab then
CheckXyMap('wcpEOL', p( 2,6, 22,2, 15,2, 1)); // in #9 / next line
CheckXyMap('wcpEOL', p( 5,6, 25,2, 16,2)); // after 1st #9 / next line
CheckXyMap('wcpEOL', p(10,6, 30,2, 18,2)); // after "x"
CheckXyMap('wcpEOL', p(11,6, 31,2, 19,2)); // after "z" EOL
CheckXyMap('wcpEOL', p( 1,7, 1,3, 1,3));
CheckXyMap('wcpEOL', p( 2,7, 2,3, 3,3));
CheckXyMap('wcpEOL', p( 8,7, 8,3, 14,3)); // after "ö"
CheckXyMap('wcpEOL', p( 9,7, 1,8, 9,3, 15,3)); // after " "
CheckXyMap('wcpEOL', p( 2,8, 10,3, 17,3)); // after "Ä"
CheckXyMap('wcpEOL', p( 9,8, 1,9, 17,3, 29,3)); // after " "
FWordWrap.CaretWrapPos := wcpBOL;
CheckXyMap('wcpBOL', p( 1,1, 1,1, 1,1),
[c(ecRight, 2,1, 2,1, 2,1,0),
c(ecDown, 1,2, 9,1, 9,1,0),
c([ecDown,ecDown], 1,3, 18,1, 18,1,0),
c([ecDown,ecDown, ecRight,ecRight], 3,3, 20,1, 20,1,0),
c([ecDown,ecDown, ecRight,ecRight,ecDown], 2,4, 2,2, 2,2,0, SkipTab),
c([ecDown,ecDown, ecRight,ecRight,ecDown], 3,4, 3,2, 2,2,1, not SkipTab),
c([ecDown,ecDown,ecDown], 1,4, 1,2, 1,2,0),
c([ecDown,ecDown,ecDown,ecDown], 1,5, 11,2, 7,2,0)
]);
CheckXyMap('wcpBOL', p( 2,1, 2,1, 2,1),
[c(ecDown, 2,2, 10,1, 10,1,0)
]);
CheckXyMap('wcpBOL', p( 7,1, 7,1, 7,1)); // after "e"
CheckXyMap('wcpBOL', p( 8,1, 8,1, 8,1));
CheckXyMap('wcpBOL', p( 1,2, 9,1, 9,1, 9,1)); // after " " / still on line 1
CheckXyMap('wcpBOL', p( 2,2, 10,1, 10,1));
CheckXyMap('wcpBOL', p( 9,2, 17,1, 17,1), // after "G"
[c([ecUp], 8,1, 8,1, 8,1,0),
c([ecUp, ecDown], 9,2, 17,1, 17,1,0, KeepX),
c([ecUp, ecDown], 8,2, 16,1, 16,1,0, not KeepX)
]);
CheckXyMap('wcpBOL', p( 1,3, 10,2, 18,1, 18,1)); // after " "
CheckXyMap('wcpBOL', p( 2,3, 19,1, 19,1)); // after "X"
CheckXyMap('wcpBOL', p( 4,3, 21,1, 21,1)); // after "Z" EOL
if AllowPastEOL then
CheckXyMap('wcpBOL', p( 5,3, 22,1, 22,1)); // at EOL + 1
CheckXyMap('wcpBOL', p( 1,4, 1,2, 1,2),
[c([ecDown], 1,5, 11,2, 7,2,0),
c([ecDown,ecDown], 1,6, 21,2, 15,2,0),
c([ecDown,ecDown,ecDown], 1,7, 1,3, 1,3,0)
]);
CheckXyMap('wcpBOL', p( 2,4, 2,2, 2,2, 0)); // before tab
if not SkipTab then
CheckXyMap('wcpBOL', p( 3,4, 3,2, 2,2, 1)); // 1 inside tab
CheckXyMap('wcpBOL', p( 5,4, 5,2, 3,2, 0)); // after tab
CheckXyMap('wcpBOL', p( 9,4, 9,2, 5,2)); // after tab, before "C"
CheckXyMap('wcpBOL', p(10,4, 10,2, 6,2), // after "C"
[c([ecDown], 8,5, 18,2, 14,2,0, SkipTab), // G#9H#|9
c([ecDown], 10,5, 20,2, 14,2,2, not SkipTab), // G#9H#|9
c([ecDown,ecDown], 10,6, 30,2, 18,2,0, KeepX), // #9#9X|Y
c([ecDown,ecDown], 5,6, 25,2, 16,2,0, (not KeepX) and SkipTab) // #9#9X|Y
]);
CheckXyMap('wcpBOL', p( 1,5, 11,4, 11,2, 7,2)); // after " "
CheckXyMap('wcpBOL', p( 2,5, 12,2, 8,2)); // after "D"
CheckXyMap('wcpBOL', p( 8,5, 18,2, 14,2)); // after "H"
if not SkipTab then
CheckXyMap('wcpBOL', p( 9,5, 19,2, 14,2, 1)); // in #9
if not SkipTab then
CheckXyMap('wcpBOL', p(10,5, 20,2, 14,2, 2)); // in #9
CheckXyMap('wcpBOL', p( 1,6, 11,5, 21,2, 15,2)); // after #9
if not SkipTab then
CheckXyMap('wcpBOL', p( 2,6, 22,2, 15,2, 1)); // in #9 / next line
CheckXyMap('wcpBOL', p( 5,6, 25,2, 16,2)); // after 1st #9 / next line
CheckXyMap('wcpBOL', p(10,6, 30,2, 18,2)); // after "x"
CheckXyMap('wcpBOL', p(11,6, 31,2, 19,2), // after "y" EOL
[c([ecUp], 8,5, 18,2, 14,2,0, SkipTab), // G#9H#|9
c([ecUp], 10,5, 20,2, 14,2,2, not SkipTab), // G#9H#|9
c([ecUp,ecDown], 11,6, 31,2, 19,2,0, KeepX)
]);
CheckXyMap('wcpBOL', p( 1,7, 1,3, 1,3));
CheckXyMap('wcpBOL', p( 2,7, 2,3, 3,3));
CheckXyMap('wcpBOL', p( 8,7, 8,3, 14,3)); // after "ö"
CheckXyMap('wcpBOL', p( 1,8, 9,7, 9,3, 15,3)); // after " "
CheckXyMap('wcpBOL', p( 2,8, 10,3, 17,3)); // after "Ä"
CheckXyMap('wcpBOL', p( 1,9, 9,8, 17,3, 29,3)); // after " "
end;
CheckLineIndexMapping('LineMap 0', 0, 0, 2);
CheckLineIndexMapping('LineMap 1', 1, 3, 5);
CheckLineIndexMapping('LineMap 2', 2, 6, 8);
CheckLineIndexMapping('LineMap 3', 3, 9, 9);
SynEdit.ExecuteCommand(ecEditorBottom, '', nil);
AssertEquals('ecEditorBottom', 4 ,SynEdit.CaretY);
AssertEquals('ecEditorBottom', 10 ,SynEdit.CaretObj.ViewedLineCharPos.y);
SetSynEditWidth(65);
AddLines(0, 6000, 60, 'A');
CheckLine('', 0, 'A_0_0');
CheckLine('', 1, 'A_1_0');
CheckLine('', 2, 'A_2_0');
CheckLine('', 3, 'A_3_0');
SetSynEditWidth(35);
CheckLine('', 0, 'A_0_0');
CheckLine('', 1, 'A_0_3');
CheckLine('', 2, 'A_1_0');
// ' '
end;
procedure TTestWordWrapPlugin.TestWrapSplitJoin;
procedure AddLineTestCount(AName: String; ALineIdx, ACount, ALen: Integer; AExpCount: Integer);
begin
AddLines(ALineIdx, ACount, ALen, 'A');
AssertEquals(Format('%s : After ins %d Line(s) at %d Len %d', [AName, ACount, ALineIdx, ALen]), AExpCount, TreeNodeCount);
end;
procedure AddLineTestCount(AName: String; ALineIdx, ALen: Integer; AExpCount: Integer);
begin
AddLineTestCount(AName, ALineIdx, 1, ALen, AExpCount);
end;
procedure ChangeLineTestCount(AName: String; ALineIdx, ALen: Integer; AExpCount: Integer);
begin
AddLines(ALineIdx, 1, ALen, 'A', False, True);
AssertEquals(Format('%s : After ins %d Line(s) at %d Len %d', [AName, 1, ALineIdx, ALen]), AExpCount, TreeNodeCount);
end;
procedure ChangeLineTestCount(AName: String; ALineIdx, ANodeIdx, ALen: Integer; AExpCount: Integer);
var
n: TSynEditLineMapPageHolder;
begin
n := TreeNodeHolder[ANodeIdx];
if ALineIdx >= 0
then AddLines(n.RealStartLine + ALineIdx, 1, ALen, 'A', False, True)
else AddLines(n.RealEndLine + 1 + ALineIdx, 1, ALen, 'A', False, True);
AssertEquals(Format('%s : After ins %d Line(s) at %d Len %d', [AName, 1, ALineIdx, ALen]), AExpCount, TreeNodeCount);
end;
procedure DelLineTestCount(AName: String; ALineIdx: Integer; AExpCount: Integer);
begin
if ALineIdx < 0
then SynEdit.Lines.Delete(SynEdit.Lines.Count + 1 + ALineIdx)
else SynEdit.Lines.Delete(ALineIdx);
AssertEquals(Format('%s : After DEL Line at %d Len %d', [AName, ALineIdx]), AExpCount, TreeNodeCount);
end;
procedure DelLineTestCount(AName: String; ANodeIdx, ALineIdx: Integer; AExpCount: Integer);
var
n: TSynEditLineMapPageHolder;
begin
n := TreeNodeHolder[ANodeIdx];
if ALineIdx < 0
then SynEdit.Lines.Delete(n.RealEndLine + 1 + ALineIdx)
else SynEdit.Lines.Delete(n.RealStartLine + ALineIdx);
AssertEquals(Format('%s : After DEL Line at %d Len %d', [AName, ALineIdx]), AExpCount, TreeNodeCount);
end;
var
t: TSynLineMapAVLTree;
n1, n2: TSynEditLineMapPageHolder;
i: Integer;
begin
ReCreateEdit(10);
SynEdit.Options := [];
t := FWordWrap.FLineMapView.Tree;
debugln(' split %d join %d dist %d', [t.PageSplitSize, t.PageJoinSize, t.PageJoinDistance]);
AddLineTestCount('new: split - 2', 0, t.PageSplitSize - 2, 18, 1);
AddLineTestCount('insert start: split - 1', 0, 18, 1);
AddLineTestCount('insert start: split - 0', 0, 18, 1);
AddLineTestCount('insert start: split + 1', 0, 18, 2);
AddLineTestCount('insert start: split + 2', 0, 18, 2);
ReCreateEdit(10);
SynEdit.Options := [];
t := FWordWrap.FLineMapView.Tree;
AddLineTestCount('new: split - 2', 0, t.PageSplitSize - 2, 18, 1);
AddLineTestCount('insert end: split - 1', SynEdit.Lines.Count, 18, 1);
AddLineTestCount('insert end: split - 0', SynEdit.Lines.Count, 18, 1);
AddLineTestCount('insert end: split + 1', SynEdit.Lines.Count, 18, 2);
AddLineTestCount('insert end: split + 2', SynEdit.Lines.Count, 18, 2);
ReCreateEdit(10);
SynEdit.Options := [];
t := FWordWrap.FLineMapView.Tree;
AddLineTestCount('new: split - 2', 0, t.PageSplitSize - 2, 18, 1);
AddLineTestCount('insert @10: split - 1', 10, 18, 1);
AddLineTestCount('insert @10: split - 0', 10, 18, 1);
AddLineTestCount('insert @10: split + 1', 10, 18, 2);
AddLineTestCount('insert @10: split + 2', 10, 18, 2);
ReCreateEdit(10);
SynEdit.Options := [];
t := FWordWrap.FLineMapView.Tree;
i := t.PageSplitSize - 2;
AddLineTestCount('new: split - 2', 0, i, 18, 1);
AddLineTestCount('new: split - 2', i, 10, 1, 1);
ChangeLineTestCount('update end: split - 1', i, 18, 1); inc(i);
ChangeLineTestCount('update end: split - 0', i, 18, 1); inc(i);
ChangeLineTestCount('update end: split + 1', i, 18, 2); inc(i);
ChangeLineTestCount('update end: split + 2', i, 18, 2); inc(i);
ReCreateEdit(10);
SynEdit.Options := [];
t := FWordWrap.FLineMapView.Tree;
i := 9;
AddLineTestCount('new: split - 2', 0, t.PageSplitSize - 2, 18, 1);
AddLineTestCount('new: split - 2', 0, 10, 1, 1);
ChangeLineTestCount('update start: split - 1', i, 18, 1); dec(i);
ChangeLineTestCount('update start: split - 0', i, 18, 1); dec(i);
ChangeLineTestCount('update start: split + 1', i, 18, 2); dec(i);
ChangeLineTestCount('update start: split + 2', i, 18, 2); dec(i);
///////////////////
ReCreateEdit(10);
SynEdit.Options := [];
t := FWordWrap.FLineMapView.Tree;
AddLineTestCount('new: split double', 0, t.PageSplitSize + t.PageJoinSize + 2, 18, 2);
n1 := TreeNodeHolder[0];
while n1.RealCount > t.PageJoinSize + 1 do
SynEdit.Lines.Delete(n1.RealStartLine + 2);
AssertEquals('insert start: split + 2', 2, TreeNodeCount);
n2 := TreeNodeHolder[1];
while n2.RealCount > t.PageJoinSize + 1 do
SynEdit.Lines.Delete(n2.RealStartLine + 2);
AssertEquals('insert start: split + 2', 2, TreeNodeCount);
SynEdit.Lines.Delete(1);
SynEdit.Lines.Delete(SynEdit.Lines.Count - 2);
AssertEquals('insert start: split + 2', 1, TreeNodeCount);
ReCreateEdit(10);
SynEdit.Options := [];
t := FWordWrap.FLineMapView.Tree;
AddLineTestCount('new: split double', 0, t.PageSplitSize + t.PageJoinSize + 2, 18, 2);
n1 := TreeNodeHolder[0];
while n1.RealCount > t.PageJoinSize + 1 do
ChangeLineTestCount('edit n1 start', 0,0, 1, 2);
n2 := TreeNodeHolder[1];
while n2.RealCount > t.PageJoinSize + 1 do
ChangeLineTestCount('edit n2 end', -1,1, 1, 2);
ChangeLineTestCount('edit n1 start', 0,0, 1, 2);
ChangeLineTestCount('edit n2 end ', -1,1, 1, 1);
// do not join
ReCreateEdit(10);
SynEdit.Options := [];
t := FWordWrap.FLineMapView.Tree;
AddLineTestCount('new: split * 2 - 2', 0, t.PageSplitSize * 2 - 2, 18, 2);
n1 := TreeNodeHolder[0];
while n1.RealCount > t.PageJoinSize + 1 do
ChangeLineTestCount('edit n1 end', -1,0, 1, 2);
n2 := TreeNodeHolder[1];
while n2.RealCount > t.PageJoinSize + 1 do
ChangeLineTestCount('edit n2 start', 0,1, 1, 2);
ChangeLineTestCount('edit n1 start', -1,0, 1, 2);
ChangeLineTestCount('edit n2 end ', 0,1, 1, 2);
ChangeLineTestCount('edit n1 start', -1,0, 1, 2);
ChangeLineTestCount('edit n2 end ', 0,1, 1, 2);
ChangeLineTestCount('edit n1 start', -1,0, 1, 2);
ChangeLineTestCount('edit n2 end ', 0,1, 1, 2);
end;
procedure TTestWordWrapPlugin.TestEditorEdit;
begin
SynEdit.Options := [];
SynEdit.TabWidth := 4;
SetLines([
'abc def ' + 'ABC DEFG ' + 'XYZ',
'',
//'A' #9'B' #9'C ' + 'DEF G'#9'H' #9 + '' #9 #9'xy',
'A'#9'B'#9'C ' + 'DEF G'#9'H'#9 + #9#9'xy',
'',
'äää ööö ' + 'ÄÄÄ ÖÖÖ ' + 'ÜÜÜ',
'',
'999'
]);
SetSynEditWidth(10);
SetSynEditWidth(10);
CheckLines('', 0, [
'abc def ',
'ABC DEFG ',
'XYZ',
'',
'A'#9'B'#9'C ',
'DEF G'#9'H'#9,
#9#9'xy',
'',
'äää ööö ',
'ÄÄÄ ÖÖÖ ',
'ÜÜÜ',
'',
'999'
], True);
SynEdit.BeginUpdate;
SynEdit.TestTypeText(1,7, '4 ');
SynEdit.TestTypeText(1,3, '2 ');
SynEdit.TestTypeText(1,5, '3 ');
SynEdit.TestTypeText(1,1, '1 ');
SynEdit.EndUpdate;
CheckLines('', 0, [
'1 abc def ',
'ABC DEFG ',
'XYZ',
'',
'2 A'#9'B'#9'C ',
'DEF G'#9'H'#9,
#9#9'xy',
'',
'3 äää ööö ',
'ÄÄÄ ÖÖÖ ',
'ÜÜÜ',
'',
'4 999'
], True);
end;
initialization
RegisterTest(TTestWordWrap);
RegisterTest(TTestWordWrapPlugin);
end.