{ *************************************************************************** * * * This source is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This code is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * A copy of the GNU General Public License is available on the World * * Wide Web at . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. * * * *************************************************************************** Author: Mattias Gaertner Abstract: Find all alternative declarations of an identifier. TCarbonControl = class(TCarbonWidget) procedure TCarbonControl.CreateWidget(const AParams: TCreateParams); TCarbonCustomCheckBox = class(TCarbonControl) TCarbonCheckBox = class(TCarbonCustomCheckBox) procedure TCarbonCheckBox.CreateWidget(const AParams: TCreateParams); } unit FindOverloadsDlg; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Laz_AVL_Tree, // LCL LCLProc, LazFileUtils, Forms, Controls, StdCtrls, ButtonPanel, ComCtrls, // codetools FindDeclarationTool, PascalParserTool, CodeTree, CodeCache, CodeToolManager, FindOverloads, // IDE LazIDEIntf, ProjectIntf, SrcEditorIntf, IDEProcs; type { TFOWFile } TFOWFile = class private FCode: TCodeBuffer; FFilename: string; FOnlyInterface: boolean; FScanned: boolean; procedure SetCode(const AValue: TCodeBuffer); procedure SetScanned(const AValue: boolean); public constructor Create(const TheFilename: string); destructor Destroy; override; property Filename: string read FFilename; property OnlyInterface: boolean read FOnlyInterface write FOnlyInterface; property Scanned: boolean read FScanned write SetScanned; property Code: TCodeBuffer read FCode write SetCode; end; TFindOverloadsScope = ( fosProject, fosPackages, fosOtherSources ); TFindOverloadsScopes = set of TFindOverloadsScope; TFOWStage = ( fowsStart, fowsFinished ); { TFindOverloadsWorker } TFindOverloadsWorker = class private FFiles: TAvlTree; FScanFiles: TAvlTree; FStagePosition: integer; FStagePosMax: integer; FStageTitle: string; procedure CollectProjectFiles; procedure CollectPackageFiles; procedure CollectOtherSourceFiles; procedure ScanSomeFiles; procedure ScanFile(AFile: TFOWFile); procedure CollectStartSource; public StartSourceScanned: boolean; Scopes: TFindOverloadsScopes; CompletedScopes: TFindOverloadsScopes; Graph: TDeclarationOverloadsGraph; constructor Create; destructor Destroy; override; procedure Clear; procedure Work; function Done: boolean; procedure StopSearching; function AddFileToScan(const Filename: string; CheckExtension: boolean = true): TFOWFile; function FindFile(const Filename: string): TFOWFile; property Files: TAvlTree read FFiles; // tree of TFindOverloadsWorkerFile property ScanFiles: TAvlTree read FScanFiles;// tree of TFindOverloadsWorkerFile property StageTitle: string read FStageTitle write FStageTitle; property StagePosition: integer read FStagePosition write FStagePosition; property StagePosMax: integer read FStagePosMax write FStagePosMax; end; { TFindOverloadsDialog } TFindOverloadsDialog = class(TForm) ButtonPanel1: TButtonPanel; SearchAllCheckBox: TCheckBox; CurGroupBox: TGroupBox; ResultsProgressBar: TProgressBar; ResultsGroupBox: TGroupBox; CurTreeView: TTreeView; ResultsTreeView: TTreeView; procedure ButtonPanel1Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure OnIdle(Sender: TObject; var Done: Boolean); private FIdleConnected: boolean; fCurTreeViewComplete: boolean; fWorker: TFindOverloadsWorker; procedure SetIdleConnected(const AValue: boolean); procedure UpdateProgress; procedure StopWorking; procedure UpdateCurTreeView; public property Worker: TFindOverloadsWorker read fWorker; property IdleConnected: boolean read FIdleConnected write SetIdleConnected; end; function ShowFindOverloadsDialog: TModalResult; function ShowFindOverloadsDialog(Code: TCodeBuffer; X, Y: integer): TModalResult; function CompareFOWFiles(File1, File2: TFOWFile): integer; function CompareFilenameWithFOWFile(FilenameAnsiString, FOWFile: Pointer): integer; implementation {$R *.lfm} function ShowFindOverloadsDialog: TModalResult; var SrcEdit: TSourceEditorInterface; Code: TCodeBuffer; XY: TPoint; begin SrcEdit:=SourceEditorManagerIntf.ActiveEditor; if SrcEdit=nil then exit(mrCancel); Code:=TCodeBuffer(SrcEdit.CodeToolsBuffer); XY:=SrcEdit.CursorTextXY; Result:=ShowFindOverloadsDialog(Code,XY.X,XY.Y); end; function ShowFindOverloadsDialog(Code: TCodeBuffer; X, Y: integer): TModalResult; var FindOverloadsDialog: TFindOverloadsDialog; Graph: TDeclarationOverloadsGraph; begin if not LazarusIDE.BeginCodeTools then exit; Graph:=nil; FindOverloadsDialog:=nil; CodeToolBoss.ActivateWriteLock; try if not CodeToolBoss.GatherOverloads(Code,X,Y,Graph) then begin LazarusIDE.DoJumpToCodeToolBossError; exit(mrCancel); end; //DebugLn(['ShowFindOverloadsDialog ',Graph.StartCode.Filename,' ',Graph.StartX,',',Graph.StartY]); FindOverloadsDialog:=TFindOverloadsDialog.Create(nil); FindOverloadsDialog.Worker.Graph:=Graph; Result:=FindOverloadsDialog.ShowModal; finally CodeToolBoss.DeactivateWriteLock; FindOverloadsDialog.Free; Graph.Free; end; end; function CompareFOWFiles(File1, File2: TFOWFile): integer; begin Result:=CompareFilenames(File1.Filename,File2.Filename); end; function CompareFilenameWithFOWFile(FilenameAnsiString, FOWFile: Pointer): integer; begin Result:=CompareFilenames(ansistring(FilenameAnsiString),TFOWFile(FOWFile).Filename); end; { TFindOverloadsDialog } procedure TFindOverloadsDialog.FormCreate(Sender: TObject); begin Caption:='Find overloads'; CurGroupBox.Caption:='Current identifier'; ResultsGroupBox.Caption:='Overloads'; SearchAllCheckBox.Caption:='Search in other sources too'; SearchAllCheckBox.ShowHint:=true; SearchAllCheckBox.Hint:='Enable this to search in system sources too. For example the RTL and FCL sources. This can take some minutes on slow machines.'; ButtonPanel1.CancelButton.OnClick:=@ButtonPanel1Click; fWorker:=TFindOverloadsWorker.Create; IdleConnected:=true; UpdateProgress; end; procedure TFindOverloadsDialog.ButtonPanel1Click(Sender: TObject); begin StopWorking; end; procedure TFindOverloadsDialog.FormDestroy(Sender: TObject); begin IdleConnected:=false; FreeAndNil(fWorker); end; procedure TFindOverloadsDialog.OnIdle(Sender: TObject; var Done: Boolean); begin fWorker.Work; Done:=fWorker.Done; if Done then IdleConnected:=false; UpdateProgress; if not fCurTreeViewComplete then UpdateCurTreeView; end; procedure TFindOverloadsDialog.SetIdleConnected(const AValue: boolean); begin if FIdleConnected=AValue then exit; FIdleConnected:=AValue; if FIdleConnected then begin ButtonPanel1.CancelButton.Enabled:=true; ButtonPanel1.CloseButton.Enabled:=false; Application.AddOnIdleHandler(@OnIdle) end else begin ButtonPanel1.CancelButton.Enabled:=false; ButtonPanel1.CloseButton.Enabled:=true; Application.RemoveOnIdleHandler(@OnIdle); end; end; procedure TFindOverloadsDialog.UpdateProgress; begin if Worker.Done then ResultsProgressBar.Visible:=false else begin ResultsProgressBar.Max:=Worker.StagePosMax; ResultsProgressBar.Position:=Worker.StagePosition; ResultsProgressBar.Visible:=true; end; end; procedure TFindOverloadsDialog.StopWorking; begin IdleConnected:=false; Worker.StopSearching; end; procedure TFindOverloadsDialog.UpdateCurTreeView; var s: String; Node: TCodeTreeNode; Tool: TFindDeclarationTool; ParentNode: TCodeTreeNode; begin fCurTreeViewComplete:=true; CurTreeView.BeginUpdate; CurTreeView.Items.Clear; Node:=Worker.Graph.StartCodeNode; Tool:=Worker.Graph.StartTool; if Node<>nil then begin DebugLn(['TFindOverloadsDialog.UpdateCurTreeView ',Node.DescAsString,' ',dbgstr(copy(Tool.Src,Node.StartPos,20))]); // unit name s:=Tool.GetSourceName(false)+': '; // keyword case Node.Desc of ctnEnumIdentifier: s:=s+'enum'; ctnVarDefinition: s:=s+'var'; ctnConstDefinition: s:=s+'const'; ctnTypeDefinition: s:=s+'type'; ctnGenericType: s:=s+'generic'; ctnProperty: s:=s+'property'; ctnProcedure: s:=s+'procedure'; ctnUseUnit: s:=s+'uses'; ctnUnit: s:=s+'unit'; ctnProgram: s:=s+'program'; ctnPackage: s:=s+'package'; ctnLibrary: s:=s+'library'; end; s:=s+' '; // context if Node.Desc<>ctnEnumIdentifier then begin ParentNode:=Node.Parent; while ParentNode<>nil do begin case ParentNode.Desc of ctnTypeDefinition,ctnGenericType: s:=s+Tool.ExtractDefinitionName(Node)+'.'; end; ParentNode:=ParentNode.Parent; end; end; // name case Node.Desc of ctnEnumIdentifier, ctnTypeDefinition, ctnConstDefinition, ctnVarDefinition, ctnGenericType: s:=s+Tool.ExtractDefinitionName(Node); ctnProperty: s:=s+Tool.ExtractPropName(Node,false); ctnProcedure: s:=s+Tool.ExtractProcName(Node,[phpWithoutClassName,phpCommentsToSpace]); ctnUseUnit: s:=s+Tool.ExtractNode(Node,[phpCommentsToSpace]); ctnUnit,ctnProgram,ctnPackage,ctnLibrary: s:=s+Tool.GetSourceName(false); end; // add node CurTreeView.Items.Add(nil,s); end; CurTreeView.EndUpdate; end; { TFOWFile } procedure TFOWFile.SetCode(const AValue: TCodeBuffer); begin if FCode=AValue then exit; FCode:=AValue; end; procedure TFOWFile.SetScanned(const AValue: boolean); begin if FScanned=AValue then exit; FScanned:=AValue; end; constructor TFOWFile.Create(const TheFilename: string); begin FFilename:=TheFilename; FOnlyInterface:=true; end; destructor TFOWFile.Destroy; begin inherited Destroy; end; { TFindOverloadsWorker } procedure TFindOverloadsWorker.CollectProjectFiles; var AProject: TLazProject; i: Integer; ProjFile: TLazProjectFile; begin AProject:=LazarusIDE.ActiveProject; if AProject<>nil then begin for i:=0 to AProject.FileCount-1 do begin ProjFile:=AProject.Files[i]; if ProjFile.IsPartOfProject then AddFileToScan(ProjFile.Filename); end; end; Include(CompletedScopes,fosProject); end; procedure TFindOverloadsWorker.CollectPackageFiles; begin Include(CompletedScopes,fosPackages); end; procedure TFindOverloadsWorker.CollectOtherSourceFiles; begin Include(CompletedScopes,fosOtherSources); end; procedure TFindOverloadsWorker.ScanSomeFiles; const MaxScanTime = 0.3/86400; // 0.3 seconds var StartTime: TDateTime; CurFile: TFOWFile; begin StartTime:=Now; while FScanFiles.Count>0 do begin CurFile:=TFOWFile(FScanFiles.FindLowest.Data); ScanFile(CurFile); if Now-StartTime>=MaxScanTime then break; end; end; procedure TFindOverloadsWorker.ScanFile(AFile: TFOWFile); var Tool: TCodeTool; Filename: String; MainFile: TFOWFile; Code: TCodeBuffer; begin FScanFiles.Remove(AFile); if AFile.Scanned then exit; AFile.Scanned:=true; //DebugLn(['TFindOverloadsWorker.ScanFile File=',AFile.Filename]); // get codetool Filename:=TrimFilename(AFile.Filename); Code:=CodeToolBoss.LoadFile(Filename,true,false); if Code=nil then begin DebugLn(['TFindOverloadsWorker.ScanFile file not readable: ',Filename]); exit; end; Tool:=TCodeTool(CodeToolBoss.GetCodeToolForSource(Code,true,false)); if Tool=nil then begin DebugLn(['TFindOverloadsWorker.ScanFile file not in a unit: ',Filename]); exit; end; // check if AFile is an include file Filename:=Tool.MainFilename; MainFile:=FindFile(Filename); // get unit if MainFile=nil then begin MainFile:=TFOWFile.Create(Filename); FFiles.Add(MainFile); end; if (MainFile<>AFile) and MainFile.Scanned then begin //DebugLn(['TFindOverloadsWorker.ScanFile already scanned: ',Filename]); exit; end; // scan unit FScanFiles.Remove(MainFile); MainFile.Scanned:=true; if not AFile.OnlyInterface then MainFile.OnlyInterface:=false; //DebugLn(['TFindOverloadsWorker.ScanFile scanning: ',Tool.MainFilename]); Graph.ScanToolForIdentifier(Tool,MainFile.OnlyInterface); end; procedure TFindOverloadsWorker.CollectStartSource; var Filename: String; aFile: TFOWFile; begin Filename:=Graph.StartCode.Filename; aFile:=FindFile(Filename); if aFile=nil then begin aFile:=TFOWFile.Create(Filename); aFile.OnlyInterface:=false; FFiles.Add(aFile); FScanFiles.Add(aFile); end; end; function TFindOverloadsWorker.AddFileToScan(const Filename: string; CheckExtension: boolean): TFOWFile; begin if CheckExtension and (not FilenameIsPascalSource(Filename)) then exit(nil); Result:=FindFile(Filename); if Result<>nil then exit; Result:=TFOWFile.Create(Filename); FFiles.Add(Result); FScanFiles.Add(Result); end; function TFindOverloadsWorker.FindFile(const Filename: string): TFOWFile; var AVLNode: TAvlTreeNode; begin AVLNode:=FFiles.FindKey(Pointer(Filename),@CompareFilenameWithFOWFile); if AVLNode<>nil then Result:=TFOWFile(AVLNode.Data) else Result:=nil; end; constructor TFindOverloadsWorker.Create; begin Scopes:=[fosProject,fosPackages]; FFiles:=TAvlTree.Create(TListSortCompare(@CompareFOWFiles)); FScanFiles:=TAvlTree.Create(TListSortCompare(@CompareFOWFiles)); FStagePosMax:=100; end; destructor TFindOverloadsWorker.Destroy; begin Clear; FreeAndNil(FFiles); FreeAndNil(FScanFiles); inherited Destroy; end; procedure TFindOverloadsWorker.Clear; begin FFiles.FreeAndClear; FScanFiles.Clear; FStageTitle:='Finished'; FStagePosition:=0; FStagePosMax:=100; StartSourceScanned:=false; end; procedure TFindOverloadsWorker.Work; begin DebugLn(['TFindOverloadsWorker.Work START']); if FScanFiles.Count>0 then begin // scan files ScanSomeFiles; end else if not StartSourceScanned then begin StageTitle:='Scanning start source ...'; StagePosition:=10; StartSourceScanned:=true; CollectStartSource; end else if (fosProject in Scopes) and not (fosProject in CompletedScopes) then begin // collect project files StageTitle:='Scanning project ...'; StagePosition:=20; CollectProjectFiles; end else if (fosPackages in Scopes) and not (fosPackages in CompletedScopes) then begin // collect package files StageTitle:='Scanning packages ...'; StagePosition:=40; CollectPackageFiles; end else if (fosOtherSources in Scopes) and not (fosOtherSources in CompletedScopes) then begin // collect other sources StageTitle:='Scanning other sources ...'; StagePosition:=60; CollectOtherSourceFiles; end else begin StageTitle:='Finished'; StagePosition:=StagePosMax; Graph.ComputeShortestPaths; end; DebugLn(['TFindOverloadsWorker.Work END ',StageTitle,' ',StagePosition,'/',StagePosMax]); end; function TFindOverloadsWorker.Done: boolean; begin Result:=(Scopes-CompletedScopes=[]) and (FScanFiles.Count=0); end; procedure TFindOverloadsWorker.StopSearching; begin CompletedScopes:=Scopes; FScanFiles.Clear; Graph.ComputeShortestPaths; end; end.