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