1 {
2 /***************************************************************************
3 textstrings.pas
4 ---------------
5 Component Library Code
6
7 ***************************************************************************/
8
9 *****************************************************************************
10 This file is part of LazUtils.
11
12 See the file COPYING.modifiedLGPL.txt, included in this distribution,
13 for details about the license.
14 *****************************************************************************
15
16 TTextStrings is a TStrings descendent that is optimized for handling the
17 complete text as whole (instead of as line by line as in TStringList).
18
19 TCustomMemoStrings is a TStrings descendent which works around the behavior
20 of TMemo.Lines, which contains the text with wordwrap line endings, in order
21 to store the text in the LFM without those wordwrap line endings. See bug 30659
22 }
23 unit TextStrings;
24
25 {$mode objfpc}{$H+}
26
27 interface
28
29 uses
30 Classes, SysUtils,
31 // LazUtils
32 LazUtf8Classes, LazUtilsStrConsts;
33
34 type
35 { TTextStrings }
36
37 TTextLineRange = record
38 Line: string; // cached line as string
39 TheObject: TObject; // user data
40 StartPos: integer; // start of line in Text
41 EndPos: integer; // end of line in Text (= start of newline character(s))
42 end;
43 PTextLineRange = ^TTextLineRange;
44
45 TCustomMemoStrings = class(TStrings)
46 protected
47 procedure DoReadData(Reader: TReader); virtual;
48 procedure DoWriteData(Writer: TWriter); virtual;
49 procedure DefineProperties(Filer: TFiler); override;
50 end;
51
52 TTextStrings = class(TCustomMemoStrings)
53 private
54 FOnChange: TNotifyEvent;
55 FOnChanging: TNotifyEvent;
56 protected
57 FArraysValid: boolean;
58 FLineCount: integer;
59 FLineCapacity: integer;
60 FLineRanges: PTextLineRange;// array of TTextLineRange
61 FText: string;
62 FUpdateCount: integer;
63 FChangedWhileUpdate: boolean;
GetTextStrnull64 function GetTextStr: string; override;
65 procedure SetTextStr(const AValue: string); override;
66 procedure BuildArrays; virtual;
GetCountnull67 function GetCount: Integer; override;
68 procedure Changed; virtual;
69 procedure Changing; virtual;
Getnull70 function Get(Index: Integer): string; override;
71 procedure ClearArrays;
GetObjectnull72 function GetObject(Index: Integer): TObject; override;
73 procedure Put(Index: Integer; const S: string); override;
74 procedure PutObject(Index: Integer; AnObject: TObject); override;
GetLineLennull75 function GetLineLen(Index: integer; IncludeNewLineChars: boolean): integer; inline;
GetLineEndnull76 function GetLineEnd(Index: integer; IncludeNewLineChars: boolean): integer;
CountLineEndingsnull77 function CountLineEndings(const s: string): integer;
78 public
79 constructor Create;
80 destructor Destroy; override;
81 procedure Clear; override;
82 procedure SetText(TheText: PChar); override;
83 procedure Insert(Index: Integer; const S: string); override;
84 procedure Delete(Index: Integer); override;
85 procedure Exchange(Index1, Index2: Integer); override;
86 procedure Move(CurIndex, NewIndex: Integer); override;
87 procedure MakeTextBufferUnique;
88 procedure BeginUpdate;
89 procedure EndUpdate;
GetTextnull90 function GetText: PChar; override;
IndexOfnull91 function IndexOf(const S: string): Integer; override;
Addnull92 function Add(const S: string): Integer; override;
AddObjectnull93 function AddObject(const S: string; AObject: TObject): Integer; override;
94 procedure AddStrings(TheStrings: TStrings); override;
95 procedure LoadFromFile(const FileName: string); override;
96 procedure SaveToFile(const FileName: string); override;
97 public
98 property Text: string read FText write SetTextStr;
99 property OnChange: TNotifyEvent read FOnChange write FOnChange;
100 property OnChanging: TNotifyEvent read FOnChanging write FOnChanging;
101 end;
102
103 implementation
104
105 { TCustomMemoStrings }
106
107 procedure TCustomMemoStrings.DoReadData(Reader: TReader);
108 begin
109 Reader.ReadListBegin;
110 BeginUpdate;
111 try
112 Clear;
113 while not Reader.EndOfList do
114 Add(Reader.ReadString);
115 finally
116 EndUpdate;
117 end;
118 Reader.ReadListEnd;
119 end;
120
121 procedure TCustomMemoStrings.DoWriteData(Writer: TWriter);
122 var
123 i: Integer;
124 lStringsNoWordWrap: TStringList;
125 begin
126 lStringsNoWordWrap := TStringList.Create;
127 try
128 lStringsNoWordWrap.Text := Text;
129
130 Writer.WriteListBegin;
131 for i := 0 to lStringsNoWordWrap.Count - 1 do
132 Writer.WriteString(lStringsNoWordWrap.Strings[i]);
133 Writer.WriteListEnd;
134 finally
135 lStringsNoWordWrap.Free;
136 end;
137 end;
138
139 procedure TCustomMemoStrings.DefineProperties(Filer: TFiler);
140 var
141 HasData: Boolean;
142 begin
143 HasData := Count > 0;
144 Filer.DefineProperty('Strings', @DoReadData, @DoWriteData, HasData);
145 end;
146
147 { TTextStrings }
148
GetTextStrnull149 function TTextStrings.GetTextStr: string;
150 begin
151 Result:=FText;
152 end;
153
154 procedure TTextStrings.SetTextStr(const AValue: string);
155 begin
156 if FText=AValue then exit;
157 FText:=AValue;
158 FArraysValid:=false;
159 end;
160
161 procedure TTextStrings.BuildArrays;
162 var
163 p, line: integer;
164 l: Integer;
165 ArraySize: Integer;
166 begin
167 if FArraysValid then exit;
168 ClearArrays;
169 FArraysValid:=true;
170 // count line ends
171 FLineCount:=CountLineEndings(FText);
172 l:=length(FText);
173 if (FText<>'') and (not (FText[l] in [#10,#13])) then
174 inc(FLineCount);
175 FLineCapacity:=FLineCount;
176 // build line range list
177 if FLineCount>0 then begin
178 ArraySize:=FLineCount*SizeOf(TTextLineRange);
179 GetMem(FLineRanges,ArraySize);
180 FillByte(FLineRanges^,ArraySize,0);
181 p:=1;
182 line:=0;
183 FLineRanges[line].StartPos:=1;
184 FLineRanges[FLineCount-1].EndPos:=l+1;
185 while (p<=l) do begin
186 if (not (FText[p] in [#10,#13])) then begin
187 inc(p);
188 end else begin
189 // new line
190 FLineRanges[line].EndPos:=p;
191 inc(line);
192 inc(p);
193 if (p<=l) and (FText[p] in [#10,#13])
194 and (FText[p]<>FText[p-1]) then
195 inc(p);
196 if line<FLineCount then
197 FLineRanges[line].StartPos:=p;
198 end;
199 end;
200 end;
201 end;
202
GetCountnull203 function TTextStrings.GetCount: Integer;
204 begin
205 if not FArraysValid then BuildArrays;
206 Result:=FLineCount;
207 end;
208
209 procedure TTextStrings.Changed;
210 // called after text changed
211 begin
212 if (FUpdateCount>0) then begin
213 FChangedWhileUpdate:=true;
214 exit;
215 end;
216 if Assigned(FOnChange) then
217 FOnChange(Self);
218 end;
219
220 procedure TTextStrings.Changing;
221 begin
222 if FUpdateCount=0 then
223 if Assigned(FOnChanging) then
224 FOnChanging(Self);
225 end;
226
Getnull227 function TTextStrings.Get(Index: Integer): string;
228 var
229 Line: PTextLineRange;
230 begin
231 if not FArraysValid then BuildArrays;
232 if (Index<0) or (Index>=FLineCount) then
233 Error(lrsListIndexExceedsBounds, Index);
234 Line:=@FLineRanges[Index];
235 if (Line^.Line='')
236 and (Line^.StartPos<Line^.EndPos) then begin
237 Line^.Line:=copy(FText,Line^.StartPos,Line^.EndPos-Line^.StartPos);
238 end;
239 Result:=Line^.Line;
240 end;
241
242 procedure TTextStrings.ClearArrays;
243 var
244 i: Integer;
245 begin
246 FArraysValid:=false;
247 if FLineRanges<>nil then begin
248 for i:=0 to FLineCount-1 do
249 FLineRanges[i].Line:='';
250 FreeMem(FLineRanges);
251 FLineRanges:=nil;
252 end;
253 FLineCapacity:=0;
254 end;
255
TTextStrings.GetObjectnull256 function TTextStrings.GetObject(Index: Integer): TObject;
257 begin
258 if FArraysValid then begin
259 if (Index<0) or (Index>=FLineCount) then
260 Error(lrsListIndexExceedsBounds, Index);
261 Result:=FLineRanges[Index].TheObject;
262 end else
263 Result:=nil;
264 end;
265
266 procedure TTextStrings.Put(Index: Integer; const S: string);
267 var
268 OldLineLen: Integer;
269 NewLineLen: Integer;
270 Movement: Integer;
271 OldStartPos: LongInt;
272 OldEndPos: LongInt;
273 MoveLen: Integer;
274 i: Integer;
275 NewEndPos: Integer;
276 begin
277 if not FArraysValid then BuildArrays;
278 if (Index<0) or (Index>=FLineCount) then
279 Error(lrsListIndexExceedsBounds, Index);
280 OldStartPos:=FLineRanges[Index].StartPos;
281 OldEndPos:=FLineRanges[Index].EndPos;
282 NewLineLen:=length(s);
283 OldLineLen:=OldEndPos-OldStartPos;
284 Movement:=NewLineLen-OldLineLen;
285 NewEndPos:=OldEndPos+Movement;
286 // move text behind
287 MoveLen := Length(FText) - OldEndPos + 1;
288 if (Movement<>0) and (MoveLen>0) then
289 begin
290 if Movement > 0 then
291 SetLength(FText, Length(FText) + Movement);
292 System.Move(FText[OldEndPos], FText[NewEndPos], MoveLen);
293 if Movement < 0 then
294 SetLength(FText, Length(FText) + Movement);
295
296 for i := Index + 1 to FLineCount - 1 do
297 begin
298 inc(FLineRanges[i].StartPos, Movement);
299 inc(FLineRanges[i].EndPos, Movement);
300 end;
301 end;
302 FLineRanges[Index].EndPos:=NewEndPos;
303 // copy text
304 if NewLineLen>0 then
305 System.Move(S[1],FText[OldStartPos],NewLineLen);
306 FLineRanges[Index].Line:=S;
307 // check if arrays need rebuild
308 i:=NewLineLen;
309 while (i>0) and (not (S[i] in [#10,#13])) do dec(i);
310 if i>0 then begin
311 // S contains new line chars => rebuild needed
312 FArraysValid:=false;
313 end;
314 end;
315
316 procedure TTextStrings.PutObject(Index: Integer; AnObject: TObject);
317 begin
318 if not FArraysValid then BuildArrays;
319 if (Index<0) or (Index>=FLineCount) then
320 Error(lrsListIndexExceedsBounds, Index);
321 FLineRanges[Index].TheObject:=AnObject;
322 end;
323
GetLineLennull324 function TTextStrings.GetLineLen(Index: integer; IncludeNewLineChars: boolean
325 ): integer;
326 begin
327 Result:=GetLineEnd(Index,IncludeNewLineChars)-FLineRanges[Index].StartPos;
328 end;
329
TTextStrings.GetLineEndnull330 function TTextStrings.GetLineEnd(Index: integer; IncludeNewLineChars: boolean
331 ): integer;
332 begin
333 if not FArraysValid then BuildArrays;
334 if not IncludeNewLineChars then
335 Result:=FLineRanges[Index].EndPos
336 else if Index=FLineCount-1 then
337 Result:=length(FText)+1
338 else
339 Result:=FLineRanges[Index+1].StartPos;
340 end;
341
TTextStrings.CountLineEndingsnull342 function TTextStrings.CountLineEndings(const s: string): integer;
343 var
344 p: Integer;
345 l: Integer;
346 begin
347 Result:=0;
348 l:=length(s);
349 p:=1;
350 while p<=l do begin
351 if s[p] in [#10,#13] then
352 begin
353 inc(Result);
354 inc(p);
355 if (p<=l) and (s[p] in [#10,#13]) and (s[p-1]<>s[p]) then
356 inc(p);
357 end else begin
358 inc(p);
359 end;
360 end;
361 end;
362
363 constructor TTextStrings.Create;
364 begin
365 inherited Create;
366 CheckSpecialChars;
367 end;
368
369 destructor TTextStrings.Destroy;
370 begin
371 Clear;
372 inherited Destroy;
373 end;
374
375 procedure TTextStrings.Clear;
376 begin
377 ClearArrays;
378 FLineCount:=0;
379 FText:='';
380 end;
381
382 procedure TTextStrings.SetText(TheText: PChar);
383 begin
384 if FText=TheText then exit;
385 FText:=TheText;
386 FArraysValid:=false;
387 end;
388
389 procedure TTextStrings.Insert(Index: Integer; const S: string);
390
391 procedure RaiseOutOfBounds;
392 begin
393 raise EListError.Create('insert index '+IntToStr(Index)+' out of bounds '+IntToStr(FLineCount));
394 end;
395
396 var
397 NewStartPos: Integer;
398 NewLineCharCount: Integer;
399 NewLineLen: Integer;
400 i: Integer;
401 SEndsInNewLine: boolean;
402 Range: PTextLineRange;
403 NewCapacity: Integer;
404 begin
405 if not FArraysValid then BuildArrays;
406 NewLineLen:=length(S);
407 SEndsInNewLine:=(S<>'') and (S[NewLineLen] in [#10,#13]);
408 if Index<FLineCount then
409 begin
410 if Index<0 then
411 RaiseOutOfBounds;
412 NewStartPos:=FLineRanges[Index].StartPos;
413 end else begin
414 if Index>FLineCount then
415 RaiseOutOfBounds;
416 NewStartPos:=length(FText)+1;
417 end;
418 NewLineCharCount:=0;
419 if SEndsInNewLine then begin
420 inc(NewLineCharCount);
421 if (NewLineLen>1)
422 and (S[NewLineLen-1] in [#10,#13])
423 and (S[NewLineLen-1]<>S[NewLineLen]) then
424 inc(NewLineCharCount);
425 System.Insert(S,FText,NewStartPos);
426 end else begin
427 // append missing newline char
428 System.Insert(S+LineEnding,FText,NewStartPos);
429 NewLineCharCount:=length(LineEnding);
430 inc(NewLineLen,NewLineCharCount);
431 end;
432 // adjust arrays
433 if FLineCount=FLineCapacity then begin
434 if FLineCapacity<8 then
435 NewCapacity:=8
436 else
437 NewCapacity:=FLineCapacity shl 1;
438 ReAllocMem(FLineRanges,SizeOf(TTextLineRange)*NewCapacity);
439 FillByte(FLineRanges[FLineCapacity],SizeOf(TTextLineRange)*(NewCapacity-FLineCapacity),0);
440 FLineCapacity:=NewCapacity;
441 end;
442 if Index<FLineCount then begin
443 System.Move(FLineRanges[Index],FLineRanges[Index+1],
444 (FLineCount-Index)*SizeOf(TTextLineRange));
445 FillByte(FLineRanges[Index],SizeOf(TTextLineRange),0);
446 for i:=Index+1 to FLineCount do begin
447 inc(FLineRanges[i].StartPos,NewLineLen);
448 inc(FLineRanges[i].EndPos,NewLineLen);
449 end;
450 end;
451 inc(FLineCount);
452 Range:=@FLineRanges[Index];
453 Range^.Line:=S;
454 Range^.StartPos:=NewStartPos;
455 Range^.EndPos:=NewStartPos+NewLineLen-NewLineCharCount;
456 end;
457
458 procedure TTextStrings.Delete(Index: Integer);
459 var
460 OldLineLen: Integer;
461 OldStartPos: Integer;
462 i: Integer;
463 begin
464 if not FArraysValid then BuildArrays;
465 if (Index<0) or (Index>=FLineCount) then
466 Error(lrsListIndexExceedsBounds, Index);
467 // adjust text
468 OldLineLen:=GetLineLen(Index,true);
469 if OldLineLen>0 then begin
470 OldStartPos:=FLineRanges[Index].StartPos;
471 System.Delete(FText,OldStartPos,OldLineLen);
472 end;
473 // adjust arrays
474 dec(FLineCount);
475 FLineRanges[Index].Line:='';
476 if Index<FLineCount then begin
477 System.Move(FLineRanges[Index+1],FLineRanges[Index],
478 (FLineCount-Index)*SizeOf(TTextLineRange));
479 for i:=Index to FLineCount-1 do begin
480 dec(FLineRanges[i].StartPos,OldLineLen);
481 dec(FLineRanges[i].EndPos,OldLineLen);
482 end;
483 end;
484 // clear last element
485 FillByte(FLineRanges[FLineCount],SizeOf(TTextLineRange),0);
486 end;
487
488 procedure TTextStrings.Exchange(Index1, Index2: Integer);
489 var
490 LineLen1: Integer;
491 LineLen2: Integer;
492 buf: Pointer;
493 Dummy: Integer;
494 OldBetweenStart: Integer;
495 NewBetweenStart: Integer;
496 BetweenLength: Integer;
497 StartPos1: LongInt;
498 StartPos2: LongInt;
499 i: Integer;
500 Movement: Integer;
501 Obj: TObject;
502 LineShortLen1: LongInt;
503 LineShortLen2: LongInt;
504 Line1: PTextLineRange;
505 Line2: PTextLineRange;
506 begin
507 // check values
508 if Index1=Index2 then exit;
509 if Index1<0 then
510 Error(lrsListIndexExceedsBounds, Index1);
511 if Index2<0 then
512 Error(lrsListIndexExceedsBounds, Index2);
513 if not FArraysValid then BuildArrays;
514 if Index1>=FLineCount then
515 Error(lrsListIndexExceedsBounds, Index1);
516 if Index2>=FLineCount then
517 Error(lrsListIndexExceedsBounds, Index2);
518
519 // make sure Index1<Index2
520 if Index1>Index2 then begin
521 Dummy:=Index1;
522 Index1:=Index2;
523 Index2:=Dummy;
524 end;
525
526 Line1:=@FLineRanges[Index1];
527 Line2:=@FLineRanges[Index2];
528
529 // adjust text
530 MakeTextBufferUnique;
531
532 if (Index2=FLineCount-1) and (Line2^.EndPos>length(FText))
533 then begin
534 // The last line should be exchanged,
535 // but Text has no new line character(s) at the end
536 // => add LineEnding
537 FText:=FText+LineEnding;
538 end;
539
540 // get line lengths including new line chars
541 LineLen1:=GetLineLen(Index1,true);
542 LineLen2:=GetLineLen(Index2,true);
543 if (LineLen1<1) and (LineLen2<1) then exit;
544 LineShortLen1:=GetLineLen(Index1,false);
545 LineShortLen2:=GetLineLen(Index2,false);
546
547 // save the bigger line
548 StartPos1:=Line1^.StartPos;
549 StartPos2:=Line2^.StartPos;
550 if LineLen1>=LineLen2 then begin
551 GetMem(buf,LineLen1);
552 System.Move(FText[StartPos1],buf^,LineLen1);
553 end else begin
554 GetMem(buf,LineLen2);
555 System.Move(FText[StartPos2],buf^,LineLen2);
556 end;
557
558 // move text in between
559 OldBetweenStart:=StartPos1+LineLen1;
560 BetweenLength:=StartPos2-OldBetweenStart;
561 NewBetweenStart:=StartPos1+LineLen2;
562 Movement:=NewBetweenStart-OldBetweenStart;
563 if (BetweenLength>0) and (Movement<>0) then
564 System.Move(FText[OldBetweenStart],FText[NewBetweenStart],BetweenLength);
565
566 // move both lines
567 Line1^.Line:='';
568 Line2^.Line:='';
569 if LineLen1>=LineLen2 then begin
570 System.Move(FText[StartPos2],FText[StartPos1],LineLen2);
571 System.Move(buf^,FText[StartPos2+Movement],LineLen1);
572 end else begin
573 System.Move(FText[StartPos1],FText[StartPos2+Movement],LineLen1);
574 System.Move(buf^,FText[StartPos1],LineLen2);
575 end;
576
577 // adjust line ranges
578 if Movement<>0 then
579 begin
580 Line1^.EndPos:=Line1^.StartPos+LineShortLen2;
581 inc(Line2^.StartPos,Movement);
582 Line2^.EndPos:=Line2^.StartPos+LineShortLen1;
583 for i:=Index1+1 to Index2-1 do begin
584 inc(FLineRanges[i].StartPos,Movement);
585 inc(FLineRanges[i].EndPos,Movement);
586 end;
587 end;
588
589 // exchange TheObject
590 Obj:=Line1^.TheObject;
591 Line1^.TheObject:=Line2^.TheObject;
592 Line2^.TheObject:=Obj;
593
594 // clean up
595 FreeMem(buf);
596 end;
597
598 procedure TTextStrings.Move(CurIndex, NewIndex: Integer);
599 var
600 SrcPos1: LongInt;
601 SrcPos2: LongInt;
602 SrcPos3: LongInt;
603 LineStr: String;
604 LineLen: Integer;
605 i: LongInt;
606 Obj: TObject;
607 LineShortLen: LongInt;
608 Line: PTextLineRange;
609 begin
610 // check values
611 if CurIndex=NewIndex then exit;
612 if CurIndex<0 then
613 Error(lrsListIndexExceedsBounds, CurIndex);
614 if NewIndex<0 then
615 Error(lrsListIndexExceedsBounds, NewIndex);
616 if not FArraysValid then BuildArrays;
617 if CurIndex>=FLineCount then
618 Error(lrsListIndexExceedsBounds, CurIndex);
619 if NewIndex>=FLineCount then
620 Error(lrsListIndexExceedsBounds, NewIndex);
621
622 // adjust text
623 MakeTextBufferUnique;
624
625 if CurIndex<NewIndex then
626 begin
627 // move to higher index
628 if (NewIndex=FLineCount-1) and (FLineRanges[NewIndex].EndPos>length(FText))
629 then begin
630 // CurIndex should be moved to the end,
631 // but Text has no new line character(s) at the end
632 // => add LineEnding
633 FText:=FText+LineEnding;
634 end;
635 SrcPos1:=FLineRanges[CurIndex].StartPos;
636 SrcPos2:=FLineRanges[CurIndex+1].StartPos;
637 SrcPos3:=GetLineEnd(NewIndex,true);
638 // store current line with line end
639 LineLen:=SrcPos2-SrcPos1;
640 LineShortLen:=GetLineLen(CurIndex,false);
641 LineStr:=copy(FText,SrcPos1,LineLen);
642 Obj:=FLineRanges[CurIndex].TheObject;
643 // move lines -1
644 System.Move(FText[SrcPos2],FText[SrcPos1],SrcPos3-SrcPos2);
645 for i:=CurIndex+1 to NewIndex do begin
646 dec(FLineRanges[i].StartPos,LineLen);
647 dec(FLineRanges[i].EndPos,LineLen);
648 end;
649 System.Move(FLineRanges[CurIndex+1],FLineRanges[CurIndex],
650 SizeOf(TTextLineRange)*(NewIndex-CurIndex));
651 // put current line at new position
652 i:=SrcPos3-LineLen;
653 System.Move(LineStr[1],FText[i],LineLen);
654 Line:=@FLineRanges[NewIndex];
655 Line^.StartPos:=i;
656 Line^.EndPos:=i+LineShortLen;
657 Pointer(Line^.Line):=nil; // this will be updated on demand, see Get
658 Line^.TheObject:=Obj;
659 end else begin
660 // move to lower index
661 if (CurIndex=FLineCount-1) and (FLineRanges[CurIndex].EndPos>length(FText))
662 then begin
663 // CurIndex should be moved from the end,
664 // but Text has no new line character(s) at the end
665 // => add LineEnding
666 FText:=FText+LineEnding;
667 end;
668 SrcPos1:=FLineRanges[NewIndex].StartPos;
669 SrcPos2:=FLineRanges[CurIndex].StartPos;
670 SrcPos3:=GetLineEnd(CurIndex,true);
671 // store current line with line end
672 LineLen:=SrcPos3-SrcPos2;
673 LineShortLen:=GetLineLen(CurIndex,false);
674 LineStr:=copy(FText,SrcPos2,LineLen);
675 Obj:=FLineRanges[CurIndex].TheObject;
676 // move lines +1
677 System.Move(FText[SrcPos1],FText[SrcPos1+LineLen],SrcPos2-SrcPos1);
678 for i:=CurIndex-1 downto NewIndex do begin
679 inc(FLineRanges[i].StartPos,LineLen);
680 inc(FLineRanges[i].EndPos,LineLen);
681 end;
682 System.Move(FLineRanges[NewIndex],FLineRanges[NewIndex+1],
683 SizeOf(TTextLineRange)*(CurIndex-NewIndex));
684 // put current line at new position
685 System.Move(LineStr[1],FText[SrcPos1],LineLen);
686 Line:=@FLineRanges[NewIndex];
687 Line^.StartPos:=SrcPos1;
688 Line^.EndPos:=SrcPos1+LineShortLen;
689 Pointer(Line^.Line):=nil; // this will be updated on demand, see Get
690 Line^.TheObject:=Obj;
691 end;
692 end;
693
694 procedure TTextStrings.MakeTextBufferUnique;
695 begin
696 // make string unique (refcount=1) to be able to edit it directly
697 UniqueString(FText);
698 end;
699
700 procedure TTextStrings.BeginUpdate;
701 begin
702 inc(FUpdateCount);
703 end;
704
705 procedure TTextStrings.EndUpdate;
706
707 procedure RaiseUpdateCount;
708 begin
709 raise Exception.Create('TTextStrings.EndUpdate');
710 end;
711
712 begin
713 if FUpdateCount<=0 then RaiseUpdateCount;
714 dec(FUpdateCount);
715 if FUpdateCount=0 then begin
716 if FChangedWhileUpdate then
717 Changed;
718 end;
719 end;
720
TTextStrings.GetTextnull721 function TTextStrings.GetText: PChar;
722 begin
723 Result:=PChar(FText);
724 end;
725
IndexOfnull726 function TTextStrings.IndexOf(const S: string): Integer;
727 begin
728 Result:=inherited IndexOf(S);
729 end;
730
Addnull731 function TTextStrings.Add(const S: string): Integer;
732 begin
733 Result:=AddObject(S,nil);
734 end;
735
TTextStrings.AddObjectnull736 function TTextStrings.AddObject(const S: string; AObject: TObject): Integer;
737 var
738 e: String;
739 NewLineCount: Integer;
740 OldTxtLen: Integer;
741 p: Integer;
742 l: Integer;
743 begin
744 Result:=Count;
745 if (FText<>'') and (not (FText[length(FText)] in [#10,#13])) then
746 e:=LineEnding
747 else
748 e:='';
749 OldTxtLen:=length(FText);
750 FText:=Text+e+S+LineEnding;
751 if AObject<>nil then
752 BuildArrays;
753 if FArraysValid then
754 begin
755 // update FLineRanges
756 NewLineCount:=FLineCount+CountLineEndings(S)+1;
757 if NewLineCount>FLineCapacity then begin
758 FLineCapacity:=FLineCapacity*2+10;
759 if FLineCapacity<NewLineCount then
760 FLineCapacity:=NewLineCount;
761 ReAllocMem(FLineRanges,SizeOf(TTextLineRange)*FLineCapacity);
762 FillByte(FLineRanges[FLineCount],SizeOf(TTextLineRange)*(FLineCapacity-FLineCount),0);
763 end;
764 FLineRanges[FLineCount].TheObject:=AObject;
765 p:=OldTxtLen+length(e)+1;
766 l:=length(FText);
767 while FLineCount<NewLineCount do begin
768 FLineRanges[FLineCount].StartPos:=p;
769 while (p<=l) and (not (FText[p] in [#10,#13])) do
770 inc(p);
771 FLineRanges[FLineCount].EndPos:=p;
772 inc(p);
773 if (p<=l) and (FText[p] in [#10,#13]) and (FText[p]<>FText[p-1]) then
774 inc(p);
775 inc(FLineCount);
776 end;
777 end;
778 end;
779
780 procedure TTextStrings.AddStrings(TheStrings: TStrings);
781 var
782 s: String;
783 i: Integer;
784 AddEachLine: Boolean;
785 SrcTextStrings: TTextStrings;
786 SrcItem: PTextLineRange;
787 DstItem: PTextLineRange;
788 begin
789 if TheStrings.Count=0 then exit;
790 if FLineCount=0 then begin
791 if TheStrings is TTextStrings then begin
792 // copy Text, lineranges
793 SrcTextStrings:=TTextStrings(TheStrings);
794 FText:=SrcTextStrings.Text;
795 ClearArrays;
796 if not SrcTextStrings.FArraysValid then exit;
797 // copy line range list
798 FLineCount:=SrcTextStrings.Count;
799 FLineCapacity:=FLineCount;
800 FLineRanges:=AllocMem(FLineCount*SizeOf(TTextLineRange));
801 SrcItem:=SrcTextStrings.FLineRanges;
802 DstItem:=FLineRanges;
803 for i:=0 to FLineCount-1 do begin
804 DstItem^:=SrcItem^;
805 inc(SrcItem);
806 inc(DstItem);
807 end;
808 FArraysValid:=true;
809 exit;
810 end;
811 end;
812 AddEachLine:=false;
813 if FArraysValid then begin
814 for i:=0 to FLineCount-1 do
815 if FLineRanges[i].TheObject<>nil then begin
816 // old objects have to be kept
817 AddEachLine:=true;
818 break;
819 end;
820 end;
821 if not AddEachLine then begin
822 for i:=0 to TheStrings.Count-1 do begin
823 if TheStrings.Objects[i]<>nil then begin
824 // new objects have to be kept
825 AddEachLine:=true;
826 break;
827 end;
828 end;
829 end;
830 if AddEachLine then begin
831 // append line by line, this can be very slow
832 for i:=0 to TheStrings.Count-1 do
833 AddObject(TheStrings[i],TheStrings.Objects[i]);
834 end else begin
835 // append the whole text at once
836 // Beware: #10,#13 characters in lines are now converted to multiple lines
837 if (FText<>'') and (not (FText[length(FText)] in [#10,#13])) then
838 s:=LineEnding
839 else
840 s:='';
841 FArraysValid:=false;
842 FText:=FText+s+TheStrings.Text;
843 BuildArrays;
844 end;
845 end;
846
847 procedure TTextStrings.LoadFromFile(const FileName: string);
848 var
849 TheStream: TFileStreamUTF8;
850 begin
851 TheStream:=TFileStreamUtf8.Create(FileName,fmOpenRead or fmShareDenyWrite);
852 try
853 LoadFromStream(TheStream);
854 finally
855 TheStream.Free;
856 end;
857 end;
858
859 procedure TTextStrings.SaveToFile(const FileName: string);
860 var
861 TheStream: TFileStreamUTF8;
862 begin
863 TheStream:=TFileStreamUtf8.Create(FileName,fmCreate);
864 try
865 SaveToStream(TheStream);
866 finally
867 TheStream.Free;
868 end;
869 end;
870
871 end.
872