codetools: implemented searching abstract methods, added example

git-svn-id: trunk@13188 -
This commit is contained in:
mattias 2007-12-07 00:28:12 +00:00
parent f26448f1f7
commit 363234509c
11 changed files with 631 additions and 54 deletions

3
.gitattributes vendored
View File

@ -86,6 +86,8 @@ components/codetools/examples/addmethod.lpi svneol=native#text/plain
components/codetools/examples/addmethod.lpr svneol=native#text/plain
components/codetools/examples/codecompletion.lpi svneol=native#text/plain
components/codetools/examples/codecompletion.lpr svneol=native#text/plain
components/codetools/examples/completeabstractmethods.lpi svneol=native#text/plain
components/codetools/examples/completeabstractmethods.lpr svneol=native#text/plain
components/codetools/examples/finddeclaration.lpi svneol=native#text/plain
components/codetools/examples/finddeclaration.lpr svneol=native#text/plain
components/codetools/examples/fixdefinitionorder.lpi svneol=native#text/plain
@ -107,6 +109,7 @@ components/codetools/examples/reduceifdefs.lpr svneol=native#text/plain
components/codetools/examples/replaceresourcedirectives.lpi svneol=native#text/plain
components/codetools/examples/replaceresourcedirectives.lpr svneol=native#text/plain
components/codetools/examples/scanexamples/BigLettersUnit.pas svneol=native#text/plain
components/codetools/examples/scanexamples/abstractclass1.pas svneol=native#text/plain
components/codetools/examples/scanexamples/addeventexample.pas svneol=native#text/plain
components/codetools/examples/scanexamples/brokenfilenames.pas svneol=native#text/plain
components/codetools/examples/scanexamples/brokenincfiles.inc svneol=native#text/plain

View File

