1unit SynEditWrappedView experimental;
2
3{$mode objfpc}{$H+}
4
5interface
6
7uses
8  Classes, SysUtils, math, LazSynEditText, SynEdit, SynEditViewedLineMap,
9  SynEditTypes, SynEditMiscProcs, SynEditHighlighter, SynEditMiscClasses,
10  Graphics, LazLogger, LazListClasses;
11
12type
13  TLazSynEditLineWrapPlugin = class;
14
15  TSynWordWrapLineData = Integer;
16  PSynWordWrapLineData = ^TSynWordWrapLineData;
17
18  TSynWordWrapIndexPage = class;
19  TSynWordWrapLineMap = class;
20
21  TSynWordWrapInvalidLinesRecord = record
22    First, Last: Integer;
23  end;
24  PSynWordWrapInvalidLinesRecord = ^TSynWordWrapInvalidLinesRecord;
25
26  { TSynWordWrapInvalidLines }
27
28  TSynWordWrapInvalidLinesRecordSize = specialize TLazListClassesItemSize<TSynWordWrapInvalidLinesRecord>;
29  TSynWordWrapInvalidLines = object(specialize TLazShiftBufferListObjBase<PSynWordWrapInvalidLinesRecord, TSynWordWrapInvalidLinesRecordSize>)
30  private
31    function GetFirstInvalidEndLine: Integer; inline;
32    function GetFirstInvalidLine: Integer; inline;
33    function GetLastInvalidLine: Integer;
34    function GetItem(AnIndex: Integer): TSynWordWrapInvalidLinesRecord; inline;
35
36    function GrowCapacity(ARequired: Integer): Integer;
37    function ShrinkCapacity(ARequired: Integer): Integer;
38    procedure InsertRows(AIndex, ACount: Integer); inline;
39    procedure DeleteRows(AIndex, ACount: Integer); inline;
40  public
41    function FindIndexFor(ALine: Integer): Integer; // find first Result.First after ALine
42    procedure InvalidateLines(AFromOffset, AToOffset: Integer);
43    procedure InsertInvalidateLines(AFromOffset, ACount: Integer);
44    procedure InsertLines(AFromOffset, ACount: Integer; ASplit: Boolean = False);
45    procedure RemoveLines(AFromOffset, ACount: Integer);
46    procedure ValidateLines(AnOffset: Integer); inline;
47    procedure MoveRangeAtStartTo(var ADestLines: TSynWordWrapInvalidLines; ASourceEndLine, AnAdjust: Integer);
48    procedure MoveRangeAtEndTo(var ADestLines: TSynWordWrapInvalidLines; ASourceStartLine, AnAdjust: Integer);
49    property FirstInvalidLine: Integer read GetFirstInvalidLine;
50    property FirstInvalidEndLine: Integer read GetFirstInvalidEndLine;
51    property LastInvalidLine: Integer read GetLastInvalidLine;
52    property Item[AnIndex: Integer]: TSynWordWrapInvalidLinesRecord read GetItem;
53  end;
54
55  { TSynWordWrapLineMap }
56
57  TSynWordWrapLineMap = class
58  private
59    FAvlNode: TSynWordWrapIndexPage;
60    FInvalidLines: TSynWordWrapInvalidLines;
61    FDeferredAdjustFromOffs, FDeferredAdjustFromVal: Integer;
62
63    (* FWrappedExtraSums:
64       Sum of all extra lines due to wrapping up to (and including) the current element.
65       If a line wraps and needs 3 viewed-lines, then that is 2 extra lines
66       - WrappedOffsetFor(n) => Viewed start of line n
67       - FWrappedExtraSums[n] => Viewed start of line n+1
68    *)
69    FWrappedExtraSumsCount: Integer;
70    FWrappedExtraSums: Array of TSynWordWrapLineData; // -1 based
71    FOffsetAtStart: Integer;
72  private
73    function  GetCapacity: Integer; inline;
74    procedure SetCapacity(AValue: Integer); inline;
75    procedure GrowCapacity(ARequired: Integer);
76    procedure ShrinkCapacity;
77
78  private
79    function GetViewedCount: Integer;
80    function GetViewedRealCountDifference: Integer;
81    function GetWrappedExtraSumBefore(ARealOffset: Integer): Integer; inline; // Must have: ARealOffset < FWrappedExtraSumsCount // ingnores FOffsetAtStart
82
83    function GetWrappedOffsetFor(ARealOffset: IntIdx): IntIdx; inline;
84
85    function  GetFirstInvalidLine: Integer; inline;
86    function  GetFirstInvalidEndLine: Integer; inline;
87    function  GetLastInvalidLine: Integer; inline;
88    procedure AddToInvalidList; inline;
89    procedure RemoveFromInvalidList(AMode: TRemoveFromInvalidListMode = rfiDefault); inline;
90    procedure MaybeUpdateViewedSizeDifference;  inline;
91
92    property Capacity: Integer read GetCapacity write SetCapacity;
93  public
94    constructor Create;
95    destructor Destroy; override;
96    function GetDumpData: String;
97
98    property AvlNode: TSynWordWrapIndexPage read FAvlNode;
99    property Offset: Integer read FOffsetAtStart;
100    property RealCount: Integer read FWrappedExtraSumsCount;
101    property ViewedCount: Integer read GetViewedCount;
102    property ViewedRealCountDifference: Integer read GetViewedRealCountDifference; // viewed - real
103
104
105    procedure InvalidateLines(AFromOffset, AToOffset: Integer); //
106    procedure ValidateLine(ALineOffset, AWrappCount: Integer);
107    procedure EndValidate;
108    property FirstInvalidLine: Integer read GetFirstInvalidLine;
109    property FirstInvalidEndLine: Integer read GetFirstInvalidEndLine;
110    property LastInvalidLine: Integer read GetLastInvalidLine;
111
112    procedure InsertLinesAtOffset(ALineOffset, ALineCount: Integer);
113    procedure DeleteLinesAtOffset(ALineOffset, ALineCount: Integer; ADoNotShrink: Boolean = False);
114
115    procedure MoveLinesAtStartTo(ADestPage: TSynWordWrapLineMap; ASourceEndLine, ATargetStartLine: Integer);
116    procedure MoveLinesAtEndTo(ADestPage: TSynWordWrapLineMap; ASourceStartLine, ALineCount: Integer);
117
118    function GetOffsetForWrap(AViewedOffset: IntIdx; out ASubOffset: IntIdx): IntIdx;
119    property WrappedOffsetFor[ARealOffset: IntIdx]: IntIdx read GetWrappedOffsetFor;
120  end;
121
122  { TSynWordWrapIndexPage }
123
124  TSynWordWrapIndexPage = class(TSynEditLineMapPage)
125  private
126    FSynWordWrapLineMap: TSynWordWrapLineMap;
127    FSynEditWrappedPlugin :TLazSynEditLineWrapPlugin;
128
129    procedure UpdateViewedSizeDifference;
130    procedure MaybeJoinWithSibling;
131  protected
132    function GetFirstInvalidLine: Integer; override;
133    function GetFirstInvalidEndLine: Integer; override;
134    function GetLastInvalidLine: Integer; override;
135    function GetViewedRealCountDifference: Integer; override;
136
137    function GetWrappedOffsetFor(ARealOffset: IntIdx): IntIdx; override;
138    function IsValid: boolean; override;
139  public
140    property SynWordWrapLineMapStore: TSynWordWrapLineMap read FSynWordWrapLineMap; experimental; // 'For test case only';
141  public
142    constructor Create(ATree: TSynLineMapAVLTree); override;
143    destructor Destroy; override;
144    procedure DumpNode(ALine: Integer = 0; AnIndent: Integer = 0); override;
145
146    function CanExtendStartTo(ALineOffs: Integer; AIgnoreJoinDist: Boolean = False): boolean; override;
147    function CanExtendEndTo(ALineOffs: Integer; AIgnoreJoinDist: Boolean = False): boolean; override;
148
149    procedure InsertLinesAtOffset(ALineOffset, ALineCount: IntIdx); override;
150    procedure DeleteLinesAtOffset(ALineOffset, ALineCount: IntIdx; ADoNotShrink: Boolean = False); override;
151
152    procedure AdjustForLinesInserted(AStartLine, ALineCount: IntIdx; ABytePos: Integer); override;
153    procedure AdjustForLinesDeleted(AStartLine, ALineCount: IntIdx; ABytePos: Integer); override;
154
155    procedure MoveLinesAtStartTo(ADestPage: TSynEditLineMapPage; ASourceEndLine, ATargetStartLine: Integer); override;
156    procedure MoveLinesAtEndTo(ADestPage: TSynEditLineMapPage; ASourceStartLine, ACount: Integer); override;
157
158    // must be FirstInvalidLine (or Last) => so FirstInvalidLine can be set.
159    procedure EndValidate; override;
160    procedure ValidateLine(ALineOffset, AWrappCount: Integer); override;
161    procedure InvalidateLines(AFromOffset, AToOffset: Integer); override; // TODO: adjust offset
162    function ExtendAndInvalidateLines(AFromLineIdx, AToLineIdx: TLineIdx): Boolean; override;
163
164    function RealCount: Integer; override; // count of real lines
165    function RealStartLine: Integer; override; // Offset
166    function RealEndLine: Integer; override; // Offset + RealCount - 1;
167
168    function GetOffsetForWrap(AWrapOffset: IntIdx; out ASubOffset: IntIdx): IntIdx; override;
169
170    function TextXYIdxToViewXYIdx(ATextXYIdx: TPhysPoint; ANodeStartLine: IntIdx): TPhysPoint; override;
171    function ViewXYIdxToTextXYIdx(AViewXYIdx: TPhysPoint; ANodeStartLine: IntIdx): TPhysPoint; override;
172  end;
173
174
175  TLazSynEditWrapCaretPos = (wcpEOL, wcpBOL);
176
177  { TLazSynDisplayWordWrap }
178
179  TLazSynDisplayWordWrap = class(TLazSynDisplayLineMapping)
180  private
181    FWrapPlugin: TLazSynEditLineWrapPlugin;
182
183    FCurSubLineLogStartIdx, FCurSubLineNextLogStartIdx, FCurSubLinePhysStartIdx: Integer;
184    FCurToken: TLazSynDisplayTokenInfo;
185    FCurLineLogIdx: Integer;
186  public
187    constructor Create(AWrappedView: TSynEditLineMappingView; AWrapPlugin: TLazSynEditLineWrapPlugin);
188    //destructor Destroy; override;
189    procedure SetHighlighterTokensLine(AWrappedLine: TLineIdx; out
190      ARealLine: TLineIdx; out AStartBytePos, ALineByteLen: Integer); override;
191    function GetNextHighlighterToken(out ATokenInfo: TLazSynDisplayTokenInfo): Boolean; override;
192  end;
193
194  { TLazSynEditLineWrapPlugin }
195
196  TLazSynEditLineWrapPlugin = class(TLazSynEditPlugin)
197  private
198    FCaretWrapPos: TLazSynEditWrapCaretPos;
199    procedure DoLinesChanged(Sender: TObject);
200    procedure DoWidthChanged(Sender: TObject; Changes: TSynStatusChanges);
201    function GetWrapColumn: Integer;
202public
203    FLineMapView: TSynEditLineMappingView;
204    function CreatePageMapNode(AMapTree: TSynLineMapAVLTree
205      ): TSynEditLineMapPage;
206  protected
207    procedure SetEditor(const AValue: TCustomSynEdit); override;
208
209    function CalculateNextBreak(ALine: PChar; ALogStartFrom: IntIdx; AMaxWidth: Integer;
210      const PhysCharWidths: TPhysicalCharWidths; out APhysWidth: Integer): IntIdx;
211    function  GetSublineCount (ALine: String; AMaxWidth: Integer; const APhysCharWidths: TPhysicalCharWidths): Integer; inline;
212    procedure GetSublineBounds(ALine: String; AMaxWidth: Integer;
213      const APhysCharWidths: TPhysicalCharWidths; ASubLine: Integer; out ALogStartX,
214      ANextLogStartX, APhysStart: IntIdx; out APhysWidth: integer);
215    function  GetSubLineFromX (ALine: String; AMaxWidth: Integer; const APhysCharWidths: TPhysicalCharWidths; var APhysXPos: Integer): integer;
216
217    procedure GetWrapInfoForViewedXY(var AViewedXY: TPhysPoint; AFlags: TViewedXYInfoFlags; out AFirstViewedX: IntPos; ALogPhysConvertor: TSynLogicalPhysicalConvertor);
218
219    function TextXYToLineXY(ATextXY: TPhysPoint): TPhysPoint;
220    function LineXYToTextX(ARealLine: IntPos; ALineXY: TPhysPoint): Integer;
221    function CalculateWrapForLine(ALineIdx: IntIdx; AMaxWidth: integer): Integer; inline;
222  public
223    constructor Create(AOwner: TComponent); override;
224
225    procedure WrapAll; experimental;
226    procedure ValidateAll; experimental;
227
228    property CaretWrapPos: TLazSynEditWrapCaretPos read FCaretWrapPos write FCaretWrapPos;
229    property WrapColumn: Integer read GetWrapColumn;
230  end;
231
232implementation
233
234{ TSynWordWrapInvalidLines }
235
236function TSynWordWrapInvalidLines.GetFirstInvalidEndLine: Integer;
237begin
238  if Count = 0 then
239    Result := -1
240  else
241    Result := Item[0].Last;
242end;
243
244function TSynWordWrapInvalidLines.GetFirstInvalidLine: Integer;
245begin
246  if Count = 0 then
247    Result := -1
248  else
249    Result := Item[0].First;
250end;
251
252function TSynWordWrapInvalidLines.GetLastInvalidLine: Integer;
253var
254  i: Integer;
255begin
256  i := Count;
257  if i = 0 then
258    Result := -1
259  else
260    Result := Item[i-1].Last;
261end;
262
263function TSynWordWrapInvalidLines.GetItem(AnIndex: Integer): TSynWordWrapInvalidLinesRecord;
264begin
265  Result := ItemPointer[AnIndex]^;
266end;
267
268function TSynWordWrapInvalidLines.GrowCapacity(ARequired: Integer): Integer;
269begin
270  Result := ARequired + 16;
271end;
272
273function TSynWordWrapInvalidLines.ShrinkCapacity(ARequired: Integer): Integer;
274begin
275  //if ARequired = 0 then
276  //  Result := 0
277  //else
278  if ARequired * 8 < Count then
279    Result := ARequired + 4
280  else
281    Result := -1;
282end;
283
284procedure TSynWordWrapInvalidLines.InsertRows(AIndex, ACount: Integer);
285begin
286  InsertRowsEx(AIndex, ACount, @GrowCapacity);
287end;
288
289procedure TSynWordWrapInvalidLines.DeleteRows(AIndex, ACount: Integer);
290begin
291  DeleteRowsEx(AIndex, ACount, @ShrinkCapacity);
292end;
293
294function TSynWordWrapInvalidLines.FindIndexFor(ALine: Integer): Integer;
295var
296  l, h: integer;
297begin
298  l := 0;
299  h := Count-1;
300  if (h < 0) then begin
301    Result := 0;
302    exit;
303  end;
304
305  Result := (l + h) div 2;
306  while (h > l) do begin
307    if PSynWordWrapInvalidLinesRecord(ItemPointer[Result])^.First >= ALine then
308      h := Result
309    else
310      l := Result + 1;
311    Result := cardinal(l + h) div 2;
312  end;
313  if PSynWordWrapInvalidLinesRecord(ItemPointer[Result])^.First < ALine then
314    inc(Result);
315end;
316
317procedure TSynWordWrapInvalidLines.InvalidateLines(AFromOffset, AToOffset: Integer);
318var
319  i, j, c: Integer;
320begin
321  c := Count;
322  i := FindIndexFor(AFromOffset);
323  if (i > 0) and (Item[i-1].Last >= AFromOffset-1) then begin
324    if (Item[i-1].Last >= AToOffset) then
325      exit;
326    dec(i);
327    PSynWordWrapInvalidLinesRecord(ItemPointer[i])^.Last := AToOffset;
328  end
329  else
330  if (i < c) and (Item[i].First = AToOffset+1) then begin
331    PSynWordWrapInvalidLinesRecord(ItemPointer[i])^.First := AFromOffset;
332    if AToOffset > PSynWordWrapInvalidLinesRecord(ItemPointer[i])^.Last then
333      PSynWordWrapInvalidLinesRecord(ItemPointer[i])^.Last := AToOffset;
334  end
335  else begin
336    assert((i = 0) or (Item[i-1].Last < AFromOffset-1), 'TSynWordWrapInvalidLines.InvalidateLines: (i = 0) or (Item[i-1].Last < AFromOffset-1)');
337    assert((i >= c -1) or (Item[i+1].First > AToOffset+1), 'TSynWordWrapInvalidLines.InvalidateLines: (i < Cnt-1) or (Item[i+1].First > AToOffset+1)');
338    InsertRows(i, 1);
339    PSynWordWrapInvalidLinesRecord(ItemPointer[i])^.First := AFromOffset;
340    PSynWordWrapInvalidLinesRecord(ItemPointer[i])^.Last := AToOffset;
341    inc(c);
342  end;
343  j := i + 1;
344  while j < c do begin
345    if (Item[j].Last <= AToOffset) then begin
346      DeleteRows(j,1);
347      dec(c);
348    end
349    else
350    if (Item[j].First <= AToOffset+1) then begin
351      PSynWordWrapInvalidLinesRecord(ItemPointer[i])^.Last := Item[j].Last;
352      DeleteRows(j,1);
353      dec(c);
354    end
355    else
356      break;
357  end;
358end;
359
360procedure TSynWordWrapInvalidLines.InsertInvalidateLines(AFromOffset, ACount: Integer);
361begin
362  InsertLines(AFromOffset, ACount);
363  InvalidateLines(AFromOffset, AFromOffset + ACount - 1);
364end;
365
366procedure TSynWordWrapInvalidLines.InsertLines(AFromOffset, ACount: Integer; ASplit: Boolean);
367var
368  i, j: Integer;
369begin
370  i := Count;
371  while i > 0 do begin
372    dec(i);
373    j := Item[i].First;
374    if j >= AFromOffset then begin
375      PSynWordWrapInvalidLinesRecord(ItemPointer[i])^.First := j + ACount;
376      PSynWordWrapInvalidLinesRecord(ItemPointer[i])^.Last := Item[i].Last + ACount;
377    end
378    else
379    if Item[i].Last >= AFromOffset then begin
380      if ASplit then begin
381        j := PSynWordWrapInvalidLinesRecord(ItemPointer[i])^.Last;
382        PSynWordWrapInvalidLinesRecord(ItemPointer[i])^.Last := AFromOffset - 1;
383        InsertInvalidateLines(AFromOffset+ACount, j-AFromOffset+ 1);
384      end
385      else
386        PSynWordWrapInvalidLinesRecord(ItemPointer[i])^.Last := Item[i].Last + ACount;
387    end
388    else begin
389      assert((Item[i].Last < AFromOffset), 'TSynWordWrapInvalidLines.InsertInvalidateLines: (Item[i].Last < AFromOffset)');
390      break;
391    end;
392  end;
393end;
394
395procedure TSynWordWrapInvalidLines.RemoveLines(AFromOffset, ACount: Integer);
396var
397  i, f, l, k: Integer;
398begin
399  i := Count;
400  while i > 0 do begin
401    dec(i);
402    f := ItemPointer[i]^.First;
403    l := ItemPointer[i]^.Last;
404    if f >= AFromOffset then begin
405      k := Max(AFromOffset, f - ACount);
406      if l - ACount < k then begin
407        DeleteRows(i,1);
408      end
409      else begin
410        ItemPointer[i]^.First := k;
411        ItemPointer[i]^.Last := l - ACount;
412      end;
413    end
414    else
415    if l >= AFromOffset then begin
416      k := Max(AFromOffset - 1, l - ACount);
417      assert(k >= f, 'TSynWordWrapInvalidLines.RemoveLines: k >= f');
418      PSynWordWrapInvalidLinesRecord(ItemPointer[i])^.Last := k;
419    end
420    else begin
421      break;
422    end;
423  end;
424end;
425
426procedure TSynWordWrapInvalidLines.ValidateLines(AnOffset: Integer);
427var
428  i, c: Integer;
429begin
430  assert((AnOffset >= 0) and (AnOffset = FirstInvalidLine) or (AnOffset = LastInvalidLine), 'TSynWordWrapInvalidLines.ValidateLines: (AnOffset >= 0) and (AnOffset = FirstInvalidLine) or (AnOffset = LastInvalidLine)');
431  i := Item[0].First;
432  if AnOffset = i then begin
433    if i < Item[0].Last then
434      PSynWordWrapInvalidLinesRecord(ItemPointer[0])^.First := i + 1
435    else
436      DeleteRows(0,1);
437  end
438  else begin
439    c := Count-1;
440    i := Item[c].last;
441    if i > Item[c].First then
442      PSynWordWrapInvalidLinesRecord(ItemPointer[c])^.Last := i - 1
443    else
444      DeleteRows(c,1);
445  end;
446end;
447
448procedure TSynWordWrapInvalidLines.MoveRangeAtStartTo(
449  var ADestLines: TSynWordWrapInvalidLines; ASourceEndLine, AnAdjust: Integer);
450var
451  i, c, ItemFirst, ItemLast: Integer;
452  DelTo: Integer;
453begin
454  DelTo := -1;
455  c := Count;
456  i := 0;
457  while i < c do begin
458    ItemFirst := ItemPointer[i]^.First;
459    ItemLast := ItemPointer[i]^.Last;
460    if ItemLast <= ASourceEndLine then begin
461      ADestLines.InvalidateLines(ItemFirst + AnAdjust, ItemLast + AnAdjust);
462      DelTo := i;
463    end
464    else
465    if ItemFirst <= ASourceEndLine then begin
466      ADestLines.InvalidateLines(ItemFirst + AnAdjust, ASourceEndLine + AnAdjust);
467      ItemPointer[i]^.First := ASourceEndLine;
468      break;
469    end
470    else
471      break;
472    inc(i);
473  end;
474  if DelTo >= 0 then
475    DeleteRows(0, DelTo + 1);
476end;
477
478procedure TSynWordWrapInvalidLines.MoveRangeAtEndTo(
479  var ADestLines: TSynWordWrapInvalidLines; ASourceStartLine, AnAdjust: Integer);
480var
481  i, ItemFirst, ItemLast: Integer;
482  DelFrom: Integer;
483begin
484  i := Count;
485  DelFrom := i;
486  while i > 0 do begin
487    dec(i);
488    ItemFirst := ItemPointer[i]^.First;
489    ItemLast := ItemPointer[i]^.Last;
490    if ItemFirst >= ASourceStartLine then begin
491      ADestLines.InvalidateLines(ItemFirst + AnAdjust, ItemLast + AnAdjust);
492      DelFrom := i;
493    end
494    else
495    if ItemLast >= ASourceStartLine then begin
496      ADestLines.InvalidateLines(ASourceStartLine + AnAdjust, ItemLast + AnAdjust);
497      ItemPointer[i]^.Last := ASourceStartLine - 1;
498      break;
499    end
500    else
501      break;
502  end;
503  if DelFrom < Count then
504    DeleteRows(DelFrom, Count - DelFrom);
505end;
506
507procedure WrapInfoFillFrom(ATarget: PSynWordWrapLineData; ACount, AValue: Integer); inline;
508var
509  i: Integer;
510begin
511  for i := 0 to ACount - 1 do begin
512    ATarget^ := AValue;
513    inc(ATarget);
514  end;
515end;
516
517procedure WrapInfoCopyFromTo(ASource, ATarget: PSynWordWrapLineData; ACount: Integer); inline;
518var
519  i: Integer;
520begin
521  for i := 0 to ACount - 1 do begin
522    ATarget^ := ASource^;
523    inc(ASource);
524    inc(ATarget);
525  end;
526end;
527
528procedure WrapInfoMoveUpFromTo(ASource, ATarget: PSynWordWrapLineData; ACount: Integer); inline;
529var
530  i: Integer;
531begin
532  inc(ASource, ACount - 1);
533  inc(ATarget, ACount - 1);
534  for i := 0 to ACount - 1 do begin
535    ATarget^ := ASource^;
536    dec(ASource);
537    dec(ATarget);
538  end;
539end;
540
541procedure WrapInfoCopyAndAdjustFromTo(ASource, ATarget: PSynWordWrapLineData; ACount, AnAdjustVal: Integer); inline;
542var
543  i: Integer;
544begin
545  for i := 0 to ACount - 1 do begin
546    ATarget^ := ASource^ + AnAdjustVal;
547    inc(ASource);
548    inc(ATarget);
549  end;
550end;
551
552procedure WrapInfoMoveUpAndAdjustFromTo(ASource, ATarget: PSynWordWrapLineData; ACount, AnAdjustVal: Integer); inline;
553var
554  i: Integer;
555begin
556  inc(ASource, ACount - 1);
557  inc(ATarget, ACount - 1);
558  for i := 0 to ACount - 1 do begin
559    ATarget^ := ASource^ + AnAdjustVal;
560    dec(ASource);
561    dec(ATarget);
562  end;
563end;
564
565{ TSynWordWrapLineMap }
566
567function TSynWordWrapLineMap.GetCapacity: Integer;
568begin
569  Result := Length(FWrappedExtraSums);
570end;
571
572procedure TSynWordWrapLineMap.SetCapacity(AValue: Integer);
573begin
574  if AValue < FWrappedExtraSumsCount then
575    AValue := FWrappedExtraSumsCount;
576  SetLength(FWrappedExtraSums, AValue);
577end;
578
579procedure TSynWordWrapLineMap.GrowCapacity(ARequired: Integer);
580begin
581  if Capacity < ARequired then
582    Capacity := ARequired + SYN_WORD_WRAP_GROW_SIZE;
583end;
584
585procedure TSynWordWrapLineMap.ShrinkCapacity;
586var
587  i: Integer;
588begin
589  i := 0;
590  while (i < FWrappedExtraSumsCount) and (FWrappedExtraSums[i] = 0) do
591    inc(i);
592  if i > 0 then begin
593    WrapInfoCopyFromTo(
594      @FWrappedExtraSums[i],
595      @FWrappedExtraSums[0],
596      FWrappedExtraSumsCount-i);
597    FOffsetAtStart := FOffsetAtStart + i;
598    FWrappedExtraSumsCount := FWrappedExtraSumsCount - i;
599  end;
600
601  if Capacity > FWrappedExtraSumsCount + 2 * SYN_WORD_WRAP_GROW_SIZE then
602    Capacity := FWrappedExtraSumsCount + SYN_WORD_WRAP_GROW_SIZE;
603end;
604
605function TSynWordWrapLineMap.GetViewedCount: Integer;
606begin
607  assert((FOffsetAtStart = 0) or (FWrappedExtraSumsCount > 0), 'TSynWordWrapLineMap.GetViewedCount: (FOffsetAtStart = 0) or (FWrappedExtraSumsCount > 0)');
608  Result := FWrappedExtraSumsCount;
609  if FWrappedExtraSumsCount > 0 then
610    Result := Result + FWrappedExtraSums[FWrappedExtraSumsCount - 1];
611end;
612
613function TSynWordWrapLineMap.GetViewedRealCountDifference: Integer;
614begin
615  assert((FOffsetAtStart = 0) or (FWrappedExtraSumsCount > 0), 'TSynWordWrapLineMap.GetViewedRealCountDifference: (FOffsetAtStart = 0) or (FWrappedExtraSumsCount > 0)');
616  if FWrappedExtraSumsCount > 0 then
617    Result := FWrappedExtraSums[FWrappedExtraSumsCount - 1]
618  else
619    Result := 0;
620end;
621
622function TSynWordWrapLineMap.GetWrappedExtraSumBefore(ARealOffset: Integer
623  ): Integer;
624begin
625  if ARealOffset = 0 then
626    Result := 0
627  else
628    Result := FWrappedExtraSums[ARealOffset-1];
629end;
630
631function TSynWordWrapLineMap.GetWrappedOffsetFor(ARealOffset: IntIdx): IntIdx;
632begin
633  Result := ARealOffset;
634  if ARealOffset <= FOffsetAtStart then
635    exit;
636  ARealOffset := ARealOffset - FOffsetAtStart;
637  if ARealOffset > FWrappedExtraSumsCount then begin
638    if FWrappedExtraSumsCount > 0 then
639      Result := Result + FWrappedExtraSums[FWrappedExtraSumsCount - 1];
640  end
641  else
642  if ARealOffset > 0 then
643    Result := Result + FWrappedExtraSums[ARealOffset - 1];
644end;
645
646procedure TSynWordWrapLineMap.AddToInvalidList;
647begin
648  FAvlNode.AddToInvalidList;
649end;
650
651procedure TSynWordWrapLineMap.RemoveFromInvalidList(
652  AMode: TRemoveFromInvalidListMode);
653begin
654  FAvlNode.RemoveFromInvalidList(AMode);
655end;
656
657procedure TSynWordWrapLineMap.MaybeUpdateViewedSizeDifference;
658begin
659  if FirstInvalidLine < 0 then
660    FAvlNode.UpdateViewedSizeDifference;
661end;
662
663procedure TSynWordWrapLineMap.InvalidateLines(AFromOffset,
664  AToOffset: Integer);
665begin
666  assert((FOffsetAtStart = 0) or (FWrappedExtraSumsCount > 0), 'TSynWordWrapLineMap.InvalidateLines: (FOffsetAtStart = 0) or (FWrappedExtraSumsCount > 0)');
667  FInvalidLines.InvalidateLines(AFromOffset, AToOffset);
668  AddToInvalidList;
669end;
670
671procedure TSynWordWrapLineMap.ValidateLine(ALineOffset, AWrappCount: Integer);
672var
673  i, j: Integer;
674begin
675  assert(ALineOffset >= 0, 'TSynWordWrapLineMap.ValidateLine: ALineOffset >= 0');
676  assert((FOffsetAtStart = 0) or (FWrappedExtraSumsCount > 0), 'TSynWordWrapLineMap.ValidateLine: (FOffsetAtStart = 0) or (FWrappedExtraSumsCount > 0)');
677  FInvalidLines.ValidateLines(ALineOffset);
678
679  if ALineOffset - FOffsetAtStart < FDeferredAdjustFromOffs then begin
680    EndValidate;
681  end
682  else
683  if FDeferredAdjustFromOffs > 0 then begin
684    j := FDeferredAdjustFromVal;
685    //AWrappCount := AWrappCount + j;
686    for i := FDeferredAdjustFromOffs to Min(FWrappedExtraSumsCount - 1, ALineOffset - FOffsetAtStart) do
687      FWrappedExtraSums[i] := FWrappedExtraSums[i] + j;
688    FDeferredAdjustFromOffs := 0;
689  end;
690
691
692  if ALineOffset < FOffsetAtStart then begin
693    if AWrappCount <> 1 then begin
694      i := FirstInvalidLine;
695      if (i < 0) or (ALineOffset < i) then
696        i := ALineOffset;
697      j := FOffsetAtStart - i;
698      GrowCapacity(FWrappedExtraSumsCount + j);
699      WrapInfoMoveUpAndAdjustFromTo(
700        @FWrappedExtraSums[0],
701        @FWrappedExtraSums[j],
702        FWrappedExtraSumsCount,
703        AWrappCount - 1);
704      WrapInfoFillFrom(
705        @FWrappedExtraSums[i - ALineOffset],
706        j - (i - ALineOffset),
707        AWrappCount - 1);
708
709      assert(FOffsetAtStart >= j, 'TSynWordWrapLineMap.ValidateLine: FOffsetAtStart >= j');
710      FOffsetAtStart := FOffsetAtStart - j;
711      FWrappedExtraSumsCount := FWrappedExtraSumsCount + j;
712    end;
713  end
714
715  else
716  begin
717    ALineOffset := ALineOffset - FOffsetAtStart;
718    if ALineOffset >= FWrappedExtraSumsCount then begin
719      if AWrappCount > 1 then begin
720        i := FWrappedExtraSumsCount;
721  // TODO: check LastInvalidLine and SYN_WORD_WRAP_SPLIT_SIZE;
722        GrowCapacity(ALineOffset + 1);
723        FWrappedExtraSumsCount := ALineOffset + 1;
724        WrapInfoFillFrom(
725          @FWrappedExtraSums[i],
726          FWrappedExtraSumsCount - i,
727          GetWrappedExtraSumBefore(i));
728        FWrappedExtraSums[ALineOffset] := AWrappCount - 1; // last element, no AdjustFrom() needed
729        if ALineOffset > 0 then
730          FWrappedExtraSums[ALineOffset] := FWrappedExtraSums[ALineOffset] + FWrappedExtraSums[ALineOffset-1];
731      end;
732    end
733
734    else
735    if (AWrappCount = 1) and (ALineOffset = FWrappedExtraSumsCount - 1) then begin
736      if ALineOffset = 0 then begin
737        FWrappedExtraSumsCount := 0;
738        FOffsetAtStart := 0;
739      end
740      else begin
741// TODO: defer if there are further invalid lines after ALineOffset
742        i := ALineOffset - 1; // i = FWrappedExtraSumsCount - 2
743        j := FWrappedExtraSums[i];
744        if j = 0 then begin
745          FWrappedExtraSumsCount := 0;
746          FOffsetAtStart := 0;
747        end
748        else begin
749          dec(i);
750          while (i >= 0) and (j = FWrappedExtraSums[i]) do
751            dec(i);
752          FWrappedExtraSumsCount := i + 2;
753        end;
754      end;
755    end
756
757    else
758    begin
759      j := AWrappCount - 1 + GetWrappedExtraSumBefore(ALineOffset);
760      FDeferredAdjustFromOffs := ALineOffset + 1;
761      FDeferredAdjustFromVal  := FDeferredAdjustFromVal + j - FWrappedExtraSums[ALineOffset];
762      FWrappedExtraSums[ALineOffset] := j;
763    end;
764  end;
765end;
766
767procedure TSynWordWrapLineMap.EndValidate;
768var
769  v, i: Integer;
770begin
771  if FDeferredAdjustFromOffs > 0 then begin
772    v := FDeferredAdjustFromVal;
773    for i := FDeferredAdjustFromOffs to FWrappedExtraSumsCount - 1 do
774      FWrappedExtraSums[i] := FWrappedExtraSums[i] + v;
775  end;
776  FDeferredAdjustFromOffs := 0;
777  FDeferredAdjustFromVal  := 0;
778
779  if (FInvalidLines.Count = 0) then begin
780    FAvlNode.UpdateViewedSizeDifference;
781    RemoveFromInvalidList;
782    ShrinkCapacity;
783  end;
784end;
785
786procedure TSynWordWrapLineMap.MoveLinesAtStartTo(ADestPage: TSynWordWrapLineMap;
787  ASourceEndLine, ATargetStartLine: Integer);
788var
789  MinLineCount, TrgO1: Integer;
790  W: TSynWordWrapLineData;
791begin
792  assert(ATargetStartLine >= ADestPage.FWrappedExtraSumsCount + ADestPage.FOffsetAtStart, 'TSynWordWrapLineMap.InsertLinesFromPage: ATargetStartLine > ADestPage.FWrappedExtraSumsCount + ADestPage.FOffsetAtStart');
793
794  FInvalidLines.MoveRangeAtStartTo(ADestPage.FInvalidLines, ASourceEndLine, ATargetStartLine);
795
796  if (FWrappedExtraSumsCount = 0) then
797    exit;
798
799  ASourceEndLine := ASourceEndLine - Offset;
800  if ASourceEndLine < 0 then
801    exit;
802
803  if (ASourceEndLine > 0) and (ASourceEndLine < FWrappedExtraSumsCount) then begin
804    W := FWrappedExtraSums[ASourceEndLine];
805    while (ASourceEndLine > 0) and
806          (FWrappedExtraSums[ASourceEndLine - 1] = W)
807    do
808      dec(ASourceEndLine);
809  end;
810
811  if ADestPage.FWrappedExtraSumsCount = 0 then begin
812    // Target page is empty
813    ADestPage.FOffsetAtStart := ATargetStartLine + Offset;
814    assert(ADestPage.FOffsetAtStart >= 0, 'TSynWordWrapLineMap.MoveLinesAtStartTo: ADestPage.FOffsetAtStart >= 0');
815
816    MinLineCount := Min(ASourceEndLine+1, FWrappedExtraSumsCount);
817    ADestPage.GrowCapacity(MinLineCount);
818    WrapInfoCopyFromTo(
819      @FWrappedExtraSums[0],
820      @ADestPage.FWrappedExtraSums[0],
821      MinLineCount);
822    ADestPage.FWrappedExtraSumsCount := MinLineCount;
823    ADestPage.MaybeUpdateViewedSizeDifference;
824    exit;
825  end;
826
827  ATargetStartLine  := ATargetStartLine + Offset - ADestPage.FOffsetAtStart;
828  MinLineCount := Min(ASourceEndLine+1, FWrappedExtraSumsCount);
829  TrgO1 := ADestPage.GetWrappedExtraSumBefore(ADestPage.FWrappedExtraSumsCount);
830
831  ADestPage.GrowCapacity(ATargetStartLine + MinLineCount);
832  if ATargetStartLine > ADestPage.FWrappedExtraSumsCount then begin
833    WrapInfoFillFrom(
834      @ADestPage.FWrappedExtraSums[ADestPage.FWrappedExtraSumsCount],
835      ATargetStartLine - ADestPage.FWrappedExtraSumsCount,
836      TrgO1);
837  end;
838
839  WrapInfoCopyAndAdjustFromTo(
840    @FWrappedExtraSums[0],
841    @ADestPage.FWrappedExtraSums[ATargetStartLine],
842    MinLineCount,
843    TrgO1);
844  ADestPage.FWrappedExtraSumsCount := ATargetStartLine + MinLineCount;
845  ADestPage.MaybeUpdateViewedSizeDifference;
846end;
847
848procedure TSynWordWrapLineMap.MoveLinesAtEndTo(ADestPage: TSynWordWrapLineMap;
849  ASourceStartLine, ALineCount: Integer);
850var
851  OldOffset, SrcO1, SrcO2, MinLineCount: Integer;
852  W: TSynWordWrapLineData;
853begin
854  assert(ASourceStartLine-FOffsetAtStart+ALineCount >= FWrappedExtraSumsCount, 'TSynWordWrapLineMap.MoveLinesAtEndTo: ASourceStartLine+ACount >= FWrappedExtraSumsCount');
855
856  ADestPage.FInvalidLines.InsertLines(0, ALineCount);
857  FInvalidLines.MoveRangeAtEndTo(ADestPage.FInvalidLines, ASourceStartLine, -ASourceStartLine);
858
859  if (FWrappedExtraSumsCount = 0) then begin
860    if ADestPage.FWrappedExtraSumsCount = 0 then
861      exit;
862    ADestPage.FOffsetAtStart := ADestPage.FOffsetAtStart + ALineCount;
863    assert(ADestPage.FOffsetAtStart >= 0, 'TSynWordWrapLineMap.MoveLinesAtEndTo: ADestPage.FOffsetAtStart >= 0');
864    exit;
865  end;
866
867  OldOffset := ADestPage.FOffsetAtStart;
868  ADestPage.FOffsetAtStart := 0;
869  if ASourceStartLine < Offset then begin
870    ADestPage.FOffsetAtStart := Offset - ASourceStartLine;
871    ASourceStartLine := 0;
872    ALineCount := ALineCount - ADestPage.FOffsetAtStart;
873
874    if ALineCount = 0 then begin
875      ADestPage.FOffsetAtStart := ADestPage.FOffsetAtStart + OldOffset;
876      assert(ADestPage.FOffsetAtStart >= 0, 'TSynWordWrapLineMap.MoveLinesAtEndTo: ADestPage.FOffsetAtStart >= 0');
877      exit;
878    end;
879  end
880  else
881    ASourceStartLine := ASourceStartLine - Offset;
882
883
884  if (ASourceStartLine > 0) and (ASourceStartLine < FWrappedExtraSumsCount) then begin
885    SrcO2 := ASourceStartLine;
886    W := FWrappedExtraSums[ASourceStartLine - 1];
887    while (ASourceStartLine < FWrappedExtraSumsCount) and
888          (FWrappedExtraSums[ASourceStartLine] = W)
889    do
890      inc(ASourceStartLine);
891    ALineCount := ALineCount + SrcO2 - ASourceStartLine;
892    ADestPage.FOffsetAtStart := ADestPage.FOffsetAtStart + ASourceStartLine - SrcO2;
893    if ALineCount <= 0 then
894      exit;
895  end;
896
897
898  SrcO1 := GetWrappedExtraSumBefore(Min(ASourceStartLine,              FWrappedExtraSumsCount));
899  MinLineCount := Max(0, Min(ALineCount, FWrappedExtraSumsCount - ASourceStartLine));
900
901  if ADestPage.FWrappedExtraSumsCount = 0 then begin
902    // Moving to an empty page. Do NOT include any lines after FWrappedExtraSumsCount
903    if MinLineCount > 0 then begin;
904      ADestPage.GrowCapacity(MinLineCount);
905      WrapInfoCopyAndAdjustFromTo(
906        @FWrappedExtraSums[ASourceStartLine],
907        @ADestPage.FWrappedExtraSums[0],
908        MinLineCount,
909        -SrcO1);
910    end;
911    ADestPage.FWrappedExtraSumsCount := MinLineCount;
912    ADestPage.MaybeUpdateViewedSizeDifference;
913    exit;
914  end;
915
916  SrcO2 := GetWrappedExtraSumBefore(Min(ASourceStartLine + ALineCount, FWrappedExtraSumsCount));
917  ADestPage.GrowCapacity(ADestPage.FWrappedExtraSumsCount + ALineCount + OldOffset);
918  WrapInfoMoveUpAndAdjustFromTo(
919    @ADestPage.FWrappedExtraSums[0],
920    @ADestPage.FWrappedExtraSums[ALineCount + OldOffset],
921    ADestPage.FWrappedExtraSumsCount,
922    SrcO2 - SrcO1);
923  if MinLineCount > 0 then
924    WrapInfoCopyAndAdjustFromTo(
925      @FWrappedExtraSums[ASourceStartLine],
926      @ADestPage.FWrappedExtraSums[0],
927      MinLineCount,
928      -SrcO1);
929  WrapInfoFillFrom(
930    @ADestPage.FWrappedExtraSums[MinLineCount],
931    ALineCount - MinLineCount + OldOffset,
932    SrcO2 - SrcO1);
933  ADestPage.FWrappedExtraSumsCount := ADestPage.FWrappedExtraSumsCount + ALineCount + OldOffset;
934  ADestPage.MaybeUpdateViewedSizeDifference;
935end;
936
937procedure TSynWordWrapLineMap.InsertLinesAtOffset(ALineOffset,
938  ALineCount: Integer);
939var
940  j, k: Integer;
941begin
942  assert((FOffsetAtStart = 0) or (FWrappedExtraSumsCount > 0), 'TSynWordWrapLineMap.InsertLinesAtOffset: (FOffsetAtStart = 0) or (FWrappedExtraSumsCount > 0)');
943  if ALineCount = 0 then
944    exit;
945
946  FInvalidLines.InsertInvalidateLines(ALineOffset, ALineCount);
947  AddToInvalidList;
948
949  if ALineOffset <= FOffsetAtStart then begin
950    if FWrappedExtraSumsCount > 0 then
951      FOffsetAtStart := FOffsetAtStart + ALineCount;
952    assert(FOffsetAtStart >= 0, 'TSynWordWrapLineMap.MoveLinesAtEndTo: FOffsetAtStart >= 0');
953    exit;
954  end;
955  ALineOffset := ALineOffset - FOffsetAtStart;
956
957  if ALineOffset < FWrappedExtraSumsCount then begin
958    GrowCapacity(FWrappedExtraSumsCount + ALineCount);
959    move(FWrappedExtraSums[ALineOffset], FWrappedExtraSums[ALineOffset+ALineCount],
960      sizeof(FWrappedExtraSums[0]) * (FWrappedExtraSumsCount - ALineOffset));
961    FWrappedExtraSumsCount := FWrappedExtraSumsCount + ALineCount;
962    j := GetWrappedExtraSumBefore(ALineOffset);
963    for k := ALineOffset to ALineOffset + ALineCount - 1 do
964      FWrappedExtraSums[k] := j;
965    // TODO: only if NO invalid lines?
966    FAvlNode.UpdateViewedSizeDifference;
967  end;
968end;
969
970procedure TSynWordWrapLineMap.DeleteLinesAtOffset(ALineOffset,
971  ALineCount: Integer; ADoNotShrink: Boolean);
972var
973  i, j: Integer;
974begin
975  assert((FOffsetAtStart = 0) or (FWrappedExtraSumsCount > 0), 'TSynWordWrapLineMap.DeleteLinesAtOffset: (FOffsetAtStart = 0) or (FWrappedExtraSumsCount > 0)');
976  if ALineCount = 0 then
977    exit;
978  FInvalidLines.RemoveLines(ALineOffset, ALineCount);
979
980  if ALineOffset < FOffsetAtStart then begin
981    i := Min(ALineCount, FOffsetAtStart - ALineOffset);
982    FOffsetAtStart := FOffsetAtStart -i;
983    assert(FOffsetAtStart >= 0, 'TSynWordWrapLineMap.MoveLinesAtEndTo: FOffsetAtStart >= 0');
984    ALineCount := ALineCount - i;
985    if ALineCount = 0 then
986      exit;
987    ALineOffset := ALineOffset - FOffsetAtStart;
988  end
989  else
990    ALineOffset := ALineOffset - FOffsetAtStart;
991
992  if ALineOffset < FWrappedExtraSumsCount then begin
993    ALineCount := Min(ALineCount, FWrappedExtraSumsCount - ALineOffset);
994    WrapInfoCopyAndAdjustFromTo(
995      @FWrappedExtraSums[ALineOffset + ALineCount],
996      @FWrappedExtraSums[ALineOffset],
997      FWrappedExtraSumsCount - (ALineCount + ALineOffset),
998      GetWrappedExtraSumBefore(ALineOffset) - FWrappedExtraSums[ALineOffset + ALineCount - 1] );
999    FWrappedExtraSumsCount := FWrappedExtraSumsCount - ALineCount;
1000
1001    if (ALineOffset > 0) and (ALineOffset = FWrappedExtraSumsCount) then begin
1002      i := FWrappedExtraSumsCount - 1;
1003      j := FWrappedExtraSums[i];
1004      dec(i);
1005      while (i >= 0) and (j = FWrappedExtraSums[i]) do
1006        dec(i);
1007      inc(i);
1008
1009      if i < LastInvalidLine then
1010        i := LastInvalidLine;
1011
1012      inc(i);
1013      if i < FWrappedExtraSumsCount then begin
1014        FWrappedExtraSumsCount := i;
1015      end;
1016    end;
1017
1018    if FWrappedExtraSumsCount = 0 then
1019      FOffsetAtStart := 0;
1020  end;
1021
1022  if (FInvalidLines.Count = 0) then begin
1023    ShrinkCapacity;
1024    RemoveFromInvalidList;
1025  end;
1026
1027  // TODO: only if NO invalid lines?
1028  FAvlNode.UpdateViewedSizeDifference;
1029end;
1030
1031function TSynWordWrapLineMap.GetOffsetForWrap(AViewedOffset: IntIdx; out
1032  ASubOffset: IntIdx): IntIdx;
1033var
1034  l, h: Integer;
1035begin
1036  Result := 0;
1037
1038  ASubOffset := 0;
1039  if (FWrappedExtraSumsCount = 0) or (AViewedOffset <= FOffsetAtStart) then
1040    exit(AViewedOffset);
1041  AViewedOffset := AViewedOffset - FOffsetAtStart;
1042  if AViewedOffset >= FWrappedExtraSums[FWrappedExtraSumsCount - 1] + FWrappedExtraSumsCount then
1043    exit(AViewedOffset - FWrappedExtraSums[FWrappedExtraSumsCount - 1] + FOffsetAtStart);
1044
1045  l := 0;
1046  h := FWrappedExtraSumsCount - 1;
1047  Result := FWrappedExtraSumsCount div 2;
1048  while h > l do begin
1049    if FWrappedExtraSums[Result]+Result >= AViewedOffset then
1050      h := Result
1051    else
1052      l := Result + 1;
1053    Result := (h+l) div 2;
1054  end;
1055  assert(h=l, 'TSynWordWrapLineMap.GetOffsetForWrap: h=l');
1056
1057  if Result = 0 then
1058    ASubOffset := AViewedOffset
1059  else
1060    ASubOffset := AViewedOffset - FWrappedExtraSums[Result - 1] - Result;
1061  Result := Result + FOffsetAtStart;
1062end;
1063
1064constructor TSynWordWrapLineMap.Create;
1065begin
1066  FInvalidLines.Create;
1067  inherited Create;
1068end;
1069
1070destructor TSynWordWrapLineMap.Destroy;
1071begin
1072  FInvalidLines.Destroy;
1073  inherited Destroy;
1074end;
1075
1076function TSynWordWrapLineMap.GetDumpData: String;
1077begin
1078  Result := format('Offs=%3d  Rlcnt=%4d  VCnt=%4d  Diff=%3d   Inval=%3d..%3d // ', [FOffsetAtStart, RealCount, ViewedCount, ViewedRealCountDifference, GetFirstInvalidLine, GetLastInvalidLine]);
1079  if FWrappedExtraSumsCount = 1 then
1080    Result := Result + IntToStr(FWrappedExtraSums[0])
1081  else if FWrappedExtraSumsCount = 2 then
1082    Result := Result + IntToStr(FWrappedExtraSums[0]) + ', ' + IntToStr(FWrappedExtraSums[1])
1083  else if FWrappedExtraSumsCount > 2 then
1084    Result := Result + IntToStr(FWrappedExtraSums[0]) + ' / ' +
1085              IntToStr(FWrappedExtraSums[FWrappedExtraSumsCount-2]) + ', ' + IntToStr(FWrappedExtraSums[FWrappedExtraSumsCount-1])
1086end;
1087
1088function TSynWordWrapLineMap.GetFirstInvalidLine: Integer;
1089begin
1090  Result := FInvalidLines.FirstInvalidLine;
1091end;
1092
1093function TSynWordWrapLineMap.GetFirstInvalidEndLine: Integer;
1094begin
1095  Result := FInvalidLines.FirstInvalidEndLine;
1096end;
1097
1098function TSynWordWrapLineMap.GetLastInvalidLine: Integer;
1099begin
1100  Result := FInvalidLines.LastInvalidLine;
1101end;
1102
1103{ TSynWordWrapIndexPage }
1104
1105function TSynWordWrapIndexPage.GetFirstInvalidLine: Integer;
1106begin
1107  Result := FSynWordWrapLineMap.FirstInvalidLine;
1108end;
1109
1110function TSynWordWrapIndexPage.GetFirstInvalidEndLine: Integer;
1111begin
1112  Result := FSynWordWrapLineMap.FirstInvalidEndLine;
1113end;
1114
1115function TSynWordWrapIndexPage.GetLastInvalidLine: Integer;
1116begin
1117  Result := FSynWordWrapLineMap.LastInvalidLine;
1118end;
1119
1120function TSynWordWrapIndexPage.GetViewedRealCountDifference: Integer;
1121begin
1122  Result := FSynWordWrapLineMap.ViewedRealCountDifference;
1123end;
1124
1125procedure TSynWordWrapIndexPage.UpdateViewedSizeDifference;
1126begin
1127  UpdateNodeSize(FSynWordWrapLineMap.ViewedRealCountDifference);
1128end;
1129
1130procedure TSynWordWrapIndexPage.MaybeJoinWithSibling;
1131var
1132  dummy, NextLineOffs, PrevLineOffs, NextLineDist, PrevLineDist, c: Integer;
1133  NextPage, PrevPage: TSynEditLineMapPage;
1134begin
1135  if (FSynWordWrapLineMap.FirstInvalidLine < 0) and
1136     (RealCount <= Tree.PageJoinSize)
1137  then begin
1138    NextLineOffs := 0;
1139    dummy := 0;
1140    NextPage := Successor(NextLineOffs, dummy);
1141    if NextPage <> nil then begin
1142      assert(NextLineOffs > RealEndLine, 'TSynWordWrapIndexPage.MaybeJoinWithSibling: NextLineOffs > RealEndLine');
1143      NextLineDist := NextLineOffs - (RealEndLine+1) + NextPage.RealStartLine;
1144      c := NextPage.RealCount;
1145      if ( (c <> 0) and (NextLineDist > Tree.PageJoinDistance) ) or
1146         (c > Tree.PageJoinSize) or
1147         (NextPage.FirstInvalidLine >= 0) or
1148         (not NextPage.CanExtendStartTo(-NextLineOffs + RealStartLine, True))
1149      then
1150        NextLineOffs := 0;
1151    end
1152    else
1153      NextLineOffs := 0;
1154
1155    PrevLineOffs := 0;
1156    dummy := 0;
1157    PrevPage := Precessor(PrevLineOffs, dummy);
1158    if PrevPage <> nil then begin
1159      PrevLineOffs := -PrevLineOffs;
1160      assert(PrevLineOffs > PrevPage.RealEndLine, 'TSynWordWrapIndexPage.MaybeJoinWithSibling: -PrevLineOffs > PrevPage.RealEndLine');
1161      PrevLineDist := PrevLineOffs + RealStartLine - (PrevPage.RealEndLine+1);
1162      c := PrevPage.RealCount;
1163      if ( (c <> 0) and (PrevLineDist> Tree.PageJoinDistance) ) or
1164         (c > Tree.PageJoinSize) or
1165         (PrevPage.FirstInvalidLine >= 0) or
1166         (not PrevPage.CanExtendEndTo(PrevLineOffs + RealEndLine, True))
1167      then
1168        PrevLineOffs := 0;
1169    end
1170    else
1171      PrevLineOffs := 0;
1172
1173  if (NextLineOffs > 0) and
1174     ( (PrevLineOffs = 0) or (PrevLineDist > NextLineDist) )
1175  then begin
1176    MoveLinesAtEndTo(NextPage, 0, NextLineOffs);
1177    Tree.FreeNode(Self);
1178    NextPage.AdjustPosition(-NextLineOffs);
1179  end
1180  else
1181  if (PrevLineOffs > 0)
1182  then begin
1183    MoveLinesAtStartTo(PrevPage, RealEndLine, PrevLineOffs);
1184    Tree.FreeNode(Self);
1185  end;
1186
1187  end;
1188end;
1189
1190function TSynWordWrapIndexPage.GetWrappedOffsetFor(ARealOffset: IntIdx): IntIdx;
1191begin
1192  Result := FSynWordWrapLineMap.GetWrappedOffsetFor(ARealOffset);
1193end;
1194
1195function TSynWordWrapIndexPage.IsValid: boolean;
1196begin
1197  Result := FSynWordWrapLineMap.FInvalidLines.Count = 0;
1198end;
1199
1200function TSynWordWrapIndexPage.CanExtendStartTo(ALineOffs: Integer;
1201  AIgnoreJoinDist: Boolean): boolean;
1202begin
1203  Result := (RealEndLine - ALineOffs < Tree.PageSplitSize) and
1204            (AIgnoreJoinDist or (RealStartLine - ALineOffs < Tree.PageJoinDistance));
1205end;
1206
1207function TSynWordWrapIndexPage.CanExtendEndTo(ALineOffs: Integer;
1208  AIgnoreJoinDist: Boolean): boolean;
1209begin
1210  Result := (ALineOffs - RealStartLine < Tree.PageSplitSize) and
1211            (AIgnoreJoinDist or (ALineOffs - RealEndLine < Tree.PageJoinDistance));
1212end;
1213
1214function TSynWordWrapIndexPage.GetOffsetForWrap(AWrapOffset: IntIdx; out
1215  ASubOffset: IntIdx): IntIdx;
1216begin
1217  Result := FSynWordWrapLineMap.GetOffsetForWrap(AWrapOffset, ASubOffset);
1218end;
1219
1220function TSynWordWrapIndexPage.TextXYIdxToViewXYIdx(ATextXYIdx: TPhysPoint;
1221  ANodeStartLine: IntIdx): TPhysPoint;
1222var
1223  p: TPoint;
1224begin
1225  Result := inherited TextXYIdxToViewXYIdx(ATextXYIdx, ANodeStartLine);
1226
1227  p := FSynEditWrappedPlugin.TextXYToLineXY(Result);
1228  Result.y := ANodeStartLine + GetWrappedOffsetFor(Result.y - ANodeStartLine);
1229
1230  Result.x := p.x;
1231  Result.y := Result.y + p.y;
1232end;
1233
1234function TSynWordWrapIndexPage.ViewXYIdxToTextXYIdx(AViewXYIdx: TPhysPoint;
1235  ANodeStartLine: IntIdx): TPhysPoint;
1236var
1237  SubOffset: Integer;
1238begin
1239  Result := inherited ViewXYIdxToTextXYIdx(AViewXYIdx, ANodeStartLine);
1240  Result.y := ANodeStartLine + GetOffsetForWrap(Result.y - ANodeStartLine, SubOffset);
1241
1242  Result.x := FSynEditWrappedPlugin.LineXYToTextX(Result.y, Point(Result.x, SubOffset) );
1243end;
1244
1245procedure TSynWordWrapIndexPage.InvalidateLines(AFromOffset, AToOffset: Integer);
1246begin
1247  FSynWordWrapLineMap.InvalidateLines(AFromOffset, AToOffset);
1248end;
1249
1250function TSynWordWrapIndexPage.ExtendAndInvalidateLines(AFromLineIdx,
1251  AToLineIdx: TLineIdx): Boolean;
1252begin
1253  Result := True;
1254  if AFromLineIdx < 0 then begin
1255    AdjustPosition(AFromLineIdx);
1256    InsertLinesAtOffset(0, -AFromLineIdx);
1257    AToLineIdx := AToLineIdx - AFromLineIdx;
1258    AFromLineIdx := 0;
1259  end;
1260  InvalidateLines(AFromLineIdx, AToLineIdx);
1261end;
1262
1263constructor TSynWordWrapIndexPage.Create(ATree: TSynLineMapAVLTree);
1264begin
1265  FSynWordWrapLineMap := TSynWordWrapLineMap.Create;
1266  FSynWordWrapLineMap.FAvlNode := Self;
1267  inherited Create(ATree);
1268end;
1269
1270destructor TSynWordWrapIndexPage.Destroy;
1271begin
1272  FSynWordWrapLineMap.Destroy;
1273  inherited Destroy;
1274end;
1275
1276procedure TSynWordWrapIndexPage.DumpNode(ALine: Integer; AnIndent: Integer);
1277var
1278  s: String;
1279begin
1280  s:= StringOfChar(' ', AnIndent)+IntToHex(AnIndent,1);
1281  ALine := ALine + NodeLineOffset;
1282  if Left <> nil then Left.DumpNode(ALine, AnIndent+1);
1283  DebugLn('%-10s WRAP  LnOffs=%5d LINE=%5d   LSzSum==%4d LineCnt=%4d   Sz=%3d %s', [
1284    s,
1285    NodeLineOffset, ALine, LeftSizeSum, RealCount, FSize,
1286    FSynWordWrapLineMap.GetDumpData
1287  ]);
1288  if Right <> nil then Right.DumpNode(ALine, AnIndent+1);
1289end;
1290
1291procedure TSynWordWrapIndexPage.AdjustForLinesInserted(AStartLine,
1292  ALineCount: IntIdx; ABytePos: Integer);
1293var
1294  rs, re, LineOffs, dummy, Cnt: Integer;
1295  NextPage, PrevPage: TSynEditLineMapPage;
1296begin
1297  assert(AStartLine >= 0, 'TSynWordWrapIndexPage.AdjustForLinesInserted: AStartLine >= 0');
1298
1299  rs := RealStartLine;
1300  re := RealEndLine;
1301
1302  if (AStartLine <= rs) or (AStartLine > re) or
1303     (re - rs + 1 + ALineCount <= Tree.PageSplitSize)
1304  then begin
1305    InsertLinesAtOffset(AStartLine, ALineCount);
1306    if AStartLine = 0 then
1307      AdjustPosition(-ALineCount);
1308    exit;
1309  end;
1310
1311  (* This node was NOT moved by the callers AdjustForLinesInserted.
1312     This would only have happened if AStartLine = 0
1313  *)
1314  assert(RealCount > 0, 'TSynWordWrapIndexPage.InsertLines: RealCount > 0');
1315
1316  if AStartLine > rs + (re-rs) div 2 then begin
1317    // try split to next
1318    LineOffs := 0;
1319    dummy := 0;
1320    NextPage := Successor(LineOffs, dummy);
1321    if (NextPage<>nil) and NextPage.CanExtendStartTo(AStartLine + ALineCount - LineOffs) then begin
1322      //CurrentPage.SplitNodeToNext(NextPage, AStartLine);
1323      Cnt := LineOffs - (AStartLine + ALineCount);
1324      MoveLinesAtEndTo(NextPage, AStartLine, Cnt);
1325      NextPage.AdjustPosition(-Cnt);
1326      //CurrentPage.InsertLinesAtIndex(AStartLine, ACount);
1327      InsertLinesAtOffset(AStartLine, ALineCount);
1328      exit;
1329    end;
1330    LineOffs := 0;
1331    dummy := 0;
1332    PrevPage := Precessor(LineOffs, dummy);
1333    if (PrevPage<>nil) and PrevPage.CanExtendEndTo(AStartLine - 1  - LineOffs) then begin
1334      //CurrentPage.SplitNodeToPrev(PrevPage, AStartLine - 1);
1335      MoveLinesAtStartTo(PrevPage, AStartLine - 1, -LineOffs);
1336      AdjustPosition(AStartLine + ALineCount);
1337      //PrevPage.InsertLinesAtIndex(AStartLine, ACount);
1338      PrevPage.InsertLinesAtOffset(-LineOffs + AStartLine, ALineCount);
1339      exit;
1340    end;
1341    //CurrentPage.SplitNodeToNewNext(NextPage, AStartLine);
1342    NextPage := Tree.FindPageForLine(GetPosition + AStartLine + ALineCount, afmCreate).Page;
1343    MoveLinesAtEndTo(NextPage, AStartLine, Max(LastInvalidLine, RealCount));  // May be bigger than needed....
1344    //CurrentPage.InsertLinesAtIndex(AStartLine, ACount);
1345    InsertLinesAtOffset(AStartLine, ALineCount);
1346  end
1347  else begin
1348    // try split to prev
1349    LineOffs := 0;
1350    dummy := 0;
1351    PrevPage := Precessor(LineOffs, dummy);
1352
1353    if (PrevPage<>nil) and PrevPage.CanExtendEndTo(AStartLine - 1 - LineOffs) then begin
1354      //CurrentPage.SplitNodeToPrev(PrevPage, AStartLine - 1);
1355      MoveLinesAtStartTo(PrevPage, AStartLine - 1, -LineOffs);
1356      AdjustPosition(AStartLine + ALineCount);
1357      //PrevPage.InsertLinesAtIndex(AStartLine, ACount);
1358      PrevPage.InsertLinesAtOffset(-LineOffs + AStartLine, ALineCount);
1359      exit;
1360    end;
1361    LineOffs := 0;
1362    dummy := 0;
1363    NextPage := Successor(LineOffs, dummy);
1364    if (NextPage<>nil) and NextPage.CanExtendStartTo(AStartLine + ALineCount - LineOffs) then begin
1365      //CurrentPage.SplitNodeToNext(NextPage, AStartLine { + ALineCount});
1366      Cnt := LineOffs - (AStartLine + ALineCount);
1367      MoveLinesAtEndTo(NextPage, AStartLine, Cnt);
1368      NextPage.AdjustPosition(-Cnt);
1369      //CurrentPage.InsertLinesAtIndex(AStartLine, ACount);
1370      InsertLinesAtOffset(AStartLine, ALineCount);
1371      exit;
1372    end;
1373    //CurrentPage.SplitNodeToNewPrev(PrevPage, AStartLine - 1);
1374    dummy := GetPosition;
1375    AdjustPosition(AStartLine + ALineCount);
1376    PrevPage := Tree.FindPageForLine(dummy, afmCreate).Page;
1377    MoveLinesAtStartTo(PrevPage, AStartLine - 1, 0);
1378    //PrevPage.InsertLinesAtIndex(AStartLine, ACount);
1379    PrevPage.InsertLinesAtOffset(AStartLine, ALineCount);
1380  end;
1381
1382//  inherited AdjustForLinesInserted(AStartLine, ALineCount, ABytePos);
1383end;
1384
1385procedure TSynWordWrapIndexPage.AdjustForLinesDeleted(AStartLine,
1386  ALineCount: IntIdx; ABytePos: Integer);
1387begin
1388  DeleteLinesAtOffset(AStartLine, ALineCount);
1389  MaybeJoinWithSibling;
1390end;
1391
1392procedure TSynWordWrapIndexPage.InsertLinesAtOffset(ALineOffset,
1393  ALineCount: IntIdx);
1394begin
1395  FSynWordWrapLineMap.InsertLinesAtOffset(ALineOffset, ALineCount);
1396end;
1397
1398procedure TSynWordWrapIndexPage.DeleteLinesAtOffset(ALineOffset,
1399  ALineCount: IntIdx; ADoNotShrink: Boolean);
1400begin
1401  FSynWordWrapLineMap.DeleteLinesAtOffset(ALineOffset, ALineCount, ADoNotShrink);
1402end;
1403
1404procedure TSynWordWrapIndexPage.MoveLinesAtStartTo(
1405  ADestPage: TSynEditLineMapPage; ASourceEndLine, ATargetStartLine: Integer);
1406begin
1407// TODO: adestpage <> TSynWordWrapIndexPage
1408  assert(ADestPage is TSynWordWrapIndexPage, 'TSynWordWrapIndexPage.MoveLinesAtStartTo: ADestPage is TSynWordWrapIndexPage');
1409  FSynWordWrapLineMap.MoveLinesAtStartTo(TSynWordWrapIndexPage(ADestPage).FSynWordWrapLineMap, ASourceEndLine, ATargetStartLine);
1410  FSynWordWrapLineMap.DeleteLinesAtOffset(0, ASourceEndLine + 1);
1411end;
1412
1413procedure TSynWordWrapIndexPage.MoveLinesAtEndTo(
1414  ADestPage: TSynEditLineMapPage; ASourceStartLine, ACount: Integer);
1415begin
1416// TODO: adestpage <> TSynWordWrapIndexPage
1417  assert(ADestPage is TSynWordWrapIndexPage, 'TSynWordWrapIndexPage.MoveLinesAtEndTo: ADestPage is TSynWordWrapIndexPage');
1418  FSynWordWrapLineMap.MoveLinesAtEndTo(TSynWordWrapIndexPage(ADestPage).FSynWordWrapLineMap, ASourceStartLine, ACount);
1419  FSynWordWrapLineMap.DeleteLinesAtOffset(ASourceStartLine, ACount);
1420end;
1421
1422procedure TSynWordWrapIndexPage.EndValidate;
1423begin
1424  FSynWordWrapLineMap.EndValidate;
1425  MaybeJoinWithSibling;
1426end;
1427
1428procedure TSynWordWrapIndexPage.ValidateLine(ALineOffset, AWrappCount: Integer);
1429begin
1430  FSynWordWrapLineMap.ValidateLine(ALineOffset, AWrappCount);
1431end;
1432
1433function TSynWordWrapIndexPage.RealCount: Integer;
1434begin
1435  Result := FSynWordWrapLineMap.RealCount;
1436end;
1437
1438function TSynWordWrapIndexPage.RealStartLine: Integer;
1439begin
1440  Result := FSynWordWrapLineMap.Offset;
1441end;
1442
1443function TSynWordWrapIndexPage.RealEndLine: Integer;
1444begin
1445  Result := FSynWordWrapLineMap.Offset + FSynWordWrapLineMap.RealCount - 1;
1446end;
1447
1448{ TLazSynDisplayWordWrap }
1449
1450constructor TLazSynDisplayWordWrap.Create(AWrappedView: TSynEditLineMappingView;
1451  AWrapPlugin: TLazSynEditLineWrapPlugin);
1452begin
1453  FWrapPlugin := AWrapPlugin;
1454  inherited Create(AWrappedView);
1455end;
1456
1457procedure TLazSynDisplayWordWrap.SetHighlighterTokensLine(
1458  AWrappedLine: TLineIdx; out ARealLine: TLineIdx; out AStartBytePos,
1459  ALineByteLen: Integer);
1460var
1461  IsNext: Boolean;
1462  PrevSub: IntIdx;
1463  LineTxt: String;
1464  PWidth: TPhysicalCharWidths;
1465  PhysWidth, MaxW: Integer;
1466begin
1467  IsNext := (AWrappedLine = FCurWrappedLine + 1) and (FCurWrappedLine >= 0);
1468  PrevSub := FCurrentWrapSubline;
1469
1470  inherited SetHighlighterTokensLine(AWrappedLine, ARealLine, AStartBytePos, ALineByteLen);
1471
1472  LineTxt := FLineMappingView.NextLines.Strings[ARealLine];
1473  FLineMappingView.LogPhysConvertor.CurrentLine := ARealLine;
1474  PWidth := FLineMappingView.LogPhysConvertor.CurrentWidthsDirect;
1475  //PWidth  := FLineMappingView.GetPhysicalCharWidths(ARealLine);
1476  MaxW    := FWrapPlugin.WrapColumn;
1477  if IsNext and (FCurrentWrapSubline = PrevSub + 1) then begin
1478    FCurSubLineLogStartIdx := FCurSubLineNextLogStartIdx;
1479    FCurSubLineNextLogStartIdx := FWrapPlugin.CalculateNextBreak(PChar(LineTxt), FCurSubLineNextLogStartIdx,
1480      MaxW, PWidth, PhysWidth);
1481    FCurSubLinePhysStartIdx := FCurSubLinePhysStartIdx + PhysWidth;
1482  end
1483  else begin
1484    FWrapPlugin.GetSublineBounds(LineTxt, MaxW, PWidth, FCurrentWrapSubline,
1485      FCurSubLineLogStartIdx, FCurSubLineNextLogStartIdx, FCurSubLinePhysStartIdx, PhysWidth);
1486  end;
1487  AStartBytePos := AStartBytePos + FCurSubLineLogStartIdx;
1488  ALineByteLen := FCurSubLineNextLogStartIdx - FCurSubLineLogStartIdx;
1489
1490  FCurLineLogIdx := 0;
1491end;
1492
1493function TLazSynDisplayWordWrap.GetNextHighlighterToken(out
1494  ATokenInfo: TLazSynDisplayTokenInfo): Boolean;
1495var
1496  PreStart: Integer;
1497begin
1498  If FCurLineLogIdx >= FCurSubLineNextLogStartIdx then begin
1499    Result := False;
1500    exit;
1501  end;
1502
1503  repeat
1504    PreStart := FCurSubLineLogStartIdx - FCurLineLogIdx;
1505    Result := inherited GetNextHighlighterToken(ATokenInfo);
1506    if (not Result) or (ATokenInfo.TokenLength <= 0) then begin
1507      exit;
1508    end;
1509    FCurToken := ATokenInfo;
1510
1511    FCurLineLogIdx := FCurLineLogIdx + ATokenInfo.TokenLength;
1512  until FCurLineLogIdx > FCurSubLineLogStartIdx;
1513
1514  if PreStart > 0 then begin
1515    ATokenInfo.TokenStart := ATokenInfo.TokenStart + PreStart;
1516    ATokenInfo.TokenLength := ATokenInfo.TokenLength - PreStart;
1517    Result := ATokenInfo.TokenLength > 0;
1518    if not Result then
1519      exit;
1520  end;
1521
1522
1523  If FCurLineLogIdx > FCurSubLineNextLogStartIdx then begin
1524    ATokenInfo.TokenLength := ATokenInfo.TokenLength - (FCurLineLogIdx - FCurSubLineNextLogStartIdx);
1525    Result := ATokenInfo.TokenLength > 0;
1526  end;
1527end;
1528
1529{ TLazSynEditLineWrapPlugin }
1530
1531procedure TLazSynEditLineWrapPlugin.DoLinesChanged(Sender: TObject);
1532begin
1533  ValidateAll;
1534end;
1535
1536procedure TLazSynEditLineWrapPlugin.DoWidthChanged(Sender: TObject;
1537  Changes: TSynStatusChanges);
1538begin
1539  FLineMapView.KnownLengthOfLongestLine := WrapColumn;
1540  FLineMapView.InvalidateLines(0, FLineMapView.NextLines.Count);
1541end;
1542
1543function TLazSynEditLineWrapPlugin.GetWrapColumn: Integer;
1544begin
1545  Result := TSynEdit(Editor).CharsInWindow;
1546end;
1547
1548function TLazSynEditLineWrapPlugin.CreatePageMapNode(AMapTree: TSynLineMapAVLTree): TSynEditLineMapPage;
1549begin
1550  Result := TSynWordWrapIndexPage.Create(AMapTree);
1551  TSynWordWrapIndexPage(Result).FSynEditWrappedPlugin := Self;
1552end;
1553
1554procedure TLazSynEditLineWrapPlugin.SetEditor(const AValue: TCustomSynEdit);
1555begin
1556  if (Editor <> nil) and (AValue <> nil) then
1557    raise Exception.Create('Not allowed to change editor');
1558  inherited SetEditor(AValue);
1559end;
1560
1561function TLazSynEditLineWrapPlugin.CalculateNextBreak(ALine: PChar;
1562  ALogStartFrom: IntIdx; AMaxWidth: Integer;
1563  const PhysCharWidths: TPhysicalCharWidths; out APhysWidth: Integer): IntIdx;
1564const
1565  // todo, other break chars // utf8
1566  BREAKCHARS = [#9, #32, '.', ',', ':', ';', '=', '-', '+', '*', '/', '(', ')', '{', '}', '[', ']', '!', '<', '>'];
1567var
1568  PhysWidthPtr: PByte;
1569  CurCharPhysWidth: Cardinal;
1570  LastGoodPos: PChar;
1571begin
1572  if (ALine = nil) or (ALine^ = #0) then
1573    exit(0);
1574
1575  PhysWidthPtr := @PhysCharWidths[ALogStartFrom];
1576  APhysWidth := AMaxWidth;
1577  Result := ALogStartFrom;
1578  ALine := ALine + ALogStartFrom;
1579  LastGoodPos := ALine;
1580
1581  while ALine <> nil do begin
1582    if ALine^ in BREAKCHARS then
1583      while ALine^ in BREAKCHARS do begin
1584        CurCharPhysWidth := PhysWidthPtr^ and PCWMask;
1585        if CurCharPhysWidth <= AMaxWidth then begin
1586          inc(ALine);
1587          inc(PhysWidthPtr);
1588          inc(Result);
1589          dec(AMaxWidth, CurCharPhysWidth);
1590        end
1591        else begin
1592          ALine := nil; // break outer loop
1593          break;
1594        end;
1595      end
1596
1597    else begin
1598      CurCharPhysWidth := 0;
1599      LastGoodPos := ALine;
1600      while (ALine^ <> #0) and not (ALine^ in BREAKCHARS) do begin
1601        CurCharPhysWidth := CurCharPhysWidth + PhysWidthPtr^ and PCWMask;
1602        inc(ALine);
1603        inc(PhysWidthPtr);
1604      end;
1605
1606      if (CurCharPhysWidth > 0) and (CurCharPhysWidth <= AMaxWidth) then begin
1607        inc(Result, ALine-LastGoodPos);
1608        dec(AMaxWidth, CurCharPhysWidth);
1609      end
1610      else begin
1611        ALine := nil; // break outer loop
1612        break;
1613      end;
1614    end;
1615  end;
1616
1617  if Result = ALogStartFrom then begin
1618    PhysWidthPtr := @PhysCharWidths[0];
1619    ALine := LastGoodPos;
1620    while ALine^ <> #0 do begin
1621      CurCharPhysWidth := PhysWidthPtr^ and PCWMask;
1622      if (CurCharPhysWidth <= AMaxWidth) or (Result = ALogStartFrom) then begin
1623        inc(ALine);
1624        inc(PhysWidthPtr);
1625        inc(Result);
1626        dec(AMaxWidth, CurCharPhysWidth);
1627      end
1628      else
1629        break;
1630    end;
1631  end;
1632  APhysWidth := APhysWidth - AMaxWidth;
1633end;
1634
1635function TLazSynEditLineWrapPlugin.GetSublineCount(ALine: String;
1636  AMaxWidth: Integer; const APhysCharWidths: TPhysicalCharWidths): Integer;
1637var
1638  x, dummy: Integer;
1639begin
1640  Result := 1;
1641  if Length(ALine) = 0 then
1642    exit;
1643  x := CalculateNextBreak(PChar(ALine), 0, AMaxWidth, APhysCharWidths, dummy);
1644  while (x < Length(ALine)) do begin
1645    inc(Result);
1646    x := CalculateNextBreak(PChar(ALine), x, AMaxWidth, APhysCharWidths, dummy);
1647  end;
1648end;
1649
1650procedure TLazSynEditLineWrapPlugin.GetSublineBounds(ALine: String;
1651  AMaxWidth: Integer; const APhysCharWidths: TPhysicalCharWidths; ASubLine: Integer;
1652  out ALogStartX, ANextLogStartX, APhysStart: IntIdx; out APhysWidth: integer);
1653begin
1654  ALogStartX := 0;
1655  ANextLogStartX := 0;
1656  APhysStart := 0;
1657  if Length(ALine) = 0 then
1658    exit;
1659  ANextLogStartX := CalculateNextBreak(PChar(ALine), ALogStartX, AMaxWidth, APhysCharWidths, APhysWidth);
1660  while ASubLine > 0 do begin
1661    ALogStartX := ANextLogStartX;
1662    APhysStart := APhysStart + APhysWidth;
1663    ANextLogStartX := CalculateNextBreak(PChar(ALine), ALogStartX, AMaxWidth, APhysCharWidths, APhysWidth);
1664    dec(ASubLine);
1665  end;
1666end;
1667
1668function TLazSynEditLineWrapPlugin.GetSubLineFromX(ALine: String;
1669  AMaxWidth: Integer; const APhysCharWidths: TPhysicalCharWidths;
1670  var APhysXPos: Integer): integer;
1671var
1672  x, PhysWidth: Integer;
1673begin
1674  Result := 0;
1675  if Length(ALine) = 0 then
1676    exit;
1677  Result := -1;
1678  x := 0;
1679  APhysXPos := ToIdx(APhysXPos);
1680  while (x < Length(ALine)) do begin
1681    inc(Result);
1682    x := CalculateNextBreak(PChar(ALine), x, AMaxWidth, APhysCharWidths, PhysWidth);
1683    if x >= Length(ALine) then
1684      break;
1685    if (FCaretWrapPos = wcpBOL) and (PhysWidth = APhysXPos) and (x < Length(ALine))
1686    then begin
1687      inc(Result);
1688      APhysXPos := APhysXPos - PhysWidth;
1689      break;
1690    end;
1691    if PhysWidth >= APhysXPos then
1692      break;
1693    APhysXPos := APhysXPos - PhysWidth;
1694  end;
1695  APhysXPos := ToPos(APhysXPos);
1696end;
1697
1698procedure TLazSynEditLineWrapPlugin.GetWrapInfoForViewedXY(
1699  var AViewedXY: TPhysPoint; AFlags: TViewedXYInfoFlags;
1700  out AFirstViewedX: IntPos; ALogPhysConvertor: TSynLogicalPhysicalConvertor);
1701var
1702  SubLineOffset, YIdx: TLineIdx;
1703  LineTxt: String;
1704  PWidth: TPhysicalCharWidths;
1705  LogX, NextLogX, PhysX: IntIdx;
1706  PhysWidth: Integer;
1707begin
1708
1709  YIdx := FLineMapView.Tree.GetLineForForWrap(ToIdx(AViewedXY.y), SubLineOffset);
1710  YIdx := FLineMapView.NextLines.ViewToTextIndex(YIdx);
1711
1712  LineTxt := FLineMapView.Strings[YIdx];
1713  ALogPhysConvertor.CurrentLine := YIdx;
1714  PWidth  := ALogPhysConvertor.CurrentWidthsDirect;
1715
1716  GetSublineBounds(LineTxt, WrapColumn, PWidth, SubLineOffset, LogX, NextLogX, PhysX, PhysWidth);
1717
1718  case CaretWrapPos of
1719    wcpEOL: begin
1720        if (SubLineOffset > 0) and (AViewedXY.x <= 1) then
1721          AViewedXY.x := 2
1722        else
1723        if (NextLogX < length(LineTxt)) and (AViewedXY.x > ToPos(PhysWidth)) then
1724          AViewedXY.x := ToPos(PhysWidth);
1725        AFirstViewedX := 2;
1726      end;
1727    wcpBOL: begin
1728        if (NextLogX < length(LineTxt)) and (AViewedXY.x >= ToPos(PhysWidth)) then
1729          AViewedXY.x := ToPos(PhysWidth) - 1;
1730        AFirstViewedX := 1;
1731      end;
1732  end;
1733
1734  AViewedXY.y := ToPos(YIdx);
1735  AViewedXY.x := AViewedXY.x + PhysX;
1736end;
1737
1738function TLazSynEditLineWrapPlugin.TextXYToLineXY(ATextXY: TPhysPoint
1739  ): TPhysPoint;
1740begin
1741  FLineMapView.LogPhysConvertor.CurrentLine := ATextXY.y;
1742  Result.x := ATextXY.x;
1743  Result.y :=
1744    GetSubLineFromX(FLineMapView.NextLines.Strings[ATextXY.y],
1745      WrapColumn,
1746      FLineMapView.LogPhysConvertor.CurrentWidthsDirect,
1747      Result.x
1748    );
1749end;
1750
1751function TLazSynEditLineWrapPlugin.LineXYToTextX(ARealLine: IntPos;
1752  ALineXY: TPhysPoint): Integer;
1753var
1754  dummy, dummy2: IntIdx;
1755  dummy3: integer;
1756begin
1757  FLineMapView.LogPhysConvertor.CurrentLine := ARealLine;
1758  GetSublineBounds(FLineMapView.NextLines.Strings[ARealLine],
1759    WrapColumn,
1760    FLineMapView.LogPhysConvertor.CurrentWidthsDirect,
1761    ALineXY.y, dummy, dummy2, Result, dummy3
1762  );
1763  Result := Result + ALineXY.x;
1764end;
1765
1766function TLazSynEditLineWrapPlugin.CalculateWrapForLine(ALineIdx: IntIdx;
1767  AMaxWidth: integer): Integer;
1768begin
1769  FLineMapView.LogPhysConvertor.CurrentLine := ALineIdx;
1770  Result := GetSublineCount(FLineMapView.NextLines.Strings[ALineIdx], AMaxWidth,
1771    FLineMapView.LogPhysConvertor.CurrentWidthsDirect);
1772end;
1773
1774constructor TLazSynEditLineWrapPlugin.Create(AOwner: TComponent);
1775begin
1776  inherited Create(AOwner);
1777  FLineMapView := TSynEditLineMappingView(TSynEdit(Editor).TextViewsManager.SynTextViewByClass[TSynEditLineMappingView]);
1778  if FLineMapView = nil then begin
1779    FLineMapView := TSynEditLineMappingView.Create;
1780    TSynEdit(Editor).TextViewsManager.AddTextView(FLineMapView);
1781  end
1782  else
1783  if (not(FLineMapView.DisplayView is TLazSynDisplayLineMapping)) or
1784     (FLineMapView.PageMapCreator <> nil)
1785  then
1786    raise Exception.Create('Conflicting Plugin detected');
1787
1788  FLineMapView.SetDisplayView(TLazSynDisplayWordWrap.Create(FLineMapView, Self));
1789  FLineMapView.PageMapCreator := @CreatePageMapNode;
1790  FLineMapView.WrapInfoForViewedXYProc := @GetWrapInfoForViewedXY;
1791  FLineMapView.AddLinesChangedHandler(@DoLinesChanged);
1792  TSynEdit(Editor).RegisterStatusChangedHandler(@DoWidthChanged, [scCharsInWindow]);
1793  FLineMapView.KnownLengthOfLongestLine := WrapColumn;
1794  WrapAll;
1795end;
1796
1797procedure TLazSynEditLineWrapPlugin.WrapAll;
1798var
1799  c: Integer;
1800begin
1801  FLineMapView.Tree.Clear;
1802  c := FLineMapView.NextLines.Count;
1803  if c > 0 then
1804    FLineMapView.Tree.AdjustForLinesInserted(0, c, 0);
1805  ValidateAll;
1806end;
1807
1808procedure TLazSynEditLineWrapPlugin.ValidateAll;
1809var
1810  AMaxWidth, i, w: Integer;
1811  LowLine, HighLine: TLineIdx;
1812begin
1813if not FLineMapView.Tree.NeedsValidation then exit;
1814  AMaxWidth := WrapColumn;
1815
1816  while FLineMapView.Tree.NextBlockForValidation(LowLine, HighLine) do begin
1817    for i := LowLine to HighLine do begin
1818      w := CalculateWrapForLine(i, AMaxWidth);
1819      FLineMapView.Tree.ValidateLine(i, w);
1820    end;
1821  end;
1822  FLineMapView.Tree.EndValidate;
1823  FLineMapView.SendNotification(senrLineMappingChanged, FLineMapView, 0, 0);
1824  TSynEdit(Editor).Invalidate;
1825end;
1826
1827end.
1828
1829