1 {
2  *****************************************************************************
3   See the file COPYING.modifiedLGPL.txt, included in this distribution,
4   for details about the license.
5  *****************************************************************************
6 
7   Author: Mattias Gaertner
8 
9   Abstract:
10     Defines a converter and tools to modify a Text. The Text can be a file,
11     a string or a TStrings.
12     Packages can register extra tools, which the IDE user can then be put
13     together to define a series of changes. For example several Find&Replace
14     tools can be added and then executed automatically.
15     For an extensive example, see the package in components/h2pas/.
16 }
17 unit IDETextConverter;
18 
19 {$mode objfpc}{$H+}
20 
21 interface
22 
23 uses
24   Classes, SysUtils, TypInfo,
25   // LCL
26   LCLProc, Controls, Forms,
27   // LazUtils
28   FileUtil, LazFileUtils, LazUTF8, LazUTF8Classes, LazLoggerBase,
29   // IdeIntf
30   SrcEditorIntf, PropEdits, ObjInspStrConsts;
31 
32 type
33   TCustomTextConverterTool = class;
34 
35   TTextConverterType = (
36     tctSource,
37     tctFile,
38     tctStrings,
39     tctCodeBuffer  // TCodeBuffer
40     );
41 
42   { TIDETextConverter
43     A component to hold a Text and tools to change the Text.
44     For example to do several find and replace operations on the text.
45     The Text can be a file, a string, TStrings or a TCodeBuffer.
46     The Text is converted on the fly, whenever someone reads/write one of the
47     formats.
48     The tools are decendants of TCustomTextConverterTool. }
49 
50   TIDETextConverter = class(TComponent)
51   private
52     FFilename: string;
53     FSource: string;
54     FCodeBuffer: Pointer;
55     FStrings: TStrings;
56     FCurrentType: TTextConverterType;
57     FFileIsTemporary: boolean;
58     FStringsIsTemporary: Boolean;
59     procedure CreateTempFilename;
GetCodeBuffernull60     function GetCodeBuffer: Pointer;
GetFilenamenull61     function GetFilename: string;
GetSourcenull62     function GetSource: string;
GetStringsnull63     function GetStrings: TStrings;
64     procedure ResetStrings;
65     procedure ResetFile;
66     procedure ConvertToFile(const NewFilename: string);
67     procedure SetCodeBuffer(const AValue: Pointer);
68     procedure SetFilename(const AValue: string);
69     procedure SetSource(const AValue: string);
70     procedure SetStrings(const AValue: TStrings);
71     procedure SetCurrentType(const AValue: TTextConverterType);
72     procedure SetFileIsTemporary(const AValue: boolean);
73     procedure SetStringsIsTemporary(const AValue: Boolean);
74   protected
GetTempFilenamenull75     function GetTempFilename: string; virtual;
76   public
77     constructor Create(TheOwner: TComponent); override;
78     destructor Destroy; override;
79     procedure Clear;
80     procedure CheckType(aTextType: TTextConverterType);
SupportsTypenull81     function SupportsType(aTextType: TTextConverterType): boolean; virtual;
Executenull82     function Execute(ToolList: TComponent; out ErrorTool: TComponent): TModalResult;// run the tools
LoadFromFilenull83     function LoadFromFile(const AFilename: string;
84                           UseIDECache: Boolean = true;
85                           UpdateFromDisk: Boolean = true;
86                           Revert: Boolean = false
87                           ): Boolean; virtual;
88     procedure InitWithFilename(const AFilename: string);
89     procedure InitWithSource(const ASource: string);
90     procedure InitWithStrings(const aStrings: TStrings);
91     procedure InitWithCodeBuffers(const aBuffer: Pointer);
92     property CurrentType: TTextConverterType read FCurrentType write SetCurrentType;
93     property Source: string read GetSource write SetSource;
94     property CodeBuffer: Pointer read GetCodeBuffer write SetCodeBuffer;
95     property Filename: string read GetFilename write SetFilename;
96     property Strings: TStrings read GetStrings write SetStrings;
97     property FileIsTemporary: boolean read FFileIsTemporary write SetFileIsTemporary;
98     property StringsIsTemporary: Boolean read FStringsIsTemporary write SetStringsIsTemporary;
99   end;
100 
101   { TCustomTextConverterTool
102     An abstract component to change a Text (TIDETextConverter). }
103 
104   TCustomTextConverterTool = class(TComponent)
105   private
106     FCaption: string;
107     FDescription: string;
108     FEnabled: boolean;
109     FErrorColumn: integer;
110     FErrorFilename: string;
111     FErrorLine: integer;
112     FErrorMsg: string;
113     FErrorTopLine: integer;
IsCaptionStorednull114     function IsCaptionStored: boolean;
115     procedure SetCaption(const AValue: string);
116     procedure SetDescription(const AValue: string);
117   public
ClassDescriptionnull118     class function ClassDescription: string; virtual; abstract;//the first line should be a short title
FirstLineOfClassDescriptionnull119     class function FirstLineOfClassDescription: string;
120     constructor Create(TheOwner: TComponent); override;
Executenull121     function Execute(aText: TIDETextConverter): TModalResult; virtual; abstract;
122     procedure Assign(Source: TPersistent); override;
123     procedure ClearError; virtual;
124     procedure AssignError(Source: TCustomTextConverterTool);
125     procedure AssignCodeToolBossError;
126     property ErrorMsg: string read FErrorMsg write FErrorMsg;
127     property ErrorLine: integer read FErrorLine write FErrorLine;
128     property ErrorColumn: integer read FErrorColumn write FErrorColumn;
129     property ErrorTopLine: integer read FErrorTopLine write FErrorTopLine;
130     property ErrorFilename: string read FErrorFilename write FErrorFilename;
131   published
132     property Caption: string read FCaption write SetCaption stored IsCaptionStored;
133     property Description: string read FDescription write SetDescription;
134     property Enabled: boolean read FEnabled write FEnabled default True;
135   end;
136   TCustomTextConverterToolClass = class of TCustomTextConverterTool;
137 
138   { TCustomTextReplaceTool
139     A tool to do a 'find and replace' in a text. }
140 
141   TTextReplaceToolOption = (
142     trtMatchCase,       // search case sensitive
143     trtWholeWord,       // search at word boundaries
144     trtRegExpr,         // use regular expressions for find and replace
145     trtMultiLine        // ignore type of line endings in pattern (e.g. #10 = #13#10)
146     //TODO trtSearchInReplacement,// when replaced, continue search at start of replacement, instead of end of replacement
147     //TODO trtReplaceUntilNotFound// restart replace until pattern not found
148     );
149   TTextReplaceToolOptions = set of TTextReplaceToolOption;
150 
151   TCustomTextReplaceTool = class(TCustomTextConverterTool)
152   private
153     FOptions: TTextReplaceToolOptions;
154     FReplaceWith: string;
155     FSearchFor: string;
156     procedure SetOptions(const AValue: TTextReplaceToolOptions);
157     procedure SetReplaceWith(const AValue: string);
158     procedure SetSearchFor(const AValue: string);
159   public
ClassDescriptionnull160     class function ClassDescription: string; override;
Executenull161     function Execute(aText: TIDETextConverter): TModalResult; override;
162     procedure Assign(Source: TPersistent); override;
163     property SearchFor: string read FSearchFor write SetSearchFor;
164     property ReplaceWith: string read FReplaceWith write SetReplaceWith;
165     property Options: TTextReplaceToolOptions read FOptions write SetOptions;
166   end;
167 
168   { TTextReplaceTool }
169 
170   TTextReplaceTool = class(TCustomTextReplaceTool)
171   published
172     property SearchFor;
173     property ReplaceWith;
174     property Options;
175   end;
176 
177   { TTextConverterToolClasses
178     A list to hold the registered TCustomTextConverterToolClass(es) }
179 
180   TTextConverterToolClasses = class
181   private
182     FItems: TFPList;// list of TCustomTextConverterToolClass
GetCountnull183     function GetCount: integer;
GetItemsnull184     function GetItems(Index: integer): TCustomTextConverterToolClass;
185   public
186     constructor Create;
187     destructor Destroy; override;
188     procedure RegisterClass(AClass: TCustomTextConverterToolClass);
189     procedure UnregisterClass(AClass: TCustomTextConverterToolClass);
FindByNamenull190     function FindByName(const aClassName: string): TCustomTextConverterToolClass;
FindByFirstLineOfClassDescriptionnull191     function FindByFirstLineOfClassDescription(const Line: string
192                                                ): TCustomTextConverterToolClass;
193     procedure FindClass(Reader: TReader; const aClassName: string;
194                         var ComponentClass: TComponentClass);
195     property Items[Index: integer]: TCustomTextConverterToolClass read GetItems; default;
196     property Count: integer read GetCount;
197 
SupportsTypenull198     function SupportsType(aTextType: TTextConverterType): boolean; virtual; abstract;
GetTempFilenamenull199     function GetTempFilename: string; virtual; abstract;
LoadFromFilenull200     function LoadFromFile(Converter: TIDETextConverter; const AFilename: string;
201                           UpdateFromDisk, Revert: Boolean): Boolean; virtual; abstract;
SaveCodeBufferToFilenull202     function SaveCodeBufferToFile(Converter: TIDETextConverter;
203                            const AFilename: string): Boolean; virtual; abstract;
GetCodeBufferSourcenull204     function GetCodeBufferSource(Converter: TIDETextConverter;
205                                 out Source: string): boolean; virtual; abstract;
CreateCodeBuffernull206     function CreateCodeBuffer(Converter: TIDETextConverter;
207                               const Filename, NewSource: string;
208                               out CodeBuffer: Pointer): boolean; virtual; abstract;
LoadCodeBufferFromFilenull209     function LoadCodeBufferFromFile(Converter: TIDETextConverter;
210                                  const Filename: string;
211                                  UpdateFromDisk, Revert: Boolean;
212                                  out CodeBuffer: Pointer): boolean; virtual; abstract;
213     procedure AssignCodeToolBossError(Target: TCustomTextConverterTool); virtual;
214   end;
215 
216 var
217   TextConverterToolClasses: TTextConverterToolClasses = nil;// set by the IDE
218 
219 procedure ClearTextConverterToolList(List: TComponent);
220 procedure CopyTextConverterToolList(Src, Dest: TComponent;
221                                     ClearDest: boolean = true);
222 
223 procedure MakeToolNameUnique(List: TComponent;
224                              NewTool: TCustomTextConverterTool);
225 procedure MakeToolCaptionUnique(List: TComponent;
226                                 NewTool: TCustomTextConverterTool);
227 procedure MakeToolCaptionAndNameUnique(List: TComponent;
228                                        NewTool: TCustomTextConverterTool);
AddNewTextConverterToolnull229 function AddNewTextConverterTool(List: TComponent;
230              NewClass: TCustomTextConverterToolClass): TCustomTextConverterTool;
231 
232 
233 implementation
234 
235 
236 procedure MakeToolNameUnique(List: TComponent;
237   NewTool: TCustomTextConverterTool);
238 var
239   NewName: String;
240 
241   procedure MakeValidIdentifier;
242   var
243     i: Integer;
244   begin
245     for i:=length(NewName) downto 1 do
246       if not (NewName[i] in ['0'..'9','_','a'..'z','A'..'Z']) then
247         System.Delete(NewName,i,1);
248     if (NewName<>'') and (NewName[1] in ['0'..'9']) then
249       NewName:='_'+NewName;
250   end;
251 
NameIsUniquenull252   function NameIsUnique: Boolean;
253   var
254     i: Integer;
255     CurTool: TCustomTextConverterTool;
256   begin
257     MakeValidIdentifier;
258     if NewName='' then exit(false);
259     for i:=0 to List.ComponentCount-1 do begin
260       CurTool:=TCustomTextConverterTool(List.Components[i]);
261       if CurTool=NewTool then continue;
262       if CompareText(CurTool.Name,NewName)=0 then exit(false);
263     end;
264     Result:=true;
265     NewTool.Name:=NewName;
266   end;
267 
268 begin
269   NewName:=NewTool.Name;
270   if NameIsUnique then exit;
271   NewName:=NewTool.FirstLineOfClassDescription;
272   if NewName='' then NewName:=NewTool.ClassName;
273   while not NameIsUnique do
274     NewName:=CreateNextIdentifier(NewName);
275 end;
276 
277 procedure MakeToolCaptionUnique(List: TComponent;
278   NewTool: TCustomTextConverterTool);
279 var
280   NewCaption: String;
281 
CaptionIsUniquenull282   function CaptionIsUnique: Boolean;
283   var
284     i: Integer;
285     CurTool: TCustomTextConverterTool;
286   begin
287     if NewCaption='' then exit(false);
288     for i:=0 to List.ComponentCount-1 do begin
289       CurTool:=TCustomTextConverterTool(List.Components[i]);
290       if CurTool=NewTool then continue;
291       if CompareText(CurTool.Caption,NewCaption)=0 then exit(false);
292     end;
293     Result:=true;
294     NewTool.Caption:=NewCaption;
295   end;
296 
297 begin
298   NewCaption:=NewTool.Caption;
299   if CaptionIsUnique then exit;
300   NewCaption:=NewTool.FirstLineOfClassDescription;
301   if NewCaption='' then NewCaption:=NewTool.ClassName;
302   while not CaptionIsUnique do
303     NewCaption:=CreateNextIdentifier(NewCaption);
304 end;
305 
306 procedure MakeToolCaptionAndNameUnique(List: TComponent;
307   NewTool: TCustomTextConverterTool);
308 begin
309   MakeToolNameUnique(List,NewTool);
310   MakeToolCaptionUnique(List,NewTool);
311 end;
312 
AddNewTextConverterToolnull313 function AddNewTextConverterTool(List: TComponent;
314   NewClass: TCustomTextConverterToolClass): TCustomTextConverterTool;
315 begin
316   Result:=NewClass.Create(List);
317   MakeToolCaptionAndNameUnique(List,Result);
318   if Result.Caption='' then RaiseGDBException('');
319 end;
320 
321 procedure ClearTextConverterToolList(List: TComponent);
322 begin
323   if List=nil then exit;
324   while List.ComponentCount>0 do
325     List.Components[List.ComponentCount-1].Free;
326 end;
327 
328 procedure CopyTextConverterToolList(Src, Dest: TComponent; ClearDest: boolean);
329 var
330   i: Integer;
331   SrcTool: TCustomTextConverterTool;
332   NewTool: TCustomTextConverterTool;
333 begin
334   if ClearDest then
335     ClearTextConverterToolList(Dest);
336   for i:=0 to Src.ComponentCount-1 do begin
337     SrcTool:=Src.Components[i] as TCustomTextConverterTool;
338     NewTool:=TCustomTextConverterToolClass(SrcTool.ClassType).Create(Dest);
339     NewTool.Assign(SrcTool);
340     NewTool.Name:=SrcTool.Name;
341   end;
342 end;
343 
344 { TIDETextConverter }
345 
346 procedure TIDETextConverter.SetFilename(const AValue: string);
347 begin
348   ConvertToFile(AValue);
349 end;
350 
TIDETextConverter.GetFilenamenull351 function TIDETextConverter.GetFilename: string;
352 begin
353   CurrentType:=tctFile;
354   Result:=FFilename;
355 end;
356 
TIDETextConverter.GetSourcenull357 function TIDETextConverter.GetSource: string;
358 begin
359   CurrentType:=tctSource;
360   Result:=FSource;
361 end;
362 
TIDETextConverter.GetStringsnull363 function TIDETextConverter.GetStrings: TStrings;
364 begin
365   CurrentType:=tctStrings;
366   Result:=FStrings;
367 end;
368 
369 procedure TIDETextConverter.ResetStrings;
370 begin
371   if StringsIsTemporary then
372     FStrings.Free;
373   FStrings:=nil;
374   FStringsIsTemporary:=false;
375 end;
376 
377 procedure TIDETextConverter.ResetFile;
378 begin
379   if FileIsTemporary then begin
380     DeleteFileUTF8(FFilename);
381     // do not change FFileIsTemporary, so that File > Source > File sequences
382     // keep the file temporary.
383   end;
384 end;
385 
386 procedure TIDETextConverter.SetSource(const AValue: string);
387 begin
388   FCurrentType:=tctSource;
389   ResetStrings;
390   ResetFile;
391   FSource:=AValue;
392 end;
393 
394 procedure TIDETextConverter.SetStrings(const AValue: TStrings);
395 begin
396   FCurrentType:=tctStrings;
397   ResetFile;
398   ResetStrings;
399   FStrings:=AValue;
400 end;
401 
402 procedure TIDETextConverter.SetCurrentType(const AValue: TTextConverterType);
403 var
404   fs: TFileStream;
405 begin
406   if FCurrentType=AValue then exit;
407   CheckType(AValue);
408   //DebugLn(['TIDETextConverter.SetCurrentType ',ord(FCurrentType),' ',ord(AValue)]);
409   case AValue of
410   tctSource:
411     // convert to Source
412     begin
413       FSource:='';
414       case FCurrentType of
415       tctStrings:
416         if FStrings<>nil then begin
417           FSource:=FStrings.Text;
418           ResetStrings;
419         end;
420       tctFile:
421         if FileExistsUTF8(FFilename) then begin
422           fs:=TFileStreamUTF8.Create(FFilename,fmOpenRead);
423           try
424             SetLength(FSource,fs.Size);
425             fs.Read(FSource[1],length(FSource));
426           finally
427             fs.Free;
428           end;
429           ResetFile;
430         end;
431       tctCodeBuffer:
432         begin
433           TextConverterToolClasses.GetCodeBufferSource(Self,FSource);
434           FCodeBuffer:=nil;
435         end;
436       end;
437     end;
438 
439   tctStrings:
440     // convert to TStrings
441     begin
442       if FStrings<>nil then
443         RaiseGDBException('TTextConverterText.SetCurrentType FStrings<>nil');
444       FStrings:=TStringList.Create;
445       fStringsIsTemporary:=true;
446       case FCurrentType of
447       tctSource:
448         begin
449           FStrings.Text:=FSource;
450           FSource:='';
451         end;
452       tctFile:
453         if FileExistsUTF8(FFilename) then begin
454           if FStrings is TStringListUTF8 then
455             FStrings.LoadFromFile(FFilename)
456           else
457             FStrings.LoadFromFile(UTF8ToSys(FFilename));
458           ResetFile;
459         end;
460       tctCodeBuffer:
461         begin
462           TextConverterToolClasses.GetCodeBufferSource(Self,FSource);
463           FStrings.Text:=FSource;
464           FSource:='';
465           FCodeBuffer:=nil;
466         end;
467       end;
468     end;
469 
470   tctFile:
471     // convert to File
472     begin
473       // keep old Filename, so that a Filename, Source, Filename combination
474       // uses the same Filename
475       if FFilename='' then
476         CreateTempFilename;
477       case FCurrentType of
478       tctSource:
479         begin
480           fs:=TFileStreamUTF8.Create(FFilename,fmCreate);
481           try
482             if FSource<>'' then begin
483               fs.Write(FSource[1],length(FSource));
484               FSource:='';
485             end;
486           finally
487             fs.Free;
488           end;
489         end;
490       tctStrings:
491         if FStrings<>nil then begin
492           if FStrings is TStringListUTF8 then
493             FStrings.LoadFromFile(FFilename)
494           else
495             FStrings.LoadFromFile(UTF8ToSys(FFilename));
496           ResetStrings;
497         end;
498       tctCodeBuffer:
499         begin
500           TextConverterToolClasses.SaveCodeBufferToFile(Self,FFilename);
501           FCodeBuffer:=nil;
502         end;
503       end;
504     end;
505 
506   tctCodeBuffer:
507     // convert to CodeBuffer
508     begin
509       // keep old Filename, so that a Filename, Source, Filename combination
510       // uses the same Filename
511       if FFilename='' then
512         CreateTempFilename;
513       case FCurrentType of
514       tctSource:
515         begin
516           TextConverterToolClasses.CreateCodeBuffer(Self,FFilename,FSource,
517                                                     FCodeBuffer);
518           FSource:='';
519         end;
520       tctStrings:
521         begin
522           TextConverterToolClasses.CreateCodeBuffer(Self,FFilename,
523                                                     FStrings.Text,FCodeBuffer);
524           ResetStrings;
525         end;
526       tctFile:
527         begin
528           TextConverterToolClasses.LoadCodeBufferFromFile(Self,FFilename,
529                                                          true,true,FCodeBuffer);
530           ResetFile;
531         end;
532       end;
533     end;
534   end;
535   FCurrentType:=AValue;
536 end;
537 
538 procedure TIDETextConverter.SetFileIsTemporary(const AValue: boolean);
539 begin
540   if FFileIsTemporary=AValue then exit;
541   FFileIsTemporary:=AValue;
542 end;
543 
544 procedure TIDETextConverter.ConvertToFile(const NewFilename: string);
545 var
546   fs: TFileStream;
547   TrimmedFilename: String;
548 begin
549   TrimmedFilename:=TrimFilename(NewFilename);
550   case CurrentType of
551   tctFile:
552     if (FFilename<>'') and (FFilename<>TrimmedFilename)
553     and (FileExistsUTF8(FFilename)) then
554       RenameFileUTF8(FFilename,TrimmedFilename);
555   tctSource:
556     begin
557       fs:=TFileStreamUTF8.Create(TrimmedFilename,fmCreate);
558       try
559         if FSource<>'' then
560           fs.Write(FSource[1],length(FSource));
561       finally
562         fs.Free;
563       end;
564     end;
565   tctStrings:
566     begin
567       if FStrings is TStringListUTF8 then
568         FStrings.SaveToFile(TrimmedFilename)
569       else
570         FStrings.SaveToFile(UTF8ToSys(TrimmedFilename));
571       ResetStrings;
572     end;
573   tctCodeBuffer:
574     begin
575       TextConverterToolClasses.SaveCodeBufferToFile(Self,NewFilename);
576       FCodeBuffer:=nil;
577     end;
578   end;
579   FCurrentType:=tctFile;
580   FFilename:=TrimmedFilename;
581 end;
582 
583 procedure TIDETextConverter.SetCodeBuffer(const AValue: Pointer);
584 begin
585   CheckType(tctCodeBuffer);
586   FCurrentType:=tctCodeBuffer;
587   ResetStrings;
588   FCodeBuffer:=AValue;
589 end;
590 
591 procedure TIDETextConverter.CreateTempFilename;
592 begin
593   FFilename:=GetTempFilename;
594   FFileIsTemporary:=true;
595 end;
596 
TIDETextConverter.GetCodeBuffernull597 function TIDETextConverter.GetCodeBuffer: Pointer;
598 begin
599   CurrentType:=tctCodeBuffer;
600   Result:=FCodeBuffer;
601 end;
602 
603 procedure TIDETextConverter.SetStringsIsTemporary(const AValue: Boolean);
604 begin
605   if FStringsIsTemporary=AValue then exit;
606   FStringsIsTemporary:=AValue;
607 end;
608 
GetTempFilenamenull609 function TIDETextConverter.GetTempFilename: string;
610 begin
611   if TextConverterToolClasses<>nil then
612     Result:=TextConverterToolClasses.GetTempFilename
613   else
614     Result:='';
615   if Result='' then
616     Result:='temp.txt';
617 end;
618 
619 constructor TIDETextConverter.Create(TheOwner: TComponent);
620 begin
621   inherited Create(TheOwner);
622   FCurrentType:=tctSource;
623 end;
624 
625 destructor TIDETextConverter.Destroy;
626 begin
627   ResetFile;
628   ResetStrings;
629   inherited Destroy;
630 end;
631 
632 procedure TIDETextConverter.Clear;
633 begin
634   FFilename:='';
635   FSource:='';
636   FCodeBuffer:=nil;
637   ResetStrings;
638   FCurrentType:=tctSource;
639 end;
640 
641 procedure TIDETextConverter.CheckType(aTextType: TTextConverterType);
642 
643   procedure RaiseNotSupported;
644   begin
645     raise Exception.Create('TIDETextConverter.CheckType:'
646       +' type not supported '+GetEnumName(TypeInfo(TTextConverterType),ord(aTextType)));
647   end;
648 
649 begin
650   if not SupportsType(aTextType) then RaiseNotSupported;
651 end;
652 
TIDETextConverter.SupportsTypenull653 function TIDETextConverter.SupportsType(aTextType: TTextConverterType
654   ): boolean;
655 begin
656   Result:=(aTextType in [tctSource,tctFile,tctStrings])
657       or ((TextConverterToolClasses<>nil)
658            and (TextConverterToolClasses.SupportsType(aTextType)));
659 end;
660 
TIDETextConverter.Executenull661 function TIDETextConverter.Execute(ToolList: TComponent;
662   out ErrorTool: TComponent): TModalResult;
663 var
664   i: Integer;
665   Tool: TCustomTextConverterTool;
666   CurResult: TModalResult;
667 begin
668   Result:=mrOk;
669   ErrorTool:=nil;
670   for i:=0 to ToolList.ComponentCount-1 do begin
671     if ToolList.Components[i] is TCustomTextConverterTool then begin
672       Tool:=TCustomTextConverterTool(ToolList.Components[i]);
673       if Tool.Enabled then begin
674         Tool.ClearError;
675         CurResult:=Tool.Execute(Self);
676         if CurResult=mrIgnore then
677           Result:=mrOk
678         else if CurResult<>mrOk then begin
679           ErrorTool:=Tool;
680           exit(mrAbort);
681         end;
682       end;
683     end;
684   end;
685 end;
686 
TIDETextConverter.LoadFromFilenull687 function TIDETextConverter.LoadFromFile(const AFilename: string;
688   UseIDECache: Boolean; UpdateFromDisk: Boolean; Revert: Boolean): Boolean;
689 var
690   fs: TFileStream;
691 begin
692   if UseIDECache and (TextConverterToolClasses<>nil) then begin
693     //DebugLn(['TIDETextConverter.LoadFromFile using IDE cache']);
694     Result:=TextConverterToolClasses.LoadFromFile(Self,AFilename,
695                                                   UpdateFromDisk,Revert);
696   end else begin
697     //DebugLn(['TIDETextConverter.LoadFromFile loading directly CurrentType=',ord(CurrentType),' FFilename="',FFilename,'"']);
698     Result:=false;
699     try
700       case CurrentType of
701       tctSource:
702         begin
703           fs:=TFileStreamUTF8.Create(AFilename,fmOpenRead);
704           try
705             SetLength(FSource,fs.Size);
706             if fSource<>'' then
707               fs.Read(fSource[1],length(fSource));
708           finally
709             fs.Free;
710           end;
711         end;
712       tctFile:
713         CopyFile(AFilename,FFilename);
714       tctStrings:
715         if FStrings is TStringListUTF8 then
716           FStrings.LoadFromFile(FFilename)
717         else
718           FStrings.LoadFromFile(UTF8ToSys(FFilename));
719       end;
720       Result:=true;
721     except
722     end;
723   end;
724 end;
725 
726 procedure TIDETextConverter.InitWithFilename(const AFilename: string);
727 begin
728   Clear;
729   FCurrentType:=tctFile;
730   FFilename:=AFilename;
731 end;
732 
733 procedure TIDETextConverter.InitWithSource(const ASource: string);
734 begin
735   Clear;
736   FCurrentType:=tctSource;
737   FSource:=ASource;
738 end;
739 
740 procedure TIDETextConverter.InitWithStrings(const aStrings: TStrings);
741 begin
742   Clear;
743   FCurrentType:=tctStrings;
744   FStrings:=aStrings;
745 end;
746 
747 procedure TIDETextConverter.InitWithCodeBuffers(const aBuffer: Pointer);
748 begin
749   CheckType(tctCodeBuffer);
750   Clear;
751   FCurrentType:=tctCodeBuffer;
752   FCodeBuffer:=aBuffer;
753 end;
754 
755 { TCustomTextConverterTool }
756 
757 procedure TCustomTextConverterTool.SetCaption(const AValue: string);
758 begin
759   if FCaption=AValue then exit;
760   FCaption:=AValue;
761 end;
762 
IsCaptionStorednull763 function TCustomTextConverterTool.IsCaptionStored: boolean;
764 begin
765   Result:=Caption<>FirstLineOfClassDescription;
766 end;
767 
768 procedure TCustomTextConverterTool.SetDescription(const AValue: string);
769 begin
770   if FDescription=AValue then exit;
771   FDescription:=AValue;
772 end;
773 
774 constructor TCustomTextConverterTool.Create(TheOwner: TComponent);
775 begin
776   inherited Create(TheOwner);
777   Enabled:=true;
778   Caption:=FirstLineOfClassDescription;
779 end;
780 
781 procedure TCustomTextConverterTool.Assign(Source: TPersistent);
782 var
783   Src: TCustomTextConverterTool;
784 begin
785   if Source is TCustomTextConverterTool then begin
786     Src:=TCustomTextConverterTool(Source);
787     Caption:=Src.Caption;
788     Description:=Src.Description;
789   end else
790     inherited Assign(Source);
791 end;
792 
793 procedure TCustomTextConverterTool.ClearError;
794 begin
795   FErrorMsg:='';
796   FErrorLine:=0;
797   FErrorColumn:=0;
798   FErrorTopLine:=0;
799   FErrorFilename:='';
800 end;
801 
802 procedure TCustomTextConverterTool.AssignError(Source: TCustomTextConverterTool);
803 begin
804   FErrorMsg:=Source.ErrorMsg;
805   FErrorLine:=Source.ErrorLine;
806   FErrorColumn:=Source.ErrorColumn;
807   FErrorTopLine:=Source.ErrorTopLine;
808   FErrorFilename:=Source.ErrorFilename;
809 end;
810 
811 procedure TCustomTextConverterTool.AssignCodeToolBossError;
812 begin
813   if Assigned(TextConverterToolClasses) then
814     TextConverterToolClasses.AssignCodeToolBossError(Self)
815   else
816     ClearError;
817 end;
818 
TCustomTextConverterTool.FirstLineOfClassDescriptionnull819 class function TCustomTextConverterTool.FirstLineOfClassDescription: string;
820 var
821   p: Integer;
822 begin
823   Result:=ClassDescription;
824   p:=1;
825   while (p<=length(Result)) do begin
826     if Result[p] in [#10,#13] then begin
827       Result:=copy(Result,1,p-1);
828       exit;
829     end;
830     inc(p);
831   end;
832 end;
833 
834 { TCustomTextReplaceTool }
835 
836 procedure TCustomTextReplaceTool.SetOptions(
837   const AValue: TTextReplaceToolOptions);
838 begin
839   if FOptions=AValue then exit;
840   FOptions:=AValue;
841 end;
842 
843 procedure TCustomTextReplaceTool.SetReplaceWith(const AValue: string);
844 begin
845   if FReplaceWith=AValue then exit;
846   FReplaceWith:=AValue;
847 end;
848 
849 procedure TCustomTextReplaceTool.SetSearchFor(const AValue: string);
850 begin
851   if FSearchFor=AValue then exit;
852   FSearchFor:=AValue;
853 end;
854 
TCustomTextReplaceTool.Executenull855 function TCustomTextReplaceTool.Execute(aText: TIDETextConverter
856   ): TModalResult;
857 var
858   Source: String;
859   Flags: TSrcEditSearchOptions;
860   Prompt: Boolean;
861 begin
862   //DebugLn(['TCustomTextReplaceTool.Execute ',dbgsName(Self),' aText=',dbgsName(aText),' SearchFor="',dbgstr(SearchFor),'"']);
863   Result:=mrCancel;
864   if aText=nil then exit;
865   if SearchFor='' then exit(mrOk);
866   Source:=aText.Source;
867   Flags:=[sesoReplace,sesoReplaceAll];
868   if trtMatchCase in Options then Include(Flags,sesoMatchCase);
869   if trtWholeWord in Options then Include(Flags,sesoWholeWord);
870   if trtRegExpr in Options then Include(Flags,sesoRegExpr);
871   if trtMultiLine in Options then Include(Flags,sesoMultiLine);
872   Prompt:=false;
873   Result:=IDESearchInText('',Source,SearchFor,ReplaceWith,Flags,Prompt,nil);
874   if Result=mrOk then
875     aText.Source:=Source;
876   //DebugLn(['TCustomTextReplaceTool.Execute END Result=',Result=mrOk]);
877 end;
878 
879 procedure TCustomTextReplaceTool.Assign(Source: TPersistent);
880 var
881   Src: TCustomTextReplaceTool;
882 begin
883   if Source is TCustomTextReplaceTool then begin
884     Src:=TCustomTextReplaceTool(Source);
885     SearchFor:=Src.SearchFor;
886     ReplaceWith:=Src.ReplaceWith;
887     Options:=Src.Options;
888   end;
889   inherited Assign(Source);
890 end;
891 
TCustomTextReplaceTool.ClassDescriptionnull892 class function TCustomTextReplaceTool.ClassDescription: string;
893 begin
894   Result := itcsSearchAndReplace;
895 end;
896 
897 { TTextConverterToolClasses }
898 
TTextConverterToolClasses.GetCountnull899 function TTextConverterToolClasses.GetCount: integer;
900 begin
901   if Self<>nil then
902     Result:=FItems.Count
903   else
904     Result:=0;
905 end;
906 
TTextConverterToolClasses.GetItemsnull907 function TTextConverterToolClasses.GetItems(Index: integer
908   ): TCustomTextConverterToolClass;
909 begin
910   Result:=TCustomTextConverterToolClass(FItems[Index]);
911 end;
912 
913 constructor TTextConverterToolClasses.Create;
914 begin
915   FItems:=TFPList.Create;
916 end;
917 
918 destructor TTextConverterToolClasses.Destroy;
919 begin
920   FItems.Clear;
921   FreeAndNil(FItems);
922   inherited Destroy;
923 end;
924 
925 procedure TTextConverterToolClasses.RegisterClass(
926   AClass: TCustomTextConverterToolClass);
927 begin
928   if Self=nil then exit;
929   if FItems.IndexOf(AClass)<0 then
930     FItems.Add(AClass);
931 end;
932 
933 procedure TTextConverterToolClasses.UnregisterClass(
934   AClass: TCustomTextConverterToolClass);
935 begin
936   if Self=nil then exit;
937   FItems.Remove(AClass);
938 end;
939 
TTextConverterToolClasses.FindByNamenull940 function TTextConverterToolClasses.FindByName(const aClassName: string
941   ): TCustomTextConverterToolClass;
942 var
943   i: Integer;
944 begin
945   if Self<>nil then
946     for i:=0 to FItems.Count-1 do begin
947       Result:=Items[i];
948       if CompareText(Result.ClassName,aClassName)=0 then exit;
949     end;
950   Result:=nil;
951 end;
952 
FindByFirstLineOfClassDescriptionnull953 function TTextConverterToolClasses.FindByFirstLineOfClassDescription(
954   const Line: string): TCustomTextConverterToolClass;
955 var
956   i: Integer;
957 begin
958   if Self<>nil then
959     for i:=0 to FItems.Count-1 do begin
960       Result:=Items[i];
961       if Result.FirstLineOfClassDescription=Line then exit;
962     end;
963   Result:=nil;
964 end;
965 
966 procedure TTextConverterToolClasses.FindClass(Reader: TReader;
967   const aClassName: string; var ComponentClass: TComponentClass);
968 begin
969   if Reader=nil then ;
970   ComponentClass:=FindByName(aClassName);
971 end;
972 
973 procedure TTextConverterToolClasses.AssignCodeToolBossError(
974   Target: TCustomTextConverterTool);
975 begin
976   Target.ErrorMsg:='';
977   Target.ErrorLine:=-1;
978   Target.ErrorColumn:=-1;
979   Target.ErrorTopLine:=-1;
980   Target.ErrorFilename:='';
981 end;
982 
983 initialization
984   RegisterPropertyEditor(TypeInfo(AnsiString),
985     TCustomTextReplaceTool, 'SearchFor', TStringMultilinePropertyEditor);
986   RegisterPropertyEditor(TypeInfo(AnsiString),
987     TCustomTextReplaceTool, 'ReplaceWith', TStringMultilinePropertyEditor);
988 
989 end.
990 
991