@ -273,8 +273,15 @@ function RemoveFormComponentFromSource(Source:TSourceLog;
ComponentName, ComponentClassName: string): boolean;
function FindClassAncestorName(const Source, FormClassName: string): string;
// procedure specifiers
function SearchProcSpecifier(const ProcText, Specifier: string;
out SpecifierEndPosition: integer;
NestedComments: boolean = false;
WithSpaceBehindSemicolon: boolean = true;
CaseSensitive: boolean = false): integer;
// code search
function SearchCodeInSource(const Source, Find: string; StartPos:integer;
function SearchCodeInSource(const Source, Find: string; StartPos: integer;
out EndFoundPosition: integer; CaseSensitive: boolean;
NestedComments: boolean = false): integer;
function ReadNextPascalAtom(const Source: string;
@ -987,6 +994,7 @@ function SearchCodeInSource(const Source, Find: string; StartPos: integer;
NestedComments: boolean):integer;
// search pascal atoms of Find in Source
// returns the start pos
// -1 on failure
var
FindLen: Integer;
SrcLen: Integer;
@ -1459,6 +1467,37 @@ begin
while (IsIdentChar[Identifier[Result]]) do inc(Result);
end;
function SearchProcSpecifier(const ProcText, Specifier: string;
out SpecifierEndPosition: integer; NestedComments: boolean;
WithSpaceBehindSemicolon: boolean; CaseSensitive: boolean): integer;
// Result = -1 on failure
// Result = start of Specifier on success
// SpecifierEndPosition on semicolon or >length(ProcText)
// if WithSpaceBehindSemicolon then SpecifierEndPosition is start of next specifier
var
AtomStart: integer;
begin
Result:=SearchCodeInSource(ProcText, Specifier, 1, SpecifierEndPosition,
CaseSensitive, NestedComments);
if Result<1 then exit;
while (SpecifierEndPosition<=length(ProcText))
and (ProcText[SpecifierEndPosition]<>';') do begin
ReadRawNextPascalAtom(ProcText,SpecifierEndPosition,AtomStart,NestedComments);
if AtomStart>length(ProcText) then exit;
if ProcText[AtomStart] in ['[','('] then begin
if not ReadTilPascalBracketClose(ProcText,AtomStart,NestedComments)
then
exit(-1);
SpecifierEndPosition:=AtomStart;
end;
end;
if WithSpaceBehindSemicolon and (SpecifierEndPosition<=length(ProcText)) then
begin
SpecifierEndPosition:=FindLineEndOrCodeAfterPosition(ProcText,
SpecifierEndPosition+1,0,NestedComments);
end;
end;
function ReadNextPascalAtom(const Source:string;
var Position, AtomStart: integer; NestedComments: boolean):string;
var DirectiveName:string;

View File

@ -186,6 +186,10 @@ type
function CompleteCode(CursorPos: TCodeXYPosition; OldTopLine: integer;
out NewPos: TCodeXYPosition; out NewTopLine: integer;
SourceChangeCache: TSourceChangeCache): boolean;
function AddMethods(CursorPos: TCodeXYPosition;// position in class declaration
ListOfPCodeXYPosition: TFPList;
const VirtualToOverride: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
function AddPublishedVariable(const UpperClassName,VarName, VarType: string;
SourceChangeCache: TSourceChangeCache): boolean; override;
function GetRedefinitionNodeText(Node: TCodeTreeNode): string;
@ -5624,6 +5628,148 @@ begin
{$ENDIF}
end;
function TCodeCompletionCodeTool.AddMethods(CursorPos: TCodeXYPosition;
ListOfPCodeXYPosition: TFPList;
const VirtualToOverride: boolean;
SourceChangeCache: TSourceChangeCache): boolean;
var
CleanCursorPos: integer;
CursorNode: TCodeTreeNode;
i: Integer;
CodeXYPos: TCodeXYPosition;
ProcNode: TCodeTreeNode;
NewMethods: TAVLTree;// Tree of TCodeTreeNodeExtension
NewCodeTool: TFindDeclarationTool;
CleanProcCode: String;
FullProcCode: String;
VirtualStartPos: LongInt;
VirtualEndPos: integer;
AbstractStartPos: LongInt;
AbstractEndPos: integer;
VisibilityDesc: TCodeTreeNodeDesc;
NodeExt: TCodeTreeNodeExtension;
AVLNode: TAVLTreeNode;
ProcName: String;
NewClassPart: TNewClassPart;
begin
Result:=false;
if (ListOfPCodeXYPosition=nil) or (ListOfPCodeXYPosition.Count=0) then
exit(true);
if (SourceChangeCache=nil) then
RaiseException('need a SourceChangeCache');
NewMethods:=TAVLTree.Create(@CompareCodeTreeNodeExt);
try
// collect all methods
for i:=0 to ListOfPCodeXYPosition.Count-1 do begin
//get next code position
CodeXYPos:=PCodeXYPosition(ListOfPCodeXYPosition[i])^;
// get codetool for this position
NewCodeTool:=OnGetCodeToolForBuffer(Self,CodeXYPos.Code,true);
if NewCodeTool=nil then begin
DebugLn(['TCodeCompletionCodeTool.AddMethods unit not found for source ',CodeXYPos.Code.Filename,'(',CodeXYPos.Y,',',CodeXYPos.X,')']);
exit;
end;
// parse unit
NewCodeTool.BuildTreeAndGetCleanPos(trAll,CodeXYPos,CleanCursorPos,[]);
// find node at position
ProcNode:=FindDeepestExpandedNodeAtPos(CleanCursorPos,true);
if (ProcNode.Desc<>ctnProcedure)
or (ProcNode.Parent=nil) then begin
MoveCursorToNodeStart(ProcNode);
RaiseException('TCodeCompletionCodeTool.AddMethods source position not a procedure');
end;
// find visibility
VisibilityDesc:=ctnClassPublic;
if ProcNode.Parent.Desc in AllClassBaseSections then
VisibilityDesc:=ProcNode.Parent.Desc;
// extract proc
ProcName:=NewCodeTool.ExtractProcName(ProcNode,[phpWithoutClassName,phpInUpperCase]);
CleanProcCode:=NewCodeTool.ExtractProcHead(ProcNode,[phpWithoutClassName]);
FullProcCode:=NewCodeTool.ExtractProcHead(ProcNode,
[phpWithStart,phpWithoutClassName,phpWithVarModifiers,
phpWithParameterNames,phpWithDefaultValues,phpWithResultType,
phpWithCallingSpecs,phpWithProcModifiers]);
if VirtualToOverride then begin
VirtualStartPos:=SearchProcSpecifier(FullProcCode,'virtual',
VirtualEndPos,NewCodeTool.Scanner.NestedComments,false);
if VirtualStartPos>=1 then begin
// replace virtual with override
FullProcCode:=copy(FullProcCode,1,VirtualStartPos-1)
+'override'
+copy(FullProcCode,VirtualEndPos,length(FullProcCode));
end;
// remove abstract
AbstractStartPos:=SearchProcSpecifier(FullProcCode,'abstract',
AbstractEndPos,NewCodeTool.Scanner.NestedComments,true);
if AbstractStartPos>=1 then begin
// replace virtual with override
FullProcCode:=copy(FullProcCode,1,AbstractStartPos-1)
+copy(FullProcCode,AbstractEndPos,length(FullProcCode));
end;
end;
// add method data
NodeExt:=NodeExtMemManager.NewNode;
NodeExt.Txt:=CleanProcCode;
NodeExt.ExtTxt1:=FullProcCode;
NodeExt.ExtTxt2:=ProcName;
NodeExt.Flags:=VisibilityDesc;
NewMethods.Add(NodeExt);
DebugLn(['TCodeCompletionCodeTool.AddMethods ',i,' CleanProcTxt=',CleanProcCode,' FullProcTxt=',FullProcCode]);
end;
BuildTreeAndGetCleanPos(trAll,CursorPos, CleanCursorPos,[]);
// find node at position
CursorNode:=FindDeepestExpandedNodeAtPos(CleanCursorPos,true);
// if cursor is on type node, find class node
if CursorNode.Desc=ctnTypeDefinition then
CursorNode:=CursorNode.FirstChild
else if CursorNode.Desc=ctnGenericType then
CursorNode:=CursorNode.LastChild;
if (CursorNode=nil) or (CursorNode.Desc<>ctnClass) then begin
DebugLn(['TIdentCompletionTool.AddMethods cursor not in a class']);
exit;
end;
CodeCompleteSrcChgCache:=SourceChangeCache;
CodeCompleteClassNode:=CursorNode;
// add methods
AVLNode:=NewMethods.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
CleanProcCode:=NodeExt.Txt;
FullProcCode:=NodeExt.ExtTxt1;
ProcName:=NodeExt.ExtTxt2;
VisibilityDesc:=TCodeTreeNodeDesc(NodeExt.Flags);
case VisibilityDesc of
ctnClassPrivate: NewClassPart:=ncpPrivateProcs;
ctnClassProtected: NewClassPart:=ncpProtectedProcs;
ctnClassPublic: NewClassPart:=ncpPublicProcs;
ctnClassPublished: NewClassPart:=ncpPublishedProcs;
else NewClassPart:=ncpPublicProcs;
end;
AddClassInsertion(CleanProcCode,FullProcCode,ProcName,NewClassPart);
AVLNode:=NewMethods.FindSuccessor(AVLNode);
end;
// apply changes
if not InsertAllNewClassParts then exit;
if not SourceChangeCache.Apply then
RaiseException(ctsUnableToApplyChanges);
Result:=true;
finally
NodeExtMemManager.DisposeAVLTree(NewMethods);
end;
end;
constructor TCodeCompletionCodeTool.Create;
begin
inherited Create;

View File

@ -165,7 +165,8 @@ type
destructor Destroy; override;
procedure Init(Config: TCodeToolsOptions);
procedure SimpleInit(const ConfigFilename: string);
procedure ActivateWriteLock;
procedure DeactivateWriteLock;
property ChangeStep: integer read FChangeStep;
@ -388,7 +389,9 @@ type
function GetIdentifierAt(Code: TCodeBuffer; X,Y: integer;
var Identifier: string): boolean;
function IdentItemCheckHasChilds(IdentItem: TIdentifierListItem): boolean;
function FindAbstractMethods(Code: TCodeBuffer; X,Y: integer;
out ListOfPCodeXYPosition: TFPList): boolean;
// rename identifier
function FindReferences(IdentifierCode: TCodeBuffer;
X, Y: integer; TargetCode: TCodeBuffer; SkipComments: boolean;
@ -438,6 +441,9 @@ type
function CompleteCode(Code: TCodeBuffer; X,Y,TopLine: integer;
out NewCode: TCodeBuffer;
out NewX, NewY, NewTopLine: integer): boolean;
function AddMethods(Code: TCodeBuffer; X,Y: integer;
ListOfPCodeXYPosition: TFPList;
const VirtualToOverride: boolean): boolean;
function FindRedefinitions(Code: TCodeBuffer;
out TreeOfCodeTreeNodeExt: TAVLTree; WithEnums: boolean): boolean;
function RemoveRedefinitions(Code: TCodeBuffer;
@ -867,6 +873,40 @@ begin
'$(#LazarusSrcDir)','$(#LCLWidgetType)','$(#ProjectDir)',nil));
end;
procedure TCodeToolManager.SimpleInit(const ConfigFilename: string);
var
Options: TCodeToolsOptions;
begin
// setup the Options
Options:=TCodeToolsOptions.Create;
try
// To not parse the FPC sources every time, the options are saved to a file.
if FileExists(ConfigFilename) then
Options.LoadFromFile(ConfigFilename);
// setup your paths
DebugLn(['TCodeToolManager.SimpleInit Config=',ConfigFilename]);
if FileExists(ConfigFilename) then
Options.LoadFromFile(ConfigFilename);
Options.InitWithEnvironmentVariables;
if Options.FPCSrcDir='' then
Options.FPCSrcDir:=ExpandFileName('~/freepascal/fpc');
if Options.LazarusSrcDir='' then
Options.LazarusSrcDir:=ExpandFileName('~/pascal/lazarus');
DebugLn(['TCodeToolManager.SimpleInit PP=',Options.FPCPath,' FPCDIR=',Options.FPCSrcDir,' LAZARUSDIR=',Options.LazarusSrcDir]);
// init the codetools
if not Options.UnitLinkListValid then
debugln('Scanning FPC sources may take a while ...');
CodeToolBoss.Init(Options);
// save the options and the FPC unit links results.
Options.SaveToFile(ConfigFilename);
finally
Options.Free;
end;
end;
procedure TCodeToolManager.BeginUpdate;
begin
SourceChangeCache.BeginUpdate;
@ -1905,7 +1945,7 @@ begin
{$ENDIF}
try
Result:=FCurCodeTool.GatherIdentifiers(CursorPos,IdentifierList,
SourceChangeCache.BeautifyCodeOptions);
SourceChangeCache.BeautifyCodeOptions);
except
on e: Exception do HandleException(e);
end;
@ -1948,6 +1988,27 @@ begin
end;
end;
function TCodeToolManager.FindAbstractMethods(Code: TCodeBuffer; X, Y: integer;
out ListOfPCodeXYPosition: TFPList): boolean;
var
CursorPos: TCodeXYPosition;
begin
{$IFDEF CTDEBUG}
DebugLn('TCodeToolManager.FindRedefinitions A ',Code.Filename);
{$ENDIF}
Result:=false;
ListOfPCodeXYPosition:=nil;
if not InitCurCodeTool(Code) then exit;
CursorPos.X:=X;
CursorPos.Y:=Y;
CursorPos.Code:=Code;
try
Result:=FCurCodeTool.FindAbstractMethods(CursorPos,ListOfPCodeXYPosition);
except
on e: Exception do Result:=HandleException(e);
end;
end;
function TCodeToolManager.FindReferences(IdentifierCode: TCodeBuffer;
X, Y: integer; TargetCode: TCodeBuffer; SkipComments: boolean;
var ListOfPCodeXYPosition: TFPList): boolean;
@ -2939,6 +3000,28 @@ begin
end;
end;
function TCodeToolManager.AddMethods(Code: TCodeBuffer; X, Y: integer;
ListOfPCodeXYPosition: TFPList; const VirtualToOverride: boolean
): boolean;
var
CursorPos: TCodeXYPosition;
begin
{$IFDEF CTDEBUG}
DebugLn('TCodeToolManager.AddMethods A ',Code.Filename);
{$ENDIF}
Result:=false;
if not InitCurCodeTool(Code) then exit;
CursorPos.X:=X;
CursorPos.Y:=Y;
CursorPos.Code:=Code;
try
Result:=FCurCodeTool.AddMethods(CursorPos,ListOfPCodeXYPosition,
VirtualToOverride,SourceChangeCache);
except
on e: Exception do Result:=HandleException(e);
end;
end;
function TCodeToolManager.FindRedefinitions(Code: TCodeBuffer; out
TreeOfCodeTreeNodeExt: TAVLTree; WithEnums: boolean): boolean;
begin

View File

@ -253,9 +253,14 @@ end;
procedure TCodeToolsOptions.InitWithEnvironmentVariables;
begin
FPCPath:=FindDefaultCompilerFilename;
FPCSrcDir:=GetEnvironmentVariable('FPCDIR');
LazarusSrcDir:=GetEnvironmentVariable('LAZARUSDIR');
if FPCPath='' then
FPCPath:=FindDefaultCompilerFilename;
if FPCPath='' then
FPCPath:=GetEnvironmentVariable('PP');
if FPCSrcDir='' then
FPCSrcDir:=GetEnvironmentVariable('FPCDIR');
if LazarusSrcDir='' then
LazarusSrcDir:=GetEnvironmentVariable('LAZARUSDIR');
end;
function TCodeToolsOptions.FindDefaultCompilerFilename: string;

View File

@ -201,7 +201,7 @@ type
function CleanPosToCodePos(CleanPos: integer;
out CodePos:TCodePosition): boolean; // true=ok, false=invalid CleanPos
function CleanPosToCaret(CleanPos: integer;
var Caret:TCodeXYPosition): boolean; // true=ok, false=invalid CleanPos
out Caret:TCodeXYPosition): boolean; // true=ok, false=invalid CleanPos
function CleanPosToCaretAndTopLine(CleanPos: integer;
var Caret:TCodeXYPosition; var NewTopLine: integer): boolean; // true=ok, false=invalid CleanPos
function CleanPosToStr(CleanPos: integer): string;
@ -2142,10 +2142,11 @@ begin
end;
function TCustomCodeTool.CleanPosToCaret(CleanPos: integer;
var Caret:TCodeXYPosition): boolean; // true=ok, false=invalid CleanPos
out Caret:TCodeXYPosition): boolean; // true=ok, false=invalid CleanPos
var p: integer;
Code: Pointer;
begin
Caret:=CleanCodeXYPosition;
Result:=Scanner.CleanedPosToCursor(CleanPos,p,Code);
if Result then begin
Caret.Code:=TCodeBuffer(Code);

