1 unit TestWordWrap;
2 
3 {$mode objfpc}{$H+}
4 
5 interface
6 
7 uses
8   Classes, SysUtils, math, TestBase, SynEditViewedLineMap, SynEditMiscClasses,
9   SynEditTypes, SynEditWrappedView,
10   LazSynEditText, SynEditHighlighterFoldBase, LazLoggerBase,
11   SynEditKeyCmds, SynEdit, SynEditPointClasses, testregistry;
12 
13 type
14   TIntArray = Array of integer;
15 
16   { TExpWraps }
17 
18   TExpWraps = object
19     w: Array of Integer;
20     len: Integer;
Initnull21     function Init(const a: array of integer): TExpWraps;
22     procedure SetCapacity(l: Integer);
23     procedure InitFill(AFrom, ATo: integer; AIncrease: Integer = 1);
24     procedure FillRange(AStartIdx, ACount, AFromVal: integer; AIncrease: Integer = 1);
25     procedure Join(const a: TExpWraps; AInsertPos: Integer = -1);
26     procedure Join(const a: array of integer; AInsertPos: Integer = -1);
27     procedure SpliceArray(ADelFrom, ADelCount: integer);
28   end;
29 
30   { TTestWordWrapBase }
31 
32   TTestWordWrapBase = class(TTestBase)
33   protected
TheTreenull34     function TheTree: TSynLineMapAVLTree; virtual; abstract;
TreeNodeCountnull35     function  TreeNodeCount: integer;
36     procedure CheckTree(AName: String); virtual;
37     procedure CheckTree(AName: String; ANode: TSynEditLineMapPage; ANodeLine: TLineIdx; AMinLine, AMaxLine: TLineIdx);
38 
39   end;
40 
41   { TTestWordWrap }
42 
43   TTestWordWrap = class(TTestWordWrapBase)
44   private
45     FTree: TSynLineMapAVLTree;
46     procedure AssertRealToWrapOffsets(const AName: String; ALine: TSynWordWrapLineMap;
47       const ExpWrapOffsets: TExpWraps; AStartOffs: Integer = 0);
48     procedure AssertWrapToRealOffset(const AName: String; ALine: TSynWordWrapLineMap;
49       const ExpRealAndSubOffsets: TExpWraps; AStartOffs: Integer = 0);
50     procedure AssertLineForWraps(const AName: String; ALine: TSynWordWrapLineMap;
51       const ExpWrapForEachLine: TExpWraps; AnExpAllValid: Boolean = False);
52     procedure InitLine(ALine: TSynWordWrapLineMap;
53       const AWrapValues: TExpWraps);
OnPageNeedednull54     function OnPageNeeded(AMapTree: TSynLineMapAVLTree): TSynEditLineMapPage;
55     procedure ValidateWraps(ALine: TSynWordWrapLineMap;
56       const AWrapValues: TExpWraps; AStartOffs: Integer = 0; ABackward: Boolean = False);
57     procedure ValidateNeededWraps(ALine: TSynWordWrapLineMap; const AWrapValues: TExpWraps);
58 
59     procedure ValidateTreeWraps(const AWrapValues: TExpWraps; AStartOffs: Integer = 0);
60     procedure AssertTreeForWraps(const AName: String; const ExpWrapForEachLine: TExpWraps; AStartOffs: Integer = 0);
61 
CreateTreenull62     function CreateTree(APageJoinSize, APageSplitSize, APageJoinDistance: Integer): TSynLineMapAVLTree;
63   protected
TheTreenull64     function TheTree: TSynLineMapAVLTree; override;
65 
66     procedure SetUp; override;
67     procedure TearDown; override;
68   published
69     procedure TestWordWrapLineMap;
70     procedure TestWordWrapLineMapInvalidate;
71     procedure TestWordWrapLineMapInvalidateNoneContineous;
72     procedure TestWordWrapLineMapValidate;
73     procedure TestWordWrapLineMapMerge;
74     procedure TestWordWrapLineMapMergeInvalidate;
75     procedure TestWordWrapJoinWithSibling;
76 
77     procedure TestWordWrapTreeInsertThenDelete;
78     procedure TestWordWrapTreeDeleteThenInsert;
79   end;
80 
81   TPointType = (ptViewed, ptAlternateViewed, ptPhys, ptLog);
82 
83   TPointSpecs = record
84     XY: array [TPointType] of TPoint;
85     LogOffs: Integer;
86   end;
87 
88   TCommandAndPointSpecs = record
89     Exp: TPointSpecs;
90     Cmd: Array of TSynEditorCommand;
91     RunOnlyIf: Boolean;
92   end;
93 
94   TTestWrapLineInfo = record
95     TextIdx: TLineIdx;
96     ViewedIdx, ViewedTopIdx, ViewedBottomIdx: TLineIdx;
97     SubIdx: Integer;
98     //FirstLogX: Integer;
99     TextStartMatch: String;
100     NoTrim: Boolean;
101   end;
102   TTestViewedLineRangeInfo = array of TTestWrapLineInfo;
103 
104   TTripleBool = (tTrue, tFalse, tKeep);
105 
lnull106 function l(ATxtIdx: TLineIdx; ASubIdx: Integer; AText: String; ANoTrim: Boolean = False): TTestWrapLineInfo;
ViewedExpnull107 function ViewedExp(AFirstViewedIdx: TLineIdx; ALines:
108   array of TTestWrapLineInfo; ANoTrim: TTripleBool = tKeep): TTestViewedLineRangeInfo;
109 
110 type
111 
112   { TTestWordWrapPluginBase }
113 
114   TTestWordWrapPluginBase = class(TTestWordWrapBase)
115   private
116     procedure ClearCaret;
GetTreeNodeHoldernull117     function GetTreeNodeHolder(AIndex: Integer): TSynEditLineMapPageHolder;
118     procedure SetCaret(SourcePt: TPointType; APos: TPoint);
119     procedure TestCaret(AName: String; SourcePt, ExpPt: TPointType; AnExp: TPoint;
120       AnExpOffs: Integer = -1);
121   protected
122     FWordWrap: TLazSynEditLineWrapPlugin;
123     class procedure AssertEquals(const AMessage: string; Expected, Actual: TPoint); overload;
124     procedure AddLines(AFirstLineIdx, ACount, ALen: Integer; AnID: String; SkipBeginUpdate: Boolean = False; AReplaceExisting: Boolean = False);
125     procedure InternalCheckLine(AName: String; dsp: TLazSynDisplayView; ALine: TLineIdx; AExpTextStart: String; NoTrim: Boolean = False);
126     procedure CheckLine(AName: String; ALine: TLineIdx; AExpTextStart: String; NoTrim: Boolean = False);
127     procedure CheckLines(AName: String; AStartLine: TLineIdx; AExpTextStart: array of String; NoTrim: Boolean = False);
128 
129     procedure CheckLine(AName: String; AExpLine: TTestWrapLineInfo);
130     procedure CheckLines(AName: String; AExpLines: TTestViewedLineRangeInfo);
131 
132     procedure CheckXyMap(AName: String; APhysTExtXY, AViewedXY: TPoint; OnlyViewToText: Boolean = False);
133     procedure CheckXyMap(AName: String; APhysTExtX, APhysTExtY, AViewedX, AViewedY: integer; OnlyViewToText: Boolean = False);
134 
135     procedure CheckXyMap(AName: String; APoints: TPointSpecs);
136     procedure CheckXyMap(AName: String; APoints: TPointSpecs;
137       ATestCommands: array of TCommandAndPointSpecs);
138 
139     procedure CheckLineIndexMapping(AName: String; ATextIdx, AViewTopIdx, AViewBottomIdx: TLineIdx);
140 
TheTreenull141     function TheTree: TSynLineMapAVLTree; override;
142     property TreeNodeHolder[AIndex: Integer]: TSynEditLineMapPageHolder read GetTreeNodeHolder;
143 
144     procedure ReCreateEdit(ADispWidth: Integer);
145     procedure SetUp; override;
146     procedure TearDown; override;
147   end;
148 
149   TTestWordWrapPlugin = class(TTestWordWrapPluginBase)
150   published
151     procedure TestEditorWrap;
152     procedure TestWrapSplitJoin;
153     procedure TestEditorEdit;
154   end;
155 
156 implementation
157 
pnull158 function p(VX, VY,  AVX, AVY,  PX, PY,  LX, LY: Integer; Offs: Integer = -1): TPointSpecs; overload;
159 begin
160   with Result do begin
161     XY[ptViewed].X          := VX;
162     XY[ptViewed].Y          := VY;
163     XY[ptAlternateViewed].X := AVX;
164     XY[ptAlternateViewed].Y := AVY;
165     XY[ptPhys].X            := PX;
166     XY[ptPhys].Y            := PY;
167     XY[ptLog].X             := LX;
168     XY[ptLog].Y             := LY;
169     LogOffs                 := Offs;
170   end;
171 end;
172 
pnull173 function p(VX, VY,  PX, PY,  LX, LY: Integer; Offs: Integer = -1): TPointSpecs; overload;
174 begin
175   Result := p(VX, VY, -1, -1, PX, PY, LX, LY, Offs);
176 end;
177 
cnull178 function c(Cmd: Array of TSynEditorCommand; VX, VY,  AVX, AVY,  PX, PY,  LX, LY: Integer; Offs: Integer = -1; RunOnlyIf: Boolean = True): TCommandAndPointSpecs; overload;
179 begin
180   Result.Exp := p(VX, VY, AVX, AVY, PX, PY, LX, LY, Offs);
181   SetLength(Result.Cmd, Length(Cmd));
182   move(Cmd[0], Result.Cmd[0], SizeOf(cmd[0]) * Length(Cmd));
183   Result.RunOnlyIf := RunOnlyIf;
184 end;
185 
cnull186 function c(Cmd: Array of TSynEditorCommand; VX, VY,  PX, PY,  LX, LY: Integer; Offs: Integer = -1; RunOnlyIf: Boolean = True): TCommandAndPointSpecs; overload;
187 begin
188   Result := c(Cmd, VX, VY, -1, -1, PX, PY, LX, LY, Offs, RunOnlyIf);
189 end;
190 
cnull191 function c(Cmd: TSynEditorCommand; VX, VY,  PX, PY,  LX, LY: Integer; Offs: Integer = -1; RunOnlyIf: Boolean = True): TCommandAndPointSpecs; overload;
192 begin
193   Result := c([Cmd], VX, VY, -1, -1, PX, PY, LX, LY, Offs, RunOnlyIf);
194 end;
195 
FillArraynull196 function FillArray(AFrom, ATo: integer; AIncrease: Integer = 1): TIntArray;
197 var
198   i: Integer;
199 begin
200   SetLength(Result, ATo - AFrom + 1);
201   for i := 0 to high(Result) do
202     Result[i] := AFrom + i * AIncrease;
203 end;
204 
lnull205 function l(ATxtIdx: TLineIdx; ASubIdx: Integer; AText: String; ANoTrim: Boolean
206   ): TTestWrapLineInfo;
207 begin
208   Result.TextIdx := ATxtIdx;
209   Result.SubIdx  := ASubIdx;
210   Result.TextStartMatch := AText;
211   Result.NoTrim  := ANoTrim;
212 end;
213 
ViewedExpnull214 function ViewedExp(AFirstViewedIdx: TLineIdx;
215   ALines: array of TTestWrapLineInfo; ANoTrim: TTripleBool
216   ): TTestViewedLineRangeInfo;
217 var
218   i, j: Integer;
219 begin
220   SetLength(Result, Length(ALines));
221   j := 0;
222   for i := 0 to Length(ALines) - 1 do begin
223     if (i > 0) and (ALines[i].SubIdx = 0) then begin
224       while j < i do begin
225         Result[j].ViewedBottomIdx := AFirstViewedIdx - 1;
226         inc(j);
227       end;
228       j := i;
229     end;
230     Result[i] := ALines[i];
231     Result[i].ViewedIdx    := AFirstViewedIdx;
232     Result[i].ViewedTopIdx := AFirstViewedIdx - Result[i].SubIdx;
233     case ANoTrim of
234       tFalse: Result[i].NoTrim := False;
235       tTrue:  Result[i].NoTrim := True;
236     end;
237     inc(AFirstViewedIdx);
238   end;
239   while j < Length(ALines) do begin
240     Result[j].ViewedBottomIdx := AFirstViewedIdx - 1;
241     inc(j);
242   end;
243 end;
244 
245 { TTestWordWrapBase }
246 
TTestWordWrapBase.TreeNodeCountnull247 function TTestWordWrapBase.TreeNodeCount: integer;
248 var
249   n: TSynEditLineMapPageHolder;
250 begin
251   Result := 0;
252   n := TheTree.FirstPage;
253   while n.HasPage do begin
254     inc(Result);
255     n := n.Next;
256   end;
257 end;
258 
259 procedure TTestWordWrapBase.CheckTree(AName: String);
260 var
261   n: TSynEditLineMapPageHolder;
262 begin
263   if TheTree = nil then
264     AssertTrue(AName, False);
265   n := TheTree.FirstPage;
266   if n.HasPage then
267     CheckTree(AName, n.Page, n.StartLine, 0, MaxInt);
268 end;
269 
270 procedure TTestWordWrapBase.CheckTree(AName: String;
271   ANode: TSynEditLineMapPage; ANodeLine: TLineIdx; AMinLine, AMaxLine: TLineIdx
272   );
273 var
274   n: TSynEditLineMapPage;
275   dummy, i, EndLine: Integer;
276   nl: TLineIdx;
277 begin
278   nl := ANodeLine;
279   n := ANode.Precessor(nl, dummy);
280   while n <> nil do begin
281     ANode := n;
282     ANodeLine := nl;
283     n := ANode.Precessor(nl, dummy);
284   end;
285 
286   i := 0;
287   while ANode <> nil do begin
288     AssertTrue(Format('%s(%d): MinLine', [AName, i]), ANodeLine >= AMinLine);
289     EndLine := ANodeLine + Max(0, ANode.RealEndLine);
290     AssertTrue(Format('%s(%d): EndLine', [AName, i]), EndLine <= AMaxLine);
291 
292     AMinLine := EndLine + 1;
293     ANode := ANode.Successor(ANodeLine, dummy);
294     inc(i);
295   end;
296 end;
297 
298 { TExpWraps }
299 
Initnull300 function TExpWraps.Init(const a: array of integer): TExpWraps;
301 begin
302   len := Length(a);
303   if len > 0 then begin
304     SetCapacity(len);
305     move(a[0], w[0], SizeOf(w[0]) * len);
306   end;
307   Result := self;
308 end;
309 
310 procedure TExpWraps.SetCapacity(l: Integer);
311 begin
312   if Length(w) < l then
313     SetLength(w, l*2);
314 end;
315 
316 procedure TExpWraps.InitFill(AFrom, ATo: integer; AIncrease: Integer);
317 var
318   p: PLongInt;
319   i: Integer;
320 begin
321   len := ATo - AFrom + 1;
322   SetCapacity(len);
323   p := @w[0];
324   for i := 0 to len - 1 do begin
325     p^ := AFrom;
326     inc(p);
327     inc(AFrom, AIncrease);
328   end;
329 end;
330 
331 procedure TExpWraps.FillRange(AStartIdx, ACount, AFromVal: integer;
332   AIncrease: Integer);
333 var
334   p: PLongInt;
335   i: Integer;
336 begin
337   if len < AStartIdx + ACount then
338     len := AStartIdx + ACount;
339   SetCapacity(len);
340 
341   p := @w[AStartIdx];
342   for i := 0 to ACount - 1 do begin
343     p^ := AFromVal;
344     inc(p);
345     inc(AFromVal, AIncrease);
346   end;
347 
348 end;
349 
350 procedure TExpWraps.Join(const a: TExpWraps; AInsertPos: Integer);
351 var
352   i, old: Integer;
353 begin
354   if AInsertPos < 0 then
355     AInsertPos := Len;
356 
357   i := (Len-AInsertPos);
358   old := len;
359   len := len + a.len;
360   if i < 0 then
361     len := len - i;
362   SetCapacity(len);
363 
364   if i > 0 then begin
365     move(w[AInsertPos], w[AInsertPos+a.len], sizeof(w[0]) * i);
366   end
367   else
368   if i < 0 then begin
369     FillDWord(w[old], -i, 1);
370   end;
371   move(a.w[0], w[AInsertPos], sizeof(w[0]) * a.len);
372 end;
373 
374 procedure TExpWraps.Join(const a: array of integer; AInsertPos: Integer);
375 var
376   i, la, old: Integer;
377 begin
378   if AInsertPos < 0 then
379     AInsertPos := Len;
380 
381   i := (Len-AInsertPos);
382   la := Length(a);
383   old := len;
384   len := len + la;
385   if i < 0 then
386     len := len - i;
387   SetCapacity(len);
388 
389   if i > 0 then begin
390     move(w[AInsertPos], w[AInsertPos+la], sizeof(w[0]) * i);
391   end
392   else
393   if i < 0 then begin
394     FillDWord(w[old], -i, 1);
395   end;
396   move(a[0], w[AInsertPos], sizeof(w[0]) * la);
397 end;
398 
399 procedure TExpWraps.SpliceArray(ADelFrom, ADelCount: integer);
400 var
401   i: Integer;
402 begin
403   len := len - ADelCount;
404 
405   i := Length(w) - ADelFrom - ADelCount;
406   if i > 0 then
407     move(w[ADelFrom+ADelCount], w[ADelFrom], sizeof(w[0]) * (i));
408 end;
409 
410 { TTestWordWrap }
411 
412 procedure TTestWordWrap.AssertRealToWrapOffsets(const AName: String;
413   ALine: TSynWordWrapLineMap; const ExpWrapOffsets: TExpWraps;
414   AStartOffs: Integer);
415 var
416   i: Integer;
417 begin
418   for i := 0 to ExpWrapOffsets.len - 1 do
419     AssertEquals(format('%s: RealToWrap Idx %d StartOffs: %d ', [AName, i, AStartOffs]),
420       ExpWrapOffsets.w[i], ALine.WrappedOffsetFor[AStartOffs + i]);
421 end;
422 
423 procedure TTestWordWrap.AssertWrapToRealOffset(const AName: String;
424   ALine: TSynWordWrapLineMap; const ExpRealAndSubOffsets: TExpWraps;
425   AStartOffs: Integer);
426 var
427   i, sub, r: Integer;
428 begin
429   for i := 0 to ExpRealAndSubOffsets.len div 2 - 1 do begin
430     r := ALine.GetOffsetForWrap(AStartOffs + i, sub);
431     AssertEquals(format('%s: WrapToReal Idx %d StartOffs: %d ', [AName, i, AStartOffs]),
432       ExpRealAndSubOffsets.w[i*2], r);
433     AssertEquals(format('%s: WrapToReal(SUB) Idx %d StartOffs: %d ', [AName, i, AStartOffs]),
434       ExpRealAndSubOffsets.w[i*2+1], sub);
435   end;
436 end;
437 
438 procedure TTestWordWrap.AssertLineForWraps(const AName: String;
439   ALine: TSynWordWrapLineMap; const ExpWrapForEachLine: TExpWraps;
440   AnExpAllValid: Boolean);
441 var
442   i, j, ExpWrap, TestWrapToReal, GotReal, sub: Integer;
443 begin
444   if AnExpAllValid then
445     AssertTrue(AName + ' - all lines valid', ALine.FirstInvalidLine < 0);
446   i := 0;
447   while (i < ExpWrapForEachLine.len) and (ExpWrapForEachLine.w[i] = 1) do
448     inc(i);
449   if i = ExpWrapForEachLine.len then
450     i := 0;
451   AssertEquals(Format('%s: Offset', [AName]), i, ALine.Offset);
452 
453   j := ExpWrapForEachLine.len - 1;
454   while (j >= 0) and (ExpWrapForEachLine.w[j] = 1) do
455     dec(j);
456   AssertEquals(Format('%s: RealCount', [AName]), j + 1 - i, ALine.RealCount);
457 
458   ExpWrap := 0;
459   TestWrapToReal := 0;
460   for i := 0 to ExpWrapForEachLine.len - 1 do begin
461     AssertEquals(Format('%s: RealToWrap Idx %d', [AName, i]), ExpWrap, ALine.WrappedOffsetFor[i]);
462     ExpWrap := ExpWrap + ExpWrapForEachLine.w[i];
463 
464     for j := 0 to ExpWrapForEachLine.w[i] - 1 do begin
465       GotReal := ALine.GetOffsetForWrap(TestWrapToReal, sub);
466       AssertEquals(Format('%s: WrapToReal Idx %d', [AName, TestWrapToReal]), i, GotReal);
467       AssertEquals(Format('%s: WrapToReal Idx %d SUB', [AName, TestWrapToReal]), j, sub);
468       inc(TestWrapToReal);
469     end;
470   end;
471 
472   CheckTree(AName+'TreeCheck');
473 end;
474 
475 procedure TTestWordWrap.InitLine(ALine: TSynWordWrapLineMap;
476   const AWrapValues: TExpWraps);
477 begin
478   ALine.DeleteLinesAtOffset(0, max(ALine.RealCount + ALine.Offset, ALine.LastInvalidLine+1));
479   if AWrapValues.len > 0 then begin
480     ALine.InsertLinesAtOffset(0, AWrapValues.len);
481     ValidateWraps(ALine, AWrapValues);
482   end;
483   AssertEquals('all valid', -1, ALine.FirstInvalidLine);
484 end;
485 
TTestWordWrap.OnPageNeedednull486 function TTestWordWrap.OnPageNeeded(AMapTree: TSynLineMapAVLTree
487   ): TSynEditLineMapPage;
488 begin
489   Result := TSynWordWrapIndexPage.Create(AMapTree);
490   //TSynWordWrapIndexPage(Result).FSynEditWrappedPlugin := Self;
491 end;
492 
493 procedure TTestWordWrap.ValidateWraps(ALine: TSynWordWrapLineMap;
494   const AWrapValues: TExpWraps; AStartOffs: Integer; ABackward: Boolean);
495 var
496   i: Integer;
497 begin
498   if ABackward then begin
499     for i := AWrapValues.len - 1 downto 0 do
500       ALine.ValidateLine(AStartOffs + i, AWrapValues.w[i]);
501   end
502   else begin
503     for i := 0 to AWrapValues.len - 1 do
504       ALine.ValidateLine(AStartOffs + i, AWrapValues.w[i]);
505   end;
506   ALine.EndValidate;
507 end;
508 
509 procedure TTestWordWrap.ValidateNeededWraps(ALine: TSynWordWrapLineMap;
510   const AWrapValues: TExpWraps);
511 var
512   i: Integer;
513 begin
514   i := ALine.FirstInvalidLine;
515   while i >= 0 do begin
516     ALine.ValidateLine(i, AWrapValues.w[i]);
517     i := ALine.FirstInvalidLine;
518   end;
519   ALine.EndValidate;
520 end;
521 
522 procedure TTestWordWrap.ValidateTreeWraps(const AWrapValues: TExpWraps;
523   AStartOffs: Integer);
524 var
525   i: Integer;
526   LowLine, HighLine: TLineIdx;
527 begin
528   while FTree.NextBlockForValidation(LowLine, HighLine) do begin
529     for i := LowLine to HighLine do begin
530       AssertTrue(i-AStartOffs < AWrapValues.len);
531       FTree.ValidateLine(i, AWrapValues.w[i-AStartOffs]);
532     end;
533   end;
534   FTree.EndValidate;
535 end;
536 
537 procedure TTestWordWrap.AssertTreeForWraps(const AName: String;
538   const ExpWrapForEachLine: TExpWraps; AStartOffs: Integer);
539 var
540   i, w: Integer;
541   sub: TLineIdx;
542 begin
543   w := AStartOffs;
544   for i := 0 to (ExpWrapForEachLine.len - 1) do begin
545     AssertEquals(Format('%s // l=%d getWrap', [AName, i]),
546       w,
547       FTree.GetWrapLineForForText(AStartOffs + i)
548     );
549     w := w + ExpWrapForEachLine.w[i];
550     AssertEquals(Format('%s // l=%d getLine', [AName, i]),
551       i,
552       FTree.GetLineForForWrap(w-1, sub)
553     );
554     AssertEquals(Format('%s // l=%d sub', [AName, i]),
555       ExpWrapForEachLine.w[i]-1,
556       sub
557     );
558   end;
559 
560   CheckTree(AName+'TreeCheck');
561 end;
562 
CreateTreenull563 function TTestWordWrap.CreateTree(APageJoinSize, APageSplitSize,
564   APageJoinDistance: Integer): TSynLineMapAVLTree;
565 begin
566   Result := TSynLineMapAVLTree.Create(APageJoinSize, APageSplitSize, APageJoinDistance);
567   Result.PageCreatorProc := @OnPageNeeded;
568 end;
569 
TTestWordWrap.TheTreenull570 function TTestWordWrap.TheTree: TSynLineMapAVLTree;
571 begin
572   Result := FTree;
573 end;
574 
575 procedure TTestWordWrap.SetUp;
576 begin
577   FTree := CreateTree(15, 60, 20);
578   inherited SetUp;
579 end;
580 
581 procedure TTestWordWrap.TearDown;
582 begin
583   inherited TearDown;
584   FTree.Free;
585 end;
586 
587 procedure TTestWordWrap.TestWordWrapLineMap;
588 var
589   ALine: TSynWordWrapLineMap;
590   ANode: TSynEditLineMapPage;
591   i: Integer;
592   ATestName: String;
593   w: TExpWraps;
594 begin
595   ANode := FTree.FindPageForLine(0, afmCreate).Page;
596   ALine := TSynWordWrapIndexPage(ANode).SynWordWrapLineMapStore;
597   ALine.InsertLinesAtOffset(0, 5);
598   ALine.InvalidateLines(2,3);
599   ValidateWraps(ALine, w.init([1, 1, 3, 3, 1]));
600   AssertLineForWraps('', ALine, w.init([1, 1, 3, 3, 1,   1,1]));
601   //AssertRealToWrapOffsets('', ALine, [0, 1, 2, 5, 8, 9, 10]);
602   //AssertWrapToRealOffset('', ALine, [0,0,  1,0,  2,0, 2,1, 2,2,  3,0, 3,1, 3,2,  4,0,  5,0]);
603   AssertEquals('all valid', -1, ALine.FirstInvalidLine);
604 
605   for i := 1 to 2 do begin
606 
607     // insert into offset
608     ATestName := 'Insert at start of "Offset"';
609     ALine.InsertLinesAtOffset(0, 2);
610     ValidateWraps(ALine, w.init([2, 2]), 0, i mod 1 = 1);
611     AssertLineForWraps(ATestName, ALine, w.init([2, 2,   1, 1, 3, 3, 1,   1,1]));
612     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
613 
614     ALine.DeleteLinesAtOffset(0, 2);
615     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
616     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
617 
618 
619     ATestName := 'Insert at middle of "Offset"';
620     ALine.InsertLinesAtOffset(1, 2);
621     ValidateWraps(ALine, w.init([2, 2]), 1, i mod 1 = 1);
622     AssertLineForWraps(ATestName, ALine, w.init([1,   2, 2,   1, 3, 3, 1,   1,1]));
623     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
624 
625     ALine.DeleteLinesAtOffset(1, 2);
626     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
627     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
628 
629 
630     ATestName := 'Insert at end of "Offset"';
631     ALine.InsertLinesAtOffset(2, 2);
632     ValidateWraps(ALine, w.init([2, 2]), 2, i mod 1 = 1);
633     AssertLineForWraps(ATestName, ALine, w.init([1, 1,   2, 2,   3, 3, 1,   1,1]));
634     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
635 
636     ALine.DeleteLinesAtOffset(2, 2);
637     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
638     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
639 
640 
641 
642     ATestName := 'Insert at start of "Offset" - single lines';
643     ALine.InsertLinesAtOffset(0, 2);
644     ValidateWraps(ALine, w.init([1, 1]), 0, i mod 1 = 1);
645     AssertLineForWraps(ATestName, ALine, w.init([1, 1,   1, 1, 3, 3, 1,   1,1]));
646     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
647 
648     ALine.DeleteLinesAtOffset(0, 2);
649     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
650     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
651 
652 
653     ATestName := 'Insert at middle of "Offset" - single lines';
654     ALine.InsertLinesAtOffset(1, 2);
655     ValidateWraps(ALine, w.init([1, 1]), 1, i mod 1 = 1);
656     AssertLineForWraps(ATestName, ALine, w.init([1,   1, 1,   1, 3, 3, 1,   1,1]));
657     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
658 
659     ALine.DeleteLinesAtOffset(1, 2);
660     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
661     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
662 
663 
664     ATestName := 'Insert at end of "Offset" - single lines';
665     ALine.InsertLinesAtOffset(2, 2);
666     ValidateWraps(ALine, w.init([1, 1]), 2, i mod 1 = 1);
667     AssertLineForWraps(ATestName, ALine, w.init([1, 1,   1, 1,   3, 3, 1,   1,1]));
668     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
669 
670     ALine.DeleteLinesAtOffset(2, 2);
671     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
672     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
673 
674 
675 
676     ATestName := 'Insert at start of "Offset" - single/wrap lines';
677     ALine.InsertLinesAtOffset(0, 2);
678     ValidateWraps(ALine, w.init([1, 2]), 0, i mod 1 = 1);
679     AssertLineForWraps(ATestName, ALine, w.init([1, 2,   1, 1, 3, 3, 1,   1,1]));
680     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
681 
682     ALine.DeleteLinesAtOffset(1, 1);
683     AssertLineForWraps(ATestName, ALine, w.init([1,   1, 1, 3, 3, 1,   1,1]));
684     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
685     ALine.DeleteLinesAtOffset(0, 1);
686     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
687     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
688 
689 
690     ATestName := 'Delete mixed offset/data';
691     ALine.DeleteLinesAtOffset(1, 2);
692     AssertLineForWraps(ATestName, ALine, w.init([1,    3, 1,   1,1]));
693     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
694 
695     ALine.InsertLinesAtOffset(1, 2);
696     ValidateWraps(ALine, w.init([1, 3]), 1, i mod 1 = 1);
697     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
698     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
699 
700 
701     // insert into data
702     ATestName := 'Insert at middle of Data';
703     ALine.InsertLinesAtOffset(3, 2);
704     ValidateWraps(ALine, w.init([2, 2]), 3, i mod 1 = 1);
705     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3,   2, 2,   3, 1,   1,1]));
706     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
707 
708     ALine.DeleteLinesAtOffset(3, 2);
709     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
710     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
711 
712 
713 
714     ATestName := 'Insert at middle of Data';
715     ALine.InsertLinesAtOffset(3, 2);
716     ValidateWraps(ALine, w.init([2, 2]), 3, i mod 1 = 1);
717     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3,   2, 2,   3, 1,   1,1]));
718     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
719 
720     ALine.DeleteLinesAtOffset(3, 2);
721     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
722     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
723 
724 
725 
726     // insert after data
727     ATestName := 'Insert at end of Data';
728     ALine.InsertLinesAtOffset(5, 1);
729     ValidateWraps(ALine, w.init([4]), 5, i mod 1 = 1);
730     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   4,   1,1]));
731     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
732 
733     ALine.DeleteLinesAtOffset(5, 1);
734     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
735     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
736 
737 
738     ATestName := 'Insert at end of Data - single line';
739     ALine.InsertLinesAtOffset(5, 1);
740     ValidateWraps(ALine, w.init([1]), 5, i mod 1 = 1);
741     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,   1,1]));
742     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
743 
744     ALine.DeleteLinesAtOffset(5, 1);
745     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
746     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
747 
748 
749     ATestName := 'Insert behind end of Data';
750     ALine.InsertLinesAtOffset(6, 1);
751     ValidateWraps(ALine, w.init([4]), 6, i mod 1 = 1);
752     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1, 4,   1,1]));
753     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
754 
755     ALine.DeleteLinesAtOffset(6, 1);
756     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
757     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
758 
759 
760     ATestName := 'Insert behind end of Data - single line';
761     ALine.InsertLinesAtOffset(6, 1);
762     ValidateWraps(ALine, w.init([1]), 6, i mod 1 = 1);
763     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1, 1,   1,1]));
764     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
765 
766     ALine.DeleteLinesAtOffset(6, 1);
767     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
768     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
769 
770 
771     ATestName := 'Delete mixed data/after';
772     ALine.DeleteLinesAtOffset(3, 2);
773     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3,    1,1]));
774     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
775 
776     ALine.InsertLinesAtOffset(3, 2);
777     ValidateWraps(ALine, w.init([3, 1]), 3, i mod 1 = 1);
778     AssertLineForWraps(ATestName, ALine, w.init([1, 1, 3, 3, 1,   1,1]));
779     AssertEquals('all valid', -1, ALine.FirstInvalidLine);
780 
781   end;
782 
783   ALine.InvalidateLines(0, 4);
784   ValidateWraps(ALine, w.init([1,1,1,1,1]), 0, False);
785   AssertLineForWraps('', ALine, w.init([1, 1, 1, 1, 1,   1,1]));
786   AssertEquals('all valid', -1, ALine.FirstInvalidLine);
787 
788   ALine.InsertLinesAtOffset(0, 5);
789   ValidateWraps(ALine, w.init([1, 1, 3, 3, 1]));
790   AssertLineForWraps('', ALine, w.init([1, 1, 3, 3, 1,   1,1]));
791   AssertEquals('all valid', -1, ALine.FirstInvalidLine);
792 
793   ALine.InvalidateLines(0, 4);
794   ValidateWraps(ALine, w.init([1,1,1,1,1]), 0, True);
795   AssertLineForWraps('', ALine, w.init([1, 1, 1, 1, 1,   1,1]));
796   AssertEquals('all valid', -1, ALine.FirstInvalidLine);
797 
798 end;
799 
800 procedure TTestWordWrap.TestWordWrapLineMapInvalidate;
801 var
802   ANode1: TSynWordWrapIndexPage;
803   ALine1: TSynWordWrapLineMap;
804   //ATestName: String;
805   w: TExpWraps;
806 begin
807   // invalidate and insert/remove lines
808   ANode1 := TSynWordWrapIndexPage(FTree.FindPageForLine(0, afmCreate).Page);
809   ALine1 := ANode1.SynWordWrapLineMapStore;
810 
811   InitLine(ALine1, w.init([1]));
812   ALine1.InvalidateLines(3,6);
813   AssertEquals('invalid', 3, ALine1.FirstInvalidLine);
814   AssertEquals('invalid', 6, ALine1.LastInvalidLine);
815 
816   ALine1.DeleteLinesAtOffset(6,2);
817   AssertEquals('invalid', 3, ALine1.FirstInvalidLine);
818   AssertEquals('invalid', 5, ALine1.LastInvalidLine);
819 
820   ALine1.InsertLinesAtOffset(2,1);
821   AssertEquals('invalid', 2, ALine1.FirstInvalidLine);
822   ValidateWraps(ALine1, w.init([1]), 2);
823 
824   AssertEquals('invalid', 4, ALine1.FirstInvalidLine);
825   AssertEquals('invalid', 6, ALine1.LastInvalidLine);
826 
827   ALine1.InsertLinesAtOffset(5,1);
828   AssertEquals('invalid', 4, ALine1.FirstInvalidLine);
829   AssertEquals('invalid', 7, ALine1.LastInvalidLine);
830 
831   ALine1.DeleteLinesAtOffset(4,1);
832   AssertEquals('invalid', 4, ALine1.FirstInvalidLine);
833   AssertEquals('invalid', 6, ALine1.LastInvalidLine);
834 
835 end;
836 
837 procedure TTestWordWrap.TestWordWrapLineMapInvalidateNoneContineous;
838 var
839   ANode1: TSynWordWrapIndexPage;
840   ALine1: TSynWordWrapLineMap;
841   //ATestName: String;
842   w: TExpWraps;
843 begin
844   // invalidate and insert/remove lines
845   ANode1 := TSynWordWrapIndexPage(FTree.FindPageForLine(0, afmCreate).Page);
846   ALine1 := ANode1.SynWordWrapLineMapStore;
847 
848   InitLine(ALine1, w.init([1]));
849   ALine1.InvalidateLines(30,31);
850   ALine1.InvalidateLines(32,35);
851   ALine1.InvalidateLines(40,41);
852   ALine1.InvalidateLines(10,11);
853   ALine1.InvalidateLines(20,21);
854   ALine1.InvalidateLines(22,23);
855 
856   AssertEquals('invalid first from', 10, ALine1.FirstInvalidLine);
857   AssertEquals('invalid first to',   11, ALine1.FirstInvalidEndLine);
858   AssertEquals('invalid last',       41, ALine1.LastInvalidLine);
859 
860   ALine1.ValidateLine(10, 1);
861   AssertEquals('invalid first from', 11, ALine1.FirstInvalidLine);
862   AssertEquals('invalid first to',   11, ALine1.FirstInvalidEndLine);
863   AssertEquals('invalid last',       41, ALine1.LastInvalidLine);
864 
865   ALine1.ValidateLine(11, 1);
866   AssertEquals('invalid first from', 20, ALine1.FirstInvalidLine);
867   AssertEquals('invalid first to',   23, ALine1.FirstInvalidEndLine);
868   AssertEquals('invalid last',       41, ALine1.LastInvalidLine);
869 
870   ALine1.ValidateLine(20, 1);
871   AssertEquals('invalid first from', 21, ALine1.FirstInvalidLine);
872   AssertEquals('invalid first to',   23, ALine1.FirstInvalidEndLine);
873   AssertEquals('invalid last',       41, ALine1.LastInvalidLine);
874 
875   ALine1.ValidateLine(21, 1);
876   ALine1.ValidateLine(22, 1);
877   ALine1.ValidateLine(23, 1);
878   AssertEquals('invalid first from', 30, ALine1.FirstInvalidLine);
879   AssertEquals('invalid first to',   35, ALine1.FirstInvalidEndLine);
880   AssertEquals('invalid last',       41, ALine1.LastInvalidLine);
881 
882   ALine1.ValidateLine(30, 1);
883   ALine1.ValidateLine(31, 1);
884   ALine1.ValidateLine(32, 1);
885   ALine1.ValidateLine(33, 1);
886   ALine1.ValidateLine(34, 1);
887   ALine1.ValidateLine(35, 1);
888   AssertEquals('invalid first from', 40, ALine1.FirstInvalidLine);
889   AssertEquals('invalid first to',   41, ALine1.FirstInvalidEndLine);
890   AssertEquals('invalid last',       41, ALine1.LastInvalidLine);
891 
892   ALine1.ValidateLine(40, 1);
893   AssertEquals('invalid first from', 41, ALine1.FirstInvalidLine);
894   AssertEquals('invalid first to',   41, ALine1.FirstInvalidEndLine);
895   AssertEquals('invalid last',       41, ALine1.LastInvalidLine);
896 
897   ALine1.ValidateLine(41, 1);
898   AssertEquals('invalid first from', -1, ALine1.FirstInvalidLine);
899   AssertEquals('invalid first to',   -1, ALine1.FirstInvalidEndLine);
900   AssertEquals('invalid last',       -1, ALine1.LastInvalidLine);
901 
902 end;
903 
904 procedure TTestWordWrap.TestWordWrapLineMapValidate;
905 var
906   ANode1: TSynWordWrapIndexPage;
907   ALine1: TSynWordWrapLineMap;
908   ATestName: String;
909   w: TExpWraps;
910   i: Integer;
911 begin
912   // invalidate/ re-validate => increase/decrease offset/tail by switching between wrap and one-line lines
913   ANode1 := TSynWordWrapIndexPage(FTree.FindPageForLine(0, afmCreate).Page);
914   ALine1 := ANode1.SynWordWrapLineMapStore;
915 
916   ATestName := 'fill one-lines at start - increasing';
917   InitLine(ALine1, w.init(FillArray(10, 19)));
918   w.Join([1,1]);
919   for i := 0 to 3 do begin
920     ALine1.InvalidateLines(0, 3);
921     w.w[i] := 1;
922     ValidateNeededWraps(ALine1, w);
923     AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
924   end;
925 
926   ATestName := 'fill one-lines at start - decreasing';
927   InitLine(ALine1, w.init(FillArray(10, 19)));
928   w.Join([1,1]);
929   for i := 3 downto 0 do begin
930     ALine1.InvalidateLines(0, 3);
931     w.w[i] := 1;
932     ValidateNeededWraps(ALine1, w);
933     AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
934   end;
935 
936 
937 
938   ATestName := 'fill one-lines at end - decreasing';
939   InitLine(ALine1, w.init(FillArray(10, 19)));
940   w.Join([1,1]);
941   for i := 9 downto 7 do begin
942     ALine1.InvalidateLines(7, 9);
943     w.w[i] := 1;
944     ValidateNeededWraps(ALine1, w);
945     AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
946   end;
947 
948   ATestName := 'fill one-lines at end - increasing';
949   InitLine(ALine1, w.init(FillArray(10, 19)));
950   w.Join([1,1]);
951   for i := 7 to 9 do begin
952     ALine1.InvalidateLines(7, 9);
953     w.w[i] := 1;
954     ValidateNeededWraps(ALine1, w);
955     AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
956   end;
957 
958 
959   ATestName := 'fill one-lines - all, incr';
960   InitLine(ALine1, w.init(FillArray(10, 19)));
961   w.Join([1,1]);
962   for i := 0 to 9 do begin
963     ALine1.InvalidateLines(0, 9);
964     w.w[i] := 1;
965     ValidateNeededWraps(ALine1, w);
966     AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
967   end;
968 
969   ATestName := 'fill one-lines - all, decr';
970   InitLine(ALine1, w.init(FillArray(10, 19)));
971   w.Join([1,1]);
972   for i := 9 downto 0 do begin
973     ALine1.InvalidateLines(0, 9);
974     w.w[i] := 1;
975     ValidateNeededWraps(ALine1, w);
976     AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
977   end;
978 
979 
980   ATestName := 'fill one-lines - all, incr then decr';
981   InitLine(ALine1, w.init(FillArray(10, 19)));
982   w.Join([1,1]);
983   for i := 0 to 4 do begin
984     ALine1.InvalidateLines(0, 9);
985     w.w[i] := 1;
986     ValidateNeededWraps(ALine1, w);
987     AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
988   end;
989   for i := 9 downto 5 do begin
990     ALine1.InvalidateLines(0, 9);
991     w.w[i] := 1;
992     ValidateNeededWraps(ALine1, w);
993     AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
994   end;
995 
996   ATestName := 'fill one-lines - all, decr then incr';
997   InitLine(ALine1, w.init(FillArray(10, 19)));
998   w.Join([1,1]);
999   for i := 9 downto 5 do begin
1000     ALine1.InvalidateLines(0, 9);
1001     w.w[i] := 1;
1002     ValidateNeededWraps(ALine1, w);
1003     AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
1004   end;
1005   for i := 0 to 4 do begin
1006     ALine1.InvalidateLines(0, 9);
1007     w.w[i] := 1;
1008     ValidateNeededWraps(ALine1, w);
1009     AssertLineForWraps(Format('%s %d', [ATestName, i]), ALine1, w, True);
1010   end;
1011 
1012 end;
1013 
1014 procedure TTestWordWrap.TestWordWrapLineMapMerge;
1015 var
1016   ANode1, ANode2: TSynWordWrapIndexPage;
1017   ALine1, ALine2: TSynWordWrapLineMap;
1018   ATestName: String;
1019   w: TExpWraps;
1020 begin
1021   ANode1 := TSynWordWrapIndexPage(FTree.FindPageForLine(0, afmCreate).Page);
1022   ANode2 := TSynWordWrapIndexPage(FTree.FindPageForLine(100, afmCreate).Page);
1023   ALine1 := ANode1.SynWordWrapLineMapStore;
1024   ALine2 := ANode2.SynWordWrapLineMapStore;
1025 
1026   ATestName := 'Insert at start: no-offset => no-offset';
1027   InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
1028   InitLine(ALine2, w.init([4, 5, 6]));
1029   ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
1030   //ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
1031   AssertLineForWraps('', ALine1, w.init([4, 5, 6,   2, 1, 3, 3, 1,   1,1]));
1032 
1033   ATestName := 'Insert at start: no-offset => offset';
1034   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1035   InitLine(ALine2, w.init([4, 5, 6]));
1036   ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
1037   //ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
1038   AssertLineForWraps('', ALine1, w.init([4, 5, 6,   1, 1, 3, 3, 1,   1,1]));
1039 
1040   ATestName := 'Insert at start: offset => no offset';
1041   InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
1042   InitLine(ALine2, w.init([1, 5, 6]));
1043   ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
1044   //ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
1045   AssertLineForWraps('', ALine1, w.init([1, 5, 6,   2, 1, 3, 3, 1,   1,1]));
1046 
1047   ATestName := 'Insert at start: offset => offset';
1048   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1049   InitLine(ALine2, w.init([1, 5, 6]));
1050   ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
1051   //ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
1052   AssertLineForWraps('', ALine1, w.init([1, 5, 6,   1, 1, 3, 3, 1,   1,1]));
1053 
1054 
1055   ATestName := 'Insert at start: no-offset 2nd => no-offset';
1056   InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
1057   InitLine(ALine2, w.init([4, 5, 6]));
1058   ALine2.MoveLinesAtEndTo(ALine1, 1, 2);
1059   //ALine1.InsertLinesFromPage(ALine2, 1, 0, 2);
1060   AssertLineForWraps('', ALine1, w.init([5, 6,   2, 1, 3, 3, 1,   1,1]));
1061 
1062   ATestName := 'Insert at start: no-offset 2nd => offset';
1063   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1064   InitLine(ALine2, w.init([4, 5, 6]));
1065   ALine2.MoveLinesAtEndTo(ALine1, 1, 2);
1066   //ALine1.InsertLinesFromPage(ALine2, 1, 0, 2);
1067   AssertLineForWraps('', ALine1, w.init([5, 6,   1, 1, 3, 3, 1,   1,1]));
1068 
1069   ATestName := 'Insert at start: offset 2nd => no offset';
1070   InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
1071   InitLine(ALine2, w.init([1, 5, 6]));
1072   ALine2.MoveLinesAtEndTo(ALine1, 1, 2);
1073   //ALine1.InsertLinesFromPage(ALine2, 1, 0, 2);
1074   AssertLineForWraps('', ALine1, w.init([5, 6,   2, 1, 3, 3, 1,   1,1]));
1075 
1076   ATestName := 'Insert at start: offset 2nd => offset';
1077   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1078   InitLine(ALine2, w.init([1, 5, 6]));
1079   ALine2.MoveLinesAtEndTo(ALine1, 1, 2);
1080   //ALine1.InsertLinesFromPage(ALine2, 1, 0, 2);
1081   AssertLineForWraps('', ALine1, w.init([5, 6,   1, 1, 3, 3, 1,   1,1]));
1082 
1083   ATestName := 'Insert at start: offset 3rd => no offset';
1084   InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
1085   InitLine(ALine2, w.init([1, 5, 6, 7]));
1086   ALine2.MoveLinesAtEndTo(ALine1, 2, 2);
1087   //ALine1.InsertLinesFromPage(ALine2, 2, 0, 2);
1088   AssertLineForWraps('', ALine1, w.init([6, 7,   2, 1, 3, 3, 1,   1,1]));
1089 
1090   ATestName := 'Insert at start: offset 3rd => offset';
1091   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1092   InitLine(ALine2, w.init([1, 5, 6, 7]));
1093   ALine2.MoveLinesAtEndTo(ALine1, 2, 2);
1094   //ALine1.InsertLinesFromPage(ALine2, 2, 0, 2);
1095   AssertLineForWraps('', ALine1, w.init([6, 7,   1, 1, 3, 3, 1,   1,1]));
1096 
1097 
1098   ATestName := 'Insert at start: overlen';
1099   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1100   InitLine(ALine2, w.init([4, 5, 6]));
1101   ALine2.MoveLinesAtEndTo(ALine1, 0, 4);
1102   //ALine1.InsertLinesFromPage(ALine2, 0, 0, 4);
1103   AssertLineForWraps(ATestName, ALine1, w.init([4, 5, 6, 1,   1, 1, 3, 3, 1,   1,1]));
1104 
1105   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1106   InitLine(ALine2, w.init([4, 5, 6]));
1107   ALine2.MoveLinesAtEndTo(ALine1, 1, 4);
1108   //ALine1.InsertLinesFromPage(ALine2, 1, 0, 4);
1109   AssertLineForWraps(ATestName, ALine1, w.init([5, 6, 1, 1,   1, 1, 3, 3, 1,   1,1]));
1110 
1111   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1112   InitLine(ALine2, w.init([1]));
1113   ALine2.MoveLinesAtEndTo(ALine1, 1, 4);
1114   //ALine1.InsertLinesFromPage(ALine2, 1, 0, 4);
1115   AssertLineForWraps(ATestName, ALine1, w.init([1, 1, 1, 1,   1, 1, 3, 3, 1,   1,1]));
1116 
1117 
1118 
1119   ATestName := 'Insert at end';
1120   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1121   InitLine(ALine2, w.init([4, 5, 6]));
1122   ALine2.MoveLinesAtStartTo(ALine1, 2, 5);
1123   //ALine1.InsertLinesFromPage(ALine2, 0, 5, 3);
1124   AssertLineForWraps(ATestName, ALine1, w.init([1, 1, 3, 3, 1,   4, 5, 6,   1,1]));
1125 
1126   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1127   InitLine(ALine2, w.init([4, 5, 6]));
1128   ALine2.MoveLinesAtStartTo(ALine1, 2, 6);
1129   //ALine1.InsertLinesFromPage(ALine2, 0, 6, 3);
1130   AssertLineForWraps(ATestName, ALine1, w.init([1, 1, 3, 3, 1,   1, 4, 5, 6,   1,1]));
1131 
1132 
1133   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1134   InitLine(ALine2, w.init([1, 5, 6]));
1135   ALine2.MoveLinesAtStartTo(ALine1, 2, 5);
1136   //ALine1.InsertLinesFromPage(ALine2, 0, 5, 3);
1137   AssertLineForWraps(ATestName, ALine1, w.init([1, 1, 3, 3, 1,   1, 5, 6,   1,1]));
1138 
1139   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1140   InitLine(ALine2, w.init([1, 5, 6]));
1141   ALine2.MoveLinesAtStartTo(ALine1, 2, 6);
1142   //ALine1.InsertLinesFromPage(ALine2, 0, 6, 3);
1143   AssertLineForWraps(ATestName, ALine1, w.init([1, 1, 3, 3, 1,   1, 1, 5, 6,   1,1]));
1144 
1145 ///////////////////////////////////////
1146   (* split node at none wrapping lines - ensure the none-wrap "WrappedExtraSums" are stripped *)
1147   ATestName := 'Insert at start: empty lines in the middle -> dest 2';
1148   InitLine(ALine1, w.init([4, 5]));
1149   InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
1150   ALine2.MoveLinesAtEndTo(ALine1, 3, 3);
1151   AssertLineForWraps('', ALine1, w.init([1, 1, 3, 4, 5,   1,1]));
1152 
1153   ATestName := 'Insert at start: empty lines in the middle -> dest 1';
1154   InitLine(ALine1, w.init([4]));
1155   InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
1156   ALine2.MoveLinesAtEndTo(ALine1, 3, 3);
1157   AssertLineForWraps('', ALine1, w.init([1, 1, 3, 4,   1,1]));
1158 
1159   ATestName := 'Insert at start: empty lines in the middle -> empty dest';
1160   InitLine(ALine1, w.init([]));
1161   InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
1162   ALine2.MoveLinesAtEndTo(ALine1, 3, 3);
1163   AssertLineForWraps('', ALine1, w.init([1, 1, 3,   1,1]));
1164 
1165   ATestName := 'Insert at start: empty lines in the middle -> dest 2 - with dest offset';
1166   InitLine(ALine1, w.init([1, 1, 4, 5]));
1167   InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
1168   ALine2.MoveLinesAtEndTo(ALine1, 3, 3);
1169   AssertLineForWraps('', ALine1, w.init([1, 1, 3, 1, 1, 4, 5,   1,1]));
1170 
1171   ATestName := 'Insert at start: empty lines in the middle -> dest 2 - with source offset';
1172   InitLine(ALine1, w.init([4, 5]));
1173   InitLine(ALine2, w.init([1, 1, 2, 1, 1, 1, 1, 3]));
1174   ALine2.MoveLinesAtEndTo(ALine1, 5, 3);
1175   AssertLineForWraps('', ALine1, w.init([1, 1, 3, 4, 5,   1,1]));
1176 
1177 
1178 
1179   ATestName := 'Insert at end : empty lines in the middle -> dest 2';
1180   InitLine(ALine1, w.init([4, 5]));
1181   InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
1182   ALine2.MoveLinesAtStartTo(ALine1, 3, 2);
1183   AssertLineForWraps('', ALine1, w.init([4, 5, 2, 1, 1,   1,1]));
1184 
1185   ATestName := 'Insert at end : empty lines in the middle -> dest 1';
1186   InitLine(ALine1, w.init([4]));
1187   InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
1188   ALine2.MoveLinesAtStartTo(ALine1, 3, 1);
1189   AssertLineForWraps('', ALine1, w.init([4, 2, 1, 1,   1,1]));
1190 
1191   ATestName := 'Insert at end : empty lines in the middle -> emyty dest';
1192   InitLine(ALine1, w.init([]));
1193   InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
1194   ALine2.MoveLinesAtStartTo(ALine1, 3, 0);
1195   AssertLineForWraps('', ALine1, w.init([2, 1, 1,   1,1]));
1196 
1197   ATestName := 'Insert at end : empty lines in the middle -> dest 2 - with dest offset';
1198   InitLine(ALine1, w.init([1, 1, 4, 5]));
1199   InitLine(ALine2, w.init([2, 1, 1, 1, 1, 3]));
1200   ALine2.MoveLinesAtStartTo(ALine1, 3, 4);
1201   AssertLineForWraps('', ALine1, w.init([1, 1, 4, 5, 2, 1, 1,   1,1]));
1202 
1203   ATestName := 'Insert at end : empty lines in the middle -> dest 2 - with source offset';
1204   InitLine(ALine1, w.init([4, 5]));
1205   InitLine(ALine2, w.init([1, 1, 2, 1, 1, 1, 1, 2]));
1206   ALine2.MoveLinesAtStartTo(ALine1, 5, 2);
1207   AssertLineForWraps('', ALine1, w.init([4, 5, 1, 1, 2, 1, 1,   1,1]));
1208 
1209 end;
1210 
1211 procedure TTestWordWrap.TestWordWrapLineMapMergeInvalidate;
1212 var
1213   ANode1, ANode2: TSynWordWrapIndexPage;
1214   ALine1, ALine2: TSynWordWrapLineMap;
1215   ATestName: String;
1216   w: TExpWraps;
1217 
1218   procedure DoMoveLinesAtEndTo(const AName: String;
1219     const AWrapValues1, AWrapValues2: array of integer; AInvalLine: Integer; const AInvalDest: Boolean;
1220     ASourceStartLine, ALineCount: Integer;
1221     Exp: array of integer;  ExpInval: Integer
1222   );
1223   begin
1224     InitLine(ALine1, w.init(AWrapValues1));
1225     InitLine(ALine2, w.init(AWrapValues2));
1226     if AInvalDest then
1227       ALine1.InvalidateLines(AInvalLine, AInvalLine)
1228     else
1229       ALine2.InvalidateLines(AInvalLine, AInvalLine);
1230     ALine2.MoveLinesAtEndTo(ALine1, ASourceStartLine, ALineCount);
1231     AssertLineForWraps(AName, ALine1, w.init(Exp));
1232     AssertEquals(AName+' invalid', ExpInval, ALine1.FirstInvalidLine);
1233     AssertEquals(AName+' invalid', ExpInval, ALine1.LastInvalidLine);
1234   end;
1235 
1236   procedure DoMoveLinesAtStartTo(const AName: String;
1237     const AWrapValues1, AWrapValues2: array of integer; AInvalLine: Integer; const AInvalDest: Boolean;
1238     ASourceEndLine, ATargetStartLine: Integer;
1239     Exp: array of integer;  ExpInval: Integer
1240   );
1241   begin
1242     InitLine(ALine1, w.init(AWrapValues1));
1243     InitLine(ALine2, w.init(AWrapValues2));
1244     if AInvalDest then
1245       ALine1.InvalidateLines(AInvalLine, AInvalLine)
1246     else
1247       ALine2.InvalidateLines(AInvalLine, AInvalLine);
1248     ALine2.MoveLinesAtStartTo(ALine1, ASourceEndLine, ATargetStartLine);
1249     AssertLineForWraps(AName, ALine1, w.init(Exp));
1250     AssertEquals(AName+' invalid', ExpInval, ALine1.FirstInvalidLine);
1251     AssertEquals(AName+' invalid', ExpInval, ALine1.LastInvalidLine);
1252   end;
1253 
1254 begin
1255   ANode1 := TSynWordWrapIndexPage(FTree.FindPageForLine(0, afmCreate).Page);
1256   ANode2 := TSynWordWrapIndexPage(FTree.FindPageForLine(100, afmCreate).Page);
1257   ALine1 := ANode1.SynWordWrapLineMapStore;
1258   ALine2 := ANode2.SynWordWrapLineMapStore;
1259 
1260   ATestName := 'Insert at start: target inval';
1261   InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
1262   InitLine(ALine2, w.init([4, 5, 6]));
1263   ALine1.InvalidateLines(1, 1);
1264   ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
1265   //ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
1266   AssertLineForWraps('', ALine1, w.init([4, 5, 6,   2, 1, 3, 3, 1,   1,1]));
1267   AssertEquals('invalid', 4, ALine1.FirstInvalidLine);
1268   AssertEquals('invalid', 4, ALine1.LastInvalidLine);
1269 
1270   ATestName := 'Insert at start: source inval';
1271   InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
1272   InitLine(ALine2, w.init([4, 5, 6]));
1273   ALine2.InvalidateLines(1, 1);
1274   ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
1275   //ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
1276   AssertLineForWraps('', ALine1, w.init([4, 5, 6,   2, 1, 3, 3, 1,   1,1]));
1277   AssertEquals('invalid', 1, ALine1.FirstInvalidLine);
1278   AssertEquals('invalid', 1, ALine1.LastInvalidLine);
1279 
1280   ATestName := 'Insert at start: source inval';
1281   InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
1282   InitLine(ALine2, w.init([4, 5, 6]));
1283   ALine2.InvalidateLines(0, 1);
1284   ALine2.MoveLinesAtEndTo(ALine1, 1, 2);
1285   //ALine1.InsertLinesFromPage(ALine2, 1, 0, 2);
1286   AssertLineForWraps('', ALine1, w.init([5, 6,   2, 1, 3, 3, 1,   1,1]));
1287   AssertEquals('invalid', 0, ALine1.FirstInvalidLine);
1288   AssertEquals('invalid', 0, ALine1.LastInvalidLine);
1289 
1290   ATestName := 'Insert at start: source inval';
1291   InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
1292   InitLine(ALine2, w.init([4, 5, 6]));
1293   ALine2.InvalidateLines(0, 1);
1294   ALine2.MoveLinesAtEndTo(ALine1, 2, 1);
1295   //ALine1.InsertLinesFromPage(ALine2, 2, 0, 1);
1296   AssertLineForWraps('', ALine1, w.init([6,   2, 1, 3, 3, 1,   1,1]));
1297   AssertEquals('invalid', -1, ALine1.FirstInvalidLine);
1298   AssertEquals('invalid', -1, ALine1.LastInvalidLine);
1299 
1300   ATestName := 'Insert at start: both inval';
1301   InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
1302   InitLine(ALine2, w.init([4, 5, 6]));
1303   ALine1.InvalidateLines(1, 1);
1304   ALine2.InvalidateLines(2, 2);
1305   ALine2.MoveLinesAtEndTo(ALine1, 0, 3);
1306   //ALine1.InsertLinesFromPage(ALine2, 0, 0, 3);
1307   AssertLineForWraps('', ALine1, w.init([4, 5, 6,   2, 1, 3, 3, 1,   1,1]));
1308   AssertEquals('invalid', 2, ALine1.FirstInvalidLine);
1309   AssertEquals('invalid', 4, ALine1.LastInvalidLine);
1310 
1311 
1312   ATestName := 'Insert at end: source inval';
1313   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1314   InitLine(ALine2, w.init([4, 5, 6]));
1315   ALine2.InvalidateLines(2, 2);
1316   ALine2.MoveLinesAtStartTo(ALine1, 2, 5);
1317   //ALine1.InsertLinesFromPage(ALine2, 0, 5, 3);
1318   AssertLineForWraps(ATestName, ALine1, w.init([1, 1, 3, 3, 1,   4, 5, 6,   1,1]));
1319   AssertEquals('invalid', 7, ALine1.FirstInvalidLine);
1320   AssertEquals('invalid', 7, ALine1.LastInvalidLine);
1321 
1322 
1323 
1324   ATestName := 'Insert from end to empty';
1325   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1326   InitLine(ALine2, w.init([]));
1327   ALine1.MoveLinesAtEndTo(ALine2, 3, 3);
1328   AssertLineForWraps(ATestName, ALine2, w.init([3, 1, 1,  1,1,1]));
1329 
1330   ATestName := 'Insert from end to empty';
1331   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1332   InitLine(ALine2, w.init([]));
1333   ALine1.MoveLinesAtEndTo(ALine2, 2, 3);
1334   AssertLineForWraps(ATestName, ALine2, w.init([3, 3, 1,  1,1,1]));
1335 
1336   ATestName := 'Insert from end to empty';
1337   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1338   InitLine(ALine2, w.init([]));
1339   ALine1.MoveLinesAtEndTo(ALine2, 1, 3);
1340   AssertLineForWraps(ATestName, ALine2, w.init([1, 3, 3,  1,1,1]));
1341 
1342   ATestName := 'Insert from end to empty';
1343   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1344   InitLine(ALine2, w.init([]));
1345   ALine1.MoveLinesAtEndTo(ALine2, 1, 4);
1346   AssertLineForWraps(ATestName, ALine2, w.init([1, 3, 3, 1,  1,1,1]));
1347 
1348   ATestName := 'Insert from after end to empty';
1349   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1350   InitLine(ALine2, w.init([]));
1351   ALine1.MoveLinesAtEndTo(ALine2, 6, 3);
1352   AssertLineForWraps(ATestName, ALine2, w.init([1, 1, 1,  1,1,1]));
1353 
1354 
1355   ATestName := 'Insert from start to empty';
1356   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1357   InitLine(ALine2, w.init([]));
1358   ALine1.MoveLinesAtStartTo(ALine2, 2, 0);
1359   AssertLineForWraps(ATestName, ALine2, w.init([1, 1, 3,    1,1,1]));
1360 
1361   ATestName := 'Insert from start to empty';
1362   InitLine(ALine1, w.init([2, 1, 3, 3, 1]));
1363   InitLine(ALine2, w.init([]));
1364   ALine1.MoveLinesAtStartTo(ALine2, 2, 0);
1365   AssertLineForWraps(ATestName, ALine2, w.init([2, 1, 3,    1,1,1,1]));
1366 
1367   ATestName := 'Insert from start to empty';
1368   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1369   InitLine(ALine2, w.init([]));
1370   ALine1.MoveLinesAtStartTo(ALine2, 1, 0);
1371   AssertLineForWraps(ATestName, ALine2, w.init([1, 1,     1,1,1,1]));
1372 
1373   ATestName := 'Insert from start to empty';
1374   InitLine(ALine1, w.init([1, 1, 3, 3, 1]));
1375   InitLine(ALine2, w.init([]));
1376   ALine1.MoveLinesAtStartTo(ALine2, 2, 3);
1377   AssertLineForWraps(ATestName, ALine2, w.init([1, 1, 1,  1, 1, 3,    1,1,1]));
1378 
1379 
1380 ///////////////////////////////////////
1381   (* split node at none wrapping lines - ensure the none-wrap "WrappedExtraSums" are stripped *)
1382   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2',
1383     [4, 5],  [2, 1, 1, 1, 1, 3],   1,  True,    3, 3,  {=>}  [1, 1, 3, 4, 5,   1,1],  4);
1384 
1385   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2',
1386     [4, 5],  [2, 1, 1, 1, 1, 3],   1,  False,    3, 3, {=>}   [1, 1, 3, 4, 5,   1,1],  -1);
1387   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2',
1388     [4, 5],  [2, 1, 1, 1, 1, 3],   2,  False,    3, 3, {=>}   [1, 1, 3, 4, 5,   1,1],  -1);
1389   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2',
1390     [4, 5],  [2, 1, 1, 1, 1, 3],   3,  False,    3, 3, {=>}   [1, 1, 3, 4, 5,   1,1],  0);
1391   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2',
1392     [4, 5],  [2, 1, 1, 1, 1, 3],   4,  False,    3, 3, {=>}   [1, 1, 3, 4, 5,   1,1],  1);
1393 
1394 
1395   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - gap at source end',
1396     [4, 5],  [2, 1, 1, 1, 1, 3],   1,  True,    3, 4,  {=>}  [1, 1, 3, 1, 4, 5,   1,1],  5);
1397 
1398   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - gap at source end',
1399     [4, 5],  [2, 1, 1, 1, 1, 3],   1,  False,    3, 4,  {=>}  [1, 1, 3, 1, 4, 5,   1,1],  -1);
1400   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - gap at source end',
1401     [4, 5],  [2, 1, 1, 1, 1, 3],   4,  False,    3, 4,  {=>}  [1, 1, 3, 1, 4, 5,   1,1],  1);
1402 
1403 
1404   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 1',
1405     [4],  [2, 1, 1, 1, 1, 3],      1,  True,    3, 3,  {=>}  [1, 1, 3, 4,   1,1],  4);
1406 
1407   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 1',
1408     [4],  [2, 1, 1, 1, 1, 3],      1,  False,    3, 3, {=>}   [1, 1, 3, 4,   1,1],  -1);
1409   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 1',
1410     [4],  [2, 1, 1, 1, 1, 3],      3,  False,    3, 3, {=>}   [1, 1, 3, 4,   1,1],  0);
1411 
1412 
1413   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> empty dest',
1414     [],  [2, 1, 1, 1, 1, 3],       1,  True,    3, 3,  {=>}  [1, 1, 3,   1,1],  4);
1415 
1416   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> empty dest',
1417     [],  [2, 1, 1, 1, 1, 3],       1,  False,    3, 3, {=>}   [1, 1, 3,   1,1],  -1);
1418   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> empty dest',
1419     [],  [2, 1, 1, 1, 1, 3],       4,  False,    3, 3, {=>}   [1, 1, 3,   1,1],  1);
1420 
1421 
1422   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset',
1423     [1,1,4, 5],  [2, 1, 1, 1, 1, 3],   1,  True,    3, 3,  {=>}  [1, 1, 3, 1,1,4, 5,   1,1],  4);
1424 
1425   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset',
1426     [1,1,4, 5],  [2, 1, 1, 1, 1, 3],   1,  False,   3, 3,  {=>}  [1, 1, 3, 1,1,4, 5,   1,1], -1);
1427   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset',
1428     [1,1,4, 5],  [2, 1, 1, 1, 1, 3],   4,  False,   3, 3,  {=>}  [1, 1, 3, 1,1,4, 5,   1,1],  1);
1429 
1430 
1431   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
1432     [4, 5],  [1,1,2, 1, 1, 1, 1, 3],   1,  True,    5, 3,  {=>}  [1, 1, 3, 4, 5,   1,1],  4);
1433 
1434   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
1435     [4, 5],  [1,1,2, 1, 1, 1, 1, 3],   1,  False,    5, 3,  {=>}  [1, 1, 3, 4, 5,   1,1],  -1);
1436   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
1437     [4, 5],  [1,1,2, 1, 1, 1, 1, 3],   3,  False,    5, 3,  {=>}  [1, 1, 3, 4, 5,   1,1],  -1);
1438   DoMoveLinesAtEndTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
1439     [4, 5],  [1,1,2, 1, 1, 1, 1, 3],   6,  False,    5, 3,  {=>}  [1, 1, 3, 4, 5,   1,1],  1);
1440 
1441 
1442 
1443 
1444 
1445   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2',
1446     [4, 5],  [2, 1, 1, 1, 1, 3],   1,  True,    3, 2,  {=>}  [4, 5, 2, 1, 1,   1,1],  1);
1447 
1448   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2',
1449     [4, 5],  [2, 1, 1, 1, 1, 3],   1,  False,    3, 2,  {=>}  [4, 5, 2, 1, 1,   1,1],  3);
1450   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2',
1451     [4, 5],  [2, 1, 1, 1, 1, 3],   4,  False,    3, 2,  {=>}  [4, 5, 2, 1, 1,   1,1],  -1);
1452 
1453 
1454   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - gap at target end',
1455     [4, 5],  [2, 1, 1, 1, 1, 3],   1,  True,    3, 3,  {=>}  [4, 5, 1,  2, 1, 1,   1,1],  1);
1456 
1457   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - gap at target end',
1458     [4, 5],  [2, 1, 1, 1, 1, 3],   1,  False,    3, 3,  {=>}  [4, 5, 1,  2, 1, 1,   1,1],  4);
1459   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - gap at target end',
1460     [4, 5],  [2, 1, 1, 1, 1, 3],   4,  False,    3, 3,  {=>}  [4, 5, 1,  2, 1, 1,   1,1],  -1);
1461 
1462 
1463   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 1',
1464     [4],  [2, 1, 1, 1, 1, 3],   1,  True,    3, 1,  {=>}  [4, 2, 1, 1,   1,1],  1);
1465 
1466   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 1',
1467     [4],  [2, 1, 1, 1, 1, 3],   1,  False,    3, 1,  {=>}  [4, 2, 1, 1,   1,1],  2);
1468   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 1',
1469     [4],  [2, 1, 1, 1, 1, 3],   4,  False,    3, 1,  {=>}  [4, 2, 1, 1,   1,1],  -1);
1470 
1471 
1472 // empty dest can not have invalid...
1473   //DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> empty dest',
1474   //  [],  [2, 1, 1, 1, 1, 3],   1,  True,    3, 0,  {=>}  [2, 1, 1,   1,1],  1);
1475 
1476   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> empty dest',
1477     [],  [2, 1, 1, 1, 1, 3],   1,  False,    3, 0,  {=>}  [2, 1, 1,   1,1],  1);
1478   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> empty dest',
1479     [],  [2, 1, 1, 1, 1, 3],   4,  False,    3, 0,  {=>}  [2, 1, 1,   1,1],  -1);
1480 
1481 
1482   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> empty dest - gap',
1483     [],  [2, 1, 1, 1, 1, 3],   1,  True,    3, 2,  {=>}  [1,1, 2, 1, 1,   1,1],  1);
1484 
1485   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> empty dest - gap',
1486     [],  [2, 1, 1, 1, 1, 3],   1,  False,    3, 2,  {=>}  [1,1, 2, 1, 1,   1,1],  3);
1487   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> empty dest- gap',
1488     [],  [2, 1, 1, 1, 1, 3],   4,  False,    3, 2,  {=>}  [1,1, 2, 1, 1,   1,1],  -1);
1489 
1490 
1491   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset',
1492     [1,1, 4, 5],  [2, 1, 1, 1, 1, 3],   1,  True,    3, 4,  {=>}  [1,1, 4, 5, 2, 1, 1,   1,1],  1);
1493 
1494   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset',
1495     [1,1, 4, 5],  [2, 1, 1, 1, 1, 3],   1,  False,    3, 4,  {=>}  [1,1, 4, 5, 2, 1, 1,   1,1],  5);
1496   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset',
1497     [1,1, 4, 5],  [2, 1, 1, 1, 1, 3],   4,  False,    3, 4,  {=>}  [1,1, 4, 5, 2, 1, 1,   1,1],  -1);
1498 
1499 
1500   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset - gap',
1501     [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);
1502 
1503   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset - gap',
1504     [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);
1505   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with dest offset - gap',
1506     [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);
1507 
1508 
1509   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
1510     [4, 5],  [1,1, 2, 1, 1, 1, 1, 3],   1,  True,    5, 2,  {=>}  [4, 5, 1,1, 2, 1, 1,   1,1],  1);
1511 
1512   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
1513     [4, 5],  [1,1, 2, 1, 1, 1, 1, 3],   1,  False,    5, 2,  {=>}  [4, 5, 1,1, 2, 1, 1,   1,1],  3);
1514   DoMoveLinesAtStartTo('Insert at start: empty lines in the middle -> dest 2 - with source offset',
1515     [4, 5],  [1,1, 2, 1, 1, 1, 1, 3],   6,  False,    5, 2,  {=>}  [4, 5, 1,1, 2, 1, 1,   1,1],  -1);
1516 
1517 
1518 end;
1519 
1520 procedure TTestWordWrap.TestWordWrapJoinWithSibling;
1521 var
1522   CurWraps: TExpWraps;
1523 
1524   procedure DoTestAssert(AName: String; ExpNodeCnt: Integer);
1525   begin
1526     //debugln(AName); FTree.DebugDump ;
1527     AssertTreeForWraps(AName + ' Wrap', CurWraps);
1528     AssertEquals(AName + ' Cnt ', ExpNodeCnt, TreeNodeCount);
1529   end;
1530 
1531   procedure DoTestInit(AName: String; AFromVal, ALineCount, AnIncrease, ExpNodeCnt: Integer);
1532   begin
1533     FTree.Clear;
1534     CurWraps.InitFill(AFromVal, AFromVal+ALineCount-1, AnIncrease); // 4 pages
1535     FTree.AdjustForLinesInserted(0, ALineCount, 0);
1536     ValidateTreeWraps(CurWraps);
1537 
1538     DoTestAssert(Format('%s (Init %d, %d) Wrap', [AName, AFromVal, ALineCount]), ExpNodeCnt);
1539   end;
1540 
1541   procedure DoTestChgWrp(AName: String; AStartLine, ALineCount, AFromVal, AnIncrease, ExpNodeCnt: Integer);
1542   begin
1543     CurWraps.FillRange(AStartLine, ALineCount, AFromVal, AnIncrease);
1544     FTree.InvalidateLines(AStartLine, AStartLine + ALineCount - 1);
1545     ValidateTreeWraps(CurWraps);
1546 
1547     DoTestAssert(Format('%s (Changed %d, %d) Wrap', [AName, AStartLine, ALineCount]), ExpNodeCnt);
1548   end;
1549 
1550   procedure DoTestDelete(AName: String; AStartLine, ALineCount, ExpNodeCnt: Integer);
1551   begin
1552     FTree.AdjustForLinesDeleted(AStartLine, ALineCount,0);
1553     CurWraps.SpliceArray(AStartLine, ALineCount);
1554 
1555     DoTestAssert(Format('%s (Deleted %d, %d) Wrap', [AName, AStartLine, ALineCount]), ExpNodeCnt);
1556   end;
1557 
1558 var
1559   OffsetAtStart, OffsetAtEnd, Node1Del, Node2Del, FinalNodeCount: Integer;
1560   N: String;
1561 begin
1562   (* After "DoTestInit" each node should be filled to the max.
1563      So the test knows where each node begins
1564   *)
1565   FTree := CreateTree(10, 30, 4); // APageJoinSize, APageSplitSize, APageJoinDistance
1566 
1567   For OffsetAtStart := 0 to 3 do
1568   For OffsetAtEnd := 0 to 3 do  // RealEnd = NextNode.Startline-1 - x
1569   For Node1Del := 18 to 25 do
1570   For Node2Del := 18 to 25 do
1571   begin
1572     N := Format('Start: %d  End: %d  Del1: %d Del2: %d', [OffsetAtStart, OffsetAtEnd, Node1Del, Node2Del]);
1573 
1574     FinalNodeCount := 3;
1575     if (OffsetAtStart + OffsetAtEnd > 4) or   //  APageJoinDistance not met
1576        (Node1Del + OffsetAtEnd < 20) or (Node2Del + OffsetAtStart < 20)
1577     then
1578       FinalNodeCount := 4;
1579 
1580     DoTestInit  (N, 10, 4*30, 1,    4);  // Create 4 full nodes
1581 
1582     if OffsetAtStart > 0 then
1583       DoTestChgWrp(N, 90,              OffsetAtStart, 1, 0,   4);  // At Start of Last node
1584     if OffsetAtEnd > 0 then
1585       DoTestChgWrp(N, 90-OffsetAtEnd,  OffsetAtEnd,   1, 0,   4);  // At End of 2nd-Last node
1586 
1587     DoTestDelete(N, 60,          Node1Del,         4);
1588     DoTestDelete(N, 95-Node1Del, Node2Del,         FinalNodeCount);
1589 
1590     // Delete from 2nd node before 1st node
1591     DoTestInit  (N, 10, 4*30, 1,    4);  // Create 4 full nodes
1592 
1593     if OffsetAtStart > 0 then
1594       DoTestChgWrp(N, 90,              OffsetAtStart, 1, 0,   4);  // At Start of Last node
1595     if OffsetAtEnd > 0 then
1596       DoTestChgWrp(N, 90-OffsetAtEnd,  OffsetAtEnd,   1, 0,   4);  // At End of 2nd-Last node
1597 
1598     DoTestDelete(N, 95, Node2Del,         4);
1599     DoTestDelete(N, 60, Node1Del,         FinalNodeCount);
1600 
1601   end;
1602 end;
1603 
1604 
1605 procedure TTestWordWrap.TestWordWrapTreeInsertThenDelete;
1606 var
1607   CurWraps: TExpWraps;
1608   InsPos, InsLen, DelPos, DelCount: Integer;
1609 begin
1610   FTree.Free;
1611   FTree := CreateTree(2, 9, 4);
1612 //  FTree := TSynLineMapAVLTree.Create(TSynWordWrapIndexPage, 2, 11, 4);
1613   for DelPos := 0 to 26 do
1614   for DelCount := 1 to Min(5, 27-DelPos) do
1615   for InsPos := 0 to 29 do
1616   for InsLen := 1 to 4 do begin
1617     FTree.Clear;
1618 
1619     // init
1620     CurWraps.InitFill(10, 10+26);
1621     FTree.AdjustForLinesInserted(0, 27, 0);
1622     ValidateTreeWraps(CurWraps);
1623     //FTree.DebugDump;
1624     AssertTreeForWraps(Format('Before ins at pos %d Len %d', [InsPos, InsLen]), CurWraps);
1625 
1626     // ins
1627     CurWraps.Join(FillArray(500, 499+InsLen), InsPos);
1628     FTree.AdjustForLinesInserted(InsPos, InsLen, 0);
1629     //FTree.DebugDump;
1630     ValidateTreeWraps(CurWraps);
1631     //FTree.DebugDump;
1632     AssertTreeForWraps(Format('After ins at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]), CurWraps);
1633 
1634     // del
1635     FTree.AdjustForLinesDeleted(DelPos, DelCount, 0);
1636     CurWraps.SpliceArray(DelPos, DelCount);
1637     AssertTrue(Format('valid After del at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]),
1638       not FTree.NeedsValidation);
1639     AssertTreeForWraps(Format('After del at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]), CurWraps);
1640   end;
1641 end;
1642 
1643 procedure TTestWordWrap.TestWordWrapTreeDeleteThenInsert;
1644 var
1645   CurWraps: TExpWraps;
1646   InsPos, InsLen, DelPos, DelCount: Integer;
1647 begin
1648   FTree.Free;
1649   FTree := CreateTree(2, 9, 4);
1650 //  FTree := TSynLineMapAVLTree.Create(TSynWordWrapIndexPage, 2, 11, 4);
1651   for DelPos := 0 to 26 do
1652   for DelCount := 1 to Min(5, 27-DelPos) do
1653   for InsPos := 0 to 29 do
1654   for InsLen := 1 to 4 do begin
1655 //if  (InsPos<>15) or (InsLen<>1) or (DelPos<>0) or (DelCount<>1) then continue;
1656     FTree.Clear;
1657 
1658     // init
1659     CurWraps.InitFill(10, 10+26);
1660     FTree.AdjustForLinesInserted(0, 27, 0);
1661     ValidateTreeWraps(CurWraps);
1662     //FTree.DebugDump;
1663     AssertTreeForWraps(Format('Before del at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]), CurWraps);
1664 
1665     // del
1666     FTree.AdjustForLinesDeleted(DelPos, DelCount, 0);
1667     CurWraps.SpliceArray(DelPos, DelCount);
1668     AssertTrue(Format('valid After del at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]),
1669       not FTree.NeedsValidation);
1670     AssertTreeForWraps(Format('After del at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]), CurWraps);
1671 
1672     // ins
1673     FTree.AdjustForLinesInserted(InsPos, InsLen, 0);
1674     CurWraps.Join(FillArray(500, 499+InsLen), InsPos);
1675     //FTree.DebugDump;
1676     ValidateTreeWraps(CurWraps);
1677     //FTree.DebugDump;
1678     AssertTreeForWraps(Format('After del/ins : del at pos %d Len %d ins at pos %d Len %d', [DelPos, DelCount, InsPos, InsLen]), CurWraps);
1679   end;
1680 end;
1681 
1682 { TTestWordWrapPluginBase }
1683 
1684 procedure TTestWordWrapPluginBase.ClearCaret;
1685 begin
1686   SynEdit.CaretXY := Point(2,2);
1687   SynEdit.CaretXY := Point(1,1);
1688 end;
1689 
TTestWordWrapPluginBase.GetTreeNodeHoldernull1690 function TTestWordWrapPluginBase.GetTreeNodeHolder(AIndex: Integer
1691   ): TSynEditLineMapPageHolder;
1692 begin
1693   Result := FWordWrap.FLineMapView.Tree.FirstPage;
1694   while (AIndex > 0) and Result.HasPage do begin
1695     dec(AIndex);
1696     Result := Result.Next;
1697   end;
1698 
1699 end;
1700 
1701 procedure TTestWordWrapPluginBase.SetCaret(SourcePt: TPointType; APos: TPoint);
1702 begin
1703   case SourcePt of
1704     ptViewed:          SynEdit.CaretObj.ViewedLineCharPos := Apos;
1705     ptAlternateViewed: SynEdit.CaretObj.ViewedLineCharPos := Apos;
1706     ptPhys:            SynEdit.CaretObj.LineCharPos       := Apos;
1707     ptLog:             SynEdit.CaretObj.LineBytePos       := APos;
1708   end;
1709 end;
1710 
1711 procedure TTestWordWrapPluginBase.TestCaret(AName: String;SourcePt, ExpPt: TPointType;
1712   AnExp: TPoint; AnExpOffs: Integer);
1713 var
1714   got: TPoint;
1715   src, dest: String;
1716 begin
1717   case ExpPt of
1718     ptViewed:          got := SynEdit.CaretObj.ViewedLineCharPos;
1719     ptAlternateViewed: got := SynEdit.CaretObj.ViewedLineCharPos;
1720     ptPhys:            got := SynEdit.CaretObj.LineCharPos;
1721     ptLog:             got := SynEdit.CaretObj.LineBytePos;
1722   end;
1723   writestr(src, SourcePt);
1724   writestr(dest, ExpPt);
1725   AssertEquals(Format('%s (%s -> %s)', [AName, src, dest]), AnExp, got);
1726   if (ExpPt = ptLog) and (AnExpOffs >= 0) then
1727     AssertEquals(Format('%s (%s -> %s) Offs: ', [AName, src, dest]), AnExpOffs, SynEdit.CaretObj.BytePosOffset);
1728 end;
1729 
1730 class procedure TTestWordWrapPluginBase.AssertEquals(const AMessage: string;
1731   Expected, Actual: TPoint);
1732 begin
1733   AssertEquals(AMessage, dbgs(Expected), dbgs(Actual));
1734 end;
1735 
1736 procedure TTestWordWrapPluginBase.AddLines(AFirstLineIdx, ACount,
1737   ALen: Integer; AnID: String; SkipBeginUpdate: Boolean;
1738   AReplaceExisting: Boolean);
1739 var
1740   i, j: Integer;
1741   l: String;
1742 begin
1743   if not SkipBeginUpdate then SynEdit.BeginUpdate;
1744   for i := 0 to ACount - 1 do begin
1745     l := '';
1746     j := 0;
1747     while Length(l) < ALen do begin
1748       l := l + copy(AnID+'_'+IntToStr(i)+'_'+IntToStr(j) + '            ',1,12);
1749       inc(j);
1750     end;
1751     l := copy(l, 1, ALen);
1752     if AReplaceExisting then
1753       SynEdit.Lines[AFirstLineIdx + i] := l
1754     else
1755       SynEdit.Lines.Insert(AFirstLineIdx + i, l);
1756   end;
1757   if not SkipBeginUpdate then SynEdit.EndUpdate;
1758 end;
1759 
1760 procedure TTestWordWrapPluginBase.InternalCheckLine(AName: String;
1761   dsp: TLazSynDisplayView; ALine: TLineIdx; AExpTextStart: String;
1762   NoTrim: Boolean);
1763 var
1764   gotRealLine: TLineIdx;
1765   gotStartPos, GotLineLen: Integer;
1766   gotTokenOk: Boolean;
1767   gotToken: TLazSynDisplayTokenInfo;
1768   gotText: PChar;
1769   s: String;
1770 begin
1771   dsp.SetHighlighterTokensLine(ALine, gotRealLine, gotStartPos, GotLineLen);
1772   gotTokenOk := dsp.GetNextHighlighterToken(gotToken);
1773   if gotTokenOk then
1774     gotText := gotToken.TokenStart
1775   else
1776     gotText := '';
1777 
1778   if AExpTextStart = '' then begin
1779     AssertEquals(AName, '', gotText);
1780     AssertEquals(AName, 0, GotLineLen);
1781     exit;
1782   end
1783   else
1784   if gotText = '' then begin
1785     AssertTrue(AName, False);
1786     exit;
1787   end;
1788 
1789   if NoTrim then
1790     s := copy(gotText, 1, Length(AExpTextStart))
1791   else
1792     s := copy(Trim(gotText), 1, Length(AExpTextStart));
1793   if not(AExpTextStart = s) then begin
1794     debugln(['Failed ', AName, ' ', ALine, ':']);
1795     DebugLn(['GOT: "', StringReplace(gotText, #9, '#9', [rfReplaceAll]), '"']);
1796     DebugLn(['EXP: "', StringReplace(AExpTextStart, #9, '#9', [rfReplaceAll]), '"']);
1797   end;
1798   AssertEquals(AName, AExpTextStart, s);
1799 end;
1800 
1801 procedure TTestWordWrapPluginBase.CheckLine(AName: String; ALine: TLineIdx;
1802   AExpTextStart: String; NoTrim: Boolean);
1803 var
1804   v: TSynEditStringsLinked;
1805   dsp: TLazSynDisplayView;
1806 begin
1807   v := SynEdit.TextViewsManager.SynTextView[SynEdit.TextViewsManager.Count - 1];
1808   dsp := v.DisplayView;
1809   dsp.InitHighlighterTokens(nil);
1810   try
1811     InternalCheckLine(AName, dsp, ALine, AExpTextStart, NoTrim);
1812   finally
1813     dsp.FinishHighlighterTokens;
1814   end;
1815 end;
1816 
1817 procedure TTestWordWrapPluginBase.CheckLines(AName: String;
1818   AStartLine: TLineIdx; AExpTextStart: array of String; NoTrim: Boolean);
1819 var
1820   v: TSynEditStringsLinked;
1821   dsp: TLazSynDisplayView;
1822   i, gotStartPos, GotLineLen: Integer;
1823   gotTokenOk: Boolean;
1824   gotToken: TLazSynDisplayTokenInfo;
1825   s: String;
1826   gotRealLine: TLineIdx;
1827 begin
1828   v := SynEdit.TextViewsManager.SynTextView[SynEdit.TextViewsManager.Count - 1];
1829   dsp := v.DisplayView;
1830   dsp.InitHighlighterTokens(nil);
1831   try
1832     try
1833       for i := 0 to Length(AExpTextStart)-1 do
1834         InternalCheckLine(AName, dsp, AStartLine+i, AExpTextStart[i], NoTrim);
1835     except
1836       dsp.FinishHighlighterTokens;
1837       dsp.InitHighlighterTokens(nil);
1838       for i := 0 to Length(AExpTextStart)-1 do begin
1839         dsp.SetHighlighterTokensLine(AStartLine+i, gotRealLine, gotStartPos, GotLineLen);
1840         s := '';
1841         while dsp.GetNextHighlighterToken(gotToken) and (gotToken.TokenLength > 0) do
1842           s := s + copy(gotToken.TokenStart, 1, gotToken.TokenLength);
1843         debugln('Line %d (real %d): "%s" %d/%d start %d',
1844           [AStartLine+i, gotRealLine, StringReplace(s, #9, '#9', [rfReplaceAll]), length(s), GotLineLen, gotStartPos]);
1845       end;
1846       raise;
1847     end;
1848   finally
1849     dsp.FinishHighlighterTokens;
1850   end;
1851 end;
1852 
1853 procedure TTestWordWrapPluginBase.CheckLine(AName: String;
1854   AExpLine: TTestWrapLineInfo);
1855 begin
1856   CheckLine(AName, AExpLine.ViewedIdx, AExpLine.TextStartMatch, AExpLine.NoTrim);
1857 end;
1858 
1859 procedure TTestWordWrapPluginBase.CheckLines(AName: String;
1860   AExpLines: TTestViewedLineRangeInfo);
1861 var
1862   i: Integer;
1863   n: TSynEditLineMapPageHolder;
1864 begin
1865   for i := 0 to length(AExpLines) - 1 do begin
1866     CheckLine(Format('%s (%d)', [AName, i]), AExpLines[i]);
1867     CheckLineIndexMapping(Format('%s (%d)', [AName, i]), AExpLines[i].TextIdx, AExpLines[i].ViewedTopIdx, AExpLines[i].ViewedBottomIdx);
1868   end;
1869   if TheTree <> nil then begin
1870     n := TheTree.FirstPage;
1871     if n.HasPage then
1872       CheckTree(AName+' (TreeCheck)', n.Page, n.StartLine, 0, SynEdit.Lines.Count-1);
1873   end;
1874 end;
1875 
1876 procedure TTestWordWrapPluginBase.CheckXyMap(AName: String; APhysTExtXY,
1877   AViewedXY: TPoint; OnlyViewToText: Boolean);
1878 var
1879   v: TSynEditStringsLinked;
1880   GotTextXY, GotViewXY: TPoint;
1881 begin
1882   v := SynEdit.TextViewsManager.SynTextView[SynEdit.TextViewsManager.Count - 1];
1883   GotTextXY := v.ViewXYToTextXY(AViewedXY);
1884   GotViewXY := v.TextXYToViewXY(APhysTExtXY);
1885 
1886   AssertTrue(Format('%s: Viewed %s to Text %s (exp) => got %s', [AName, dbgs(AViewedXY), dbgs(APhysTExtXY), dbgs(GotTextXY)]),
1887              (GotTextXY.x = APhysTExtXY.x) and (GotTextXY.y = APhysTExtXY.y) );
1888   if not OnlyViewToText then
1889   AssertTrue(Format('%s: Text %s to viewed %s (exp) => got %s', [AName, dbgs(APhysTExtXY), dbgs(AViewedXY), dbgs(GotViewXY)]),
1890              (GotViewXY.x = AViewedXY.x) and (GotViewXY.y = AViewedXY.y) );
1891 end;
1892 
1893 procedure TTestWordWrapPluginBase.CheckXyMap(AName: String; APhysTExtX,
1894   APhysTExtY, AViewedX, AViewedY: integer; OnlyViewToText: Boolean);
1895 begin
1896   CheckXyMap(AName, Point(APhysTExtX, APhysTExtY), Point(AViewedX, AViewedY), OnlyViewToText);
1897 end;
1898 
1899 procedure TTestWordWrapPluginBase.CheckXyMap(AName: String;
1900   APoints: TPointSpecs);
1901 var
1902   StartP, TestP: TPointType;
1903 begin
1904   CheckXyMap(AName + 'XyMap', APoints.xy[ptPhys], APoints.xy[ptViewed]);
1905   if APoints.xy[ptAlternateViewed].x > 0 then
1906     CheckXyMap(AName+ 'XyMap(a)', APoints.xy[ptPhys], APoints.xy[ptAlternateViewed], True);
1907 
1908 
1909   for TestP in TPointType do begin
1910     if TestP = ptAlternateViewed then
1911       continue;
1912 
1913     ClearCaret;
1914     SynEdit.CaretXY := APoints.XY[ptPhys];
1915     TestCaret(AName, ptPhys, TestP, APoints.XY[TestP], APoints.LogOffs);
1916 
1917     if APoints.LogOffs <= 0 then begin
1918       ClearCaret;
1919       SynEdit.LogicalCaretXY := APoints.XY[ptLog];
1920       TestCaret(AName, ptLog, TestP, APoints.XY[TestP], APoints.LogOffs);
1921     end;
1922 
1923     AName := AName + ' [CaretObj] ';
1924     for StartP in TPointType do begin
1925       if APoints.xy[StartP].x <= 0 then
1926         Continue;
1927     if (StartP = ptAlternateViewed) then // and (TestP = ptViewed) then
1928       continue;
1929     if (StartP = ptLog) and (APoints.LogOffs > 0) then
1930       continue;
1931 
1932       ClearCaret;
1933       SetCaret(StartP, APoints.XY[StartP]);
1934       TestCaret(AName, StartP, TestP, APoints.XY[TestP], APoints.LogOffs);
1935     end;
1936   end;
1937 
1938 end;
1939 
1940 procedure TTestWordWrapPluginBase.CheckXyMap(AName: String;
1941   APoints: TPointSpecs; ATestCommands: array of TCommandAndPointSpecs);
1942 var
1943   i: Integer;
1944   StartP, TestP: TPointType;
1945   n: string;
1946   c: TSynEditorCommand;
1947 begin
1948   CheckXyMap(AName+'(p)', APoints);
1949 
1950   for i := 0 to Length(ATestCommands) - 1 do begin
1951     if not ATestCommands[i].RunOnlyIf then
1952       Continue;
1953     n := AName+'(p'+IntToStr(i)+')';
1954     CheckXyMap(n, ATestCommands[i].Exp);
1955 
1956     for StartP in TPointType do begin
1957       if APoints.xy[StartP].x <= 0 then
1958         Continue;
1959       if (StartP = ptAlternateViewed) then // and (TestP = ptViewed) then
1960         continue;
1961       if (StartP = ptLog) and (APoints.LogOffs > 0) then
1962         continue;
1963 
1964       for TestP in TPointType do begin
1965         if TestP = ptAlternateViewed then
1966           continue;
1967         ClearCaret;
1968         SetCaret(StartP, APoints.XY[StartP]);
1969         for c in ATestCommands[i].Cmd do
1970           SynEdit.ExecuteCommand(c, '', nil);
1971 
1972         TestCaret(n, StartP, TestP, ATestCommands[i].Exp.XY[TestP], ATestCommands[i].Exp.LogOffs);
1973       end;
1974     end;
1975   end;
1976 end;
1977 
1978 procedure TTestWordWrapPluginBase.CheckLineIndexMapping(AName: String;
1979   ATextIdx, AViewTopIdx, AViewBottomIdx: TLineIdx);
1980 var
1981   v: TSynEditStringsLinked;
1982   i: TLineIdx;
1983   dv: TLazSynDisplayView;
1984   r: TLineRange;
1985 begin
1986   v := SynEdit.TextViewsManager.SynTextView[SynEdit.TextViewsManager.Count - 1];
1987   dv := v.DisplayView;
1988 
1989   AssertEquals(AName + ' TextToViewIndex', AViewTopIdx, v.TextToViewIndex(ATextIdx));
1990   for i := AViewTopIdx to AViewBottomIdx do
1991     AssertEquals(AName + ' ViewToTextIndex', ATextIdx, v.ViewToTextIndex(i));
1992 
1993   r := dv.TextToViewIndex(ATextIdx);
1994   AssertEquals(AName + 'DispView.TextToViewIndex Top', AViewTopIdx, r.Top);
1995   AssertEquals(AName + 'DispView.TextToViewIndex Bottom', AViewBottomIdx, r.Bottom);
1996   for i := AViewTopIdx to AViewBottomIdx do begin
1997     AssertEquals(AName + ' ViewToTextIndex', ATextIdx, dv.ViewToTextIndex(i));
1998 
1999     AssertEquals(AName + ' ViewToTextIndexEx', ATextIdx, dv.ViewToTextIndexEx(i, r));
2000     AssertEquals(AName + ' ViewToTextIndexEx Top', AViewTopIdx, r.Top);
2001     AssertEquals(AName + ' ViewToTextIndexEx Bottom', AViewBottomIdx, r.Bottom);
2002   end;
2003 end;
2004 
TheTreenull2005 function TTestWordWrapPluginBase.TheTree: TSynLineMapAVLTree;
2006 begin
2007   Result := nil;
2008   if FWordWrap = nil then
2009     exit;
2010   Result := FWordWrap.FLineMapView.Tree;
2011 end;
2012 
2013 procedure TTestWordWrapPluginBase.ReCreateEdit(ADispWidth: Integer);
2014 begin
2015   TearDown;
2016   SetUp;
2017   SetSynEditWidth(ADispWidth);
2018 end;
2019 
2020 procedure TTestWordWrapPluginBase.SetUp;
2021 begin
2022   inherited SetUp;
2023   FWordWrap := TLazSynEditLineWrapPlugin.Create(SynEdit);
2024 end;
2025 
2026 procedure TTestWordWrapPluginBase.TearDown;
2027 begin
2028   inherited TearDown;
2029 end;
2030 
2031 { TTestWordWrapPlugin }
2032 
2033 procedure TTestWordWrapPlugin.TestEditorWrap;
2034 var
2035   SkipTab, AllowPastEOL, KeepX: Boolean;
2036 begin
2037   SynEdit.Options := [];
2038   SynEdit.TabWidth := 4;
2039 
2040   SetLines([
2041     'abc def ' + 'ABC DEFG ' + 'XYZ',
2042     //'A'  #9'B'  #9'C ' + 'DEF G'#9'H'  #9 + ''   #9   #9'xy',
2043     'A'#9'B'#9'C ' + 'DEF G'#9'H'#9 + #9#9'xy',
2044     'äää ööö ' + 'ÄÄÄ ÖÖÖ ' + 'ÜÜÜ',
2045     '999'
2046   ]);
2047 
2048   SetSynEditWidth(10);
2049   CheckLines('', 0, [
2050     'abc def ',
2051     'ABC DEFG ',
2052     'XYZ',
2053     'A'#9'B'#9'C ',
2054     'DEF G'#9'H'#9,
2055     #9#9'xy',
2056     'äää ööö ',
2057     'ÄÄÄ ÖÖÖ ',
2058     'ÜÜÜ',
2059     '999'
2060   ], True);
2061 
2062   CheckLines('', ViewedExp(0, [
2063     l(0, 0, 'abc def '),
2064     l(0, 1, 'ABC DEFG '),
2065     l(0, 2, 'XYZ'),
2066     l(1, 0, 'A'#9'B'#9'C '),
2067     l(1, 1, 'DEF G'#9'H'#9),
2068     l(1, 2, #9#9'xy'),
2069     l(2, 0, 'äää ööö '),
2070     l(2, 1, 'ÄÄÄ ÖÖÖ '),
2071     l(2, 2, 'ÜÜÜ'),
2072     l(3, 0, '999')
2073   ], tTrue));
2074 
2075 
2076   //CheckXyMap('', pt(4,3,   21,1,   21,1); // after "Z" EOL
2077   for AllowPastEOL in boolean do
2078   for KeepX in boolean do
2079   for SkipTab in boolean do begin
2080     if AllowPastEOL
2081     then SynEdit.Options := SynEdit.Options + [eoScrollPastEol]
2082     else SynEdit.Options := SynEdit.Options - [eoScrollPastEol];
2083     if SkipTab
2084     then SynEdit.Options2 := SynEdit.Options2 + [eoCaretSkipTab]
2085     else SynEdit.Options2 := SynEdit.Options2 - [eoCaretSkipTab];
2086     if KeepX
2087     then SynEdit.Options := SynEdit.Options + [eoKeepCaretX]
2088     else SynEdit.Options := SynEdit.Options - [eoKeepCaretX];
2089 
2090     FWordWrap.CaretWrapPos := wcpEOL;
2091     CheckXyMap('wcpEOL', p( 1,1,           1,1,    1,1),
2092                         [c(ecRight,                         2,1,   2,1,    2,1,0),
2093                          c(ecDown,                          2,2,  10,1,   10,1,0),
2094                          c([ecDown,ecDown],                 2,3,  19,1,   19,1,0),
2095                          c([ecDown,ecDown, ecRight],        3,3,  20,1,   20,1,0),
2096                          c([ecDown,ecDown, ecRight,ecDown], 2,4,   2,2,    2,2,0, SkipTab),
2097                          c([ecDown,ecDown, ecRight,ecDown], 3,4,   3,2,    2,2,1, not SkipTab),
2098                          c([ecDown,ecDown,ecDown],          1,4,   1,2,    1,2,0, KeepX),
2099                          c([ecDown,ecDown,ecDown],          2,4,   2,2,    2,2,0, not KeepX),
2100                          c([ecDown,ecDown,ecDown,ecDown],   2,5,  12,2,    8,2,0)
2101                         ]);
2102     CheckXyMap('wcpEOL', p( 2,1,           2,1,    2,1),
2103                         [c(ecDown,                          2,2,  10,1,   10,1,0)
2104                         ]);
2105     CheckXyMap('wcpEOL', p( 7,1,           7,1,    7,1), // after "e"
2106                         [c([ecColSelDown],              7,4,   7,2,   4,2,1,  not SkipTab ),    // A#9B#|9  // 1 in tab
2107                          c([ecColSelDown],              6,4,   6,2,   4,2,0,  SkipTab ),    // A#9B|#9  // 1 in tab
2108                          c([ecColSelDown,ecColSelDown], 7,7,   7,3,  12,3,0,  (not SkipTab) or KeepX ),
2109                          c([ecColSelDown,ecColSelDown], 6,7,   6,3,  10,3,0,  SkipTab and not KeepX ),
2110                          c([ecColSelDown,ecColSelDown,ecColSelDown], 4,10,   4,4,  4,4,0, not AllowPastEOL),
2111                          c([ecColSelDown,ecColSelDown,ecColSelDown], 7,10,   7,4,  7,4,0, AllowPastEOL and ((not SkipTab) or KeepX) )
2112                         ]);
2113     CheckXyMap('wcpEOL', p( 8,1,           8,1,    8,1));
2114     if AllowPastEOL then
2115     CheckXyMap('wcpEOL', p( 9,1,   1,2,    9,1,    9,1), // def |
2116                         [c([ecDown],                        9,2,  17,1,   17,1,0), // DEFG|
2117                          c([ecDown,ecDown],                 9,3,  26,1,   26,1,0), // XYZ     |
2118                          c([ecDown,ecDown,ecDown],          9,4,   9,2,    5,2,0), // A#9B#9|C
2119                          c([ecDown,ecDown,ecDown,ecDown],   8,5,  18,2,   14,2,0,  SkipTab),     // G#9|H#9
2120                          c([ecDown,ecDown,ecDown,ecDown],   9,5,  19,2,   14,2,1,  not SkipTab), // G#9H#|9 //in #9
2121                          c([ecDown,ecDown,ecDown,ecDown,ecDown],   9,6,  29,2,   17,2,0,  (not SkipTab) or KeepX), // before x
2122                          c([ecDown,ecDown,ecDown,ecDown,ecDown],   5,6,  25,2,   16,2,0,  SkipTab       and not KeepX),  // in tab, before x
2123                          c([ecDown,ecDown,ecDown,ecDown,ecDown,ecDown],   9,7,   9,3,   15,3,0,  (not SkipTab) or KeepX),
2124                          c([ecDown,ecDown,ecDown,ecDown,ecDown,ecDown],   5,7,   5,3,    8,3,0,  SkipTab and not KeepX)
2125                         ])
2126     else // not AllowPastEOL then
2127     CheckXyMap('wcpEOL', p( 9,1,   1,2,    9,1,    9,1), // after " " / still on line 1
2128                         [c([ecDown],                        9,2,  17,1,   17,1,0),             // DEFG|
2129                          c([ecDown,ecDown],                 4,3,  21,1,   21,1,0),             // XYZ|
2130                          c([ecDown,ecDown,ecDown],          9,4,   9,2,    5,2,0,  KeepX),     // A#9B#9|C
2131                          c([ecDown,ecDown,ecDown],          4,4,   4,2,    2,2,2,  (not KeepX) and (not SkipTab) ), // A#|9B#9C //in tab
2132                          c([ecDown,ecDown,ecDown],          2,4,   2,2,    2,2,0,  (not KeepX) and SkipTab)  // A#|9B#9C //in tab
2133                         ]);
2134     CheckXyMap('wcpEOL', p( 2,2,          10,1,   10,1));
2135     CheckXyMap('wcpEOL', p( 4,2,          12,1,   12,1), // ABC| DE
2136                          [c([ecColSelDown],              2,5,  12,2,   8,2,0),             // D|EF G#9
2137                           c([ecColSelDown,ecColSelDown], 4,8,  12,3,   21,3,0),
2138                           c([ecColSelDown,ecColSelDown,ecColSelDown], 4,10,  4,4,   4,4,0,  not AllowPastEOL)
2139                          ]);
2140     CheckXyMap('wcpEOL', p( 9,2,          17,1,   17,1)); // after "G"
2141     CheckXyMap('wcpEOL', p(10,2,   1,3,   18,1,   18,1)); // after " "
2142     CheckXyMap('wcpEOL', p( 2,3,          19,1,   19,1)); // after "X"
2143     CheckXyMap('wcpEOL', p( 4,3,          21,1,   21,1)); // after "Z" EOL
2144     if AllowPastEOL then
2145     CheckXyMap('wcpEOL', p( 5,3,          22,1,   22,1)); // at EOL + 1
2146 
2147     CheckXyMap('wcpEOL', p( 1,4,           1,2,    1,2),
2148                         [c([ecDown],                  2,5,  12,2,   8,2,0),
2149                          c([ecDown,ecDown],           5,6,  25,2,  16,2,0,  SkipTab),
2150                          c([ecDown,ecDown],           2,6,  22,2,  15,2,1,  not SkipTab),
2151                          c([ecDown,ecDown,ecDown],    1,7,   1,3,   1,3,0,  KeepX),
2152                          c([ecDown,ecDown,ecDown],    5,7,   5,3,   8,3,0,  (SkipTab) and not KeepX)
2153                         ]);
2154     CheckXyMap('wcpEOL', p( 2,4,           2,2,    2,2, 0)); // before tab
2155     if not SkipTab then
2156     CheckXyMap('wcpEOL', p( 3,4,           3,2,    2,2, 1)); // 1 inside tab
2157     CheckXyMap('wcpEOL', p( 5,4,           5,2,    3,2, 0)); // after tab
2158     CheckXyMap('wcpEOL', p( 9,4,           9,2,    5,2)); // after tab, before "C"
2159     CheckXyMap('wcpEOL', p(10,4,          10,2,    6,2)); // after "C"
2160     CheckXyMap('wcpEOL', p(11,4,   1,5,   11,2,    7,2)); // after " "
2161     CheckXyMap('wcpEOL', p( 2,5,          12,2,    8,2)); // after "D"
2162     CheckXyMap('wcpEOL', p( 8,5,          18,2,   14,2)); // after "H"
2163     if not SkipTab then
2164     CheckXyMap('wcpEOL', p( 9,5,          19,2,   14,2, 1)); // in #9
2165     if not SkipTab then
2166     CheckXyMap('wcpEOL', p(10,5,          20,2,   14,2, 2)); // in #9
2167     CheckXyMap('wcpEOL', p(11,5,   1,6,   21,2,   15,2)); // after #9
2168     if not SkipTab then
2169     CheckXyMap('wcpEOL', p( 2,6,          22,2,   15,2, 1)); // in #9 / next line
2170     CheckXyMap('wcpEOL', p( 5,6,          25,2,   16,2)); // after 1st #9 / next line
2171     CheckXyMap('wcpEOL', p(10,6,          30,2,   18,2)); // after "x"
2172     CheckXyMap('wcpEOL', p(11,6,          31,2,   19,2)); // after "z" EOL
2173 
2174     CheckXyMap('wcpEOL', p( 1,7,           1,3,    1,3));
2175     CheckXyMap('wcpEOL', p( 2,7,           2,3,    3,3));
2176     CheckXyMap('wcpEOL', p( 8,7,           8,3,   14,3)); // after "ö"
2177     CheckXyMap('wcpEOL', p( 9,7,   1,8,    9,3,   15,3)); // after " "
2178     CheckXyMap('wcpEOL', p( 2,8,          10,3,   17,3)); // after "Ä"
2179     CheckXyMap('wcpEOL', p( 9,8,   1,9,   17,3,   29,3)); // after " "
2180 
2181     FWordWrap.CaretWrapPos := wcpBOL;
2182     CheckXyMap('wcpBOL', p( 1,1,           1,1,    1,1),
2183                         [c(ecRight,                         2,1,   2,1,    2,1,0),
2184                          c(ecDown,                          1,2,   9,1,    9,1,0),
2185                          c([ecDown,ecDown],                 1,3,  18,1,   18,1,0),
2186                          c([ecDown,ecDown, ecRight,ecRight],        3,3,  20,1,   20,1,0),
2187                          c([ecDown,ecDown, ecRight,ecRight,ecDown], 2,4,   2,2,    2,2,0, SkipTab),
2188                          c([ecDown,ecDown, ecRight,ecRight,ecDown], 3,4,   3,2,    2,2,1, not SkipTab),
2189                          c([ecDown,ecDown,ecDown],          1,4,   1,2,    1,2,0),
2190                          c([ecDown,ecDown,ecDown,ecDown],   1,5,  11,2,    7,2,0)
2191                         ]);
2192     CheckXyMap('wcpBOL', p( 2,1,           2,1,    2,1),
2193                         [c(ecDown,                          2,2,  10,1,   10,1,0)
2194                         ]);
2195     CheckXyMap('wcpBOL', p( 7,1,           7,1,    7,1)); // after "e"
2196     CheckXyMap('wcpBOL', p( 8,1,           8,1,    8,1));
2197     CheckXyMap('wcpBOL', p( 1,2,   9,1,    9,1,    9,1)); // after " " / still on line 1
2198     CheckXyMap('wcpBOL', p( 2,2,          10,1,   10,1));
2199     CheckXyMap('wcpBOL', p( 9,2,          17,1,   17,1), // after "G"
2200                         [c([ecUp],                          8,1,   8,1,   8,1,0),
2201                          c([ecUp, ecDown],                  9,2,  17,1,  17,1,0,  KeepX),
2202                          c([ecUp, ecDown],                  8,2,  16,1,  16,1,0,  not KeepX)
2203                         ]);
2204     CheckXyMap('wcpBOL', p( 1,3,  10,2,   18,1,   18,1)); // after " "
2205     CheckXyMap('wcpBOL', p( 2,3,          19,1,   19,1)); // after "X"
2206     CheckXyMap('wcpBOL', p( 4,3,          21,1,   21,1)); // after "Z" EOL
2207     if AllowPastEOL then
2208     CheckXyMap('wcpBOL', p( 5,3,          22,1,   22,1)); // at EOL + 1
2209 
2210     CheckXyMap('wcpBOL', p( 1,4,           1,2,    1,2),
2211                         [c([ecDown],                  1,5,  11,2,   7,2,0),
2212                          c([ecDown,ecDown],           1,6,  21,2,  15,2,0),
2213                          c([ecDown,ecDown,ecDown],    1,7,   1,3,   1,3,0)
2214                         ]);
2215     CheckXyMap('wcpBOL', p( 2,4,           2,2,    2,2, 0)); // before tab
2216     if not SkipTab then
2217     CheckXyMap('wcpBOL', p( 3,4,           3,2,    2,2, 1)); // 1 inside tab
2218     CheckXyMap('wcpBOL', p( 5,4,           5,2,    3,2, 0)); // after tab
2219     CheckXyMap('wcpBOL', p( 9,4,           9,2,    5,2)); // after tab, before "C"
2220     CheckXyMap('wcpBOL', p(10,4,          10,2,    6,2), // after "C"
2221                         [c([ecDown],                        8,5,  18,2,   14,2,0, SkipTab),      // G#9H#|9
2222                          c([ecDown],                       10,5,  20,2,   14,2,2, not SkipTab),  // G#9H#|9
2223                          c([ecDown,ecDown],                10,6,  30,2,   18,2,0,  KeepX),       // #9#9X|Y
2224                          c([ecDown,ecDown],                 5,6,  25,2,   16,2,0,  (not KeepX) and SkipTab)    // #9#9X|Y
2225                         ]);
2226     CheckXyMap('wcpBOL', p( 1,5,  11,4,   11,2,    7,2)); // after " "
2227     CheckXyMap('wcpBOL', p( 2,5,          12,2,    8,2)); // after "D"
2228     CheckXyMap('wcpBOL', p( 8,5,          18,2,   14,2)); // after "H"
2229     if not SkipTab then
2230     CheckXyMap('wcpBOL', p( 9,5,          19,2,   14,2, 1)); // in #9
2231     if not SkipTab then
2232     CheckXyMap('wcpBOL', p(10,5,          20,2,   14,2, 2)); // in #9
2233     CheckXyMap('wcpBOL', p( 1,6,  11,5,   21,2,   15,2)); // after #9
2234     if not SkipTab then
2235     CheckXyMap('wcpBOL', p( 2,6,          22,2,   15,2, 1)); // in #9 / next line
2236     CheckXyMap('wcpBOL', p( 5,6,          25,2,   16,2)); // after 1st #9 / next line
2237     CheckXyMap('wcpBOL', p(10,6,          30,2,   18,2)); // after "x"
2238     CheckXyMap('wcpBOL', p(11,6,          31,2,   19,2), // after "y" EOL
2239                         [c([ecUp],                        8,5,  18,2,   14,2,0, SkipTab),      // G#9H#|9
2240                          c([ecUp],                       10,5,  20,2,   14,2,2, not SkipTab),  // G#9H#|9
2241                          c([ecUp,ecDown],                11,6,  31,2,   19,2,0,  KeepX)
2242                         ]);
2243 
2244     CheckXyMap('wcpBOL', p( 1,7,           1,3,    1,3));
2245     CheckXyMap('wcpBOL', p( 2,7,           2,3,    3,3));
2246     CheckXyMap('wcpBOL', p( 8,7,           8,3,   14,3)); // after "ö"
2247     CheckXyMap('wcpBOL', p( 1,8,   9,7,    9,3,   15,3)); // after " "
2248     CheckXyMap('wcpBOL', p( 2,8,          10,3,   17,3)); // after "Ä"
2249     CheckXyMap('wcpBOL', p( 1,9,   9,8,   17,3,   29,3)); // after " "
2250 
2251   end;
2252 
2253 
2254   CheckLineIndexMapping('LineMap 0',   0,   0, 2);
2255   CheckLineIndexMapping('LineMap 1',   1,   3, 5);
2256   CheckLineIndexMapping('LineMap 2',   2,   6, 8);
2257   CheckLineIndexMapping('LineMap 3',   3,   9, 9);
2258 
2259 
2260   SynEdit.ExecuteCommand(ecEditorBottom, '', nil);
2261   AssertEquals('ecEditorBottom', 4 ,SynEdit.CaretY);
2262   AssertEquals('ecEditorBottom', 10 ,SynEdit.CaretObj.ViewedLineCharPos.y);
2263 
2264   SetSynEditWidth(65);
2265   AddLines(0, 6000, 60, 'A');
2266 
2267   CheckLine('', 0, 'A_0_0');
2268   CheckLine('', 1, 'A_1_0');
2269   CheckLine('', 2, 'A_2_0');
2270   CheckLine('', 3, 'A_3_0');
2271 
2272   SetSynEditWidth(35);
2273   CheckLine('', 0, 'A_0_0');
2274   CheckLine('', 1, 'A_0_3');
2275   CheckLine('', 2, 'A_1_0');
2276 
2277 // '		'
2278 
2279 end;
2280 
2281 procedure TTestWordWrapPlugin.TestWrapSplitJoin;
2282   procedure AddLineTestCount(AName: String; ALineIdx, ACount, ALen: Integer; AExpCount: Integer);
2283   begin
2284     AddLines(ALineIdx, ACount, ALen, 'A');
2285     AssertEquals(Format('%s : After ins %d Line(s) at %d  Len %d', [AName, ACount, ALineIdx, ALen]), AExpCount, TreeNodeCount);
2286   end;
2287   procedure AddLineTestCount(AName: String; ALineIdx, ALen: Integer; AExpCount: Integer);
2288   begin
2289     AddLineTestCount(AName, ALineIdx, 1, ALen, AExpCount);
2290   end;
2291 
2292   procedure ChangeLineTestCount(AName: String; ALineIdx, ALen: Integer; AExpCount: Integer);
2293   begin
2294     AddLines(ALineIdx, 1, ALen, 'A', False, True);
2295     AssertEquals(Format('%s : After ins %d Line(s) at %d  Len %d', [AName, 1, ALineIdx, ALen]), AExpCount, TreeNodeCount);
2296   end;
2297   procedure ChangeLineTestCount(AName: String; ALineIdx, ANodeIdx, ALen: Integer; AExpCount: Integer);
2298   var
2299     n: TSynEditLineMapPageHolder;
2300   begin
2301     n := TreeNodeHolder[ANodeIdx];
2302     if ALineIdx >= 0
2303     then AddLines(n.RealStartLine + ALineIdx, 1, ALen, 'A', False, True)
2304     else AddLines(n.RealEndLine + 1 + ALineIdx, 1, ALen, 'A', False, True);
2305     AssertEquals(Format('%s : After ins %d Line(s) at %d  Len %d', [AName, 1, ALineIdx, ALen]), AExpCount, TreeNodeCount);
2306   end;
2307 
2308   procedure DelLineTestCount(AName: String; ALineIdx: Integer; AExpCount: Integer);
2309   begin
2310     if ALineIdx < 0
2311     then SynEdit.Lines.Delete(SynEdit.Lines.Count + 1 + ALineIdx)
2312     else SynEdit.Lines.Delete(ALineIdx);
2313     AssertEquals(Format('%s : After DEL Line at %d  Len %d', [AName, ALineIdx]), AExpCount, TreeNodeCount);
2314   end;
2315   procedure DelLineTestCount(AName: String; ANodeIdx, ALineIdx: Integer; AExpCount: Integer);
2316   var
2317     n: TSynEditLineMapPageHolder;
2318   begin
2319     n := TreeNodeHolder[ANodeIdx];
2320     if ALineIdx < 0
2321     then SynEdit.Lines.Delete(n.RealEndLine + 1 + ALineIdx)
2322     else SynEdit.Lines.Delete(n.RealStartLine + ALineIdx);
2323     AssertEquals(Format('%s : After DEL Line at %d  Len %d', [AName, ALineIdx]), AExpCount, TreeNodeCount);
2324   end;
2325 
2326 var
2327   t: TSynLineMapAVLTree;
2328   n1, n2: TSynEditLineMapPageHolder;
2329   i: Integer;
2330 begin
2331   ReCreateEdit(10);
2332   SynEdit.Options := [];
2333   t := FWordWrap.FLineMapView.Tree;
2334 debugln(' split %d  join %d  dist %d', [t.PageSplitSize, t.PageJoinSize, t.PageJoinDistance]);
2335 
2336   AddLineTestCount('new: split - 2', 0, t.PageSplitSize - 2, 18,    1);
2337   AddLineTestCount('insert start: split - 1', 0, 18,    1);
2338   AddLineTestCount('insert start: split - 0', 0, 18,    1);
2339   AddLineTestCount('insert start: split + 1', 0, 18,    2);
2340   AddLineTestCount('insert start: split + 2', 0, 18,    2);
2341 
2342 
2343   ReCreateEdit(10);
2344   SynEdit.Options := [];
2345   t := FWordWrap.FLineMapView.Tree;
2346   AddLineTestCount('new: split - 2', 0, t.PageSplitSize - 2, 18,    1);
2347   AddLineTestCount('insert end: split - 1', SynEdit.Lines.Count, 18,    1);
2348   AddLineTestCount('insert end: split - 0', SynEdit.Lines.Count, 18,    1);
2349   AddLineTestCount('insert end: split + 1', SynEdit.Lines.Count, 18,    2);
2350   AddLineTestCount('insert end: split + 2', SynEdit.Lines.Count, 18,    2);
2351 
2352 
2353   ReCreateEdit(10);
2354   SynEdit.Options := [];
2355   t := FWordWrap.FLineMapView.Tree;
2356   AddLineTestCount('new: split - 2', 0, t.PageSplitSize - 2, 18,    1);
2357   AddLineTestCount('insert @10: split - 1', 10, 18,    1);
2358   AddLineTestCount('insert @10: split - 0', 10, 18,    1);
2359   AddLineTestCount('insert @10: split + 1', 10, 18,    2);
2360   AddLineTestCount('insert @10: split + 2', 10, 18,    2);
2361 
2362 
2363   ReCreateEdit(10);
2364   SynEdit.Options := [];
2365   t := FWordWrap.FLineMapView.Tree;
2366   i := t.PageSplitSize - 2;
2367   AddLineTestCount('new: split - 2', 0,  i, 18,    1);
2368   AddLineTestCount('new: split - 2', i, 10,  1,    1);
2369   ChangeLineTestCount('update end: split - 1', i, 18,   1);   inc(i);
2370   ChangeLineTestCount('update end: split - 0', i, 18,   1);   inc(i);
2371   ChangeLineTestCount('update end: split + 1', i, 18,   2);   inc(i);
2372   ChangeLineTestCount('update end: split + 2', i, 18,   2);   inc(i);
2373 
2374 
2375   ReCreateEdit(10);
2376   SynEdit.Options := [];
2377   t := FWordWrap.FLineMapView.Tree;
2378   i := 9;
2379   AddLineTestCount('new: split - 2', 0,  t.PageSplitSize - 2, 18,    1);
2380   AddLineTestCount('new: split - 2', 0, 10,  1,    1);
2381   ChangeLineTestCount('update start: split - 1', i, 18,   1);   dec(i);
2382   ChangeLineTestCount('update start: split - 0', i, 18,   1);   dec(i);
2383   ChangeLineTestCount('update start: split + 1', i, 18,   2);   dec(i);
2384   ChangeLineTestCount('update start: split + 2', i, 18,   2);   dec(i);
2385 
2386 
2387 
2388   ///////////////////
2389   ReCreateEdit(10);
2390   SynEdit.Options := [];
2391   t := FWordWrap.FLineMapView.Tree;
2392   AddLineTestCount('new: split double', 0,  t.PageSplitSize + t.PageJoinSize + 2, 18,    2);
2393 
2394 
2395   n1 := TreeNodeHolder[0];
2396   while n1.RealCount > t.PageJoinSize + 1 do
2397     SynEdit.Lines.Delete(n1.RealStartLine + 2);
2398   AssertEquals('insert start: split + 2', 2, TreeNodeCount);
2399 
2400   n2 := TreeNodeHolder[1];
2401   while n2.RealCount > t.PageJoinSize + 1 do
2402     SynEdit.Lines.Delete(n2.RealStartLine + 2);
2403   AssertEquals('insert start: split + 2', 2, TreeNodeCount);
2404 
2405   SynEdit.Lines.Delete(1);
2406   SynEdit.Lines.Delete(SynEdit.Lines.Count - 2);
2407 
2408   AssertEquals('insert start: split + 2', 1, TreeNodeCount);
2409 
2410 
2411 
2412 
2413 
2414   ReCreateEdit(10);
2415   SynEdit.Options := [];
2416   t := FWordWrap.FLineMapView.Tree;
2417   AddLineTestCount('new: split double', 0,  t.PageSplitSize + t.PageJoinSize + 2, 18,    2);
2418 
2419 
2420   n1 := TreeNodeHolder[0];
2421   while n1.RealCount > t.PageJoinSize + 1 do
2422     ChangeLineTestCount('edit n1 start', 0,0,  1,   2);
2423   n2 := TreeNodeHolder[1];
2424   while n2.RealCount > t.PageJoinSize + 1 do
2425     ChangeLineTestCount('edit n2 end', -1,1,  1,   2);
2426 
2427   ChangeLineTestCount('edit n1 start',  0,0,  1,   2);
2428   ChangeLineTestCount('edit n2 end  ', -1,1,  1,   1);
2429 
2430 
2431 
2432 
2433   // do not join
2434   ReCreateEdit(10);
2435   SynEdit.Options := [];
2436   t := FWordWrap.FLineMapView.Tree;
2437   AddLineTestCount('new: split * 2 - 2', 0,  t.PageSplitSize * 2 - 2, 18,    2);
2438 
2439 
2440   n1 := TreeNodeHolder[0];
2441   while n1.RealCount > t.PageJoinSize + 1 do
2442     ChangeLineTestCount('edit n1 end', -1,0,  1,   2);
2443   n2 := TreeNodeHolder[1];
2444   while n2.RealCount > t.PageJoinSize + 1 do
2445     ChangeLineTestCount('edit n2 start', 0,1,  1,   2);
2446 
2447   ChangeLineTestCount('edit n1 start', -1,0,  1,   2);
2448   ChangeLineTestCount('edit n2 end  ',  0,1,  1,   2);
2449   ChangeLineTestCount('edit n1 start', -1,0,  1,   2);
2450   ChangeLineTestCount('edit n2 end  ',  0,1,  1,   2);
2451   ChangeLineTestCount('edit n1 start', -1,0,  1,   2);
2452   ChangeLineTestCount('edit n2 end  ',  0,1,  1,   2);
2453 
2454 
2455 
2456 end;
2457 
2458 procedure TTestWordWrapPlugin.TestEditorEdit;
2459 begin
2460   SynEdit.Options := [];
2461   SynEdit.TabWidth := 4;
2462 
2463   SetLines([
2464     'abc def ' + 'ABC DEFG ' + 'XYZ',
2465     '',
2466     //'A'  #9'B'  #9'C ' + 'DEF G'#9'H'  #9 + ''   #9   #9'xy',
2467     'A'#9'B'#9'C ' + 'DEF G'#9'H'#9 + #9#9'xy',
2468     '',
2469     'äää ööö ' + 'ÄÄÄ ÖÖÖ ' + 'ÜÜÜ',
2470     '',
2471     '999'
2472   ]);
2473   SetSynEditWidth(10);
2474 
2475   SetSynEditWidth(10);
2476   CheckLines('', 0, [
2477     'abc def ',
2478     'ABC DEFG ',
2479     'XYZ',
2480     '',
2481     'A'#9'B'#9'C ',
2482     'DEF G'#9'H'#9,
2483     #9#9'xy',
2484     '',
2485     'äää ööö ',
2486     'ÄÄÄ ÖÖÖ ',
2487     'ÜÜÜ',
2488     '',
2489     '999'
2490   ], True);
2491 
2492   SynEdit.BeginUpdate;
2493   SynEdit.TestTypeText(1,7, '4 ');
2494   SynEdit.TestTypeText(1,3, '2 ');
2495   SynEdit.TestTypeText(1,5, '3 ');
2496   SynEdit.TestTypeText(1,1, '1 ');
2497   SynEdit.EndUpdate;
2498 
2499   CheckLines('', 0, [
2500     '1 abc def ',
2501     'ABC DEFG ',
2502     'XYZ',
2503     '',
2504     '2 A'#9'B'#9'C ',
2505     'DEF G'#9'H'#9,
2506     #9#9'xy',
2507     '',
2508     '3 äää ööö ',
2509     'ÄÄÄ ÖÖÖ ',
2510     'ÜÜÜ',
2511     '',
2512     '4 999'
2513   ], True);
2514 end;
2515 
2516 initialization
2517 
2518   RegisterTest(TTestWordWrap);
2519   RegisterTest(TTestWordWrapPlugin);
2520 end.
2521 
2522