mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-15 05:42:37 +02:00
2522 lines
93 KiB
ObjectPascal
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.
|
|
|