View File

@ -0,0 +1,53 @@
<?xml version="1.0"?>
<CONFIG>
<ProjectOptions>
<PathDelim Value="/"/>
<Version Value="6"/>
<General>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
<IconPath Value="./"/>
<TargetFileExt Value=""/>
</General>
<PublishOptions>
<Version Value="2"/>
<IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
<ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/>
</PublishOptions>
<RunParams>
<local>
<FormatVersion Value="1"/>
<LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/>
</local>
</RunParams>
<RequiredPackages Count="1">
<Item1>
<PackageName Value="CodeTools"/>
</Item1>
</RequiredPackages>
<Units Count="2">
<Unit0>
<Filename Value="completeabstractmethods.lpr"/>
<IsPartOfProject Value="True"/>
<UnitName Value="CompleteAbstractMethods"/>
</Unit0>
<Unit1>
<Filename Value="scanexamples/abstractclass1.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="AbstractClass1"/>
</Unit1>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="5"/>
<SearchPaths>
<OtherUnitFiles Value="scanexamples/"/>
</SearchPaths>
<CodeGeneration>
<Generate Value="Faster"/>
</CodeGeneration>
<Other>
<CompilerPath Value="$(CompPath)"/>
</Other>
</CompilerOptions>
</CONFIG>

View File

@ -0,0 +1,103 @@
{
***************************************************************************
* *
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
***************************************************************************
Author: Mattias Gaertner
Abstract:
Demonstration, how to setup the codetools, FPC and Lazarus Source
directory to find abstract methods not yet implemented in a class.
}
program CompleteAbstractMethods;
{$mode objfpc}{$H+}
uses
Classes, SysUtils, CodeCache, CodeToolManager, CodeTree, CodeAtom,
AbstractClass1;
const
ConfigFilename = 'codetools.config';
var
Code: TCodeBuffer;
X: Integer;
Y: Integer;
Filename: String;
ListOfPCodeXYPosition: TFPList;
i: Integer;
CodeXYPos: TCodeXYPosition;
begin
if (ParamCount>=1) and (Paramcount<>3) then begin
writeln('Usage:');
writeln(' ',ParamStr(0));
writeln(' ',ParamStr(0),' <filename> <X> <Y>');
end;
// setup the Options
ListOfPCodeXYPosition:=nil;
try
CodeToolBoss.SimpleInit(ConfigFilename);
// Example: complete identifier s,
// by adding a local variable declaration (var s: string)
Filename:=GetCurrentDir+'/scanexamples/abstractclass1.pas';
X:=3;
Y:=18;
if (ParamCount>=3) then begin
Filename:=ExpandFileName(ParamStr(1));
X:=StrToInt(ParamStr(2));
Y:=StrToInt(ParamStr(3));
end;
// Step 1: load the file
Code:=CodeToolBoss.LoadFile(Filename,false,false);
if Code=nil then
raise Exception.Create('loading failed '+Filename);
// find abstract methods
if CodeToolBoss.FindAbstractMethods(Code,X,Y,ListOfPCodeXYPosition)
then begin
writeln('FindAbstractMethods succeeded: ');
if ListOfPCodeXYPosition<>nil then begin
for i:=0 to ListOfPCodeXYPosition.Count-1 do begin
CodeXYPos:=PCodeXYPosition(ListOfPCodeXYPosition[i])^;
writeln(i,' ',CodeXYPos.Code.Filename,'(',CodeXYPos.Y,',',CodeXYPos.X,')');
end;
end;
end else begin
writeln('FindAbstractMethods failed: ',CodeToolBoss.ErrorMessage);
end;
if CodeToolBoss.AddMethods(Code,X,Y,ListOfPCodeXYPosition,true)
then begin
writeln('AddMethods succeeded: ');
writeln(Code.Source);
end else begin
writeln('AddMethods failed: ',CodeToolBoss.ErrorMessage);
end;
except
on E: Exception do begin
writeln(E.Message);
end;
end;
CodeToolBoss.FreeListOfPCodeXYPosition(ListOfPCodeXYPosition);
end.

