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