1 {
2  ***************************************************************************
3  *                                                                         *
4  *   This source is free software; you can redistribute it and/or modify   *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This code is distributed in the hope that it will be useful, but      *
10  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
12  *   General Public License for more details.                              *
13  *                                                                         *
14  *   A copy of the GNU General Public License is available on the World    *
15  *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
16  *   obtain it by writing to the Free Software Foundation,                 *
17  *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
18  *                                                                         *
19  ***************************************************************************
20 
21   Author: Mattias Gaertner
22 
23   Abstract:
24     Options dialog and methods for finding and renaming identifier references.
25 }
26 unit FindRenameIdentifier;
27 
28 {$mode objfpc}{$H+}
29 
30 interface
31 
32 uses
33   // RTL + FCL
34   Classes, SysUtils, Laz_AVL_Tree,
35   // LCL
36   LCLProc, Forms, Controls, Dialogs, StdCtrls, ExtCtrls, ComCtrls, ButtonPanel,
37   LclIntf,
38   // CodeTools
39   FileProcs, CTUnitGraph, CodeTree, CodeCache, CodeToolManager, BasicCodeTools,
40   // LazUtils
41   LazFileUtils, LazFileCache, laz2_DOM, LazStringUtils, AvgLvlTree,
42   // IdeIntf
43   LazIDEIntf, IDEWindowIntf, SrcEditorIntf, PackageIntf, IDEDialogs,
44   // IDE
45   LazarusIDEStrConsts, IDEProcs, MiscOptions, DialogProcs,
46   InputHistory, SearchResultView, CodeHelp, TransferMacros;
47 
48 type
49 
50   { TFindRenameIdentifierDialog }
51 
52   TFindRenameIdentifierDialog = class(TForm)
53     ButtonPanel1: TButtonPanel;
54     ShowResultCheckBox: TCheckBox;
55     CurrentGroupBox: TGroupBox;
56     CurrentListBox: TListBox;
57     ExtraFilesEdit: TEdit;
58     ExtraFilesGroupBox: TGroupBox;
59     NewEdit: TEdit;
60     NewGroupBox: TGroupBox;
61     RenameCheckBox: TCheckBox;
62     ScopeCommentsCheckBox: TCheckBox;
63     ScopeGroupBox: TGroupBox;
64     ScopeRadioGroup: TRadioGroup;
65     procedure FindOrRenameButtonClick(Sender: TObject);
66     procedure FindRenameIdentifierDialogClose(Sender: TObject;
67       var {%H-}CloseAction: TCloseAction);
68     procedure FindRenameIdentifierDialogCreate(Sender: TObject);
69     procedure FormShow(Sender: TObject);
70     procedure HelpButtonClick(Sender: TObject);
71     procedure RenameCheckBoxChange(Sender: TObject);
72   private
73     FAllowRename: boolean;
74     FIdentifierFilename: string;
75     FIdentifierPosition: TPoint;
76     FIsPrivate: boolean;
77     procedure SetAllowRename(const AValue: boolean);
78     procedure SetIsPrivate(const AValue: boolean);
79     procedure UpdateRename;
80   public
81     procedure LoadFromConfig;
82     procedure SaveToConfig;
83     procedure LoadFromOptions(Options: TFindRenameIdentifierOptions);
84     procedure SaveToOptions(Options: TFindRenameIdentifierOptions);
85     procedure SetIdentifier(const NewIdentifierFilename: string;
86                             const NewIdentifierPosition: TPoint);
87     property IdentifierFilename: string read FIdentifierFilename;
88     property IdentifierPosition: TPoint read FIdentifierPosition;
89     property AllowRename: boolean read FAllowRename write SetAllowRename;
90     property IsPrivate: boolean read FIsPrivate write SetIsPrivate;
91   end;
92 
93 procedure CleanUpFileList(Files: TStringList);
94 
ShowFindRenameIdentifierDialognull95 function ShowFindRenameIdentifierDialog(const Filename: string;
96   const Position: TPoint;
97   AllowRename: boolean; // allow user to disable/enable rename
98   SetRenameActive: boolean; // check rename
99   Options: TFindRenameIdentifierOptions): TModalResult;
DoFindRenameIdentifiernull100 function DoFindRenameIdentifier(
101   AllowRename: boolean; // allow user to disable/enable rename
102   SetRenameActive: boolean; // check rename
103   Options: TFindRenameIdentifierOptions): TModalResult;
GatherIdentifierReferencesnull104 function GatherIdentifierReferences(Files: TStringList;
105   DeclarationCode: TCodeBuffer; const DeclarationCaretXY: TPoint;
106   SearchInComments: boolean;
107   var TreeOfPCodeXYPosition: TAVLTree): TModalResult;
GatherUnitReferencesnull108 function GatherUnitReferences(Files: TStringList;
109   UnitCode: TCodeBuffer; SearchInComments, IgnoreErrors, IgnoreMissingFiles: boolean;
110   var TreeOfPCodeXYPosition: TAVLTree): TModalResult;
ShowIdentifierReferencesnull111 function ShowIdentifierReferences(
112   DeclarationCode: TCodeBuffer; const DeclarationCaretXY: TPoint;
113   TreeOfPCodeXYPosition: TAVLTree): TModalResult;
114 procedure AddReferencesToResultView(DeclarationCode: TCodeBuffer;
115   const DeclarationCaretXY: TPoint;
116   TreeOfPCodeXYPosition: TAVLTree; ClearItems: boolean; SearchPageIndex: integer);
117 
GatherFPDocReferencesForPascalFilesnull118 function GatherFPDocReferencesForPascalFiles(PascalFiles: TStringList;
119   DeclarationCode: TCodeBuffer; const DeclarationCaretXY: TPoint;
120   var ListOfLazFPDocNode: TFPList): TModalResult;
GatherReferencesInFPDocFilenull121 function GatherReferencesInFPDocFile(
122   const OldPackageName, OldModuleName, OldElementName: string;
123   const FPDocFilename: string;
124   var ListOfLazFPDocNode: TFPList): TModalResult;
125 
126 
127 implementation
128 
129 {$R *.lfm}
130 
131 procedure CleanUpFileList(Files: TStringList);
132 var
133   i: Integer;
134 begin
135   // sort files
136   Files.Sort;
137   // remove doubles
138   i:=0;
139   while i<=Files.Count-2 do begin
140     while (i<=Files.Count-2) and (CompareFilenames(Files[i],Files[i+1])=0) do
141       Files.Delete(i+1);
142     inc(i);
143   end;
144   // remove non files
145   for i:=Files.Count-1 downto 0 do
146     if ExtractFilename(Files[i])='' then begin
147       debugln(['Note: (lazarus) [FindRenameIdentifier.CleanUpFileList] invalid file "',Files[i],'"']);
148       Files.Delete(i);
149     end;
150 end;
151 
ShowFindRenameIdentifierDialognull152 function ShowFindRenameIdentifierDialog(const Filename: string;
153   const Position: TPoint; AllowRename: boolean; SetRenameActive: boolean;
154   Options: TFindRenameIdentifierOptions): TModalResult;
155 var
156   FindRenameIdentifierDialog: TFindRenameIdentifierDialog;
157 begin
158   FindRenameIdentifierDialog:=TFindRenameIdentifierDialog.Create(nil);
159   try
160     FindRenameIdentifierDialog.LoadFromConfig;
161     FindRenameIdentifierDialog.SetIdentifier(Filename,Position);
162     FindRenameIdentifierDialog.AllowRename:=AllowRename;
163     FindRenameIdentifierDialog.RenameCheckBox.Checked:=SetRenameActive and AllowRename;
164     if Options<>nil then
165       FindRenameIdentifierDialog.ShowResultCheckBox.Checked:=Options.RenameShowResult and AllowRename;
166     Result:=FindRenameIdentifierDialog.ShowModal;
167     if Result=mrOk then
168       if Options<>nil then
169         FindRenameIdentifierDialog.SaveToOptions(Options);
170   finally
171     FindRenameIdentifierDialog.Free;
172   end;
173 end;
174 
DoFindRenameIdentifiernull175 function DoFindRenameIdentifier(AllowRename: boolean; SetRenameActive: boolean;
176   Options: TFindRenameIdentifierOptions): TModalResult;
177 
178   // TODO: replace Files: TStringsList with a AVL tree
179 
AddExtraFilesnull180   function AddExtraFiles(Files: TStrings): TModalResult;
181   var
182     i: Integer;
183     CurFileMask: string;
184     FileInfo: TSearchRec;
185     CurDirectory: String;
186     CurFilename: String;
187     OnlyPascalSources: Boolean;
188   begin
189     Result:=mrCancel;
190     if (Options.ExtraFiles<>nil) then begin
191       for i:=0 to Options.ExtraFiles.Count-1 do begin
192         CurFileMask:=Options.ExtraFiles[i];
193         if not GlobalMacroList.SubstituteStr(CurFileMask) then exit;
194         CurFileMask:=ChompPathDelim(CurFileMask);
195         if not FilenameIsAbsolute(CurFileMask) then begin
196           if LazarusIDE.ActiveProject.IsVirtual then continue;
197           CurFileMask:=AppendPathDelim(LazarusIDE.ActiveProject.Directory+CurFileMask);
198         end;
199         CurFileMask:=TrimFilename(CurFileMask);
200         OnlyPascalSources:=false;
201         if DirPathExistsCached(CurFileMask) then begin
202           // a whole directory
203           OnlyPascalSources:=true;
204           CurFileMask:=AppendPathDelim(CurFileMask)+AllFilesMask;
205         end else if FileExistsCached(CurFileMask) then begin
206           // single file
207           Files.Add(CurFileMask);
208           continue;
209         end else begin
210           // a mask
211         end;
212         if FindFirstUTF8(CurFileMask,faAnyFile,FileInfo)=0
213         then begin
214           CurDirectory:=AppendPathDelim(ExtractFilePath(CurFileMask));
215           repeat
216             // check if special file
217             if (FileInfo.Name='.') or (FileInfo.Name='..') or (FileInfo.Name='')
218             then
219               continue;
220             if OnlyPascalSources and not FilenameIsPascalSource(FileInfo.Name)
221             then
222               continue;
223             CurFilename:=CurDirectory+FileInfo.Name;
224             //debugln(['AddExtraFiles ',CurFilename]);
225             if FileIsText(CurFilename) then
226               Files.Add(CurFilename);
227           until FindNextUTF8(FileInfo)<>0;
228         end;
229         FindCloseUTF8(FileInfo);
230       end;
231     end;
232     Result:=mrOk;
233   end;
234 
235 var
236   StartSrcEdit: TSourceEditorInterface;
237   DeclCode, StartSrcCode: TCodeBuffer;
238   DeclX, DeclY, DeclTopLine, StartTopLine, i: integer;
239   LogCaretXY, DeclarationCaretXY: TPoint;
240   OwnerList: TFPList;
241   ExtraFiles: TStrings;
242   Files: TStringList;
243   Identifier: string;
244   PascalReferences: TAVLTree;
245   ListOfLazFPDocNode: TFPList;
246   CurUnitname: String;
247   OldChange, Completed: Boolean;
248   Graph: TUsesGraph;
249   Node: TAVLTreeNode;
250   UGUnit: TUGUnit;
251 begin
252   Result:=mrCancel;
253   if not LazarusIDE.BeginCodeTools then exit(mrCancel);
254 
255   StartSrcEdit:=SourceEditorManagerIntf.ActiveEditor;
256   StartSrcCode:=TCodeBuffer(StartSrcEdit.CodeToolsBuffer);
257   StartTopLine:=StartSrcEdit.TopLine;
258 
259   // find the main declaration
260   LogCaretXY:=StartSrcEdit.CursorTextXY;
261   if not CodeToolBoss.FindMainDeclaration(StartSrcCode,
262     LogCaretXY.X,LogCaretXY.Y,
263     DeclCode,DeclX,DeclY,DeclTopLine) then
264   begin
265     LazarusIDE.DoJumpToCodeToolBossError;
266     exit(mrCancel);
267   end;
268   DeclarationCaretXY:=Point(DeclX,DeclY);
269   Result:=LazarusIDE.DoOpenFileAndJumpToPos(DeclCode.Filename, DeclarationCaretXY,
270     DeclTopLine,-1,-1,[ofOnlyIfExists,ofRegularFile,ofDoNotLoadResource]);
271   if Result<>mrOk then
272     exit;
273 
274   CodeToolBoss.GetIdentifierAt(DeclCode,DeclarationCaretXY.X,DeclarationCaretXY.Y,Identifier);
275   CurUnitname:=ExtractFileNameOnly(DeclCode.Filename);
276 
277   //debugln('TMainIDE.DoFindRenameIdentifier A DeclarationCaretXY=',dbgs(DeclarationCaretXY));
278 
279   Files:=nil;
280   OwnerList:=nil;
281   PascalReferences:=nil;
282   ListOfLazFPDocNode:=nil;
283   try
284     // let user choose the search scope
285     Result:=ShowFindRenameIdentifierDialog(DeclCode.Filename,DeclarationCaretXY,
286       AllowRename,SetRenameActive,nil);
287     if Result<>mrOk then begin
288       debugln('Error: (lazarus) DoFindRenameIdentifier failed: user cancelled dialog');
289       exit;
290     end;
291 
292     // create the file list
293     Files:=TStringList.Create;
294     Files.Add(DeclCode.Filename);
295     if CompareFilenames(DeclCode.Filename,StartSrcCode.Filename)<>0 then
296       Files.Add(StartSrcCode.Filename);
297 
298     Options:=MiscellaneousOptions.FindRenameIdentifierOptions;
299 
300     // add packages, projects
301     case Options.Scope of
302     frProject:
303       begin
304         OwnerList:=TFPList.Create;
305         OwnerList.Add(LazarusIDE.ActiveProject);
306       end;
307     frOwnerProjectPackage,frAllOpenProjectsAndPackages:
308       begin
309         OwnerList:=PackageEditingInterface.GetOwnersOfUnit(StartSrcCode.Filename);
310         if (OwnerList<>nil) and (OwnerList.Count=0) then
311           FreeAndNil(OwnerList);
312         if (OwnerList=nil) then
313           OwnerList:=PackageEditingInterface.GetPossibleOwnersOfUnit(
314             StartSrcCode.Filename,[piosfExcludeOwned,piosfIncludeSourceDirectories]);
315         if (OwnerList<>nil) and (OwnerList.Count=0) then
316           FreeAndNil(OwnerList);
317         if (OwnerList<>nil) then begin
318           if Options.Scope=frAllOpenProjectsAndPackages then begin
319             PackageEditingInterface.ExtendOwnerListWithUsedByOwners(OwnerList);
320             ReverseList(OwnerList);
321           end;
322         end else begin
323           // unknown unit -> search everywhere
324           OwnerList:=TFPList.Create;
325           OwnerList.Add(LazarusIDE.ActiveProject);
326           for i:=0 to PackageEditingInterface.GetPackageCount-1 do
327             OwnerList.Add(PackageEditingInterface.GetPackages(i));
328           ReverseList(OwnerList);
329         end;
330       end;
331     end;
332 
333     // get source files of packages and projects
334     if OwnerList<>nil then begin
335       // start in all listed files of the package(s)
336       ExtraFiles:=PackageEditingInterface.GetSourceFilesOfOwners(OwnerList);
337       if ExtraFiles<>nil then
338       begin
339         // parse all used units
340         Graph:=CodeToolBoss.CreateUsesGraph;
341         try
342           for i:=0 to ExtraFiles.Count-1 do
343             Graph.AddStartUnit(ExtraFiles[i]);
344           Graph.AddTargetUnit(DeclCode.Filename);
345           Graph.Parse(true,Completed);
346           Node:=Graph.FilesTree.FindLowest;
347           while Node<>nil do begin
348             UGUnit:=TUGUnit(Node.Data);
349             Files.Add(UGUnit.Filename);
350             Node:=Node.Successor;
351           end;
352         finally
353           ExtraFiles.Free;
354           Graph.Free;
355         end;
356       end;
357     end;
358 
359     //debugln(['DoFindRenameIdentifier ',Files.Text]);
360 
361     // add user defined extra files
362     Result:=AddExtraFiles(Files);
363     if Result<>mrOk then begin
364       debugln('Error: (lazarus) DoFindRenameIdentifier unable to add user defined extra files');
365       exit;
366     end;
367 
368     // search pascal source references
369     Result:=GatherIdentifierReferences(Files,DeclCode,
370       DeclarationCaretXY,Options.SearchInComments,PascalReferences);
371     if CodeToolBoss.ErrorMessage<>'' then
372       LazarusIDE.DoJumpToCodeToolBossError;
373     if Result<>mrOk then begin
374       debugln('Error: (lazarus) DoFindRenameIdentifier GatherIdentifierReferences failed');
375       exit;
376     end;
377 
378     {$IFDEF EnableFPDocRename}
379     // search fpdoc references
380     Result:=GatherFPDocReferencesForPascalFiles(Files,DeclarationUnitInfo.Source,
381                                   DeclarationCaretXY,ListOfLazFPDocNode);
382     if Result<>mrOk then begin
383       debugln('Error: (lazarus) DoFindRenameIdentifier GatherFPDocReferences failed');
384       exit;
385     end;
386     {$ENDIF}
387 
388     // ToDo: search lfm source references
389     // ToDo: search i18n references
390     // ToDo: designer references
391 
392     // rename identifier
393     if Options.Rename then begin
394       if CompareIdentifiers(PChar(Identifier),PChar(CurUnitName))=0 then
395       begin
396         IDEMessageDialog(srkmecRenameIdentifier,
397           lisTheIdentifierIsAUnitPleaseUseTheFileSaveAsFunction,
mtInformationnull398           mtInformation,[mbCancel],'');
399         exit(mrCancel);
400       end;
401       OldChange:=LazarusIDE.OpenEditorsOnCodeToolChange;
402       LazarusIDE.OpenEditorsOnCodeToolChange:=true;
403       try
404         if not CodeToolBoss.RenameIdentifier(PascalReferences,
405           Identifier,Options.RenameTo, DeclCode, @DeclarationCaretXY)
406         then begin
407           LazarusIDE.DoJumpToCodeToolBossError;
408           debugln('Error: (lazarus) DoFindRenameIdentifier unable to commit');
409           Result:=mrCancel;
410           exit;
411         end;
412       finally
413         LazarusIDE.OpenEditorsOnCodeToolChange:=OldChange;
414       end;
415       if Options.RenameShowResult then
416         Result:=ShowIdentifierReferences(DeclCode,
417           DeclarationCaretXY,PascalReferences);
418     end;
419 
420     // show result
421     Result:=mrOk;
422     if (not Options.Rename) or (not SetRenameActive) then begin
423       Result:=ShowIdentifierReferences(DeclCode,
424         DeclarationCaretXY,PascalReferences);
425       if Result<>mrOk then exit;
426     end;
427 
428   finally
429     Files.Free;
430     OwnerList.Free;
431     CodeToolBoss.FreeTreeOfPCodeXYPosition(PascalReferences);
432     FreeListObjects(ListOfLazFPDocNode,true);
433 
434     // jump back in source editor
435     Result:=LazarusIDE.DoOpenFileAndJumpToPos(StartSrcCode.Filename, LogCaretXY,
436       StartTopLine,-1,-1,[ofOnlyIfExists,ofRegularFile,ofDoNotLoadResource]);
437   end;
438 end;
439 
GatherIdentifierReferencesnull440 function GatherIdentifierReferences(Files: TStringList;
441   DeclarationCode: TCodeBuffer; const DeclarationCaretXY: TPoint;
442   SearchInComments: boolean;
443   var TreeOfPCodeXYPosition: TAVLTree): TModalResult;
444 var
445   i: Integer;
446   LoadResult: TModalResult;
447   Code: TCodeBuffer;
448   ListOfPCodeXYPosition: TFPList;
449   Cache: TFindIdentifierReferenceCache;
450 begin
451   Result:=mrCancel;
452   ListOfPCodeXYPosition:=nil;
453   TreeOfPCodeXYPosition:=nil;
454   Cache:=nil;
455   try
456     CleanUpFileList(Files);
457 
458     // search in every file
459     for i:=0 to Files.Count-1 do begin
460       //debugln(['GatherIdentifierReferences ',Files[i]]);
461       LoadResult:=
462           LoadCodeBuffer(Code,Files[i],[lbfCheckIfText,lbfUpdateFromDisk,lbfIgnoreMissing],true);
463       if LoadResult=mrAbort then begin
464         debugln('GatherIdentifierReferences unable to load "',Files[i],'"');
465         exit;
466       end;
467       if LoadResult<>mrOk then continue;
468 
469       // search references
470       CodeToolBoss.FreeListOfPCodeXYPosition(ListOfPCodeXYPosition);
471       if not CodeToolBoss.FindReferences(
472         DeclarationCode,DeclarationCaretXY.X,DeclarationCaretXY.Y,
473         Code, not SearchInComments, ListOfPCodeXYPosition, Cache) then
474       begin
475         debugln('GatherIdentifierReferences unable to FindReferences in "',Code.Filename,'"');
476         Result:=mrAbort;
477         exit;
478       end;
479       //debugln('GatherIdentifierReferences FindReferences in "',Code.Filename,'" ',dbgs(ListOfPCodeXYPosition<>nil));
480 
481       // add to tree
482       if ListOfPCodeXYPosition<>nil then begin
483         if TreeOfPCodeXYPosition=nil then
484           TreeOfPCodeXYPosition:=CodeToolBoss.CreateTreeOfPCodeXYPosition;
485         CodeToolBoss.AddListToTreeOfPCodeXYPosition(ListOfPCodeXYPosition,
486                                               TreeOfPCodeXYPosition,true,false);
487       end;
488     end;
489 
490     Result:=mrOk;
491   finally
492     CodeToolBoss.FreeListOfPCodeXYPosition(ListOfPCodeXYPosition);
493     if Result<>mrOk then
494       CodeToolBoss.FreeTreeOfPCodeXYPosition(TreeOfPCodeXYPosition);
495     Cache.Free;
496   end;
497 end;
498 
GatherUnitReferencesnull499 function GatherUnitReferences(Files: TStringList; UnitCode: TCodeBuffer;
500   SearchInComments, IgnoreErrors, IgnoreMissingFiles: boolean;
501   var TreeOfPCodeXYPosition: TAVLTree): TModalResult;
502 var
503   ListOfPCodeXYPosition: TFPList;
504   LoadResult: TModalResult;
505   Code: TCodeBuffer;
506   i: Integer;
507 begin
508   Result:=mrCancel;
509   ListOfPCodeXYPosition:=nil;
510   TreeOfPCodeXYPosition:=nil;
511   try
512     CleanUpFileList(Files);
513 
514     Result:=mrOk;
515     // search in every file
516     for i:=0 to Files.Count-1 do begin
517       if CompareFilenames(Files[i],UnitCode.Filename)=0 then continue;
518       if IgnoreMissingFiles then
519       begin
520         if FilenameIsAbsolute(Files[i]) then
521         begin
522           if not FileExistsCached(Files[i]) then continue;
523         end else begin
524           Code:=CodeToolBoss.LoadFile(Files[i],false,false);
525           if (Code=nil) then continue;
526         end;
527       end;
528       LoadResult:=
529           LoadCodeBuffer(Code,Files[i],[lbfCheckIfText,lbfUpdateFromDisk],true);
530       if LoadResult=mrAbort then begin
531         debugln('GatherUnitReferences unable to load "',Files[i],'"');
532         if IgnoreErrors then
533           continue;
534         Result:=mrCancel;
535         exit;
536       end;
537       if LoadResult<>mrOk then continue;
538 
539       // search references
540       CodeToolBoss.FreeListOfPCodeXYPosition(ListOfPCodeXYPosition);
541       if not CodeToolBoss.FindUnitReferences(
542         UnitCode, Code, not SearchInComments, ListOfPCodeXYPosition) then
543       begin
544         debugln('GatherUnitReferences unable to FindUnitReferences in "',Code.Filename,'"');
545         if IgnoreErrors then
546           continue;
547         Result:=mrCancel;
548         exit;
549       end;
550       //debugln('GatherUnitReferences FindUnitReferences in "',Code.Filename,'" ',dbgs(ListOfPCodeXYPosition<>nil));
551 
552       // add to tree
553       if ListOfPCodeXYPosition<>nil then begin
554         if TreeOfPCodeXYPosition=nil then
555           TreeOfPCodeXYPosition:=CodeToolBoss.CreateTreeOfPCodeXYPosition;
556         CodeToolBoss.AddListToTreeOfPCodeXYPosition(ListOfPCodeXYPosition,
557                                               TreeOfPCodeXYPosition,true,false);
558       end;
559     end;
560   finally
561     CodeToolBoss.FreeListOfPCodeXYPosition(ListOfPCodeXYPosition);
562   end;
563 end;
564 
ShowIdentifierReferencesnull565 function ShowIdentifierReferences(
566   DeclarationCode: TCodeBuffer; const DeclarationCaretXY: TPoint;
567   TreeOfPCodeXYPosition: TAVLTree): TModalResult;
568 var
569   Identifier: string;
570   OldSearchPageIndex: TTabSheet;
571   SearchPageIndex: TTabSheet;
572 begin
573   Result:=mrCancel;
574   LazarusIDE.DoShowSearchResultsView(iwgfShow);
575   SearchPageIndex:=nil;
576   try
577     // show result
578     CodeToolBoss.GetIdentifierAt(DeclarationCode,
579       DeclarationCaretXY.X,DeclarationCaretXY.Y,Identifier);
580     // create a search result page
581     //debugln(['ShowIdentifierReferences ',DbgSName(SearchResultsView)]);
582     SearchPageIndex:=SearchResultsView.AddSearch(
583       'Ref: '+Identifier,
584       Identifier,
585       '',
586       ExtractFilePath(DeclarationCode.Filename),
587       '*.pas;*.pp;*.p;*.inc',
588       [fifWholeWord,fifSearchDirectories]);
589     if SearchPageIndex = nil then exit;
590 
591     // list results
592     SearchResultsView.BeginUpdate(SearchPageIndex.PageIndex);
593     AddReferencesToResultView(DeclarationCode,DeclarationCaretXY,
594                    TreeOfPCodeXYPosition,true,SearchPageIndex.PageIndex);
595     OldSearchPageIndex:=SearchPageIndex;
596     SearchPageIndex:=nil;
597     SearchResultsView.EndUpdate(OldSearchPageIndex.PageIndex);
598     IDEWindowCreators.ShowForm(SearchResultsView,true);
599   finally
600     if SearchPageIndex <> nil then
601       SearchResultsView.EndUpdate(SearchPageIndex.PageIndex);
602   end;
603 end;
604 
605 procedure AddReferencesToResultView(DeclarationCode: TCodeBuffer;
606   const DeclarationCaretXY: TPoint; TreeOfPCodeXYPosition: TAVLTree;
607   ClearItems: boolean; SearchPageIndex: integer);
608 var
609   Identifier: string;
610   CodePos: PCodeXYPosition;
611   CurLine: String;
612   TrimmedLine: String;
613   TrimCnt: Integer;
614   ANode: TAVLTreeNode;
615 begin
616   CodeToolBoss.GetIdentifierAt(DeclarationCode,
617     DeclarationCaretXY.X,DeclarationCaretXY.Y,Identifier);
618 
619   SearchResultsView.BeginUpdate(SearchPageIndex);
620   if ClearItems then
621     SearchResultsView.Items[SearchPageIndex].Clear;
622   if (TreeOfPCodeXYPosition<>nil) then begin
623     ANode:=TreeOfPCodeXYPosition.FindHighest;
624     while ANode<>nil do begin
625       CodePos:=PCodeXYPosition(ANode.Data);
626       CurLine:=TrimRight(CodePos^.Code.GetLine(CodePos^.Y-1,false));
627       TrimmedLine:=Trim(CurLine);
628       TrimCnt:=length(CurLine)-length(TrimmedLine);
629       //debugln('ShowReferences x=',dbgs(CodePos^.x),' y=',dbgs(CodePos^.y),' ',CurLine);
630       SearchResultsView.AddMatch(SearchPageIndex,
631                                  CodePos^.Code.Filename,
632                                  Point(CodePos^.X,CodePos^.Y),
633                                  Point(CodePos^.X+length(Identifier),CodePos^.Y),
634                                  TrimmedLine,
635                                  CodePos^.X-TrimCnt, length(Identifier));
636       ANode:=TreeOfPCodeXYPosition.FindPrecessor(ANode);
637     end;
638   end;
639   SearchResultsView.EndUpdate(SearchPageIndex);
640 end;
641 
GatherFPDocReferencesForPascalFilesnull642 function GatherFPDocReferencesForPascalFiles(PascalFiles: TStringList;
643   DeclarationCode: TCodeBuffer; const DeclarationCaretXY: TPoint;
644   var ListOfLazFPDocNode: TFPList): TModalResult;
645 var
646   PascalFilenames, FPDocFilenames: TFilenameToStringTree;
647   CacheWasUsed: boolean;
648   Chain: TCodeHelpElementChain;
649   CHResult: TCodeHelpParseResult;
650   CHElement: TCodeHelpElement;
651   FPDocFilename: String;
652   S2SItem: PStringToStringItem;
653 begin
654   Result:=mrCancel;
655   PascalFilenames:=nil;
656   FPDocFilenames:=nil;
657   try
658     // gather FPDoc files
659     CleanUpFileList(PascalFiles);
660 
661     PascalFilenames:=TFilenameToStringTree.Create(false);
662     PascalFilenames.AddNames(PascalFiles);
663     CodeHelpBoss.GetFPDocFilenamesForSources(PascalFilenames,true,FPDocFilenames);
664     if FPDocFilenames=nil then begin
665       DebugLn(['GatherFPDocReferences no fpdoc files found']);
666       exit(mrOk);
667     end;
668 
669     // get codehelp element
670     CHResult:=CodeHelpBoss.GetElementChain(DeclarationCode,
671              DeclarationCaretXY.X,DeclarationCaretXY.Y,true,Chain,CacheWasUsed);
672     if CHResult<>chprSuccess then begin
673       DebugLn(['GatherFPDocReferences CodeHelpBoss.GetElementChain failed']);
674       exit;
675     end;
676     CHElement:=Chain[0];
677     DebugLn(['GatherFPDocReferences OwnerName=',CHElement.ElementOwnerName,' FPDocPkg=',CHElement.ElementFPDocPackageName,' Name=',CHElement.ElementName]);
678 
679     // search FPDoc files
680     for S2SItem in FPDocFilenames do begin
681       FPDocFilename:=S2SItem^.Name;
682       Result:=GatherReferencesInFPDocFile(
683                 CHElement.ElementFPDocPackageName,CHElement.ElementUnitName,
684                 CHElement.ElementName,
685                 FPDocFilename,ListOfLazFPDocNode);
686       if Result<>mrOk then exit;
687     end;
688 
689     Result:=mrOk;
690   finally
691     PascalFilenames.Free;
692     FPDocFilenames.Free;
693     if Result<>mrOk then begin
694       FreeListObjects(ListOfLazFPDocNode,true);
695       ListOfLazFPDocNode:=nil;
696     end;
697   end;
698 end;
699 
GatherReferencesInFPDocFilenull700 function GatherReferencesInFPDocFile(
701   const OldPackageName, OldModuleName, OldElementName: string;
702   const FPDocFilename: string;
703   var ListOfLazFPDocNode: TFPList
704   ): TModalResult;
705 var
706   DocFile: TLazFPDocFile;
707   IsSamePackage: Boolean;
708   IsSameModule: Boolean;// = same unit
709 
710   procedure CheckLink(Node: TDOMNode; Link: string);
711   var
712     p: LongInt;
713     PackageName: String;
714   begin
715     if Link='' then exit;
716     if Link[1]='#' then begin
717       p:=System.Pos('.',Link);
718       if p<1 then exit;
719       PackageName:=copy(Link,2,p-2);
720       if SysUtils.CompareText(PackageName,OldPackageName)<>0 then exit;
721       delete(Link,1,p);
722     end;
723     if (SysUtils.CompareText(Link,OldElementName)=0)
724     or (SysUtils.CompareText(Link,OldModuleName+'.'+OldElementName)=0) then
725     begin
726       DebugLn(['CheckLink Found: ',Link]);
727       if ListOfLazFPDocNode=nil then
728         ListOfLazFPDocNode:=TFPList.Create;
729       ListOfLazFPDocNode.Add(TLazFPDocNode.Create(DocFile,Node));
730     end;
731   end;
732 
733   procedure SearchLinksInChildNodes(Node: TDomNode);
734   // search recursively for links
735   begin
736     Node:=Node.FirstChild;
737     while Node<>nil do begin
738       if (Node.NodeName='link')
739       and (Node is TDomElement) then begin
740         CheckLink(Node,TDomElement(Node).GetAttribute('id'));
741       end;
742       SearchLinksInChildNodes(Node);
743       Node:=Node.NextSibling;
744     end;
745   end;
746 
747 var
748   CHResult: TCodeHelpParseResult;
749   CacheWasUsed: boolean;
750   Node: TDOMNode;
751 begin
752   Result:=mrCancel;
753   DebugLn(['GatherFPDocReferences ',
754     ' OldPackageName=',OldPackageName,
755     ' OldModuleName=',OldModuleName,' OldElementName=',OldElementName,
756     ' FPDocFilename=',FPDocFilename]);
757 
758   CHResult:=CodeHelpBoss.LoadFPDocFile(FPDocFilename,[chofUpdateFromDisk],
759                                        DocFile,CacheWasUsed);
760   if CHResult<>chprSuccess then begin
761     DebugLn(['GatherReferencesInFPDocFile CodeHelpBoss.LoadFPDocFile failed File=',FPDocFilename]);
762     exit(mrCancel);
763   end;
764 
765   // search in Doc nodes
766   IsSamePackage:=SysUtils.CompareText(DocFile.GetPackageName,OldPackageName)=0;
767   IsSameModule:=SysUtils.CompareText(DocFile.GetModuleName,OldModuleName)=0;
768   DebugLn(['GatherReferencesInFPDocFile ',DocFile.GetPackageName,'=',OldPackageName,' ',DocFile.GetModuleName,'=',OldModuleName]);
769   Node:=DocFile.GetFirstElement;
770   while Node<>nil do begin
771     if Node is TDomElement then begin
772       if (SysUtils.CompareText(TDomElement(Node).GetAttribute('name'),OldElementName)=0)
773       and IsSamePackage and IsSameModule
774       then begin
775         // this is the element itself
776         DebugLn(['GatherReferencesInFPDocFile Element itself found: ',Node.NodeName,' ',Node.NodeValue]);
777         if ListOfLazFPDocNode=nil then
778           ListOfLazFPDocNode:=TFPList.Create;
779         ListOfLazFPDocNode.Add(TLazFPDocNode.Create(DocFile,Node));
780       end;
781       CheckLink(Node,TDomElement(Node).GetAttribute('link'));
782       SearchLinksInChildNodes(Node);
783     end;
784     Node:=Node.NextSibling;
785   end;
786 
787   Result:=mrOk;
788 end;
789 
790 { TFindRenameIdentifierDialog }
791 
792 procedure TFindRenameIdentifierDialog.FindRenameIdentifierDialogCreate(
793   Sender: TObject);
794 begin
795   IDEDialogLayoutList.ApplyLayout(Self,450,480);
796 
797   Caption:=lisFRIFindOrRenameIdentifier;
798   CurrentGroupBox.Caption:=lisCodeToolsOptsIdentifier;
799   ExtraFilesGroupBox.Caption:=lisFRIAdditionalFilesToSearchEGPathPasPath2Pp;
800   ButtonPanel1.OKButton.Caption:=lisFRIFindReferences;
801   ButtonPanel1.OKButton.ModalResult:=mrNone;
802   ButtonPanel1.CancelButton.Caption:=lisCancel;
803   NewGroupBox.Caption:=lisFRIRenaming;
804   RenameCheckBox.Caption:=lisRename;
805   ShowResultCheckBox.Caption:=lisRenameShowResult;
806   ScopeCommentsCheckBox.Caption:=lisFRISearchInCommentsToo;
807   ScopeGroupBox.Caption:=lisFRISearch;
808   ScopeRadioGroup.Caption:=dlgSearchScope;
809   ScopeRadioGroup.Items[0]:=lisFRIinCurrentUnit;
810   ScopeRadioGroup.Items[1]:=lisFRIinMainProject;
811   ScopeRadioGroup.Items[2]:=lisFRIinProjectPackageOwningCurrentUnit;
812   ScopeRadioGroup.Items[3]:=lisFRIinAllOpenPackagesAndProjects;
813 
814   LoadFromConfig;
815 end;
816 
817 procedure TFindRenameIdentifierDialog.FormShow(Sender: TObject);
818 begin
819   if NewEdit.CanFocus then
820   begin
821     NewEdit.SelectAll;
822     NewEdit.SetFocus;
823   end;
824 end;
825 
826 procedure TFindRenameIdentifierDialog.HelpButtonClick(Sender: TObject);
827 begin
828   OpenUrl('http://wiki.freepascal.org/IDE_Window:_Find_or_Rename_identifier');
829 end;
830 
831 procedure TFindRenameIdentifierDialog.RenameCheckBoxChange(Sender: TObject);
832 begin
833   UpdateRename;
834 end;
835 
836 procedure TFindRenameIdentifierDialog.UpdateRename;
837 begin
838   RenameCheckBox.Enabled:=AllowRename;
839   NewEdit.Enabled:=RenameCheckBox.Checked and RenameCheckBox.Enabled;
840   ShowResultCheckBox.Enabled:=RenameCheckBox.Checked and RenameCheckBox.Enabled;
841   if NewEdit.Enabled then
842     ButtonPanel1.OKButton.Caption:=lisFRIRenameAllReferences
843   else
844     ButtonPanel1.OKButton.Caption:=lisFRIFindReferences;
845 end;
846 
847 procedure TFindRenameIdentifierDialog.SetAllowRename(const AValue: boolean);
848 begin
849   if FAllowRename=AValue then exit;
850   FAllowRename:=AValue;
851   UpdateRename;
852 end;
853 
854 procedure TFindRenameIdentifierDialog.SetIsPrivate(const AValue: boolean);
855 begin
856   if FIsPrivate=AValue then exit;
857   FIsPrivate:=AValue;
858   ExtraFilesGroupBox.Enabled:=not IsPrivate;
859   ScopeRadioGroup.Enabled:=not IsPrivate;
860   ScopeRadioGroup.ItemIndex:=0;
861 end;
862 
863 procedure TFindRenameIdentifierDialog.FindOrRenameButtonClick(Sender: TObject);
864 var
865   NewIdentifier: String;
866 begin
867   NewIdentifier:=NewEdit.Text;
868   if not IsValidIdent(NewIdentifier) then begin
869     IDEMessageDialog(lisFRIInvalidIdentifier,
870       Format(lisSVUOisNotAValidIdentifier, [NewIdentifier]), mtError, [mbCancel]);
871     ModalResult:=mrNone;
872     exit;
873   end;
874   ModalResult:=mrOk;
875 end;
876 
877 procedure TFindRenameIdentifierDialog.FindRenameIdentifierDialogClose(
878   Sender: TObject; var CloseAction: TCloseAction);
879 begin
880   SaveToConfig;
881   IDEDialogLayoutList.SaveLayout(Self);
882 end;
883 
884 procedure TFindRenameIdentifierDialog.LoadFromConfig;
885 begin
886   LoadFromOptions(MiscellaneousOptions.FindRenameIdentifierOptions);
887 end;
888 
889 procedure TFindRenameIdentifierDialog.SaveToConfig;
890 begin
891   SaveToOptions(MiscellaneousOptions.FindRenameIdentifierOptions);
892 end;
893 
894 procedure TFindRenameIdentifierDialog.LoadFromOptions(
895   Options: TFindRenameIdentifierOptions);
896 begin
897   RenameCheckBox.Checked:=Options.Rename;
898   ExtraFilesEdit.Text:=StringListToText(Options.ExtraFiles,';',true);
899   NewEdit.Text:=Options.RenameTo;
900   ShowResultCheckBox.Checked:=Options.RenameShowResult;
901   ScopeCommentsCheckBox.Checked:=Options.SearchInComments;
902   case Options.Scope of
903   frCurrentUnit: ScopeRadioGroup.ItemIndex:=0;
904   frProject: ScopeRadioGroup.ItemIndex:=1;
905   frOwnerProjectPackage: ScopeRadioGroup.ItemIndex:=2;
906   else
907     ScopeRadioGroup.ItemIndex:=3;
908   end;
909   UpdateRename;
910 end;
911 
912 procedure TFindRenameIdentifierDialog.SaveToOptions(
913   Options: TFindRenameIdentifierOptions);
914 begin
915   Options.Rename:=RenameCheckBox.Checked;
916   if ExtraFilesGroupBox.Enabled then
917     SplitString(ExtraFilesEdit.Text,';',Options.ExtraFiles,true);
918   Options.RenameTo:=NewEdit.Text;
919   Options.RenameShowResult := ShowResultCheckBox.Checked;
920   Options.SearchInComments:=ScopeCommentsCheckBox.Checked;
921   if ScopeRadioGroup.Enabled then
922     case ScopeRadioGroup.ItemIndex of
923     0: Options.Scope:=frCurrentUnit;
924     1: Options.Scope:=frProject;
925     2: Options.Scope:=frOwnerProjectPackage;
926     else Options.Scope:=frAllOpenProjectsAndPackages;
927     end;
928 end;
929 
930 procedure TFindRenameIdentifierDialog.SetIdentifier(
931   const NewIdentifierFilename: string; const NewIdentifierPosition: TPoint);
932 var
933   s: String;
934   ACodeBuffer: TCodeBuffer;
935   ListOfCodeBuffer: TFPList;
936   i: Integer;
937   CurCode: TCodeBuffer;
938   NewIdentifier: String;
939   Tool: TCodeTool;
940   CodeXY: TCodeXYPosition;
941   CleanPos: integer;
942   Node: TCodeTreeNode;
943 begin
944   FIdentifierFilename:=NewIdentifierFilename;
945   FIdentifierPosition:=NewIdentifierPosition;
946   //debugln(['TFindRenameIdentifierDialog.SetIdentifier ',FIdentifierFilename,' ',dbgs(FIdentifierPosition)]);
947   CurrentListBox.Items.Clear;
948   s:=IdentifierFilename
949      +'('+IntToStr(IdentifierPosition.Y)+','+IntToStr(IdentifierPosition.X)+')';
950   CurrentListBox.Items.Add(s);
951   LoadCodeBuffer(ACodeBuffer,IdentifierFileName,[lbfCheckIfText],false);
952   if ACodeBuffer<>nil then begin
953     CodeToolBoss.GetIncludeCodeChain(ACodeBuffer,true,ListOfCodeBuffer);
954     if ListOfCodeBuffer<>nil then begin
955       for i:=0 to ListOfCodeBuffer.Count-1 do begin
956         CurCode:=TCodeBuffer(ListOfCodeBuffer[i]);
957         if CurCode=ACodeBuffer then break;
958         s:=CurCode.Filename;
959         CurrentListBox.Items.Insert(0,s);
960       end;
961       ListOfCodeBuffer.Free;
962     end;
963     if CodeToolBoss.GetIdentifierAt(ACodeBuffer,
964       NewIdentifierPosition.X,NewIdentifierPosition.Y,NewIdentifier) then
965     begin
966       CurrentGroupBox.Caption:=Format(lisFRIIdentifier, [NewIdentifier]);
967       NewEdit.Text:=NewIdentifier;
968     end;
969     // check if in implementation or private section
970     if CodeToolBoss.Explore(ACodeBuffer,Tool,false) then begin
971       CodeXY:=CodeXYPosition(NewIdentifierPosition.X,NewIdentifierPosition.Y,ACodeBuffer);
972       if Tool.CaretToCleanPos(CodeXY,CleanPos)=0 then begin
973         Node:=Tool.BuildSubTreeAndFindDeepestNodeAtPos(CleanPos,false);
974         if (Node=nil)
975         or Node.HasParentOfType(ctnImplementation)
976         or Node.HasParentOfType(ctnClassPrivate) then
977           IsPrivate:=true;
978       end;
979     end;
980   end;
981 end;
982 
983 end.
984 
985 
986