View File

@ -0,0 +1,23 @@
unit AbstractClass1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils;
type
TAbstractClass = class
public
procedure Increase; virtual; abstract;
procedure Decrease; virtual; abstract;
end;
TMyClass = class(TAbstractClass)
end;
implementation
end.

View File

@ -270,6 +270,7 @@ type
ClassAndAncestors: TFPList;// list of PCodeXYPosition
FoundPublicProperties: TAVLTree;// tree of PChar (pointing to the
// property names in source)
FoundMethods: TAVLTree;// tree of TCodeTreeNodeExtension Txt=clean text
protected
CurrentIdentifierList: TIdentifierList;
CurrentContexts: TCodeContextInfo;
@ -294,13 +295,19 @@ type
function CollectAllContexts(Params: TFindDeclarationParams;
const FoundContext: TFindContext): TIdentifierFoundResult;
procedure AddCollectionContext(Tool: TFindDeclarationTool;
Node: TCodeTreeNode);
Node: TCodeTreeNode);
procedure InitFoundMethods;
procedure ClearFoundMethods;
function CollectMethods(Params: TFindDeclarationParams;
const FoundContext: TFindContext): TIdentifierFoundResult;
public
function GatherIdentifiers(const CursorPos: TCodeXYPosition;
var IdentifierList: TIdentifierList;
BeautifyCodeOptions: TBeautifyCodeOptions): boolean;
function FindCodeContext(const CursorPos: TCodeXYPosition;
out CodeContexts: TCodeContextInfo): boolean;
function FindAbstractMethods(const CursorPos: TCodeXYPosition;
out ListOfPCodeXYPosition: TFPList): boolean;
end;
const
@ -1297,6 +1304,53 @@ begin
//DebugLn('TIdentCompletionTool.AddCollectionContext ',Node.DescAsString,' ',ExtractNode(Node,[]));
end;
procedure TIdentCompletionTool.InitFoundMethods;
begin
if FoundMethods<>nil then ClearFoundMethods;
FoundMethods:=TAVLTree.Create(@CompareCodeTreeNodeExt);
end;
procedure TIdentCompletionTool.ClearFoundMethods;
begin
if FoundMethods=nil then exit;
NodeExtMemManager.DisposeAVLTree(FoundMethods);
FoundMethods:=nil;
end;
function TIdentCompletionTool.CollectMethods(
Params: TFindDeclarationParams; const FoundContext: TFindContext
): TIdentifierFoundResult;
var
ProcText: String;
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
begin
// proceed searching ...
Result:=ifrProceedSearch;
{$IFDEF ShowFoundIdents}
//if FoundContext.Tool=Self then
DebugLn('::: COLLECT IDENT ',FoundContext.Node.DescAsString,
' "',StringToPascalConst(copy(FoundContext.Tool.Src,FoundContext.Node.StartPos,50)),'"');
{$ENDIF}
if FoundContext.Node.Desc=ctnProcedure then begin
ProcText:=FoundContext.Tool.ExtractProcHead(FoundContext.Node,
[phpWithoutClassKeyword]);
AVLNode:=FindCodeTreeNodeExtAVLNode(FoundMethods,ProcText);
if AVLNode<>nil then begin
// method is overriden => ignore
end else begin
// new method
NodeExt:=NodeExtMemManager.NewNode;
NodeExt.Node:=FoundContext.Node;
NodeExt.Data:=FoundContext.Tool;
NodeExt.Txt:=ProcText;
FoundMethods.Add(NodeExt);
end;
end;
end;
function TIdentCompletionTool.GatherIdentifiers(
const CursorPos: TCodeXYPosition; var IdentifierList: TIdentifierList;
BeautifyCodeOptions: TBeautifyCodeOptions): boolean;
@ -1602,19 +1656,85 @@ begin
end;
end;
function TIdentCompletionTool.FindAbstractMethods(
const CursorPos: TCodeXYPosition; out ListOfPCodeXYPosition: TFPList
): boolean;
var
CleanCursorPos: integer;
CursorNode: TCodeTreeNode;
Params: TFindDeclarationParams;
AVLNode: TAVLTreeNode;
NodeExt: TCodeTreeNodeExtension;
ATool: TFindDeclarationTool;
ANode: TCodeTreeNode;
ProcXYPos: TCodeXYPosition;
begin
Result:=false;
ListOfPCodeXYPosition:=nil;
ActivateGlobalWriteLock;
Params:=nil;
try
BuildTreeAndGetCleanPos(trTillCursor,CursorPos,CleanCursorPos,
[{$IFNDEF DisableIgnoreErrorAfter}btSetIgnoreErrorPos{$ENDIF}]);
// find node at position
CursorNode:=FindDeepestExpandedNodeAtPos(CleanCursorPos,true);
// if cursor is on type node, find class node
if CursorNode.Desc=ctnTypeDefinition then
CursorNode:=CursorNode.FirstChild
else if CursorNode.Desc=ctnGenericType then
CursorNode:=CursorNode.LastChild;
if (CursorNode=nil) or (CursorNode.Desc<>ctnClass)
or ((CursorNode.SubDesc and ctnsForwardDeclaration)>0) then begin
DebugLn(['TIdentCompletionTool.FindAbstractMethods cursor not in a class']);
exit;
end;
Params:=TFindDeclarationParams.Create;
// gather all identifiers in context
Params.ContextNode:=CursorNode;
Params.SetIdentifier(Self,nil,@CollectMethods);
Params.Flags:=[fdfSearchInAncestors,fdfCollect,fdfFindVariable];
InitFoundMethods;
FindIdentifierInContext(Params);
if FoundMethods<>nil then begin
AVLNode:=FoundMethods.FindLowest;
while AVLNode<>nil do begin
NodeExt:=TCodeTreeNodeExtension(AVLNode.Data);
ANode:=NodeExt.Node;
ATool:=TFindDeclarationTool(NodeExt.Data);
//DebugLn(['TIdentCompletionTool.FindAbstractMethods ',NodeExt.Txt,' ',ATool.ProcNodeHasSpecifier(ANode,psABSTRACT)]);
if ATool.ProcNodeHasSpecifier(ANode,psABSTRACT) then begin
if not ATool.CleanPosToCaret(ANode.StartPos,ProcXYPos) then
raise Exception.Create('TIdentCompletionTool.FindAbstractMethods inconsistency');
AddCodePosition(ListOfPCodeXYPosition,ProcXYPos);
end;
AVLNode:=FoundMethods.FindSuccessor(AVLNode);
end;
end;
Result:=true;
finally
Params.Free;
ClearFoundMethods;
DeactivateGlobalWriteLock;
end;
end;
{ TIdentifierListItem }
function TIdentifierListItem.GetParamList: string;
var
CurNode: TCodeTreeNode;
ANode: TCodeTreeNode;
begin
if not (iliParamListValid in Flags) then begin
// Note: if you implement param lists for other than ctnProcedure, check
// CompareParamList
CurNode:=Node;
if (CurNode<>nil) and (CurNode.Desc=ctnProcedure) then begin
FParamList:=Tool.ExtractProcHead(CurNode,
ANode:=Node;
if (ANode<>nil) and (ANode.Desc=ctnProcedure) then begin
FParamList:=Tool.ExtractProcHead(ANode,
[phpWithoutClassKeyword,phpWithoutClassName,
phpWithoutName,phpInUpperCase]);
//debugln('TIdentifierListItem.GetParamList A ',GetIdentifier(Identifier),' ',Tool.MainFilename,' ',dbgs(CurNode.StartPos));
@ -1664,7 +1784,7 @@ end;
function TIdentifierListItem.AsString: string;
var
CurNode: TCodeTreeNode;
ANode: TCodeTreeNode;
begin
Result:=IdentifierCompatibilityNames[Compatibility];
if HasChilds then
@ -1676,19 +1796,19 @@ begin
Result:=Result+' Lvl='+IntToStr(Level);
if Tool<>nil then
Result:=Result+' File='+Tool.MainFilename;
CurNode:=Node;
if CurNode<>nil then
Result:=Result+' Node='+CurNode.DescAsString
+' "'+StringToPascalConst(copy(Tool.Src,CurNode.StartPos,50))+'"';
ANode:=Node;
if ANode<>nil then
Result:=Result+' Node='+ANode.DescAsString
+' "'+StringToPascalConst(copy(Tool.Src,ANode.StartPos,50))+'"';
end;
function TIdentifierListItem.GetDesc: TCodeTreeNodeDesc;
var
CurNode: TCodeTreeNode;
ANode: TCodeTreeNode;
begin
CurNode:=Node;
if CurNode<>nil then
Result:=CurNode.Desc
ANode:=Node;
if ANode<>nil then
Result:=ANode.Desc
else
Result:=DefaultDesc;
end;
@ -1712,32 +1832,32 @@ end;
function TIdentifierListItem.IsProcNodeWithParams: boolean;
var
CurNode: TCodeTreeNode;
ANode: TCodeTreeNode;
begin
CurNode:=Node;
Result:=(CurNode<>nil) and Tool.ProcNodeHasParamList(CurNode);
ANode:=Node;
Result:=(ANode<>nil) and Tool.ProcNodeHasParamList(ANode);
end;
function TIdentifierListItem.IsPropertyWithParams: boolean;
var
CurNode: TCodeTreeNode;
ANode: TCodeTreeNode;
begin
CurNode:=Node;
Result:=(CurNode<>nil) and Tool.PropertyNodeHasParamList(CurNode);
ANode:=Node;
Result:=(ANode<>nil) and Tool.PropertyNodeHasParamList(ANode);
end;
function TIdentifierListItem.CheckHasChilds: boolean;
// returns true if test was successful
var
CurNode: TCodeTreeNode;
ANode: TCodeTreeNode;
begin
Result:=false;
if GetDesc in [ctnClass,ctnRecordType,ctnClassInterface] then begin
Result:=true;
exit;
end;
CurNode:=Node;
if CurNode=nil then exit;
ANode:=Node;
if ANode=nil then exit;
UpdateBaseContext;
if (BaseExprType.Desc=xtContext)
and (BaseExprType.Context.Node<>nil)
@ -1749,16 +1869,16 @@ end;
function TIdentifierListItem.CanBeAssigned: boolean;
var
CurNode: TCodeTreeNode;
ANode: TCodeTreeNode;
begin
Result:=false;
CurNode:=Node;
if (CurNode=nil) then exit;
ANode:=Node;
if (ANode=nil) then exit;
if (GetDesc=ctnVarDefinition) then
Result:=true;
if (CurNode.Desc in [ctnProperty,ctnGlobalProperty]) then begin
if Tool.PropertyHasSpecifier(CurNode,'write') then exit(true);
if Tool.PropNodeIsTypeLess(CurNode) then begin
if (ANode.Desc in [ctnProperty,ctnGlobalProperty]) then begin
if Tool.PropertyHasSpecifier(ANode,'write') then exit(true);
if Tool.PropNodeIsTypeLess(ANode) then begin
exit(true);// ToDo: search the real property definition
end;
end;
@ -1767,17 +1887,17 @@ end;
procedure TIdentifierListItem.UpdateBaseContext;
var
Params: TFindDeclarationParams;
CurNode: TCodeTreeNode;
ANode: TCodeTreeNode;
begin
if (iliBaseExprTypeValid in Flags) then exit;
BaseExprType:=CleanExpressionType;
BaseExprType.Desc:=xtNone;
CurNode:=Node;
if (CurNode<>nil) and (Tool<>nil) then begin
ANode:=Node;
if (ANode<>nil) and (Tool<>nil) then begin
Tool.ActivateGlobalWriteLock;
Params:=TFindDeclarationParams.Create;
try
BaseExprType.Context:=Tool.FindBaseTypeOfNode(Params,CurNode);
BaseExprType.Context:=Tool.FindBaseTypeOfNode(Params,ANode);
if (BaseExprType.Context.Node<>nil) then
BaseExprType.Desc:=xtContext;
finally
@ -1795,11 +1915,11 @@ end;
function TIdentifierListItem.IsFunction: boolean;
var
CurNode: TCodeTreeNode;
ANode: TCodeTreeNode;
begin
if not (iliIsFunctionValid in Flags) then begin
CurNode:=Node;
if (CurNode<>nil) and Tool.NodeIsFunction(CurNode) then
ANode:=Node;
if (ANode<>nil) and Tool.NodeIsFunction(ANode) then
Include(Flags,iliIsFunction);
Include(Flags,iliIsFunctionValid);
end;
@ -1808,12 +1928,12 @@ end;
function TIdentifierListItem.IsAbstractMethod: boolean;
var
CurNode: TCodeTreeNode;
ANode: TCodeTreeNode;
begin
if not (iliIsAbstractMethodValid in Flags) then begin
CurNode:=Node;
if (CurNode<>nil)
and Tool.MoveCursorToProcSpecifier(CurNode,psABSTRACT) then
ANode:=Node;
if (ANode<>nil)
and Tool.ProcNodeHasSpecifier(ANode,psABSTRACT) then
Include(Flags,iliIsAbstractMethod);
Include(Flags,iliIsAbstractMethodValid);
end;
@ -1837,16 +1957,16 @@ end;
function TIdentifierListItem.CompareParamList(CompareItem: TIdentifierListItem
): integer;
var
CurNode: TCodeTreeNode;
ANode: TCodeTreeNode;
CmpNode: TCodeTreeNode;
begin
Result:=0;
if Self=CompareItem then exit;
CurNode:=Node;
ANode:=Node;
CmpNode:=CompareItem.Node;
if (CurNode=CmpNode) then exit;
if (CurNode=nil) or (CmpNode=nil) then exit;
if (CurNode.Desc<>ctnProcedure) or (CmpNode.Desc<>ctnProcedure) then
if (ANode=CmpNode) then exit;
if (ANode=nil) or (CmpNode=nil) then exit;
if (ANode.Desc<>ctnProcedure) or (CmpNode.Desc<>ctnProcedure) then
exit;
{DbgOut('TIdentifierListItem.CompareParamList ',GetIdentifier(Identifier),'=',GetIdentifier(CompareItem.Identifier));
if Node<>nil then

View File

@ -651,6 +651,7 @@ procedure TPascalReaderTool.MoveCursorToFirstProcSpecifier(
// After the call,
// CurPos will stand on the first proc specifier or on a semicolon
begin
//DebugLn(['TPascalReaderTool.MoveCursorToFirstProcSpecifier ',ProcNode.DescAsString,' ',ProcNode.StartPos]);
if (ProcNode=nil) or (ProcNode.Desc<>ctnProcedure) then begin
SaveRaiseException('Internal Error in'
+' TPascalParserTool.MoveCursorFirstProcSpecifier: '