lazarus/ide/findoverloadsdlg.pas

582 lines
16 KiB
ObjectPascal

{
***************************************************************************
* *
* 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 <http://www.gnu.org/copyleft/gpl.html>. 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, AVL_Tree, System.UITypes,
// LCL
Forms, StdCtrls, ButtonPanel, ComCtrls,
// LazUtils
LazFileUtils, LazLoggerBase,
// codetools
FindDeclarationTool, PascalParserTool, CodeTree, CodeCache, CodeToolManager,
FindOverloads,
// BuildIntf
ProjectIntf,
// IdeIntf
SrcEditorIntf, LazIDEIntf,
// IdeConfig
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.